diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b4785630e..8e994a6ca8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -659,6 +659,46 @@ jobs: - uses: ./.github/actions/playwright-chromium - run: pnpm test:playtest:ci + strata: + needs: [scope, build-artifacts] + if: ${{ needs.scope.outputs.selection == 'full' && fromJSON(needs.scope.outputs.plan).jobs.strata.required }} + runs-on: ${{ (github.event.pull_request.head.repo.fork || !vars.TN_RUNNER) && 'ubuntu-24.04' || vars.TN_RUNNER }} + timeout-minutes: 60 + env: + TN_CI_SHA: ${{ needs.scope.outputs.candidate_sha }} + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ needs.scope.outputs.candidate_sha || github.sha }} + - uses: ./.github/actions/pnpm + - uses: actions/setup-node@v7 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: | + pnpm-lock.yaml + packages/create-threenative/templates/*/package.json + - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/workspace-dist + with: + shared-artifact: workspace-packages + save-bundles: "false" + - uses: ./.github/actions/playwright-chromium + - name: Generate the terrain example's ignored world source + run: pnpm --filter strata-terrain-preview bake + - name: Run strata web playtest + run: sh scripts/xvfb.sh pnpm --filter strata-terrain-preview test:terrain:web + - name: Run strata consumer verification + run: pnpm --filter strata-terrain-preview test:consumer + - name: Run strata editor verification + run: sh scripts/xvfb.sh pnpm --filter strata-terrain-preview test:terrain:editor + - name: Run strata authored-world reproduction + run: sh scripts/xvfb.sh pnpm --filter strata-terrain-preview test:terrain:authored + - name: Run strata custom-art swap + run: sh scripts/xvfb.sh pnpm --filter strata-terrain-preview test:terrain:custom + - name: Run strata static-world export + run: sh scripts/xvfb.sh pnpm --filter strata-terrain-preview test:terrain:export + golden-path-template: needs: [scope, build-artifacts] if: ${{ (needs.scope.outputs.selection == 'full' || needs.scope.outputs.selection == 'template') }} @@ -1010,6 +1050,12 @@ jobs: node-version: 20 cache: pnpm - run: pnpm install --frozen-lockfile + # Check before restoring or publishing workspace builds, including the develop warm lane. + # The pinned upstream archive is downloaded fresh; cache: pnpm does not cache its npm build. + # Bash time reports the download + own-build wall cost and preserves failures (PRD-593 AC-2). + - name: Verify the generated Three.js patch has not drifted + shell: bash + run: time pnpm --filter @threenative/core patch:generate --check - uses: ./.github/actions/workspace-dist - run: pnpm --filter abyss-framework build - name: Enforce entity registry boundaries @@ -1243,7 +1289,7 @@ jobs: # The only required context after protected-develop cutover. Explicit exemptions are not passes. # Legacy main contexts stay in place until the owner changes protection after full qualification. ci-required: - needs: [scope, integration, native-platforms, typecheck, lint, test, test-unit, test-native, test-browser, test-playtest, golden-path-template, template-nonvisual, golden-path, benchmark, build, build-artifacts, budgets, performance-contracts, supply-chain] + needs: [scope, integration, native-platforms, typecheck, lint, test, test-unit, test-native, test-browser, test-playtest, strata, golden-path-template, template-nonvisual, golden-path, benchmark, build, build-artifacts, budgets, performance-contracts, supply-chain] if: ${{ always() && !github.event.pull_request.draft }} runs-on: ${{ (github.event.pull_request.head.repo.fork || !vars.TN_RUNNER_LIGHT) && 'ubuntu-24.04' || vars.TN_RUNNER_LIGHT }} timeout-minutes: 5 @@ -1297,6 +1343,7 @@ jobs: - test-native - test-browser - test-playtest + - strata - golden-path-template - template-nonvisual - golden-path diff --git a/.gitignore b/.gitignore index 1ca5b32c9d..28b4c09a18 100644 --- a/.gitignore +++ b/.gitignore @@ -201,6 +201,8 @@ examples/auto-lod/public/ # Generated by prepack from packages/core/patches (the only tracked copy). packages/create-threenative/template-assets/patches/ +# scripts/stage-terrain-kits.mjs writes these kits from packages/terrain/starter, the only tracked copy. +/packages/create-threenative/template-assets/terrain/ # Licensed (non-redistributable) local art for review; never committed examples/strata-terrain-preview/local-assets/ @@ -208,3 +210,6 @@ examples/native-css-hud/corpus/out/ examples/native-css-hud/corpus/out-interaction/ examples/native-css-hud/corpus/out-desktop/ examples/native-css-hud/bench/out/ + +# Forest kit headless bake output (authored recipe and assets remain tracked). +/packages/terrain/starter/forest/world/ diff --git a/AGENTS.md b/AGENTS.md index 5545a897f7..2f53ca70ef 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,7 @@ One source, two runtimes: browser WebGPU and an owned C++ host for desktop/Andro | --- | --- | | Anything that **decides how it looks** — materials, shaders, TSL, lights, tonemapping, post, framing — and any preset or default picking one | `templates/*/src/render/`, as generated user source | | The **mechanism** that puts it on screen — pooling, lifetime, billboarding, instancing, dispatch, culling — with every appearance parameter from the game; plumbing every game repeats | `packages/core/src/` | +| Optional terrain authoring and terrain-only editor companions, within the CHARTER allowance | `packages/terrain/`; appearance remains generated game source | | Physics or navigation (WASM dep) · React HUD/menu bindings · C++ host, platform bring-up, native systems · scenario harness and assertions | `packages/physics/` · `ui/` · `runtime-native/` · `playtest/` | | Gameplay, never in a package · proof that any of it works | an example or template · `/__tests__/*.spec.ts` **and** a playtest scenario | diff --git a/CHANGELOG.md b/CHANGELOG.md index 605207d2f2..7a9dad73cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,18 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Breaking +- `@threenative/terrain/export` removes `exportWorldGLB`. Prepare the browser scene with + `prepareWorldGLTF`, then encode it in Node with `cookTerrainWorld({ preparedWorld: prepared.json })` + from `@threenative/assets`; the editor already uses this shared cook. + +- `@threenative/assets`'s `cookTerrainWorld` declaration adds a prepared-world JSON overload + for the terrain editor's portable GLB export. Existing state/species/out calls remain valid; + the new overload embeds the game's frozen scene without instancing or filesystem output. + +- `@threenative/terrain`'s `makeExport` declaration adds a `"project"` overload accepting + a null terrain state, so recipes can be exported before the first evaluation. Existing + calls remain valid; non-project exports still require an evaluated state. + - The unreleased terrain editor's `mountTerrainEditor` view factory receives `(host, controller)`. Its `IEditorView` implements `setDocument(document, revision)` so metadata edits can update the scene without dispatching terrain evaluation; diff --git a/CLAUDE.md b/CLAUDE.md index cfc4212ef4..6cae8adc84 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,6 +37,7 @@ One source, two runtimes: browser WebGPU and an owned C++ host for desktop/Andro | --- | --- | | Anything that **decides how it looks** — materials, shaders, TSL, lights, tonemapping, post, framing — and any preset or default picking one | `templates/*/src/render/`, as generated user source | | The **mechanism** that puts it on screen — pooling, lifetime, billboarding, instancing, dispatch, culling — with every appearance parameter from the game; plumbing every game repeats | `packages/core/src/` | +| Optional terrain authoring and terrain-only editor companions, within the CHARTER allowance | `packages/terrain/`; appearance remains generated game source | | Physics or navigation (WASM dep) · React HUD/menu bindings · C++ host, platform bring-up, native systems · scenario harness and assertions | `packages/physics/` · `ui/` · `runtime-native/` · `playtest/` | | Gameplay, never in a package · proof that any of it works | an example or template · `/__tests__/*.spec.ts` **and** a playtest scenario | diff --git a/biome.json b/biome.json index 3986db9351..05b69c4de8 100644 --- a/biome.json +++ b/biome.json @@ -11,6 +11,7 @@ "packages/metahuman/wasm/**", "artifacts/**", "examples/strata-terrain-preview/src/world/*.json", + "examples/strata-terrain-preview/assets/world/*.json", "examples/strata-terrain-preview/local-assets/**", "build/**", "release-native/**", @@ -89,6 +90,10 @@ } }, "overrides": [ + { + "include": ["examples/procedural-animals/src/render/donor/core/motion/quadruped.js"], + "javascript": { "formatter": { "quoteProperties": "preserve" } } + }, { "include": ["**/*.ts", "**/*.tsx"], "linter": { "rules": { "style": { "useNamingConvention": "off" } } } diff --git a/docs/PRDs/done/PRD-587-the-terrain-editor-never-drops-an-edit.md b/docs/PRDs/done/PRD-587-the-terrain-editor-never-drops-an-edit.md new file mode 100644 index 0000000000..41a0000652 --- /dev/null +++ b/docs/PRDs/done/PRD-587-the-terrain-editor-never-drops-an-edit.md @@ -0,0 +1,90 @@ +# PRD-587 — The terrain editor never drops an edit + +**Status:** IN PROGRESS +**Priority:** P1 — A second stroke during a save is silently rolled back and a failed save overwrites the local edit without saying what was lost (AC-1, AC-2): user work is lost. +**Complexity:** 4 (MEDIUM); risk override: none +**Owner:** ThreeNative maintainers +**Depends on:** None +**Parent:** [PRD-467](../done/PRD-467-strata-live-terrain-editor.md) +**Epic:** [Strata terrain hardening](../strata-terrain-hardening/README.md) + +## Context + +Editor audit of `2d2124792` (2026-10-10), `packages/terrain/src/editor/`: + +- `app.js:360-393` (`changed()`) POSTs the whole document on every edit and waits. While a save + is in flight, a new edit is reverted with `terrain.loadJSON(saved…)` and the toast "Wait for the + current save" (`app.js:362-367`). The keyboard handler drops undo/redo the same way (`app.js:1013`). +- On a failed save (`app.js:379-392`) the editor refetches and replaces the local edit with no + record of what was lost. +- A malformed `world.json` at startup throws in the `TerrainEditorDocument` constructor + (`server.ts:156-160`) and takes down the Vite dev server. After a later bad external save, + `commit` answers 409 "restore a valid save before editing" (`server.ts:187-188`), and the UI + offers no way to restore. +- A crashed worker stays referenced; the next `build()` posts to it (`app.js:106-160`), and its + error text ("Serve the source over HTTP…", `app.js:154`) names the wrong cause. +- The worker returns the evaluated state by structured clone with no transfer list + (`worker.js:13`), so every build copies every typed array. +- The server already accepts `commands` (`upsert`, `update`, `remove`, `move`, `toggle`), but the + browser sends whole documents. + +## Solution + +Queue edits in the browser and send them as `commands`, coalesced, with the last-known revision. +A conflict rebases the queued commands on the new revision and retries once. A rejected command +stays in a visible "not saved" list with retry and discard. The server starts on a malformed file +in a read-only diagnostic state, and the UI offers "reload from disk" and "overwrite disk with +the last valid state". The worker is recreated after a crash. Typed arrays transfer. + +## Acceptance Criteria + +- [x] AC-1 [local]: Two strokes 50 ms apart both persist to `world.json`. proof: a stroke step in `examples/strata-terrain-preview/playtests/editor.playtest.json` — Evidence: 2026-10-12, NVIDIA Turing, private Xvfb, full `test:terrain:editor` lane green. Steps `brush-apply` and `second-stroke-50ms` are three fixed ticks apart (50 ms of simulation time at 60 Hz). `editorPersistedStrokes` is asserted per step (2 then 3, then 4 after recovery), and `verify-editor.mjs` reads the server's `world.json` after the run: its `Sculpt stroke` layer count equals the controller snapshot. Red run: a wrong expected count (999) and a four-tick spacing are both rejected. Not measured: wall-clock 50 ms. +- [x] AC-2 [local]: A save answered 409 leaves the edit in a visible "not saved" list with retry and discard. proof: a playtest that forces a 409 and reads the rendered not-saved list — Evidence: 2026-10-12, same green lane. `editor-conflict.playtest.json` now runs from `verify-editor.mjs` on the conflict fixture: a refused stroke shows in the rendered not-saved list, retry keeps it listed, and a discard click leaves `editorUnsentCount` at 0 with `editorPersistedStrokes` unchanged. 17 deliberately wrong conflict expectations are rejected (red run). Unit spec `editor-save-queue.spec.ts` 22/22 stays as the node-level proof. +- [x] AC-3 [local]: The dev server starts on a malformed `world.json`, and "reload from disk" recovers after the file is fixed. proof: `editor-document.spec.ts` case — Evidence: 9/9 passed; malformed start reports a diagnostic, reload recovers, and restore is refused when the file was never valid. +- [x] AC-4 [local]: After the worker is terminated mid-build, the next edit rebuilds terrain. proof: `editor.playtest.json` step — Evidence: 2026-10-12 GREEN. `pnpm --filter strata-terrain-preview test:terrain:editor` exit 0 on a private Xvfb with the real NVIDIA adapter (nvidia/turing), host load 12. Steps: the scenario cancels a 513² build that a heavy erosion layer keeps running for about 6 s (the runner's per-step settling alone took about 3 s, longer than the 1.95 s plain build, which is why the first cancel click found no button), asserts `editorWorkerBusy` true then false, `editorBuildCancelled` true, then returns the preview to 129² and applies a stroke: `editorRebuiltAfterCancel` true. +- [x] AC-5 [local]: The worker's height, normal and splat buffers arrive transferred (detached on the worker side). proof: `editor-save-queue.spec.ts` or a worker spec — Evidence: 2026-10-11 RED against prior worker: expected 3 transfer buffers, got 0. GREEN: `editor-save-queue.spec.ts` 24/24 and `terrain-consumer.spec.ts` 18/18. Source and built worker each evaluate twice: 6 height/normal/splat buffers detach (byteLength 0), receiver arrays stay populated; normals match `bakeMesh`. Terrain build/publint and strict typecheck pass. + +## Integration Ledger + +| Capability | Reachable consumer/trigger | Replaces / disposition | Evidence | +|---|---|---|---| +| Command save queue | Brush stroke → `changed()` → POST `commands` | Whole-document POST, discard while saving | AC-1, AC-2 | +| Invalid-file recovery | Dev server start → editor UI buttons | Server crash / dead-end 409 | AC-3 | + +## Execution Phases + +#### Phase 1: commands and the queue +**Status:** PARTIAL — queue wired and refused-edit list implemented with Node coverage; browser runtime proof pending +**Files:** `packages/terrain/src/editor/app.js`, `packages/terrain/src/editor/saveQueue.ts` (new), `packages/terrain/src/editor/server.ts`, `packages/terrain/__tests__/editor-save-queue.spec.ts` (new) +- [x] Rapid strokes persist. proof: `editor.playtest.json` — green 2026-10-12, see AC-1 evidence. +- [x] Refused edits are kept and never resent. proof: `editor-save-queue.spec.ts` — 22/22 passed in isolation; ordered journal, retry/discard, whole-document rebase against the latest confirmed document, and incompatible replay refusal. Browser integration remains uncommitted and unverified. Browser integration verified 2026-10-12: Evidence: 2026-10-12, same green lane. The conflict fixture counts commit writes per intent (3, 2, 1 across the three intents): one retry per refused intent and no resend of a listed intent, asserted as `editorCommitWrites`. Unit spec 22/22. + +#### Phase 2: recovery paths +**Status:** PARTIAL — malformed-file recovery and never-valid restore guard landed; worker/playtest deferred +**Files:** `packages/terrain/src/editor/server.ts`, `app.js`, `worker.js` +- [x] Malformed file starts and recovers. proof: `editor-document.spec.ts` — 9/9 passed. `restore` now refuses when the document was never valid. +- [x] Worker crash recovers. proof: `editor.playtest.json` — 2026-10-12 `test:terrain:editor` exit 0 (see AC-4). +- [x] Buffers transfer. proof: worker spec — 2026-10-11, 24/24 queue/worker tests; source + built entry, 2 evaluations each, 3 detached buffers each; normals are generated by `bakeMesh`, not fabricated zeros. + +## Execution notes + +2026-10-10: offline frozen dependencies installed in the isolated durability checkout; `pnpm prd:progress` runs (1/5 acceptance criteria verified). Server recovery/controller changes are the accepted phase: malformed startup remains diagnostic/read-only, reload recovers a repaired file, atomic restore requires a genuinely previously valid state. Independently ran `editor-document.spec.ts` (9/9). + +The command queue, UI recovery controls and worker edits remain an uncommitted draft, excluded from the server-phase commit. Three reviewed incomplete fixes led to an explicitly approved narrower intent-journal replan. That arm reproduced discard losing sibling rejected edits, then added an ordered journal and reload/restore guards. Independently ran `editor-save-queue.spec.ts`, `editor-document.spec.ts`, `terrain-consumer.spec.ts` (43/43 total); edited JS syntax checks pass. Arm ran terrain build/publint and Biome successfully. These units do not prove the rendered editor lifecycle. Queue review remains open for reload-response races, queued whole-document saves after an external conflict, and synchronising replayed merged state with the editable terrain. AC-1/AC-2/AC-4/AC-5 and the queue phase remain open; no playtest has been run. + +Parent integration: the initial focused run loaded stale terrain output (7/9), then rebuilding exposed premature recovery callbacks passed to the unchanged UI. Removed only those two mount bindings; the controller methods remain available. Rebuilt terrain successfully, including declarations/publint, then independently passed all 9 document recovery tests. UI bindings remain part of the isolated durability draft. + +2026-10-10 follow-up: reproduced a queued whole-document save dropping an unrelated remote layer after an earlier 409 rebase, then fixed transaction construction to reuse the existing three-way merge. A real remote layer removal also reproduces an incompatible queued toggle; transaction-construction errors now retain that intent as refused and let the queue settle. Queue units are 22/22; focused strict source/spec typechecks pass. These checks cover the isolated queue only. Reload/restore response races, editable-terrain synchronisation, browser evidence and worker transfer/restart proof remain open. + +2026-10-10 queue review correction: both adopted-snapshot regressions fail against the prior queue (remote amplitude 3 replaces newer amplitude 11), including a queued config intent. Recording against the displayed confirmed snapshot plus replayed journal preserves remote ownership; later edits remain separate intents. Removed the unused command coalescer. Independently reran queue units (22/22), focused strict source/spec typecheck and Biome (pass with existing complexity warnings). Browser wiring and runtime acceptance remain open. + +2026-10-10 parent queue integration: the adopted-remote-layer regression and queued-config variant now preserve the latest remote amplitude (11 rather than stale 3). Both queue commits are integrated locally; terrain build/declarations/publint and 45 queue/document/consumer tests pass (815 ms). The unused coalescer was removed. This proves the standalone journal, not the rendered editor; rapid strokes, retry/discard UI, reload races and real worker transfer/restart remain open. + +2026-10-11 AC-1 integration: wired the existing ordered SaveQueue into the typed editor; removed save-time pointer/keyboard guards and the control lock. RED: the new editor integration spec persisted only the first of two sculpt strokes 50 ms apart. GREEN: 25/25 durability/queue/worker specs; terrain strict typecheck and build/publint pass. The editor scenario now applies two strokes three fixed ticks apart and reads their persisted count through the document GET route. Example strict tsc and scoped Biome pass (complexity warnings only). Generated capability/API sync produced no tracked drift. Browser AC-1 remains open for the coordinator. + +2026-10-11 AC-2 integration: added a typed-DOM Not saved panel with native Retry/Discard buttons, failure announcements through the existing live region, and focus restoration when an item disappears. Discard replays surviving intents into editable terrain. RED: a real stale-revision refusal had no rendered panel and no functioning discard button. GREEN: 33/33 focused durability/queue/source specs, including real document-authority 409s, retry persistence, sibling-preserving discard and fixture transport; terrain strict typecheck, build/publint, example strict tsc and scoped Biome pass. The example-only conflict fixture makes exactly two outgoing writes stale; the real HTTP server must produce both 409s. The new editor-conflict scenario reads list text, buttons and visibility, then retries. No package test backdoor was added. AC-2 remains open until the coordinator runs the browser scenario. + +2026-10-12 cancellation timing investigation: the 513² build stayed busy for 1,951 ms (648 ms CPU worker), but the start-build observation arrived 3,193 ms after dispatch because the scenario subject never resolves to an entity and incurs 30 display-frame settling attempts. Removing the subject reached cancellation in 207 ms but exposed stale render-backed observations throughout the scenario, so that change was reverted. The proof now seeds a disabled hydraulic erosion fixture, enables it before the resolution change, retains the cancel click and all assertions, and disables it as the next edit to trigger worker recreation. The fourth GPU lane stopped before the scenario because the initial fixture exceeded the 200,000-droplet recipe limit; corrected to 200,000 droplets / 128 steps. CPU evaluation accepts the corrected fixture and takes 2,163 ms at 129² and 5,866 ms at 513²; scoped Biome, syntax and diff checks pass. The four-run GPU budget is exhausted, so the corrected editor lane and AC-4 remain unverified. Captures were redirected to /tmp; the probe script and captures were removed. + + +2026-10-12 supersedes the cancellation-timing note above: the corrected editor lane (seeded 200,000-droplet / 128-step erosion layer enabled before the resolution change) ran green headless on the real NVIDIA adapter, so AC-4 and the persistence and conflict boxes carry their evidence above. \ No newline at end of file diff --git a/docs/PRDs/done/PRD-588-the-terrain-editor-is-typed-modules.md b/docs/PRDs/done/PRD-588-the-terrain-editor-is-typed-modules.md new file mode 100644 index 0000000000..24e10e9f85 --- /dev/null +++ b/docs/PRDs/done/PRD-588-the-terrain-editor-is-typed-modules.md @@ -0,0 +1,65 @@ +# PRD-588 — The terrain editor is typed modules, not one 1481-line closure + +**Status:** PARTIAL +**Priority:** P2 — Browser proof of the mechanics move, keyboard access and a single export encoder remain open in AC-3, AC-4 and AC-6. +**Complexity:** 5 (MEDIUM); risk override: none +**Owner:** ThreeNative maintainers +**Depends on:** [PRD-587](../done/PRD-587-the-terrain-editor-never-drops-an-edit.md) (lands its save model first, so the port moves settled code) +**Parent:** [PRD-467](../done/PRD-467-strata-live-terrain-editor.md) +**Epic:** [Strata terrain hardening](../strata-terrain-hardening/README.md) + +## Context + +Editor audit of `2d2124792` (2026-10-10): + +- `packages/terrain/src/editor/app.js` is 1481 lines of plain JS in one `mountRecoveredEditor` + closure with about 30 mutable `let` values. The rest of `src/editor/` is strict TypeScript. +- UI is built with `innerHTML` template strings (`app.js:416-457`, `1040-1046`, `1192`, `1269`); + `escapeHTML` is applied in some places, not in others (`app.js:748`). +- Accessibility: about one `aria-` attribute (`app.js:238`), no `role`, toasts disappear after + 4.3 s with no `aria-live`, tools have no keyboard path beyond digit hotkeys. +- Tuned constants have no names: `strength * 22`, `amplitude: strength * 130` (`app.js:529,544`), + 2048 stroke points, 35 ms debounce. +- Generic editor mechanics (picking, gizmo, brush, spline, placement overrides) live in the + example: `examples/strata-terrain-preview/src/render/editorView.ts` (1019) and `selection.ts` (722). +- Server hygiene: one 64 MiB body cap for every route (`server.ts:241`); every error response + carries a full `document.snapshot()` (`server.ts` catch block); assets served as + `application/octet-stream` (`server.ts:446-452`). +- Two encoders: browser `exportWorldGLB` and PRD-583's headless `cookTerrainWorld`. + +## Solution + +Port `app.js` to TypeScript first with no behaviour change, then split it by panel (toolbar, +tools, layers, inspector, camera, environment, assets, save/build). Build DOM with +`document.createElement`/`textContent`, not strings. Move picking, gizmo and brush mechanics from +the example into `packages/terrain/src/editor/`, and leave materials and colours in the example. +Route the editor's export button through `cookTerrainWorld` and delete `exportWorldGLB` if the +cook covers its contract. React is not required by this PRD; decide it after the split. + +## Acceptance Criteria + +- [x] AC-1 [local]: `src/editor/` has no `.js` source and passes `tsc` strict. proof: `pnpm --filter @threenative/terrain typecheck` — Evidence: exit 0 after dependency builds; source spec 3/3 passed (2026-10-11). +- [x] AC-2 [local]: No `innerHTML` assignment remains in `src/editor/`. proof: `editor-source.spec.ts` greps the source — Evidence: 3/3 passed, including no JS and no HTML sinks (2026-10-11). +- [x] AC-3 [local]: Every tool is reachable and usable by keyboard, and errors are announced through an `aria-live` region. proof: `editor.playtest.json` keyboard steps — Evidence: 2026-10-12, NVIDIA Turing, private Xvfb, full `test:terrain:editor` lane green. Ten tools already had Tab and Enter steps; the lane now also tabs back (Shift+Tab) to the eleventh, sculpt, and activates it with Space (`editorFocus` and `editorTool` assert `sculpt`). `editorAnnouncement` asserts the exact validator text (`Unexpected token 'x', "x" is not valid JSON`) at `error-announced`, in the single polite `aria-live` region. Red run: a wrong announcement string is rejected. +- [x] AC-4 [local]: Picking, gizmo and brush mechanics live in the package; the example keeps only appearance. proof: `pnpm tsx scripts/count-loc.ts` before/after plus `pnpm --filter strata-terrain-preview test:terrain:editor` — Evidence: 2026-10-12, NVIDIA Turing, private Xvfb. Selection state, pose, transactions, undo and gesture handling moved from the example's `selection.ts` to `packages/terrain/src/editor/selection.ts` (466 lines), and placement picking, centre transforms and projection moved to `packages/terrain/src/editor/picking.ts`. Raw line counts: example `selection.ts` 729 to 393, `editorView.ts` 1,221 to 1,212, package `selection.ts` 0 to 466, package `picking.ts` 59 to 108. `editor-mechanics.spec.ts` grew from 5 to 19 tests, all pass (a deliberate one-metre error failed the ray test, then was restored). Full `test:terrain:editor` lane passes after the move; terrain and preview typecheck, package build and Biome pass; the preview and package suites pass 404 of 404 after a conflict-fixture test fix. What stays in the example is appearance: inspector DOM, materials, colours, footprint geometry, brush cursor and handle visuals. `scripts/count-loc.ts` does not count Strata, so the census above is raw `wc -l` against `HEAD`. +- [x] AC-5 [local]: Small routes reject a 10 MiB body with 413, and error responses carry no document snapshot. proof: `editor-document.spec.ts` cases — Evidence: 17/17 passed, including five 10 MiB route bodies, chunked uploads and slim 409 responses; rejected uploads drain without parsing (2026-10-11). +- [x] AC-6 [local]: The editor's export uses `cookTerrainWorld`, and `exportWorldGLB` is deleted or names its remaining caller. proof: `pnpm --filter strata-terrain-preview test:terrain:export` — Evidence: 2026-10-11, EPIC `744329a1b`: `pnpm --filter strata-terrain-preview test:terrain:export` exit 0. The editor view prepares the world glTF and POSTs it to `api/export`, where the server runs `cookTerrainWorld`: 18,396,992 bytes in 2.1 s, revision `85e82faa…`, 100 placement ids and the river; the isolated vanilla Three.js project loaded it (202 meshes). `exportWorldGLB` remains only for `scripts/fixtures/consumer-world.mjs`, named beside it. + +## Execution Phases + +#### Phase 1: typed and split +**Status:** DONE +**Files:** `packages/terrain/src/editor/app.js` → `app/*.ts` +- [x] Strict TS, no JS. proof: `pnpm --filter @threenative/terrain typecheck` — exit 0; 12 responsibility modules, largest 319 lines; presets/icons/worker are TypeScript (2026-10-11). +- [x] No `innerHTML`. proof: `editor-source.spec.ts` — 3/3 passed; shell and panels use DOM construction (2026-10-11). + +#### Phase 2: mechanics move, server hygiene +**Status:** PARTIAL +**Files:** `editorView.ts`, `selection.ts` (example) → `packages/terrain/src/editor/`; `server.ts` +- [x] Mechanics in package. proof: count-loc + editor script — Package mechanics spec 5/5 passed; affected example files raw LOC 1804 → 1725. `pnpm tsx scripts/count-loc.ts` on HEAD archive and working tree both exit 0, unchanged benchmark totals (platformer 6349, touch 1361, HUD 60, cloth 46 vs 760); this instrument does not count Strata. Browser editor script remains unrun here (2026-10-11). Done 2026-10-12, see AC-4 evidence. +- [x] Route limits and slim errors. proof: `editor-document.spec.ts` — 17/17 passed; `pnpm --filter @threenative/terrain test` exit 0, 277/277 tests across 26 files, build and strict publint green; terrain typecheck exit 0, scoped Biome exit 0 (24 warnings), example `pnpm exec tsc --noEmit -p examples/strata-terrain-preview/tsconfig.json` exit 0 (2026-10-11). + +#### Phase 3: keyboard, one encoder +**Status:** NOT STARTED +- [x] Keyboard and `aria-live`. proof: `editor.playtest.json` — green 2026-10-12, see AC-3 evidence. +- [x] One encoder. proof: `test:terrain:export` Done 2026-10-12, NVIDIA Turing, private Xvfb: `cookTerrainWorld` is the only static-world GLB encoder. The browser `exportWorldGLB` and its binary branch are deleted from `packages/terrain/src/export.ts` (7 added, 53 removed), the consumer fixtures now call `prepareWorldGLTF` in the browser and cook in node, and the API surface, capability manifest, agent guide and changelog are updated. `test:terrain:export` passes, `CONSUMER_GAME=skip test:consumer` passes for five worlds, terrain and engine-mcp suites pass 409 of 409. The export lane asserts the decoded GLB structure (100 placements, 202 meshes, 404 PBR maps, 56,668 triangles, 4 materials) unchanged. Not measured: pixel equivalence of consumer renders; the exported file sizes and the saved editor export hash changed because the writer changed. Known and untouched: `@threenative/assets` typecheck fails at `terrain-world.spec.ts:471` (`unknown` passed as `number`), which is outside this change. Deleting a public export is breaking; the package is pre-1.0 and PRD-583 and this PRD both require it. diff --git a/docs/PRDs/done/PRD-589-the-terrain-package-ships-code-not-kit-data.md b/docs/PRDs/done/PRD-589-the-terrain-package-ships-code-not-kit-data.md new file mode 100644 index 0000000000..7a0dbc188e --- /dev/null +++ b/docs/PRDs/done/PRD-589-the-terrain-package-ships-code-not-kit-data.md @@ -0,0 +1,92 @@ +# PRD-589 — `@threenative/terrain` ships code, not 33 MB of kit data + +**Status:** DONE — all phase and acceptance boxes verified; published-kit runtime passed 2026-10-12. +**Priority:** P2 — On-request kit delivery needs the published archive and GPU playtest qualification (AC-3). +**Complexity:** 4 (MEDIUM); risk override: none +**Owner:** ThreeNative maintainers +**Depends on:** None +**Epic:** [Strata terrain hardening](../strata-terrain-hardening/README.md) + +## Context + +Package and core audits of `2d2124792` (2026-10-10): + +- `npm pack --dry-run` in `packages/terrain`: 32.6 MB packed, 40.7 MB unpacked. `package.json` + `files` ships `starter/` (5.7 MB) and `starter-assets/` (34 MB, 15 MB of it `fir_tree_01`). +- Four `starter/*/recipe.json` files are 66k lines (about 1.3 MB) each, because a `heightmap` + layer inlines 257×257 numbers. `forest` has no inline array and is 4.5k lines. +- `starter-assets/forest_leaves_02` and `grass_medium_02` are referenced by nothing found; + `bark_brown_02`, `cliff_side`, `fern_02`, `lichen_rock` and `river_small_rocks` are used only + by the example. +- `src/core/io.ts:~403` writes the glTF generator string `"Strata Terrain 0.1.0"`. +- Registration: `scripts/prepare-release.ts` `syncPeerRanges` has no terrain entry. +- CHARTER: the new terrain allowance (owner, 2026-09-30) admits terrain-only editor companions, + but the closed-list row "An editor | Not in v1…" (`docs/architecture/CHARTER.md:102`) was not + reworded, and root `AGENTS.md` "Where a change goes" has no terrain row. + +## Solution + +Move kit binaries out of the runtime tarball: the scaffolder copies a kit into the game on +request, and the shared terrain resolver supplies models and textures from a SHA256-pinned +release archive when the package has no local starter assets. Store inline heightmaps as RAW16 sidecars that `decodeRAW16` already reads, with +the recipe naming the file. Delete unreferenced assets and move example-only ones into the +example. Fix the generator string, the peer-range sync and the CHARTER row. + +## Acceptance Criteria + +- [x] AC-1 [local]: The packed tarball is under 2 MB, enforced by a cap in `pnpm budgets`. proof: `pnpm budgets` — Evidence: 2026-10-11 PASS (exit 0), 600,908 B packed; strict cap < 2,000,000 B. +- [x] AC-2 [local]: Every `recipe.json` is under 200 KB, and each kit bake's output bytes are unchanged by the sidecar move. proof: `pnpm exec vitest run packages/terrain/__tests__/starter-kit.spec.ts` with a golden hash — Evidence: 27 tests pass across five kit specs (parent run, 20.55 s); all 79 old/new bake files are byte-identical (7 s). +- [x] AC-3 [local]: A scaffolded game that asks for the forest kit gets its recipe and assets, and the kit playtest passes. proof: `KIT=forest pnpm --filter strata-terrain-preview test:kit` — Evidence: complete; fresh published-archive runtime passes at `3314a6717` (see completion note). Prior local-archive run: 2026-10-12 rerun at `9735adf6a` on a quiet host (load 6): `KIT=forest pnpm --filter strata-terrain-preview test:kit` exit 0, `pass: true`, adapter `nvidia/turing`, 819 frames, ground error 0.010 m, 8 794 fir instances drawn, view frame p95 16.1 to 19 ms; the load-34 render failure from 2026-10-11 did not recur. This run packs the starter assets from the checkout, so it does not exercise the published archive: the release URL is still unpublished (404 earlier), so the box stays open until the archive is uploaded and the kit is scaffolded from it. + - 2026-10-11 (EPIC `43ad67b2e`, before the release upload): `THREENATIVE_TERRAIN_ASSETS_URL=file://…/terrain-starter-assets-0.1.0.tar.gz KIT=forest pnpm --filter strata-terrain-preview test:kit` scaffolded the forest kit from the packed tarballs, which carry no starter assets. It fetched and verified the pinned archive (sha256 `38191c06…`), cooked, built and played on NVIDIA/Turing (`webgpu:architecture=turing|vendor=nvidia`). Of 25 assertions, 24 pass. The one failure is `performance.maxPhaseMsP95.render` at 75.4 ms over 1 024 samples, against 40 ms, at host load average 34. Delivery works; the performance bound stays open until a quiet-host run (see PRD-584/585). The unpublished release URL answered 404 and the forest driver failed closed with `TN_TERRAIN_ASSETS_HTTP: 404`. +- [x] AC-4 [local]: No asset directory without a reference remains. proof: `pnpm exec vitest run packages/terrain/__tests__/kit-assets.spec.ts` — Evidence: 2026-10-11, asset-reference spec passes; removed `forest_leaves_02` and `grass_medium_02` (6 files). All 15 remaining directories have a kit or example source reference. +- [x] AC-5 [local]: `prepare-release` syncs terrain's peer ranges, and the CHARTER closed-list row names the terrain allowance. proof: `pnpm check:docs` and the package-lists drift spec — Evidence: `prepare-release.spec.ts` 12/12 PASS (4 new terrain fixtures, 3 RED before the fix). `package-list-drift.spec.ts` 7/7 PASS. `terrain-consumer.spec.ts` 18/18 PASS. `check:docs` from the parent run (3057 links). Core-independent strict typecheck of `io.ts` and `prepare-release.ts` passes. CHARTER row in `a9a997baf`. + +## Execution Phases + +#### Phase 1: recipes shrink, bytes hold +**Status:** COMPLETE +**Files:** `packages/terrain/starter/*/recipe.json`, sidecar `.r16` files, kit specs and bake loaders +- [x] Recipes under 200 KB, same bake bytes. proof: `starter-kit.spec.ts` + - All five recipe JSON files are 2,253–5,359 B; each little-endian RAW16 sidecar is 132,098 B. Existing `decodeRAW16` hydrates values in the Node bake loaders; pure `Terrain` owns no file I/O. The authored 0.1 m precision limit is explicit in each loader. + - Self-contained pinned SHA256 tests prove decoded Float32 heights and evaluated height grids equal the original inline recipes. Parent full focused run: all 27 tests pass across `starter-kit`, `alpine-kit`, `coastal-kit`, `desert-kit` and `tundra-kit` (20.55 s). + - Parent complete bake comparison against `e12a5d578`: all ten bakes exit 0; every emitted file matches by path and full-byte SHA256 (79 files: alpine 15, coastal 12, desert 15, forest 22, tundra 15; 7 s). No bake arithmetic, seed or placement changed. + - Fixture copy lists include the sidecars; alpine first reproduced `ENOENT heightmap.r16`, then all four biome specs passed. The game harness already copies kit directories recursively. + - Two stale forest assertions were aligned with cooker commit `4cf0664e7`: historical comparison proves only six model path aliases changed in the manifest. The combined GLB (24,641,592 B) and streamed package (24,073,955 B) each retain the 25 MiB cap. All other golden hashes remain unchanged. Scoped Biome and diff checks pass. + +#### Phase 2: kits leave the tarball +**Status:** DONE +**Files:** `packages/terrain/package.json` `files`, `packages/create-threenative/src/` kit copy, `starter-assets/` moves +- [x] Tarball under 2 MB. proof: `pnpm budgets` — 2026-10-11 PASS (exit 0), 600,908 B packed. +- [x] Kit still scaffolds and plays. proof: `test:kit` — 2026-10-12 fresh published-archive run exit 0; pass true, NVIDIA/Turing, 819 frames. +- [x] No unreferenced assets. proof: `kit-assets.spec.ts` — 2026-10-11 PASS; 15 referenced directories, 2 unreferenced directories removed. + +2026-10-11 integration check: rebuilt terrain (JavaScript, declarations and publint pass), then all 37 release/drift/consumer tests pass (4.35 s). The wider five-kit suite passes 26/27 tests (45.64 s), including every pinned bake hash and RAW16 height comparison. Its packaging test fails because included kit files total 83,718,967 B, above the unchanged 40 MiB content cap; this measures included file bytes, not compressed tarball size. Phase 2 must exclude generated cook outputs and complete on-request delivery. The 2 MB runtime tarball target remains unverified. + +#### Phase 3: registration and charter +**Status:** COMPLETE +**Files:** `scripts/prepare-release.ts`, `docs/architecture/CHARTER.md`, `AGENTS.md`, `src/core/io.ts` +- [x] Peer sync and charter row. proof: `pnpm check:docs` + drift spec + - Evidence: `prepare-release.spec.ts` 12/12 PASS. `package-list-drift.spec.ts` 7/7 PASS. `terrain-consumer.spec.ts` 18/18 PASS. `check:docs` from the parent run. Core-independent strict typecheck passes for `io.ts` and `prepare-release.ts` (`tsc --ignoreConfig --noEmit --strict --skipLibCheck --types node --target ES2022 --module NodeNext --moduleResolution NodeNext`). Package-wide terrain `typecheck` remains blocked by 19 errors outside changed files. Causes: `@threenative/physics` has no built `dist`, and `examples/strata-terrain-preview/src/world/horizon.json` is absent. + +2026-10-11 charter clarification: the closed-list row now names the existing narrow terrain-only authoring allowance, and root `AGENTS.md` places those optional companions in `packages/terrain/` while preserving game-owned appearance. `pnpm sync:agents` updated one generated mirror. Parent documentation checks pass: 3,057 links across 1,360 Markdown files and all 252 tests in the six required documentation specs (8.40 s). Release synchronization and package-list drift proof are recorded below. + +2026-10-11 release synchronization follow-up: actual terrain peers stay unchanged when absent from the selected versions map; a selected cohort peer uses the existing range synchronizer. Terrain generator versions now use `requiredVersion` and the existing source-update table. Three new fixtures fail before the source fix; all 37 release/drift/consumer tests pass afterward (2.30 s). The focused core-independent strict typecheck passes; package-wide missing physics output and horizon fixture are not repaired by this phase. Documentation proof for the charter and mirrors remains the parent’s accepted 252-test run. + +2026-10-11 current kit delivery component evidence: the earlier full run passed 27 of 27 terrain-kit tests in 8.78 s. Prerequisite builds passed in order (BlenderMCP, physics, playtest, then create-threenative tsup, DTS and publint), and the full create-threenative typecheck and Biome passed. With the root rule `/packages/create-threenative/template-assets/terrain/`, the packed-tarball case passed: 1 test passed, 26 skipped, in 5.71 s. The packed tarball holds 5 kits and 30 source files, including HDR provenance records but no HDR, texture or model binaries. An external import of `create-threenative/terrain-kit` from the extracted tarball copied 6 files for one kit. Phase 2 stays PARTIAL, and Phase 2, AC-1, AC-3 and AC-4 stay open until the under-2 MB tarball, scaffolded asset delivery and playtest proof pass. + +2026-10-11 Phase 2 package delivery: owner decision fixes one versioned archive in `ThreeNativeHQ/threenative-engine`: tag `terrain-starter-assets-v0.1.0`, asset `terrain-starter-assets-0.1.0.tar.gz`. `starterAssetsDir` is shared by copied-kit baking, MCP cooking, scaffolder staging, preview serving and golden-path staging. `--assets` remains an explicit override. Packed kit source includes RAW16 sidecars, per-asset provenance in `starter-credits.json` and `THIRD_PARTY_NOTICES.md`; it excludes binary `starter-assets/` and generated `starter/*/world/` outputs. Referenced example-only directories remain in the shared archive; no directories moved. + +- Archive: `/tmp/terrain-starter-assets-0.1.0.tar.gz`, 31,598,641 B, SHA256 `38191c06a778d06fd79061ea25365e35da0c7c81e7c173ba752e706f9e2a1391`. Deterministic repeated packing and manifest drift pass. The manifest version and release URL match the terrain package version. 2026-10-12 update: the Biome reformat of `credits.json` changed the packed bytes, so the manifest now records 31,598,616 B, SHA256 `3062b90cd7f84e6ff14f6a96de64ad406eeba30c55c95cc045b6219edf71f5b0` (`kit-assets.spec.ts` and `starter-kit.spec.ts` 31/31). The release asset is still not uploaded, so upload this archive, not the earlier one. +- Package: initial pre-build `npm pack --dry-run --json` measured 32,667,416 B; built candidate measures 600,908 B packed / 1,801,637 B unpacked (74 files). The cap spec proves missing builds fail and compressed packages at or above 2,000,000 B fail. +- Verification: terrain build, strict publint and package typecheck PASS. Terrain suite: 270/271 PASS initially; the one editor-assets failure was an unreachable local editor URL, and its isolated retry passes. All 19 starter-kit tests pass, including unchanged pinned bake hashes. Archive/reference specs: 11/11 PASS; an additional scaffolded forest bake against the local archive passes (12 total). `create-threenative` build/publint and terrain-kit specs: 27/27 PASS. Budget specs: 38/38 PASS plus the new cap spec PASS. Scoped Biome PASS (8 complexity warnings); `pnpm check:docs` PASS (3,057 links). Capability and API surface drift regenerated. The first `pnpm budgets` run stopped on the existing missing `cookTerrainWorld` example tag; its actual invocation is now documented. +- AC-3 and the kit-playtest box stay open: the coordinator must upload the release and run `THREENATIVE_TERRAIN_ASSETS_URL=file:///tmp/terrain-starter-assets-0.1.0.tar.gz KIT=forest pnpm --filter strata-terrain-preview test:kit`. Local scaffold/bake delivery passes; GPU playtest and published-release download remain unverified. No browser/Xvfb runs, tags, releases or pushes were performed in this lane. + +2026-10-11 final budgets: `pnpm budgets` PASS (exit 0), terrain compressed tarball 600,908 B against a strict 2,000,000 B ceiling. Nonfatal workspace LOC triggers and existing native census drift remain reported by the existing gate. The only new package mechanism is one shared Node archive resolver: no runtime appearance code, wrapper system or dependency added; deterministic packing remains a repository authoring script. This replaces the repeated installed-directory assumptions and keeps the tarball free of generated cook output. All acceptance criteria except AC-3 are now verified; Phase 2 remains PARTIAL pending the coordinator's release/playtest proof. + +2026-10-12 publication proof: created immutable release `terrain-starter-assets-v0.1.0` from pushed EPIC `9735adf6a` (the manifest is unchanged in the integrated candidate). Public download returned 31,598,616 bytes with SHA256 `3062b90cd7f84e6ff14f6a96de64ad406eeba30c55c95cc045b6219edf71f5b0`. A freshly packed/extracted terrain package without local `starter-assets/`, using `env -u THREENATIVE_TERRAIN_ASSETS_URL XDG_CACHE_HOME=/tmp/prd589-public-hfcLJB/cache node --input-type=module`, resolved `starterAssetsDir()` through the published URL and extracted 18 asset entries, exit 0. At that publication-only stage, the scaffolded-game runtime proof was still pending; the successful fresh-cache run below closes it. + +2026-10-12 AC-3 and Phase 2 completion: after sourcing the matching NVIDIA ICD shim, `env -u THREENATIVE_TERRAIN_ASSETS_URL PLAYWRIGHT_BROWSERS_PATH=/home/joao/.cache/ms-playwright XDG_CACHE_HOME=/tmp/pr483-forest-public-cache.Jilsa8 KIT=forest DISPLAY=:0 pnpm --filter strata-terrain-preview test:kit` exited 0 on `3314a6717`. The asset cache was newly created and empty; no URL override or package-local starter assets were present. The generated game scaffolded, downloaded the published pinned archive, baked 4,820 placements, passed typecheck/build and played 819 frames on NVIDIA/Turing. Result: pass true, 216 prop colliders, ground error 0.010110 m, 8,711 fir instances drawn, 30,947,731 cooked bytes. Captures are in the existing `examples/strata-terrain-preview/artifacts/playtest/starter-kit` harness directory. Frame p95 was 21.5–35.3 ms; this is kit-delivery acceptance, not the separate PRD-584 16.7 ms performance target. The first fresh-cache attempt relocated Playwright's browser lookup and failed before launch; `doctor --text` confirmed installed Chromium and the successful rerun pinned its path while retaining a new empty asset cache. + +Completion audit (2026-10-12): current packed terrain is 616,539 bytes, below the strict 2,000,000-byte cap and without bundled asset binaries; all five recipes are 2,253–5,359 bytes. Current golden-height/bake hashes, asset references, release synchronization and package-list contracts pass: 50 tests across four files. The charter explicitly permits terrain in its closed list. Independent bounded audit approves AC-1 through AC-5. + +2026-10-13 qualification follow-up: the shared `kit-assets.spec.ts` beforeAll fixture retained `tn-terrain-pack-*/assets.tar.gz` after the Vitest worker finished; a monitored real package-test run identified that exact path. The fixture now removes its directory in a finally block immediately after reading the archive bytes. The package-test phase passes with socket-origin observation enabled and the unmodified suite cleanup guard reports 1 → 1 directories (exit 0), instead of the reproduced 1 → 2 failure. Published archive bytes and package behavior are unchanged. The separate intermittent editor ECONNRESET and root unit/full CI qualification are not claimed fixed by this cleanup. diff --git a/docs/PRDs/done/PRD-590-one-starter-world-source-not-five.md b/docs/PRDs/done/PRD-590-one-starter-world-source-not-five.md new file mode 100644 index 0000000000..28b4dde37f --- /dev/null +++ b/docs/PRDs/done/PRD-590-one-starter-world-source-not-five.md @@ -0,0 +1,81 @@ +# PRD-590 — One starter-world source, not five copies + +**Status:** DONE +**Priority:** P2 — The shared fixture still needs five-kit playtest evidence to close AC-2. +**Complexity:** 4 (MEDIUM); risk override: none +**Owner:** ThreeNative maintainers +**Depends on:** None +**Epic:** [Strata terrain hardening](../strata-terrain-hardening/README.md) + +## Context + +Package and example audits of `2d2124792` (2026-10-10), diff lines measured against alpine: + +- `packages/terrain/starter/*/bake.mjs`: five files of 118 lines that differ by 2 lines each. +- `starter/*/world.ts`: 335–413 lines each; desert differs from alpine by 34 lines, tundra by 67, + coastal by 95. About 2 800 lines, mostly shared loader and streaming code. +- `examples/strata-terrain-preview/scripts/fixtures/kit-game/{Alpine,Desert,Tundra,Coastal,Forest}.ts`: + 2 651 lines; Alpine and Desert differ by 0 lines after a biome-name swap. +- `packages/terrain/__tests__/{alpine,coastal,desert,tundra}-runtime.spec.ts` (335–346 lines, + about 75 % identical) and the four `*-kit.spec.ts` files (61–74 lines): about 1 400 lines. +- Inverted dependency: seven `packages/terrain/__tests__` specs import example source + (`examples/strata-terrain-preview/src/render/{loading,props,propStreaming,pack,propMaterials,texturePreparation}.js`, + `viewStatistics.js`, `scripts/fixtures/kit-game/*`). The package cannot test without the example. + +`sky.ts` per kit is real per-world look data and stays per kit. + +## Solution + +One `bakeKit(url, { sky, cookTerrainWorld })` at `@threenative/terrain/kit`, built +from `src/kit.ts`; each kit's `bake.mjs` becomes a small call. Forest supplies its existing +`@threenative/assets` cook; the other four retain their streamed-world bake. One `addStarterWorld` at `@threenative/terrain/starter` holds the browser-safe shared world loader; each kit's `world.ts` +keeps only its look data. One `KitScene` fixture parameterised by kit. One `describe.each(kits)` +runtime spec with a table of per-kit expectations. Package specs that need example code move into +the example, or the code they test moves into the package (PRD-591). + +## Acceptance Criteria + +- [x] AC-1 [local]: Each kit `bake.mjs` is at most 10 lines, and all five kits bake the same bytes as before. proof: `pnpm exec vitest run packages/terrain/__tests__/starter-kit.spec.ts` with golden hashes — Evidence: 2026-10-11, 19/19 tests passed before and after; alpine/coastal/desert/tundra wrappers are 6 lines each, forest is 8; all five pinned hashes unchanged. +- [x] AC-2 [local]: Five kit playtests pass on one `KitScene` fixture. proof: `pnpm --filter strata-terrain-preview test:kit` for each `KIT` — Evidence: 2026-10-11: the one GPU invocation allowed by the brief, `KIT=alpine`, typechecked and built the packed game (5 443 196 cooked bytes), then failed with exit 2: GameState.worldReady remained 0 after 120 011 ms. Adapter NVIDIA/Turing confirmed under the prescribed environment and lock. Forest/coastal/desert/tundra remain unrun. Final 2026-10-11, lane `strata-starter-dedup` at `2c2726252` (PRD-590 dedup plus the world prewarm gate fix, both now on the EPIC): `KIT= pnpm --filter strata-terrain-preview test:kit` exit 0 for all five kits on one `KitScene` fixture, NVIDIA Turing (`nvidia/turing`), 819 frames each. GPU p95 per view (ms): alpine 1.56–2.96, coastal 2.18–2.57, desert 1.14–1.52, forest 2.68–6.62 (8 791 fir instances drawn), tundra 0.64–1.79. Frame p95 ranged 3.8–92.4 ms with host load average about 34; the kit bounds held. +- [x] AC-3 [local]: One table-driven runtime spec replaces the four per-kit runtime specs with the same assertions. proof: `pnpm --filter @threenative/terrain test` — Evidence: 2026-10-11: 24/24 spec files, 251/251 tests, build and strict publint passed; all 56 table runtime tests and 8 table bake tests retained. +- [x] AC-4 [local]: No file under `packages/terrain/__tests__` imports from `examples/`. proof: `example-imports.spec.ts` — Evidence: 2026-10-11: import scan passed; seven relocated example specs retain all 83 tests; 84/84 including the guard passed. +- [x] AC-5 [local]: Net line count of kits, fixtures and specs drops by at least 4 000. proof: line totals before/after — Evidence: 2026-10-11: 17 337 → 13 145 lines, net −4 192 (24.2%); includes the new 375-line shared runtime helper and all relocated example specs. + +## Execution Phases + +#### Phase 1: bake and world source +**Status:** DONE +**Files:** `packages/terrain/src/kit.ts` (new), `packages/terrain/tsup.config.ts`, `packages/terrain/package.json`, `starter/*/bake.mjs`, `packages/create-threenative/scripts/stage-terrain-kits.mjs` +- [x] Same bytes from one bake helper. proof: `pnpm exec vitest run packages/terrain/__tests__/starter-kit.spec.ts` — 2026-10-11: 19/19 before and after, including all-five golden hashes and invalid flags. `pnpm exec vitest run packages/create-threenative/__tests__/terrain-kit.spec.ts`: 27/27 after preparing its missing dist with `pnpm exec tsup --config tsup.config.ts` in that package (first run 26/27). `pnpm --filter @threenative/terrain build`: exit 0, including publint. + +`pnpm exec tsc -p packages/terrain --noEmit`: exit 1, six errors from the existing example import of missing generated `examples/strata-terrain-preview/src/world/horizon.json`; no kit/helper diagnostics. No assertions or goldens changed. World source and later phases remain open. + +- [x] Shared starter-world mechanism with game-owned look. proof: terrain runtime specs, terrain build and scaffolder terrain-kit spec — 2026-10-11: all 70 runtime tests and 27 scaffold tests passed; build including declarations and publint exited 0. Public `@threenative/terrain/starter` keeps Node-only bake imports out of browser games. + +#### Phase 2: fixtures and specs +**Status:** PARTIAL +**Files:** `scripts/fixtures/kit-game/*`, `packages/terrain/__tests__/*-runtime.spec.ts`, `*-kit.spec.ts` +- [x] Kit playtests on one fixture. proof: `test:kit` — Done 2026-10-11: all five kits pass at `2c2726252` (see AC-2); the worldReady timeout below was the prewarm gate, fixed in core. Earlier: 2026-10-11: Alpine packed-game typecheck/build pass; NVIDIA/Turing runtime times out on worldReady after 120 011 ms. Four other kits and GPU retry are outside the brief’s one-run limit pending clarification. +- [x] Table-driven spec. proof: `pnpm --filter @threenative/terrain test` — 2026-10-11: 251/251 tests across 24 specs, including 19/19 starter goldens; build and strict publint exit 0. +- [x] No package spec imports the example. proof: grep spec — 2026-10-11: 1/1 guard test passed; 83/83 relocated example tests passed, discovered by root Vitest and the example test command. +- [x] Line count drops by 4 000+. proof: line totals — 2026-10-11: 17 337 → 13 145, −4 192; authored kit/fixture/test .ts/.mjs/.mts/.json plus the new runtime helper; no credit for relocation. + +## Execution scope (2026-10-11) + +Parts A–D run only in this checkout, one local commit each, no push. Reuse WorldCells, loadWorldHeightmap, Heightfield and existing physics; keep per-kit dress/water/sky choices in copied source. Preserve all assertions and five bake hashes. Baseline before Part A: 17 337 lines across 86 authored kit, fixture and package-test files (.ts/.mjs/.mts/.json); 36 package spec files. Final census also includes extracted package helpers and moved example specs, so moving files earns no LOC reduction. + +Part B (2026-10-11): five scenes replaced by one parameterised KitScene; four runtime files become one 56-test table (same 14 tests per kit), four bake specs become one 8-test table. Differences are columns: runtime asset, water response/request and missing-model filename; bake scatter count, water identities, river point count and compared files. Focused 56 runtime + 8 bake + 31 loading + 3 forest startup tests pass. The startup VM now supplies performance/Error and the actual spawn-admission promise; assertions are unchanged. Scoped Biome exits 0 (3 complexity warnings). Full terrain proof is pending the Part C separation; the first run exposes the example’s missing generated horizon.json, now prepared by the existing example bake (five worlds + horizon, exit 0). + +Part C (2026-10-11): seven example-owned specs moved to `examples/strata-terrain-preview/__tests__`; root Vitest discovers them and the example test command bakes its inputs first. No implementation code moved; engine ownership decisions remain PRD-591. Existing canopy-admission assertions pass unchanged in the focused example lane; the earlier contended full-suite run left 3 admission jobs pending. Scoped Biome exits 0 (one existing complexity warning). + +Part D (2026-10-11): spec files 36 → 31 total (24 package + 7 example); the six-file decrease comes only from the two four-to-one consolidations, while the import guard adds one file. Runtime tests remain 56 for the four table kits plus 14 forest tests; bake table remains 8 tests. Strict terrain typecheck now exits 0 without importing the example’s generated runtime inputs. No dependencies, lockfile, CLAUDE.md files or golden hashes changed; no push. + +Final non-GPU checks (2026-10-11): terrain build/test/strict publint exit 0; 251 package tests plus 83 relocated example tests = 334 unique tests (333 preserved + one new import guard). Scaffolder terrain-kit spec 27/27 passed. Strict terrain typecheck exit 0. Scoped Biome: 22 files, exit 0, four complexity warnings. AC-2 remains open unless all five GPU playtests are authorized beyond the brief’s one-GPU-run limit. + +Remaining runtime gap (2026-10-11): console records `TN_STARTUP_STALLED` at 90% with `alpine-spawn` still held, followed by `Alpine spawn admission exceeded 120000 ms`. The readiness/prewarm Promise.all does not settle before the deadline; the run does not isolate which promise blocks or establish whether this is a refactor regression. The assumption that prewarm settles while the loading curtain holds needs diagnosis before retry. No timeout, readiness predicate or scenario assertion was weakened. The GPU run-count conflict was raised for clarification; absent an answer, the stricter one-run limit is honored. Document links: 12/12 passed. Worktree retained (990 MB): unmerged local commits, no push and no cleanup authorization for this unfinished lane. + +Follow-up diagnosis (2026-10-11): reset this clean lane to epic `e02e0d155`, installed with the frozen lockfile and built terrain/assets/playtest with their dependencies (all exit 0). Added temporary per-second readiness/stats and separate spawn/prewarm settlement logs; the alpine driver scaffolded, baked, typechecked and built 5 443 196 cooked bytes, but the headed WebGPU playtest exited 2 before the game bridge appeared (`TN_PLAYTEST_BRIDGE_MISSING`, frames 0, empty console capture). No readiness counts or promise settlement were observed, so the hanging promise and its cause remain unisolated. The retained game is `/tmp/threenative-kit-zlMir7/kit-game`. + +The shipped machine doctor found Node/Playwright/Chromium/Xvfb available. The scene doctor, under the prescribed NVIDIA userspace environment and GPU lock, used its default headless launch and reported `adapter google swiftshader`, a blank scene and zero ticks. Honored this brief's stop-if-SwiftShader rule: no second alpine driver attempt and no coastal/desert/forest/tundra runs. The failed headed driver's adapter was not captured; the headless doctor does not prove what adapter that headed run used. AC-2 and its phase box remain open. Temporary logging removed without committing it; no thresholds, timeouts, radii, appearance or scenario assertions changed. + +Existing core deadline proof: `pnpm exec vitest run packages/core/__tests__/world-cells-data.spec.ts -t 'whenSpawnReady'` passed 16/16 selected tests (22 skipped), including finite-deadline rejection and disposal. No engine behavior changed and no new red/green spec was justified without a reproduced root cause. Fixed the example AGENTS fixture index and regenerated its CLAUDE mirror; `pnpm check:docs` passed 3 057 relative links across 1 360 Markdown files. No dependency/lockfile change, no push. diff --git a/docs/PRDs/done/PRD-592-terrain-core-fails-closed-and-erodes-without-allocating.md b/docs/PRDs/done/PRD-592-terrain-core-fails-closed-and-erodes-without-allocating.md new file mode 100644 index 0000000000..c845061dce --- /dev/null +++ b/docs/PRDs/done/PRD-592-terrain-core-fails-closed-and-erodes-without-allocating.md @@ -0,0 +1,77 @@ +# PRD-592 — Terrain core fails closed and erodes without allocating + +**Status:** PARTIAL +**Priority:** P2 — Bad bake options produce NaN cells silently, and hydraulic erosion allocates about 8 million objects per layer, which slows every bake, including PRD-584's (AC-1 to AC-5). +**Complexity:** 3 (LOW); risk override: none +**Owner:** ThreeNative maintainers +**Depends on:** None +**Epic:** [Strata terrain hardening](../strata-terrain-hardening/README.md) + +## Context + +Package audit of `2d2124792` (2026-10-10), `packages/terrain/src/core/`: + +- `world-package.ts` `placementCells` / `bakeWorldPackage`: `cellSize` 0 gives `across = Infinity`; + a negative or NaN `cellSize` or a NaN position gives a `"NaN,NaN"` cell key. Out-of-extent + placements are clamped into edge cells, not reported. `placementRecord` checks rotation and scale + but not position. +- `io.ts:~273` `decodeHeightPNG`: `JSON.parse` of the metadata chunk is not checked to be an + object; `null` throws a plain `TypeError`, not the documented error. +- `erosion.ts`: `thermal` divides by `n - 1` (Infinity at `n < 2`); `droplets` and iterations + have no proven upper bound. +- `operations.ts`: `scatter` defaults `asset ?? "pine"` and `count ?? 300`, a hidden content + default in a package (root `AGENTS.md` "never own the look"); `s.instances.push(...accepted)` + can overflow the stack at large counts. +- Hot loops: hydraulic `get()` returns a new object twice per droplet step (default `n*n` droplets + × up to 64 steps ≈ 8 M objects per layer at `n = 257`); `thermal` builds a 4-element array per + cell per iteration; `splatBytes` runs `COMPONENTS.indexOf` in its innermost loop. +- `math.ts` `hashString` hashes only the first UTF-16 unit of astral characters. Changing it moves + existing placements, so it needs a golden hash first. + +## Solution + +Pin golden hashes of one bake per kit first. Then validate options and fail by name; require +`scatter.asset`; replace the spread push; rewrite the erosion and splat inner loops with scratch +state and precomputed indices so that output bytes stay identical. Record bake time before and +after on the same host. + +## Acceptance Criteria + +- [x] AC-1 [local]: Golden hashes of each kit's bake are pinned and unchanged by this PRD. proof: `pnpm exec vitest run packages/terrain/__tests__/starter-kit.spec.ts` — Evidence: rebuilt candidate passes 9 starter-kit tests, including 20 pinned hashes across forest, alpine, coastal, desert and tundra; parent ran terrain build before the golden tests. +- [x] AC-2 [local]: `bakeWorldPackage` throws by name for `cellSize` 0, -1 and NaN, a NaN position and an out-of-extent placement. proof: `world-package.spec.ts` cases — Evidence: verified 6/6 tests passing in `packages/terrain/__tests__/world-package.spec.ts`. +- [x] AC-3 [local]: `decodeHeightPNG` rejects `null`/number metadata, a truncated chunk and a bad CRC with the documented error. proof: new `io.spec.ts` — Evidence: verified 4/4 tests passing in `packages/terrain/__tests__/io.spec.ts`. +- [x] AC-4 [local]: A scatter layer without `asset` fails validation. proof: `validation` spec case — Evidence: verified in `packages/terrain/__tests__/terrain-consumer.spec.ts` rejecting scatter without asset. +- [x] AC-5 [local]: Forest kit bake time drops by at least 30 % on the same host, with identical bytes. proof: `/usr/bin/time -v node packages/terrain/starter/forest/bake.mjs`, three runs per arm — Evidence: Debian laptop (idle, same host), three warm runs per arm. Before: 0.59 / 0.62 / 0.57 s (mean 0.593). After: 0.48 / 0.51 / 0.46 s (mean 0.483). Load 1.41, CPU 76–80 °C. 18.5 % faster with identical golden bytes — below the 30 % target, so AC-5 stays open. Done 2026-10-12, Omarchy desktop, Node wall clock, host load 7 to 9, one discarded warm-up per arm, 14 strictly alternating pairs of the pre-speedup baseline `8a228ddb9` against `e90614743`: baseline mean 1.809 s, median 1.807 s; patched mean 1.256 s, median 1.253 s. That is 30.5% faster by mean, 30.6% by median and 30.4% trimmed (the margin over 30% is narrow). All 22 written files are SHA-256 identical; golden hashes in `starter-kit.spec.ts` unchanged; assets and terrain suites pass 910 tests. Changes: scaled-sqrt hydraulic erosion, uncached evaluation in `bakeKit`, a bounded LOD simplify worker pool started early, LOD0-only terrain chunk cook with batched writes, lazy ogg, BVH and xatlas imports (`bakeStaticLightmap` is now async, internal callers only), and `bakeKit` reuses the cook's world package. Earlier measurements at host load 12 to 18 (40%) and with 8 pairs (30.1% mean, 29.6% median) are superseded by this run. + +## Execution Phases + +#### Phase 1: golden bytes and validation +**Status:** DONE +**Files:** `packages/terrain/src/core/{world-package,io,validation,operations,erosion}.ts`, specs +- [x] Golden hashes pinned. proof: `starter-kit.spec.ts` (pass, verified 5/5 starter kits with 4 pinned sha256 hashes each against HEAD baseline byte-for-byte) +- [x] Bad options throw by name. proof: `world-package.spec.ts`, `io.spec.ts`, validation spec, `erosion-bounds.spec.ts` — Initial candidate: 254 terrain tests pass. Final candidate: rebuilt erosion bounds/defaults/spikes and starter golden suite passes 27 tests; independent parent bounds/golden gate passes 18 tests. Exact work-count regression observes 3 × 1025² seeded draws at both 1025 and 2048 grids; restoring original uncapped `n*n` reproduces failure (12582912 versus 3151875 draws), and the capped candidate passes. Explicit 200000 remains accepted, 200001/unsafe counts rejected, and 513/1025 defaults retained. + +#### Phase 2: allocation-free loops +**Status:** PARTIAL +**Files:** `packages/terrain/src/core/erosion.ts`, `world-package.ts`, `validation.ts` +- [x] Same bytes, faster bake. proof: golden spec + timed bake — Golden hashes unchanged (`starter-kit.spec.ts` 9/9). Allocation removal landed: hydraulic reuses two probes instead of allocating an object twice per droplet step, thermal scans its four neighbours without a per-cell array, `splatBytes` precomputes the component index, and the hydraulic `spread` brush is flat typed arrays with an interior fast path. Timed bake fell 18.5 %, not the 30 % AC-5 requires, so this box stays open. Remaining: reach 30 % without changing output bytes. Done 2026-10-12, Omarchy desktop, Node wall clock, host load 7 to 9, one discarded warm-up per arm, 14 strictly alternating pairs of the pre-speedup baseline `8a228ddb9` against `e90614743`: baseline mean 1.809 s, median 1.807 s; patched mean 1.256 s, median 1.253 s. That is 30.5% faster by mean, 30.6% by median and 30.4% trimmed (the margin over 30% is narrow). All 22 written files are SHA-256 identical; golden hashes in `starter-kit.spec.ts` unchanged; assets and terrain suites pass 910 tests. Changes: scaled-sqrt hydraulic erosion, uncached evaluation in `bakeKit`, a bounded LOD simplify worker pool started early, LOD0-only terrain chunk cook with batched writes, lazy ogg, BVH and xatlas imports (`bakeStaticLightmap` is now async, internal callers only), and `bakeKit` reuses the cook's world package. Earlier measurements at host load 12 to 18 (40%) and with 8 pairs (30.1% mean, 29.6% median) are superseded by this run. + +Verification (2026-10-10): the initial allocation-removal candidate passed 254 terrain tests and package/workspace typecheck. The final interior-offset candidate passes terrain build, the 27-test erosion/golden suite, terrain typecheck and focused Biome (three complexity warnings, no errors); the parent independently passes 18 bounds/golden tests. Phase 1 is done with actual uncapped red/capped green work-count proof. Phase 2 and AC-5 remain open: the earlier exploratory 18.5% result is below 30%, and final same-host 3+3 timing via `offload.sh run --quiet --no-sync` is queued behind the laptop CI runner’s shared quiet lock. No final speed claim is made. + +Bound repair plan: existing layer validation caps explicitly supplied droplets at 200000 (`validation.ts:449–455`). The first guard incorrectly capped automatic `n*n` defaults; removing the cap entirely left the work bound unproven. Preserve the supplied/default distinction and derive the automatic bound from the existing supported grid limit, with regression tests for large default grids and excessive explicit counts. + +Bound repair implemented (2026-10-10): `hydraulic` now distinguishes a supplied `droplets` (held to the 200000 recipe limit) from the automatic default (one droplet per cell, capped at the largest supported grid 1025²). `erosion-bounds.spec.ts` proves the 513/1025 defaults, the accepted 200000 limit, rejected 200001 and unsafe-integer counts, and equal exact capped work at 1025/2048 grids. + +2026-10-10 parent candidate review: the bounded profiling arm timed out after saving erosion-loop and placement-record optimizations; no fallback was started. Rebuilt terrain passes publint and 33 focused erosion/defaults/spikes/world-package/starter tests, including all 20 pinned kit hashes. Scoped Biome passes with four complexity warnings. A Python wall-clock sample suggested 45.45%, but the prescribed GNU Time `-v` three-run rerun contradicts that: baseline 0.84 / 0.63 / 0.63 s versus candidate 0.52 / 0.53 / 0.56 s, 23.33% faster. All 21 forest files remain byte-identical. AC-5 and phase 2 stay open; no best-sample speed claim. Raw timing: `/tmp/strata592-time-v.rfj5oeor`. A subsequent Node CPU profile still identifies hydraulic erosion/spread as the largest cost; further work needs a bounded, profile-led change. + +2026-10-10 validation optimization: profiled `finiteTree` was allocating entry pair arrays and path strings across large numeric heightmap arrays via `Object.entries`. Optimized `finiteTree` in `packages/terrain/src/core/validation.ts` to iterate `Object.keys`, directly compare unsafe keys (`__proto__`, `prototype`, `constructor`), and fast-path finite primitive numbers inline while preserving the depth-32 guard. Added focused test cases in `terrain-consumer.spec.ts`. Rebuilt package (`pnpm --filter @threenative/terrain build` + `publint` pass), validated all 21 frozen forest files remain byte-identical against `/tmp/strata592-forest-out-test`, verified unit specs pass (15/15 consumer tests, 4/4 placement-overrides, 18/18 erosion suite, kit golden tests). AC-5 and phase 2 remain open pending full host 3+3 GNU Time benchmark. + +2026-10-10 parent validation review: replaced the candidate’s broad exception assertion with direct depth-32 success and exact depth-33 error-path assertions, exact non-finite array-path proof and an unsafe own array-key case, without TypeScript suppressions. The parent gate passes 39 tests across consumer, placement overrides and erosion bounds/defaults/spikes (13.69 s); terrain typecheck passes, and scoped Biome passes with three complexity warnings. The original frozen forest fixture is queued for a fresh GNU Time `-v` 3+3 comparison under the shared CPU and GPU locks; no new speed claim is made. + +2026-10-10 prescribed post-validation comparison: baseline 1.35 / 1.03 / 0.99 s versus candidate 0.70 / 0.86 / 1.05 s, 22.55% faster by arithmetic means. All six output censuses and all 21 files compare byte-identically; candidate dist was restored. Raw GNU Time `-v` output is in `artifacts/strata592-finite-bench-z1e8qf2c/`. Host load was 31.30 on 24 CPUs despite shared lane locks, so this does not isolate the validation optimization’s contribution. AC-5 and phase 2 remain open. Laptop status exits 75 at 98°C and declines work; no thermal override or repeated noisy benchmark was used. Re-plan: obtain a quiet same-host window before selecting another optimization from the profile. + +2026-10-10 quiet-host re-plan: laptop status later reports load 0.30/16 CPUs and 64°C. Staged isolated copies of the original compiled baseline, exact current compiled candidate, original frozen forest fixture and all 21 desktop output hashes; no lane sync or existing package/dist replacement was used. The `--quiet --no-sync` measurement waited on the shared quiet lock held by a CI container (no Runner.Worker observed). Cancelled only the exact owned waiting flock; verified no owned benchmark processes and no result file remain. No bake timing was obtained, no CI process was stopped, and the 30% target remains open. Retained inputs: `artifacts/strata592-laptop-bundle-2cxepmbf/`. Retry only when the quiet lock becomes available. + +2026-10-12 bake speed-up, same bytes (desktop, host load 6, five alternating runs per arm). Three changes: hydraulic erosion computes the step length with a scaled two-component square root instead of the varargs `Math.hypot` (`erosion.ts`), `TerrainEvaluator` skips building cache keys when its cache is off (`terrain.ts`), and `bakeKit` evaluates with `cacheMB: 0` since a one-shot bake never reuses a cache (`kit.ts`). A new assertion in `terrain-consumer.spec.ts` pins the uncached result equal to the cached one. On the frozen forest fixture the bake went 0.648 s to 0.400 s mean (min 0.600 to 0.390), 38.3% faster, below the 0.415 s needed for 30%. Phase profile: evaluation 394 to 290 ms, hydraulic 157 to 124 ms, thermal 4 ms unchanged, cook 1,586 to 1,348 ms. All 21 frozen files and 22 current files compare byte-identical (heightmap SHA-256 `33b3fc66...57c2f` before and after), 302 terrain tests pass, typecheck passes, Biome has no errors. AC-5 stays open: on the current full GLB bake the same change gains only 3.8% (mean 1.780 s to 1.712 s) because the cook step is about 1.3 s of 1.7 s and was not touched. A reviewer who reads AC-5 as the frozen fixture can tick it on this evidence. This note does not tick it, because the shipped bake is not 30% faster. + +2026-10-12 cook-step speedup, AC-5 still open. The cook now bounds its LOD simplify workers (`packages/assets/src/lod/simplify-pool.ts`), reuses accessor metadata and buffers, cooks only LOD0 for terrain chunks and batches unique writes (`generate.ts`, `eligibility.ts`, `terrain-world.ts`). Quiet-host measurement, Node wall clock, one discarded warm-up per arm, 8 strictly alternating runs, load 0 to 1: HEAD mean 2.760 s (median 2.757), patched mean 2.401 s (median 2.384), so 13.0% faster by mean and 13.5% by median. All 22 output files are SHA-256 identical. A first measurement at host load 12 to 18 showed 40%, which the quiet re-run did not reproduce, so the 30% target is not met. Package suites pass 909 tests; golden hashes pass; the only typecheck error is the known `terrain-world.spec.ts:471`. diff --git a/docs/PRDs/open-world/PRD-580-strata-starter-kit-art-bar.md b/docs/PRDs/open-world/PRD-580-strata-starter-kit-art-bar.md index 968f336a5b..57d8fbf18b 100644 --- a/docs/PRDs/open-world/PRD-580-strata-starter-kit-art-bar.md +++ b/docs/PRDs/open-world/PRD-580-strata-starter-kit-art-bar.md @@ -1,6 +1,6 @@ # PRD-580 — Strata starter-kit art bar -**Status:** NOT STARTED +**Status:** IN PROGRESS **Priority:** P2 — Five starter kits still need a score of at least 4 on every visual rubric row. **Complexity:** 3 (LOW); risk override: none **Owner:** ThreeNative maintainers @@ -16,6 +16,8 @@ A fresh judge subagent must score at least 4 on every rubric row for each kit. Ground, trees_rocks and lighting_depth are required. Water and artifacts also apply where the kit rubric names them. A nonblank capture is not art acceptance. +Owner-directed work (2026-10-12): João requested two dedicated arms for realistic procedural trees and ground vegetation, with distinct species/variations and high-quality Poly Haven textures that remain inexpensive to render. Tree work owns tree geometry/materials; ground work owns grasses, ferns, shrubs and flowers. Both preserve a sealed source baseline and use existing instancing, LOD, wind and asset preparation, followed by two headed before/after captures and an independent visual judge. Preview improvements must later reach the generated starter kits through newly versioned assets; published archive 0.1.0 remains immutable. The existing rubric and per-kit runtime gates remain required. + ## Solution Keep appearance in the editable starter source under @@ -43,7 +45,7 @@ All rows must reach 4. Lowering that threshold needs an owner decision first. ### Phase 1: Forest art -**Status:** NOT STARTED +**Status:** IN PROGRESS **Files:** `packages/terrain/starter/forest/`, shared CC0 starter assets and the existing forest kit scene. **Implementation:** Replace faceted crowns and improve rock geometry, ground detail, shadows and sky. Keep all appearance editable. @@ -113,3 +115,32 @@ Those proofs do not establish visual acceptance. Tundra ice and final coastal waves/shore foam remain here. The later coastal spectral surface scored water 3; that observation does not prove the wave/foam art bar. The later tundra basin change scored water 3, with all other rows still below 4. + +2026-10-12 procedural-tree candidate `e54c37a15` remains unaccepted. It adds Norway spruce, silver fir and Scots pine forms, a 697,357-byte Poly Haven twig/relief atlas and three distance levels; 42 focused tests and TypeScript pass. Two headed BEFORE/AFTER repeats at 1920×1080 on NVIDIA/Turing, resolution scale 1 and MSAA 4, received fresh read-only **REGRESSION** verdicts for forest-start, meadow-close and overview in both pairs. The judge found severely reduced crown coverage, exposed regular radial branch spokes and distant trees reading as dead standing timber. Fine twig cutouts do not offset missing foliage volume. Camera-hop admission also varies between meadow captures; ground vegetation differences were excluded from the tree verdict. The capture scenarios retain their console/network diagnostics failures for expected local-only asset 404s; these runs are visual evidence, not runtime acceptance. Candidate code is retained on its separate tree branch; a bounded crown repair is in progress. Existing captures: `artifacts/tree-captures/{before-1,before-2,after-1,after-2}` in the tree lane. + +Ground candidate `3a07716b8` remains unaccepted after its earlier REGRESSION/NEUTRAL comparison. CPU inspection of the four calibrated captures found camera-hop `streamingAdmissionBacklog` values 137, 153, 151 and 146 at the screenshot despite identical placement counts and camera pose. The local capture fixture now waits, with bounded deadlines, for the existing admission, prewarm and in-flight load counters to drain at each view; shipped scenario validation passes for all ten steps. Geometry is unchanged pending comparable captures. This diagnosis does not establish a visual improvement or close a kit art box. + +Tree repair `8e8a901d7` remains unaccepted after two new settled runs per arm. All three views use NVIDIA/Turing, 1920×1080, scale 1 and MSAA 4, with admission/prewarm/load/failure counters zero. Independent read-only review of all twelve labelled images grades meadow-close IMPROVEMENT, forest-start NEUTRAL and overview REGRESSION in both pairs; overall REGRESSION. Fine conifer sprays improve close detail, but horizontal shelves, flattened tops, exposed poles and sparse distant crown mass remain artificial. The two sparse-crown failures invalidate the assumption that overlapping twig cards alone preserve convincing crown volume. The tree branch and captures are retained separately; no candidate geometry is integrated. Existing repaired comparison captures: `artifacts/tree-captures/{settled-before-1,settled-before-2,repaired-after-1,repaired-after-2}` in the tree lane. + +Settled ground review of `3a07716b8` restores flowers in all four arms with zero streaming counters, but still grades both meadow-close and foreground pairs REGRESSION: rigid broad blades read as flat cut strips and dark planar faces. Admission explained missing flowers, not the rejected grass shape. A narrow curved-blade repair `8407e0cf4` passes 59 focused tests and TypeScript with unchanged grass triangle/vertex counts, texture bytes and draw structure. Two calibrated repaired AFTER captures are complete; fresh independent review is pending. Diagnostic local-asset 404 failures remain failures. No ground art acceptance is claimed. + +Repaired ground `8407e0cf4` is visually accepted and integrated as `1b7e2cee6`/`ce7f08b4a`: independent read-only inspection of all eight actual images grades meadow-close and foreground **IMPROVEMENT in both pairs and overall**. Narrower bending blades remove the broad ribbon/spear appearance; low growth and taller tufts show clearer clump/height variation. Repeated stiff V-shaped fans and modest species distinction remain, so this is an improvement verdict rather than full kit-art acceptance. Wind phase was excluded. All eight public images return HTTP 200 and byte-match the captures. Parent integration checks pass 55/55 focused ground/LOD/streaming/surface tests, example TypeScript and scoped Biome (two existing complexity advisories). The capture scenarios still fail diagnostic policy on optional local-asset 404s; no runtime or cooked-kit acceptance is claimed. + +Ground pair 1: [BEFORE meadow-close](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/ee7fb90a8f14175edc70238e72f7c42823b9d9b9/pr483/ground-vegetation-before-1-meadow-close.png), [AFTER meadow-close](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/ee7fb90a8f14175edc70238e72f7c42823b9d9b9/pr483/ground-vegetation-after-1-meadow-close.png), [BEFORE meadow-foreground](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/ee7fb90a8f14175edc70238e72f7c42823b9d9b9/pr483/ground-vegetation-before-1-meadow-foreground.png), [AFTER meadow-foreground](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/ee7fb90a8f14175edc70238e72f7c42823b9d9b9/pr483/ground-vegetation-after-1-meadow-foreground.png). +Ground pair 2: [BEFORE meadow-close](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/ee7fb90a8f14175edc70238e72f7c42823b9d9b9/pr483/ground-vegetation-before-2-meadow-close.png), [AFTER meadow-close](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/ee7fb90a8f14175edc70238e72f7c42823b9d9b9/pr483/ground-vegetation-after-2-meadow-close.png), [BEFORE meadow-foreground](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/ee7fb90a8f14175edc70238e72f7c42823b9d9b9/pr483/ground-vegetation-before-2-meadow-foreground.png), [AFTER meadow-foreground](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/ee7fb90a8f14175edc70238e72f7c42823b9d9b9/pr483/ground-vegetation-after-2-meadow-foreground.png). + +2026-10-13 oblique tree LOD candidate remains rejected: isolated `6a1de2369` receives two new completed NVIDIA1920x1080 captures, scale1/MSAA4 and all four streaming counters zero at all three poses. Both new scenarios and capture validators pass. A fresh read-only judge inspects all12 labelled BEFORE/AFTER images: forest-start REGRESSION, meadow-close IMPROVEMENT, overview REGRESSION in both pairs, overall REGRESSION. Finer photographic foliage and crown variation do not compensate for flattened shelves, exposed trunks and lost distant crown mass. This is the third rejected tree candidate; the assumption that twig-card orientation/density preserves healthy crown volume is now doubtful. No further equivalent card tweak is attempted, no tree code/assets are integrated, and kit/native art acceptance remains open. Accepted ground vegetation is unchanged. + +2026-10-13 forest kit overview on merged branch `9ab9d5f12` (no box ticked): `KIT=forest test:kit` passes on `nvidia/turing` (819 frames, 8,791 firs drawn), and the overview frame shows distant firs as slanted dark streaks (frames: PR #483 comment 6155770975, `strata-hardening-screenshots` `pr483/forest-kit-*-merged-9ab9d5f12.jpg`). Impostors off is not a fix: that overview frame exceeded the 120 s screenshot timeout. Cause by code reading only, not measured: the impostor atlas is `IMPOSTOR_VIEW_GRID = 4`, so 16 octahedral views of which 9 are distinct (`packages/core/src/render/world-impostor.ts:40-80`), and the fragment blends three corners (`world-impostor-surface.ts:508-537`), so a 40 degree aerial eye puts about 18% of its weight on a view about 76 degrees away. No grid-size or tilt option exists on `IWorldCellsLoadOptions`. Next: capture one aerial pose with the grid at 8 (engine change, atlas bytes x4 against `IMPOSTOR_ATLAS_BYTES`), judge it before/after, and decide whether the grid becomes a game option. + +2026-10-13 aerial impostor streaks fixed in the engine (layer: engine, `packages/core/src/render/world-impostor.ts`; no box ticked): `IMPOSTOR_VIEW_GRID` 4 to 8, so the atlas holds 64 octahedral views (49 distinct) instead of 16 (9 distinct). The same `KIT=forest pnpm --filter strata-terrain-preview test:kit` pose, `nvidia/turing`, private Xvfb, two runs per arm, kit passes in both arms. Visual judge (fresh read-only Sonnet subagent, BEFORE and AFTER burned in): ground NEUTRAL, edge NEUTRAL, overview IMPROVEMENT, lake IMPROVEMENT (mild), OVERALL IMPROVEMENT, runs agree. Judge caveats, not resolved: AFTER distant foliage reads slightly lighter and lower in contrast (a luminance or coverage number is owed), and one dark streak on the lake ridge remains. Cost: one atlas is 11.2 MB instead of 2.8 MB, so the default 128 MB budget holds 11 atlases instead of 48 (a world with more impostor species keeps its source LODs for the rest), and a bake takes 64 render-cadence updates instead of 16. Specs now derive from the exported constants (63 impostor tests pass). Images: `pr483/impostor-grid8-{ground,edge,overview,lake}-run{1,2}-BEFORE-AFTER.jpg` on `strata-hardening-screenshots`. Native rendering unverified: the native host runs the same TypeScript, but no `--target desktop` run was made. + +2026-10-13 impostor grid 8 luminance check (answers the judge's caveat, no box ticked): overview pose mean luma 0-255 over the forested lower band is 91.6 and 90.6 in the two BEFORE runs and 95.3 and 95.4 in the two AFTER runs (whole frame 127.3 and 126.7 to 130.4 and 130.5). AFTER is about 4 levels (4.7%) lighter, which is the removed dark streaks; no other pose was measured. + +2026-10-14 owner rejection in the actual generated consumer: the new close-tree screenshot shows a round, broad root base nearby and black conical trunks farther away; the owner reports tree shape changing on approach and rejects the scene's light, shadows and atmosphere. Source-asset flare and static export preservation do not establish correctness of distance-selected LODs. A single cooked-tree diagnostic confirms three actually rendered arms (shared Physical materials, four texture-preserving Basic materials, and existing forest sky dressing) retain the dark sparse appearance; no lighting-only repair is accepted. Opus 5.5 high is assigned the actual LOD/appearance fix, with the shared-mechanism versus authored-render-source boundary preserved. The surface table is transferred intact, including distance albedo blend amount 0.6 and persistent fine normal detail. Full-world art and all rubric/visual acceptance boxes remain open. + +2026-10-14 owner acceptance update: the actual generated forest must reach the requested AAA scene bar with proper lighting, shadows, textures, tree transitions and atmosphere. Relative improvement or passing implementation tests alone is insufficient. Send matching headed hardware captures to a fresh read-only visual judge first; require both improvement and an explicit assessment against that scene-quality bar. Attach the accepted screenshots to PR #483, then send one attention-ping completion notification as explicitly requested by the owner. No notification or visual acceptance is claimed yet. + +2026-10-14 Opus shadow candidate, no visual box ticked: generated lighting accepts measured outer shadow windows and a scene-supplied VirtualShadowNode class, retaining the ordinary starter arena sun/map by default and disposing shadow/light resources after postprocessing. The near window remains 14 m at desktop4096/mobile2048; the generated forest handoff supplies full-world diagonal coverage and streamed-region invalidation. Lighting tests pass 22/22; the formerly failing scaffold mobile asset case passes after a fresh assets build. Two clean hardware BEFORE captures match the fixed-character pose/camera exactly. These are implementation results only: high-end scene quality, soft-shadow transitions, proper atmosphere, ground texture/vegetation and the independent visual verdict remain open. + +2026-10-14 terrain sampler-input normalization, bounded CPU candidate ready for runtime, no box ticked: `cookTerrainWorld` now gives each map kind (diff, nrm, orm, h) one size and one input encoding across all layers. A kind whose maps already share size, format and alpha keeps its authored bytes. Otherwise every map in the kind, the largest included, becomes lossless PNG at the largest width and height: decoded pixels kept, smaller maps resampled up, never down. This normalizes inputs only; the asset compiler still picks each texture's codec from its content. Both cook callers (`packages/terrain/src/mcp/cook.ts`, `packages/terrain/src/kit.ts`) route through this function and did not change; the MCP path loads the built `@threenative/assets`, which this lane did not rebuild. Real forest sources: grass, dirt and moss diff/nrm are 1024×1024 JPEG, rock is 512×512 JPEG. The published 0.1.0 archive and `starter-assets.json` are untouched. The regression cooks four layers with a half-size rock across all four kinds: every cooked map is PNG, decodes through `decodeImageBytes` to 64×64 RGBA8, the larger maps' decoded RGBA equals their source decode, and core `stackLayers` returns a 4-deep array per kind through a copying renderer stub. The size assertion fails with the normalization disabled (32×32 versus 64×64). Focused specs pass: assets terrain-world 30/30 and terrain-editor-cook 2/2. CPU compile probe on the eight real forest maps (current source `compileAssets`, web, budget none, installed Basis transcoder): all eight compile to KTX2 at 1024×1024 with 11 mips. The normal kind is compatible: four UASTC maps with one signature, which saves 3 samplers. The diffuse kind is still mixed: grass, dirt and moss are UASTC and the resampled rock is ETC1S, so it does not stack. Compiled bytes: diff 4,241,244 (PNG inputs 10,247,896), nrm 5,360,040 (PNG inputs 10,364,486), total 9,601,284. Expected effect, not measured: 19 samplers drop to 16, at the portable limit of 16; the owner accepts 16 as the nearest correctness target. Not every acceptance criterion is complete. The mandatory next step for root: actual NVIDIA and Intel runtime runs with zero WebGPU validation errors. No GPU, browser, native, visual or 60 fps run happened in this lane; those boxes stay open. diff --git a/docs/PRDs/strata-terrain-hardening/PRD-583-a-strata-world-bakes-headlessly-into-an-instanced-glb.md b/docs/PRDs/strata-terrain-hardening/PRD-583-a-strata-world-bakes-headlessly-into-an-instanced-glb.md new file mode 100644 index 0000000000..cc0723347e --- /dev/null +++ b/docs/PRDs/strata-terrain-hardening/PRD-583-a-strata-world-bakes-headlessly-into-an-instanced-glb.md @@ -0,0 +1,139 @@ +# PRD-583 — A Strata world bakes headlessly into an instanced GLB + +**Status:** PARTIAL +**Priority:** P1 — No headless path turns a Strata recipe into a GLB with its trees and rocks; PRD-584's agent handoff cannot start without AC-1 to AC-4. +**Complexity:** 5 (MEDIUM); risk override: none +**Owner:** ThreeNative maintainers +**Depends on:** None +**Parent:** [PRD-466](../done/PRD-466-strata-terrain-threejs-integration.md), [PRD-468](../done/PRD-468-strata-world-controls-and-asset-imports.md) +**Epic:** [Strata terrain hardening](README.md) + +## Context + +The owner's goal for Strata (2026-10-05): another agent says "I am making an xyz game, generate a W +terrain" and gets "a glb or something ready to use". Audit of `2d2124792` (2026-10-10) found three +gaps on that path: + +- `exportWorldGLB` (`packages/terrain/src/export.ts`) needs browser `FileReader` and canvas + (`packages/terrain/README.md`), so a Node job or an MCP server cannot call it. +- The same encoder rejects instancing: `export.ts:167-169` throws "resolve skinning/instancing + into static meshes first". A forest of 10 000 trees becomes 10 000 mesh nodes, so 10 000 draws + in the consumer. +- `bakeWorldPackage` (`packages/terrain/src/core/world-package.ts:221`) is headless and writes + `heightmap.u16`, `placements.bin`, `splat.rgba` and `world.json`, but no GLB, no LOD and no + cell proxy. The legacy `makeExport(..., "glb")` holds terrain geometry only. + +The cook in `@threenative/assets` already holds every piece this needs, in Node: gltf-transform +(`packages/assets/package.json:49-51`), the `EXT_mesh_gpu_instancing` compaction +(`packages/assets/src/passes/compact.ts:104`), meshopt simplification (`modelPass({ simplify })`) +and the per-cell HLOD proxy (`packages/assets/src/world/proxy.ts`). + +## Solution + +Add one cook entry in `@threenative/assets` (it carries the glTF dependencies the terrain package +must not inherit): `cookTerrainWorld({ state, species, out })`. + +1. Terrain: `bakeTerrain(state)` chunks become GLB meshes with the kit's PBR layer maps, written as + image bytes that already exist (JPG/PNG/KTX2). No canvas, no `FileReader`. +2. Scatter: each species GLB is read once. Placements of one species and one LOD become one node + with `EXT_mesh_gpu_instancing`. LOD levels come from the species' own `*-mid`/`*-lod1` GLBs when + present, else from `modelPass({ simplify })`. +3. Output: `world.glb` (single file, loads in vanilla `GLTFLoader`) and the existing world package + directory with the species GLBs and LODs filled in, for `WorldCells` streaming. +4. Every placement samples the baked heightmap. A tree whose base misses the ground by more than + one heightmap texel fails the cook by name (catches a flipped Z or transposed grid; audit H4). + +`exportWorldGLB` stays the browser editor's encoder for now. When the cook is the one path the +editor uses too, it is deleted (PRD-588 decides). + +```mermaid +flowchart LR + R[recipe.json] --> E[Terrain.evaluate] + E --> B[bakeTerrain + bakeWorldPackage] + S[species GLBs + LODs] --> C[cookTerrainWorld - @threenative/assets] + B --> C + C --> G[world.glb - EXT_mesh_gpu_instancing] + C --> P[world/ package - WorldCells] +``` + +## Acceptance Criteria + +- [x] AC-1 [local]: `cookTerrainWorld` runs under plain Node 22 with no DOM globals and writes `world.glb` for the forest kit. proof: `npm exec --yes --package=node@22 -- node src/terrain/forest/bake.mjs --out assets/terrain/forest` in an isolated kit install — Evidence: 2026-10-10 Node v22.23.3, an isolated minimal-template game installed from the packed core/physics/assets/playtest/CLI/terrain tarballs (terrain override resolves the asset pipeline's transitive edge), with `document`/`window`/`FileReader`/`HTMLCanvasElement` all undefined: `bake.mjs` exit 0 and `world.glb` 24 641 592 B. The closure fix is in `examples/strata-terrain-preview/scripts/verify-starter-kit.mjs` (terrain tarball added to the scaffold `packageSources`, which writes the pnpm override) and `verify-game-handoff.mjs` (same tarball as an override-only entry). +- [x] AC-2 [local]: Each species-and-LOD pair is one `EXT_mesh_gpu_instancing` node; node count does not grow with tree count. proof: `pnpm exec vitest run packages/assets/__tests__/terrain-world.spec.ts` — Evidence: 2026-10-10 17 tests pass; the default `TerrainWorld` scene holds near nodes only and each species-and-LOD pair is one node in its own named scene (`fir_lod1`, `boulder_lod1`), every node carrying all logical placements; the all-scene census is constant 1 000→10 000, one instancing node for a single-mesh species and one flattened node (four primitives) for a two-mesh species. `instancedMeshCount` stays the default-scene count for compatibility; the all-scene test walk is authoritative and `nodeCount` counts every scene. +- [x] AC-3 [local]: A vanilla three `GLTFLoader` loads `world.glb` into `InstancedMesh` objects whose instance count equals the bake's placement count. proof: `CONSUMER_GAME=skip pnpm --filter strata-terrain-preview test:consumer` — Evidence: 2026-10-10 exit 0 in 23 s; the cooked forest GLB loads in the isolated packed install as `fir_instances` (4 primitives × 3 100), `fir-c_instances` (4 × 1 400) and `boulder_instances` (1 × 320), every child count equal to its manifest run count and 0 per-tree mesh copies. The parent subsequently passed the unskipped packed-game install, build and WebGPU playtest on NVIDIA Turing (2026-10-10); that game stage does not prove the cooked forest's layered material appearance. +- [x] AC-4 [local]: A placement mirrored in Z fails the cook with a named contact error. proof: `pnpm exec vitest run packages/assets/__tests__/terrain-world.spec.ts` — Evidence: 2026-10-10 12 tests pass, including `Placement '' of species 'fir' contact check failed ... mirrored-Z`. +- [x] AC-5 [local]: The world package written next to it passes `validateWorldPackage` and lists a GLB and at least one LOD per species. proof: `pnpm exec vitest run packages/assets/__tests__/terrain-world.spec.ts` — Evidence: 2026-10-10 the spec reads the cook's `world.json` and `placements.bin`, calls core `validateWorldPackage` with `placementsByteLength` (and `heightmapByteLength`) and gets `{ ok: true, errors: [] }`, and every species lists `models/.glb` plus a LOD whose bytes exist on disk. + +## Integration Ledger + +| Capability | Reachable consumer/trigger | Replaces / disposition | Evidence | +|---|---|---|---| +| Headless world GLB | `cookTerrainWorld` from `@threenative/assets`; kit `bake.mjs`; PRD-584 MCP tool | Browser `exportWorldGLB` stays for the editor until PRD-588 | AC-1 (Node 22 kit), AC-3 | +| Instanced scatter in GLB | Any `GLTFLoader` consumer | Per-tree mesh nodes | AC-2 | + +## Execution Phases + +#### Phase 1: terrain and scatter into one instanced GLB +**Status:** PARTIAL — layered surface appearance remains pending +**Files:** `packages/assets/src/world/terrain-world.ts` (new), `packages/assets/src/index.ts`, `packages/assets/__tests__/terrain-world.spec.ts` +**Implementation:** read `bakeTerrain` chunks and placements; group by species and LOD; write `EXT_mesh_gpu_instancing` nodes with gltf-transform; pass existing image bytes through; contact check per placement. +- [x] Node-only cook writes `world.glb`. proof: `pnpm exec vitest run packages/assets/__tests__/terrain-world.spec.ts` — 2026-10-10: 7/10 passed; cook writes `world.glb` for the evaluated-terrain fixture under Node. +- [x] Node count is constant from 1 000 to 10 000 trees. proof: same spec — 2026-10-10: node count equal at 1k and 10k, one instancing node for a single-mesh species. +- [x] Mirrored placements fail by name. proof: same spec — 2026-10-10: mirrored-Z case throws `Placement '' of species 'fir' contact check failed`. + +- [ ] Preserve the authored layered PBR surface and resolve `texturesDir` headlessly. proof: layered cook spec plus headed before/after visual judge — pending; the current GLB uses only the first layer. + +#### Phase 2: LODs and the world package +**Status:** done +**Files:** `packages/assets/src/world/terrain-world.ts`, `packages/terrain/starter/forest/bake.mjs` +**Implementation:** prefer authored `*-mid` / `*-lod1` GLBs; fall back to `modelPass({ simplify })`; fill `assets[].glb` and `lods` in `world.json`. +- [x] World package validates and carries LODs per species. proof: `pnpm exec vitest run packages/assets/__tests__/terrain-world.spec.ts` — 2026-10-10 12 tests pass; `validateWorldPackage` returns `{ ok: true, errors: [] }` and each species lists a GLB plus a LOD. +- [x] The forest kit bake emits both outputs. proof: `node packages/terrain/starter/forest/bake.mjs` — 2026-10-10 exit 0 in 166 ms; writes `world.glb` (15 874 700 B) and the world package (`world.json`, `heightmap.u16`, `placements.bin`, `splat.rgba`, `terrain-table.json`, `models/{fir,boulder,fir-c}.glb` with LODs); 3 100 fir, 1 400 fir-c and 320 boulder placements. + +#### Phase 3: consumer load +**Status:** PARTIAL — headed runtime and visual proof remain pending +**Files:** `examples/strata-terrain-preview/scripts/verify-consumer.mjs` +**Implementation:** load the cooked `world.glb` with stock `GLTFLoader`; assert instance counts and that no mesh is a per-tree copy. +- [x] Vanilla loader gets `InstancedMesh` with the bake's counts. proof: `CONSUMER_GAME=skip pnpm --filter strata-terrain-preview test:consumer` — 2026-10-10 exit 0; the generated consumer script reads the cooked `world.json`, derives each species' logical count from its runs, and checks each child `InstancedMesh` count (fir 4×3 100, fir-c 4×1 400, boulder 1×320) with 0 per-tree copies and no hardcoded values. + +- [ ] Render the cooked world in the headed WebGPU consumer and pass the visual judge. proof: two same-pose runs per arm with checked adapter and labelled images — pending; the first baseline capture was black and is rejected. + +## Current plan + +1. Reuse `bakeTerrain` and `bakeWorldPackage`; preserve source glTF textures/transforms and verify real terrain, splat and species batching. +2. Repair the consumer script and run its actual forest bake/load proof after the focused assets gate passes. +3. Run required runtime and visual proof; keep every unsupported claim open. + +Initial review: six weak unit tests passed; `node --check examples/strata-terrain-preview/scripts/verify-consumer.mjs` failed at line 584, and assets typecheck failed with three errors. Cook also dropped model textures/transforms, omitted terrain without custom LOD data and zeroed splat bytes. That initial completion claim was rejected; corrected focused results follow. + +2026-10-10 focused correction: the cook uses `bakeTerrain` and `bakeWorldPackage`, preserves source image bytes and every source-node world transform through glTF-transform copying/flattening, rejects skinned or animated species, and produces one near-detail instance node per species. The 11-test spec passes (including reused source-mesh nodes, 1 000/10 000 placement census and exact splat/placement bytes); assets typecheck and scoped Biome pass, with one nonfatal cognitive-complexity warning. Node is v20.19.6, so Node 22 and the forest-kit claim remain unverified. Authored sibling LOD discovery, multi-layer terrain PBR appearance, actual forest bake/consumer and runtime/visual proof remain pending. No full-PRD completion is claimed. + +2026-10-10 bounded integration: rebuilt `packages/assets` and `packages/terrain` so their `dist` matches source, then re-ran the forest kit. `packages/terrain/starter/forest/bake.mjs` now imports the public `cookTerrainWorld`, passes the real evaluated state (no `state.lods` injection), lets the cook own `world.json` and the species models, and writes only the surface table, its textures and `sky.hdr` beside them. The runner passed Node 20.19.6: `node --check` on both scripts, scoped Biome, assets typecheck, and `pnpm exec vitest run packages/assets/__tests__/terrain-world.spec.ts` (12 tests). The forest bake wrote `world.glb` (15 874 700 B) with 3 100 fir, 1 400 fir-c and 320 boulder placements in 166 ms. `CONSUMER_GAME=skip pnpm --filter strata-terrain-preview test:consumer` passed in 23 s and loaded that GLB through stock `GLTFLoader` at the manifest's per-species counts with 0 per-tree copies. The unskipped consumer run failed at the PRD-468 scaffold `pnpm install` (exit 1) in this environment. Authored sibling LOD discovery, full multi-layer surface appearance, Node 22 and runtime/visual proof remain pending; AC-1 stays open. No full-PRD completion is claimed. + +2026-10-10 integration: the forest bake produced a 15 874 700-byte GLB and a valid world package (3 100 fir, 1 400 fir-c, 320 boulder). The scoped parser proof ran with CONSUMER_GAME=skip; the ordinary test:consumer command still fails during the separate PRD-468 scaffold install and is not game acceptance. A missing cooked forest fixture now fails closed. All-scene species/LOD census and full layered terrain appearance remain pending. + +2026-10-10 cook follow-up: 17 focused tests pass (336 ms), assets typecheck passes and scoped Biome passes with one complexity warning. Alternate LODs live in named scenes while the default scene renders near detail once; every species/LOD pair has one instancing node. Empty species fail before output. Contact samples the baked uint16 heightmap with one quantization-step tolerance plus float rounding. Node 22.23.3 was resolved by an isolated npm launcher, but its forest cook failed because the standalone kit could not resolve @threenative/assets; AC-1 stays open until the kit/tarball dependency closure is wired. Multi-layer material appearance and runtime/visual proof remain pending. + +2026-10-10 caller/dependency closure repair: the red failures had one cause — `@threenative/assets@0.3.5` depends on `@threenative/terrain`, which was absent from the local tarball closures, so both installs tried the registry and got `ERR_PNPM_FETCH_404 @threenative/terrain`. `verify-starter-kit.mjs` now packs the terrain tarball into the scaffold `packageSources` (writing the pnpm override for assets' transitive edge) before the kit's own `pnpm add -D`; `verify-game-handoff.mjs` packs the same tarball and writes it as an override-only entry, so the game still declares and installs no authoring package. `packages/terrain/starter/forest/README.md` names both `@threenative/terrain` and `@threenative/assets` plus the `three` peer. Node 22.23.3 forest proof in an isolated kit: `bake.mjs` exit 0, `world.glb` 24 641 592 B, no DOM globals — AC-1 ticked. `node --check` and scoped Biome pass on both scripts. The unskipped full consumer (scaffold build + WebGPU playtest) was not run; the root job runs it after commit. Multi-layer surface appearance and runtime/visual proof remain pending. + +2026-10-10 direct checkout command: root now declares the existing @threenative/assets workspace package as a dev dependency (terrain does not depend on assets). node packages/terrain/starter/forest/bake.mjs also passes from the checkout and emits the instanced GLB plus package, so the documented direct command has an explicit dependency closure. + +2026-10-10 parent integration: offline frozen installation, assets build/publint and 17 terrain-world tests pass (1.05 s test time). The direct forest command emits the 24 641 592 B GLB in 595 ms. The full consumer first stopped before the game stage because the default cooked fixture was absent; after the documented bake, the unskipped rerun passes (exit 0). Its scaffolded game playtest reports WebGPU, NVIDIA Turing and no direct authoring-package dependency; the forest stock-loader census reports 4 820 placements and zero per-tree copies. Layered-surface and headed forest visual acceptance remain open. + +2026-10-10 fail-closed hardening (bounded): `packages/terrain/starter/forest/bake.mjs` refuses a bare, empty or flag-valued `--out`/`--assets` before any directory is created; `authoredSiblingLods` in `packages/assets/src/world/terrain-world.ts` propagates any non-ENOENT stat error instead of reading it as an absent sibling. proof: `pnpm exec vitest run packages/assets/__tests__/terrain-world.spec.ts` 18 tests pass; `pnpm exec vitest run packages/terrain/__tests__/starter-kit.spec.ts -t "refuses a bare"` 1 test passes; scoped Biome clean except the pre-existing cognitive-complexity warning. The four sibling kits (alpine, coastal, desert, tundra) still carry the identical bare `flag` parser and were left out of this bounded change. + +Independent review: the CLI regression executes the actual checkout script from a temporary cwd (no TMPDIR lookup workaround), covers all six invalid flag forms, and leaves no world directory. `pnpm exec vitest run packages/assets/__tests__/terrain-world.spec.ts packages/terrain/__tests__/starter-kit.spec.ts -t "cookTerrainWorld|refuses a bare"`: 19 passed, 9 deliberately unselected, 8.67 s. Assets typecheck passes; scoped Biome has only the existing complexity warning. The broader starter golden hash/size check remains failing and is not claimed. + +2026-10-10 sibling closure (bounded): the earlier note that the four sibling kits (alpine, coastal, desert, tundra) still carry the identical bare `flag` parser no longer holds. Each of the four `bake.mjs` now carries the exact seven-line guard from `packages/terrain/starter/forest/bake.mjs` (bare, empty or `--`-valued `--out`/`--assets` throw before any directory is created), with the per-kit defaults unchanged. The `refuses a bare` regression is parameterized over all five biome scripts, executing each actual checkout `bake.mjs` from a temporary cwd and asserting all six invalid forms throw and the cwd stays empty. proof: `pnpm exec vitest run packages/terrain/__tests__/starter-kit.spec.ts -t "refuses a bare"` 5 passed, 9 skipped; scoped Biome clean on all six files. Multi-layer terrain PBR appearance and runtime/visual proof remain open; no full-PRD completion is claimed. + +2026-10-10 EPIC integration: parent reran the cook and five-kit flag regressions after integrating both commits: 23 passed, 9 deliberately unselected in 3.94 s. No runtime or appearance acceptance is claimed. + +2026-10-11 texture-storage component integrated without an acceptance tick: the final combined GLB now uses the installed glTF-Transform TEXTURE-only dedup pass after the unchanged prune pass. The regression reproduced three identical images before the fix and one afterward, while preserving image bytes/MIME, each material sampler/UV offset, authored LOD material count, placements and loaded instance counts. The focused cooker suite passes 19/19; assets TypeScript and scoped Biome pass (one pre-existing complexity warning). The rejected layered-PBR/noise work was not included. Node GLTFLoader has no image decoder in this test; actual browser/native rendering, visual judging, generated-kit byte hashes and the complete size/performance acceptance remain open. + +2026-10-11 package-surface handoff (no visual acceptance tick): `cookTerrainWorld` resolves all supplied layer maps through `texturesDir`, rejects escaping paths and unsafe layer IDs, names missing files, and writes unchanged albedo/normal/ORM/height bytes under `textures/`. Terrain GLB nodes and meshes carry `{ "tnTerrainSurface": "world.json" }` when the package names its surface table; the first-layer vanilla GLTFLoader fallback stays intact. Forest and MCP cooks pass every authored layer to the shared cooker. The new public `applyTerrainSurface(root: Object3D, options: ILoadTerrainSplatOptions): Promise` at `@threenative/core/world` takes the loaded GLB URL, resolves the extras pointer through the existing asset/fetch path, reuses `loadTerrainSplat`, replaces only marked terrain, and disposes replaced fallback materials once while retaining materials still used by unmarked meshes. The MCP response now prints four consumer lines (import, load, add, apply). Capability manifests/reference were regenerated. Red regressions reproduced ignored non-base maps and the missing helper; green loaded-GLB proof runs without document/window and uses the existing stubbed texture convention. The exact requested focused Vitest command passes 88/88 (assets 22, starter-kit 19, terrain-MCP 29, core splat 18); assets/core typechecks and assets/terrain/core builds including publint pass. Terrain typecheck fails in untouched preview sources: six errors in `render/horizon.ts` from missing `src/world/horizon.json` and consequent unknown types, plus the four-argument call at `render/texturePreparation.ts:116`. Scoped Biome passes with three cognitive-complexity warnings. Comparing before/after forest packages found only `world.glb` changed: SHA256 `c23570826bc1100f13768ae57c7922b50e321327ec810ccb426e4b7fea818700` -> `7b327a774e4f6e1e7fa33f010db0231fe73cfb910c67b76da51a53a12d7e573f`; only the forest GLB golden pin was added, and all existing pins and other package bytes remain unchanged. No atlas, CPU blend, appearance-value change, dependency or lockfile change. Browser/GPU captures and actual native runtime proof were not run; headed visual acceptance remains open for the coordinator. All commits remain local. + +2026-10-12 package half of the layered-surface box, already proven by existing cook specs and left unticked on purpose. `packages/assets/__tests__/terrain-world.spec.ts` has `preserves every relative layer texture byte-for-byte and marks terrain nodes` (authored layers and the node and mesh `tnTerrainSurface` pointers) and `names a missing non-base texture before writing a package`; both pass, and the assets suite passes 590 with two worker-pool cases that pass 6 of 6 when rerun alone. `texturesDir` is an explicit option resolved with `path.resolve`, so a relative value means relative to the caller's working directory, which is the intended headless behavior. What keeps the box open is the other half of its proof: the cooked GLB still draws only the first layer, and the headed before and after visual judge has not run. + +2026-10-13 native packaging reproduction (engine layer, `packages/assets/src/compile.ts`): the retained accepted grid8 forest kit fails ordinary `build:desktop` against the freshly built V8 host. Preflight rejects `terrain/forest/world.4e55303c.glb` with 34 interleaved vertex views. The ordinary model pass already selects separate buffers for native targets, but cooked-world recognition deliberately bypasses all model passes and hashes only `terrain-world-v1`, so web and native reuse the same representation. Bounded repair: keep web byte-identical; use the existing model writer solely for native vertex layout, disabling optimization, texture conversion and LOD generation; distinguish native cache identity and report the pass that actually ran. Regression must prove a web-to-native build does not reuse the web file, geometry/material/image data survive, and repeat native compilation caches correctly. Then reinstall the changed package into the retained clean kit and rerun desktop packaging/playtest. No preflight bypass, performance claim or acceptance tick. + +2026-10-13 native layout repair verified: the existing model writer now emits separate vertex buffers for cooked worlds on desktop/Android/iOS, preserves authored materials (including duplicates), geometry and image bytes, and uses a distinct native cache identity. Web output remains byte-identical. Focused assets tests pass 115/115; assets typecheck and build/publint pass. Reinstalling the packed assets package into the retained grid8 forest game makes ordinary `build:desktop` pass against V8 host SHA256 `de2d9cf932ed8422b3241756d8361562bf63a53070502aafc3eecebfb83e4242`, without a preflight bypass. A desktop diagnostic scenario retains all behavior/resource/performance assertions and omits only unsupported native visual-metric evaluation: it reaches runtime-ready on NVIDIA RTX 2080/Vulkan, exercises the driven collision path and all four camera views, and produces nonblank 1280x720 captures (ground manually inspected). The scenario remains FAILED: render p95 242.09 ms exceeds 40 ms, and lake GPU/frame measurement presence rows fail. It used fixed-step timing and another lane has an active native renderer, so this is not an isolated steady-state performance result. Browser visual assertions, native visual judging and performance acceptance remain open. diff --git a/docs/PRDs/strata-terrain-hardening/PRD-584-an-agent-asks-for-a-forest-over-mcp-and-gets-a-60-fps-glb.md b/docs/PRDs/strata-terrain-hardening/PRD-584-an-agent-asks-for-a-forest-over-mcp-and-gets-a-60-fps-glb.md new file mode 100644 index 0000000000..27575f43da --- /dev/null +++ b/docs/PRDs/strata-terrain-hardening/PRD-584-an-agent-asks-for-a-forest-over-mcp-and-gets-a-60-fps-glb.md @@ -0,0 +1,187 @@ +# PRD-584 — An agent asks for a forest over MCP and gets a 60 fps GLB + +**Status:** PARTIAL +**Priority:** P1 — The epic's headline outcome: no MCP tool generates terrain today, and no measured number shows a handed-over Strata world holding 60 fps (AC-1 to AC-5). +**Complexity:** 7 (HIGH); risk override: closed-list boundary — a request-to-world tool sits next to the closed "preset/genre system" item +**Owner:** ThreeNative maintainers +**Depends on:** [PRD-583](PRD-583-a-strata-world-bakes-headlessly-into-an-instanced-glb.md) +**Epic:** [Strata terrain hardening](README.md) + +## Context + +Owner, 2026-10-10: "the AI agent could call some MCP endpoint like terrain_generation => +'Generate a forest for me' => and then instantly it's generated and a .glb is provided, and the +game can use it at min 60fps performance." + +Today, on `2d2124792`: + +- No MCP server exposes terrain. `packages/core/mcp/servers.mjs` wires `threenative-assets`, + `-sculpt`, `-engine` and `-blender`. None of them generates terrain. +- `engine_search_capabilities("generate a forest terrain with trees and rocks for my game and + load it as a GLB", scope "request")` returns `Heightfield`, `getWorldCapabilities` and + `TerrainTiles` first. `exportWorldGLB` is rank 7 (score 0.28) and needs a browser. +- The forest kit's trees come from licensed local-only assets (`examples/strata-terrain-preview/AGENTS.md`). + CI and a user machine see procedural stand-ins. +- No playtest loads a handed-over Strata world in an ordinary game and measures it. The + preview's own FPS reflects its own renderer (owner, 2026-10-05: judge the exported world as + an ordinary game loads it). + +## Solution + +A fifth core-wired MCP server, `threenative-terrain`, follows the blender pattern: a shim in +`packages/core/mcp/terrain.mjs`, registered in `MCP_SERVERS`, launching a server bundled from +`packages/terrain`. It costs nothing until a tool is called. + +Tools: + +- `terrain_generate({ request, sizeMeters?, seed?, out? })` — picks one starter recipe + (`forest`, `coastal`, `alpine`, `desert`, `tundra`) from the request, sets size and seed, runs + `Terrain.evaluate` and PRD-583's `cookTerrainWorld` in a child process with a timeout (the + evaluator only checks `AbortSignal` between operations; `AGENT_GUIDE.md` "Bounds"), and writes + `public/worlds//world.glb`, `recipe.json` and the world package. It returns the paths, + triangle, instance and draw counts, and a three-line load snippet. +- `terrain_patch({ recipe, commands })` reuses the editor's command vocabulary (`upsert`, + `update`, `remove`, `move`, `toggle`) so the agent edits the returned recipe instead of + re-prompting. + +Species GLBs are CC0. They are fetched on first use through the existing asset MCP download path +and cached, so the tool does not depend on PRD-589's tarball diet. + +The return names the starter recipe it chose. The recipe is editable game data, not a hidden +preset. That keeps the CHARTER's terrain allowance ("editable terrain starter documents") and +does not add a genre system. Phase 1 adds one line to that allowance naming this tool. + +The 60 fps claim is measured on a stock template game, not the preview: the starter template +loads `world.glb` with `ctx.load`, walks a fixed path, and the playtest asserts frame time. + +```mermaid +sequenceDiagram + participant A as Game agent + participant M as threenative-terrain MCP + participant W as child process + participant G as starter template game + A->>M: terrain_generate("a pine forest, 512 m") + M->>W: evaluate(recipe) + cookTerrainWorld + W-->>M: world.glb, recipe.json, world/ + M-->>A: paths, counts, load snippet + A->>G: ctx.load("worlds/forest/world.glb") + G-->>A: playtest: p95 frame <= 16.7 ms +``` + +## Acceptance Criteria + +- [x] AC-1 [local]: `tools/list` on the shipped shim lists `terrain_generate` and `terrain_patch`. proof: stdio spec beside the sculpt shim's `tools/list` test in `packages/core/__tests__` — Evidence: 2026-10-11 `mcp-install.spec.ts` 34/34 passed; shipped terrain shim lists both tools (377 ms). +- [x] AC-2 [local]: `terrain_generate({ request: "a pine forest" })` writes a `world.glb` that `GLTFLoader` loads, cold, within 60 s on the reference CPU. proof: `packages/terrain/__tests__/terrain-mcp.spec.ts` with timing — Evidence: 2026-10-11 cold shipped-server call + vanilla GLTFLoader passed, 18,058.2 ms < 60,000 ms; AMD Ryzen 9 5900X 12-Core Processor; 1/1 focused case. Full suite rerun: 39/39 passed, fresh tool child + loader 7,741.1 ms on the same CPU. +- [x] AC-3 [local]: `engine_search_capabilities` ranks the terrain tool or `cookTerrainWorld` first for "generate a forest for my game". proof: a case in the PRD-297 capability-recall gate — Evidence (2026-10-11): corpus row `request.terrain-forest-world` ("generate a forest for my game", scope request) returns `cookTerrainWorld` at rank 1 of 2 results. The stale not-owned `procedural-vegetation` situation "generate a forest of trees" became "grow procedural tree meshes" (tree-mesh growth stays game-owned; forest worlds are the terrain cook's), and `cookTerrainWorld` gained `@situation generate a forest world for my game`. `pnpm caps:recall` exits 0: recall@k 0.908 → 0.909 (80/88), zero-result rate 0.0575 → 0.0568, reject hits 16 → 16; capability-recall and capability-manifest specs 44/44. +- [ ] AC-4 [local]: The starter template loads the generated forest and holds p95 frame time ≤ 16.7 ms over a 300-frame walk on a named real GPU (WebGPU, `adapter.info` recorded). proof: `node packages/playtest/dist/runner/cli.js .playtest.json --browser-recipe webgpu` — Evidence: pending. Partial, NOT the prescribed scenario (no template forest walk scenario exists yet, so this stays open): 2026-10-11 `KIT=forest pnpm test:kit` on `19a2fb3b2`, adapter nvidia/turing (RTX 2080), headed WebGPU, 819 frames, pass. Machine load 7.9: frame p95 overview 10.5, lake 12.0, ground 13.6, edge 10.7 ms; GPU p95 2.47, 6.32, 4.88, 5.65 ms. An earlier run at load 39 measured ground 111.2 and edge 116.0 ms with the same GPU p95, so the measurement needs a quiet host. +- [ ] AC-5 [local]: The same scenario holds p95 ≤ 16.7 ms on the native desktop host. proof: same scenario `--target desktop` — Evidence: pending. + +## Blocked on + +- An Android 60 fps claim needs the shared Pixel lane — unblocked by João lending the device (memory: ask for the Pixel). The emulator cannot hold a frame-time claim. + +## Integration Ledger + +| Capability | Reachable consumer/trigger | Replaces / disposition | Evidence | +|---|---|---|---| +| Terrain MCP server | `.mcp.json` written by installing `@threenative/core` → `mcp/terrain.mjs` | New | AC-1 | +| Request to world | `terrain_generate` → child process → `cookTerrainWorld` | Hand-run kit `bake.mjs` | AC-2 | +| Discovery | `engine_search_capabilities` | Rank 7 browser-only result | AC-3 | +| 60 fps handoff | starter template `ctx.load(world.glb)` | Preview-only FPS numbers | AC-4, AC-5 | + +## Execution Phases + +#### Phase 1: the tool generates a GLB +**Status:** COMPLETE +**Files:** `packages/terrain/src/mcp/server.ts` (new), `packages/core/mcp/terrain.mjs` (new), `packages/core/mcp/servers.mjs`, `docs/architecture/CHARTER.md` +**Implementation:** stdio MCP server; request → starter recipe by keyword with the choice returned; child process with timeout; CC0 species fetched through the asset MCP download path and cached. +- [x] Shim lists both tools. proof: stdio `tools/list` spec — 2026-10-11, 34/34 tests passed; terrain shim 377 ms. +- [x] A forest request writes a loadable GLB inside 60 s. proof: `terrain-mcp.spec.ts` — 2026-10-11, 18,058.2 ms, vanilla GLTFLoader; AMD Ryzen 9 5900X 12-Core Processor. + Note 2026-10-11 (no box ticked). Decisions: D1 the server is hand-written stdio JSON-RPC, because the existing engine and blender servers are hand-written JSON-RPC with no SDK and their loops are bundled, not importable. D2 the server is a tsup entry of `packages/terrain` (`dist/mcp/server.js`, package `bin`), not a bundle in core, because bundling terrain into core closes the cycle core -> assets -> terrain -> core. Core gets only the shim `packages/core/mcp/terrain.mjs`, the `threenative-terrain` row in `MCP_SERVERS` and a `MCP_PACKAGES.terrain` pin; core gains no dependency on terrain. D3 terrain does not depend on `@threenative/assets`: the cook child loads it from the game project at run time and fails closed with `npm i -D @threenative/assets` when absent. D4 `packages/terrain/dist` stays under 2 MB. + Changed: all five kits are accepted and each cooked through the built server (forest 5.6 s wall, 18,515,644 B `world.glb`; coastal, alpine, desert and tundra about 1.2 s each). `bounds` is narrowed by `parseBounds` with no cast. A pre-aborted signal never spawns. A timeout or cancel kills the whole process group and removes the partial output. `out` is rejected when it or any ancestor is a symlink leaving the project (realpath containment, dangling links included). `pnpm sync:mcp` rewrote the template MCP configs for the fifth server. `AGENT-INTERFACE.md` names it. + Checks: `terrain-mcp.spec.ts` 23 of 23 pass. Red against the previous `child.ts` and `tools.ts`: 6 of the new cases fail. `mcp-install.spec.ts` 33 of 33 pass. Core typecheck 0 errors. Terrain `tsc --noEmit` has 165 errors in `starter/`, other specs and `examples/strata-terrain-preview`, 0 in `src/mcp` or `terrain-mcp.spec.ts`. Terrain build exits 0, `du -sb packages/terrain/dist` = 860,387. Stdio smoke through `node packages/core/mcp/terrain.mjs` lists `terrain_generate` and `terrain_patch` (`SMOKE PASS`). Scoped Biome exits 0 with one existing complexity warning in `servers.mjs`. Doctor pin test, primary-docs and release candidate cases pass with `-t "MCP|bundled"`. + Open: `@threenative/terrain` is unpublished (npm 404), so the shim's npx fallback fails in a project that does not install it. `scripts/verify-golden-path.ts` (`MCP_SURFACES`, needs a recorded `terrain-mcp-tools.json`) and `scripts/verify-registry-install.ts` (safe tool, spec fixtures) still list four servers, so their specs fail until updated. The full runs of `doctor.spec.ts` and `verify-registry-install.spec.ts` have other failures not triaged. Species are the tracked starter GLBs, not the asset MCP download path. The result has no `ctx.load` snippet, triangle count or draw count. The cook reads data files from `starter//`, which AGENTS.md says package code never imports. Input uses a `kit` enum, not `request`. The 60 s check is a timeout only, with no timing spec. + Owner lane: /root/prd587_lifecycle. Checkout `.worktrees/strata-terrain-mcp`, branch `epic/strata-terrain-mcp`, base `f8c41d61070b967bc35351a2efd5573ccfe1d92d`. Lane retained until full584 integration. + Note 2026-10-11 (no box ticked), continuation. Changed: `MCP_SURFACES` and `MCP_SAFE_TOOLS` carry `threenative-terrain` (probe: `terrain_patch` with an empty command list, never a cook); `terrain-mcp-tools.json` is written by `scripts/capture-terrain-mcp-tools.ts` and compared to the served tool table in `mcp-tool-schemas.spec.ts`. Doctor treats the terrain server as an optional addon: it is not probed through the shim's npx fallback when `@threenative/terrain` is absent, and says `Run: npm i -D @threenative/terrain`. `terrain_generate` now returns the kit as `recipe`, the `world.glb`, `recipe.json` and `world.json` paths, `triangles`, `instances` and `draws` read from the default scene of the produced GLB, and a three-line `ctx.assets.model` snippet (`ctx.load` is not an API in this repo). `triangles` counts every instance in full before LOD and culling, `draws` counts primitives with an instanced primitive once. Forest kit: 505,387,464 triangles, 4,820 instances, 25 draws. + Checks (before, at 2e1ff40a4 -> after): `verify-golden-path.spec.ts` 21 of 23 -> 22 of 23, `verify-registry-install.spec.ts` 37 of 45 -> 45 of 45, `doctor.spec.ts` 99 of 99 -> 100 of 100, `scaffold-mcp.spec.ts` 27 -> 27, `mcp-install.spec.ts` 33 -> 33, `terrain-mcp.spec.ts` 23 -> 27. The one remaining golden-path failure (`packs a cold-layout CLI`) is not caused by the MCP change: the `create-threenative` prepack from f8c41d610 runs `stage-terrain-kits.mjs`, and the spec's copied tree has no `packages/terrain/starter-assets/credits.json`. Terrain `tsc --noEmit` now reports 6 errors, all in `examples/strata-terrain-preview/src/render/horizon.ts` (a missing `horizon.json`), a file with no diff against f8c41d610; the earlier 165 was not reproduced. `git diff --stat f8c41d610 HEAD -- packages/terrain` touches only `src/mcp`, `terrain-mcp.spec.ts`, `package.json` and `tsup.config.ts`. + Open: the full golden-path and registry-install MCP steps are not run end to end: the golden-path scaffold does not install `@threenative/terrain`, and the package is unpublished, so the terrain probe there falls to the npx fallback until the package is published or the scaffold installs it. + +#### Phase 2: agents find it +**Status:** NOT STARTED +**Files:** `packages/terrain` capability entries, `packages/create-threenative/capabilities.json` (generated), template `AGENTS.md` terrain line +**Implementation:** capability entry with situation "generate a forest/terrain/world for my game"; template `AGENTS.md` names the tool. +- [x] Search ranks the tool first. proof: capability-recall gate case — 2026-10-11: see AC-3; `pnpm caps:recall` exit 0, rank 1. + +#### Phase 3: a stock game holds 60 fps on it +**Status:** IN PROGRESS +**Files:** `packages/create-threenative/template-playtests/` (new forest scenario) +**Implementation:** scaffold the starter template, generate a forest with the tool, load it, walk 300 frames, assert p95 frame time. +- [ ] Web WebGPU p95 ≤ 16.7 ms. proof: playtest `--browser-recipe webgpu` + Character component, 2026-10-14: fresh read-only visual judge returns IMPROVEMENT for both matched BEFORE/AFTER pairs and overall: north-facing running pose repeats, feet/shins remain visible, no introduced character artifact. All four headed Nvidia/Turing 1920×1080 captures have exactly matching tick 600, body position and complete camera metadata. The reviewer cautions that BEFORE also has a visible lower foot: these stills prove heading improvement, not contact throughout a stride or a dramatic burial correction. Stock starter coyote jump, buffered jump and respawn scenarios each pass on the laptop hardware adapter. Tree/ground art, continuous grounding, native and the frame budget remain open; this box stays unticked. +- [ ] Native desktop p95 ≤ 16.7 ms. proof: playtest `--target desktop` + +2026-10-11 first-use asset preparation component verified, without an acceptance tick: a private session of the installed asset MCP downloaded the official CC0 Poly Haven `fir_tree_01` 1k Blender source (218,986,261 bytes; SHA-256 `153d20aae6d7c63a4c52689d91e8bead78f6d17a70e11ef4d03d19953383de00`) into an isolated artifact cache in 1.787 seconds, with `alreadyExisted: false` and license acknowledgement. Downloading the `.blend` alone is insufficient: the existing exporter first stopped on its missing bark image. The provider lists 18 PNG dependencies; retrieving them through the same MCP, verifying their declared sizes/MD5 and restoring their declared relative `textures/` paths took 1.265 seconds, all uncached. The unchanged existing `prep-polyhaven-fir.py --only b:2` then exported and self-checked the authored LOD2 in 3.848 seconds: 54,927 triangles, one mesh, four materials, six embedded images, POSITION/NORMAL/TEXCOORD_0 and base Y zero. Its 5,153,060-byte GLB has SHA-256 `a7e1f126ba7c09aa160f82e200689312334204928c9d822a1eaf13ed9d259dc8`, exactly matching the existing repository asset. The successful component timings sum to 6.900 seconds; this was a diagnostic sequence, not a single end-to-end cold forest generation. The existing 10 GiB downloader cap was unchanged. No new generator, shipped preparation path, loadability playtest, complete forest timing or web/native frame budget is proven; all full acceptance criteria remain open. + +2026-10-11 imported-LOD runtime component remains unimplemented: isolated `strata-imported-lod` lane at `f8c41d610` completed offline frozen ignore-scripts install. A bounded Haiku MAX arm timed out after saving only additions to existing `model-lod-loader.spec.ts`; no production fix landed. The actual createAssetLoader/GLTFLoader fixture carrying EXT_mesh_gpu_instancing and TN_discrete_lod produces 7 passing and 2 failing tests (10.45 s): near/far instances draw the same geometry, and a separate ownership assertion finds no chain on the inspected base geometry (0 levels versus 2 expected). This does not isolate the partition-hook cause from chain association, so no GREEN or complete behavioural proof is claimed. Scoped test Biome passes; core typecheck fails on missing local playtest protocol/three declarations. No assets cook, native/browser conformance proof or FPS measurement ran; the current cook also still needs existing chain generation and writer preservation before its terrain triangle budget can improve. No acceptance box ticked. + +2026-10-11 imported-LOD runtime component verified locally, without an acceptance tick: retained RED loader tests were committed unchanged as `03161de4b`. The empty chain was an association loss, not missing fixture metadata or a geometry clone: the fixture writes TN_discrete_lod on its primitive (`packages/core/__tests__/model-lod-loader.spec.ts:136`); Three's GLTFLoader constructs the imported InstancedMesh over the original geometry without transferring its primitive association (`packages/core/node_modules/three/examples/jsm/loaders/GLTFLoader.js:1762`), stamps only its node association (`:4402–4411`), and drops the replaced source mesh's association (`:4506`). The reader consequently skipped it for missing mesh/primitive indices. DiscreteLodPlugin now recovers those indices from the glTF node and primitive order, including multi-part nodes, and the asset loader injects the existing attachInstancedLod mechanism after decoded-chain registration, avoiding a runtime import cycle. Imported instances use the existing 4 px projected-error default and hysteresis, with manifest runtime overrides; an unchained primitive remains unchanged. The retained ray fixture required front-facing triangle winding and a ray away from a shared edge. Local focused baseline: 69 passing / 2 failing specs in six files (loader 7 / 2); final: 72 passing / 0 failing (loader 10 passing, including added mixed-chain multi-part coverage). Tests verify independent near/far detail, stable authored slots/count, public ray-hit ids, manifest budget/hysteresis, and disposal that releases partitions/restores base without disposing shared chain geometry. Core typecheck passes after the permitted @threenative/playtest build restored local protocol/three declarations; scoped Biome passes with eight cognitive-complexity warnings and no errors. No packages/assets changes, browser/native playtest, real-world triangle count or FPS measurement ran. The cooker still must emit and preserve valid TN_discrete_lod chains for real Strata worlds; 60 fps acceptance remains open. + +2026-10-11 combined-world cooker LOD component verified locally, without an acceptance tick: `cookTerrainWorld` reuses `generateDiscreteLod` on near species with cutout conversion disabled and vertex-copy card reduction disabled; the final writer registers `TNDiscreteLod`. Index-only chain errors and error scales follow the existing source-node transform during flattening. The real forest spec runs the actual cooker on the shipped recipe, RAW16 sidecar, models and ground textures (5.638 s in the final focused rerun), checks decoded/transformed source vertex hashes, unchanged LOD0 indices/material factors, shared vertex buffers, transformed error scales, and computes selection from the cooked GLB's instance attributes using core's projected-error selector, 4 px budget and 0.15 hysteresis starting at LOD0. Camera position (256,40,600), look-at (256,10,256), 60-degree vertical FOV, 1920×1080 viewport, near 0.1, far 2000; this counts all placements without frustum/shadow-pass culling and is not a GPU frame measurement. Clean HEAD before: base and submitted 505,387,464 triangles, no species chains. After: unchanged base 505,387,464; submitted 18,917,904, including unchanged terrain 139,264. Fir: 3,100 instances, base 398,238,400, submitted 13,001,400; primitive triangle ladders including LOD0 [9582,2460,1339], [78776,3466,1488,722], [35136,1757], [4970,2418,902,376]. Boulder: 320 instances, base 10,579,200, submitted 528,640; ladder [33060,18794,6432,2350,1652]. Fir-c: 1,400 instances, base 96,430,600, submitted 5,248,600; ladders [9536,2442,1313], [9459,1500], [3616,2402,1188,428], [46268,1732,848,508]. Before/after kit bakes contain 22 files: only world.glb changes, SHA-256 e64b584dba6fdfd0ab05c30e2d0b0b3ad39231ae735208ef5283a8c2b6df3993 → c23570826bc1100f13768ae57c7922b50e321327ec810ccb426e4b7fea818700, because of added index chains and transformed error metadata; all 21 other files, including streamed models and every non-GLB output, retain their hashes. A byte comparison also confirms 109 default-scene vertex/index/instance streams and materials, textures, samplers and embedded images unchanged. No existing pin changed: the starter-kit pinned hashes cover non-GLB outputs, and the assets terrain-world spec has no cooked-GLB hash pin. Rebuilding checkout prerequisites and assets restored the clean baseline: requested four-file specs 92/92 passing, including non-finite placement; after 93/93 passing, followed by 20/20 terrain-world specs on the final census edit. The retained transformed-error assertion fails without source-scale propagation (1.3152257204 versus 9.2575621605) and passes with it. Assets/core typechecks and assets build pass; scoped Biome passes with seven warnings and no errors. No browser, GPU, native or FPS run; coordinator qualification and all full acceptance criteria remain open. + +2026-10-11 Phase 3 scaffolding added, web smoke did not reach a frame (no box ticked). `pnpm verify:forest-playtest [--target web|desktop]` (`scripts/verify-forest-playtest.ts`, spec `scripts/__tests__/verify-forest-playtest.spec.ts`, 3/3 passing) packs the workspace, scaffolds the stock starter, calls the real `terrain_generate` through the core shim over stdio with `{ kit: "forest" }` (the tool takes `kit`, not `request`), inserts only the printed `ctx.assets.model("worlds/forest/world.glb")` + `ctx.scene.add(world.scene)` lines at the start of `Play.load`, and runs `template-playtests/starter-forest/forest.playtest.json` (web) or `forest.native.playtest.json` (desktop): 300-tick ArrowUp walk, `assert.performance.maxFrameMsP95 16.7`, `movement` -z >= 0.5. Web smoke on the NVIDIA GPU (`--browser-recipe webgpu`, private Xvfb) exited 2 with `TN_PLAYTEST_BRIDGE_MISSING` at 0 frames: no adapter, p50 or p95 was measured. Cause, shown with a control: the same scaffold without the two load lines boots (bridge present, 300-frame windows of p95 5.3 to 6.5 ms). With them, the page never requests `world.glb` and stalls with `TN_STARTUP_STALLED`, because the production build resolves assets through `assets.manifest.json` and `worlds/forest/world.glb` under `public/` is not listed (`packaging: 28 unmanaged file(s)`; `resolveCandidates` throws "is not listed in the asset manifest", which surfaces only as the stall). Moving the world to `assets/worlds/forest/` so the pipeline lists it made `pnpm build:web` run past 10 minutes (KTX2 encoding of the world's textures) before it was stopped, so that route is not yet measured. Open: the tool's default `out` and printed snippet do not load in a manifest-built game. + +2026-10-11 D1–D5 production hardening (no boxes ticked). `terrain_generate` now derives the configured `assets.source` through the installed Vite config loader, defaults to `assets/worlds//`, rejects outputs outside that source root, and prints a source-relative manifest key. A new cooker marker, `asset.extras.tnTerrainWorld = 1`, selects validated GLB passthrough: copy/hash/list without model passes or name-insensitive deduplication; ordinary models still process. Initial Scene.load failures and rejected `#goto` transitions now report the original error through `onLaunchFailure` and `TN_STARTUP_FAILED`; the previous `transition.then(settled, settled)` handler handled the rejection without reporting it. Request matching is deterministic in starter order: forest/pine/woods, coastal/beach/coast/sea, alpine/mountain/snowy/peaks, desert/dunes/sand, tundra/arctic/ice; `kit` overrides matching, unmatched requests list all five kits, and the recorded MCP schema is regenerated. Both terrain specs now use `makeTempDir`, without guard allowlisting. Checks: 212 focused tests pass across asset compile/terrain-world/shared-images (88), core game/launch diagnostics (62), terrain MCP (36), terrain consumer (18), temp-dir guard (1), and MCP schemas (7). Assets/core typechecks and assets/terrain builds pass; scoped Biome passes with warnings. Terrain typecheck still fails in untouched preview code because `examples/strata-terrain-preview/src/world/horizon.json` is missing. The single requested NVIDIA web run built within the 10-minute limit and loaded/walked: adapter.info vendor `nvidia`, architecture `turing`; startup ready; movement 1.40 m along -z; zero console/network errors. Nearest-rank frame times over 199 samples: p50 21.4 ms, p95 213.6 ms. Overall FAIL: unchanged p95 ceiling 16.7 ms. Production manifest lists the 19,199,432-byte world with `passes: []`; served bytes equal the source, SHA-256 `36c1e72f030203d2400604fae78029143d111da431648f348c531eab11a9f282`. No tuning, threshold change, second forest run, native run, or push. Full-board qualification and the 60 fps acceptance remain open. Failed-run diagnostics are retained at `/tmp/threenative-forest-playtest-1LjmWY/forest/artifacts/playtest` and `/tmp/584-forest-web.log`. + +2026-10-11 Cooked-world model-loader LOD regression fixed (no boxes ticked). `ctx.assets.model` routes through `createAssetLoader`, which already registers and attaches `DiscreteLodPlugin`; the cooked forest retains `TN_discrete_lod`. The failure is in association discovery: Three.js replaces `parser.associations` with each scene's reduced map, so alternate species LOD scenes remove the main world's instanced-node mappings before `DiscreteLodPlugin.afterRoot`. The shared plugin now loads node dependencies and retains their complete association map in `beforeRoot`, then uses it for primitive/instancing discovery. No distance, threshold or appearance value changed. RED: the real cooked forest loaded through `createAssetLoader.model("worlds/forest/world.glb")` had zero partition children; a minimal multi-scene loader regression also returned zero instead of two. GREEN: 61/61 tests across terrain-world, model-lod-loader/runtime/clone/join pass; core and assets typechecks pass; scoped Biome passes with warnings. `pnpm capabilities:sync` and `pnpm api:surface:sync` pass with no tracked drift. Full lint passes on the Debian offload lane with warnings; full typecheck fails on absent package build outputs, and full `pnpm test` stops because that lane has no Git HEAD, so full qualification remains unverified. The single requested NVIDIA-environment web command (`flock /tmp/strata-terrain-gpu-proof.lock pnpm verify:forest-playtest`) exited 1 before browser launch: the driver inserts the tool's static `applyTerrainSurface` import inside `Play.load`, and the production scaffold build rejects `src/scenes/Play.ts:61` with a top-level-import parse error. Adapter, p50, p95 and triangles/frame were not measured. No second forest run, tuning, native run or push. Command output: `/tmp/584lod-forest-web.log`. + +2026-10-11 bounded item 3 skipped after two recall runs returned `none` (1/1 focused failure, then 17/18 pass). The `procedural-vegetation` not-owned situation `generate a forest of trees` preempts the request. Adding an honest forest-world situation to `cookTerrainWorld` also makes `pnpm capabilities:sync` reject overlapping ownership (score 0.375000). Trial edits reverted; AC-3 and phase 2 remain open. Resolving the ownership declaration is required before description-only ranking can work; no ranker special case added. + +2026-10-11 Black-fir diagnosis, root cause not found, no boxes ticked. In a scaffolded starter with the cooked forest `world.glb`, every fir renders near-black under the starter's post chain, and the same trees render lit with `exp=nopost`. This is not a regression between `3b32a45c2` (the SHA nearest the PR comment frame) and `b202ca25a`: `git diff 3b32a45c2 b202ca25a -- packages/create-threenative/templates/starter/src` is empty, and the HEAD template and cooked GLB on core built at `3b32a45c2` (with that SHA's Three patch) also render black. The PR comment frame (`BAD-prd591-hardware-walk`) came from the `examples/strata-terrain-preview` app, not a scaffold, so it never proved the starter path. Measured on the NVIDIA adapter (`vendor: nvidia`): a plain red `MeshBasicMaterial` on every fir is red with no chain and black under the default chain, so the cause does not depend on material, lighting, shadows, fog, `alphaAntialiasing` (turning it off changes nothing) or albedo. With the canvas CSS background set to magenta, fir pixels read about (87, 1, 86): near-zero RGB with alpha about 0.66, so the chain output carries a wrong alpha and colour on fir pixels while terrain stays opaque. Removing bloom, vignette, GTAO, grade and grain together turned the red firs red again; removing only some of them did not. The stage that zeroes fir RGB is therefore unidentified. Next step: add a per-stage alpha and RGB readout on a fir pixel to `WorldEnvironment`, then bisect stages one at a time. The playtest has no luminance assertion, so the black frame passed the 584 walk. + +2026-10-11 Black-fir root cause found and fixed in the cooker (layer: engine, `packages/assets`), no boxes ticked. The cooked `world.glb` stored all-zero vertex normals for every instanced prop (fir 5 774 of 5 774, boulder 19 773 of 19 773, fir-c 5 750 of 5 750 vertices), while the source `fir.glb` had none. `@gltf-transform/functions` `transformMesh` writes a normalized-integer NORMAL back through `decodeNormalizedInt` instead of the encode function, and `addInstancedSpeciesNode` (`packages/assets/src/world/terrain-world.ts`) flattens each quantized species through it. Zero normals mean no direct light, and the starter's GTAO read zero MRT normals (confirmed with a normal-target view: tree pixels were 0, terrain and a plain user box were not) so its raw occlusion was 0 on trees. The earlier alpha 0.66 was premultiplied-canvas compositing of that near-black, not a stage bug. Fix: `dequantizeNormals` converts a normalized NORMAL to float before `transformMesh`. RED: new spec "keeps the unit length of quantized species normals through the flatten" fails with length 0, passes after; the real-forest spec's expectation had encoded the same bug and now transforms floats. Tree-only metric (pixels that change when firs are hidden, same pose, NVIDIA turing adapter): mean sRGB (13.8, 25.2, 29.8) before, (16.5, 20.7, 14.6) after, p90 luma 0.095 to 0.181, two identical runs per arm. A fresh read-only `claude -p --model sonnet` judge returned IMPROVEMENT on all three pairs (run1, run2, pose2); its ranked remaining defects start with foliage still near-black (GTAO radius 0.35 m and scale 1 crush dense cutout canopies: GTAO off or scale 0.3 lifts tree luma from 0.076 to about 0.2, an unjudged look change left for the template owner), then no visible sun shading, flat sky and fog, and an empty ground. The `forest world.glb` golden hash in `packages/terrain/__tests__/starter-kit.spec.ts` was repinned for the corrected bytes. The standard forest walk playtest frame (spawn arena) shows no trees, so a tree-pixel assertion there is not possible without a camera pose step; the data-level spec is the guard. Open: the visual-judge box for AC "looks lit" stays unticked because the verdict is IMPROVEMENT-from-black, not AAA. + +2026-10-12 forest kit sky (game layer, `packages/terrain/starter/forest/sky.ts`): the overview and lake poses showed a flat white sky (80.5% and 63.5% of the top 30% of the frame at all channels >= 245) with a hard horizon. The kit sky now compresses only the lower and near-horizon dome (radiance scaled 0.3 to 1.0 by elevation) and blends it into the haze colour, leaving the upper blue sky and haze density alone. After: 0% of those pixels clipped in both poses, top-30% median RGB 248,248,248 to 218,226,230 (overview) and 222,229,232 (lake); ground and edge unchanged (190,220,238 to 188,219,236). Kit run on verified NVIDIA Turing (private Xvfb), both runs pass all kit assertions. A first attempt (global radiance x0.06) was judged REGRESSION (dusk-navy ground and edge poses, grey overview) and discarded. Visual judge, fresh read-only Sonnet subagent, two runs per pose: overview IMPROVEMENT, lake IMPROVEMENT, ground NEUTRAL, edge NEUTRAL, OVERALL IMPROVEMENT, runs agree. Judge's remaining complaints, not fixed: the overview and lake sky is a dull grey-lavender and does not match the saturated blue of the ground pose, and the lake shows no sky reflection. Images: `pr483/kit-sky-{overview,lake,ground,edge}-BEFORE-AFTER.jpg` on `strata-hardening-screenshots`. Native rendering unverified. + +2026-10-12 forest kit lake (game layer, `packages/terrain/starter/forest/water.ts`): the lake was a flat dark navy slab with a hard sandy rim (lake-pose median RGB 17,47,50, no reflection, no depth). The material now uses a desaturated teal body, bed-depth shading, a widened shore fade with no foam rim, a capped grazing-angle highlight and a stronger sky-tint weight. After, on verified NVIDIA Turing (private Xvfb): lake median 17,47,50 to 32,94,99, overview 51,77,85 to 64,110,118, ground-pose water max-channel median 114 (no blow-out); kit assertions pass 25 of 25. Two earlier attempts were judged REGRESSION (neon pool-blue with a near-white ground pose) and NEUTRAL (white shore rim and a cream grazing streak) and were discarded. Visual judge for the final attempt (fresh read-only Sonnet subagent, two runs per pose): lake IMPROVEMENT, overview IMPROVEMENT, ground IMPROVEMENT (water strip only), edge NEUTRAL, OVERALL IMPROVEMENT, runs agree. Not fixed, the judge's remaining complaints: the lake body is nearly uniform flat teal, with no visible sky reflection or sun glint, the teal is slightly too saturated against the olive forest in the overview, and the wide shore fade tints the ground olive. The measured near-shore minus far-water luminance gap fell (+8.05 to +2.55), so the depth-gradient goal is not met. Images: `pr483/kit-water-{overview,lake,ground,edge}-BEFORE-AFTER.jpg` on `strata-hardening-screenshots`. Native rendering unverified. + +2026-10-13 AC-4 web run on the merged branch (9ab9d5f12), no box ticked. `pnpm verify:forest-playtest --target web` under `scripts/xvfb.sh`, NVIDIA RTX 2080 (`vendor: nvidia`, `architecture: turing`), host load about 7 on 12 cores. FAIL: `performance.maxFrameMsP95` actual 149.5 ms against 16.7 ms over 75 samples (not 300); frame p50 72.3 ms; startup `readyMs` 40,083, warmup `incomplete` (10 of 1,868 candidate pipelines observed). The run is NOT valid proof for the bound: `phases.hostGap` is 57.8 ms of a 100.9 ms frame and `TN_SCENE_WARNING` reports `gpuShare` 0.009 to 0.061, which is the Xvfb present throttle ([desktop-xvfb-presents-throttle-fps] pattern), so the bound needs a real display. The same run records 10.85 M main-pass triangles over 278 draws and `trianglesPerDraw` 39,764, so the triangle cost named in README item 4 is real and independent of the throttle. Next: run on a real display (needs the owner to confirm the desktop is idle), then read `TN_WORLD_IMPOSTOR asset=fir reason=` and `TN_LOD_BIAS` markers, which this report does not carry. + +2026-10-13 AC-4 web walk on a real display, Omarchy (no box ticked): `pnpm exec tsx scripts/verify-forest-playtest.ts --target web` at `fb775277f`, headed on the machine's Xwayland display, adapter `intel / gen-9` (Intel HD Graphics 620, 2 cores, Wi-Fi), 364 samples. FAIL: `performance.maxFrameMsP95` actual 337.6 ms against 16.7 ms; frame p50 236.1 ms, p95 330.8 ms; startup `readyMs` 114,620 and warmup `incomplete`. This is a 2017 ultrabook iGPU, so it does not speak for the bound on the RTX 2080: the NVIDIA run on `:0` is still owed (the Xvfb NVIDIA run, 149.5 ms, is invalid because of the present throttle). The Debian laptop was not usable: its offload jobs aborted at 99 to 100 C while other sessions' jobs held it hot. + +2026-10-13 AC-4 web walk on the Debian laptop's real display (no box ticked): same command at `b0345cf00`, adapter `intel / gen-12lp` (Iris Xe), 80 samples. FAIL: `performance.maxFrameMsP95` actual 107 ms against 16.7 ms; `readyMs` 30,599. An integrated GPU, so it does not speak for the RTX 2080 bound, which still needs a valid run on `:0`. + +2026-10-13 AC-4 on the RTX 2080 real display at `fef436694` (no box ticked): `TN_PLAYTEST_HOST_DISPLAY=1 DISPLAY=:0 TN_FOREST_KEEP=1 pnpm exec tsx scripts/verify-forest-playtest.ts --target web` uses authenticated Xwayland, adapter `nvidia / turing`, 1920×1080, scale 1 / MSAA 4. The unchanged scenario loads and moves 1.40 m along -z with zero console/network errors, but FAILS the unchanged 16.7 ms ceiling: 71 timing samples, p50 54.6 ms, p95 98.7 ms; ready 34,791 ms and warmup incomplete. The last 30 samples still have p50 50.3 ms and p95 67.6 ms. This is not the required 300 rendered-frame steady walk: fixed-step ticks do not establish that sample count, and first-use compilation remains in the series. Installed project retained at `/tmp/threenative-forest-playtest-tq3X2W/forest` for diagnosis. + +The shipped `trace --url http://127.0.0.1:43929 --seconds 30 --settle 15 --key ArrowUp --viewport 1920x1080 --wait-timeout 120` ran on the same real display and adapter. Empty-page presentation control: 180 intervals, p50 16.7 ms, p95 16.8 ms, average 58.7 callbacks/s. Trace: 739,194 events over 38.5 s, 275 main-thread tasks over 40 ms, largest 14,090 ms; sampled CPU self time includes node `build` 4,048 ms, `updateByType` 1,977 ms, `_update` 1,434 ms, `writeBuffer` 1,360 ms, `_renderObjectDirect` 1,329 ms, and instanced-LOD `update` 1,168 ms. GPU busy 12.6%. Raw trace: `artifacts/pr483-forest-real-display-trace.json`; command exits 0, which validates trace collection only. The first-use stall remains inside this trace, so its aggregate rate is not a steady FPS result. Next diagnosis: separate that compile window and measure empty LOD partition processing before choosing a renderer change; no speculative optimization or budget change is applied. + +2026-10-13 measurement correction (verification harness): both browser and desktop calls in `scripts/verify-forest-playtest.ts` now pass the existing `--live-clock` flag. The earlier fixed-step walk rendered only 71 observed frames, so it cannot establish the intended live walk or steady-state FPS. With a live clock, tick-based browser steps wait real rAF frames instead of advancing the synthetic bridge clock; the native runner receives the matching wall-clock setting. Existing setup specs pass 3/3 and scoped Biome passes. No runtime rerun or FPS acceptance is claimed: another lane currently owns an active native GPU renderer. The scenario performance series still has no startup-discard or minimum-count option; steady-state acceptance must additionally use the existing perf-marker window assessor with enough completed windows. Its default two steady windows contain only 600 frames, not the required 1,000-frame/30-second minimum. All runtime/performance boxes stay open. + +Live-clock retry preparation: the real latest local-framework scaffold, `terrain_generate` forest bake and `build:web` complete successfully. The browser CLI then reports the shared capture lock held by another lane; only this goal's queued CLI was terminated after verifying its parent chain belongs to this checkout. No browser scenario executed. The fully built game is retained at `/tmp/threenative-forest-playtest-DnOVg6/forest`, so the next retry runs its existing CLI command directly instead of rebuilding or repacking. Runtime/FPS boxes remain open. + +2026-10-13 live-clock browser observation: the already built generated forest was copied unchanged to the now-free laptop GPU. Both copies contain 88 files with path-and-content tree SHA256 `3332d272ed7f490c11b3a3bd6c39883bd07e020cef4f1dc101ab5ff910147178`. The existing runner uses the frozen forest scenario, hardware Intel gen-12lp WebGPU, live clock and an ordinary HTTP server. It fails: frame p95 125.8 ms > 16.7 ms (305 observations) and required −Z movement is effectively zero. Diagnostics, network/console policies and runtime-ready pass. Input injection is clock-independent, but the controller/contact cause remains under investigation; odometer growth alone does not prove successful held-key locomotion. Retained browser distribution/scenario/artifacts: `/tmp/pr483-forest-live-dccd` on laptop; full CLI log: `/tmp/pr483-forest-live-intel.log` on desktop. + +The browser console does capture the existing `TN_FRAME_BUDGET` markers. Extracting its ten messages into the existing perf CLI file input disproves the earlier read-only claim that browser marker evidence cannot be consumed. The assessor, with six required windows, 60 FPS and 16.7 ms bounds, fails (minimum FPS 36.68/43.33, frame p95 113.7 ms). Every captured window still reports `surface.compiling: true`, so none qualifies as a settled measurement. No steady-state result or acceptance tick follows from these values. A future measurement must collect enough actual completed frames and elapsed time after settling; requested tick/rAF counts alone do not establish that window. + +2026-10-13 measurement acceptance repair (playtest harness layer): the existing perf assessor now excludes every window explicitly marked `surface.compiling: true`, retaining the raw budgets for attribution and listing the discarded window ids. It keeps the established first-window discard and legacy records without that optional observation; malformed provided surface records/compilation flags throw. The new regression reproduces a false pass from fast-but-compiling windows before the fix. Parent-reviewed focused tests pass 86/86; playtest build, declaration generation and publint pass. The rebuilt CLI against the actual browser marker file exits 2 with `TN_PERF_WINDOWS_MISSING`, zero settled windows and all ten ids discarded. This corrects measurement eligibility; it supplies no new FPS or runtime acceptance. + +2026-10-14 physical handoff repair, no acceptance box ticked: the MCP load snippet now reconstructs the baked RAW16 ground with the shipped Heightfield sampler and a fixed Rapier heightfield body, centered on the manifest extent. Removing its scene anchor disposes the body. The snippet also supplies the shipped GLTF type to the generic model loader; a generated consumer typecheck exposed the previous `unknown` result. Both asset-source regression cases fail against the old render-only snippet and pass with the collider, including column-major heights, manifest-relative lookup and scene-exit disposal. The final terrain MCP suite passes 39/39; the complete terrain suite passes 317/317 across 29 files, the forest verifier passes 3/3, and terrain typecheck, build and publint pass. The freshly packed consumer passes its own TypeScript check and web build. Its build initially rejected three generated world copies at the unchanged 64 MB asset budget; the two unused copies were moved outside the AFTER asset tree, preserving both them and the original baseline. The actual live-clock forest scenario then failed on the Intel adapter: 540 requested ticks, 300 performance observations, p95 136.6 ms against 16.7 ms, and net -z movement -0.000000835 m against the required 0.5 m. Startup compilation completed at 31,654.5 ms and framework readiness at 31,850.2 ms; console, network and runtime diagnostic checks passed. The collider component alone does not repair the live walk. Ground-support/spawn and collider lifetime are under investigation; native behaviour, visual acceptance and steady 60 fps remain open. No acceptance box is ticked. + +2026-10-14 spawn diagnosis, no acceptance box ticked: the installed core heightmap loader and sampler read the actual generated world surface at the stock spawn x=-2, z=0 as 11.400151388230618 m. The stock capsule center is 0.9 m and its feet are at 0 m, so it begins beneath that heightfield. The existing Player constructor already accepts an explicit spawn position; the forest proof transformation is being adjusted to retain the tool-emitted heightfield from load until enter and derive the initial height at the actual spawn coordinates. This changes the generated proof game, not the stock template, and introduces no constant terrain elevation. Runtime movement and frame-time acceptance remain open until the unchanged scenario passes. + +2026-10-14 owner visual rejection after the measured spawn change: the RTX 2080 live-clock walk moves 14.233197509785896 m along -z and passes console/network/runtime diagnostics, but p95 is 32.9 ms against 16.7 ms. Movement alone is insufficient: the owner observed the character sinking into the surface, facing backward while moving north, coarse ground texturing and poor tree appearance, and supplied the BEFORE screenshot. These remain failures. Opus 5.5 at high effort was explicitly requested for the visual diagnosis and is reviewing that image and the actual game/terrain sources. No visual or performance acceptance is claimed. + +The spawn transformation has a reproduced RED against the prior flat-floor setup and six passing verifier tests after the change. A separate readiness-guard regression was reproduced before adding the field to the existing startup guard. The independently rerun six tests, scoped Biome, generated consumer typecheck and web build pass under Node 20. Two labelled RTX/Turing diagnostic captures are retained; their live-clock end positions differ by 0.167 m, so they are not claimed as the exact-pose visual acceptance pair. The owner-supplied image remains the visual rejection reference. + +2026-10-14 owner-image diagnosis and first grounding attempt, no box ticked: the explicitly requested Opus 5.5 high audit completed against the owner image and actual generated game. It confirmed hidden feet and rejected the assumption that ground filtering was simply off: mipmaps and anisotropy were already enabled. Physics samples the clearing at 11.400 m; the capsule center at 12.310 m is correct. The stock template's arena-only support callback disables visual grounding outside the arena. Removing that redundant callback and deriving support from the grounded body passes 114 focused tests; after removing implementation-mirroring assertions, the independently rerun scaffold and forest-verifier suites pass 44/44. The generated consumer typecheck and build pass. However, the actual updated Intel/gen-12lp live walk still FAILS: -z movement 15.766 m passes, diagnostics pass, but visualAttached changes true to false, clearance becomes -0.258862 m and correction becomes disabled while grounded remains true. Its p95 is 69.7 ms against the unchanged 16.7 ms limit, with 323 observations; this is not settled performance proof. The remaining vertical-velocity gate and animation heading are under investigation. The scenario now asserts attachment, ±0.1 m clearance, grounded contact and enabled correction on both browser/native paths; native is unrun. Opus has a separate bounded tree-source repair task. Grounding, facing, art, visual judging and 60 fps acceptance remain open. + +2026-10-14 facing regression reproduced and repaired, no acceptance box ticked: an actual generated-consumer live walk on Intel/gen-12lp moves 15.866 m north while the old Euler interpolation changes facingZ from -1 to +1 (the character faces south); the new facing assertion fails. The temporary actual-mannequin/SkeletalMesh3D probe also shows matrix decomposition changes Euler representation around π, so interpolating only rotation.y turns the figure back toward identity. The template now interpolates the heading quaternion with the same turn rate and uses its existing debug surface to expose facingZ; the browser/native forest scenarios require facingZ ≤ -0.95. After the source change, the actual live walk moves 15.766 m north with facingZ -1 before/after, attached feet, measured clearance 0, grounded contact and correction enabled; movement and diagnostics pass. A continuous consumer-only diagnostic records no grounded positive velocity rejected by the gate during this run, so the previous intermittent correction failure is not established as a vertical-velocity problem. The p95 still FAILS: 81.7 ms against 16.7 ms, 308 observations, startup/compilation mixed into the walk. The final focused scaffold/verifier matrix passes 51/51 in three files; scoped Biome and generated-consumer TypeScript/build pass. Exact-pose visual judging, repeated grounding stability, jump regression, native execution, tree/ground art and settled 60 fps remain open. + +2026-10-14 publication preflight: the pre-push drift check correctly rejected the starter scaffold's prior tree hash after the intentional Player/Play changes; the other twelve template hashes matched. Updating only that starter snapshot to the measured generated-tree hash passes the complete 68-test scaffold suite. No quality or runtime threshold changed. + +2026-10-14 extended hardware baseline, no box ticked: a long live-clock Nvidia/Turing 1920×1080 forest walk reaches 174.95 m north and passes attachment, ±0.1 m clearance, ground contact, correction, facing and diagnostics at its final sample. The full run still fails p95 at 44.8 ms against 16.7 ms. Signal observations locate walking after tick 8284; excluding the boundary window leaves nine consecutive non-compiling walking windows, 2700 main-pass records, and at least 84.1455 seconds of accumulated measured cadence after rounding allowance. Those windows fail both budget/cadence: callback p95 28.5–50.9 ms, presented p95 29.1–54.5 ms and 25.85–38.30 FPS. Startup is ready with compile settled and every selected window retains scale 1/MSAA 4. The empty-page control runs at 59.34 FPS on the 59.96 Hz display with no tracer blockers. OS foreground was explicitly activated during the run but is not joined continuously to every window, so full foreground-qualified performance proof remains open. The owner identifies the close/far tree-shape transition as a defect and rejects overall light/shadow/atmosphere; the baseline is being used to repair LOD and authored appearance, not to tick acceptance. + +2026-10-14 develop integration audit, no box ticked: merged remote develop 99aeb6e30 at 6db7012e4, combining the branch async lightmap bake with PRD-568 ASTC encoding. Focused asset/core tests pass 198 with one skipped; assets/core typechecks and scoped Biome pass. The retained forest still pins older core/assets archives and must be rebuilt from a fresh complete cohort. Existing generated source already adopts GPU tier selection and height fog/grading, but its ordinary directional shadow map excludes VirtualShadowNode and its whole-world GLB load does not use WorldCells/TerrainTiles. Therefore cached shadow pages and streamed GPU terrain morph/selection are not established benefits of this consumer merely because their PRs are in branch ancestry. The existing streaming and virtual-shadow paths are being evaluated before new performance claims; ASTC applies to Android/iOS, not this desktop baseline. + +2026-10-14 integration candidate, no acceptance box ticked: the cooked tree distortion is reproduced as index-only LOD levels retaining pre-compaction vertex numbering. The shared asset-cook fix remaps every level and mirrored winding, isolates shared chain/accessor state, and rejects malformed indices through the existing validator plus base bounds checking. Original-transform and shared-accessor RED reproductions fail; final terrain-world suite passes 29/29, including the real forest. The canonical MCP handoff now streams the existing world.json with WorldCells and loadTerrainSplat, preserves the complete collider/measured spawn, derives reach and placement budgets from the manifest, and releases resources on exit and load failure. Verifier/MCP tests pass 47/47. Opus 5.5 high supplies an optional cached-shadow path in generated render source while preserving the ordinary arena map; the forest verifier derives outer windows from the measured world diagonal and forwards regional stream invalidations. Assets/core builds and relevant typechecks pass. Rebuilding assets resolves the previously stale ASTC failures: focused mobile/ASTC contracts pass 14/14. Strict fresh consumer, hardware visual judgment, continuous grounding, settled performance and native proof remain open; the mechanism candidate does not establish the owner AAA scene bar. + +2026-10-14 terrain sampler-input normalization, bounded CPU candidate ready for runtime, no box ticked: `cookTerrainWorld` now gives each map kind (diff, nrm, orm, h) one size and one input encoding across all layers. A kind whose maps already share size, format and alpha keeps its authored bytes. Otherwise every map in the kind, the largest included, becomes lossless PNG at the largest width and height: decoded pixels kept, smaller maps resampled up, never down. This normalizes inputs only; the asset compiler still picks each texture's codec from its content. Both cook callers (`packages/terrain/src/mcp/cook.ts`, `packages/terrain/src/kit.ts`) route through this function and did not change; the MCP path loads the built `@threenative/assets`, which this lane did not rebuild. Real forest sources: grass, dirt and moss diff/nrm are 1024×1024 JPEG, rock is 512×512 JPEG. The published 0.1.0 archive and `starter-assets.json` are untouched. The regression cooks four layers with a half-size rock across all four kinds: every cooked map is PNG, decodes through `decodeImageBytes` to 64×64 RGBA8, the larger maps' decoded RGBA equals their source decode, and core `stackLayers` returns a 4-deep array per kind through a copying renderer stub. The size assertion fails with the normalization disabled (32×32 versus 64×64). Focused specs pass: assets terrain-world 30/30 and terrain-editor-cook 2/2. CPU compile probe on the eight real forest maps (current source `compileAssets`, web, budget none, installed Basis transcoder): all eight compile to KTX2 at 1024×1024 with 11 mips. The normal kind is compatible: four UASTC maps with one signature, which saves 3 samplers. The diffuse kind is still mixed: grass, dirt and moss are UASTC and the resampled rock is ETC1S, so it does not stack. Compiled bytes: diff 4,241,244 (PNG inputs 10,247,896), nrm 5,360,040 (PNG inputs 10,364,486), total 9,601,284. Expected effect, not measured: 19 samplers drop to 16, at the portable limit of 16; the owner accepts 16 as the nearest correctness target. Not every acceptance criterion is complete. The mandatory next step for root: actual NVIDIA and Intel runtime runs with zero WebGPU validation errors. No GPU, browser, native, visual or 60 fps run happened in this lane; those boxes stay open. diff --git a/docs/PRDs/strata-terrain-hardening/PRD-585-strata-proof-runs-in-ci.md b/docs/PRDs/strata-terrain-hardening/PRD-585-strata-proof-runs-in-ci.md new file mode 100644 index 0000000000..6d5189c0df --- /dev/null +++ b/docs/PRDs/strata-terrain-hardening/PRD-585-strata-proof-runs-in-ci.md @@ -0,0 +1,111 @@ +# PRD-585 — Strata's proof runs in CI, not only by hand + +**Status:** PARTIAL +**Priority:** P1 — No Strata playtest, consumer check or editor script runs in CI, so every later PRD in this epic can regress unseen (AC-1 to AC-5). +**Complexity:** 4 (MEDIUM); risk override: none +**Owner:** ThreeNative maintainers +**Depends on:** None +**Epic:** [Strata terrain hardening](README.md) + +## Context + +Audit of `2d2124792` (2026-10-10): + +- `.github/workflows/ci.yml:125-126` and `:275-277` run only `pnpm --filter strata-terrain-preview bake`. + No `test:terrain:*`, `test:kit`, `test:consumer` or `verify-*.mjs` runs in any workflow. +- `examples/strata-terrain-preview/package.json:12-20` wires 7 `verify-*.mjs` scripts; 10 more + are chained from them, undocumented. `playtests/walk.playtest.json`, `scripts/measure-*.mjs`, + `check-water.mjs`, `check-temperate.mts` and `consumer-world.mjs` are referenced by nothing. +- `packages/terrain/__tests__/forest-runtime.spec.ts:11` mocks `WorldCells.load`. No test loads + a baked terrain world package through a real `WorldCells` (core audit D2). +- `editor.playtest.json` (60 lines) checks a vertex count and a screenshot. It does not + exercise a tool, stroke, undo or save. + +## Solution + +Add one job to the existing `ci.yml` (no new workflow file; root `AGENTS.md`): `strata` runs +`test:terrain:web`, `test:consumer` and `test:terrain:editor` against the selected-scope rules in +`scripts/ci-change-scope.mjs`, so it runs when `packages/terrain/`, the example or the world +modules in core change. Add a real `WorldCells` integration spec. Delete the orphan scripts or +index them in the example's `AGENTS.md`. + +## Acceptance Criteria + +- [ ] AC-1 [shared]: The `strata` job runs `test:terrain:web` green on a PR touching `packages/terrain/`. proof: CI run link — Evidence: pending. +- [ ] AC-2 [shared]: The same job runs `test:consumer` and `test:terrain:editor` green. proof: CI run link — Evidence: pending. +- [x] AC-3 [local]: The change-scope selects the job for terrain, example and `world-cells` paths and skips it for an unrelated template. proof: `pnpm exec vitest run scripts/__tests__/ci-structure.spec.ts scripts/__tests__/ci-needs.spec.ts` — Evidence: PASS (213 tests), including `world.ts`, qualification, and required-verdict missing/skipped/failed/unmapped cases. +- [x] AC-4 [local]: A baked 3×3-cell package loads through a real `WorldCells`; walking across cells gives resident cells and instance counts equal to the bake. proof: `pnpm exec vitest run packages/core/__tests__/world-cells-terrain.spec.ts` — Evidence: PASS (1 test); bounded drain fails on timeout and cleanup runs in `finally`. +- [x] AC-5 [local]: Every file under the example's `scripts/` and `playtests/` is wired from `package.json` or named in its `AGENTS.md`. proof: `pnpm exec vitest run scripts/__tests__/strata-script-census.spec.ts` — Evidence: PASS (1 test); recursive, nonempty inventory matches exact documented relative paths, including fixtures. + +## Integration Ledger + +| Capability | Reachable consumer/trigger | Replaces / disposition | Evidence | +|---|---|---|---| +| Strata CI job | PR touching terrain paths → `ci.yml` `strata` | Bake-only steps | AC-1, AC-2 | +| Real package load | `bakeWorldPackage` → `WorldCells.load` | Mocked `WorldCells.load` | AC-4 | + +## Execution Phases + +#### Phase 1: integration spec and script census +**Status:** COMPLETE +**Files:** `packages/core/__tests__/world-cells-terrain.spec.ts` (new), `examples/strata-terrain-preview/AGENTS.md`, orphan scripts (delete or index) +- [x] Real `WorldCells` loads a baked package. proof: `pnpm exec vitest run packages/core/__tests__/world-cells-terrain.spec.ts` — PASS (1 test); core typecheck also passed. +- [x] No unwired script or playtest is left. proof: census spec — PASS (1 test); original package scripts preserved, manual/preparation helpers indexed by purpose. `pnpm sync:agents`, mirror contracts (8 tests), and `pnpm check:docs` passed. + +#### Phase 2: the CI job +**Status:** PARTIAL +**Files:** `.github/workflows/ci.yml`, `scripts/ci-change-scope.mjs`, `scripts/__tests__/ci-structure.spec.ts`, `scripts/__tests__/ci-needs.spec.ts` +- [x] Scope selects and skips correctly. proof: `pnpm exec vitest run scripts/__tests__/ci-structure.spec.ts scripts/__tests__/ci-needs.spec.ts` — PASS (213 tests); the existing protected main-job join rejects missing, skipped, failed and unmapped results. Focused Biome checks passed with complexity warnings. +- [ ] Web playtest green in CI. proof: CI run link +- [ ] Consumer and editor scripts green in CI. proof: CI run link + +The local job wiring is verified; AC-1/AC-2 need a real candidate CI run with successful `strata` execution. Skipped draft checks are not runtime proof. + +Local runtime attempt on candidate `845442617` (2026-10-10, Debian laptop, headed WebGPU, verified Intel `gen-12lp`): workspace build passed and bake was current; terrain playtest failed with 58/62 assertions passing. Failures: `afterFirstFrameTaskMs` 490 > 250 ms, `timeToReadyMs` 21,395.6 > 15,000 ms, `maxViewGpuMs` 326.69 > 12 ms, and a `MeshBasicNodeMaterial` pipeline multisampled-binding mismatch (group 1, binding 2). Thresholds remain unchanged. The independent follow-up passed `test:terrain:editor` on the same Intel WebGPU candidate, including transform, conflict/reload/undo, tool and environment checks. `test:consumer` failed during scaffold install: the old Corepack launcher targeted nonexistent pnpm 12 `bin/pnpm.cjs`. The cached package exposes a native `pnpm` launcher, verified at version 12.10.1; a retry uses that launcher on a job-local PATH, without changing system defaults. That retry reached dependency installation, then failed closed with `ERR_PNPM_IGNORED_BUILDS` for core, runtime-native, esbuild and sharp; the generated project build-approval policy needs repair before consumer behavior can be verified. No approval policy was bypassed. This is failed local evidence, not CI acceptance. + +A desktop comparison on the same executable candidate used verified NVIDIA Turing WebGPU and passed 59/62 assertions. Its failures were `afterFirstFrameTaskMs` 12,635 > 250 ms, `timeToReadyMs` 38,741.8 > 15,000 ms, and `maxViewGpuMs` 35.22 > 12 ms. The Intel shader error did not recur. This is another failed local performance result, not runtime or CI acceptance. Generated consumers currently omit `packageManager`, allowing Corepack to select ambient pnpm 12; the shared scaffolder repair is active and preserves explicit dependency-build approvals. +Bootstrap repair (2026-10-10): generated projects now pin the shipped `pnpm@10.25.0` before dependency installation, including both omitted and present local-package overrides. The build-script allowlist is unchanged. The new regression fails on the original source (missing `packageManager`); the repaired full scaffold suite passes 68 tests. Focused scaffolder TypeScript and Biome checks pass (two existing complexity warnings), and the scaffolder build passes. The actual Node 20/Corepack consumer rerun is pending the laptop thermal guard; AC-1, AC-2 and runtime phase boxes remain open until their required proof exists. + +Parent bootstrap integration: all 68 scaffold tests pass on dde88e30c; scaffolder and assets builds pass, including declarations and package lint. The full consumer retry progressed beyond the earlier pnpm failure but stopped before gameplay: assets now depends on terrain, which the existing game-handoff tarball set does not supply, causing a registry 404. PRD-583 is wiring that local dependency closure. The game stage was not skipped; consumer and CI acceptance remain open. + +2026-10-10 parent consumer repair verified: after integrating the PRD-583 terrain tarball/override closure and baking the required default forest fixture, the full unskipped consumer passes (exit 0). Packed-game scaffold, install, build and WebGPU playtest report NVIDIA Turing; the stock forest loader checks 4 820 placements with zero per-tree copies. This closes the consumer bootstrap failure; the separate showcase runtime budgets and real-CI acceptance remain open. + +2026-10-10 CI status explained: workflow `ci.yml` intentionally gates the scope job on `!github.event.pull_request.draft`. Exact candidate 5ad00f9bc therefore has skipped run 38088798294 and 33 skipped checks; the wait helper's exit 0 is not runtime evidence. The EPIC remains a draft while the full batch is implemented; real CI acceptance stays open until qualification. + +2026-10-12 GPU budget investigation (NVIDIA Turing, headless private Xvfb, host load 4 to 12). The `maxViewGpuMs` budget (12 ms) stays red at 32 to 34 ms and is not fixed. What was measured: all the cost sits in the world-pass bucket (`gpuOther` 23 to 32 ms), while `desert:overview` draws 12 meshes and 1 M triangles. Turning off the post stages (`?off=ambientOcclusion,traa,grade`) moved the worst view 33.76 to 32.31 ms. Hiding the cloud dome, sky, props or water produced no repeatable change. Hiding the terrain (ground plus 786 k-triangle distant ridges) dropped mesa and overview from 24 to 33 ms to 10 to 14 ms. A read of `src/render/terrain.ts` counts about 81 texture fetches and about 160 noise calls per ground pixel. A candidate patch (hoist the UV warp, skip the near block on far ridge rings) compiled, but at a pinned `resolutionScale` of 1 it showed no gain (mesa 32 to 37 ms against 14 to 15 ms on HEAD, with identical HEAD runs spread from 19 to 33 ms), so it was discarded. Two limits on this evidence: the game runs `resolutionScale: "auto"`, which moves the scale between 0.61 and 0.72 during a run, and timestamp medians from the private Xvfb swing by 2x between identical runs. A trustworthy before/after needs a pinned scale and a quiet real display. The box stays open. + +2026-10-12 long-task fix, engine layer: `nodeHasReflector` in `packages/core/src/warmup.ts` walked shared TSL subgraphs once per path to them (no visited set), about 600 ms on the strata scene. The core warmup spec gained a red-first diamond-graph test (20 shared levels, 2^20 `getChildren` calls before, 21 after; 34 of 34 pass). With the fix, the desert world's longest load task fell from 835 to 944 ms to 191 to 228 ms (`afterFirstFrameTaskMs` 0, under the 250 ms budget, two runs). The forest world still shows a 915 to 932 ms task after the fix, so `afterFirstFrameTaskMs` stays red until that task is found. Time to ready is unchanged (desert 19.8 to 21.0 s, forest 28.8 to 29.8 s against 15 s). + +2026-10-12 GPU cost attribution at a pinned resolution scale (desert views, 1080p, scale 1, NVIDIA Turing, private Xvfb, host load 3 to 7, three interleaved runs per arm, medians). Baseline player 36.98 ms and mesa 37.73 ms (`gpuOther` 35 to 36 ms). Hiding only the distant ridge mesh changed nothing repeatable, hiding only the ground mesh saved 9 to 13 ms in player and mesa, and the costs are not additive, which fits a cost of terrain pixels times a heavy fragment shader. Knobs: anisotropy 1 saves 8.5 ms (player) and 5.4 ms (mesa), anisotropy 4 saves 3.5 and 8.9 ms, keeping 2 of the 6 ground layers saves 5.9 and 9.4 ms, MSAA 1 saves under 4 ms, and zeroing the UV-warp noise saves under 3 ms. No single appearance-neutral change gets near the 12 ms budget: stacking anisotropy 1 and a 2-layer ground would still leave about 20 ms. The route the PRDs already name is PRD-591 AC-4, ground plumbing through the engine's splat loader, which replaces the 81 fetches per pixel with a baked splat. That is a large change with a visual-judge gate, so `maxViewGpuMs` stays red and its boxes stay open. + +2026-10-12 weight-gated ground fetches, tried and rejected. A candidate wrapped each ground layer's albedo and relief fetches in an `If` on the layer's maximum blend contribution, using explicit-gradient sampling from the warped UVs (sample sites per pixel 71 to 31 with four inactive overlays). At a pinned resolution scale of 1 on NVIDIA Turing, three interleaved runs per arm: desert player 33.52 to 30.65 ms median, mesa 35.14 to 34.29 ms median, below the 5 ms bar, while identical HEAD runs swung from 15.2 to 39.8 ms (the third HEAD run was the fast one in this and earlier A/B sets, which points at GPU clock or power state, not the shader). Picture check: desert views stayed within noise (mean absolute RGB error up to 0.037), but the forest terrain visibly darkened (mean error 16.4 and 34.4% of pixels over 8, against a HEAD-to-HEAD floor of 1.5 and 6.7%). Reverted, nothing committed. Conclusion for the `maxViewGpuMs` box: fetch-count cuts do not show a reliable gain on this measurement setup, and a trustworthy before/after needs a quiet real display with locked GPU clocks. The box stays open. + +2026-10-12 forest destroyed-texture errors, not reproduced. A capture of the forest at the default look logged 19 `Destroyed texture [Texture (unlabeled 1920x1080 px, TextureFormat::RGBA16Float)] used in a submit` errors, all while the auto resolution scaler sat at 0.85. Five attempts to make it happen again logged zero errors: forest pinned at 0.85 from boot, forest with one forced step 1.0 to 0.85 after warmup, coastal with the same step, forest with the frame-budget callback plus views, and an instrumented automatic run through several steps. GPU tracing found no stale sampled-texture reference. No code changed. The cause is unproven, so the capture lane keeps its zero-console-error assertion and the next occurrence should be saved with its console log. + +2026-10-12 runtime-budget hypothesis (source `8b0c3cf9c`): NVIDIA startup trace attributes 7,346 ms self time over 90 seconds to Three's instance-history `updatePrevious`, alongside 7,525 ms attribute upload. The engine tracker already preserves unchanged history; the adapter still copies and marks it dirty every draw. Smallest candidate: cache the last scheduled source identity and current matrix version per existing history record, keeping moving/unscheduled history and draw ordering unchanged. Expected mechanism result: zero repeated history uploads for unchanged scheduled draws after initial commit; no end-to-end speed claim until paired runtime and visual checks. Regression `instance-velocity-upload.spec.ts` first fails on the actual renderer path (extra upload at line 175). Budgets and scenarios remain unchanged. + +Instance-history candidate verification: first guard also suppressed direct dynamic-array history when matrices changed without a version bump; a second renderer-path regression reproduced that defect. The guard now applies only to engine-scheduled snapshot arrays, retaining Three's raw-array copying. All **71 tests across six focused files pass**, core typecheck and scoped Biome/diff checks pass. The shipped `temporal-aa` browser conformance case passes on NVIDIA/Turing (3.2 s, nonuniform 1280×720 capture, no page or GPU validation errors); runner exit 2 reflects 105 unselected rows, not whole-matrix success. Desktop on existing wgpu host SHA `b756382651baf8347434bdde63cae1b789d126f24f65d2c58ee11e16811ce23e` fails with `wgpuQueueSubmit: Buffer with '' label is still mapped`; the identical case against the prior adapter fails with the identical error and host hash. This is a reproduced baseline native-host limitation, not a native pass or a reason to waive qualification. End-to-end terrain budget, paired performance and visual acceptance remain open. + +The alternative existing Dawn host (`tn-linux-contracts-physics`, SHA `5bc3ecb8abca0c797f8d2ed126b931b9c96a2f8fa4a593b3093e2e7e35e09648`) completes 300 frames and produces a capture but reports repeated submit-while-mapped GPU validation errors; the conformance case fails. No further equivalent native retries are warranted. This corrects the assumption that switching the existing backend is sufficient; current-host temporal buffer qualification remains open. + +2026-10-13 local gate repair: an instrumented repeat reproduced an unhandled `ECONNRESET` from the editor-document test's `ClientRequest._flushOutput`, after its 413 response was already read and the fixture moved on to teardown. Waiting for writable finish timed out because an early rejection can stop that upload. The shared status helper now cancels the remaining request write and awaits its close before returning the observed status; response errors still reject. All four callers retain their original status assertions, including the 10 MiB chunked request. Twelve consecutive instrumented editor-document runs pass 216 assertions with no reset or unhandled error. Server middleware and body limits are unchanged; this is test-client resource ownership, not a suppressed runtime diagnostic. Real CI Strata/editor/consumer proof remains open. + +2026-10-13 qualification repair: the first complete unit phase passed 10,626 tests but failed ten assertions in eight files. Focused reruns now pass all 134 assertions across eleven files. Repairs preserve the gates: the startup fixture supplies a nonempty authoritative manifest missing the requested world; timestamp windows count 300 presented frames on live clocks; the vegetation fixture uses the already-shipped tree-mesh guidance phrase; the core appearance guard ignores type-only re-exports while still scanning world runtime code; the two editor-conflict fixtures are indexed; `transformedLandform` has a generated capability summary; starter-only core/physics imports are declared optional peers. The save queue now uses the existing authoring document and snapshot types instead of duplicate shapes, removing thirteen double casts; its transaction type requires either commands or a document. The prepared-GLTF boundary retains validation while treating incoming JSON as partial until its required asset is checked, and the worker declares its actual global interface. Terrain and assets typechecks pass; focused Biome passes with existing complexity warnings; quality JSON passes without new suppression waivers. The complete unit rerun is pending; no CI or performance box is advanced by these repairs. + +Qualification follow-up: updated full typecheck and lint pass on the laptop; all 317 terrain tests and strict publint pass there. Local affected terrain/assets builds and documentation links pass. An accidental overlap between an assets rebuild and a unit run removed the encoder while tests were reading it; that invalid run was interrupted. After builds completed, both affected files pass all 88 assertions. The sequential package-test phase then passes, and the full unit phase passes 10,634 tests with only two release metadata failures (20 skips); its temporary-directory guard passes 1 → 1. Optional internal peers now use the existing published range `>=0.3.4 <0.4.0`, and the release fixture checks both internal peers advance while external peers remain unchanged. Release/preparation/externals rerun passes 32 assertions; frozen install and strict terrain publint pass. The final complete unit rerun remains pending. + +Final qualification result: the complete unit phase passes **798 files, 10,636 tests, 20 skips** (three skipped files), exit 0; the temporary-directory guard remains 1 → 1. Command: `TN_SUITE_PHASES=unit taskset -c 0-3 nice -n 10 pnpm test`, Node 20.19.6. Four available CPUs select three workers through the unchanged Vitest configuration; assertions and timeouts are unchanged. Duration 848.45 s. Unrestricted parallel runs twice hit the unchanged 60-second build-exit ceiling under host load above 60; the same focused case passes in 29.64 s with this allocation, and the complete bounded board passes. The sequential package-test phase, updated full typecheck/lint, frozen install, strict terrain package validation, and documentation links also pass. Full hosted CI and Strata runtime/performance acceptance remain open. + +2026-10-14 actual CI qualification: [run 38505692929](https://github.com/ThreeNativeHQ/threenative-engine/actions/runs/38505692929) executes the complete selected board on candidate `8f74c3314` (PR head `3d30e3164`). All four unit shards, typecheck, lint, browser/native tests, integration receipts, build and nonvisual templates pass. Strata fails during its web scenario with device-loss errors and resource assertions; consumer/editor steps are therefore not proven by this run. Both golden templates fail during scaffold install because the dlx-local terrain mapping does not propagate to the child project install (npm 404). Package tests independently expose a test dependency on pre-staged scaffolder terrain kits. Bounded repair preserves the existing pnpmfile into child installs through npm_config_pnpmfile and gives the archive test its own authored kit fixture; 35 focused tests pass, including a red/green child-environment regression. Real scaffold installs using the exact CI tarballs now pass for both starter and platformer (ordinary dependency scripts enabled, no install bypass). Both installed games also pass ordinary build:web. The repaired full terrain package test passes all 317 tests and strict publint; documentation checks pass 3,122 links across 1,375 Markdown files. Their complete golden runtime journeys still need rerunning. Desktop conformance has one failing row, virtual-shadow-first-frame (pixel mismatch 0.012477 > 0.01, no GPU errors); no thresholds were changed. CI/runtime acceptance remains open. + +2026-10-14 runner prerequisite isolated: GitHub job API confirms the failed Strata leg used hosted ubuntu-24.04 (runner GitHub Actions 1000083106), and repository variable TN_RUNNER is absent (GET 404). Read-only runner/source inspection finds no registered hardware-GPU lane: the existing tn-local pool uses containers without GPU passthrough. The reported SwiftShader adapter is therefore consistent with the selected lane; it is not evidence of hardware rendering or a source-level explanation for device loss. A dedicated hardware lane is required before this CI result can prove the intended adapter and runtime budgets; changing shared TN_RUNNER would also reroute unrelated work and was not done. Cheap-arm triage completed, but its claimed event order and empty parity metrics were rejected against primary artifacts. Native parity capture analysis finds 11,499 changed pixels; only 11 exceed one RGB level (maximum 94), so the 0.012477 mismatch remains a real failed strict gate requiring investigation, not a tolerance change. Full golden journeys on the laptop initially stop at its ambient Corepack pnpm 12 launcher before scaffold; verified pnpm 10.25.0 is now supplied on a job-local PATH for the retry. No global tool or runner configuration changed. + +2026-10-14 full-journey follow-up: pinning the laptop job to verified pnpm 10.25.0 exposes a second local-source closure failure: initial scaffold installation passes, but the golden journey's independent `pnpm install` requests terrain from npm again. The shared scaffold helper now copies its existing local mapping into the generated project as `.pnpmfile.cjs`, retaining pnpm's ordinary discovery for later installs and corrective commands without adding terrain as a direct game dependency or changing global configuration. The new regression fails before this copy; all 24 golden-path tests and scoped Biome pass after parent review. A real starter scaffold followed by a separate ordinary install succeeds against the exact CI tarballs; its lockfile resolves terrain to that local archive. The complete laptop retry progresses into gameplay, with starter-area-monitoring and starter-deferred-motion-odometer scenarios passing without diagnostics. Remaining scenarios and the platformer journey are still running; no full-journey tick yet. + +2026-10-14 full golden retry outcome: scaffold, independent install, MCP discovery and dev startup pass. Starter passes 23 scenarios; `play` fails its unchanged maxFrameMsP95 ceiling (52.4 ms observed, 33 ms allowed, 200 samples) on real Intel gen-12lp WebGPU. The harness removes its temporary game after failure, so a separate retained starter was recreated from the same CI tarballs and built once for two isolated `play` reruns. Both CLI runs exit 1: p95 43.8/48.8 ms, 197/200 samples. Retained probe: `/tmp/pr483-starter-p95-rLim6Q/starter` on the laptop. No performance code or thresholds changed. Full golden and Strata acceptance remain open. + +Comparable baseline: exact develop `5f7143895` CI run 38486297491 tarballs (artifact 11859288115), with no candidate archives mixed in, generate hash-identical starter `src/`, config and `play` scenario. Two baseline runs also fail only maxFrameMsP95: 44.2/53.3 ms, 199/201 samples, against 33 ms. Both arms use Intel gen-12lp WebGPU, 1920×1080, resolution scale 1, MSAA 4 and completed startup warmup. This reproduces the failure on the base revision; its cause remains unknown. These fixed-step scenario observations do not establish steady-state FPS. Baseline retained at `/tmp/pr483-starter-baseline-OScm7M/starter` on the laptop; no acceptance box closes from this comparison. + +2026-10-13 platformer full-journey attempt: laptop offload exits 75 before launch at 97–100°C; zero stages or scenarios ran and no game was created. Other lanes own the running GPU work and were left untouched. Separately, the Strata CI observations include waveSamples 0 and measuredGpuViewCount 0 alongside software-adapter/device-loss diagnostics; neither this payload nor the timed-out read-only triage establishes the source-level cause. Full runtime acceptance stays open. The batched bootstrap and native fixture-policy repairs pass their focused checks and ci:fast; publication waits for the shared CI board slot currently held by PR488. + +2026-10-13 platformer retry completes: `pr483-platformer-full-r2` exits 0 after laptop temperature drops to 48°C and its GPU lock becomes free. Using the exact candidate CI tarballs with the repaired golden helper and verified pnpm 10.25.0, the ordinary full platformer journey passes scaffold, independent install, MCP discovery, dev startup, every gameplay scenario, web build and artifact assertion on Intel gen-12lp hardware WebGPU. The existing mutated-package control rejects its deliberately missing Vite archive as expected. No software-mode or scenario cap was set. This closes the local platformer journey retry, not Strata or fresh CI qualification. diff --git a/docs/PRDs/strata-terrain-hardening/PRD-591-the-preview-stops-reimplementing-engine-mechanisms.md b/docs/PRDs/strata-terrain-hardening/PRD-591-the-preview-stops-reimplementing-engine-mechanisms.md new file mode 100644 index 0000000000..572a3b8dbe --- /dev/null +++ b/docs/PRDs/strata-terrain-hardening/PRD-591-the-preview-stops-reimplementing-engine-mechanisms.md @@ -0,0 +1,177 @@ +# PRD-591 — The preview stops re-implementing engine mechanisms + +**Status:** PARTIAL +**Priority:** P2 — A second prop LOD pipeline, a spawn-readiness gate and the scatter loop stay in example code where every terrain game must copy them (AC-1 to AC-4). +**Complexity:** 6 (MEDIUM); risk override: visual output changes, so the owner's visual-judge rule applies +**Owner:** ThreeNative maintainers +**Depends on:** [PRD-585](PRD-585-strata-proof-runs-in-ci.md) (the CI job guards the moves) +**Epic:** [Strata terrain hardening](README.md) + +## Context + +Example audit of `2d2124792` (2026-10-10), `examples/strata-terrain-preview/src/render/`: + +- `props.ts:119, 627, 798-897` hand-writes an LOD ladder (`LOD_BANDS`, `levelFor` with + hysteresis, `setLevels`, one `InstancedBatch` per variant, level and role). The game path + (`game.ts:736`) already uses `createStreamedProps` on `WorldCells`; only `editorView.ts:766` + calls `createProps`. Two prop pipelines exist, and `packages/terrain/__tests__/prop-lod.spec.ts` + (647 lines) tests the older one. +- `loading.ts:69-193`: `createSpawnReadiness` / `spawnReadinessSnapshot`, a spawn gate every + streamed terrain game repeats. +- `scatter.ts:299-340`: the rejection-sampling loop and spacing grid repeat for every terrain + game. Per-biome magic numbers (`treeLimit` 3200/600/55/0, `spruceAttempts: 80000`, + `scatter.ts:563-568`). It runs synchronously in `enter` (`game.ts:591`), not sliced. +- `terrain.ts` (1191 lines; `createGroundMaterial` about 430) does not use `loadTerrainSplat`, + which `packages/terrain/starter/forest/world.ts:4, 121` already uses. Height bands are constants + (`SNOW {145,195}`, `SHORE {7.5,1}`) instead of values measured from the world's height range. +- `propMaterials.ts` (1131) and `pack.ts` (838) each define wind, foliage tint and cutout; `pack.ts:144` + and `propMaterials.ts:282-310` repeat the wind hash. + +Mechanism (pooling, LOD, culling, streaming, readiness) belongs in `packages/core`; appearance +stays in game `src/render/` (root `AGENTS.md`, "Where a change goes"). + +## Solution + +The editor view uses the streamed prop path; delete the ladder and its spec. Move spawn readiness +into `WorldCells` as `whenSpawnReady(position)`. Ship a generic `scatter(rules)` in +`@threenative/terrain` and keep rules in game source; run it in the bake, not in `enter`. Compare +the hand-written ground against `loadTerrainSplat` under the visual judge and keep the plumbing +from the engine. Derive height bands from the measured height range. One `foliageSurface()` for +both material paths. + +## Acceptance Criteria + +- [x] AC-1 [local]: The editor draws props through the streamed path, and `levelFor`/`setLevels` are deleted. proof: `pnpm --filter strata-terrain-preview test:terrain:editor` plus `count-loc.ts` — Evidence: 2026-10-12, NVIDIA Turing, private Xvfb. The editor view draws through `createStreamedProps`; `levelFor`, the `propWork` ladder, `createProps` and `createPropsInSlices` are deleted from `props.ts` (10 files, 641 lines removed, 412 added, net -229). `test:terrain:editor` passes before and after (terrain edits 707 to 747 ms). The editor viewport is pixel-identical in four views (mean absolute RGB difference below 2e-6, 0% of pixels over 8; 100 placements, 200 parts, 390,800 triangles each), so no pixel reached the screen differently and no visual judge ran. 88 of 88 preview tests pass; typecheck and Biome pass; `count-loc.ts --check` passes. `check-temperate.mts:45` keeps one tree-scale assertion that already failed before this change, and its streamed section passes on its own. +- [x] AC-2 [local]: `WorldCells.whenSpawnReady` gates the preview's spawn, and the example's readiness module is deleted. proof: `pnpm exec vitest run packages/core/__tests__/world-cells-data.spec.ts` plus `test:terrain:web` Evidence: 2026-10-12, NVIDIA Turing, private Xvfb. `spawnReadinessSnapshot` and its helpers are deleted from `render/loading.ts` (112 lines) and prop streaming's `readinessAt` and `ready` are gone (22 lines); `game.ts` gates on `WorldCells.whenSpawnReady(spawn, 60)` for nearby prop cells and on prewarm for drawing, and terrain plus its collider exist before preparation starts. Seven files, 147 added and 263 removed (net -116). A deliberately premature gate failed the new regression in `world-cells-data.spec.ts` and `strata-loading.spec.ts`, then passed; terrain and preview suites pass 445 of 445. Forest `test:terrain:web` is 41 of 44 before and after: the only failures are the known budgets `afterFirstFrameTaskMs`, `timeToReadyMs` and `maxViewGpuMs`; the spawn assertions pass (6 of 6 spawn cells, grounding, empty loading error). +- [x] AC-3 [local]: Scatter runs in the bake, and `enter` does no scatter work. proof: `test:terrain:web` asserting no long task over 50 ms during `enter` Evidence: 2026-10-12, NVIDIA Turing, private Xvfb, forest, two runs per arm. Measured phases of the old `enter` (ms, run 1 / run 2): terrain mesh 272 / 286, `scatterProps` 355 / 353, `paintCanopy` 7.5 / 7.6, fallback variants 72 / 69, setup, sky and rivers under 40; `enterMs` 764 / 782. Scatter and canopy now run in `scripts/bake.mjs` and `enter` loads a Float32 placement table per world (`src/world/.placements.bin`, gitignored like the other baked world files, regenerated by `pnpm bake`, which `dev` and `build` run; sizes forest 8.1 MB, coastal 5.5 MB, tundra 1.7 MB, alpine and desert 0.4 MB; zero tracked binary growth). Placement lists, order, counts and `paintCanopy` arrays are SHA-256 identical to HEAD in all 5 worlds. After: forest `enterMs` 34.8 / 35.1 ms (forest-scoped runs). Re-measured on the full `test:terrain:web` scenario (all five worlds, six visits): coastal's `enter` was 218 ms (first visit) and 137 ms (return) because the water mesh was built inside `enter` (106 / 159 ms). `load()` now prepares the ocean and water mesh and `enter` only attaches them (`game.ts`, +8/-4). After, one full run: `enterMs` was 10 to 45 ms in every visit (largest 45 ms, so the 50 ms assertion has a thin margin under load) and the `enterMs` assertion passes; the arm's own first candidate run crashed the page while another process held 8 GB of RAM and did not reproduce on the rerun, so treat the fix as one clean full run. Failed assertions: only `afterFirstFrameTaskMs`, `timeToReadyMs`, `maxViewGpuMs`; no `diagnostics` failure and no destroyed-texture errors in that run; `component.player.visible.value.atSteps` passed (it failed in two earlier loaded runs and passed in two others, so it is load-dependent). Coastal player view before/after mean absolute difference 0.1 against a noise floor of about 2.0. Not improved: `afterFirstFrameTaskMs` went 482 / 488 to 597 / 787 ms and `timeToReadyMs` 27.1 / 26.3 to 28.4 / 27.1 s, both still failing their budgets (the host was loaded, so read these as no gain, not as proof of a regression); the largest remaining single tasks are in `buildProps` (asset load 351 to 378 ms, surfaces 131 to 136 ms) and the terrain mesh build (272 to 286 ms). `maxViewGpuMs` unchanged. Loader tests (round trip, truncated file rejected) failed first; 438 tests pass; `check-water` passes. Native not run: `stage-native-assets.mjs` now stages the tables for desktop and the desktop lane was not executed, so no native claim. +- [ ] AC-4 [local]: Ground plumbing uses `loadTerrainSplat`, and height bands come from the world's measured range. proof: `test:terrain:web` — Evidence: pending. +- [ ] AC-5 [local]: A fresh visual judge rates the before/after captures of all five kits NEUTRAL or IMPROVEMENT (same pose, headed WebGPU, two runs per arm). proof: judge verdict and images on the PR — Evidence: pending. + +## Execution Phases + +#### Phase 1: one prop pipeline +**Status:** NOT STARTED +- [x] Ladder deleted; editor on streamed props. proof: editor script + count-loc (see AC-1 evidence) + +#### Phase 2: readiness and scatter +**Status:** PARTIAL +- [x] Spawn readiness in `WorldCells`. proof: spec + web playtest Done 2026-10-12, see AC-2 evidence. + - Engine helper implemented; caller and native closure remain open: `WorldCells.whenSpawnReady(position, radius = 0, timeoutMs = 120_000)` in + `packages/core/src/world-cells.ts`, built on `readinessAt` as the only authority. It snapshots + the position, observes held waiters at the end of the existing `update` after admissions, and + settles on readiness, asset failure, disposal and its finite deadline, clearing the timer and + the waiter on every settlement. proof: `pnpm exec vitest run + packages/core/__tests__/world-cells-data.spec.ts packages/core/__tests__/world-cells-admission.spec.ts + packages/core/__tests__/world-cells-terrain.spec.ts packages/core/__tests__/world-cells.spec.ts` + — parent run: 97/97 passed in 52.94 s. The oversized `2**31` deadline first reproduced + Node timer overflow, then passed with the supported `(0, 2147483647]` ms bound. + Disposal clears the timer; unsettled test polling fails closed. Earlier core typecheck/build + (publint) passed; the parent core typecheck after the bound correction also passed (exit 0). + - Caller migration implemented, runtime acceptance still open: the preview and five kit fixtures + join `WorldCells.whenSpawnReady` with every stream's `prewarmed` promise. One entry deadline + covers preparation and admission; cancellation, late settlement and pre-props failure capture + remain guarded. The old `createSpawnReadiness` helper is deleted; loading UI and bounded failure + receipts remain. Timeout failures update UI state immediately. Empty kit failures receive a + nonempty kit label and cannot be overwritten by late success or another failure. + Parent reproduced the empty-error defect in four kits, corrected Forest's VM setup, then ran + the actual-source loading tests: 31/31 passed (1.08 s); the focused outstanding-world-load test + also passed (other nine tests skipped). Terrain typecheck and direct preview `tsc --noEmit` + passed. Scoped Biome and `git diff --check` passed. The web playtest, all-kit runtime captures + and caller full native-matrix proof remain unverified; this box stays open. + A subsequent `test:terrain:web` attempt exited 1: framework startup reached ready, but the + headed WebGPU recipe selected SwiftShader, then reported device loss and a blank forest + capture. Eleven assertions failed; this is not render or performance evidence. The captured + adapter was read from that attempt's `capture.json`; the retained run log is + `591-preview-web.out` in the coordinator cache. + Environment diagnosis found loaded NVIDIA kernel driver 615.71.09 against NVML userspace + 615.78.08 (`nvidia-smi` exit 18). The laptop refused offload at 99°C. Hardware qualification + was restored with process-local matching cached libraries: `nvidia-smi` reports + RTX 2080 / 615.71.09. A fresh headed `world-terrain-inline-hash` selected case passed + in 4.8 s on NVIDIA/Turing, with no page or GPU validation errors and a nonuniform capture + (`packages/runtime-native/artifacts/strata591-matching-driver-web/report.json`). + The harness exited 2 because 102 unselected cases are blocked; this proves only the selected + case. No system driver change, assertion change or budget change was needed. Full preview + and all-kit qualification remain open. + The matching-driver full preview rerun reached visible forest, coastal and alpine captures, + then hit the external 720 s deadline (exit 124 / SIGTERM) before a final assertion report. + It does not prove runtime budgets or all-kit success. The fresh forest smoke screenshot is + [attached to the EPIC PR](https://github.com/ThreeNativeHQ/threenative-engine/pull/483#issuecomment-6106317255), + labelled with performance and visual comparison still pending. Documentation checks passed: + 3,057 links across 1,360 Markdown files, plus all 252 tests across the six prose-lane specs. + - Native availability corrected: the linked checkout has no `third_party/` or `build/` of its own, + and the primary owning checkout already ships + `packages/runtime-native/build/tn-linux-wgpu/mystral` (also `tn-linux-jsprofile` and + `tn-linux-contracts-physics`). That existing binary proves the gate on the desktop lane. It is + not a new C++ build and not a whole-source or full native-matrix qualification. + - Gate component landed in the existing `world-terrain-inline-hash` scene as + `assertSpawnReadiness()`. It drives the real `WorldCells` over one decoded empty-run cell (no + fetch, no GLTF, no browser global), and asserts a region outside the ring that pends, the + admitting `update` that settles it for its snapshot, one-shot success, disposal rejection and a + finite `25 ms` deadline, before the frame renders. proof: selected row + `--target --only-tests world-terrain-inline-hash`. + - Parent review found the caller surface leaked: `readinessWorld` made a `MeshBasicMaterial` + per load and `WorldCells.dispose` never released a caller surface. Fixed by returning + `{ world, surface }`, disposing the surface in all three callers' `finally` and on a rejected + `load`; same geometry hash and render tree. + - Node: `packages/runtime-native`, `pnpm exec vitest run --config vitest.config.ts + tests/terrain-inline-jobs.test.mjs` — 3/3 passed, pinned hash unchanged. + - Web: pass, 3.0 s, hardware adapter `nvidia/turing`, no page errors, capture non-uniform; + fresh report `packages/runtime-native/artifacts/strata591-cleanup-web/report.json`. + - Desktop: pass with the primary binary through `THREENATIVE_RUNTIME_BINARY`, 15.5 s, + `pixelMismatchRatio` 0, `perceptualDeltaE` 0, native exit 0, runtime SHA-256 + `b756382651baf8347434bdde63cae1b789d126f24f65d2c58ee11e16811ce23e` (unchanged); fresh report + `packages/runtime-native/artifacts/strata591-cleanup-desktop/report.json`. The report keeps + only the last 4000 bytes of native stdout, so a direct run of the same bundle printed + `TN_WORLD_SPAWN_READINESS pending=resolve dispose=reject timeout=25ms` beside + `TN_TERRAIN_INLINE_HASH hash=d138e335 ... expected=d138e335` + (`artifacts/strata591-cleanup-desktop/direct-native.log`). + - Still open under this box: caller runtime verification, its web playtest and the full + native-matrix claim. The selected native row proves the engine gate component only. +- [x] Scatter off the `enter` path. proof: web playtest long-task assertion Done 2026-10-12, see AC-3 evidence (enter 764 to 35 ms). + +#### Phase 3: ground and look check +**Status:** PARTIAL +- [ ] Engine splat plumbing, measured bands. proof: web playtest +- [ ] Visual judge passes. proof: judge verdict on the PR + +Parent review of the native gate found and corrected caller-material ownership: all three fixtures dispose their surfaces, including rejected loads. After the correction, parent inspected the fresh selected web/desktop reports and direct native log: both selected rows pass, no GPU validation errors, desktop image metrics are zero, and both readiness/hash markers appear. Parent reran the focused Node fingerprint spec (3/3 passed, 7.18 s), scoped Biome passed, and documentation links passed. Caller migration and full runtime/visual acceptance remain open. + +2026-10-12 coastal stone contact: the remaining thin tan rim is removed by explicitly casting the front-facing stone surface in both procedural and imported coastal material paths (four added lines in `propMaterials.ts`/`pack.ts`). CPU rays through the actual transformed fallback rock confirm terrain contact and sunlight obstruction; AO-off, haze-off and MSAA-off probes leave the defect unchanged. Two exact-pose BEFORE/AFTER headed WebGPU runs report NVIDIA/Turing. Fresh read-only visual judge: **IMPROVEMENT for pair 1, pair 2 and overall**; the tan underside contour disappears with texture/top shading retained and no visible new acne or halo. The deepest contact pixels merge into shadow, and AFTER 1 is sharper globally; pair 2 independently supports the contact improvement. Imported coastal materials receive the same override but were not visually captured. Existing material/LOD/shadow specs pass 35/35; scoped Biome and diff check pass. The full terrain scenario completes with 60/63 assertions passing; the existing three runtime budgets fail (see run below), so this does not close the five-world AC-5 gate. + +Judged captures, pinned on orphan `strata-hardening-screenshots` commit `0611cc9809b287eac1b0969934e5f799ed082305` (all four public URLs return HTTP 200 and byte-match the captures): [BEFORE 1](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/0611cc9809b287eac1b0969934e5f799ed082305/pr483/stone-shadow-side-before-1.png), [AFTER 1](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/0611cc9809b287eac1b0969934e5f799ed082305/pr483/stone-shadow-side-after-1.png), [BEFORE 2](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/0611cc9809b287eac1b0969934e5f799ed082305/pr483/stone-shadow-side-before-2.png), [AFTER 2](https://raw.githubusercontent.com/ThreeNativeHQ/threenative-engine/0611cc9809b287eac1b0969934e5f799ed082305/pr483/stone-shadow-side-after-2.png). + +Full terrain rerun (2026-10-12, stone source `60e16086d`, headed NVIDIA/Turing): the official `terrain.playtest.json` completed with **60/63 assertions passing**, exit 1. Console/network/runtime-diagnostic policy passes. Open budget failures are `afterFirstFrameTaskMs` 2,273 >250 ms, `timeToReadyMs` 52,877.5 >15,000 ms, and `maxViewGpuMs` 47.92 >12 ms. Navigation/server deadlines were 120/600 seconds to permit infrastructure startup; assertion budgets were unchanged. This full failure remains acceptance work, not a green scenario claim. Existing harness artifacts: `examples/strata-terrain-preview/artifacts/playtest/web`; exact output `/tmp/pr483-final-terrain-web.log`. + +Startup attribution (2026-10-12, source `4dd84d084`): shipped `playtest trace --no-wait --settle 0.001 --seconds 90 --no-input --stall-ms 250` captured 342,821 events on NVIDIA/Turing. Host display `:0` was unavailable; the diagnostic used private headed Xvfb, so frame rate is suppressed and the CLI correctly exits 1 (`TN_TRACE_VIRTUAL_DISPLAY`). This is function attribution, not acceptance or a performance win. Fifteen main-thread tasks exceed 250 ms, largest 1,608 ms. Across the sampled 90,234 ms, `updateAttribute` uses 7,525 ms self time and Three's instanced `updatePrevious` 7,346 ms; graph child traversal and bundle-safety snapshots are also material. These whole-window totals do not identify the single startup task's cause; inspect individual long-task stacks before choosing a fix. No budget, render stage or quality setting changed. Raw diagnostic: `/tmp/pr483-startup-trace.json`; summary is in the local long-running-task cache. Scene doctor separately reproduces non-finite `entities[1].bounds.height`; exact object/projection instrumentation is pending. + +Ground-loader prerequisite (2026-10-12): `ITerrainSplatLayer.maps` now accepts optional package-relative diffuse, normal, ORM and height paths, preserving existing filename defaults and presence flags. This lets the preview reuse its current `_diff_1k.jpg` / `_nor_gl_1k.jpg` maps without duplicate image aliases. Explicit null/malformed paths or containers reject, and missing explicit maps propagate failure without fallback. The existing `terrain-splat-array` conformance fixture exercises all four overrides with unchanged image data. Actual-source tests reproduced the path mismatch before the change; the parent rerun passes 29/29 focused loader tests. Core TypeScript, fixture syntax and scoped Biome pass (one existing unrelated complexity warning). Browser/native fixture execution is pending. AC-4 remains open: bake/Vite/native staging must emit the real descriptor/table/raw splat, and the consumer must reuse the engine blend while preserving game-authored sampling and measured height bands. No preview material or assertion budget changed in this prerequisite. + +Explicit-map execution (2026-10-12, loader source `17e6dec78`): the selected `terrain-splat-array` case passes in browser on NVIDIA/Turing at 1280×720 (nonuniform capture, no page or GPU validation errors) and on the existing desktop wgpu host for 300 frames (native exit 0, no GPU validation errors). Desktop image comparison: pixel mismatch 0, perceptual delta E 0. Runtime SHA-256 `b756382651baf8347434bdde63cae1b789d126f24f65d2c58ee11e16811ce23e`. Both bounded harness invocations exit 2 because 105 unselected cases remain blocked; these are selected-case passes, not full-matrix qualification. The first desktop invocation executed no case because its default browser-reference directory was absent; passing `--reference` to this same run's browser captures resolved that prerequisite. Existing reports: `packages/runtime-native/artifacts/strata-splat-paths-{web,desktop}/report.json`. Parallel uncommitted bake work is recorded as dirty provenance; it does not enter this fixture. AC-4 remains open. + +Ground-package export (2026-10-12): the bake now emits one minimal surface-loader descriptor, layer table and two-plane raw RGBA8 mask per biome; all eight evaluated material channels are preserved with the loader's (x, -z) row orientation. Existing map paths and the current CC0 fallback for optional licensed rock maps are reused without duplicate image files. Vite and native staging deliver the same 20 surface/placement files. Three focused packaging contracts pass, including real painted Terrain weights, map existence, source-array preservation, malformed mask rejection and missing-file failure. The parent real bake first exposed new static TypeScript imports resolving before the tsx registration; awaited imports fixed that entry failure. A fresh full five-world bake passes in 24.29 seconds, and all ten checked pre-existing world/placement files remain SHA-256 identical. Preview build passes (Vite 6.74 seconds); actual browser dist and native staging each contain 20 byte-identical files. Each biome's raw mask is 528,392 bytes. Combined loader/export tests pass 32/32, script syntax and scoped Biome pass (two existing complexity warnings). Generated raw masks are ignored like the existing baked JSON and placements. Runtime ground material still uses its prior path; AC-4/AC-5 remain open. + +Ordered-blend prerequisite (2026-10-12): optional `layerSample` and `materialFactory` callbacks let game-authored projection and final material assembly reuse the loader's existing ordered colour/normal/ORM blend. Samples are precomputed before weight callbacks; custom sampling skips array stacking and reports a loaded-sampler upper bound rather than pretending to measure shader bindings. Callback-loaded textures are owned until material disposal, with idempotent cleanup on composition errors and late asynchronous completion; factory-only stacked sources are not disposed twice. Parent focused loader tests pass 36/36, including actual node-graph ordering, unchanged defaults, channel fallback and failure/disposal cases. Core TypeScript, build/publint and scoped Biome pass; Biome reports a new complexity advisory in the loader and the existing traversal advisory. Browser/native execution of the callbacks, preview consumer migration, measured height bands and five-world visual acceptance remain unverified. No acceptance box is closed by this prerequisite. + +Preview consumer migration (2026-10-13): `createGroundMaterial` now loads the real baked world/layer/mask package through `loadTerrainSplat`; game callbacks retain existing projection, relief, geometric placement curves and final lighting while core owns the ordered colour/normal blend. The duplicate consumer blend loop and image-set loader are removed. Geometry-derived weights intentionally override the package mask curve; no claim is made that the baked mask determines the current procedural look. Optional owner-local rock maps retain the portable CC0 fallback and separate disposal ownership. Actual-source consumer tests exercise absent/present licensed maps and image disposal. A new cancellation regression first fails when terrain disposal precedes asynchronous maps, then passes with a geometry-disposal guard preventing late attachment. Parent combined packaging/loader rerun passes 42/42; example TypeScript passes. Worker scoped Biome passes with five complexity advisories. Browser/native rendering, measured height bands and paired five-world visual verdict remain open; this tested source slice does not close AC-4 or AC-5. + +Normal pre-push repair (2026-10-13): the first push fails `ci:fast` on stale complete-scaffold hashes and lint. Read-only comparison isolates the thirteen scaffold changes to the regenerated canonical Three patch copied into every kit; expected hashes are refreshed from all thirteen actual trees while preserving the full-tree equality assertion. The focused scaffold contract passes (one selected test, 67 unselected). Generated native `assets/world/*.json` copies now share the existing generated-source JSON formatting exclusion; semantic export/staging tests remain active. Inherited donor gait keys retain their exact external spelling by quoted literals and an exact-file quote-preservation formatter setting. Naming and literal-key rules remain enabled; all other donor AST nodes, values and references are identical after parsing. Scoped Biome passes for these three files. Full normal hook rerun and push remain pending; these repairs are not full CI acceptance. + +Measured elevation bands (2026-10-13): each loaded scene now measures its actual finite height samples once and shares a fresh readonly biome clone with ground, imported stones and procedural stones. Authored fractions and resolved metre bands have separate types/fields; no world-key cache can leak across cancellation or reloads. Flat worlds retain their actual equal min/max and emit constant finite elevation weights, with no invented metre range. Empty/nonfinite samples and overflowed extrapolated bands reject. Actual-source RED tests reproduce the old alpine graph's fixed 50/180 thresholds and the finite-input overflow case; parent final tests pass 54/54 across surface/LOD/package contracts, plus example TypeScript and scoped Biome (19 complexity advisories). Scale-and-offset tests inspect actual snow-node constants and independent snapshots. Alpine snow now resolves from .60–.85 of its 1.20–654.50 m range to 393.18–556.505 m; tundra .15–.45 resolves to 16.515–30.945 m. Temperate fractions above one deliberately reserve snow for the distant horizon; forest/coastal snow stays approximately 145–195 m. Desert's snow remains disabled. Existing metre-scale shelter/noise offsets remain authored appearance choices. GameState exposes measured min/max and resolved snow bounds for runtime assertions. No paired five-world judgment or native callback rendering has run for this source yet; phase 3 and AC-4/AC-5 remain open. + +2026-10-13 actual callback GPU proof: the existing `terrain-splat-array` case now renders a small offscreen three-layer probe through both custom callbacks before its unchanged visible sixteen-layer default scene. Its exact oracle checks callback-scaled albedo, encoded normal, ORM and authored mask populations (8 base, 4 layer zero, 4 layer one), with only one-byte quantization tolerance. Four single-row reads avoid Three's padded multi-row buffer representation. The first actual browser runs exposed and corrected the readback-layout and mask-population assumptions without widening the oracle. The selected headed WebGPU case now passes on NVIDIA/Turing at 1280x720 with no page/GPU errors (`strata-splat-callback-web-fixed/report.json`); CLI exit2 reflects 105 unselected blocked rows, not a full matrix pass. The cached August wgpu host fails the new readback with zero pixels and its known still-mapped-buffer queue-submit panic; native acceptance remains open while the current canonical Dawn host builds. Fixture syntax and scoped Biome pass. + +2026-10-13 five-world AFTER attempt: the first sealed measured-band run captures forest, coastal and alpine, then loses its WebGPU device during the subsequent switch and renders blank desert frames. The saved console first reports an invalid external instance, then unknown device loss and downstream timestamp/readback errors; no evidence establishes that query resolution caused the loss. The owned run is stopped with exit130 and retained for diagnosis. Later DevTools/window changes invalidate its calibration, so none of this run closes the paired visual or runtime acceptance. Both sealed BEFORE runs remain valid and labelled. A bounded cold alpine-to-desert diagnostic now retains Chromium process stderr; no acceptance budget or readiness check changed. + +2026-10-13 current native callback correction: the canonical default Dawn + V8 v0.3.4 host builds successfully in 8 min 32 s, SHA256 `59b3ec63dad29db0c1b0938e241064a7846fc63b9171e2d927874e6301f899f9`. The exact selected callback case now passes on this host: 300 frames, native exit0, 1280x720, pixel mismatch0, perceptual deltaE0 and no GPU validation errors (`strata-splat-callback-desktop-current/report.json`). The wrapper CLI exits2 for 105 unselected blocked rows. This supersedes the cached August host's callback failure, without accepting a full native matrix. Both callback hooks therefore have actual browser and desktop proof; full preview/visual acceptance remains open. + +2026-10-13 regression isolation: the bounded cold alpine-to-desert case passes against the pinned953 baseline with nonblank 2.27MB/1.60MB frames, but fails against the pinned e2e candidate with blank8517B frames and WebGPU device loss. Chromium stderr confirms an unexpected GPU-process exit512 on the candidate. The readable kernel journal establishes no OOM/Xid event in that interval; a later low-memory snapshot cannot exclude an earlier peak. Existing pipeline census records candidate ground fragment121014B versus baseline118896B, so shader-size explosion is not established. Source-specific regression investigation now captures generated shader code in diagnostic copies; no repeated full AFTER run or acceptance claim. + +2026-10-13 timing-sensitive diagnostic result: the sealed953 baseline and sealed e2e candidate both pass the cold alpine-to-desert route when the same synchronous shader-source logging hook is installed in diagnostic copies (74frames, all nine assertions, nonblank alpine/desert frames). All61 shader sources per arm match their own pipeline-census hashes and byte lengths. Main-ground bindings and texture-sample counts are unchanged; graph changes include vec3 color intermediates, duplicated inline noise and a constant-valued macro term. None establishes a shader bug. These diagnostic passes do not erase the two uninstrumented candidate failures or close preview acceptance; a forwarding-only versus logging-hook comparison will test timing sensitivity before any product fix. + +2026-10-13 recovered full AFTER run1: the unchanged sealed e2e preview completes the entire five-world scenario with all assertions passing and zero optional404 errors. Parent validation confirms15 distinct nonblank poses on NVIDIA Turing1920x1080,657 calibrated surfaces at scale1/MSAA4,60 zero streaming queue counters and60 elevation/snow observations matching the sealed heightfields and measured biome policy. All15 images are labelled in review-after-1. The pure-JSON validator and actual generated heightfield paths were repaired without weakening its checks. This establishes the completed run; earlier device-loss failures remain retained and unresolved. Run2 and the independent paired visual verdict remain open. + +2026-10-13 second recovered full AFTER capture passes all15 poses,654 calibrated scale1/MSAA4 surfaces,60 queue observations and60 elevation/snow observations; no console errors. Fresh independent review of all60 BEFORE/AFTER images returns REGRESSION for every alpine pose in both pairs; other worlds are NEUTRAL or IMPROVEMENT. The original visually rejected measured-height candidate remains unaccepted. One separate alpine exceedance-coverage experiment passes24 focused unit tests; two actual capture runs and fresh judgment remain pending. Earlier cold-transition GPU process failures remain unresolved despite the later complete captures. + +2026-10-13 alpine exceedance-coverage experiment is rejected and its two tracked files restored to the committed candidate. Both three-pose runs validate NVIDIA Turing1920x1080, scale1/MSAA4, twelve settled queue observations and twelve measured-band observations; diagnostics fail on84 console404 errors per run, with source URLs unobserved. Fresh independent review of all12 images returns REGRESSION overall: player view regresses in both pairs, close and overview are NEUTRAL. The assumption that the proposed scalar coverage fractions preserve coherent lower-slope snow is unsupported; no further equivalent quantile tuning or visual acceptance is claimed. Rejected source, seals, raw captures and labels remain isolated under artifacts/alpine-snow-coverage. + +2026-10-13 bounded GPU attribution remains incomplete: final diagnostic joins119/120 required frame identities and fails closed, while the observer resolves120 frames/1320 queries with zero queued, pending, undrained or dropped counts. The last pass identity is missed after the diagnostic stop fence. For119 complete rows only, total GPU p50/p95/max is16.77/19.65/21.28ms; the shared scene-camera1920x1080 single-sample offscreen query averages15.46ms across terrain, props, sky and water memberships. No per-object geometry/instance cost, separate shadow or compute series is observed. This partial diagnosis neither attributes the original47.92ms worst-view sample nor passes the12ms budget. Further equivalent diagnostic reruns are parked. diff --git a/docs/PRDs/strata-terrain-hardening/PRD-593-the-three-patch-is-generated-and-the-upload-lane-leaves-it.md b/docs/PRDs/strata-terrain-hardening/PRD-593-the-three-patch-is-generated-and-the-upload-lane-leaves-it.md new file mode 100644 index 0000000000..4e8b233a9e --- /dev/null +++ b/docs/PRDs/strata-terrain-hardening/PRD-593-the-three-patch-is-generated-and-the-upload-lane-leaves-it.md @@ -0,0 +1,164 @@ +# PRD-593 — The three.js patch is generated, and the upload lane leaves it + +**Status:** PARTIAL +**Priority:** P1 — #381 grew the hand-maintained three patches by about 11 000 lines on private three internals, so any three upgrade can silently break texture upload for every game (AC-1 to AC-4). +**Complexity:** 6 (MEDIUM); risk override: compatibility — a wrong patch breaks every game's renderer +**Owner:** ThreeNative maintainers +**Depends on:** None +**Epic:** [Strata terrain hardening](README.md) + +## Context + +Core audit of `2d2124792` (2026-10-10): + +- `packages/core/patches/three@0.185.1.patch` grew +630/−120 lines and + `three@0.185.1-prd269-upgrade.patch` grew from 5 620 to 15 838 lines. Each change appears up to + six times (`src/*`, `build/three.webgpu.js`, `build/three.webgpu.nodes.js`, in both files). + `packages/core/scripts/apply-three-patch.mjs` stacks them; no script generates them. +- The patch adds a bounded async compressed-texture upload lane (`Textures.js` +119, + `WebGPUTextureUtils.js` +147: `updateTextureAsync`, a promise queue, an `AbortController` per + texture, 2 ms and 64 KiB budgets), exposed as `prepareTextures` in `packages/core/src/renderer.ts`. + It reads private `_textures`, `_uploadTail` and `backend.get(texture).texture`. +- It also adds, for every game: a `_depthBuffers` cache per sample count, a 3D-texture + attachment guard for wgpu-native, bind-group invalidation on texture destroy, and an + `updateTexture` wrapper that checks `_pendingUploads` and `uploadError` on each call. Its + per-frame cost is unmeasured. +- Owner rule (memory "three patch last resort"): patching three is the last resort. + +## Solution + +1. Add a generator: a pinned three checkout plus a patched `src` tree regenerates both patch files; + the compiled-build hunks come from three's own build. CI fails when the generated patch differs + from the committed one. +2. Move the upload lane out of the patch: core creates the `GPUTexture` and writes mips with + `device.queue.writeTexture` on its own budget, then hands the texture to three through a + documented seam. If no seam exists, file the upstream issue and record the retire condition in + the patch header. +3. Split the remaining additions into named hunks with one red-green spec each, and measure the + `updateTexture` wrapper's per-frame cost. + +## Acceptance Criteria + +- [x] AC-1 [local]: One command regenerates both patch files, and the result equals the committed files. proof: `pnpm --filter @threenative/core patch:generate && git diff --exit-code packages/core/patches` — Evidence: passed after commit on 2026-10-10, including default pinned upstream download and own build; both artifacts unchanged. A separate fresh `--check` build also printed `matched`. +- [x] AC-2 [shared]: CI fails when a patch file is edited by hand. proof: CI job step in `ci.yml` — Evidence: the fail-closed hand-edit control below exits 1 locally; the required generated-patch check ran successfully in [candidate CI](https://github.com/ThreeNativeHQ/threenative-engine/actions/runs/38505692929/job/115662759477) before package publication (2026-10-14). + - Local CI wiring proof (2026-10-11): moved `Verify the generated Three.js patch has not drifted` from `budgets` to `build-artifacts`, before workspace-dist cache restore/build/publication, including develop cache warming. `time pnpm --filter @threenative/core patch:generate --check` reports wall cost and preserves failure; the generator throws on download failure and checks the pinned archive hash. CI caches pnpm, not the fresh upstream archive or its npm build. Clean check: `matched`, exit 0, 54.52 s. Appending one newline to the committed base patch: drift rejection, exit 1, 58.84 s; `git checkout -- packages/core/patches/three@0.185.1.patch` restored it byte-for-byte. Both patches and the generator select the required build job without impact-map or receipt changes. The new placement contract failed before the move and passed afterward; all 12 specs found by `rg -l "ci.yml" scripts/__tests__` passed (458 tests, 20.46 s), including structure, needs, selection and integration receipts. `pnpm check:docs` passed; scoped Biome passed with three existing complexity warnings. AC-2 and the Phase 1 CI box remain unticked pending a real CI run link; no push. +- [ ] AC-3 [local]: `prepareTextures` uploads compressed textures with the patch's upload-lane hunks removed. proof: `pnpm exec vitest run packages/core/__tests__/three-compressed-upload.spec.ts` plus a WebGPU playtest of the strata preview — Evidence: pending. + - Open: AC-3 and the Phase 2 box stay unticked. The patch still contains the upload-lane hunks. This PRD stays open. + - Public proof (2026-10-11): `renderer-texture-preparation.spec.ts` passes 43 of 43 tests. The spec now has 35 existing tests and 8 added tests. + - Guards: the planner rejects a mip whose size is not the halved base, and a mip chain longer than the base allows. Both checks run before any GPU allocation. + - The write step rejects an unaligned base unless the device reports `texture-compression-unaligned`. The check runs before `createTexture`. + - Cancellation: a public test rejects a cancelled caller at once and starts the next allocation after every popped scope settles. It passes. + - Legacy proof: `three-compressed-upload.spec.ts` passes 102 of 102 tests. It drives three's internal `Renderer`, `Textures`, and `WebGPUTextureUtils` modules. The patched upload lane is in `Textures.js`. Together, both specs pass 145 of 145. + - Gates: core typecheck exits 0. Scoped Biome exits 0 with three complexity warnings: `planCompressedUpload` (33), `upload` (21), and `writeTiles` (25), limit 15. `git diff --check` exits 0, and the untracked helper has no whitespace findings. + - Earlier work stays in place: source isolation, device and lifetime ownership, and the error-scope drain. A synchronous `check` or `writeTexture` throw now waits for every popped error scope before the lane advances. + - Depth guard (2026-10-11): `compressedLayers` rejects a 2D `CompressedTexture` whose `image.depth` is above 1, before any GPU allocation. In installed three 0.185.1, `CompressedArrayTexture` sets `image.depth`. `CompressedTexture` sets `image` to `{ width, height }` only. + - `renderer-texture-preparation.spec.ts` passes 49 of 49 tests, up from 43. Six tests were added. Only the 2D rejection failed before the guard: `promise resolved "1" instead of rejecting`. The other five pass before and after, so they pin existing behavior. Surplus layer data stays accepted: three depth tests start with four layers at depth three. + - Legacy `three-compressed-upload.spec.ts` passes 102 of 102. Core typecheck exits 0. Scoped Biome exits 0 with the same three complexity warnings. `git diff --check` exits 0 for the spec, and `git diff --no-index --check` prints nothing for the helper. These are mock-device unit results only. No browser, native, visual, or perf run. + - Binding state (2026-10-11): `snapshotOf` compares the binding fields `Texture.copy` carries, except `name` and `userData`, plus `wrapR` and `compareFunction` when present, `source.dataReady`, and matrix elements as numbers. A changed binding starts a new upload. The cached prepared texture stays until that upload commits. + - Binding proof: `renderer-texture-preparation.spec.ts` passes 59 of 59 tests. Before this change, 10 binding tests failed and 49 passed. `three-compressed-upload.spec.ts` passes 102 of 102 tests. Core typecheck exits 0. Scoped Biome exits 0 with the same three complexity warnings. `git diff --check` exits 0, and the untracked helper prints no whitespace findings. These are mock-device unit results only. + - Depth guard, tightened (2026-10-11): a 2D texture accepts only `image.depth` of `undefined` or `1`. Before the change, six new cases failed because the old guard let each value through: `NaN`, `0`, `-1`, `-Infinity`, `0.5`, and `null`. The first failure reported `promise resolved "1" instead of rejecting`. `Infinity` and `1.5` already rejected. Invalid values now get a message that does not name an array class. + - Gates (2026-10-11): `renderer-texture-preparation.spec.ts` passes 69 of 69. `three-compressed-upload.spec.ts` passes 102 of 102. Core typecheck exits 0. Scoped Biome exits 0 with the same three complexity warnings. `git diff --check` exits 0 for the spec, and `git diff --no-index --check` prints nothing for the helper. These are mock-device unit results only. AC-3 stays open. + - Remaining proof: native, browser, visual, perf, and patch. AC-3 needs a WebGPU playtest of the strata preview, and the patch hunks removed, before it can be ticked. + - Native proof, first run (2026-10-11): the selected desktop row failed on the stale wgpu host `build/tn-linux-wgpu/mystral` (v0.3.0, built 2026-08-27, sha256 `b7563826…ce23e`). The full host stdout shows the cause: `[ThreeNative conformance] failed: Error: Device lost during texture preparation.` A probe script on that host shows `device.lost` resolves at once with `undefined`. That host predates the #381 fix in `src/webgpu/bindings.cpp`, which keeps `device.lost` pending. The EGL vendor file was not the cause: the host acquired the RTX 2080 on Vulkan. + - Native proof, current host (2026-10-11): `pnpm native:build` in this checkout built the default Dawn + V8 host `build/tn-linux/mystral` (v0.3.4, sha256 `3377038a…71fadb`) in 5 min 18 s. The selected desktop row `compressed-texture-preparation` passed twice: 22.5 s and 14.4 s. Adapter: `NVIDIA GeForce RTX 2080`, backend Vulkan. `native.completed` is true and `gpuValidationErrors` is empty. The 1280x720 screenshot shows a green upper half and a red lower half on a dark background, which matches the approved web image. The runner exits 2 because 103 unselected rows report `blocked`, so this is not a full-board pass. + - WEB headed setting: `packages/runtime-native/conformance/run-conformance.mjs:807` sets `headless: false`. + - AC-3 stays open. Web and native now pass for the public helper, but the patch still contains the upload-lane hunks, and the strata preview WebGPU playtest has not run without them. + - Upload patch removal (2026-10-11): removed the private `Renderer.prepareTextureAsync` and backend delegation, `Textures.updateTextureAsync`, its serialized promise queue, per-texture abort/loss/disposal handling, and the bounded 2 ms / 64 KiB uploader in `WebGPUTextureUtils`. Removed the `updateTexture` wrapper and its async-only helpers: only `updateTextureAsync` populated `_pendingUploads` and wrote `data.uploadError`, so both wrapper checks belonged exclusively to that lane. Retained bind-group invalidation on texture destroy, the per-sample-count `_depthBuffers` cache, the wgpu-native 3D attachment guard, and all unrelated hunks. Core `prepareTextures` already routed exclusively through `CompressedTextureUploads`; removed its dead private-method declaration and private-method test doubles, preserving the three-argument `Promise` contract and optional fourth replacement map. + Both patches were regenerated only with `pnpm --filter @threenative/core patch:generate`; a second fresh build with `--check` printed `matched`. `wc -l` before/after: base 6,221 -> 5,048; upgrade 230 -> 230; total 6,451 -> 5,278. Installed Three.js matches all 30 generated target hashes, and grep found no `updateTextureAsync` or `_uploadTail` in either installed WebGPU bundle. The replacement guard checks both generated artifacts, the decompressed upgrade payload, and installed source/bundles; it failed against the old lane before removal and passes afterward. + Focused suite passed 104/104 (previously 205/205): `renderer-texture-preparation.spec.ts` 69 -> 69, `three-compressed-upload.spec.ts` 102 -> 1, `three-patch-compat.spec.ts` 9 -> 9, `three-patch-upgrade.spec.ts` 22 -> 22, and terrain `prop-texture-preparation.spec.ts` 3 -> 3. Removed all 102 tests of the obsolete private manager/uploader lifecycle and synchronous private baseline; public upload behavior reuses the existing 69-test spec, including real transcoded mip data. The compatibility expectation now accepts an unchanged predecessor when removing the upload method makes its hash identical to the generated target. Core typecheck and build exit 0; scoped Biome exits 0 with seven existing renderer complexity warnings. + Reapplication exposed two installer limitations: the Phase 1 generated bundle hashes are not registered predecessors, and stock `Batch.js` is misclassified because its deletion-only trailing-blank hunk's new text already occurs in stock. Verified every installed Phase 1 hash, reversed that exact patch to stock, applied the new generated patch with standard `patch` at zero fuzz, and verified all target hashes; the repository apply script then reported unchanged. These installer limitations remain open. No browser, GPU, native, visual, or performance run was performed here; the coordinator owns those runs. AC-3 and Phase 2 stay unticked. + - After hunk removal (2026-10-11, `1b6b0f593`): the selected conformance row `compressed-texture-preparation` passed on web (headed, adapter `nvidia`/`turing`, 3.4 s, no page or GPU validation errors) and on the desktop Dawn host (30.2 s, `native.completed` true, no GPU validation errors). The two screenshots match: green upper half, red lower half. The other 103 rows report `blocked`. + - Strata preview WebGPU playtest after hunk removal (2026-10-11): `terrain.playtest.json`, headed, `--browser-recipe webgpu`, adapter `nvidia`/`turing`, ran to completion in 16 min 23 s with `--server-timeout 600000 --timeout 120000` (the default 15 s limits expired during the cold bake and the first Vite transform). Result: 59 of 62 assertions pass and the scenario fails. The three failures are the runtime budgets that PRD-585 already records as failing on this host: `afterFirstFrameTaskMs` up to 9 685 ms (limit 250), `timeToReadyMs` (limit 15 000) and `maxViewGpuMs` up to 62.09 ms (limit 12). The console, network and runtime-diagnostic policies pass. Captures of all five kits show textured bark, foliage, grass, rock and sand. AC-3 stays open until the preview passes as a whole. +- [x] AC-4 [local]: Each remaining hunk (depth cache, 3D guard, bind-group invalidation) has a spec that fails without it. proof: `three-patch-retained.spec.ts` on 2026-10-11 — depth cache red 1 failed/green 1 passed; 3D attachment guard red 1 failed/green 1 passed; bind-group invalidation red 1 failed/green 1 passed. Stock source hashes verified; requested suite passes 106/106. +- [x] AC-5 [local]: The `updateTexture` wrapper costs under 0.05 ms per frame on machinefall. proof: `TN_FRAME_SPANS` A/B, two runs per arm — Evidence (2026-10-11): the wrapper no longer exists, so it costs 0 ms per frame by construction and there is nothing left to A/B. `1b6b0f593` deleted it with the upload lane, because only that lane wrote `_pendingUploads` and `uploadError`. `three-compressed-upload.spec.ts` fails if `updateTextureAsync`, `_uploadTail`, `_pendingUploads` or `uploadError` reappear in the generated patches or the installed three build (106 of 106 core patch and upload specs pass at `c92f8c98a`). No `TN_FRAME_SPANS` run was made. + +## Execution Phases + +#### Phase 1: generated patches +**Status:** COMPLETE +**Files:** `packages/core/scripts/generate-three-patch.mjs` (new), `packages/core/package.json`, `.github/workflows/ci.yml` +- [x] Regeneration matches. proof: default `pnpm --filter @threenative/core patch:generate && git diff --exit-code packages/core/patches` passed after commit; a separate fresh build with `--check` printed `matched`. Both artifact sha256 unchanged (base `ac74287d…170`, upgrade `721e99cd…e36`). +- [x] CI drift check. proof: [build-artifacts](https://github.com/ThreeNativeHQ/threenative-engine/actions/runs/38505692929/job/115662759477) — the required generated-patch step passed on candidate `8f74c3314`; local hand-edit rejection exits 1 (2026-10-11). + +#### Phase 2: upload lane out of the patch +**Status:** NOT STARTED +**Files:** `packages/core/src/renderer.ts`, `packages/core/src/render/texture-upload.ts` (new), patch source tree +- [ ] Upload works without patch hunks. proof: spec + WebGPU playtest + +#### Phase 3: remaining hunks proven +**Status:** NOT STARTED +- [x] One failing-without spec per hunk. proof: `three-patch-retained.spec.ts`, 2026-10-11: stock red 3 failed/0 passed (depth, 3D guard, bind-group invalidation each 1 failed); patched green 3 passed/0 failed (each 1 passed). +- [x] Wrapper cost measured. proof: `TN_FRAME_SPANS` A/B — 2026-10-11: the wrapper was deleted in `1b6b0f593`, so the cost is 0 ms by construction; the absence guard in `three-compressed-upload.spec.ts` keeps it out. No A/B run was needed. + +## Decisions + +- **2026-10-13 — scaffold fingerprints follow the geometry-cache patch.** Overlaying only the `adaeb514f` parent Three patch into thirteen generated scratch scaffolds reproduced all thirteen previous fingerprints without changing canonical source or installed dependencies; refreshed only the thirteen measured fingerprints for the current patch. The focused complete-tree equality test passed (1 passed, 67 skipped); hashing and assertions remain intact. + +- **2026-10-10 — old compiled byte identity is disproven.** The 16 uncovered source preimages are + moot: all 31 exact legacy acceptances are preserved and verified, and every generated target is + pinned solely to the source delta plus the upstream own build. The version-2 upgrade JSON format + is intentional. +- **2026-10-10 — reader hardened at the trust boundary.** `apply-three-patch.mjs` now rejects a pin + that is not exactly three `0.185.1` / `2431a09f…d5b9`, rejects generated/final/payload/compat path + sets that do not agree with the base patch, and refuses a stock hunk whose applied text does not + hash to the patch index blob. `three-patch-compat.spec.ts` adds one red test per failure; the + focused suite is 42 tests (9 compat + 22 upgrade + 11 generator), all green. +- **2026-10-10 — exact lock hash closure.** `pnpm-lock.yaml` had 56 old base-patch sha256 literals + (`f3c6b1fc…`); they are replaced by the new base sha256 (`ac74287d…`) with no version, importer or + format change. The offline attempt lacked the `threejs-procedural-animals` codeload tarball. + The authorized online `pnpm install --frozen-lockfile` then passed in 5.1 seconds and restored + dependencies without changing the lockfile sha256 (`2a680c32…dc4`). All 42 focused tests passed + again after installation. Generated unified-diff payloads retain upstream whitespace. + +- **2026-10-11 — installer predecessor and deletion classification repaired.** The frozen Phase 1 + generated patch at `9d917a551` hashes to `ac74287d…e170`; `develop` and `2d2124792` hash to + `f3c6b1fc…227f`, so these are different artifacts. The existing legacy finals retain the develop + state; the pin's compat registry now also accepts the two Phase 1 generated bundle target hashes. + Regeneration carries those acceptances into the upgrade JSON. Exact version/commit, path-set and + target-payload hash checks are unchanged. Deletion-only hunks now prefer a matching complete old + text over their new context, which can already occur in stock `src/nodes/accessors/Batch.js`. + Two new regression specs observed red 2 failed/0 passed before the fix and green 2 passed/0 failed + afterward; the complete compat/upgrade specs pass 33/33. A frozen compressed patch fixture + reconstructs the pinned stock and old generated state, checking each old and new target hash. + End-to-end commands: `git show 9d917a551:packages/core/patches/three@0.185.1.patch` saved to + `/tmp/prd593-old.patch`; save the current patch to `/tmp/prd593-new.patch`; in the resolved installed + Three.js root run `patch --reverse --batch --fuzz=0 --silent -p1 -i /tmp/prd593-new.patch` then + `patch --batch --fuzz=0 --silent -p1 -i /tmp/prd593-old.patch`; from this checkout run + `node packages/core/scripts/apply-three-patch.mjs`. All 30 old target hashes matched before apply, + the script reported `patched`, and all 30 new target hashes matched afterward. Core typecheck + exits 0. No lockfile, dependency, browser, GPU or native changes/runs; no push. + +- **2026-10-11 — AC-4 retained hunk proofs.** `three-patch-retained.spec.ts` loads the actual + installed source for `WebGPUTextureUtils.js` and `Textures.js` into an isolated temp tree. + `TN_THREE_PATCH_STOCK=1 pnpm exec vitest run packages/core/__tests__/three-patch-retained.spec.ts` + reverses those files' complete patch sections at zero fuzz and verifies both pinned stock Git + blob hashes before loading them. Observed red: depth cache 1 failed (4 -> 1 -> 4 reallocates), + 3D guard 1 failed (usage 23 instead of 7), bind-group invalidation 1 failed (stale groups and + versions still present at recreation). Observed patched green: each 1 passed, total 3/3. + The depth test also verifies both buffers are destroyed on resize; the 3D test preserves the + mipmap attachment override; invalidation retains unrelated data and covers two bind groups. + The committed patch was not edited for negative controls. The first combined run timed out + copying the complete src tree; copying only the two observed files removed that setup cost. + Final requested five-spec suite: 106 passed/0 failed (24 upgrade, 9 compat, 3 retained, + 1 compressed-upload removal, 69 public preparation). Generator `--check` prints `matched`. + Core typecheck exits 0. Scoped Biome exits 0 with two existing installer complexity warnings + (`parseUpgradeJson` 64 and `planPatch` 20). Only AC-4 and its Phase 3 hunk-proof box are ticked; + all other pending criteria remain open. No browser, GPU, native or push run. + +2026-10-10 EPIC integration: parent reviewed the generator, bounded upgrade reader, frozen predecessor fixture and CI wiring. The lockfile delta is exactly 56 substitutions of the patch hash, with no other changes. Parent frozen install passed with the merged lock unchanged; the three focused patch specs passed 42 tests in 7.50 s, including all 31 legacy predecessors and all 30 previously shipped finals. A fresh default `pnpm --filter @threenative/core patch:generate --check` downloaded and built the pinned upstream and reported `matched`. CI structure/needs contracts passed 213 tests in 8.68 s. Actual CI, hand-edit rejection, upload migration, runtime/visual proof and wrapper cost remain open. + +Scaffold drift closure: the first parent pre-push gate passed 331 tests but rejected the stale thirteen-tree fingerprints. Restoring only the previous shared Three patch and capability manifest/reference bytes recovered every old fingerprint in the actual `createProject` test. The observed current fingerprints were then recorded, and the unchanged full-tree assertion passed for all thirteen templates (1 selected test, 67 unselected; 1.63 s). No template source or hash exclusion changed. + +2026-10-10 hand-edit rejection proof: parent copied the committed pin, source delta and patch artifacts to an ignored isolated directory, appended a hand-edited line to its base patch, and ran the real generator with `check: true`. A fresh pinned upstream download/build rejected the altered artifact with `check failed, generated patch drifts from the committed patch`. The checkout patch remained byte-identical. This closes the local negative case; AC-2 remains open for actual CI execution. + +2026-10-12 correction to the "patch still contains the upload-lane hunks" line under AC-3: the upload-lane hunks were removed in `1b6b0f593`, and the 3D guard and depth-cache hunks that remain are covered by `three-patch-retained.spec.ts`. Re-run today: `three-compressed-upload.spec.ts` (1 test) and `three-patch-retained.spec.ts` (3 tests) pass, 4 of 4. A token check of the committed patch finds none of the forbidden upload-lane tokens. AC-3 and the Phase 2 box stay unticked because their proof also names a WebGPU playtest of the preview, and the preview's web lane is currently red on the three PRD-585 perf budgets (the 59 of 62 passing assertions include no compressed-upload check that isolates this change). The uploader the PRD calls `texture-upload.ts` is `CompressedTextureUploads` at `packages/core/src/compressed-texture-preparation.ts`. + +2026-10-12 bounded-upload regression coverage: `pnpm exec vitest run packages/core/__tests__/three-compressed-upload.spec.ts` passes 3/3 after develop integration. The BC7 and non-square ASTC cases reconstruct every uploaded byte from bounded queue writes, verify the external texture replacement and backing-texture disposal, and retain the guard against the removed private Three upload lane. Scoped Biome passes. This mock-device check does not establish the outstanding full-preview or CI acceptance. + +2026-10-13 compressed completion observation: the existing prop preparation helper now reports distinct compressed originals actually replaced in material maps or TSL nodes after the renderer promise succeeds. JPEGs, identity/invalid replacements, unused map entries and failed uploads cannot increment the count; the numeric preparation return and ordinary progress callback retain their contract. GameState initializes, resets and publishes `compressedTexturesPrepared`. A direct regression fails before the callback/count change, then passes; parent direct and loading suites pass 36/36, example TypeScript and scoped Biome pass (eight complexity advisories). This is observation plumbing only. The current starter contains zero KTX2 textures, its twelve GLBs contain no KTX2/Basis images, and the current local-assets folder has no compressed textures or models. Therefore no impossible positive assertion was added to the default scenario. AC-3 and Phase 2 remain open pending a real compressed CC0 asset in an isolated preview run, without changing the immutable published starter archive or lowering existing runtime budgets. + +2026-10-13 isolated positive compressed-bark runtime attempt fails: the headed NVIDIA forest capture is blank and records17 console errors plus14 runtime diagnostic errors. The sealed UASTC archive/build and CPU quality measurements do not prove runtime acceptance. The first retained failure is WebGPU Device Lost (unknown: A valid external Instance reference no longer exists), followed by query/error-scope rejection. No preceding KTX upload/validation or network failure was captured. Preparation counter1 is observed, but no visible compressed rendering, visual acceptance or compressed native result is claimed; GPU-process exit code is unobserved. + +2026-10-13 positive compressed preview proof on the new geometry-cache candidate: two headed NVIDIA/Turing forest runs each pass10/10 assertions with zero console/network/runtime errors. Actual compressedTexturesPrepared is1 at player, meadow-close and overview in both runs; each pose is1920x1080 at scale1/MSAA4 with all four streaming queues zero. The frozen11175 game, UASTC bark and Basis bytes are unchanged; all30 installed Three final targets match the canonical cache fix. Parent fail-closed validators independently pass both runs. Fresh12-image review is NEUTRAL at every pose in both pairs: visible bark/foliage/terrain quality is retained, with no visible improvement or performance/device-loss causality claim. The earlier lost-device run remains failed. This supplies actual positive preview texture proof, but AC-3 remains open until the full preview passes its unchanged budgets; no new native compressed result is claimed. + +2026-10-14 CI qualification: the generated-patch step is now observed successful in the required build-artifacts job. The original local hand-edit rejection remains the negative control; no deliberately broken patch was pushed. This closes Phase 1 only. Strata runtime qualification fails in this run, so upload-lane and runtime acceptance stay open. diff --git a/docs/PRDs/strata-terrain-hardening/PRD-594-engine-changes-from-381-get-native-and-visual-proof.md b/docs/PRDs/strata-terrain-hardening/PRD-594-engine-changes-from-381-get-native-and-visual-proof.md new file mode 100644 index 0000000000..d6649e5fd5 --- /dev/null +++ b/docs/PRDs/strata-terrain-hardening/PRD-594-engine-changes-from-381-get-native-and-visual-proof.md @@ -0,0 +1,92 @@ +# PRD-594 — The engine changes that rode in with #381 get native and visual proof + +**Status:** PARTIAL +**Priority:** P1 — Six core behaviours from #381 that every game runs have no native conformance case and no visual-judge verdict (AC-1 to AC-6); "web-only is unfinished" applies. +**Complexity:** 5 (MEDIUM); risk override: supported-platform parity +**Owner:** ThreeNative maintainers +**Depends on:** None +**Epic:** [Strata terrain hardening](README.md) + +## Context + +Core audit of `2d2124792` (2026-10-10). The squash of 485 files carried engine changes beyond the +terrain package, without their own PRDs: + +- `packages/runtime-native/conformance/registry.json` has no case for WorldCells streaming, the + HLOD/impostor/shadow proxies, `prepareTextures`, virtual-shadow `renderGroup` uniforms and + `registeredOnCreate`, velocity buffer rotation or the streaming slice budget. The only native + proofs are the example's desktop scripts (not in CI), the `loading-screen-desktop` assertion and + `bindings.cpp`'s `device.lost` test. +- `render/virtual-shadow.ts` (+67/−54), `render/velocity.ts` (buffer rotation; a perf win), + `atmosphere/luts.ts` (`textureLoad` → `texture().level(0)`) change pixels. No visual-judge + verdict is recorded for them (owner rule 2026-10-09: any change to what reaches the screen). +- `game.ts` removes the `temporalUpscale` floor lift for render-chain velocity consumers, for + every game with temporal reconstruction. No spec proves the floor still holds for a TRAA chain. +- `streaming.ts` changes the public default of `addInSlices` from 256 objects to an 8 ms budget, + with no capability-detail note. +- `FrameBudget` now registers a `longtask` `PerformanceObserver` in its constructor; it is + released only from `game.ts` teardown. + +## Solution + +Add three native conformance cases and run them on the desktop host. Capture before/after at +fixed poses for the shadow, velocity and LUT changes and run the owner's visual-judge protocol. +Add the missing specs and the capability-detail note. + +## Acceptance Criteria + +- [x] AC-1 [local]: A native conformance case streams two WorldCells cells and swaps a proxy with zero validation errors. proof: `pnpm parity` case in `registry.json` — Evidence: 2026-10-11 `run-conformance.mjs --only-tests world-cells-proxy-stream` pass on web (5.1 s) and desktop (16.2 s, nvidia, strata-terrain-proof host) at b5aa0d786; the original 1 MB proxy budget could never admit the 8 MiB HLOD reserve. +- [x] AC-2 [local]: A native conformance case uploads a compressed texture through `prepareTextures`. proof: `pnpm parity` — Evidence: 2026-10-11 `compressed-texture-preparation` pass on web (3.0 s) and desktop (13.3 s). +- [x] AC-3 [local]: A native conformance case submits a virtual-shadow first frame with no "Destroyed texture" error. proof: `pnpm parity` — Evidence: 2026-10-11 web pass (3.1 s); 2026-10-13 fresh standard V8 desktop pass (13.0 s), native exit 0, nonuniform 1280×720 capture, no GPU validation errors; current fixture tolerance and exact metrics are recorded below. +- [ ] AC-4 [local]: A fresh visual judge rates the shadow, velocity and LUT changes IMPROVEMENT against the pre-#381 parent at fixed poses (headed WebGPU, two runs per arm). proof: judge verdict and images on the PR — Evidence: 2026-10-13, two headed NVIDIA runs per arm/effect; all12 captures pass, fresh read-only judge NEUTRAL for all six pairs. Twelve labelled images published on PR483 at screenshot commit3e3f716710cee4d53857bd9bf2d1c756d277fdc9; all URLs HTTP200/SHA256 verified. Controlled exact changed-file overlays against parent1a5314a1dd184dbc6f76426f546d326007cf3545 under common runtime, not an untouched full-parent build. +- [x] AC-5 [local]: A TRAA chain holds its `temporalUpscale` floor without the removed lift. proof: `pnpm exec vitest run packages/core/__tests__/renderer.spec.ts` case — Evidence: 2026-10-11 `renderer.spec.ts` 43/43 passed; real game scaler with active TRAA velocity chain holds desktop 0.61 floor through 30 GPU-overload windows; removed velocity lift remains absent. No GPU/browser claim. +- [x] AC-6 [local]: `addInSlices`' capability detail states the 8 ms default, and `FrameBudget` disconnects its observer on `dispose()`. proof: `pnpm build` manifest diff + `frame-budget.spec.ts` case — Evidence: 2026-10-11 `frame-budget.spec.ts` 28/28 passed; observer disconnects once over two dispose calls; capability-detail assertion confirms 8 ms default. `pnpm capabilities:sync` regenerates 448 entries and both manifests/reference; `pnpm api:surface:sync` passes (422 symbols), no API drift. Observer behavior was already true. + +## Blocked on + +- Android parity for AC-1 to AC-3 needs an emulator run with a hardware GPU mode; start the emulator before declaring this blocked (memory: Android emulator lanes are runnable here). + +## Execution Phases + +#### Phase 1: native cases +**Status:** PARTIAL +**Files:** `packages/runtime-native/conformance/registry.json`, conformance fixtures +- [x] WorldCells stream. proof: `pnpm parity` — 2026-10-11 pass web and desktop. +- [x] Texture upload. proof: `pnpm parity` — 2026-10-11 pass web and desktop. +- [x] Shadow first frame. proof: `pnpm parity` — 2026-10-11 pass web and desktop. + +#### Phase 2: pixels judged +**Status:** PARTIAL +- [ ] Judge verdict IMPROVEMENT, as required by the current owner visual-judge rule. proof: PR images + verdict — 2026-10-13, all six pairs NEUTRAL;12 labelled captures on PR483, exact scope described in AC-4. + +#### Phase 3: specs and notes +**Status:** COMPLETE +**Files:** `packages/core/__tests__/renderer.spec.ts`, `frame-budget.spec.ts`, `packages/core/src/streaming.ts` doc comment +- [x] TRAA floor spec. proof: `renderer.spec.ts` — 2026-10-11, 43/43 renderer tests; floor 0.61, 30 overloaded windows. +- [x] Slice default documented, observer released. proof: manifest + `frame-budget.spec.ts` — 2026-10-11, 28/28; two dispose calls, one disconnect; 8 ms detail in regenerated manifest. + +## Implementation notes (2026-10-11) + +- AC-1 timeout fix (2026-10-11): scene-layer root cause was the 1,000,000-byte world budget; `CellChunks.#beginCellProxy` reserves 8 MiB for decoded source plus fixed proxy copies before IO and refused HLOD admission. Raised only the fixture budget to 10,000,000 bytes; camera z=308, ring=0, 180-frame timeout and all proof assertions remain unchanged. Node simulation runs the actual scene with a stub renderer: before the change it failed with `proxy stream timed out`; afterward both cells activate proxies and restore near detail. With proxy submissions withheld it still times out (compiled=1, observed=0, active=0). Runtime-native focused Vitest: 3/3 passed; core `world-cells-data`, `world-cell-proxy`, `world-cells-hlod`: 66/66 passed; scoped Biome passed. No engine or export changes. Web/desktop GPU execution remains pending coordinator; no boxes ticked. +- AC-1: `world-cells-proxy-stream` registers a shared scene that streams cells `0:0` and `2:0`, requires compiled and backend-observed HLOD swaps, restores near detail, and requires a null GPU validation-scope result. Local scene-module and registry contracts: 21/21 passed; registry-related script specs: 21/21 passed; focused Biome and `git diff --check` passed. Web/desktop execution pending coordinator; boxes remain open. +- AC-2: reuse PRD-593's existing `compressed-texture-preparation` registry row and shared scene: public `prepareTextures` uploads one BC1 texture with three mips, returns an `ExternalTexture` replacement used by the rendered material, and preserves original identity, versions, mip references and data. Scene module import passed; `renderer-texture-preparation.spec.ts` + `three-compressed-upload.spec.ts`: 70/70 passed. No duplicate scene; web/desktop execution pending coordinator, boxes remain open. +- AC-3: `virtual-shadow-first-frame` reuses the existing virtual-shadow scene builder. It captures GPU validation from resource creation through the first render and completed queue work, requires shadow stats frame 1 with submitted levels, and rejects absent validation observations or any error (including Destroyed texture). Final registry-related script specs: 21/21 passed; scene-module/registry contracts: 22/22 passed; focused registry-schema specs: 3/3 passed (73 unrelated runner tests skipped); focused Biome and `git diff --check` passed. Web/desktop execution pending coordinator; boxes remain open. +- Local scope: `pnpm install --frozen-lockfile` and the sequential `@threenative/playtest` JS build passed; lockfile unchanged. The broad runner spec was stopped during full-registry dry bundling and replaced by focused schema checks. No browser/native conformance execution or push performed. + +2026-10-13 current-host temporal check: the canonical Dawn + V8 v0.3.4 host (SHA256 `59b3ec63dad29db0c1b0938e241064a7846fc63b9171e2d927874e6301f899f9`) completes the selected `temporal-aa` scene with native exit0, a nonuniform 1280x720 frame and no GPU validation errors. The comparison still fails: pixel mismatch0.0817643 exceeds0.06, while perceptual deltaE3.26319 is within9 (`strata-velocity-upload-desktop-current/report.json`, wrapper exit1). This replaces the old cached-host queue-submit panic with a completed comparison, but does not establish native visual acceptance. No tolerance changed; baseline attribution and paired visual proof remain open. + +2026-10-13 history-guard attribution: an ignored copy of the exact current temporal bundle with only the scheduled-history skip guard removed completes300 native frames and produces a pixel-identical image to the enabled bundle (mismatch0, deltaE0). Its comparison against the old browser reference fails with exactly the same0.0817643/3.26319 metrics. Source inspection identifies different moving-scene capture phases: browser waits26 rAF callbacks while native captures after300 host frames. The shared entry now prepares26 real moving render/history cycles, freezes the existing resolve and asserts both fixture and held frame26; it preserves native frame count and all tolerances. Parent six focused temporal-velocity tests pass; actual aligned browser/native GPU proof remains pending. This does not yet close the matrix/visual box. + +2026-10-13 aligned actual GPU result: the shared temporal entry passes on headed NVIDIA WebGPU and the current Dawn native host, with pixel mismatch0, perceptual deltaE0 and no GPU validation errors (`strata-velocity-held-web/report.json`, `strata-velocity-held-desktop/report.json`). Native exit0 covers300 host frames; the fixture exercises26 moving render/history frames and then holds that resolve, rather than claiming300 moving temporal frames. Wrapper exit2 represents105 unselected blocked rows. Parent focused six tests, syntax, scoped Biome and fresh read-only review pass. Full platform matrix/whole-game visual proof remains open. + +2026-10-13 pre381 visual acceptance: virtual shadow and temporal AA each pass four headed NVIDIA1280x720 captures (two per arm); both effects share35 byte-identical common inputs around the exact three changed core-file overlays. Temporal fixture advances26 moving frames before holding the capture pose; still images do not prove motion stability. Atmosphere uses the shipped factory/forest parameters and exact old/current AtmosphereLuts under13 identical common inputs. All four atmosphere captures observe three compute submissions, two completed queue fences, null GPU validation, identical camera/sun/physical/tone/exposure and pixelRatio1 with antialiasing disabled. The initial diagnostic fixture failed before browser on empty registry exclusions; that failure is retained, and the corrected fixture uses all canonical exclusions with the existing full validator. Fresh read-only judgement is NEUTRAL per pair and overall, preserving shadow shading, temporal edges and sky/sun appearance. All12 labelled images are published on [PR483](https://github.com/ThreeNativeHQ/threenative-engine/pull/483), screenshot commit3e3f716710cee4d53857bd9bf2d1c756d277fdc9. No visual improvement, full-parent build, motion-stability or full-platform claim is added. Android follow-up remains open; PRD status stays PARTIAL. + +2026-10-13 integrated-host repeat: the freshly built standard V8 host SHA256 `de2d9cf932ed8422b3241756d8361562bf63a53070502aafc3eecebfb83e4242` passes selected desktop `temporal-aa` against the existing aligned browser reference (`artifacts/pr483-pre381-visual/after/run-1`, reference-set SHA256 `12559eb4f6b75b8a64df33736b01e815a6555bc712f944ff2aa459fa71b80264`). Native exit 0, nonuniform 1280×720 capture, pixel mismatch 0, deltaE 0, no GPU validation errors. Report: `packages/runtime-native/artifacts/pr483-temporal-current-host/report.json`; wrapper exit 2 represents 106 unselected rows. This repeats the held-frame proof after develop integration; motion stability, Android and the complete matrix remain open. + +2026-10-13 harness/registry tolerance repair (no GPU, no rerun). `virtual-shadow-first-frame` (`conformance/scenes/shared/virtual-shadow-first-frame.js:4`) imports and renders the exact `buildVirtualShadowScene` fixture that `33-virtual-shadow` registered (`conformance/scenes/shared/virtual-shadow.js:21`), so it inherits that row's visual tolerance rather than the strict `.01` default it was registered with. Exact source/capture evidence, BASE develop `5f7143895`, artifact `11860557091`: the base `33-virtual-shadow` capture and the candidate first-frame capture have IDENTICAL metric `0.012477213541666667` and deltaE `0.0056963619722485` (sizes desktop `61538` / web `20307`), and the base `33-virtual-shadow` row passes at `pixelMismatchRatio` `0.08`; BASE has no first-frame case. Changed only the `virtual-shadow-first-frame` registry tolerance: `pixelMismatchRatio` `.01` → `.08`, `perceptualDeltaE` `3.0` unchanged, matching the `33-virtual-shadow` row. New red-first contract `tests/conformance-runner.test.mjs` — "a diagnostic row reusing a scene fixture keeps that scene's visual tolerance" — asserts the two rows share tolerance (RED at `.01`, GREEN at `.08`). The first-frame GPU-submission/completed-work/zero-error assertions stay strict: `tests/virtual-shadow-first-frame.test.mjs` and the scene comparator are unchanged. This is the harness/registry configuration layer, not engine rendering; no browser, GPU, native or full-CI execution was run. Parent review: both focused native test files pass, 78/78 tests; scoped Biome passes with one existing noDelete warning. The direct Node test command first failed on TypeScript module resolution; the repository Vitest configuration supplies the correct loader. + +Archived-capture verification: the existing `compareDirectories` comparator, using only the current first-frame registry row and the failed CI web/desktop PNGs, returns one pass, zero failures and zero blocked rows at the unchanged observed metrics. This verifies the corrected image policy against those captures; it is not a new GPU run or native-matrix result. + +2026-10-13 repaired first-frame runtime proof: `sh scripts/xvfb.sh node packages/runtime-native/conformance/run-conformance.mjs --target desktop --only-tests virtual-shadow-first-frame --reference /tmp/pr483-parity-failure/conformance/web --out artifacts/pr483-shadow-first-frame-repaired` executes the real case in 13.0 s on clean commit `bd4d1ba5a`, standard V8 host SHA256 `de2d9cf932ed8422b3241756d8361562bf63a53070502aafc3eecebfb83e4242`. Native exit 0, nonuniform 1280×720 image, no GPU validation errors, pixel mismatch `0.03707573784722222` ≤ `0.08`, deltaE `0.016053466032325495` ≤ `3`. Reference-set SHA256 `bb374b87ec7033897e97d4291d47e95721707b601406d9854164df8374b49496` identifies the archived CI browser reference. Report: `packages/runtime-native/artifacts/pr483-shadow-first-frame-repaired/report.json`; wrapper exit 2 denotes 106 deliberately unselected rows, with the selected row passing. No matrix, Android or performance claim. + +Acceptance audit: the current owner rule in root `AGENTS.md` rejects NEUTRAL or REGRESSION visual verdicts. The earlier NEUTRAL preservation evidence remains recorded but cannot close AC-4 or Phase 2 under that rule; both boxes are reopened. Android functional proof is now available from CI run 38505692929, artifact 11868742891, candidate `8f74c3314bd7a8fbbd399e33307bf4498f88b0b8`: `world-cells-proxy-stream`, `compressed-texture-preparation` and `virtual-shadow-first-frame` all pass with nonuniform 1280×720 captures, zero mismatch/deltaE and no GPU validation errors. Job 115664404928 completed successfully; each native row verifies a fresh installed APK and bundle with hashed arm64/x86_64 runtime libraries. This lane uses `-gpu swiftshader_indirect`; hardware Android performance remains unproven. No physical-device claim. diff --git a/docs/PRDs/strata-terrain-hardening/PRD-595-world-cells-splits-by-seam-and-survives-a-race-fuzz.md b/docs/PRDs/strata-terrain-hardening/PRD-595-world-cells-splits-by-seam-and-survives-a-race-fuzz.md new file mode 100644 index 0000000000..2e8781dc42 --- /dev/null +++ b/docs/PRDs/strata-terrain-hardening/PRD-595-world-cells-splits-by-seam-and-survives-a-race-fuzz.md @@ -0,0 +1,95 @@ +# PRD-595 — `world-cells.ts` splits by seam and survives a load/evict race fuzz + +**Status:** PARTIAL +**Priority:** P2 — `world-cells.ts` is 10 228 lines and its async load/evict orderings are proven for one interleaving only (AC-1 to AC-3). +**Complexity:** 5 (MEDIUM); risk override: complex async state +**Owner:** ThreeNative maintainers +**Depends on:** None (coordinate with [PRD-473](../done/PRD-473-open-worlds-hold-120-fps-gpu-driven.md), which edits the same file) +**Epic:** [Strata terrain hardening](README.md) + +## Context + +Core audit of `2d2124792` (2026-10-10): + +- `packages/core/src/world-cells.ts` is 10 228 lines (about 9 100 before #381). `WorldCells` + runs from line 4101 to the end with 183 private methods. The file also holds `SharedBatch` + (1098), `BundleSurfaceSafety` (3163), `ModelLoadLimiter` (3970), `AdmissionBudget` (4038) and the + shadow-proxy merge helpers (2687–3100). +- HLOD load (`world-cells.ts:9454-9490`) reserves bytes before the load and rechecks + `#cellLive`/`cancelled` after. The asset path (`8043-8068`) hands a model that finished after + `wanted()` turned false to `#adoptAsset` without a check at the call site. + `world-cells-hlod.spec.ts:394-410` covers one ordering. +- Misplaced JSDoc: two blocks back to back before `#disposeLoaded` (about `10059-10069`); in + `frame-budget.ts`, `dispose()` sits between `beginFrame`'s JSDoc and `beginFrame`. + +## Solution + +Write the race fuzz first, against today's file, so that the split is proven behaviour-preserving. +Then extract by seam with no logic change: `world-cell-batches.ts`, `world-load-limiter.ts`, +`world-bundle-safety.ts`, `world-chunk-shadow.ts`. Fix any race the fuzz finds in its own commit, +red then green. + +## Acceptance Criteria + +- [x] AC-1 [local]: A seeded fuzz of 1 000 interleavings of load completion, eviction, `dispose()` and re-entry ends with `#bytes`, `#hlodBytes` and refcounts at 0, and each geometry disposed exactly once. proof: `pnpm exec vitest run packages/core/__tests__/world-cells-race.spec.ts` — Evidence: 2026-10-11 independent focused gate passes all 1 000 seeds in 2.31 s; every seed observes positive public residentBytes and then zero residentBytes, HLOD bytes and refcounts; 1 651 actual loaded geometries each dispose exactly once. +- [x] AC-2 [local]: After the split, no file under `packages/core/src/world-*` exceeds 3 000 lines, and all existing world specs pass unchanged. proof: `pnpm exec vitest run packages/core/__tests__/world-*.spec.ts` plus `pnpm quality` — Evidence (2026-10-11, EPIC `ebffa23e5`): the largest `world-*` files are `world-tiles.ts` 2 880, `world-gpu-scene.ts` 2 774, `world-cell-render.ts` 2 463 and `world-cells.ts` 2 444 lines (8 605 before Phase 2's collaborator work). No spec file changed. The world suite after a frozen install passed 40 of 41 files on the 4-core offload host. The 41st, `world-gpu-scene.spec.ts`, timed out one case at its 60 s limit there and on the loaded desktop (load 57), and it passed in full on the Debian laptop at the same commit. The same case timed out at the pre-split baseline under load. `pnpm quality` exits 0. +- [ ] AC-3 [local]: The strata preview and machinefall walk playtests still pass after the split. proof: `pnpm --filter strata-terrain-preview test:terrain:web` and the machinefall map-walk scenario — Evidence: pending. + - Machinefall walk (2026-10-11, core from EPIC `ebffa23e5`, adapter nvidia): the candidate's second run had 0 failures, passed all four checkpoints (camp, first stream, highway, bridge) and logged no console errors. It failed only `performance.minFps` (20.12, needs >= 25) and `maxFrameMsP95` (281.3 ms, needs <= 250). The develop control site failed the same bounds in both of its runs (19.26 and 23.92 fps, p95 223.1 and 404.1 ms). Host load was 48 to 56. The first candidate run had 4 failures from a one-off `ERR_NETWORK_CHANGED`. Functional walk: pass. Performance bounds: not proven on a loaded host. AC-3 stays open. + - Strata preview: the full `terrain.playtest.json` run on 2026-10-11 (PRD-593 lane, before this split) passed 59 of 62 assertions and failed the three PRD-585 runtime budgets. It has not been rerun with the split. + +## Execution Phases + +#### Phase 1: fuzz first +**Status:** DONE +**Files:** `packages/core/__tests__/world-cells-race.spec.ts` (new) +- [x] Fuzz green on today's code, or a found race fixed red-green. proof: `world-cells-race.spec.ts` — 2026-10-11 all 1 000 seeds pass with actual charged-byte telemetry and nonempty lifecycle observations; no lifecycle race found. + +#### Phase 2: extract by seam +**Status:** PARTIAL +**Files:** `packages/core/src/world-cells.ts` → four new modules +- [x] Files under 3 000 lines, specs unchanged. proof: world specs + `pnpm quality` — 2026-10-11: see AC-2; collaborators CellData, CellPrewarm, CellChunks, CellImpostors, CellModels and CellRender each own their state behind a named host interface of at most 20 members. +- [ ] Walk playtests pass. proof: preview + machinefall scenarios + +2026-10-11 Phase 1 resident-byte observation completed: added readonly `residentBytes` to existing `IWorldCellsStats`, returning actual `#bytes`; its documentation distinguishes placement residency charges from separately reported far-impostor/HLOD charges. The required red run failed all 1,000 seeds before the field existed because positive resident bytes could not be observed; after the minimal field addition every seed observes positive bytes and asserts zero bytes after draining. Independent focused gate passes in 2.31 s: 1,651 loads and actual geometries disposed exactly once, 417 pending disposals, 476 late completions, 1,428 evictions, 31 reentries, 357 seeds with positive HLOD bytes and all 1,000 with positive refcounts. Nearest unchanged `world-cells.spec.ts` and `world-cells-admission.spec.ts` pass 58 tests (14.01 s). Core typecheck passes after building this lane’s missing playtest dependency; no shared mutable dependency/build links were introduced. Scoped Biome passes with 22 existing/new nonfatal complexity warnings (one in the new scenario). The bounded Haiku arm timed out after saving its changes/logs; coordinator reviewed the diff and independently reran core typecheck and the fuzz gate. Parent review independently passes all 1,000 seeds (2.02 s), observing 1,650 loaded geometries disposed exactly once, 417 pending disposals, 476 late completions, 1,428 evictions, 31 reentries and 356 positive-HLOD seeds. Counts can vary with admission timing; each run retains the same seeded decisions and asserts every teardown invariant. No extraction, full-world suite or preview/machinefall proof is claimed; Phase 2 and AC-2/3 remain open. + +2026-10-11 Phase 2 first mechanical seam (PARTIAL): moved the queued model-load interface, `ModelLoadLimiter` and `AdmissionBudget` with their intact bodies/JSDoc to internal `world-load-limiter.ts`; dependencies are only type imports `Object3D` and existing `IAdmissionBudget` from `world-tiles.ts`. No public index export, behavior change, new dependency or test-file modification. Automated comparison confirms the moved block matches the committed original except class export keywords. Existing WorldCells/admission specs pass 58 tests (9.86 s); the unchanged 1,000-seed fuzz passes (2.03 s); core typecheck and scoped Biome pass (20 nonfatal warnings). The Haiku arm timed out at 600 seconds after saving the verified changes/logs; coordinator reviewed the diff and exact-body comparison. The module is 106 lines and `world-cells.ts` remains 10,230 lines, so the 3,000-line criterion is explicitly unfulfilled; other named seams, larger coherent class subsystems, the full unchanged world suite and preview/machinefall playtests remain open. + +2026-10-11 Phase 2 SharedBatch seam (PARTIAL): extracted `SharedBatch`, its owned caster/square types and packing scratch/helpers, and `PARKED_SHARED_CAPACITY` into internal `world-cell-batches.ts` (1,057 lines). Exact comparison confirms the original SharedBatch class body is unchanged apart from its export keyword. Internal exports are only `SharedBatch`, `sameSquares` and `ICasterScaleMesh`; dependencies are existing Three types/values, `InstancedBatch` (type-only), and mesh-pool functions, with no new context, public package API or runtime cycle. The architecture guard now names exactly the limiter and batch modules alongside world-cells and applies the identical prohibited constructor, appearance-property and hexadecimal-color regexes independently to all three; the global rule is unchanged. The prior genuine constraints baseline was red on `inFlight`; updated constraints pass all 4 tests. Unchanged WorldCells/admission specs pass 58 tests (18.52 s), the unchanged 1,000-seed race fuzz passes (5.11 s), core typecheck passes and scoped Biome passes with 23 nonfatal warnings. Existing world-* test files are unchanged. The bounded arm timed out after saving passing gate logs; coordinator reviewed the extraction and retained guard coverage. `world-cells.ts` remains 9,179 lines; Phase 2 and AC-2/3 remain open for further seams, coherent class subsystem extraction, full unchanged world tests and the preview/machinefall playtests. + +2026-10-11 Phase 2 bundle-safety seam (PARTIAL): moved 14 existing declarations (`PER_RENDER_UPDATE`, the graph/check interfaces, `BundleSurfaceSafety`, graph traversal/validation helpers and bundle eligibility functions) into internal `world-bundle-safety.ts` (295 lines), using only Three type imports and existing `isEngineRenderHook`; no runtime cycle, context abstraction or public package API was added. Independent TypeScript AST comparison confirms all 14 original bodies are unchanged except export keywords. The generic architecture rule actually matches the moved material-identity code, so the exact new filename is exempted and all three existing tighter appearance regexes apply unchanged to it alongside the other extracted modules. Existing world-spec files remain unchanged. The arm timed out after saving the extraction; its initial test/Biome commands had malformed arguments and did not prove those gates. Coordinator reran the corrected focused gate under the shared CPU lock/external 120-second limit: constraints, world-bundles, world-cells, world-cells-admission and the unchanged 1,000-seed race fuzz all pass (104 tests, 54.30 s). Core typecheck passes; corrected scoped Biome passes with 20 nonfatal warnings. `world-cells.ts` remains 8,889 lines, so Phase 2/AC-2/3 remain open for the remaining seams, cohesive class subsystems, full world suite and preview/machinefall playtests. + +2026-10-11 Phase 2 chunk-shadow seam (PARTIAL): moved the chunk-shadow helpers, `worldOwned` and three constants into internal `world-chunk-shadow.ts` (305 lines). Independent TypeScript AST comparison confirms all eight moved declaration bodies are unchanged except export keywords; headers/JSDoc are retained. `world-cells.ts` is 8,605 lines (8,889 before). The `bundleSafeMaterial` re-export is unchanged. Gates: core typecheck exit 0; scoped Biome exit 0 with 27 nonfatal warnings; `constraints.spec.ts` 4/4 pass; `world-cells-chunk-merge.spec.ts` 15/15 pass; `world-cells.spec.ts`, `world-cells-admission.spec.ts` and `world-cells-race.spec.ts` 59/59 pass, including the 1,000-seed race. The coordinator additionally ran unchanged shadow caster-cost and shadow-prewarm specs: 4/4 pass (2.56 s). The bounded Haiku arm timed out after saving its passing gate logs; the coordinator reviewed the diff and exact-body comparison. Existing world test files are unchanged. Other `world-*` files over 3,000 lines: `world-tiles.ts` 3,405 and `world-gpu-scene.ts` 3,243. Phase 2, AC-2 and AC-3 remain OPEN: four moves are still insufficient for 3,000 lines, and larger cohesive class subsystems, the full world suite and the preview and machinefall playtests are pending. + +2026-10-11 Phase 2 declaration seams (PARTIAL): extracted world-cell-data.ts (882 lines; 26 declarations including the verbatim existing IWorldCellsStats move, replacing untracked world-types.ts), world-tile-observation.ts (566 lines; 32 declarations including bridge topology validation and its constants), and world-gpu-validation.ts (517 lines; 30 declarations including shared matrix/indirect widths and reporting constants). TypeScript-AST token comparison, ignoring added export keywords only, passes all 88 moved declarations; all comments/JSDoc and retained declaration tokens are unchanged. Four tile helper functions use named exports to preserve their original parameter tokens under Biome. Dependencies on owner modules are type-only; no runtime owner import cycle was introduced. Original public re-exports are retained; index.ts, world.ts and every core spec remain unchanged. Remaining owner line counts: world-cells.ts 7,778, world-tiles.ts 2,880, world-gpu-scene.ts 2,774. Full unchanged world suite baseline: 40 files pass / 1 fails; 627 tests pass / 1 fails / 3 skip (84.57 s). After extraction: identical file/test counts (109.87 s); both failures are the same pre-existing 60-second timeout at world-gpu-scene.spec.ts:1156, so test retries/fixes stopped. Final core typecheck passes (exit 0); scoped Biome check --write passes (exit 0, 29 warnings); wc -l confirms both tiles and GPU scene are below 3,000 lines. No PRD boxes ticked. Phase 2 and AC-2/3 remain open: world-cells.ts is still over 3,000 lines, the baseline timeout is unresolved, and preview/machinefall playtests were not run under this declaration-only brief. No push. + +2026-10-11 Phase 2 CellPrewarm collaborator (PARTIAL): extracted 331 original lines across 24 private fields/methods, plus the four constants and their JSDoc, into internal world-cell-prewarm.ts (441 lines); WorldCells delegates through one instance and is now 7,438 lines. CellPrewarm owns its queue/key set, pending/mint/owed/drawn/epoch/wait and caster counters, awaited mesh hooks, settlement flag/resolver and promise. Its explicit 20-member ICellPrewarmHost is released, parent, invalidateShadows, backlog, keysForShadow, hasShared, hasRetired, runMax, residentCells, extentBounds, smallCaster, dressMesh, receiveShadow, adoptGpu, setShared, attach, casts, wideOwed, followCluster and wideShape; no owner instance or writable maps cross that interface. Reverse-boundary comparison of all 24 moved members, including comments and ignoring formatter whitespace, matches the original bodies. Hook restoration and queue settlement remain at their original disposal positions; mesh-visible casterPrewarmOwed is unchanged. Existing world specs and public entry-point exports are unchanged. Full unchanged world suite: 40 files pass / 1 fails; 627 tests pass / 1 fails / 3 skip (90.26 s), including the 1,000-seed race fuzz; the only failure is the documented pre-existing 60-second timeout at world-gpu-scene.spec.ts:1156. The first core typecheck caught the host smallCaster return declaration (number instead of boolean); after correcting that declaration, core typecheck passes (exit 0). Scoped Biome check --write passes (exit 0, 21 warnings); wc -l confirms world-cell-prewarm.ts 441, world-cells.ts 7,438, world-tiles.ts 2,880 and world-gpu-scene.ts 2,774. No boxes ticked; AC-2/3 remain open. No push. + +2026-10-11 Phase 2 CellChunks collaborator (PARTIAL): extracted 900 original lines across 37 members/declarations, plus the verbatim proxy eviction block, cellLoaded body and HLOD stats block, into internal world-cell-chunks.ts (1,050 lines); world-cells.ts is now 6,581 lines (7,438 before). CellChunks owns proxy states/materials, HLOD bytes/refusals/failures and cached renderer/camera as private fields; proxy cancellation delegates at its original eviction position. Nine explicitly typed immutable settings are copied once after their existing validation. Its 20-member ICellChunksHost is disposeProxyModel, disposeLoaded, releaseModelPaths, retainModelPaths, addFailures, sourceBytes, load, cellLive, add, intersectsObject, gpuSceneOn, asset, invalidateBundles, worldSphere, reportCellFailure, parent, prepareCompile, compileChunk, addChunk and publishChunkBounds. Source-model leases, bundle invalidation and ordered bounds publication use named operations; no owner instance or writable maps cross the boundary. The original async/then and liveness positions are retained. Reverse-boundary TypeScript token comparison passes all 37 moved members/declarations, the cellLoaded body and seven-line eviction block, normalizing trailing commas and formatter whitespace while preserving identifiers/operators/literals/control flow. Existing world specs, mesh-visible caster internals and public entry-point exports are unchanged. Initial wiring/import errors were corrected; an early suite started before those fixes was stopped. The first complete default-worker suite had 626 passing, two failing and three skipped tests: the documented GPU-scene 60-second timeout at world-gpu-scene.spec.ts:1156 and a 20-second timeout in the unchanged world-impostor-surface.spec.ts:254 TypeScript check under load 66.9. The full unchanged world suite rerun with --maxWorkers=1 passes 40 files / 627 tests, with three skipped and only the same documented GPU-scene timeout (312.70 s); the surface file passes all 13 tests in 10.70 s, chunk-merge passes all 15 and the unchanged 1,000-seed race fuzz passes in 3.74 s. Final core typecheck passes (exit 0); scoped Biome check --write passes (exit 0, 20 nonfatal warnings); wc -l confirms world-cell-chunks.ts 1,050, world-cells.ts 6,581, world-tiles.ts 2,880 and world-gpu-scene.ts 2,774. No boxes ticked. CellImpostors, CellModels, CellStreaming and CellRender remain unextracted; AC-2/3 remain open. No push. + +2026-10-11 Phase 2 CellImpostors collaborator (PARTIAL): moved 1,208 original lines across 40 method bodies, 22 private fields and 18 declarations into internal world-cell-impostors.ts (1,364 lines); world-cells.ts is now 5,434 lines (6,581 before). CellImpostors owns the baker, atlas cache/reservations, bake queue, far cohorts/segments/retries/cursor/temporary leases and wide retry set. Its 20-member ICellImpostorsHost is released, renderer, asset, hasAsset, assets, nearWorkPending, newAsset, startAssetLoad, release, noteGate, retargetWideCasters, terminalLevelReady, residentCell, canonical, shadowRecordsMoved, residency, add, remove, addFailure and markReleasedPart. Read-only settings and scratch transforms stay private; render-owned wide-caster handoff operations and ordered terminal-ready publication use explicit callbacks. No writable maps or owner instance cross the boundary, and the owner-module import is type-only. Reverse-boundary TypeScript token comparison, normalizing formatter whitespace/trailing commas, passes all 40 moved bodies, 22 fields, 18 declarations and 123 retained facade methods. Existing specs and public entry-point exports are unchanged. The first full world run on the overloaded desktop had 624 passed, four failed and three skipped (718.75 s): GPU-scene timeouts at 1156 and 1243, a later source-record mismatch, and the surface spec's explicit 20-second TypeScript timeout. The unchanged full rerun on the laptop checkout mirror with --maxWorkers=1 passes all 41 files / 628 tests, three skipped (190.46 s), including all 97 GPU-scene tests, all 13 surface tests and the 1,000-seed race fuzz. Final core typecheck passes (exit 0); scoped Biome check --write passes (exit 0, 15 nonfatal warnings); wc -l confirms all world-* modules except world-cells.ts remain below 3,000 lines. The extra constraints.spec.ts guard remains red (one failed / three passed): its generic filename allowlist already omits HEAD's world-cell-data/prewarm/chunks modules, and the new seam also needs coverage there; this brief prohibits editing specs. No boxes ticked; CellModels, CellStreaming and CellRender and AC-2/3 remain open. No push. + +2026-10-11 Phase 2 CellModels collaborator (PARTIAL): moved 884 original lines across 17 method bodies, nine private fields and 19 declarations/state initializers into internal world-cell-models.ts (1,029 lines); world-cells.ts is now 4,561 lines (5,434 before). CellModels owns loaded assets, loader ownership, the load limiter, model-path refcounts, shared surfaces and chain-LOD bookkeeping. The original module-global released-parts WeakSet is now private static state, retaining its cross-world identity/disposal semantics. Alias resolution remains a pure operation over the facade's immutable alias metadata; no writable map crosses the boundary. Its 16-member ICellModelsHost is canonical, noteGate, queueBuild, released, failures, setFailures, reportCellFailure, drainShared, residentCells, queuePrewarm, hasFarTemp, releaseFarTemp, maybeQueueImpostor, hasQueuedImpostor, cancelImpostor and releaseImpostor. Separate failure read/write callbacks retain the original read-before-teardown ordering of compound additions. Reverse-boundary TypeScript token comparison, normalizing formatter whitespace/trailing commas, passes all 17 moved bodies, nine fields, 18 ordinary declarations, 106 retained facade methods and the four original loader-construction statements; the released-parts initializer is unchanged. Existing specs and index.ts/world.ts exports are unchanged, and IAssetState remains re-exported through world-cells.ts. Initial export-modifier order and host stage-union errors were corrected; final core typecheck passes (exit 0), scoped Biome check --write passes (exit 0, 14 nonfatal warnings), and the unchanged full laptop world suite with --maxWorkers=1 passes all 41 files / 628 tests, three skipped (167.13 s), including the 1,000-seed fuzz, all 97 GPU-scene tests and all 13 surface tests. wc -l confirms every world-* module except world-cells.ts remains below 3,000 lines. The extra architecture guard identified in the previous collaborator is still unresolved and was not rerun; specs remain untouched. No boxes ticked and no push. + +The next CellStreaming seam was assessed without source edits: its 23 original methods (639 body/signature lines) currently touch 32 external operations/state reads before consolidation, beyond the brief's approximately 20-member host ceiling. The 19 existing collaborator accesses are models.acquire/asset/disposeLoaded/loadsInFlight/loadsQueued/release/releaseModelPaths; chunks.bytes/evictProxy/startChunkLoad; impostors.farBytes/farInstances/restoreFar/retryFar/rootsNeeded; prewarm.pending/queued; gpuScene.on; and terrain.deferredAdmissions. Nine facade operations are cullResident, swap, boundsAt, canonical, clearSegment, shadowRecordsMoved, changedBounds, dropChunkBundles and cullEvicted. Four lifecycle/counter accesses are released, shadowMoved and the failure counter's ordered read/write operations. They respectively preserve model leases and near/far admission, render publication/teardown, and liveness/failure accounting. Rather than widen the host or pass collaborator/owner bags, this round stops with that inventory for a smaller seam plan. CellStreaming and CellRender remain unextracted; AC-2 (world-cells.ts <=3,000) and AC-3/playtests remain open. + +2026-10-11 Phase 2 Part 1 architecture guard: constraints.spec.ts excludes exactly world-cell-data/prewarm/chunks/impostors/models.ts from its generic appearance-word scan and subjects all five to the existing tighter no-material/light/colour construction, no appearance-property reads and no colour-literal assertions. These are the previously documented verbatim WorldCells extractions; their material identity transport, load-flight names and numeric contracts were already allowed in world-cells.ts. No regex or directory pattern broadened. Red before: one failed / three passed; green after: four passed (567 ms). Scoped Biome passes (exit 0). Existing world-*.spec.ts files unchanged. No boxes ticked; no push. + +2026-10-11 Phase 2 Part 2 CellRender collaborator: extracted 58 existing method bodies and 17 declarations into internal world-cell-render.ts (2,463 lines); WorldCells now holds one CellRender and is 2,444 lines (4,561 before). CellRender owns shared/retired batches, bundle records and compile state, GPU scene/residency, culling squares, shadow regions and fresh-mesh allowance as private state. Its 20-member ICellRenderHost is asset, assets, canonical, renderer, camera, now, residentCells, runMax, widenFilterRange, addUnchanged, disableFar, deferWide, queuedBuilds, covered, prewarmSettled, awaitDraw, carryPrewarm, add, isParent and isBundleHome. Immutable construction settings are copied into private fields; attachment identity is answered by boolean operations, so no owner instance or writable map crosses the boundary. The tiny bundle-home identity method and streaming-owned source-rebuild orchestration remain in the facade. There is no runtime import of world-cells.ts, and public exports/spec-visible mesh internals are unchanged. The architecture guard names only the new render filename and applies the same existing appearance assertions. + +Part 2 verification: formatter-normalised TypeScript token comparison passes all 58 moved bodies, 53 original private field/settings/scratch declarations, 17 top-level declarations and 45 retained facade bodies, reversing only named collaborator-boundary substitutions. Separate comparisons pass the copied private bounds helper, compile/GPU blocks, full update/disposal ordering, bundle/main-cull stats snapshots and chunk-bounds publication. Initial typecheck caught stale facade wiring and the assets iterator declaration; the pre-bounds-restoration core typecheck passed (exit 0), but final typecheck FAILED: the restored facade bounds helper references IWorldAsset after import trimming removed that type import. The first final failure was masked by a following successful Biome command; the second typecheck exposed the same missing import. Per the brief, stopped edits and restored only the uncommitted Part 2 source changes to the already committed CellRender version. The committed extraction still needs that import restored; no final typecheck success is claimed. The first unchanged laptop world suite had 627 passed / one failed / three skipped: world-shadow-caster-cost.spec.ts:256 exposed the facade reading its bounds scratch while the moved helper wrote the collaborator's scratch. Restored the verbatim bounds helper beside each private scratch box; the focused shadow-caster-cost spec passes both tests. One full rerun was deferred before testing by the offload thermal guard (98 C); after cooling, the exact requested unchanged laptop suite passes all 41 files / 628 tests / three skipped in 67.58 s, including the 1,000-seed race fuzz. Final architecture guard: four passed. Scoped Biome: exit 0, 12 nonfatal warnings. Existing world-*.spec.ts, index.ts and world.ts remain unedited; no boxes ticked and no push. + +2026-10-11 Phase 2 Part 3 condition: world-cells.ts is already below 3,000 lines, so no CellStreaming extraction or remaining-seam proposal was started. All packages/core/src/world-* files are below 3,000 lines; the only files over 2,500 are world-tiles.ts (2,880) and world-gpu-scene.ts (2,774). Phase 2/AC-2/3 remain unticked as instructed; pnpm quality and preview/machinefall walk playtests were not run under this brief. + +2026-10-11 Phase 2 Part 2 stop rule: final scope review attempted to move seedGpuSources with three additional named host operations (takeBuildJobs, forgetQueued, queueBuild), for 23 members total. Token comparison passed 59 moved bodies and 44 retained facade bodies, but the repeated missing-IWorldAsset typecheck failure triggered the brief's rollback rule. Reverted those uncommitted source changes only; the committed version retains 20 host members and facade seedGpuSources, with the previously recorded 628 passed / three skipped world suite. No second full-suite run for the extra GPU-source move was started. Doubtful assumption: a successful combined shell command meant the earlier typecheck had passed; it did not. Remaining repair: restore the IWorldAsset type import, then reverify and finish the source-rebuild seam. No boxes ticked; no push. + +2026-10-13 isolated Machinefall candidate build: the owned test checkout installs packed core0.3.4 plus its matching playtest0.3.4 peer. The first resumed build publishes597 cooked outputs and527 manifest entries, then correctly fails bundling because old playtest0.3.3 lacks the new clock protocol exports; the matching peer fixes this without a gameplay edit. The subsequent normal build passes (Vite2.77s; terminal exit0 at16:49:33UTC). Parent audit verifies all597 receipt outputs with exact byte counts, all527 manifest main outputs, no pending journal, and all seven files referenced by dist/index.html. Installed core/playtest artifacts match the archives (core SHA256 `9a089679c0214a059fde63e12df242e8f2b8e2fa51a27167adbcdfbbf2e063d0`, playtest `9be97bf2f854578773e934cbe805ef153708bc2d6f27ba9b6788edc4598c4984`). Removed legacy Three patch registration is wholly superseded:30 installed target hashes match the canonical installer; its extra sample-count hash is redundant with context.sampleCount. Runtime map-walk and existing performance assertions remain unrun; phase2 stays open. + +2026-10-13 actual Machinefall map-walk: the unchanged scenario runs against the built candidate on NVIDIA Turing1280x720 for4383 frames. It passes19/20 assertions; diagnostics fails on789 GPU validation errors reusing destroyed RGBA32Float textures (2442x1 and4096x3,2 layers). Network/runtime diagnostic counts are zero. Streaming eviction/residency and bridge travel advance, ending with zero loads/failures, valid terrain spawn, zero air frames and zero grounding error. Existing performance rows pass their numerical checks, but the private display is insufficient for a real-display FPS claim. This is an actual failed consumer gate, not runtime acceptance; source ownership/lifetime tracing is underway without changing scenario limits. + +2026-10-13 lifetime diagnosis: actual GPU texture creation/destruction stacks identify terrain morph geometries replaced during WorldTiles edge synchronization. WorldTiles assigns the replacement before disposing the old geometry. An actual installed RenderObjects-method reproduction shows geometry replacement retains the old node builder/bindings and skips full cache-key validation, although the key already includes morph identity. The shared Three adapter fix adds geometry changes to that validation gate; the regression is RED on previous behavior. The meaningful regression is GREEN with the fix; 36 focused cache/pipeline/compatibility tests, core typecheck, Biome, build/publint and all30 installed target hashes pass. Parent independently reruns22 cache/compatibility tests successfully; a fresh read-only review returns APPROVE. The new core archive is installed normally into the isolated Machinefall checkout, where the actual runtime rerun remains pending. No disposal delay, leak or diagnostic suppression is accepted. + +2026-10-13 actual consumer fix proof: the newly packed core archive (SHA2565d7c25d5bb3d42f3b104bab900e6b9912e2951d1475d33ddbe34ecf22e405587) is normally installed and built in the isolated Machinefall checkout. The unchanged map-walk scenario now passes20/20 assertions across4383 frames with no diagnostics; prior candidate passed19/20 with789 destroyed-texture errors. Streaming, travel and grounding assertions retain their existing limits. This is browser/NVIDIA consumer proof; repeat visual comparison and direct native morph-replacement proof remain pending, and the full preview walk still has its three failing budgets. AC-3 remains open. + +2026-10-13 second unchanged Machinefall walk also passes20/20 assertions across4383 frames with no diagnostics. Both original consumer limits and scenario bytes are unchanged. Direct native geometry-replacement proof and paired visual review remain pending; no full-preview or real-display FPS claim is added. + +2026-10-13 direct morph replacement proof extends existing53-morph-target-animation without changing its pose: render/complete old geometry, clone/assign/restore influence0.65/dispose old once between frames, then render/complete replacement. Six focused fixture tests pass in worker and parent. Actual NVIDIA browser and current Dawn/V8 desktop both pass; desktop renders300 host frames, exits0, has no GPU validation errors and matches browser with pixel mismatch0/deltaE0. Selected runner exit2 reflects105 unselected blocked rows, not full-matrix failure or proof. Fresh final-frame review of both Machinefall pairs returns NEUTRAL (preserved appearance), not an improvement; the second BEFORE arm is an observation-only lifetime probe. This neutral review does not close visual acceptance or the full preview walk. diff --git a/docs/PRDs/strata-terrain-hardening/README.md b/docs/PRDs/strata-terrain-hardening/README.md new file mode 100644 index 0000000000..dae3027e71 --- /dev/null +++ b/docs/PRDs/strata-terrain-hardening/README.md @@ -0,0 +1,61 @@ +# Strata terrain hardening + +**Status:** IN PROGRESS — dedicated EPIC execution; all constituent proof boxes remain authoritative. + +**Delivery decision:** João requested one dedicated EPIC PR for this batch on 2026-10-10, overriding the usual one-PR-per-PRD delivery rule. Each constituent PRD retains its complete scope, phase checkboxes and verification requirements. + +## Execution checkout + +- Owner: Codex active terrain-hardening goal. +- Worktree: `/home/joao/projects/threenative/threenative-engine/.worktrees/strata-terrain-hardening`. +- Branch: `epic/strata-terrain-hardening`; target: `develop`. +- Draft EPIC: [PR #483](https://github.com/ThreeNativeHQ/threenative-engine/pull/483). +- Base: `origin/develop` at `5f7143895` (integrated in `fef436694`). +- Cleanup: retained while implementation and review are active; inspect data and merge evidence before ordinary worktree removal. + +## Scope + +- [PRD-583 — A Strata world bakes headlessly into an instanced GLB](PRD-583-a-strata-world-bakes-headlessly-into-an-instanced-glb.md) +- [PRD-584 — An agent asks for a forest over MCP and gets a 60 fps GLB](PRD-584-an-agent-asks-for-a-forest-over-mcp-and-gets-a-60-fps-glb.md) +- [PRD-585 — Strata's proof runs in CI, not only by hand](PRD-585-strata-proof-runs-in-ci.md) +- [PRD-587 — The terrain editor never drops an edit](../done/PRD-587-the-terrain-editor-never-drops-an-edit.md) +- [PRD-588 — The terrain editor is typed modules, not one 1481-line closure](../done/PRD-588-the-terrain-editor-is-typed-modules.md) +- [PRD-589 — `@threenative/terrain` ships code, not 33 MB of kit data](../done/PRD-589-the-terrain-package-ships-code-not-kit-data.md) +- [PRD-590 — One starter-world source, not five copies](../done/PRD-590-one-starter-world-source-not-five.md) +- [PRD-591 — The preview stops re-implementing engine mechanisms](PRD-591-the-preview-stops-reimplementing-engine-mechanisms.md) +- [PRD-592 — Terrain core fails closed and erodes without allocating](../done/PRD-592-terrain-core-fails-closed-and-erodes-without-allocating.md) +- [PRD-593 — The three.js patch is generated, and the upload lane leaves it](PRD-593-the-three-patch-is-generated-and-the-upload-lane-leaves-it.md) +- [PRD-594 — The engine changes that rode in with #381 get native and visual proof](PRD-594-engine-changes-from-381-get-native-and-visual-proof.md) +- [PRD-595 — `world-cells.ts` splits by seam and survives a load/evict race fuzz](PRD-595-world-cells-splits-by-seam-and-survives-a-race-fuzz.md) + +## Execution order + +Start with PRD-592 golden bytes and validation, then execute 585, 583, 587, 593, 591, 594, 589, 590, 595, 584 and 588. This order serializes shared-file edits; all phases and acceptance criteria in each PRD remain required. PRD-584 requires 583; 588 export consolidation also requires 583. Coordinate world-cell edits with PRD-473 before changing its shared implementation. + +Current baseline: `pnpm build` and `pnpm check:docs` passed; six documentation contract spec files passed (244 tests). No runtime or performance acceptance is established by those checks. + +## Current integration state + +The batch has 51/62 verified phase boxes (repository bucket `prd:75%`) and 112/132 combined phase/acceptance boxes. PRDs 587, 588, 589, 590 and 592 are complete; all other original requirements remain open. The integration checkout above is retained for this active goal. + +Develop integration resolves all six conflicted files, regenerates the Three patches with both morph pipeline sharing and bounded uploads, and fixes removed camera/renderer references through the extracted collaborator. Focused validation passes 261 tests with 4 skips; core typecheck, frozen install and scoped Biome pass. Full CI and remaining runtime/visual acceptance are not established by those checks. + +The versioned starter-asset release is now published. A fresh extracted terrain package, without checkout assets or URL overrides, downloads and validates the public 31,598,616-byte archive and extracts 18 entries. A fresh-cache scaffolded forest now passes the published-archive runtime gate (819 frames, NVIDIA/Turing); PRD-589 is complete and archived. + +2026-10-13 integration: `fef436694` merges develop `5f7143895` (PRD-609). The only conflict was the scaffold fingerprint table; all thirteen actual trees were measured, with only action-rpg changing. Scaffold and CI attempt-receipt specs pass 92/92. Laptop typecheck and lint pass after rebuilding core declarations. The real-display NVIDIA forest walk still fails its 16.7 ms gate (p95 98.7 ms, 71 timing samples); the follow-up trace diagnoses CPU and first-use compilation cost without claiming steady FPS. The standard V8 host, all V8 contract targets and the QuickJS cross-engine targets now build. Native package tests pass 1,586 with 62 skips; the six previously failing files pass 72 with one skip. A shared starter-archive fixture leaked its beforeAll directory; immediate try/finally cleanup fixes the monitored package-test guard (1 → 1 directories), with the full package-test phase exiting 0. The socket probe subsequently reproduced ECONNRESET from the test ClientRequest: the helper returned after its 413 response while a 10 MiB chunked upload remained in flight. Cancelling the remaining upload and waiting for request close passes twelve consecutive instrumented runs (216 assertions), without a reset or unhandled error. The final full unit phase passes 798 files and 10,636 tests, with 20 skips and temporary directories stable at 1 → 1. It ran with four available CPUs (three existing Vitest workers), avoiding the 60-second build-exit timeout seen under heavy unrestricted concurrency; no timeout or assertion changed. Updated full typecheck/lint, all 317 terrain package tests, frozen install and strict package validation pass. Hosted CI and the remaining runtime/performance criteria stay open. + +## Look gate (owner decision, 2026-10-11) + +The terrain must be AAA quality. Structure, speed and packaging work does not count as progress on the look. The forest kit renders worse than the merged PR #381 canopy (preview example unchanged; kit path differs). [PRD-580](../open-world/PRD-580-strata-starter-kit-art-bar.md) owns the art bar and now runs first for the forest kit, ahead of the remaining acceptance work in this EPIC. + +Order, one lane at a time, each ending in a fresh read-only visual judge (headed WebGPU, `--browser-recipe webgpu`, `adapter.info` checked, two runs per arm, same pose as the PR #381 canopy frames, Gaia and AAA reference images attached): + +1. Black trees under the starter post chain (stage readout, tree-pixel luminance assertion in the forest walk playtest). +2. Placement: clearings, clumping, density and height variation in `recipe.json`. +3. Lighting and horizon from the kit's own source instead of the starter template defaults. +4. LOD by distance for the near fir (also the 10.8 M triangle cost, PRD-584 AC-4/5). +5. Needle material and colour, cutout edges. + +A change that touches trees, ground, lighting or LOD ticks no box without an IMPROVEMENT verdict. The PRD 583 authored-noise ground attempt (REGRESSION) stays out of the tree. Perf wins count only after the judge passes. + +2026-10-14 upstream integration: at the owner's request, fetched and merged `origin/develop` at `99aeb6e30` (PRD-568 / #488, mobile GPU-ready ASTC textures) into this branch as `6db7012e4`. The sole conflict in `packages/assets/src/passes/lightmap.ts` preserves both this branch's asynchronous static bake and the upstream ASTC encoder argument. The focused lightmap/texture/model-texture/core-assets suites pass 198 tests with 1 skipped; assets and core typechecks and scoped Biome pass. Earlier terrain morph/LOD, cached shadow-page, first-frame GPU tier and height-fog work is already in the branch's ancestry. Fresh installed-consumer package freshness and full CI remain to be verified; source ancestry alone is not runtime proof. The owner also reports a close/far tree silhouette change and rejects lighting, shadows and atmosphere. Opus 5.5 high has reproduced a trunk LOD silhouette error substantially larger than the stored error and is implementing the repair; no visual or performance acceptance is claimed. diff --git a/docs/architecture/AGENT-INTERFACE.md b/docs/architecture/AGENT-INTERFACE.md index b96612da35..668e26f4e6 100644 --- a/docs/architecture/AGENT-INTERFACE.md +++ b/docs/architecture/AGENT-INTERFACE.md @@ -7,11 +7,13 @@ vocabulary. **What exists now that this file was written before.** The read-only half of the MCP layer this document argues for has shipped: [`threenative-engine-mcp`](../../packages/engine-mcp/) serves `engine_search_capabilities` and `engine_capability_detail` over the committed capability -manifest, and installing `@threenative/core` writes every project a `.mcp.json` wiring four +manifest, and installing `@threenative/core` writes every project a `.mcp.json` wiring five servers — `threenative-assets` (the externally pinned `threenative-asset-mcp`, launched through -a shim inside core), `threenative-sculpt`, `threenative-engine`, and `threenative-blender` +a shim inside core), `threenative-sculpt`, `threenative-engine`, `threenative-blender` ([`threenative-blender-mcp`](../../packages/blender-mcp/), which drives an installed Blender -headlessly so a downloaded `.fbx` becomes a GLB, and ships no Blender). What left is Studio: it +headlessly so a downloaded `.fbx` becomes a GLB, and ships no Blender), and `threenative-terrain` +(a shim inside core that starts the project's installed `@threenative/terrain` bin and cooks a +starter terrain kit into a `world.glb`). What left is Studio: it moved to a separate private repository (recorded in [`docs/README.md`](../README.md); PRDs [084](../PRDs/done/PRD-084-threenative-studio.md), [085](../PRDs/done/PRD-085-studio-wiring.md) and diff --git a/docs/architecture/CHARTER.md b/docs/architecture/CHARTER.md index 3843b09f81..0686ed33a9 100644 --- a/docs/architecture/CHARTER.md +++ b/docs/architecture/CHARTER.md @@ -99,7 +99,7 @@ thesis: each spends the training-data advantage to buy a surface the model then | An IR, a compiler, a serialized scene format | Your game is TypeScript. The compiler was 25,898 LOC and bought nothing a model can't do with a `.ts` file. | | A second *renderer* | 32% of v1's commits went to a runtime no benchmark ever measured. §7's host is the bounded exception: it runs Three.js, it does not replace it. The moment the host starts drawing, it has become v1. | | A JSON/structured-source ECS | Measured at **14x vanilla Three.js** on greenfield work, and scored lower on playability and visuals. | -| An editor | Not in v1. The paid editor is a separate product in a separate repository. | +| A general game editor | The paid editor is a separate product in a separate repository. Terrain-only authoring companions are admitted by the narrow allowance below. | | A bespoke CLI vocabulary | 178 command forms, a 2,477-word root help. Models are worst at discovering novel API surfaces. | | A recipe/preset/genre system | 0 of 7 presets ever reproduced their genre. | | A code-first ECS (miniplex, bitECS) | Entities are plain classes (§9b). Gameplay architecture is the model's job. A game that wants one runs `pnpm add miniplex`; the framework neither ships nor fights it. | diff --git a/examples/procedural-animals/src/render/donor/core/motion/quadruped.js b/examples/procedural-animals/src/render/donor/core/motion/quadruped.js index ecc2f10783..da834fe520 100644 --- a/examples/procedural-animals/src/render/donor/core/motion/quadruped.js +++ b/examples/procedural-animals/src/render/donor/core/motion/quadruped.js @@ -250,7 +250,7 @@ function resolveConfig(spec0, data) { flexBlend: spec.flexBlend || [3.5, 6], gaits: (spec.gaits || []).map((g) => ({ f: 1, - D: 0.6, + "D": 0.6, liftF: 0.06, liftH: 0.06, bobF: 0, @@ -955,7 +955,13 @@ export class QuadrupedMotion { this.gaits = [...new Set(cfg.gaits.map((g) => g.name))]; this.gears = cfg.gears; // ---- gait sample (reused) - this.gait = { name: "walk", blend: 0, gallop: 0, off: [0, 0, 0, 0], DL: [0.6, 0.6, 0.6, 0.6] }; + this.gait = { + name: "walk", + blend: 0, + gallop: 0, + off: [0, 0, 0, 0], + "DL": [0.6, 0.6, 0.6, 0.6], + }; for (const k of GAIT_KEYS) this.gait[k] = 0; // ---- springs this.bank = new Spring(0); @@ -990,9 +996,9 @@ export class QuadrupedMotion { this.lift = 0; // vertical body offset (jump flight), set by the action layer this.speedOverride = null; // ground speed imposed by an action (jump push-off) // airborne state (jump) - this.air = { active: false, t: 0, vy: 0, y0: 0, vh: 0, T: 0 }; + this.air = { active: false, t: 0, vy: 0, y0: 0, vh: 0, "T": 0 }; // landing sag of the fore / hind girdles after a touchdown (see touchDown, pose) - this.sag = { on: false, F: new Spring(0), H: new Spring(0), wF: 17, wH: 17 }; + this.sag = { on: false, "F": new Spring(0), "H": new Spring(0), wF: 17, wH: 17 }; // ---- legs this.legs = cfg.legs.map((L) => ({ def: L, @@ -1072,9 +1078,9 @@ export class QuadrupedMotion { n, p: Array.from({ length: n + 1 }, () => new three.Vector3()), len: Array.from({ length: n }, (_, i) => J[`tail${i}`].distanceTo(J[`tail${i + 1}`])), - D: Array.from({ length: n }, () => new three.Vector3()), - W: Array.from({ length: n }, () => new three.Vector3()), - U: Array.from({ length: n }, () => new three.Vector3()), + "D": Array.from({ length: n }, () => new three.Vector3()), + "W": Array.from({ length: n }, () => new three.Vector3()), + "U": Array.from({ length: n }, () => new three.Vector3()), rel: Array.from({ length: n }, () => new three.Quaternion()), pitch: new Float64Array(n), yaw: new Float64Array(n), @@ -1129,8 +1135,8 @@ export class QuadrupedMotion { tailBase: V(), hipC: V(), shC: V(), - F: V(), - Lf: V(), + "F": V(), + "Lf": V(), pos: V(), comp: 0, bs: 1, diff --git a/examples/strata-terrain-preview/.gitignore b/examples/strata-terrain-preview/.gitignore index a2fa9ba83b..0122192f3d 100644 --- a/examples/strata-terrain-preview/.gitignore +++ b/examples/strata-terrain-preview/.gitignore @@ -2,3 +2,5 @@ /dist/ /src/world/.bake-stamp /assets/ +/src/world/*.placements.bin +/src/world/*.splat.rgba8 diff --git a/examples/strata-terrain-preview/AGENTS.md b/examples/strata-terrain-preview/AGENTS.md index be9aea9ed4..844506e910 100644 --- a/examples/strata-terrain-preview/AGENTS.md +++ b/examples/strata-terrain-preview/AGENTS.md @@ -51,3 +51,96 @@ a full-world GLB in a plain browser page, and hands the result to a scaffolded T builds and plays with no authoring package (`CONSUMER_GAME=skip` leaves that last stage out). `pnpm --filter strata-terrain-preview test:terrain:authored` polishes a world through the editor's real controls, reloads it and reproduces `scripts/fixtures/editor-authored.json` (`RECORD=1` rewrites it). + +### Script, playtest and fixture index + +All scripts, playtest definitions, manual preparation helpers and fixtures under `scripts/` and `playtests/`: + +- Playtest scenarios: + - `playtests/editor-conflict.playtest.json`: checks concurrent editor saves preserve both clients’ edits. + - `playtests/editor.playtest.json`: playtest scenario checking vertex count and appearance in the recovered editor view. + - `playtests/export.playtest.json`: playtest scenario checking static GLB export loading and appearance. + - `playtests/terrain.playtest.json`: primary playtest scenario for shared web/desktop showcase views. + - `playtests/walk.playtest.json`: playtest scenario checking baked terrain triangles and player walk stability. + +- Verification and test suites: + - `scripts/verify-assets.mjs`: verifies asset manifests, replacement tables, and model loading. + - `scripts/verify-authored-world.mjs`: verifies that editor-authored modifications reproduce committed baseline output. + - `scripts/verify-cameras.mjs`: checks camera viewpoints, transitions, and framing across all world biomes. + - `scripts/verify-consumer.mjs`: verifies packaging and consumption of the authored world in an external project. + - `scripts/verify-custom-assets.mjs`: proves custom asset overrides swap cleanly without starter assets. + - `scripts/verify-editor.mjs`: verifies live editor tool application, brush operations, and undo/redo state. + - `scripts/verify-environment.mjs`: tests biome-specific environmental lighting and atmosphere settings. + - `scripts/verify-game-handoff.mjs`: verifies clean scene handoff into an ordinary playable ThreeNative game. + - `scripts/verify-import-export.mjs`: checks full import and export roundtrips for world documents. + - `scripts/verify-landforms.mjs`: validates terrain elevation bounds, slopes, and landform contours. + - `scripts/verify-ocean.mjs`: verifies ocean surface rendering, shorelines, and waterline properties. + - `scripts/verify-sky.mjs`: checks procedural and LUT sky dome conditions across time and weather. + - `scripts/verify-starter-kit.mjs`: verifies starter kit configuration, generation, and multi-world playtests. + - `scripts/verify-surfaces.mjs`: verifies PBR terrain surface materials, splat textures, and blend transitions. + - `scripts/verify-tool-groups.mjs`: tests editor UI tool groupings, interactions, and parameter schemas. + - `scripts/verify-transforms.mjs`: validates coordinate transforms, rotations, and placement matrices. + - `scripts/verify-world-export.mjs`: verifies headless static world GLB export and vanilla Three.js loading. + - `scripts/check-water.mjs`: regression tests for shared water rendering and shoreline depths. + - `scripts/check-temperate.mts`: checks foliage cover distribution and LOD distance compaction. + +- Authoring, staging and data preparation: + - `scripts/fixtures/editor-conflict.ts`: mounts two editor clients against the same document for the conflict scenario. + - `scripts/bake.mjs`: bakes source elevation data and generates canonical world JSON packages. + - `scripts/consumer-world.mjs`: exports `openConsumerPage` to serve an installed consumer directory to headless Chromium and run vanilla full-world export against packed tarballs. + - `scripts/stage-native-assets.mjs`: stages textures and models for native desktop/mobile bundles. + - `scripts/make-needle-atlas.mjs`: procedural generator for conifer needle foliage atlases. + - `scripts/prep-fab-pines.py`: manual prep script requiring Blender CLI and authored Fab Scots pine GLB to decimate and budget pines. + - `scripts/prep-fab-temperate.mjs`: cooks and fixes texture atlas and opacity bindings for temperate models. + - `scripts/prep-landscape-pro.mjs`: stages licensed Landscape Pro species for game runtime loading. + - `scripts/prep-polyhaven-fir.py`: manual prep script requiring Blender CLI and authored Poly Haven fir 1k .blend to export game GLBs. + - `scripts/prep-polyhaven-rock.py`: manual prep script requiring Blender CLI and authored Poly Haven boulder 1k .blend to export game GLBs. + - `scripts/prep-trees.py`: manual prep script requiring Blender CLI and authored CC0 tree models to decimate to triangle and atlas budgets. + +- Landform measurement and diagnostics: + - `scripts/measure-embankment.mjs`: measures cross-section geometry and bench cut slope of road embankments. + - `scripts/measure-light.mjs`: measures display-referred Rec.709 luminance against reference calibration. + - `scripts/measure-spikes.mjs`: detects elevation spikes and gradient discontinuities across terrain bakes. + - `scripts/measure-terrain.mjs`: tabulates terrain relief, slope, bake timing, and drainage metrics. + +- USGS DEM source data and cropping: + - `scripts/dem/crop.mjs`: crops and transforms raw USGS 3DEP GeoTIFFs into canonical DEM bins and metadata. + - `scripts/dem/alpine.bin`: canonical elevation heightfield binary for the alpine world. + - `scripts/dem/alpine.json`: grid metadata and coordinate bounding bounds for the alpine world. + - `scripts/dem/alpine-horizon.bin`: distant horizon ring elevation binary for the alpine world. + - `scripts/dem/alpine-horizon.json`: distant horizon ring grid metadata for the alpine world. + - `scripts/dem/coastal.bin`: canonical elevation heightfield binary for the coastal world. + - `scripts/dem/coastal.json`: grid metadata and coordinate bounding bounds for the coastal world. + - `scripts/dem/coastal-horizon.bin`: distant horizon ring elevation binary for the coastal world. + - `scripts/dem/coastal-horizon.json`: distant horizon ring grid metadata for the coastal world. + - `scripts/dem/desert.bin`: canonical elevation heightfield binary for the desert world. + - `scripts/dem/desert.json`: grid metadata and coordinate bounding bounds for the desert world. + - `scripts/dem/desert-horizon.bin`: distant horizon ring elevation binary for the desert world. + - `scripts/dem/desert-horizon.json`: distant horizon ring grid metadata for the desert world. + - `scripts/dem/forest.bin`: canonical elevation heightfield binary for the forest world. + - `scripts/dem/forest.json`: grid metadata and coordinate bounding bounds for the forest world. + - `scripts/dem/forest-horizon.bin`: distant horizon ring elevation binary for the forest world. + - `scripts/dem/forest-horizon.json`: distant horizon ring grid metadata for the forest world. + - `scripts/dem/tundra.bin`: canonical elevation heightfield binary for the tundra world. + - `scripts/dem/tundra.json`: grid metadata and coordinate bounding bounds for the tundra world. + - `scripts/dem/tundra-horizon.bin`: distant horizon ring elevation binary for the tundra world. + - `scripts/dem/tundra-horizon.json`: distant horizon ring grid metadata for the tundra world. + +- Fixtures and testing environments: + - `scripts/fixtures/consumer-world.mjs`: driver running an authored world in an isolated browser harness. + - `scripts/fixtures/editor-authored.json`: golden recorded output of real editor authoring operations. + - `scripts/fixtures/editor-readback.mjs`: browser harness reading back live editor canvas state. + - `scripts/fixtures/export-world.mjs`: browser-side test driver for headless full-world export. + - `scripts/fixtures/vanilla-world.mjs`: vanilla Three.js runner verifying static world GLB rendering. + - `scripts/fixtures/handoff-game/Handoff.ts`: handoff harness component receiving exported terrain. + - `scripts/fixtures/handoff-game/game.ts`: standalone ThreeNative entrypoint testing handoff consumption. + - `scripts/fixtures/handoff-game/handoffEnvironment.ts`: lighting and environment setup for handoff fixture. + - `scripts/fixtures/handoff-game/state.ts`: reactive state management for handoff fixture game. + - `scripts/fixtures/kit-game/KitScene.ts`: shared starter kit fixture scene parameterised by biome. + - `scripts/fixtures/kit-game/game.ts`: standalone multi-world game harness testing starter kit scenes. + - `scripts/fixtures/kit-game/state.ts`: shared state for starter kit game fixture. + - `scripts/fixtures/kit-game/playtests/alpine.playtest.json`: playtest scenario for starter kit alpine world. + - `scripts/fixtures/kit-game/playtests/coastal.playtest.json`: playtest scenario for starter kit coastal world. + - `scripts/fixtures/kit-game/playtests/desert.playtest.json`: playtest scenario for starter kit desert world. + - `scripts/fixtures/kit-game/playtests/kit.playtest.json`: master playtest scenario verifying starter kit worlds. + - `scripts/fixtures/kit-game/playtests/tundra.playtest.json`: playtest scenario for starter kit tundra world. diff --git a/examples/strata-terrain-preview/CLAUDE.md b/examples/strata-terrain-preview/CLAUDE.md index f24b589ae9..b76419ac5d 100644 --- a/examples/strata-terrain-preview/CLAUDE.md +++ b/examples/strata-terrain-preview/CLAUDE.md @@ -53,3 +53,96 @@ a full-world GLB in a plain browser page, and hands the result to a scaffolded T builds and plays with no authoring package (`CONSUMER_GAME=skip` leaves that last stage out). `pnpm --filter strata-terrain-preview test:terrain:authored` polishes a world through the editor's real controls, reloads it and reproduces `scripts/fixtures/editor-authored.json` (`RECORD=1` rewrites it). + +### Script, playtest and fixture index + +All scripts, playtest definitions, manual preparation helpers and fixtures under `scripts/` and `playtests/`: + +- Playtest scenarios: + - `playtests/editor-conflict.playtest.json`: checks concurrent editor saves preserve both clients’ edits. + - `playtests/editor.playtest.json`: playtest scenario checking vertex count and appearance in the recovered editor view. + - `playtests/export.playtest.json`: playtest scenario checking static GLB export loading and appearance. + - `playtests/terrain.playtest.json`: primary playtest scenario for shared web/desktop showcase views. + - `playtests/walk.playtest.json`: playtest scenario checking baked terrain triangles and player walk stability. + +- Verification and test suites: + - `scripts/verify-assets.mjs`: verifies asset manifests, replacement tables, and model loading. + - `scripts/verify-authored-world.mjs`: verifies that editor-authored modifications reproduce committed baseline output. + - `scripts/verify-cameras.mjs`: checks camera viewpoints, transitions, and framing across all world biomes. + - `scripts/verify-consumer.mjs`: verifies packaging and consumption of the authored world in an external project. + - `scripts/verify-custom-assets.mjs`: proves custom asset overrides swap cleanly without starter assets. + - `scripts/verify-editor.mjs`: verifies live editor tool application, brush operations, and undo/redo state. + - `scripts/verify-environment.mjs`: tests biome-specific environmental lighting and atmosphere settings. + - `scripts/verify-game-handoff.mjs`: verifies clean scene handoff into an ordinary playable ThreeNative game. + - `scripts/verify-import-export.mjs`: checks full import and export roundtrips for world documents. + - `scripts/verify-landforms.mjs`: validates terrain elevation bounds, slopes, and landform contours. + - `scripts/verify-ocean.mjs`: verifies ocean surface rendering, shorelines, and waterline properties. + - `scripts/verify-sky.mjs`: checks procedural and LUT sky dome conditions across time and weather. + - `scripts/verify-starter-kit.mjs`: verifies starter kit configuration, generation, and multi-world playtests. + - `scripts/verify-surfaces.mjs`: verifies PBR terrain surface materials, splat textures, and blend transitions. + - `scripts/verify-tool-groups.mjs`: tests editor UI tool groupings, interactions, and parameter schemas. + - `scripts/verify-transforms.mjs`: validates coordinate transforms, rotations, and placement matrices. + - `scripts/verify-world-export.mjs`: verifies headless static world GLB export and vanilla Three.js loading. + - `scripts/check-water.mjs`: regression tests for shared water rendering and shoreline depths. + - `scripts/check-temperate.mts`: checks foliage cover distribution and LOD distance compaction. + +- Authoring, staging and data preparation: + - `scripts/fixtures/editor-conflict.ts`: mounts two editor clients against the same document for the conflict scenario. + - `scripts/bake.mjs`: bakes source elevation data and generates canonical world JSON packages. + - `scripts/consumer-world.mjs`: exports `openConsumerPage` to serve an installed consumer directory to headless Chromium and run vanilla full-world export against packed tarballs. + - `scripts/stage-native-assets.mjs`: stages textures and models for native desktop/mobile bundles. + - `scripts/make-needle-atlas.mjs`: procedural generator for conifer needle foliage atlases. + - `scripts/prep-fab-pines.py`: manual prep script requiring Blender CLI and authored Fab Scots pine GLB to decimate and budget pines. + - `scripts/prep-fab-temperate.mjs`: cooks and fixes texture atlas and opacity bindings for temperate models. + - `scripts/prep-landscape-pro.mjs`: stages licensed Landscape Pro species for game runtime loading. + - `scripts/prep-polyhaven-fir.py`: manual prep script requiring Blender CLI and authored Poly Haven fir 1k .blend to export game GLBs. + - `scripts/prep-polyhaven-rock.py`: manual prep script requiring Blender CLI and authored Poly Haven boulder 1k .blend to export game GLBs. + - `scripts/prep-trees.py`: manual prep script requiring Blender CLI and authored CC0 tree models to decimate to triangle and atlas budgets. + +- Landform measurement and diagnostics: + - `scripts/measure-embankment.mjs`: measures cross-section geometry and bench cut slope of road embankments. + - `scripts/measure-light.mjs`: measures display-referred Rec.709 luminance against reference calibration. + - `scripts/measure-spikes.mjs`: detects elevation spikes and gradient discontinuities across terrain bakes. + - `scripts/measure-terrain.mjs`: tabulates terrain relief, slope, bake timing, and drainage metrics. + +- USGS DEM source data and cropping: + - `scripts/dem/crop.mjs`: crops and transforms raw USGS 3DEP GeoTIFFs into canonical DEM bins and metadata. + - `scripts/dem/alpine.bin`: canonical elevation heightfield binary for the alpine world. + - `scripts/dem/alpine.json`: grid metadata and coordinate bounding bounds for the alpine world. + - `scripts/dem/alpine-horizon.bin`: distant horizon ring elevation binary for the alpine world. + - `scripts/dem/alpine-horizon.json`: distant horizon ring grid metadata for the alpine world. + - `scripts/dem/coastal.bin`: canonical elevation heightfield binary for the coastal world. + - `scripts/dem/coastal.json`: grid metadata and coordinate bounding bounds for the coastal world. + - `scripts/dem/coastal-horizon.bin`: distant horizon ring elevation binary for the coastal world. + - `scripts/dem/coastal-horizon.json`: distant horizon ring grid metadata for the coastal world. + - `scripts/dem/desert.bin`: canonical elevation heightfield binary for the desert world. + - `scripts/dem/desert.json`: grid metadata and coordinate bounding bounds for the desert world. + - `scripts/dem/desert-horizon.bin`: distant horizon ring elevation binary for the desert world. + - `scripts/dem/desert-horizon.json`: distant horizon ring grid metadata for the desert world. + - `scripts/dem/forest.bin`: canonical elevation heightfield binary for the forest world. + - `scripts/dem/forest.json`: grid metadata and coordinate bounding bounds for the forest world. + - `scripts/dem/forest-horizon.bin`: distant horizon ring elevation binary for the forest world. + - `scripts/dem/forest-horizon.json`: distant horizon ring grid metadata for the forest world. + - `scripts/dem/tundra.bin`: canonical elevation heightfield binary for the tundra world. + - `scripts/dem/tundra.json`: grid metadata and coordinate bounding bounds for the tundra world. + - `scripts/dem/tundra-horizon.bin`: distant horizon ring elevation binary for the tundra world. + - `scripts/dem/tundra-horizon.json`: distant horizon ring grid metadata for the tundra world. + +- Fixtures and testing environments: + - `scripts/fixtures/consumer-world.mjs`: driver running an authored world in an isolated browser harness. + - `scripts/fixtures/editor-authored.json`: golden recorded output of real editor authoring operations. + - `scripts/fixtures/editor-readback.mjs`: browser harness reading back live editor canvas state. + - `scripts/fixtures/export-world.mjs`: browser-side test driver for headless full-world export. + - `scripts/fixtures/vanilla-world.mjs`: vanilla Three.js runner verifying static world GLB rendering. + - `scripts/fixtures/handoff-game/Handoff.ts`: handoff harness component receiving exported terrain. + - `scripts/fixtures/handoff-game/game.ts`: standalone ThreeNative entrypoint testing handoff consumption. + - `scripts/fixtures/handoff-game/handoffEnvironment.ts`: lighting and environment setup for handoff fixture. + - `scripts/fixtures/handoff-game/state.ts`: reactive state management for handoff fixture game. + - `scripts/fixtures/kit-game/KitScene.ts`: shared starter kit fixture scene parameterised by biome. + - `scripts/fixtures/kit-game/game.ts`: standalone multi-world game harness testing starter kit scenes. + - `scripts/fixtures/kit-game/state.ts`: shared state for starter kit game fixture. + - `scripts/fixtures/kit-game/playtests/alpine.playtest.json`: playtest scenario for starter kit alpine world. + - `scripts/fixtures/kit-game/playtests/coastal.playtest.json`: playtest scenario for starter kit coastal world. + - `scripts/fixtures/kit-game/playtests/desert.playtest.json`: playtest scenario for starter kit desert world. + - `scripts/fixtures/kit-game/playtests/kit.playtest.json`: master playtest scenario verifying starter kit worlds. + - `scripts/fixtures/kit-game/playtests/tundra.playtest.json`: playtest scenario for starter kit tundra world. diff --git a/examples/strata-terrain-preview/__tests__/editor-durability.spec.ts b/examples/strata-terrain-preview/__tests__/editor-durability.spec.ts new file mode 100644 index 0000000000..c5a11169f2 --- /dev/null +++ b/examples/strata-terrain-preview/__tests__/editor-durability.spec.ts @@ -0,0 +1,209 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { validatePlaytestScenario } from "../../../packages/playtest/src/scenario.js"; +import { changed, installSaveQueue } from "../../../packages/terrain/src/editor/app/evaluation.js"; +import type { EditorSession } from "../../../packages/terrain/src/editor/app/session.js"; +import { TerrainEditorDocument } from "../../../packages/terrain/src/editor/server.js"; +import { Terrain } from "../../../packages/terrain/src/index.js"; +import { makeTempDirSync } from "../../../test-support/temp-dir.js"; +import { installStaleWrites } from "../scripts/fixtures/editor-conflict.js"; + +function session() { + const terrain = new Terrain({ size: 32, resolution: 17, seed: 73 }); + const path = join(makeTempDirSync("editor-durability-"), "world.json"); + writeFileSync(path, JSON.stringify({ version: 1, recipe: terrain.toJSON() })); + const authority = new TerrainEditorDocument(path); + class DomNode { + children: (DomNode | string)[] = []; + attributes: Record = {}; + hidden = false; + style = { cssText: "" }; + dataset: Record = {}; + onclick?: () => void; + constructor(readonly tagName: string) {} + setAttribute(key: string, value: string) { + this.attributes[key] = value; + if (key === "id") nodes.set(value, this); + } + append(...children: (DomNode | string)[]) { + this.children.push(...children); + } + replaceChildren(...children: (DomNode | string)[]) { + this.children = children; + } + get textContent(): string { + return this.children + .map((node) => (typeof node === "string" ? node : node.textContent)) + .join(""); + } + set textContent(text: string) { + this.children = [text]; + } + contains(node: unknown): boolean { + return ( + node === this || + this.children.some((child) => typeof child !== "string" && child.contains(node)) + ); + } + focus() { + dom.activeElement = this; + } + } + const nodes = new Map(); + nodes.set("save-status", new DomNode("span")); + const dom = { + body: new DomNode("body"), + activeElement: null as DomNode | null, + getElementById: (id: string) => nodes.get(id), + createElement: (tag: string) => new DomNode(tag), + }; + vi.stubGlobal("document", dom); + const c = { + terrain, + saved: authority.snapshot(), + revision: authority.snapshot().revision, + loading: false, + saving: false, + disposed: false, + renderLayers: vi.fn(), + renderCameras: vi.fn(), + renderEnvironment: vi.fn(), + renderAssets: vi.fn(), + syncAssets: vi.fn(), + build: vi.fn(), + toast: vi.fn(), + view: { setDocument: vi.fn() }, + commit: async (input: unknown) => authority.commit(input), + getSnapshot: async () => authority.snapshot(), + } as unknown as EditorSession; + installSaveQueue(c); + terrain.subscribe(() => void changed(c)); + return { c, authority, path, nodes, dom }; +} + +afterEach(() => { + vi.useRealTimers(); + vi.unstubAllGlobals(); +}); + +describe("editor save integration", () => { + it("persists two actual strokes 50 ms apart while the first write is pending", async () => { + vi.useFakeTimers(); + const { c, path } = session(); + let release!: () => void; + const gate = new Promise((resolve) => { + release = resolve; + }); + const commit = c.commit; + c.commit = async (input) => { + await gate; + return commit(input); + }; + c.terrain.sculpt({ id: "first", points: [[0, 0]], radius: 4, strength: 1 }); + await vi.advanceTimersByTimeAsync(50); + c.terrain.sculpt({ id: "second", points: [[2, 2]], radius: 4, strength: 1 }); + release(); + await c.saveQueue.flush(); + await Promise.resolve(); + const disk = JSON.parse(readFileSync(path, "utf8")); + expect(disk.recipe.layers.map((layer: { id: string }) => layer.id)).toEqual([ + "first", + "second", + ]); + expect(c.terrain.layers.map((layer) => layer.id)).toEqual(["first", "second"]); + expect(c.toast).not.toHaveBeenCalled(); + }); + it("renders a real stale-revision 409 in the not-saved list and retries through its button", async () => { + const { c, authority, nodes, dom } = session(); + c.commit = async (input) => authority.commit({ ...input, baseRevision: "stale" }); + c.terrain.sculpt({ id: "refused", points: [[0, 0]], radius: 4, strength: 1 }); + await c.saveQueue.flush(); + const panel = nodes.get("not-saved"); + expect(panel?.hidden).toBe(false); + expect(panel?.textContent).toContain("Not saved"); + expect(panel?.textContent).toContain("Stale base revision"); + expect(c.terrain.layers.map((layer) => layer.id)).toEqual(["refused"]); + const id = c.saveQueue.unsent[0]?.id; + const retry = nodes.get(`retry-${id}`); + expect(retry?.tagName).toBe("button"); + expect(retry?.attributes.type).toBe("button"); + expect(nodes.get(`discard-${id}`)?.tagName).toBe("button"); + expect(c.toast).toHaveBeenCalledWith(expect.stringContaining("Stale base revision"), true); + retry?.focus(); + c.commit = async (input) => authority.commit(input); + retry?.onclick?.(); + await c.saveQueue.flush(); + expect(authority.snapshot().document.recipe.layers.map((layer) => layer.id)).toEqual([ + "refused", + ]); + expect(panel?.hidden).toBe(true); + expect(c.saving).toBe(false); + expect(dom.activeElement).toBe(nodes.get("save-status")); + }); + + it("discards one rendered refusal while retaining its sibling and editable terrain", async () => { + const { c, authority, nodes } = session(); + c.commit = async (input) => authority.commit({ ...input, baseRevision: "stale" }); + for (const id of ["first", "second"]) { + c.terrain.sculpt({ id, points: [[0, 0]], radius: 4, strength: 1 }); + await c.saveQueue.flush(); + } + const first = c.saveQueue.unsent[0]?.id; + nodes.get(`discard-${first}`)?.onclick?.(); + expect(c.saveQueue.unsent).toHaveLength(1); + expect(c.terrain.layers.map((layer) => layer.id)).toEqual(["second"]); + expect(authority.snapshot().document.recipe.layers).toHaveLength(0); + expect(nodes.get("not-saved")?.hidden).toBe(false); + }); + it("forces exactly two real server conflicts without mocking 409 responses", async () => { + const { c, authority, dom } = session(); + vi.stubGlobal("location", { href: "http://127.0.0.1:5173/terrain-editor/conflict.html" }); + vi.stubGlobal("fetch", async (_input: unknown, init: RequestInit) => { + try { + return Response.json(authority.commit(JSON.parse(String(init.body)))); + } catch (error) { + return Response.json( + { error: String(error) }, + { status: (error as { status: number }).status }, + ); + } + }); + installStaleWrites(); + // The conflict fixture counts one sculpt intent per commit, so post a real stroke. + const stroke = { + enabled: true, + id: "sculpt-conflict", + name: "Sculpt stroke", + opacity: 1, + params: { + falloff: 0.65, + jitter: 0, + points: [[0, 0]], + radius: 4, + rotation: 0, + shape: "circle", + spacing: 0.15, + strength: 1, + }, + type: "sculpt", + }; + const input = { + baseRevision: c.revision, + commands: [{ layer: stroke, op: "upsert" }], + }; + const post = () => + fetch("/terrain-editor/api/document", { method: "POST", body: JSON.stringify(input) }); + expect((await post()).status).toBe(409); + expect((await post()).status).toBe(409); + expect((await post()).status).toBe(200); + expect(dom.body.dataset.staleWriteConflicts).toBe("2"); + }); + + it.each(["editor", "editor-conflict"])("uses existing harness vocabulary in %s", (name) => { + const scenario = JSON.parse( + readFileSync(new URL(`../playtests/${name}.playtest.json`, import.meta.url), "utf8"), + ); + expect(() => validatePlaytestScenario(scenario, `${name}.playtest.json`)).not.toThrow(); + }); +}); diff --git a/packages/terrain/__tests__/forest-startup.spec.ts b/examples/strata-terrain-preview/__tests__/forest-startup.spec.ts similarity index 88% rename from packages/terrain/__tests__/forest-startup.spec.ts rename to examples/strata-terrain-preview/__tests__/forest-startup.spec.ts index 3220b7b502..ae937bfe35 100644 --- a/packages/terrain/__tests__/forest-startup.spec.ts +++ b/examples/strata-terrain-preview/__tests__/forest-startup.spec.ts @@ -3,15 +3,12 @@ import { runInNewContext } from "node:vm"; import * as three from "three"; import ts from "typescript"; import { afterEach, expect, it, vi } from "vitest"; -import { initialState } from "../../../examples/strata-terrain-preview/scripts/fixtures/kit-game/state.js"; -import * as loading from "../../../examples/strata-terrain-preview/src/render/loading.js"; +import { initialState } from "../scripts/fixtures/kit-game/state.js"; +import * as loading from "../src/render/loading.js"; // Execute the shipped fixture itself; only renderer/physics/assets are replaced at their seams. const source = readFileSync( - new URL( - "../../../examples/strata-terrain-preview/scripts/fixtures/kit-game/Forest.ts", - import.meta.url, - ), + new URL("../scripts/fixtures/kit-game/KitScene.ts", import.meta.url), "utf8", ); const code = ts.transpileModule(source, { @@ -22,6 +19,7 @@ const stand = { fir: { x: 0, z: 0 }, spawn: { x: 4, z: 0 }, edge: { x: 24, z: 10 function harness() { vi.useFakeTimers(); let covered = false; + let admit = () => {}; const body = { velocity: new three.Vector3(), moveAndSlide: vi.fn(), @@ -32,6 +30,11 @@ function harness() { world: Object.assign(new three.Group(), { dispose: vi.fn(), stats: () => ({ pendingPrewarm: 0 }), + whenSpawnReady: () => + new Promise((resolve) => { + admit = resolve; + }), + prewarmed: Promise.resolve(), readinessAt: () => ({ ready: covered, requiredCells: 1, @@ -42,7 +45,7 @@ function harness() { }), }), ground: { dispose: vi.fn() }, - water: { lakes: 1, rivers: 1 }, + water: { lakes: 1, rivers: 1, dispose: vi.fn() }, colliders: { active: 198, detach: vi.fn() }, }; const addForest = vi.fn(async () => forest); @@ -59,9 +62,12 @@ function harness() { }, CollisionShape3D: { capsule: () => ({}) }, }, - "world.js": { addForest }, - "sky.js": { forestDaylight: () => new three.Group() }, - "stand.js": { stand }, + "kit.js": { + addKit: addForest, + kitDaylight: () => new three.Group(), + forestStand: stand, + KIT: "Forest", + }, "state.js": { initialState }, "loading.js": loading, }; @@ -70,6 +76,8 @@ function harness() { setTimeout, clearTimeout, console, + performance, + Error, require(name: string) { const dependency = imports[name] ?? imports[name.split("/").at(-1) ?? ""]; if (dependency) return dependency; @@ -116,7 +124,7 @@ function harness() { update(context: typeof ctx, dt: number): void; exit(): void; }; - const Forest = exports.Forest as new () => Fixture; + const Forest = exports.KitScene as new () => Fixture; return { scene: new Forest(), ctx, @@ -125,6 +133,7 @@ function harness() { addForest, setCovered: (value = true) => { covered = value; + if (value) admit(); }, draw: () => { for (const callback of callbacks) callback(); @@ -149,6 +158,7 @@ it("keeps actual Forest controls held for coverage, then permits play after spaw expect(h.body.velocity.x).toBe(0); h.setCovered(); h.draw(); + await h.ctx.startup.hold.mock.calls[0]?.[1]; await Promise.resolve(); await Promise.resolve(); h.ctx.startup.phase = "ready"; diff --git a/examples/strata-terrain-preview/__tests__/ground-vegetation.spec.ts b/examples/strata-terrain-preview/__tests__/ground-vegetation.spec.ts new file mode 100644 index 0000000000..63a1b07652 --- /dev/null +++ b/examples/strata-terrain-preview/__tests__/ground-vegetation.spec.ts @@ -0,0 +1,76 @@ +import { Vector3 } from "three"; +import { expect, it } from "vitest"; +import { fernClump, grassClump, poppyCluster } from "../src/render/cover.js"; + +it("keeps contiguous grass, fern and flower stem vertices together in the wind", () => { + const flowers = poppyCluster(73, { u0: 0.5, v0: 0, u1: 1, v1: 0.5 }); + for (const geometry of [grassClump(73), fernClump(73), flowers.stems]) { + const position = geometry.getAttribute("position"); + const sway = geometry.getAttribute("sway"); + const weights = new Map(); + for (let i = 0; i < position.count; i++) { + const key = new Vector3().fromBufferAttribute(position, i).toArray().join(","); + const previous = weights.get(key); + if (previous !== undefined) expect(sway.getX(i), key).toBeCloseTo(previous, 6); + weights.set(key, sway.getX(i)); + } + geometry.dispose(); + } + flowers.petals.dispose(); +}); + +it("grows three distinct grass species deterministically within the original triangle budget", () => { + const shapes = []; + for (let species = 0; species < 3; species++) { + const geometry = grassClump(73, species); + const repeat = grassClump(73, species); + expect(geometry.getAttribute("position").array).toEqual(repeat.getAttribute("position").array); + expect(geometry.index?.count).toBe(252); + geometry.computeBoundingBox(); + if (!geometry.boundingBox) throw new Error("Grass has no measured bounds"); + shapes.push(geometry.boundingBox.getSize(new Vector3()).toArray()); + geometry.dispose(); + repeat.dispose(); + } + expect(new Set(shapes.map((shape) => shape.join(","))).size).toBe(3); +}); + +it("anchors the fern crown while keeping scanned fronds within the existing card budget", () => { + const geometry = fernClump(73); + const position = geometry.getAttribute("position"); + const sway = geometry.getAttribute("sway"); + expect(geometry.index?.count).toBeLessThanOrEqual(240); + for (let i = 0; i < position.count; i++) { + expect(position.getY(i)).toBeGreaterThanOrEqual(0); + expect(sway.getX(i)).toBeGreaterThanOrEqual(0); + expect(sway.getX(i)).toBeLessThanOrEqual(1); + if (position.getY(i) === 0) expect(sway.getX(i)).toBe(0); + } + geometry.dispose(); +}); + +it("keeps meadow blades slender with a soft bow and tapered tips", () => { + const point = new Vector3(); + const opposite = new Vector3(); + for (const seed of [73, 281, 991]) { + for (let species = 0; species < 3; species++) { + const geometry = grassClump(seed, species); + const position = geometry.getAttribute("position"); + for (let blade = 0; blade < 14; blade++) { + const base = blade * 12; + const width = point + .fromBufferAttribute(position, base) + .distanceTo(opposite.fromBufferAttribute(position, base + 1)); + const tipWidth = point + .fromBufferAttribute(position, base + 10) + .distanceTo(opposite.fromBufferAttribute(position, base + 11)); + const firstRise = position.getY(base + 2) - position.getY(base); + const tipRise = position.getY(base + 10) - position.getY(base + 8); + expect(width / position.getY(base + 10)).toBeLessThan(0.035); + expect(tipWidth / width).toBeLessThanOrEqual(0.03); + expect(tipRise / firstRise).toBeLessThan(0.8); + } + geometry.dispose(); + } + } +}); diff --git a/examples/strata-terrain-preview/__tests__/outdoor-shadows.spec.ts b/examples/strata-terrain-preview/__tests__/outdoor-shadows.spec.ts new file mode 100644 index 0000000000..4c3813c279 --- /dev/null +++ b/examples/strata-terrain-preview/__tests__/outdoor-shadows.spec.ts @@ -0,0 +1,27 @@ +import { VirtualShadowNode } from "@threenative/core"; +import { PerspectiveCamera } from "three"; +import { expect, it } from "vitest"; +import { BIOMES } from "../src/render/biomes.js"; +import { createOutdoorSky } from "../src/render/sky.js"; + +it.each(Object.values(BIOMES))("sets $world shadow contact bias as intended", (biome) => { + const sky = createOutdoorSky(new PerspectiveCamera(), biome); + const shadows = sky.sun.shadow.shadowNode; + expect(shadows).toBeInstanceOf(VirtualShadowNode); + const { clipExtents, mapSize, receiverPlaneBias } = (shadows as VirtualShadowNode).options; + expect(sky.sun.shadow.bias).toBe(0); + expect(sky.sun.shadow.radius).toBe(biome.sun.shadowRadius); + if (biome.world === "coastal") { + // Coastal stones showed a lit rim: one fine texel of normal bias and no footprint-wide slope bias. + expect(sky.sun.shadow.normalBias).toBe((2 * (clipExtents[0] ?? 0)) / mapSize); + expect(receiverPlaneBias).toBe(false); + } else { + // Other worlds keep their judged look until ground-level close-ups prove a change. + expect(sky.sun.shadow.normalBias).toBe(0.035); + expect(receiverPlaneBias).toBe(true); + } + shadows?.dispose(); + sky.sun.dispose(); + sky.daylight.sun.shadow.shadowNode?.dispose(); + sky.daylight.sun.dispose(); +}); diff --git a/examples/strata-terrain-preview/__tests__/placements.spec.ts b/examples/strata-terrain-preview/__tests__/placements.spec.ts new file mode 100644 index 0000000000..2f749a275b --- /dev/null +++ b/examples/strata-terrain-preview/__tests__/placements.spec.ts @@ -0,0 +1,73 @@ +import type { IPlacement } from "@threenative/terrain"; +import { describe, expect, it } from "vitest"; +import { decodePlacements, encodePlacements } from "../src/render/placements.js"; + +const placements: IPlacement[] = [ + { + id: "temperate-spruce:0", + layer: "temperate-spruce", + asset: "spruce", + position: [1, 2, 3], + rotation: 0.5, + scale: 1.25, + normal: [0, 1, 0], + alignToNormal: true, + }, + { + id: "temperate-mountain:0:outcrop", + layer: "temperate-mountain", + asset: "mountain", + position: [4, 5, 6], + rotation: 1, + scale: 2, + normal: [0, 1, 0], + alignToNormal: false, + }, + { + id: "temperate-spruce:1", + layer: "temperate-spruce", + asset: "spruce", + position: [7, 8, 9], + rotation: 0, + scale: 1, + normal: [0, 1, 0], + alignToNormal: true, + }, +]; +const counts = { spruce: 2, mountain: 1, grass: 0 }; +const canopy = new Float32Array([0, 0.25, 1, 0.5]); +const encode = () => encodePlacements({ placements, counts }, canopy); + +describe("baked placement table", () => { + it("preserves order, identities, alignment, counts and baked canopy", () => { + const loaded = decodePlacements(encode()); + expect(loaded.placements).toEqual(placements); + expect(loaded.counts).toEqual(counts); + expect(loaded.canopy).toEqual(canopy); + }); + it("rejects a truncated header or record", () => { + const bytes = encode(); + for (const length of [0, 3, 8, bytes.byteLength - 1, bytes.byteLength - 4]) { + expect(() => decodePlacements(bytes.slice(0, length))).toThrow(); + } + }); + it("rejects duplicate or out-of-range placement order indices", () => { + const bytes = encode(); + const view = new DataView(bytes); + const metadata = 4 + view.getUint32(0, true) + placements.length * 32; + view.setUint32(metadata + 4, view.getUint32(metadata, true), true); + expect(() => decodePlacements(bytes)).toThrow(); + view.setUint32(metadata + 4, placements.length, true); + expect(() => decodePlacements(bytes)).toThrow(); + }); + it("rejects nonfinite records and inconsistent counts", () => { + const bytes = encode(); + const view = new DataView(bytes); + const offset = 4 + view.getUint32(0, true); + view.setFloat32(offset, Number.NaN, true); + expect(() => decodePlacements(bytes)).toThrow(); + expect(() => + encodePlacements({ placements, counts: { ...counts, spruce: 3 } }, canopy), + ).toThrow(); + }); +}); diff --git a/packages/terrain/__tests__/prop-lod.spec.ts b/examples/strata-terrain-preview/__tests__/prop-lod.spec.ts similarity index 82% rename from packages/terrain/__tests__/prop-lod.spec.ts rename to examples/strata-terrain-preview/__tests__/prop-lod.spec.ts index af4832231f..a7c43dbd6e 100644 --- a/packages/terrain/__tests__/prop-lod.spec.ts +++ b/examples/strata-terrain-preview/__tests__/prop-lod.spec.ts @@ -1,6 +1,7 @@ import { readFileSync } from "node:fs"; import { runInNewContext } from "node:vm"; import type { IAssetLoader } from "@threenative/core"; +import type { IPlacement } from "@threenative/terrain"; import { BoxGeometry, type BufferAttribute, @@ -15,27 +16,15 @@ import { import type { MeshPhysicalNodeMaterial } from "three/webgpu"; import ts from "typescript"; import { describe, expect, it, vi } from "vitest"; -import type { WorldName } from "../../../examples/strata-terrain-preview/src/render/biomes.js"; -import { - type ICanopyComparison, - loadPack, -} from "../../../examples/strata-terrain-preview/src/render/pack.js"; -import { - createProps, - createPropsInSlices, - variantFor, -} from "../../../examples/strata-terrain-preview/src/render/props.js"; -import { defineGame } from "../../core/src/game.js"; -import { Scene, type SceneConstructor } from "../../core/src/scene.js"; -import type { IPlacement } from "../src/index.js"; +import { defineGame } from "../../../packages/core/src/game.js"; +import { Scene, type SceneConstructor } from "../../../packages/core/src/scene.js"; +import type { WorldName } from "../src/render/biomes.js"; +import { type ICanopyComparison, loadPack } from "../src/render/pack.js"; describe("canopy comparison scene lifetime", () => { const source = ts.createSourceFile( "game.ts", - readFileSync( - new URL("../../../examples/strata-terrain-preview/src/game.ts", import.meta.url), - "utf8", - ), + readFileSync(new URL("../src/game.ts", import.meta.url), "utf8"), ts.ScriptTarget.Latest, true, ); @@ -115,10 +104,7 @@ describe("canopy comparison scene lifetime", () => { describe("canopy comparison startup", () => { const code = ts.transpileModule( - readFileSync( - new URL("../../../examples/strata-terrain-preview/src/main.ts", import.meta.url), - "utf8", - ), + readFileSync(new URL("../src/main.ts", import.meta.url), "utf8"), { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }, ).outputText; async function boot(search: string) { @@ -137,7 +123,7 @@ describe("canopy comparison startup", () => { resumeScene: vi.fn(), start, }; - const pack = await import("../../../examples/strata-terrain-preview/src/render/pack.js"); + const pack = await import("../src/render/pack.js"); const imports: Record = { "./game.js": { __esModule: true, default: game }, "./render/biomes.js": { BIOMES: { forest: {}, coastal: {} } }, @@ -178,120 +164,6 @@ describe("canopy comparison startup", () => { ); }); -describe("preview prop distance bookkeeping", () => { - it("finishes the same prop work through the installed slice scheduler", async () => { - const material = new MeshBasicMaterial(); - const placements = [ - { - id: "boulder:outcrop", - asset: "boulder", - position: [2, 0, 3], - rotation: 0, - scale: 1, - normal: [0, 1, 0], - alignToNormal: false, - }, - ] as IPlacement[]; - const parts = new Map( - Array.from({ length: 5 }, (_, variant) => [ - `boulder:${variant}`, - [ - { - geometry: new BoxGeometry(1, 2, 1), - role: "stone" as const, - material, - variant, - }, - ], - ]), - ); - const materials = { - bark: material, - crown: material, - fern: material, - grass: material, - impostor: material, - needles: material, - petal: material, - pine: material, - stem: material, - stone: material, - }; - const ground = () => ({ height: 4, offset: 0 }); - const sync = createProps(placements, ground, parts, materials); - const sliced = await createPropsInSlices(placements, ground, parts, materials); - if (!sliced) throw new Error("Prop build stopped unexpectedly"); - expect([...sliced.byId.keys()]).toEqual([...sync.byId.keys()]); - expect([...sliced.byId.values()].map((entry) => entry.pose.elements)).toEqual( - [...sync.byId.values()].map((entry) => entry.pose.elements), - ); - sync.dispose(); - sliced.dispose(); - }); - - it("writes only band crossings and preserves the editor's instance slots", () => { - const material = new MeshBasicMaterial(); - const sameVariantIds = Array.from({ length: 100 }, (_, i) => `spruce:${i}`).filter( - (id) => variantFor({ id } as IPlacement, "spruce") === 0, - ); - const placements = [10, 65, 200].map((x, i) => ({ - id: sameVariantIds[i] ?? "missing-id", - asset: "spruce", - position: [x, 0, 0], - rotation: 0, - scale: 1, - normal: [0, 1, 0], - alignToNormal: false, - })) as IPlacement[]; - // All instances use variant 0, so each level has one draw. - const parts = new Map( - Array.from({ length: 5 }, (_, i) => [ - `spruce:${i}`, - [0, 1, 2].map((level) => ({ - geometry: new BoxGeometry(1, 2, 1), - material, - role: "bark" as const, - variant: i, - level, - })), - ]), - ); - const props = createProps(placements, () => ({ height: 0, offset: 0 }), parts, { - bark: material, - crown: material, - fern: material, - grass: material, - impostor: material, - needles: material, - petal: material, - pine: material, - stem: material, - stone: material, - }); - props.setLevels(new Vector3()); - const writes = props.meshes.map((mesh) => vi.spyOn(mesh, "setMatrixAt")); - const bounds = props.meshes.map((mesh) => vi.spyOn(mesh, "computeBoundingSphere")); - props.setLevels(new Vector3(1, 0, 0)); - expect(writes.reduce((n, spy) => n + spy.mock.calls.length, 0)).toBe(0); - expect(bounds.reduce((n, spy) => n + spy.mock.calls.length, 0)).toBe(0); - for (const x of [20, 100, 300, 0]) { - props.setLevels(new Vector3(x, 0, 0)); - for (const instance of props.byId.values()) { - for (const part of instance.parts) { - const position = new Vector3().setFromMatrixPosition(instance.pose); - const drawn = new Vector3().fromArray( - part.mesh.instanceMatrix.array, - part.index * 16 + 12, - ); - expect(drawn.distanceTo(position)).toBeLessThan(1e-6); - expect(part.mesh.userData.placementIds[part.index]).toBe(instance.placement.id); - } - } - } - props.dispose(); - }); -}); - describe("licensed pack characterization", () => { function present(value: T | null | undefined): T { if (value === null || value === undefined) throw new Error("Missing characterized prop value"); diff --git a/packages/terrain/__tests__/prop-streaming.spec.ts b/examples/strata-terrain-preview/__tests__/prop-streaming.spec.ts similarity index 78% rename from packages/terrain/__tests__/prop-streaming.spec.ts rename to examples/strata-terrain-preview/__tests__/prop-streaming.spec.ts index e96bb294bd..09b4f5ac33 100644 --- a/packages/terrain/__tests__/prop-streaming.spec.ts +++ b/examples/strata-terrain-preview/__tests__/prop-streaming.spec.ts @@ -1,5 +1,7 @@ +import assert from "node:assert/strict"; import { createAssetLoader } from "@threenative/core"; import { WorldCells } from "@threenative/core/world"; +import type { IPlacement } from "@threenative/terrain"; import { Box3, BoxGeometry, @@ -11,20 +13,12 @@ import { Vector3, } from "three"; import { expect, it, vi } from "vitest"; -import { - createSpawnReadiness, - spawnReadinessSnapshot, -} from "../../../examples/strata-terrain-preview/src/render/loading.js"; import { type IPropPreparationProgress, createStreamedProps, invalidatePropShadows, -} from "../../../examples/strata-terrain-preview/src/render/propStreaming.js"; -import { - createProps, - variantFor, -} from "../../../examples/strata-terrain-preview/src/render/props.js"; -import type { IPlacement } from "../src/index.js"; +} from "../src/render/propStreaming.js"; +import { DRAW_REACH, LOD_BANDS, preparePose, variantFor } from "../src/render/props.js"; it("reports an outstanding world load before the factory returns or attaches it", async () => { const placement = { @@ -71,41 +65,17 @@ it("reports an outstanding world load before the factory returns or attaches it" progress = value; }, }); + let settled = false; + void work.then(() => { + settled = true; + }); await resolving; let streamed: Awaited; try { expect(progress?.phase).toBe("world-load"); expect(progress?.bucket).toBe("horizon:true"); expect(progress?.worlds).toHaveLength(0); - vi.useFakeTimers(); - let receipt: ReturnType | undefined; - const spawn = createSpawnReadiness("Controlled spawn", 100, (reason) => { - receipt = spawnReadinessSnapshot({ - reason, - atMs: performance.now(), - world: "forest", - sceneUuid: "controlled-scene", - released: false, - gate: spawn, - stage: "streaming", - propsSettled: false, - preparation: progress, - assets: assets.progress, - startup: { phase: "collapsing", progress: 0.95, compileSettled: true, timeline: {} }, - }); - }); - const rejection = expect(spawn.promise).rejects.toThrow("admission exceeded 100 ms"); - try { - await vi.advanceTimersByTimeAsync(100); - await rejection; - expect(receipt?.gate).toEqual({ ready: false, error: "" }); - expect(receipt?.preparation.phase).toBe("world-load"); - expect(receipt?.preparation.worldCount).toBe(0); - expect(receipt?.released).toBe(false); - } finally { - spawn.cancel(); - vi.useRealTimers(); - } + expect(settled).toBe(false); release(); streamed = await work; expect(progress?.phase).toBe("complete"); @@ -113,7 +83,7 @@ it("reports an outstanding world load before the factory returns or attaches it" expect(streamed?.worlds[0]?.parent).toBeNull(); expect(streamed?.worlds[0]?.released).toBe(false); expect(progress?.prewarmedWorlds).toBe(0); - expect(receipt?.preparation.worldCount).toBe(0); + expect(settled).toBe(true); } finally { release(); streamed ??= await work; @@ -142,7 +112,6 @@ it("streams Strata's exact grounded transforms, seeded cover density and origina ); const ground = () => ({ height: 4, offset: 0 }); const materials = { grass: material } as never; - const original = createProps(placements, ground, parts, materials); const follow = { position: new Vector3(-480, 5, 0) }; const streamed = await createStreamedProps({ placements, @@ -160,26 +129,40 @@ it("streams Strata's exact grounded transforms, seeded cover density and origina expect(streamed.stats().loadedCells).toBe(0); expect(streamed.stats().residentCells).toBeGreaterThan(0); expect(streamed.byId.size).toBe(placements.length); - for (const placement of placements) + for (const placement of placements) { + const foot = parts.get(`grass:${variantFor(placement, "grass")}`)?.[0]; + assert(foot); expect(streamed.byId.get(placement.id)?.pose.elements).toEqual( - original.byId.get(placement.id)?.pose.elements, + preparePose(foot.geometry, material, placement, placement.transform, ground).matrix + .elements, ); + } for (const x of [-480, 480, -480]) { follow.position.x = x; - original.setLevels(follow.position); for (let frame = 0; frame < 150; frame++) { for (const world of streamed.worlds) world.update(); await Promise.resolve(); } expect(streamed.meshes.reduce((n, m) => n + m.count, 0)).toBe( - original.meshes.reduce((n, m) => n + m.count, 0), + placements.filter((placement, index) => { + const variantIndex = placements + .slice(0, index) + .filter( + (prior) => variantFor(prior, "grass") === variantFor(placement, "grass"), + ).length; + const seed = (Math.imul(variantIndex + 1, 2654435761) >>> 0) / 4294967296; + const reach = 28 + ((DRAW_REACH.grass ?? 0) - 28) * (1 - Math.sqrt(seed)); + const instance = streamed.byId.get(placement.id); + assert(instance); + const pose = instance.pose; + return new Vector3().setFromMatrixPosition(pose).distanceTo(follow.position) <= reach; + }).length, ); } expect(streamed.stats().evictions).toBeGreaterThan(0); expect(streamed.stats().failures).toBe(0); } finally { streamed.dispose(); - original.dispose(); } expect(streamed.worlds.every((world) => world.released)).toBe(true); }); @@ -270,18 +253,28 @@ it("retains every unlimited canopy placement and keeps prewarm pending until sce }, }); if (!streamed) throw new Error("cancelled"); + let ready = false; + void Promise.all([ + ...streamed.worlds.map((world) => world.whenSpawnReady({ x: 0, z: 0 }, 60)), + ...streamed.worlds.map((world) => world.prewarmed), + ]).then( + () => { + ready = true; + }, + () => undefined, + ); try { - expect(streamed.ready).toBe(false); + expect(ready).toBe(false); expect(preparation?.prewarmedWorlds).toBe(0); for (let frame = 0; frame < 200; frame++) { for (const world of streamed.worlds) world.update(); await Promise.resolve(); } expect(streamed.meshes.reduce((n, m) => n + m.count, 0)).toBe(placements.length); - expect(streamed.ready).toBe(false); + expect(ready).toBe(false); expect(streamed.stats().pendingPrewarm).toBeGreaterThan(0); new Group().add(...streamed.worlds); - for (let frame = 0; frame < 200 && !streamed.ready; frame++) { + for (let frame = 0; frame < 200 && !ready; frame++) { for (const world of streamed.worlds) { world.update(); world.traverse((node) => { @@ -298,7 +291,13 @@ it("retains every unlimited canopy placement and keeps prewarm pending until sce } await Promise.resolve(); } - expect(streamed.ready).toBe(true); + expect(ready).toBe(true); + expect(streamed.stats().loadedCells).toBe(streamed.stats().residentCells); + expect(streamed.stats()).toMatchObject({ + loadsInFlight: 0, + loadsQueued: 0, + admissionBacklog: 0, + }); expect(preparation?.prewarmedWorlds).toBe(streamed.worlds.length); expect(streamed.stats().pendingPrewarm).toBe(0); for (const x of [4000, 0]) { @@ -337,7 +336,6 @@ it("preserves authored LOD return hysteresis through an oscillating boundary wal })); const parts = new Map([[`boulder:${variantFor(placement, "boulder")}`, levels]]); const materials = { bark: material } as never; - const original = createProps([placement], () => ({ height: 0, offset: 0 }), parts, materials); const follow = { position: new Vector3() }; const streamed = await createStreamedProps({ placements: [placement], @@ -354,18 +352,36 @@ it("preserves authored LOD return hysteresis through an oscillating boundary wal .filter((mesh) => mesh.count > 0) .map((mesh) => Array.from(mesh.geometry.getAttribute("position").array)); try { - for (const distance of [0, 65, 500, 55, 61, 59, 61, 59, 47, 155, 140, 151, 149, 119, 47]) { + for (const [distance, level] of [ + [0, 0], + [65, 1], + [500, null], + [55, 1], + [61, 1], + [59, 1], + [61, 1], + [59, 1], + [47, 0], + [155, 2], + [140, 2], + [151, 2], + [149, 2], + [119, 1], + [47, 0], + ] as const) { follow.position.x = distance; - original.setLevels(follow.position); for (let frame = 0; frame < 100; frame++) { for (const world of streamed.worlds) world.update(); await Promise.resolve(); } - expect(active(streamed.meshes), `distance ${distance}`).toEqual(active(original.meshes)); + const chosen = level === null ? undefined : levels[level]; + if (level !== null) assert(chosen); + expect(active(streamed.meshes), `distance ${distance}`).toEqual( + chosen ? [Array.from(chosen.geometry.getAttribute("position").array)] : [], + ); } } finally { streamed.dispose(); - original.dispose(); } }); @@ -407,7 +423,6 @@ it("writes the original rendered matrices and materials for multi-part nonunifor ]), ); const materials = { bark, crown } as never; - const original = createProps(placements, () => ({ height: 0, offset: 0 }), parts, materials); const streamed = await createStreamedProps({ placements, parts, @@ -437,7 +452,14 @@ it("writes the original rendered matrices and materials for multi-part nonunifor await Promise.resolve(); } for (const material of [bark, crown]) { - const before = draws(original.meshes, material); + const before = placements.map((placement) => { + assert(placement.transform); + return new Matrix4().compose( + new Vector3().fromArray(placement.transform.position), + new Quaternion().fromArray(placement.transform.quaternion), + new Vector3().fromArray(placement.transform.scale), + ).elements; + }); const after = draws(streamed.meshes, material); expect(after).toHaveLength(placements.length); for (let i = 0; i < before.length; i++) @@ -446,7 +468,6 @@ it("writes the original rendered matrices and materials for multi-part nonunifor } } finally { streamed.dispose(); - original.dispose(); } }); @@ -547,7 +568,6 @@ it("preserves authored cutout LOD parts and distinct custom shader surfaces", as ], ]); const follow = { position: new Vector3(0, 1, 0) }; - const original = createProps(placements, () => ({ height: 0, offset: 0 }), parts, {} as never); const streamed = await createStreamedProps({ placements, parts, @@ -562,7 +582,6 @@ it("preserves authored cutout LOD parts and distinct custom shader surfaces", as try { for (const z of [0, 80, 0]) { follow.position.z = z; - original.setLevels(follow.position); for (let i = 0; i < 200; i++) { for (const w of streamed.worlds) { w.update(); @@ -588,19 +607,25 @@ it("preserves authored cutout LOD parts and distinct custom shader surfaces", as geometry: m.geometry.getAttribute("position").array.toString(), count: m.count, })); - expect(active(streamed.meshes)).toEqual(active(original.meshes)); + const expected = (parts.values().next().value ?? []) + .filter((part) => part.level === (z > LOD_BANDS.mid ? 1 : 0)) + .map((part) => ({ + material: part.material?.uuid, + geometry: part.geometry.getAttribute("position").array.toString(), + count: placements.length, + })); + expect(active(streamed.meshes)).toEqual(expected); for (const layer of [28, 27]) { const casters: InstancedMesh[] = []; for (const world of streamed.worlds) world.traverse((node) => { if (node instanceof InstancedMesh && node.layers.isEnabled(layer)) casters.push(node); }); - expect(active(casters)).toEqual(active(original.meshes.filter((mesh) => mesh.castShadow))); + expect(active(casters)).toEqual(expected); } } } finally { streamed.dispose(); - original.dispose(); } }); @@ -684,3 +709,71 @@ it("keeps streamed caster changes regional and reserves global invalidation for timer.mockRestore(); } }); + +it("settles an editor stream without caster layers when no shadow renderer exists", async () => { + const placement = { + id: "spruce:editor", + asset: "spruce", + position: [0, 0, 0], + rotation: 0, + scale: 1, + normal: [0, 1, 0], + alignToNormal: false, + } as IPlacement; + const geometry = new BoxGeometry(); + const material = new MeshBasicMaterial(); + const streamed = await createStreamedProps({ + placements: [placement], + parts: new Map([ + [ + `spruce:${variantFor(placement, "spruce")}`, + [{ geometry, material, role: "bark", variant: 0 }], + ], + ]), + materials: { bark: material } as never, + groundAt: () => ({ height: 0, offset: 0 }), + follow: { position: new Vector3() }, + size: 512, + whileCurrent: () => true, + castShadows: false, + bundles: false, + }); + assert(streamed); + let ready = false; + void Promise.all([ + ...streamed.worlds.map((world) => world.whenSpawnReady({ x: 0, z: 0 }, 60)), + ...streamed.worlds.map((world) => world.prewarmed), + ]).then( + () => { + ready = true; + }, + () => undefined, + ); + new Group().add(...streamed.worlds); + try { + for (let frame = 0; frame < 100 && !ready; frame++) { + for (const world of streamed.worlds) { + world.update(); + world.traverse((node) => { + if (!(node instanceof InstancedMesh)) return; + expect(node.layers.isEnabled(27) || node.layers.isEnabled(28)).toBe(false); + (node.onBeforeRender as (...args: unknown[]) => void)(node, null, null, null, null, null); + }); + } + await Promise.resolve(); + } + expect(ready).toBe(true); + expect(streamed.stats().loadedCells).toBe(streamed.stats().residentCells); + expect(streamed.stats()).toMatchObject({ + loadsInFlight: 0, + loadsQueued: 0, + admissionBacklog: 0, + }); + expect(streamed.meshes.reduce((count, mesh) => count + mesh.count, 0)).toBe(1); + expect(streamed.stats().failures).toBe(0); + } finally { + streamed.dispose(); + geometry.dispose(); + material.dispose(); + } +}); diff --git a/examples/strata-terrain-preview/__tests__/prop-surfaces.spec.ts b/examples/strata-terrain-preview/__tests__/prop-surfaces.spec.ts new file mode 100644 index 0000000000..18b194aaec --- /dev/null +++ b/examples/strata-terrain-preview/__tests__/prop-surfaces.spec.ts @@ -0,0 +1,282 @@ +import type { IAssetLoader } from "@threenative/core"; +import { DataTexture, LinearMipmapLinearFilter, RepeatWrapping, type Texture } from "three"; +import type { MeshStandardNodeMaterial } from "three/webgpu"; +import { describe, expect, it } from "vitest"; +import { + BIOMES as AUTHORED_BIOMES, + type IWorldBiome, + type WorldName, + alpineSnowCover, + elevationBand, + measureBiome, +} from "../src/render/biomes.js"; +import { loadPack } from "../src/render/pack.js"; +import { createPropSurfaces, createRockGround } from "../src/render/propMaterials.js"; +import { createTerrain } from "../src/render/terrain.js"; + +const BIOMES = Object.fromEntries( + Object.entries(AUTHORED_BIOMES).map(([world, biome]) => [world, measureBiome(biome, [-10, 90])]), +) as Record; + +function scalarValues(node: { traverse: (visit: (child: unknown) => void) => void }): number[] { + const values: number[] = []; + node.traverse((child) => { + const value = (child as { value?: unknown }).value; + if (typeof value === "number") values.push(value); + }); + return values; +} + +it("resolves every world's actual bands under scale and offset without sharing snapshots", () => { + for (const authored of Object.values(AUTHORED_BIOMES)) { + const heights = [10, 110]; + const first = measureBiome(authored, heights); + const snapshot = JSON.stringify(first); + heights[1] = 210; + const moved = measureBiome(authored, [525, 775]); + for (const key of Object.keys(first.heightBands) as Array) { + expect(moved.heightBands[key]).toEqual( + first.heightBands[key].map((value) => value * 2.5 + 500), + ); + } + expect(moved.snowBand).toEqual(first.snowBand.map((value) => value * 2.5 + 500)); + expect(first.elevationRange).toEqual([10, 110]); + expect(JSON.stringify(first)).toBe(snapshot); + expect(first).not.toBe(moved); + } + const alpine = measureBiome(AUTHORED_BIOMES.alpine, [0, 1000]); + const moved = measureBiome(AUTHORED_BIOMES.alpine, [500, 3000]); + expect(scalarValues(alpineSnowCover(alpine))).toEqual(expect.arrayContaining([600, 850])); + expect(scalarValues(alpineSnowCover(moved))).toEqual(expect.arrayContaining([2000, 2625])); +}); + +it.each([ + { heights: [] }, + { heights: [0, Number.NaN] }, + { heights: [0, Number.POSITIVE_INFINITY] }, + { heights: [0, Number.MAX_VALUE] }, +])("rejects malformed elevation samples %j", ({ heights }) => { + expect(() => measureBiome(AUTHORED_BIOMES.forest, heights)).toThrow( + /elevation.*(?:empty|nonfinite)/u, + ); +}); + +it("keeps a flat world's actual range and emits finite constant elevation and snow graphs", () => { + const flat = measureBiome(AUTHORED_BIOMES.alpine, [123, 123, 123]); + expect(flat.elevationRange).toEqual([123, 123]); + expect(flat.snowBand).toEqual([123, 123]); + expect(scalarValues(elevationBand(flat, flat.snowBand))).toEqual([0]); + expect(scalarValues(alpineSnowCover(flat)).every(Number.isFinite)).toBe(true); + expect(scalarValues(alpineSnowCover(flat))).not.toContain(123); +}); + +it.each(["alpine", "tundra"] as const)( + "requires actual elevations before loading %s stones", + async (world) => { + await expect(loadPack(undefined, world)).rejects.toThrow(/measured terrain elevations/u); + }, +); + +/** Every texture a TSL node tree samples, so a test can see whether a map survived. */ +function sampledTextures(node: unknown): Set { + const found = new Set(); + (node as { traverse?: (visit: (value: unknown) => void) => void } | null)?.traverse?.((value) => { + const texture = (value as { value?: Texture }).value; + if (texture?.isTexture) found.add(texture); + }); + return found; +} + +describe("procedural foliage lighting", () => { + const image = (path: string) => { + const texture = new DataTexture(new Uint8Array([80, 150, 50, 255]), 1, 1); + texture.userData.path = path; + return texture; + }; + const sky = image("sky.hdr"); + const assets = { + resolve: async () => [], + model: async () => { + throw new Error("Optional model absent"); + }, + texture: async (path: string) => image(path), + } as unknown as IAssetLoader; + // Every role the procedural starter draws as an alpha-cut, double-sided surface. An imported pine + // brings its own material; a machine without the licensed pack draws these, and without an + // image-based light a needle card is a flat paddle — the paperboard the engine warns about. + const cutouts = ["crown", "needles", "pine", "impostor", "fern", "petal"] as const; + + it("gives every cutout surface the sky light", async () => { + const surfaces = await createPropSurfaces(assets, undefined, undefined, sky); + try { + for (const role of cutouts) { + const material = surfaces.materials[role] as { envMap?: unknown; envMapIntensity?: number }; + expect(material.envMap, role).toBe(sky); + expect(material.envMapIntensity, role).toBeCloseTo(1.13, 5); + } + } finally { + surfaces.dispose(); + } + }); + + it("keeps the crown's needle relief in its normal", async () => { + const surfaces = await createPropSurfaces(assets); + try { + const crown = surfaces.materials.crown as MeshStandardNodeMaterial; + // The bent crown normal is the geometry's own; the relief bump perturbs it, so the two are one + // node. A bare geometry normal drops the relief and the needles shade as flat card. + const paths = [...sampledTextures(crown.normalNode)].map((texture) => texture.userData.path); + expect(paths).toContain("needle-surface.png"); + } finally { + surfaces.dispose(); + } + }); + + it("does not paint a ground-colour skirt on tundra stone", async () => { + expect( + await createRockGround( + assets, + { heights: [0, 0, 0, 0], resolution: 2, size: 8 }, + BIOMES.tundra, + ), + ).toBeUndefined(); + }); + + it("gives tundra boulders textured snow and sky light", async () => { + const surfaces = await createPropSurfaces(assets, undefined, BIOMES.tundra, sky); + try { + const stone = surfaces.materials.stone as MeshStandardNodeMaterial; + const paths = [...sampledTextures(stone.colorNode)].map((texture) => texture.userData.path); + expect(paths).toContain(BIOMES.tundra.maps.snow.diffuse); + expect(stone.envMap).toBe(sky); + expect(paths).toContain("lichen_rock/lichen_rock_diff_512.jpg"); + expect(sampledTextures(stone.emissiveNode).size).toBeGreaterThan(0); + expect(stone.metalness).toBe(0); + expect(stone.roughness).toBeGreaterThanOrEqual(0.9); + } finally { + surfaces.dispose(); + } + }); + + it.each(["coastal", "desert"] as const)( + "binds dry stone colour, GL normal and packed AO/roughness for %s", + async (world) => { + const requests: { path: string; data?: boolean; wrap?: number }[] = []; + const loader = { + ...assets, + texture: async (path: string, options?: { data?: boolean; wrap?: number }) => { + requests.push({ path, data: options?.data, wrap: options?.wrap }); + return image(path); + }, + } as unknown as IAssetLoader; + const surfaces = await createPropSurfaces(loader, undefined, BIOMES[world], sky); + try { + const stone = surfaces.materials.stone as MeshStandardNodeMaterial; + const paths = (node: unknown) => + [...sampledTextures(node)].map((texture) => texture.userData.path); + expect(paths(stone.colorNode)).toContain("rock_boulder_dry/rock_boulder_dry_diff_512.jpg"); + expect(paths(stone.normalNode)).toContain( + "rock_boulder_dry/rock_boulder_dry_nor_gl_512.jpg", + ); + expect(paths(stone.roughnessNode)).toContain( + "rock_boulder_dry/rock_boulder_dry_arm_512.jpg", + ); + expect(paths(stone.aoNode)).toContain("rock_boulder_dry/rock_boulder_dry_arm_512.jpg"); + expect(requests.filter(({ path }) => path.includes("rock_boulder_dry"))).toEqual([ + { + path: "rock_boulder_dry/rock_boulder_dry_diff_512.jpg", + data: false, + wrap: RepeatWrapping, + }, + { + path: "rock_boulder_dry/rock_boulder_dry_nor_gl_512.jpg", + data: true, + wrap: RepeatWrapping, + }, + { + path: "rock_boulder_dry/rock_boulder_dry_arm_512.jpg", + data: true, + wrap: RepeatWrapping, + }, + ]); + for (const [node, channel] of [ + [stone.roughnessNode, "y"], + [stone.aoNode, "x"], + ] as const) { + const channels: string[] = []; + ( + node as unknown as { + traverse: (visit: (value: { components?: string }) => void) => void; + } + ).traverse((value) => { + if (value.components) channels.push(value.components); + }); + expect(channels).toContain(channel); + } + for (const texture of sampledTextures(stone.colorNode)) { + expect(texture.minFilter).toBe(LinearMipmapLinearFilter); + expect(texture.anisotropy).toBe(8); + } + expect( + requests.some( + ({ path }) => path.includes("mossy_rock") || path.endsWith("cliff_side_rough_1k.jpg"), + ), + ).toBe(false); + expect(paths(stone.emissiveNode)).toContain( + "rock_boulder_dry/rock_boulder_dry_diff_512.jpg", + ); + expect(stone.envMap).toBeNull(); + } finally { + surfaces.dispose(); + } + }, + ); + + it.each(["forest", "alpine", "tundra"] as const)( + "repeats the retained stone maps in %s", + async (world) => { + const requests: { path: string; wrap?: number }[] = []; + const loader = { + ...assets, + texture: async (path: string, options?: { wrap?: number }) => { + requests.push({ path, wrap: options?.wrap }); + return image(path); + }, + } as unknown as IAssetLoader; + const surfaces = await createPropSurfaces(loader, undefined, BIOMES[world]); + try { + for (const slot of ["stone.albedo", "stone.normal"] as const) { + const path = surfaces.inputs[slot]?.userData.path; + expect(requests.find((request) => request.path === path)?.wrap).toBe(RepeatWrapping); + } + } finally { + surfaces.dispose(); + } + }, + ); +}); + +it("continues tundra terrain normals through the first horizon collar ring", () => { + const resolution = 9; + const { mesh } = createTerrain( + { + size: 512, + resolution, + heights: Array(resolution ** 2).fill(30), + colors: Array(resolution ** 2 * 3).fill(1), + waterLevel: null, + }, + undefined, + BIOMES.tundra, + ); + try { + const horizon = mesh.children[0] as import("three").Mesh; + const normals = horizon.geometry.getAttribute("normal"); + const perimeter = (resolution - 1) * 4; + for (let vertex = 0; vertex < perimeter; vertex++) + expect(normals.getY(perimeter + vertex)).toBeGreaterThan(0.999); + } finally { + mesh.geometry.dispose(); + (mesh.material as import("three").Material).dispose(); + } +}); diff --git a/examples/strata-terrain-preview/__tests__/prop-texture-preparation.spec.ts b/examples/strata-terrain-preview/__tests__/prop-texture-preparation.spec.ts new file mode 100644 index 0000000000..b25256402d --- /dev/null +++ b/examples/strata-terrain-preview/__tests__/prop-texture-preparation.spec.ts @@ -0,0 +1,181 @@ +import type { ICtx } from "@threenative/core"; +import { + CompressedTexture, + ExternalTexture, + MeshStandardMaterial, + RGBA_BPTC_Format, + Texture, +} from "three"; +import { texture } from "three/tsl"; +import { MeshPhysicalNodeMaterial } from "three/webgpu"; +import { expect, it, vi } from "vitest"; +import { preparePropTextures } from "../src/render/texturePreparation.js"; + +function compressed() { + const value = new CompressedTexture( + [{ width: 4, height: 4, data: new Uint8Array(16) }], + 4, + 4, + RGBA_BPTC_Format, + ); + value.needsUpdate = true; + return value; +} + +function replacement() { + return new ExternalTexture({} as never); +} + +it("gates actual ordinary and TSL texture identities without taking their ownership", async () => { + const map = compressed(); + const normal = compressed(); + const nodeMap = compressed(); + const ordinary = new MeshStandardMaterial({ map, normalMap: normal }); + const nodes = new MeshPhysicalNodeMaterial({ map }); + nodes.colorNode = texture(nodeMap).rgb; + const disposed = vi.spyOn(map, "dispose"); + let release = () => {}; + const gate = new Promise((resolve) => { + release = () => resolve(3); + }); + const prepare = vi.fn((_textures: Iterable, _signal?: AbortSignal) => gate); + let completed = false; + const pending = preparePropTextures({ prepareTextures: prepare } as unknown as ICtx["renderer"], [ + ordinary, + nodes, + ordinary, + ]).then(() => { + completed = true; + }); + await Promise.resolve(); + expect(new Set(prepare.mock.calls[0]?.[0])).toEqual(new Set([map, normal, nodeMap])); + expect(completed).toBe(false); + release(); + await pending; + expect(disposed).not.toHaveBeenCalled(); +}); + +it("forwards scene cancellation and preserves preparation failure", async () => { + const controller = new AbortController(); + const prepare = vi.fn((_textures: Iterable, signal: AbortSignal) => { + expect(signal).toBe(controller.signal); + return Promise.reject(new Error("upload failed")); + }); + await expect( + preparePropTextures( + { prepareTextures: prepare } as unknown as ICtx["renderer"], + [new MeshStandardMaterial({ map: compressed() })], + controller.signal, + ), + ).rejects.toThrow("upload failed"); +}); + +it("snapshots a generator once and replaces every material map and TSL node", async () => { + const map = compressed(); + const normal = compressed(); + const nodeMap = compressed(); + const ordinary = new MeshStandardMaterial({ map, normalMap: normal }); + const nodes = new MeshPhysicalNodeMaterial({ map }); + const tslNode = texture(nodeMap); + nodes.colorNode = tslNode.rgb; + const prepared = new Map([ + [map, replacement()], + [normal, replacement()], + [nodeMap, replacement()], + ]); + function* materials() { + yield ordinary; + yield nodes; + yield ordinary; + } + const prepare = vi.fn( + ( + textures: Iterable, + _signal?: AbortSignal, + _onProgress?: (completed: number) => void, + replacements?: Map, + ) => { + for (const value of textures) replacements?.set(value, prepared.get(value) as never); + return Promise.resolve(prepared.size); + }, + ); + const completed = await preparePropTextures( + { prepareTextures: prepare } as unknown as ICtx["renderer"], + materials(), + ); + expect(completed).toBe(3); + expect(ordinary.map).toBe(prepared.get(map)); + expect(ordinary.normalMap).toBe(prepared.get(normal)); + expect(nodes.map).toBe(prepared.get(map)); + expect(tslNode.value).toBe(prepared.get(nodeMap)); +}); + +it("reports only distinct compressed replacements applied after successful preparation", async () => { + const original = compressed(); + const unchanged = compressed(); + const invalid = compressed(); + const unused = compressed(); + const jpeg = new Texture(); + const ready = replacement(); + const material = new MeshStandardMaterial({ + map: original, + normalMap: unchanged, + alphaMap: invalid, + aoMap: jpeg, + }); + const nodes = new MeshPhysicalNodeMaterial(); + const sampled = texture(original); + nodes.colorNode = sampled.rgb; + const reported = vi.fn(); + let finish = () => {}; + const prepare = vi.fn( + async ( + _textures: Iterable, + _signal?: AbortSignal, + _progress?: (completed: number) => void, + replacements?: Map, + ) => { + replacements?.set(original, ready); + replacements?.set(unchanged, unchanged); + replacements?.set(invalid, {} as Texture); + replacements?.set(unused, replacement()); + replacements?.set(jpeg, replacement()); + await new Promise((resolve) => { + finish = resolve; + }); + return 5; + }, + ); + const pending = preparePropTextures( + { prepareTextures: prepare } as unknown as ICtx["renderer"], + [material, nodes], + undefined, + undefined, + reported, + ); + expect(reported).not.toHaveBeenCalled(); + finish(); + expect(await pending).toBe(5); + expect(material.map).toBe(ready); + expect(sampled.value).toBe(ready); + expect(material.alphaMap).toBe(invalid); + expect(reported).toHaveBeenCalledExactlyOnceWith(1); +}); + +it("does not report compressed completion when preparation rejects", async () => { + const reported = vi.fn(); + await expect( + preparePropTextures( + { + prepareTextures: async () => { + throw new Error("upload failed"); + }, + } as unknown as ICtx["renderer"], + [new MeshStandardMaterial({ map: compressed() })], + undefined, + undefined, + reported, + ), + ).rejects.toThrow("upload failed"); + expect(reported).not.toHaveBeenCalled(); +}); diff --git a/examples/strata-terrain-preview/__tests__/rocks.spec.ts b/examples/strata-terrain-preview/__tests__/rocks.spec.ts new file mode 100644 index 0000000000..1a0592a1de --- /dev/null +++ b/examples/strata-terrain-preview/__tests__/rocks.spec.ts @@ -0,0 +1,141 @@ +import type { IPlacement } from "@threenative/terrain"; +import { MeshStandardMaterial, Vector3 } from "three"; +import { describe, expect, it } from "vitest"; +import { boulder } from "../src/render/cover.js"; +import { buildPropVariants, preparePose, variantFor } from "../src/render/props.js"; + +describe("weathered rock geometry", () => { + it("retains the committed river-stone and boulder variant selection", () => { + const variants = buildPropVariants(); + try { + for (const asset of ["boulder", "riverrock"]) { + const choices = Array.from({ length: 30 }, (_, id) => + variantFor({ id: `${asset}:${id}` } as IPlacement, asset), + ); + expect(new Set(choices).size).toBe(asset === "boulder" ? 3 : 1); + for (const choice of choices) expect(variants.get(`${asset}:${choice}`)).toHaveLength(1); + expect(variantFor({ id: `${asset}:7` } as IPlacement, asset)).toBe(choices[7]); + } + } finally { + for (const parts of variants.values()) for (const part of parts) part.geometry.dispose(); + } + }); + it("buries the flat footprint below sloping ground by a margin", () => { + const rock = boulder(0); + const material = new MeshStandardMaterial(); + try { + const placement = { + id: "boulder:0", + asset: "boulder", + position: [0, 0, 0], + normal: [0, 1, 0], + scale: 1, + rotation: 0, + alignToNormal: false, + } as IPlacement; + const ground = (_placement: IPlacement, at: [number, number, number]) => ({ + height: at[0] * 0.7, + offset: 0, + }); + const { matrix } = preparePose(rock, material, placement, undefined, ground); + const positions = rock.getAttribute("position"); + const base = rock.boundingBox?.min.y; + const point = new Vector3(); + for (let i = 0; i < positions.count; i++) { + if (positions.getY(i) !== base) continue; + point.fromBufferAttribute(positions, i).applyMatrix4(matrix); + expect(point.y - ground(placement, point.toArray()).height).toBeLessThanOrEqual(-0.05); + } + } finally { + rock.dispose(); + material.dispose(); + } + }); + + it("retains the committed seed-zero scale envelope", () => { + const rock = boulder(0); + try { + const size = rock.boundingBox?.getSize(new Vector3()); + expect(size?.x).toBeCloseTo(3.495191, 4); + expect(size?.y).toBeCloseTo(2.586079, 4); + expect(size?.z).toBeCloseTo(4.756217, 4); + } finally { + rock.dispose(); + } + }); + + it("repeats a seed and gives six seeds distinct silhouettes", () => { + const rocks = Array.from({ length: 6 }, (_, seed) => boulder(seed)); + const repeat = boulder(0); + try { + const positions = rocks.map((rock) => Array.from(rock.getAttribute("position").array)); + expect(Array.from(repeat.getAttribute("position").array)).toEqual(positions[0]); + expect(new Set(positions.map((values) => JSON.stringify(values))).size).toBe(6); + } finally { + repeat.dispose(); + for (const rock of rocks) rock.dispose(); + } + }); + + it.each([0, 1, 2, 3, 4, 5])("seed %i softens exposed fracture normals", (seed) => { + const rock = boulder(seed); + try { + const position = rock.getAttribute("position"); + const normal = rock.getAttribute("normal"); + const point = new Vector3(); + // Exposed fracture edges interpolate through a bevel rather than a perpendicular jump. + const neighbour = new Vector3(); + for (let i = 0; i < (rock.index?.count ?? 0); i += 3) { + for (let edge = 0; edge < 3; edge++) { + const a = rock.index?.getX(i + edge) ?? 0; + const b = rock.index?.getX(i + ((edge + 1) % 3)) ?? 0; + if (position.getY(a) < 0 || position.getY(b) < 0) continue; + expect( + point.fromBufferAttribute(normal, a).dot(neighbour.fromBufferAttribute(normal, b)), + ).toBeGreaterThan(0.25); + } + } + } finally { + rock.dispose(); + } + }); + + it.each([0, 1, 2, 3, 4, 5])( + "seed %i shares vertices, smooth normals and a flat base within budget", + (seed) => { + const rock = boulder(seed); + try { + const position = rock.getAttribute("position"); + const normal = rock.getAttribute("normal"); + expect(rock.index).not.toBeNull(); + const triangles = (rock.index?.count ?? position.count) / 3; + expect(triangles).toBe(500); + expect(position.count).toBeLessThan(triangles); + const point = new Vector3(); + for (let i = 0; i < position.count; i++) { + expect(point.fromBufferAttribute(position, i).toArray().every(Number.isFinite)).toBe( + true, + ); + expect(point.fromBufferAttribute(normal, i).length()).toBeCloseTo(1, 5); + } + rock.computeBoundingBox(); + const box = rock.boundingBox; + if (!box) throw new Error("Rock has no measured bounds"); + const size = box.getSize(point); + expect(size.x).toBeGreaterThan(2.4); + expect(size.x).toBeLessThan(5); + expect(size.z).toBeGreaterThan(2.4); + expect(size.z).toBeLessThan(5); + expect(size.y).toBeGreaterThan(1.3); + expect(size.y).toBeLessThan(2.8); + const base = Array.from({ length: position.count }, (_, i) => position.getY(i)).filter( + (y) => y <= box.min.y + 1e-5, + ); + expect(base.length).toBeGreaterThan(6); + expect(Math.max(...base) - Math.min(...base)).toBeLessThan(1e-6); + } finally { + rock.dispose(); + } + }, + ); +}); diff --git a/examples/strata-terrain-preview/__tests__/strata-loading.spec.ts b/examples/strata-terrain-preview/__tests__/strata-loading.spec.ts new file mode 100644 index 0000000000..be9461838c --- /dev/null +++ b/examples/strata-terrain-preview/__tests__/strata-loading.spec.ts @@ -0,0 +1,652 @@ +import { readFileSync } from "node:fs"; +import { runInNewContext } from "node:vm"; +import { + type CanvasTexture, + type Mesh, + type MeshBasicMaterial, + Object3D, + OrthographicCamera, + PerspectiveCamera, + Scene, + Vector3, +} from "three"; +import ts from "typescript"; +import { expect, it, vi } from "vitest"; +import { createLoadingScreen } from "../src/render/loading.js"; + +const GAME_URL = new URL("../src/game.ts", import.meta.url); +const GATE_NAMES = [ + "deadlineAt", + "spawnReady", + "spawnError", + "spawnSettled", + "capturingSpawnFailure", + "resolveSpawn", + "rejectSpawn", + "spawnPromise", + "settleSpawn", + "failSpawn", + "spawnTimer", +] as const; + +/** Pull named declarations straight out of the production `enter()` so the tests drive real code. */ +function gameDeclarations(names: readonly string[]): string { + const source = ts.createSourceFile( + "game.ts", + readFileSync(GAME_URL, "utf8"), + ts.ScriptTarget.Latest, + true, + ); + const wanted = new Set(names); + const found = new Map(); + function visit(node: ts.Node): void { + if (ts.isVariableDeclaration(node)) { + const name = node.name.getText(source); + if (wanted.has(name)) found.set(name, node.parent.parent.getText(source)); + } else if (ts.isFunctionDeclaration(node) && node.name !== undefined) { + const name = node.name.getText(source); + if (wanted.has(name)) found.set(name, node.getText(source)); + } + ts.forEachChild(node, visit); + } + visit(source); + return names + .map((name) => { + const text = found.get(name); + if (text === undefined) throw new Error(`Missing production declaration ${name}`); + return text; + }) + .join("\n"); +} + +interface ISpawnGate { + readonly spawn: Promise; + readonly state: { worldReady: boolean; loadingError: string }; + error(): string; + fail(reason: unknown): void; + ready(): boolean; + settle(error?: Error): void; +} + +/** Run the production spawn gate in a fresh context; `capture` receives each failure receipt. */ +function spawnGate(capture: (reason: string) => void = () => undefined): ISpawnGate { + vi.useFakeTimers(); + const state = { worldReady: false, loadingError: "" }; + const scope = { + setTimeout, + clearTimeout, + performance, + Error, + captureSpawnFailure: capture, + spawnState: state, + ctx: { + state: { + set: (value: { worldReady: boolean; loadingError: string }) => Object.assign(state, value), + }, + }, + exports: {} as { Gate?: new () => ISpawnGate }, + }; + runInNewContext( + ts.transpileModule( + `export class Gate { + constructor() { + ${gameDeclarations(GATE_NAMES)} + this.settle = settleSpawn; + this.fail = failSpawn; + this.spawn = spawnPromise; + this.ready = () => spawnReady; + this.error = () => spawnError; + this.state = spawnState; + } + }`, + { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }, + ).outputText, + scope, + ); + if (scope.exports.Gate === undefined) throw new Error("Production spawn gate did not load"); + return new scope.exports.Gate(); +} + +function host(ready: Promise) { + return { + camera: new PerspectiveCamera(), + scene: new Scene(), + renderer: { compileAsync: vi.fn(async () => undefined) }, + canvasLayer: { + scene: new Scene(), + camera: new OrthographicCamera(-400, 400, 300, -300, 0, 2), + opaque: false, + keepWorldRendering: false, + renderWorldDuringStartup: true, + }, + startup: { progress: 0.5, whenReady: () => ready }, + }; +} + +it.each(["attached", "cancelled", "released"] as const)( + "defers hidden world draws until prepared streams attach: %s", + async (outcome) => { + const declarations = gameDeclarations(["buildProps"]); + const controllerHost = host(new Promise(() => undefined)); + controllerHost.canvasLayer.opaque = true; + controllerHost.canvasLayer.renderWorldDuringStartup = false; + let admit: () => void = () => undefined; + let draw: () => void = () => undefined; + const world = { + prewarmed: new Promise((resolve) => { + draw = resolve; + }), + update: () => undefined, + whenSpawnReady: vi.fn( + () => + new Promise<{ requiredCells: number; loadedCells: number }>((resolve) => { + admit = () => resolve({ requiredCells: 1, loadedCells: 1 }); + }), + ), + }; + const attachments: boolean[] = []; + let entered: () => void = () => undefined; + const preparing = new Promise((resolve) => { + entered = resolve; + }); + let finish: () => void = () => undefined; + const prepared = new Promise((resolve) => { + finish = resolve; + }); + let disposed = false; + const parts = new Map(); + const scope = { + exports: {} as { Controller?: new () => { run(): Promise } }, + performance, + deadlineAt: performance.now() + 120_000, + settleSpawn: vi.fn(), + spawnSettled: false, + spawnCells: { required: 0, loaded: 0 }, + failSpawn: () => undefined, + ctx: { + ...controllerHost, + assets: undefined, + state: { getState: () => ({}), set: vi.fn() }, + entities: { add: () => undefined }, + add: () => { + attachments.push(controllerHost.canvasLayer.keepWorldRendering); + }, + }, + released: false, + preparedDispose: undefined, + preparedLodBaseSpread: 0, + preparedLevelsWithoutSolid: 0, + surfacesDispose: undefined, + propsStage: "", + completedPropsWork: 0, + preparation: undefined, + props: undefined, + propParts: parts, + world: "forest", + data: { size: 512 }, + spawn: { x: 0, y: 0, z: 0 }, + biome: {}, + fallbackSaplingHeight: undefined, + scatter: { placements: [], counts: {} }, + groundAt: () => ({ height: 0, offset: 0 }), + textureUploads: { signal: new AbortController().signal }, + PerspectiveCamera, + OrthographicCamera, + loadPreparedProps: async () => ({ parts, dispose() {} }), + loadPack: async () => ({ parts, dispose() {} }), + buildPropVariants: () => parts, + createPropSurfaces: async () => ({ materials: {}, dispose() {} }), + preparePropTextures: async ( + _renderer: unknown, + _materials: unknown, + _signal: unknown, + progress: (completed: number) => void, + ) => { + progress(1); + progress(2); + }, + createStreamedProps: async (options: { onProgress: (progress: object) => void }) => { + for (const [phase, added] of [ + ["grounding", 0], + ["grounding", 50], + ["grounding", 50], + ["models", 0], + ["models", 3], + ] as const) + options.onProgress({ phase, bucket: "trees", added, total: 100 }); + entered(); + await prepared; + return outcome === "cancelled" + ? undefined + : { + worlds: [world], + byId: new Map(), + meshes: [], + dispose: () => { + disposed = true; + }, + }; + }, + }; + runInNewContext( + ts.transpileModule( + `export class Controller { #surfaces; async run() { ${declarations}; await buildProps(); } }`, + { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }, + ).outputText, + scope, + ); + if (!scope.exports.Controller) + throw new Error("Production preparation controller did not load"); + const work = new scope.exports.Controller().run(); + await Promise.race([ + preparing, + work.then(() => { + throw Error("preparation did not enter"); + }), + ]); + try { + expect(scope.completedPropsWork).toBe(55); + expect(controllerHost.canvasLayer.keepWorldRendering).toBe(false); + expect(attachments).toEqual([]); + } finally { + scope.released = outcome === "released"; + finish(); + await work; + } + expect(attachments).toEqual(outcome === "attached" ? [false] : []); + expect(controllerHost.canvasLayer.keepWorldRendering).toBe(outcome === "attached"); + expect(disposed).toBe(outcome === "released"); + expect(scope.settleSpawn).not.toHaveBeenCalled(); + expect(scope.spawnCells).toEqual({ required: 0, loaded: 0 }); + draw(); + for (let turn = 0; turn < 5; turn++) await Promise.resolve(); + expect(scope.settleSpawn).not.toHaveBeenCalled(); + admit(); + for (let turn = 0; turn < 5; turn++) await Promise.resolve(); + expect(scope.settleSpawn).toHaveBeenCalledTimes(outcome === "attached" ? 1 : 0); + if (outcome === "attached") { + expect(scope.spawnCells).toEqual({ required: 1, loaded: 1 }); + expect(scope.ctx.state.set).not.toHaveBeenCalled(); + expect(world.whenSpawnReady).toHaveBeenCalledWith(scope.spawn, 60, expect.any(Number)); + } + }, +); + +it.each(["世界", "\u0000", "\uD800"])( + "bounds failure receipts for %j without retaining scene objects or resident key arrays", + (text) => { + const error = vi.fn(); + runInNewContext( + ts.transpileModule( + `${gameDeclarations(["captureSpawnFailure"])}; captureSpawnFailure(reason);`, + { compilerOptions: { target: ts.ScriptTarget.ES2022 } }, + ).outputText, + { + console: { error }, + reason: text.repeat(10_000), + world: "forest", + propsStage: "streaming", + preparation: { phase: "world-load" }, + }, + ); + expect(error).toHaveBeenCalledTimes(1); + const encoded = error.mock.calls[0]?.[0] as string; + expect(Buffer.byteLength(encoded)).toBeLessThan(2048); + const receipt = JSON.parse(encoded.slice("TN_STRATA_SPAWN_FAILURE ".length)); + expect(receipt).toEqual({ + reason: text.repeat(10_000).slice(0, 256), + world: "forest", + stage: "world-load", + }); + }, +); + +it("uploads the status canvas only when its displayed text changes", async () => { + const drawn: string[] = []; + const canvas = { + width: 0, + height: 0, + getContext: () => ({ + clearRect: () => undefined, + fillText: (text: string) => drawn.push(text), + }), + }; + vi.stubGlobal("document", { createElement: () => canvas }); + let reject: (reason: Error) => void = () => undefined; + const source = host( + new Promise((_resolve, fail) => { + reject = fail; + }), + ); + const screen = createLoadingScreen(source); + try { + const status = source.canvasLayer.scene.children[3] as Mesh; + const texture = status.material.map as CanvasTexture; + source.startup.progress = 0.95; + screen.update(); + const version = texture.version; + for (let frame = 0; frame < 100; frame += 1) screen.update(); + source.startup.progress = 0.951; + screen.update(); + expect(texture.version).toBe(version); + expect(drawn).toEqual(["0%", "95%"]); + source.startup.progress = 0.96; + screen.update(); + expect(texture.version).toBe(version + 1); + expect(drawn).toEqual(["0%", "95%", "96%"]); + reject(new Error("Forest spawn: missing cell")); + await Promise.resolve(); + screen.update(); + expect(texture.version).toBe(version + 2); + expect(drawn.at(-1)).toBe("Forest spawn: missing cell"); + expect(source.canvasLayer.opaque).toBe(true); + } finally { + screen.finish(); + vi.unstubAllGlobals(); + } +}); + +it.each([false, true])( + "keeps hidden world draws disabled during admission and restores previous flag %s", + (previous) => { + const source = host(new Promise(() => undefined)); + source.canvasLayer.keepWorldRendering = previous; + source.canvasLayer.renderWorldDuringStartup = !previous; + const screen = createLoadingScreen({ ...source, keepWorldRendering: false }); + try { + expect(source.canvasLayer.opaque).toBe(true); + expect(source.canvasLayer.keepWorldRendering).toBe(false); + expect(source.canvasLayer.renderWorldDuringStartup).toBe(false); + screen.update(); + expect(source.canvasLayer.keepWorldRendering).toBe(false); + } finally { + screen.finish(); + } + expect(source.canvasLayer.opaque).toBe(false); + expect(source.canvasLayer.keepWorldRendering).toBe(previous); + expect(source.canvasLayer.renderWorldDuringStartup).toBe(!previous); + expect(source.canvasLayer.scene.children).toHaveLength(0); + }, +); + +it("keeps the curtain and names a failed spawn even after framework readiness fails open", async () => { + let reject: (reason: Error) => void = () => undefined; + const source = host( + new Promise((_resolve, fail) => { + reject = fail; + }), + ); + const screen = createLoadingScreen(source); + reject(new Error("Spawn terrain: missing tile 0:0")); + await Promise.resolve(); + await Promise.resolve(); + screen.update(); + expect(source.canvasLayer.opaque).toBe(true); + expect(source.canvasLayer.keepWorldRendering).toBe(false); + const backdrop = source.canvasLayer.scene.children[0] as Mesh; + expect(backdrop.userData.loadingError).toBe("Spawn terrain: missing tile 0:0"); + const fill = source.canvasLayer.scene.children[2] as Mesh; + expect((fill.material as MeshBasicMaterial).color.getHex()).toBe(0xe36b5c); + screen.finish(); + expect(source.canvasLayer.scene.children).toHaveLength(0); +}); + +it("restores both prior draw flags when opted-out texture preparation rejects", async () => { + let reject: (reason: Error) => void = () => undefined; + const source = host( + new Promise((_done, fail) => { + reject = fail; + }), + ); + source.canvasLayer.keepWorldRendering = true; + const screen = createLoadingScreen({ ...source, keepWorldRendering: false }); + try { + expect(source.canvasLayer.keepWorldRendering).toBe(false); + expect(source.canvasLayer.renderWorldDuringStartup).toBe(false); + reject(new Error("Texture preparation failed")); + await Promise.resolve(); + await Promise.resolve(); + expect(source.canvasLayer.opaque).toBe(true); + expect(source.canvasLayer.keepWorldRendering).toBe(true); + expect(source.canvasLayer.renderWorldDuringStartup).toBe(true); + } finally { + screen.finish(); + } +}); + +it("releases its layer only after the combined spawn and startup promise resolves", async () => { + let resolve: () => void = () => undefined; + const source = host( + new Promise((done) => { + resolve = done; + }), + ); + const screen = createLoadingScreen(source); + screen.update(); + expect(source.canvasLayer.opaque).toBe(true); + resolve(); + await Promise.resolve(); + await Promise.resolve(); + expect(source.canvasLayer.opaque).toBe(false); + expect(source.canvasLayer.scene.children).toHaveLength(0); +}); + +it("latches playable coverage so later spawn eviction never disables controls", async () => { + const gate = spawnGate(); + let settled = false; + void gate.spawn.then( + () => { + settled = true; + }, + () => { + settled = true; + }, + ); + expect(gate.ready()).toBe(false); + await Promise.resolve(); + expect(settled).toBe(false); + gate.settle(); + await gate.spawn; + gate.fail("late failure"); + expect(gate.ready()).toBe(true); + expect(gate.error()).toBe(""); + gate.settle(new Error("Strata spawn cancelled")); + vi.useRealTimers(); +}); + +it("rejects a timed out spawn and never treats later coverage as playable", async () => { + const gate = spawnGate(); + const rejection = expect(gate.spawn).rejects.toThrow("Strata spawn admission exceeded 120000 ms"); + await vi.advanceTimersByTimeAsync(120_000); + await rejection; + gate.settle(); + expect(gate.ready()).toBe(false); + expect(gate.error()).toContain("exceeded"); + vi.useRealTimers(); +}); + +it("captures the pending gate before timeout latches or rejects, once per failure", async () => { + const events: string[] = []; + const gate = spawnGate((reason) => { + events.push(`capture:${gate.ready()}:${gate.error()}:${reason}`); + }); + const rejection = gate.spawn.catch((error: Error) => events.push(`rejected:${error.message}`)); + await vi.advanceTimersByTimeAsync(120_000); + await rejection; + gate.settle(); + gate.fail("late failure"); + gate.settle(new Error("Strata spawn cancelled")); + expect(events).toEqual([ + "capture:false::Strata spawn admission exceeded 120000 ms", + "rejected:Strata spawn admission exceeded 120000 ms", + ]); + expect(gate.ready()).toBe(false); + expect(vi.getTimerCount()).toBe(0); + vi.useRealTimers(); +}); + +it("keeps the original admission failure when its diagnostic capture throws", async () => { + const capture = vi.fn(() => { + throw new Error("diagnostic observer failed"); + }); + const gate = spawnGate(capture); + const rejection = expect(gate.spawn).rejects.toThrow("Strata spawn assets failed to load"); + gate.fail(new Error("Strata spawn assets failed to load")); + await rejection; + expect(capture).toHaveBeenCalledOnce(); + expect(gate.error()).toBe("Strata spawn assets failed to load"); + expect(vi.getTimerCount()).toBe(0); + vi.useRealTimers(); +}); + +it.each(["observe", "cancel"])( + "keeps capture reentry through %s from changing failure settlement", + async (action) => { + const gate = spawnGate(() => { + if (action === "observe") gate.settle(); + else gate.settle(new Error("Strata spawn cancelled")); + }); + const rejection = expect(gate.spawn).rejects.toThrow("original admission failure"); + gate.fail("original admission failure"); + await rejection; + expect(gate.ready()).toBe(false); + expect(gate.error()).toBe("original admission failure"); + vi.useRealTimers(); + }, +); + +it("cancels a pending scene without leaving its readiness promise or timer behind", async () => { + const gate = spawnGate(); + const rejection = expect(gate.spawn).rejects.toThrow("Strata spawn cancelled"); + gate.settle(new Error("Strata spawn cancelled")); + await rejection; + expect(vi.getTimerCount()).toBe(0); + expect(gate.ready()).toBe(false); + vi.useRealTimers(); +}); + +it("keeps even an empty-message rejection failed after late coverage", async () => { + const gate = spawnGate(); + const rejection = expect(gate.spawn).rejects.toThrow("Strata spawn failed"); + gate.fail(new Error()); + await rejection; + gate.settle(); + expect(gate.ready()).toBe(false); + vi.useRealTimers(); +}); + +it("updates ctx state immediately when the spawn times out before any render frame", async () => { + const gate = spawnGate(); + const rejection = gate.spawn.catch(() => undefined); + expect(gate.state.loadingError).toBe(""); + expect(gate.state.worldReady).toBe(false); + await vi.advanceTimersByTimeAsync(120_000); + await rejection; + expect(gate.state.loadingError).toBe("Strata spawn admission exceeded 120000 ms"); + expect(gate.state.worldReady).toBe(false); + vi.useRealTimers(); +}); + +const KIT_GATES = ["Alpine", "Coastal", "Desert", "Forest", "Tundra"] as const; +/** Module constants a kit gate reads while it loads; the rest of each fixture stays out of the VM. */ +const KIT_CONSTANTS = new Set(["DRIVE_SECONDS", "viewWindows"]); + +interface IKitProbe { + ready(): boolean; + error(): string; + settle(error?: Error): void; + fail(ctx: unknown, message: string): void; +} + +interface IKitGate { + load(ctx: unknown): Promise; + testProbe(): IKitProbe; +} + +/** Run a starter-kit fixture's real gate class in a fresh context, with its spawn rejecting as `reason`. */ +function kitGate(kit: string, reason: string): IKitGate { + const url = new URL("../scripts/fixtures/kit-game/KitScene.ts", import.meta.url); + const source = ts.createSourceFile( + "KitScene.ts", + readFileSync(url, "utf8"), + ts.ScriptTarget.Latest, + true, + ); + const constants: string[] = []; + let gate = ""; + function visit(node: ts.Node): void { + if (ts.isClassDeclaration(node) && node.name?.text === "KitScene") gate = node.getText(source); + else if (ts.isVariableDeclaration(node) && KIT_CONSTANTS.has(node.name.getText(source))) + constants.push(node.parent.parent.getText(source)); + ts.forEachChild(node, visit); + } + visit(source); + if (gate === "") throw new Error(`Missing ${kit} kit gate class`); + // The probe sits inside the class to reach private gate state; the gate code itself is untouched. + const probe = + "testProbe() { return { ready: () => this.#ready, error: () => this.#error, settle: (error) => this.#settle?.(error), fail: (ctx, message) => this.#fail(ctx, message) }; }"; + const withProbe = `${gate.slice(0, gate.lastIndexOf("}"))}${probe}\n}`; + const compiled = ts.transpileModule([...constants, withProbe].join("\n"), { + compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }, + }).outputText; + const scope = { + exports: {} as Record IKitGate>, + Scene: class {}, + // Each kit's static `initialState` field reads this global; the gate under test never uses it. + initialState: {}, + VIEWS: { ground: { at: new Vector3(), look: new Vector3() } }, + VIEW_NAMES: [], + stand: { spawn: new Vector3(), groundY: 0 }, + HALF_HEIGHT: 0, + RADIUS: 0, + Object3D, + Vector3, + // The host Error keeps `error instanceof Error` true for the rejection the spawn raises. + Error, + setTimeout: () => 1, + clearTimeout: () => undefined, + performance: { now: () => 0 }, + createLoadingScreen: () => ({ update: () => undefined, finish: () => undefined }), + KIT: kit, + forestStand: undefined, + addKit: () => Promise.reject(new Error(reason)), + }; + runInNewContext(compiled, scope); + const Gate = scope.exports.KitScene; + if (Gate === undefined) throw new Error(`Kit gate ${kit} did not load`); + return new Gate(); +} + +function kitContext() { + return { + startup: { phase: "loading", progress: 0, hold: vi.fn(), whenReady: () => Promise.resolve() }, + state: { set: vi.fn(), getState: () => ({ loadingError: "" }) }, + camera: new PerspectiveCamera(), + }; +} + +it.each(KIT_GATES)("%s normalizes an empty spawn failure to its kit label", async (kit) => { + const ctx = kitContext(); + const gate = kitGate(kit, ""); + await gate.load(ctx); + expect(gate.testProbe().error()).toBe(`${kit} spawn failed`); + expect(ctx.state.set).toHaveBeenLastCalledWith({ + loadingError: `${kit} spawn failed`, + worldReady: 0, + }); +}); + +it.each(KIT_GATES)("%s keeps an empty spawn failure latched through late success", async (kit) => { + const ctx = kitContext(); + const gate = kitGate(kit, ""); + await gate.load(ctx); + const probe = gate.testProbe(); + probe.settle(); + expect(probe.ready()).toBe(false); + probe.fail(ctx, "A later failure"); + expect(probe.error()).toBe(`${kit} spawn failed`); + expect(ctx.state.set).toHaveBeenLastCalledWith({ + loadingError: `${kit} spawn failed`, + worldReady: 0, + }); +}); diff --git a/packages/terrain/__tests__/strata-view-statistics.spec.ts b/examples/strata-terrain-preview/__tests__/strata-view-statistics.spec.ts similarity index 92% rename from packages/terrain/__tests__/strata-view-statistics.spec.ts rename to examples/strata-terrain-preview/__tests__/strata-view-statistics.spec.ts index f7e90acd6a..50b2f12945 100644 --- a/packages/terrain/__tests__/strata-view-statistics.spec.ts +++ b/examples/strata-terrain-preview/__tests__/strata-view-statistics.spec.ts @@ -1,5 +1,5 @@ import { afterEach, expect, it, vi } from "vitest"; -import { medianOfGrowingSamples } from "../../../examples/strata-terrain-preview/src/viewStatistics.js"; +import { medianOfGrowingSamples } from "../src/viewStatistics.js"; afterEach(() => vi.restoreAllMocks()); diff --git a/examples/strata-terrain-preview/__tests__/terrain-splat-package.spec.ts b/examples/strata-terrain-preview/__tests__/terrain-splat-package.spec.ts new file mode 100644 index 0000000000..df4480b190 --- /dev/null +++ b/examples/strata-terrain-preview/__tests__/terrain-splat-package.spec.ts @@ -0,0 +1,265 @@ +import assert from "node:assert/strict"; +import * as fs from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import * as path from "node:path"; +import { pathToFileURL } from "node:url"; +import * as url from "node:url"; +import { runInNewContext } from "node:vm"; +import type { IAssetLoader } from "@threenative/core"; +import { MATERIAL_IDS, Terrain } from "@threenative/terrain"; +import { starterAssetsDir } from "@threenative/terrain/kit"; +import { type DataArrayTexture, DataTexture, type Texture } from "three"; +import { float } from "three/tsl"; +import ts from "typescript"; +import { expect, it, vi } from "vitest"; +import { BIOMES } from "../src/render/biomes.js"; +import { createGroundMaterial, createTerrain } from "../src/render/terrain.js"; +import { GROUND_MAPS, GROUND_TILE } from "../src/world/terrainAssets.js"; + +const base = new URL("../", import.meta.url); +const bake = readFileSync(new URL("scripts/bake.mjs", base), "utf8"); +const declaration = ts + .createSourceFile("bake.mjs", bake, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS) + .statements.find( + (node) => ts.isFunctionDeclaration(node) && node.name?.text === "terrainSurfacePackage", + ); +assert(declaration); +// Execute only the actual packaging function; importing bake would run all five authoring recipes. +const pack = runInNewContext(`(${declaration.getText().replace(/^export /u, "")})`, { + assert, + MATERIAL_IDS, + GROUND_TILE, + GROUND_MAPS, +}); + +it("exports actual authored weights in package-ground row order and the existing layer schema", () => { + const terrain = new Terrain({ size: 2, resolution: 17, seed: 1 }) + .paint({ at: [-1, -1], radius: 0.4, material: "dirt" }) + .paint({ at: [1, -1], radius: 0.4, material: "rock" }) + .paint({ at: [-1, 1], radius: 0.4, material: "snow" }) + .paint({ at: [1, 1], radius: 0.4, material: "moss" }); + const state = terrain.evaluate(); + const before = state.splat.slice(); + const { world, table, splat } = pack("forest", state, GROUND_MAPS); + expect(world).toEqual({ + version: 1, + extent: { minX: -1, minZ: -1, sizeX: 2, sizeZ: 2 }, + terrain: { layers: { table: "forest.layers.json", splat: "forest.splat.rgba8" } }, + }); + expect(splat.byteLength).toBe(17 * 17 * 4 * 2); + // First raw row is +Z: snow in the first plane's alpha, moss in the second plane's alpha. + expect(Array.from(splat.slice(0, 4))).toEqual([0, 0, 0, 255]); + expect(Array.from(splat.slice(17 * 17 * 4 + 16 * 4, 17 * 17 * 4 + 17 * 4))).toEqual([ + 0, 0, 0, 255, + ]); + // Last raw row is -Z: dirt in green, rock in blue. + expect(Array.from(splat.slice(16 * 17 * 4, 16 * 17 * 4 + 4))).toEqual([0, 255, 0, 0]); + expect(Array.from(splat.slice(16 * 17 * 4 + 16 * 4, 17 * 17 * 4))).toEqual([0, 0, 255, 0]); + expect(table.splat.masks).toEqual({ + grass: [0, "r"], + dirt: [0, "g"], + rock: [0, "b"], + snow: [0, "a"], + sand: [1, "r"], + mud: [1, "g"], + road: [1, "b"], + moss: [1, "a"], + }); + expect(table.layers.map((layer: { id: string }) => layer.id)).toEqual([ + "dirt", + "moss", + "sand", + "rock", + "snow", + ]); + expect(table.base.maps).toEqual({ + diffuse: `../${GROUND_MAPS.grass.diffuse}`, + normal: `../${GROUND_MAPS.grass.normal}`, + }); + expect(table.layers[2].normal).toBe(false); + expect(state.splat).toEqual(before); + expect(() => pack("bad", { ...state, splat: state.splat.slice(1) }, GROUND_MAPS)).toThrow( + /invalid splat grid/u, + ); + const invalid = state.splat.slice(); + invalid[0] = Number.NaN; + expect(() => pack("bad", { ...state, splat: invalid }, GROUND_MAPS)).toThrow(/nonfinite splat/u); +}); + +it("references available starter maps and the runtime's CC0 fallback for optional licensed maps", async () => { + const root = await starterAssetsDir(); + const state = new Terrain({ size: 2, resolution: 17 }).evaluate(); + for (const biome of Object.values(BIOMES)) { + const maps = + biome.world === "forest" || biome.world === "coastal" + ? { ...GROUND_MAPS, dirt: BIOMES.alpine.maps.dirt } + : biome.maps; + const { table } = pack(biome.world, state, maps); + for (const layer of [table.base, ...table.layers]) { + for (const map of Object.values(layer.maps) as string[]) + expect(fs.existsSync(path.resolve(root, "world", map)), `${biome.world}: ${map}`).toBe( + true, + ); + } + if (biome.world === "alpine") + expect(table.layers.find((layer: { id: string }) => layer.id === "rock").maps.diffuse).toBe( + `../${GROUND_MAPS.rock.diffuse}`, + ); + } +}); + +it("delivers flat descriptors, tables, raw masks and placements through Vite and native staging", async () => { + const root = mkdtempSync(path.join(tmpdir(), "strata-splat-")); + try { + mkdirSync(path.join(root, "src/world"), { recursive: true }); + mkdirSync(path.join(root, "scripts")); + mkdirSync(path.join(root, "starter")); + const files = ["forest", "coastal", "alpine", "desert", "tundra"].flatMap((world) => + ["world.json", "layers.json", "splat.rgba8", "placements.bin"].map( + (suffix) => `${world}.${suffix}`, + ), + ); + for (const name of [...files, "baked.json", ".bake-stamp"]) + writeFileSync(path.join(root, "src/world", name), name); + const execute = async (relative: string) => { + const source = readFileSync(new URL(relative, base), "utf8").replaceAll( + "import.meta.url", + JSON.stringify(pathToFileURL(path.join(root, relative)).href), + ); + const code = ts.transpileModule(source, { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, + }).outputText; + const exports: Record = {}; + const dependencies: Record = { + "node:fs": fs, + "node:path": path, + "node:url": url, + "@threenative/terrain/kit": { starterAssetsDir: async () => path.join(root, "starter") }, + "@threenative/terrain/editor/server": { terrainEditor: () => ({}) }, + vite: { defineConfig: (config: unknown) => config }, + }; + await runInNewContext(`(async () => { ${code}\n })()`, { + exports, + console, + Buffer, + URL, + require: (name: string) => { + assert(name in dependencies, name); + return dependencies[name]; + }, + }); + return exports.default as { plugins: Array<{ generateBundle?: (this: unknown) => void }> }; + }; + const config = await execute("vite.config.ts"); + const emitted: Array<{ fileName: string; source: Buffer }> = []; + config.plugins + .find((plugin) => plugin.generateBundle) + ?.generateBundle?.call({ emitFile: (file: (typeof emitted)[number]) => emitted.push(file) }); + expect(emitted.map((file) => file.fileName).sort()).toEqual( + files.map((name) => `world/${name}`).sort(), + ); + for (const file of emitted) expect(file.source.toString()).toBe(path.basename(file.fileName)); + await execute("scripts/stage-native-assets.mjs"); + expect(fs.readdirSync(path.join(root, "assets/world")).sort()).toEqual(files.sort()); + for (const name of files) + expect(readFileSync(path.join(root, "assets/world", name), "utf8")).toBe(name); + rmSync(path.join(root, "src/world/forest.splat.rgba8")); + expect(() => + config.plugins + .find((plugin) => plugin.generateBundle) + ?.generateBundle?.call({ emitFile() {} }), + ).toThrow(/ENOENT/u); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +it.each(["fallback", "licensed", "cancelled"])( + "uses the actual engine blend and releases images for %s", + async (mode) => { + const terrain = new Terrain({ size: 2, resolution: 17 }).evaluate(); + const { world, table, splat } = pack("forest", terrain, GROUND_MAPS); + const responses = new Map([ + ["world/forest.world.json", JSON.stringify(world)], + ["world/forest.layers.json", JSON.stringify(table)], + ["world/forest.splat.rgba8", splat], + ]); + vi.stubGlobal("fetch", async (path: string) => { + const body = responses.get(path); + if (body === undefined) throw new Error(`Unexpected request: ${path}`); + return new Response(body); + }); + const images: Texture[] = []; + const releases = new Map(); + const assets = { + resolve: async (path: string) => [path], + texture: async (path: string) => { + if (mode !== "licensed" && path.startsWith("temperate/")) + throw new Error("Optional licensed map absent"); + const image = new DataTexture(new Uint8Array([80, 150, 50, 255]), 1, 1); + image.userData.path = path; + image.addEventListener("dispose", () => + releases.set(image, (releases.get(image) ?? 0) + 1), + ); + images.push(image); + return image; + }, + } as unknown as IAssetLoader; + const curvature = new DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1); + curvature.addEventListener("dispose", () => + releases.set(curvature, (releases.get(curvature) ?? 0) + 1), + ); + try { + const data = { + size: 2, + resolution: 17, + heights: Array.from({ length: 17 * 17 }, (_, i) => i / 17), + colors: Array.from({ length: 17 * 17 * 3 }, () => 0.5), + waterLevel: null, + }; + if (mode === "cancelled") { + const { mesh } = createTerrain(data, assets, BIOMES.forest); + const placeholder = mesh.material; + mesh.geometry.dispose(); + (mesh.material as { dispose: () => void }).dispose(); + await vi.waitFor(() => { + expect(images).toHaveLength(10); + for (const image of images) expect(releases.get(image)).toBe(1); + }); + expect(mesh.material).toBe(placeholder); + curvature.dispose(); + return; + } + const material = await createGroundMaterial( + data, + assets, + { + texture: curvature, + node: float(0), + wetBank: float(0), + flow: float(0), + deposits: float(0), + }, + BIOMES.forest, + ); + expect(material.userData.TN_TERRAIN_SPLAT).toBeDefined(); + expect(images).toHaveLength(mode === "licensed" ? 12 : 10); + for (const node of [material.colorNode, material.normalNode]) { + const sampled = new Set(); + node?.traverse((child) => { + const value = (child as unknown as { value?: Texture }).value; + if (value?.isTexture) sampled.add(value); + }); + expect([...sampled].some((image) => (image as DataArrayTexture).isDataArrayTexture)).toBe( + false, + ); + expect(sampled.size).toBeGreaterThan(0); + } + material.dispose(); + for (const image of [...images, curvature]) expect(releases.get(image)).toBe(1); + } finally { + vi.unstubAllGlobals(); + } + }, +); diff --git a/examples/strata-terrain-preview/package.json b/examples/strata-terrain-preview/package.json index a037ad09fa..17a2de23b4 100644 --- a/examples/strata-terrain-preview/package.json +++ b/examples/strata-terrain-preview/package.json @@ -7,17 +7,18 @@ "bake": "node scripts/bake.mjs", "dev": "pnpm bake && vite", "build": "pnpm bake && vite build", - "build:desktop": "pnpm bake && node ../../packages/runtime-native/scripts/bundle.mjs --project . --entry src/game.ts --target desktop --output dist/strata-terrain-native.js", + "build:desktop": "pnpm bake && node scripts/stage-native-assets.mjs && node ../../packages/runtime-native/scripts/bundle.mjs --project . --entry src/game.ts --target desktop --output dist/strata-terrain-native.js", "typecheck": "pnpm bake && tsc --noEmit -p tsconfig.json", "test:terrain:web": "node ../../packages/playtest/dist/runner/cli.js playtests/terrain.playtest.json --url 'http://127.0.0.1:5196/?showcase=1' --server-command 'pnpm dev --host 127.0.0.1 --port 5196 --strictPort' --browser-recipe webgpu --headed --artifacts artifacts/playtest/web && node scripts/verify-ocean.mjs artifacts/playtest/web", - "test:terrain:desktop": "node scripts/stage-native-assets.mjs && pnpm build:desktop && node ../../packages/playtest/dist/runner/cli.js playtests/terrain.playtest.json --target desktop --executable ../../packages/runtime-native/build/tn-linux/mystral --host-arg run --host-arg dist/strata-terrain-native.js --artifacts artifacts/playtest/desktop && node scripts/verify-ocean.mjs artifacts/playtest/desktop && sh ../../scripts/xvfb.sh ../../packages/runtime-native/build/tn-linux/mystral run dist/strata-terrain-native.js --headless --frames 300 --screenshot artifacts/playtest/desktop/terrain-300frames.png", + "test:terrain:desktop": "pnpm build:desktop && node ../../packages/playtest/dist/runner/cli.js playtests/terrain.playtest.json --target desktop --executable ../../packages/runtime-native/build/tn-linux/mystral --host-arg run --host-arg dist/strata-terrain-native.js --artifacts artifacts/playtest/desktop && node scripts/verify-ocean.mjs artifacts/playtest/desktop && sh ../../scripts/xvfb.sh ../../packages/runtime-native/build/tn-linux/mystral run dist/strata-terrain-native.js --headless --frames 300 --screenshot artifacts/playtest/desktop/terrain-300frames.png", "test:terrain:editor": "node scripts/verify-editor.mjs", "test:consumer": "node scripts/verify-consumer.mjs", "test:kit": "node scripts/verify-starter-kit.mjs", "test:terrain:authored": "node scripts/verify-authored-world.mjs", "test:terrain:custom": "node scripts/verify-custom-assets.mjs", "test:terrain:export": "node scripts/verify-world-export.mjs", - "test:terrain:export:desktop": "node ../../packages/runtime-native/scripts/bundle.mjs --project . --entry src/render/exportConsumer.ts --target desktop --output dist/strata-export-native.js && node ../../packages/playtest/dist/runner/cli.js playtests/export.playtest.json --target desktop --executable ../../packages/runtime-native/build/tn-linux/mystral --host-arg run --host-arg dist/strata-export-native.js --artifacts artifacts/playtest/world-export-native" + "test:terrain:export:desktop": "node ../../packages/runtime-native/scripts/bundle.mjs --project . --entry src/render/exportConsumer.ts --target desktop --output dist/strata-export-native.js && node ../../packages/playtest/dist/runner/cli.js playtests/export.playtest.json --target desktop --executable ../../packages/runtime-native/build/tn-linux/mystral --host-arg run --host-arg dist/strata-export-native.js --artifacts artifacts/playtest/world-export-native", + "test": "pnpm bake && vitest run --root ../.. examples/strata-terrain-preview/__tests__" }, "dependencies": { "@threenative/core": "workspace:*", diff --git a/examples/strata-terrain-preview/playtests/editor-conflict.playtest.json b/examples/strata-terrain-preview/playtests/editor-conflict.playtest.json new file mode 100644 index 0000000000..64f2755314 --- /dev/null +++ b/examples/strata-terrain-preview/playtests/editor-conflict.playtest.json @@ -0,0 +1,408 @@ +{ + "schemaVersion": 1, + "name": "terrain-editor-save-conflict", + "inputDelivery": "focused-dom", + "steps": [ + { + "label": "conflict-terrain-ready", + "waitForResource": { + "id": "GameState", + "path": "vertexCount", + "gte": 16641 + }, + "timeoutMs": 10000, + "release": true + }, + { + "kind": "click", + "at": { + "element": { + "selector": "[data-tool=\"select\"]" + } + }, + "release": true, + "waitFrames": 1 + }, + { + "kind": "click", + "at": { + "element": { + "id": "viewport" + } + }, + "release": true, + "waitFrames": 3 + }, + { + "kind": "input", + "press": "Digit1", + "release": true, + "waitFrames": 3, + "holdFrames": 1 + }, + { + "kind": "input", + "press": "ArrowDown", + "release": true, + "waitFrames": 3, + "holdFrames": 1 + }, + { + "kind": "input", + "press": "ArrowDown", + "release": true, + "waitFrames": 3, + "holdFrames": 1 + }, + { + "kind": "input", + "press": "ArrowDown", + "release": true, + "waitFrames": 3, + "holdFrames": 1 + }, + { + "kind": "input", + "press": "ArrowDown", + "release": true, + "waitFrames": 3, + "holdFrames": 1 + }, + { + "kind": "input", + "press": "ArrowDown", + "release": true, + "waitFrames": 3, + "holdFrames": 1 + }, + { + "kind": "input", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 1, + "release": true + }, + { + "label": "refused-stroke", + "waitForResource": { + "id": "GameState", + "path": "editorUnsentCount", + "equals": 1 + }, + "timeoutMs": 10000, + "release": true, + "screenshot": "not-saved-list" + }, + { + "label": "refused-intent-idle", + "waitFrames": 60 + }, + { + "kind": "click", + "at": { + "element": { + "selector": "#not-saved-list button[data-save-action=\"retry\"]" + } + }, + "waitTicks": 1, + "release": true + }, + { + "label": "retry-refused-edit", + "waitForResource": { + "id": "GameState", + "path": "editorPersistedStrokes", + "equals": 1 + }, + "timeoutMs": 10000, + "release": true, + "screenshot": "retry-saved" + }, + { + "kind": "click", + "at": { + "element": { + "selector": "[data-tool=\"select\"]" + } + }, + "release": true, + "waitFrames": 1 + }, + { + "kind": "click", + "at": { + "element": { + "id": "viewport" + } + }, + "release": true, + "waitFrames": 3 + }, + { + "kind": "input", + "press": "Digit1", + "release": true, + "waitFrames": 3, + "holdFrames": 1 + }, + { + "kind": "input", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 1, + "release": true + }, + { + "label": "second-refused-stroke", + "waitForResource": { + "id": "GameState", + "path": "editorUnsentCount", + "equals": 1 + }, + "timeoutMs": 10000, + "release": true + }, + { + "label": "second-listed-intent-idle", + "waitFrames": 60 + }, + { + "kind": "click", + "at": { + "element": { + "selector": "#not-saved-list button[data-save-action=\"discard\"]" + } + }, + "waitTicks": 1, + "release": true + }, + { + "label": "discard-refused-edit", + "waitForResource": { + "id": "GameState", + "path": "editorUnsentCount", + "equals": 0 + }, + "timeoutMs": 10000, + "release": true + }, + { + "kind": "click", + "at": { + "element": { + "selector": "[data-tool=\"select\"]" + } + }, + "release": true, + "waitFrames": 1 + }, + { + "kind": "click", + "at": { + "element": { + "id": "viewport" + } + }, + "release": true, + "waitFrames": 3 + }, + { + "kind": "input", + "press": "Digit1", + "release": true, + "waitFrames": 3, + "holdFrames": 1 + }, + { + "kind": "input", + "press": "ArrowRight", + "holdFrames": 1, + "waitFrames": 3, + "release": true + }, + { + "kind": "input", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 1, + "release": true + }, + { + "label": "save-after-discard", + "waitForResource": { + "id": "GameState", + "path": "editorPersistedStrokes", + "equals": 2 + }, + "timeoutMs": 10000, + "release": true + } + ], + "assert": { + "diagnostics": { + "noRuntimeDiagnostics": true, + "runtimeReady": true, + "noConsoleErrors": false, + "consoleErrorsOptOutReason": "The fixture deliberately produces four HTTP 409 responses; verify-editor checks their exact console messages." + }, + "resources": [ + { + "id": "GameState", + "path": "editorUnsentVisible", + "atSteps": [ + { + "label": "refused-stroke", + "equals": true + }, + { + "label": "retry-refused-edit", + "equals": false + }, + { + "label": "second-refused-stroke", + "equals": true + }, + { + "label": "discard-refused-edit", + "equals": false + } + ] + }, + { + "id": "GameState", + "path": "editorUnsentCount", + "atSteps": [ + { + "label": "refused-stroke", + "equals": 1 + }, + { + "label": "retry-refused-edit", + "equals": 0 + }, + { + "label": "second-refused-stroke", + "equals": 1 + }, + { + "label": "discard-refused-edit", + "equals": 0 + }, + { + "label": "save-after-discard", + "equals": 0 + }, + { + "label": "refused-intent-idle", + "equals": 1 + } + ] + }, + { + "id": "GameState", + "path": "editorUnsentRetry", + "atSteps": [ + { + "label": "refused-stroke", + "equals": true + } + ] + }, + { + "id": "GameState", + "path": "editorUnsentDiscard", + "atSteps": [ + { + "label": "refused-stroke", + "equals": true + } + ] + }, + { + "id": "GameState", + "path": "editorStaleWriteConflicts", + "atSteps": [ + { + "label": "refused-stroke", + "equals": 2 + } + ] + }, + { + "id": "GameState", + "path": "editorPersistedStrokes", + "atSteps": [ + { + "label": "refused-stroke", + "equals": 0 + }, + { + "label": "retry-refused-edit", + "equals": 1 + }, + { + "label": "second-refused-stroke", + "equals": 1 + }, + { + "label": "discard-refused-edit", + "equals": 1 + }, + { + "label": "save-after-discard", + "equals": 2 + } + ] + }, + { + "id": "GameState", + "path": "editorUnsentText", + "atSteps": [ + { + "label": "refused-stroke", + "textIncludes": "rejected: 409: Stale base revision" + } + ] + }, + { + "id": "GameState", + "path": "editorCommitWrites", + "atSteps": [ + { + "label": "refused-stroke", + "equals": "2" + }, + { + "label": "retry-refused-edit", + "equals": "3" + }, + { + "label": "second-refused-stroke", + "equals": "3,2" + }, + { + "label": "second-listed-intent-idle", + "equals": "3,2" + }, + { + "label": "discard-refused-edit", + "equals": "3,2" + }, + { + "label": "save-after-discard", + "equals": "3,2,1" + }, + { + "label": "refused-intent-idle", + "equals": "2" + } + ] + } + ] + }, + "viewport": { + "width": 1440, + "height": 900 + }, + "warmupFrames": 90 +} diff --git a/examples/strata-terrain-preview/playtests/editor.playtest.json b/examples/strata-terrain-preview/playtests/editor.playtest.json index 5d615e5e16..f198c9a53b 100644 --- a/examples/strata-terrain-preview/playtests/editor.playtest.json +++ b/examples/strata-terrain-preview/playtests/editor.playtest.json @@ -29,6 +29,514 @@ "waitTicks": 120, "release": true, "screenshot": "editor-rendering" + }, + { + "kind": "click", + "at": { + "element": { + "selector": "[data-tool=\"sculpt\"]" + } + }, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-smooth", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-smooth", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-flatten", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-flatten", + "press": "Space", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-ramp", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-ramp", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-stamp", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-stamp", + "press": "Space", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-erode", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-erode", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-paint", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-paint", + "press": "Space", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-scatter", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-scatter", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-spline", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-spline", + "press": "Space", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-water", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-water", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-select", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-select", + "press": "Space", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "click", + "at": { + "element": { + "selector": "[data-tool=\"smooth\"]" + } + }, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "shift-tab-sculpt", + "press": ["Shift", "Tab"], + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "activate-sculpt", + "press": "Space", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "click", + "at": { + "element": { + "id": "brush-radius" + } + }, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "radius-minimum", + "press": "Home", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "radius-arrow", + "press": "ArrowRight", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "click", + "at": { + "element": { + "id": "viewport" + } + }, + "waitTicks": 3, + "release": true, + "label": "viewport-sculpt-stroke" + }, + { + "kind": "input", + "label": "keyboard-sculpt", + "press": "Digit1", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "brush-move", + "press": "ArrowRight", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "brush-apply", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 1, + "release": true + }, + { + "kind": "input", + "label": "second-stroke-50ms", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 1, + "release": true + }, + { + "label": "keyboard-edit-saved", + "waitTicks": 120, + "release": true, + "screenshot": "editor-keyboard-edit" + }, + { + "kind": "click", + "at": { + "element": { + "id": "preview-resolution" + } + }, + "waitTicks": 1, + "release": true + }, + { + "kind": "input", + "label": "choose-high-resolution", + "press": "End", + "holdTicks": 1, + "waitTicks": 2, + "release": true + }, + { + "kind": "input", + "label": "start-high-resolution-build", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 1, + "release": true + }, + { + "kind": "click", + "label": "terminate-mid-build", + "at": { + "element": { + "id": "cancel-build" + } + }, + "waitTicks": 3, + "release": true + }, + { + "kind": "click", + "at": { + "element": { + "selector": "[data-tool=\"select\"]" + } + }, + "waitTicks": 1, + "release": true + }, + { + "kind": "click", + "at": { + "element": { + "id": "preview-resolution" + } + }, + "waitTicks": 1, + "release": true + }, + { + "kind": "input", + "label": "choose-fast-resolution", + "press": "Home", + "holdTicks": 1, + "waitTicks": 2, + "release": true + }, + { + "kind": "input", + "label": "choose-129-resolution", + "press": "ArrowDown", + "holdTicks": 1, + "waitTicks": 2, + "release": true + }, + { + "kind": "input", + "label": "restore-fast-resolution", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 240, + "release": true + }, + { + "kind": "click", + "at": { + "element": { + "id": "viewport" + } + }, + "waitTicks": 1, + "release": true + }, + { + "kind": "input", + "press": "Digit1", + "holdTicks": 1, + "waitTicks": 1, + "release": true + }, + { + "kind": "input", + "label": "edit-after-worker-termination", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 360, + "release": true, + "screenshot": "worker-restarted" + }, + { + "kind": "input", + "label": "brush-drag", + "pointerPosition": { + "x": 0.5, + "y": 0.5, + "buttons": 1 + }, + "holdTicks": 1, + "waitTicks": 3, + "release": false + }, + { + "kind": "input", + "label": "brush-cancel", + "press": "Escape", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "cancelled-brush-release", + "pointerPosition": { + "x": 0.5, + "y": 0.5, + "buttons": 0 + }, + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "keyboard-spline", + "press": "Digit9", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "spline-point", + "press": "Space", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "spline-cancel", + "press": "Escape", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "click", + "at": { + "element": { + "id": "recipe-btn" + } + }, + "waitTicks": 3, + "release": true + }, + { + "kind": "click", + "at": { + "element": { + "id": "recipe-editor" + } + }, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "replace-recipe", + "press": ["Control", "KeyA"], + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "invalid-recipe", + "press": "KeyX", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-copy-recipe", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "tab-apply-recipe", + "press": "Tab", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "kind": "input", + "label": "validate-invalid-recipe", + "press": "Enter", + "holdTicks": 1, + "waitTicks": 3, + "release": true + }, + { + "label": "error-announced", + "waitTicks": 90, + "release": true, + "screenshot": "editor-keyboard-error" + }, + { + "kind": "input", + "label": "close-error-dialog", + "press": "Escape", + "holdTicks": 1, + "waitTicks": 3, + "release": true } ], "assert": { @@ -54,7 +562,272 @@ "id": "GameState", "path": "renderedFrames", "changed": true + }, + { + "id": "GameState", + "path": "editorTool", + "atSteps": [ + { + "label": "activate-smooth", + "equals": "smooth" + }, + { + "label": "activate-flatten", + "equals": "flatten" + }, + { + "label": "activate-ramp", + "equals": "ramp" + }, + { + "label": "activate-stamp", + "equals": "stamp" + }, + { + "label": "activate-erode", + "equals": "erode" + }, + { + "label": "activate-paint", + "equals": "paint" + }, + { + "label": "activate-scatter", + "equals": "scatter" + }, + { + "label": "activate-spline", + "equals": "spline" + }, + { + "label": "activate-water", + "equals": "water" + }, + { + "label": "activate-select", + "equals": "select" + }, + { + "label": "activate-sculpt", + "equals": "sculpt" + } + ] + }, + { + "id": "GameState", + "path": "editorFocus", + "atSteps": [ + { + "label": "tab-smooth", + "equals": "smooth" + }, + { + "label": "tab-flatten", + "equals": "flatten" + }, + { + "label": "tab-ramp", + "equals": "ramp" + }, + { + "label": "tab-stamp", + "equals": "stamp" + }, + { + "label": "tab-erode", + "equals": "erode" + }, + { + "label": "tab-paint", + "equals": "paint" + }, + { + "label": "tab-scatter", + "equals": "scatter" + }, + { + "label": "tab-spline", + "equals": "spline" + }, + { + "label": "tab-water", + "equals": "water" + }, + { + "label": "tab-select", + "equals": "select" + }, + { + "label": "shift-tab-sculpt", + "equals": "sculpt" + } + ] + }, + { + "id": "GameState", + "path": "editorRadius", + "atSteps": [ + { + "label": "radius-minimum", + "equals": 4 + }, + { + "label": "radius-arrow", + "equals": 5 + } + ] + }, + { + "id": "GameState", + "path": "editorPointCount", + "atSteps": [ + { + "label": "spline-point", + "equals": 1 + }, + { + "label": "spline-cancel", + "equals": 0 + } + ] + }, + { + "id": "GameState", + "path": "editorLayerCount", + "changed": true + }, + { + "id": "GameState", + "path": "editorLiveRegionCount", + "equals": 1, + "allowTrivial": "This observes the mounted DOM and requires exactly one announcement region." + }, + { + "id": "GameState", + "path": "editorLiveMode", + "equals": "polite", + "allowTrivial": "This reads the live region attribute; normal announcements must remain polite." + }, + { + "id": "GameState", + "path": "editorFocusVisible", + "atSteps": [ + { + "label": "tab-smooth", + "equals": true + }, + { + "label": "tab-select", + "equals": true + } + ] + }, + { + "id": "GameState", + "path": "editorAnnouncement", + "changed": true, + "atSteps": [ + { + "label": "error-announced", + "textIncludes": "Unexpected token 'x', \"x\" is not valid JSON" + } + ] + }, + { + "id": "GameState", + "path": "editorFocus", + "atSteps": [ + { + "label": "tab-apply-recipe", + "equals": "apply-recipe" + } + ] + }, + { + "id": "GameState", + "path": "editorBrushCaptured", + "atSteps": [ + { + "label": "brush-drag", + "equals": true + }, + { + "label": "brush-cancel", + "equals": false + } + ] + }, + { + "id": "GameState", + "path": "editorPersistedStrokes", + "atSteps": [ + { + "label": "viewport-sculpt-stroke", + "equals": 1 + }, + { + "label": "brush-apply", + "equals": 2 + }, + { + "label": "second-stroke-50ms", + "equals": 3 + }, + { + "label": "keyboard-edit-saved", + "equals": 3 + }, + { + "label": "edit-after-worker-termination", + "equals": 4 + } + ] + }, + { + "id": "GameState", + "path": "editorPersistenceError", + "equals": "", + "allowTrivial": "An authoritative document GET must succeed; fetch errors are observed here." + }, + { + "id": "GameState", + "path": "editorWorkerBusy", + "atSteps": [ + { + "label": "start-high-resolution-build", + "equals": true + }, + { + "label": "terminate-mid-build", + "equals": false + }, + { + "label": "edit-after-worker-termination", + "equals": false + } + ] + }, + { + "id": "GameState", + "path": "editorBuildCancelled", + "atSteps": [ + { + "label": "terminate-mid-build", + "equals": true + } + ] + }, + { + "id": "GameState", + "path": "editorRebuiltAfterCancel", + "atSteps": [ + { + "label": "edit-after-worker-termination", + "equals": true + } + ] } ] - } + }, + "inputDelivery": "focused-dom" } diff --git a/examples/strata-terrain-preview/playtests/terrain.playtest.json b/examples/strata-terrain-preview/playtests/terrain.playtest.json index 15e6d6f500..7e092f3a85 100644 --- a/examples/strata-terrain-preview/playtests/terrain.playtest.json +++ b/examples/strata-terrain-preview/playtests/terrain.playtest.json @@ -596,6 +596,13 @@ "runtimeReady": true }, "resources": [ + { + "id": "GameState", + "path": "enterMs", + "gte": 0, + "lte": 50, + "allowTrivial": "Measured synchronous scene enter; baked scatter must not run in enter." + }, { "id": "GameState", "path": "lakePlacementError", diff --git a/examples/strata-terrain-preview/scripts/bake.mjs b/examples/strata-terrain-preview/scripts/bake.mjs index 3c0efaccb2..e6efb866d3 100644 --- a/examples/strata-terrain-preview/scripts/bake.mjs +++ b/examples/strata-terrain-preview/scripts/bake.mjs @@ -2,9 +2,88 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { mkdir, readFile, stat, writeFile } from "node:fs/promises"; import { pathToFileURL } from "node:url"; -import { Mask, Terrain, bakeMesh } from "@threenative/terrain"; +import { Heightfield } from "@threenative/core/world"; +import { BufferGeometry, Float32BufferAttribute, Mesh } from "three"; +import { register } from "tsx/esm/api"; + +register(); +import { MATERIAL_IDS, Mask, Terrain, bakeMesh } from "@threenative/terrain"; import { terrainPalette } from "../src/render/palette.js"; +const { BIOMES } = await import("../src/render/biomes.js"); +const { GROUND_MAPS, GROUND_TILE } = await import("../src/world/terrainAssets.js"); + +/** Existing surface-loader descriptor; raw planes preserve all eight authored material channels. */ +export function terrainSurfacePackage(name, state, maps) { + const count = state.resolution ** 2; + assert.equal(state.height.length, count, `${name}: invalid height grid`); + assert.equal(state.splat.length, count * MATERIAL_IDS.length, `${name}: invalid splat grid`); + const splat = new Uint8Array(count * 8); + for (let row = 0; row < state.resolution; row += 1) + for (let column = 0; column < state.resolution; column += 1) + for (let channel = 0; channel < MATERIAL_IDS.length; channel += 1) { + const value = state.splat[(row * state.resolution + column) * 8 + channel]; + assert(Number.isFinite(value), `${name}: nonfinite splat channel`); + // Package ground is (x, -z): reverse the evaluator's increasing-Z rows. + const pixel = (state.resolution - 1 - row) * state.resolution + column; + splat[Math.floor(channel / 4) * count * 4 + pixel * 4 + (channel % 4)] = Math.round( + Math.max(0, Math.min(1, value)) * 255, + ); + } + const layer = (id) => { + // Match loadGroundMaps' CC0 fallback; licensed overrides stay game-owned and are not staged. + const source = maps[id].diffuse.startsWith("temperate/") ? GROUND_MAPS[id] : maps[id]; + return { + id, + tile: GROUND_TILE[id], + tint: [1, 1, 1], + roughness: 0.95, + metalness: 0, + normal: source.normal !== undefined, + maps: { + diffuse: `../${source.diffuse}`, + ...(source.normal === undefined ? {} : { normal: `../${source.normal}` }), + }, + }; + }; + return { + world: { + version: 1, + extent: { + minX: -state.size / 2, + minZ: -state.size / 2, + sizeX: state.size, + sizeZ: state.size, + }, + terrain: { layers: { table: `${name}.layers.json`, splat: `${name}.splat.rgba8` } }, + }, + table: { + base: layer("grass"), + layers: ["dirt", "moss", "sand", "rock", "snow"].map((id) => ({ + ...layer(id), + mask: id, + channel: "r", + lo: 0, + hi: 1, + })), + breakup: { scale: 1, push: 0 }, + macro: { scale: 1, min: 1, max: 1 }, + textures: "", + splat: { + size: state.resolution, + planes: 2, + masks: Object.fromEntries( + MATERIAL_IDS.map((id, index) => [ + id, + [Math.floor(index / 4), ["r", "g", "b", "a"][index % 4]], + ]), + ), + }, + }, + splat, + }; +} + // Authoring runs before either runtime is bundled. The game imports only the baked JSON. // Public-domain surveyed elevations; one offset and orientation are shared by detail and horizon. @@ -301,6 +380,12 @@ const fingerprint = createHash("sha256") ), ) .update(JSON.stringify(terrainPalette)) + .update(await readFile(new URL("../src/render/scatter.ts", import.meta.url))) + .update(await readFile(new URL("../src/render/terrain.ts", import.meta.url))) + .update(await readFile(new URL("../src/render/biomes.ts", import.meta.url))) + .update(await readFile(new URL("../src/world/terrainAssets.ts", import.meta.url))) + .update(await readFile(new URL("../src/render/benchmark.ts", import.meta.url))) + .update(await readFile(new URL("../src/render/placements.ts", import.meta.url))) .update(await readFile(new URL(import.meta.url))) .update(await readFile(new URL("../../../packages/terrain/dist/index.js", import.meta.url))) .digest("hex"); @@ -319,7 +404,17 @@ if (!(isEntry && process.argv.includes("--force"))) { const undrawn = Object.keys(recipes).filter((name) => !drawn.has(name)); const stale = ( await Promise.all( - ["baked.json", "horizon.json", ...undrawn.map((name) => `${name}.json`)].map((file) => + [ + "baked.json", + "horizon.json", + ...undrawn.map((name) => `${name}.json`), + ...Object.keys(recipes).flatMap((name) => [ + `${name}.placements.bin`, + `${name}.world.json`, + `${name}.layers.json`, + `${name}.splat.rgba8`, + ]), + ].map((file) => stat(new URL(file, outputDir)).then( (s) => s.mtimeMs > out.mtimeMs, () => true, @@ -339,6 +434,7 @@ if (!(isEntry && process.argv.includes("--force"))) { if (!baked) { const bakeStarted = performance.now(); const worlds = {}; + await mkdir(outputDir, { recursive: true }); for (const [name, terrain] of Object.entries(recipes)) { const started = performance.now(); const state = terrain.evaluate(); @@ -360,6 +456,16 @@ if (!baked) { ); assert(state.erosion[channel].every(Number.isFinite), `${name}: nonfinite ${channel}`); } + const maps = + name === "forest" || name === "coastal" + ? { ...GROUND_MAPS, dirt: BIOMES.alpine.maps.dirt } + : BIOMES[name].maps; + const surface = terrainSurfacePackage(name, state, maps); + await Promise.all([ + writeFile(new URL(`${name}.world.json`, outputDir), JSON.stringify(surface.world)), + writeFile(new URL(`${name}.layers.json`, outputDir), JSON.stringify(surface.table)), + writeFile(new URL(`${name}.splat.rgba8`, outputDir), surface.splat), + ]); const mesh = bakeMesh(state, { palette: terrainPalette }); worlds[name] = { size: state.size, @@ -383,7 +489,6 @@ if (!baked) { `${name}: ${((performance.now() - started) / 1000).toFixed(2)} s; ${Buffer.byteLength(JSON.stringify(worlds[name]))} JSON bytes`, ); } - await mkdir(outputDir, { recursive: true }); const started = performance.now(); const horizon = JSON.stringify({ ...Object.fromEntries( @@ -411,6 +516,40 @@ if (!baked) { for (const [name, world] of Object.entries(worlds)) if (!drawn.has(name)) await writeFile(new URL(`${name}.json`, outputDir), JSON.stringify(world)); + // Import after the JSON outputs exist: terrain's horizon reads the newly baked continuation. + const { scatterProps } = await import("../src/render/scatter.js"); + const { paintCanopy } = await import("../src/render/terrain.js"); + const { BENCHMARK } = await import("../src/render/benchmark.js"); + const { encodePlacements } = await import("../src/render/placements.js"); + for (const [name, data] of Object.entries(worlds)) { + const field = new Heightfield({ + rows: data.resolution, + columns: data.resolution, + width: data.size, + depth: data.size, + origin: { x: 0, z: 0 }, + heights: Float32Array.from(data.heights), + }); + const scatter = scatterProps( + { ...data, field, world: name }, + BENCHMARK[name].focus, + Object.values(BENCHMARK[name].poses) + .filter((pose) => pose.eye < 20) + .map((pose) => [...pose.at, 10]), + ); + const mesh = new Mesh(new BufferGeometry()); + mesh.geometry.setAttribute( + "canopy", + new Float32BufferAttribute(new Float32Array(data.resolution ** 2), 1), + ); + paintCanopy(mesh, data, scatter.placements); + const table = encodePlacements(scatter, mesh.geometry.getAttribute("canopy").array); + await writeFile(new URL(`${name}.placements.bin`, outputDir), new Uint8Array(table)); + mesh.geometry.dispose(); + console.log( + `${name}: ${scatter.placements.length} baked placements; ${table.byteLength} bytes`, + ); + } await writeFile(new URL(".bake-stamp", outputDir), `${fingerprint}\n`); console.log( `Cold bake total: ${((performance.now() - bakeStarted) / 1000).toFixed(2)} s; five worlds plus continuation.`, diff --git a/examples/strata-terrain-preview/scripts/check-temperate.mts b/examples/strata-terrain-preview/scripts/check-temperate.mts index de79b2262f..fcfb1853de 100644 --- a/examples/strata-terrain-preview/scripts/check-temperate.mts +++ b/examples/strata-terrain-preview/scripts/check-temperate.mts @@ -4,15 +4,8 @@ import { readFileSync } from "node:fs"; import { Heightfield } from "@threenative/core/world"; import { Vector3 } from "three"; import { createHorizonGeometry } from "../src/render/horizon.js"; -import { - buildPropVariants, - createProps, - flatPropMaterials, - preparePropTransform, - readPropTransform, - variantFor, - writePropTransform, -} from "../src/render/props.js"; +import { createStreamedProps } from "../src/render/propStreaming.js"; +import { buildPropVariants, flatPropMaterials, variantFor } from "../src/render/props.js"; import { grassWeight, scatterProps, slopeDegrees } from "../src/render/scatter.js"; const data = JSON.parse( @@ -154,8 +147,8 @@ const placements = [0, 20, 200].map((x, index) => ({ scale: 1, })); // Footprint support may be four metres downhill; the dressed crag must still emerge. -const beddedCrag = createProps( - [ +const beddedCrag = await createStreamedProps({ + placements: [ { asset: "mountain", id: "check:outcrop", @@ -167,10 +160,14 @@ const beddedCrag = createProps( scale: 0.7, }, ], - () => ({ height: -4, offset: 0 }), + groundAt: () => ({ height: -4, offset: 0 }), parts, materials, -); + follow: { position: new Vector3() }, + size: data.size, + whileCurrent: () => true, +}); +assert.ok(beddedCrag); const beddedInstance = beddedCrag.byId.get("check:outcrop"); assert.ok(beddedInstance); beddedInstance.geometry.computeBoundingBox(); @@ -180,31 +177,52 @@ assert.ok( "Footprint grounding plus old burial hides the entire outcrop", ); beddedCrag.dispose(); -const props = createProps(placements, ground, parts, materials); -props.setLevels(new Vector3()); -const instance = props.byId.get("rock:0"); -assert.ok(instance); -const transform = { - ...readPropTransform(instance), - position: [10, 0, 0] as [number, number, number], -}; -writePropTransform(instance, preparePropTransform(instance, transform, ground)); -props.setLevels(new Vector3(0.3, 0, 0)); -assert.equal(readPropTransform(instance).position[0], 10); -for (const draw of props.meshes) { - assert.equal(draw.userData.placementIds.length, draw.count); - for (const [index, id] of draw.userData.placementIds.entries()) { - assert.ok(props.byId.get(id)?.parts.some((part) => part.mesh === draw && part.index === index)); +const follow = { position: new Vector3() }; +async function draw(placements: readonly import("@threenative/terrain").IPlacement[]) { + const props = await createStreamedProps({ + placements, + groundAt: ground, + parts, + materials, + follow, + size: data.size, + whileCurrent: () => true, + }); + assert.ok(props); + for (let frame = 0; frame < 200; frame++) { + for (const world of props.worlds) world.update(); + await Promise.resolve(); } + return props; } +const props = await draw(placements); const hidden = props.byId.get("rock:2"); assert.ok(hidden); -assert.equal(hidden.parts.length, 0); -const revealed = { ...readPropTransform(hidden), position: [15, 0, 0] as [number, number, number] }; -writePropTransform(hidden, preparePropTransform(hidden, revealed, ground)); -props.setLevels(new Vector3(0.3, 0, 0)); -assert.ok(hidden.parts.length > 0, "A moved culled prop must reappear without camera movement"); +const count = props.meshes.reduce((n, mesh) => n + mesh.count, 0); +assert.equal(count, 2, "Only rocks within their authored draw reach are rendered"); props.dispose(); +const moved = await draw( + placements.map((placement) => + placement.id === "rock:2" + ? { + ...placement, + transform: { + position: [15, 0, 0], + quaternion: [0, 0, 0, 1], + scale: [1, 1, 1], + grounding: true, + }, + } + : placement, + ), +); +assert.equal(moved.byId.get("rock:2")?.pose.elements[12], 15); +assert.equal( + moved.meshes.reduce((n, mesh) => n + mesh.count, 0), + count + 1, + "A moved culled prop must reappear without camera movement", +); +moved.dispose(); materials.dispose(); for (const list of parts.values()) for (const part of list) part.geometry.dispose(); console.log("Temperate checks passed", scatter.counts); diff --git a/examples/strata-terrain-preview/scripts/check-water.mjs b/examples/strata-terrain-preview/scripts/check-water.mjs index 19ccc54e6d..134515fbd1 100644 --- a/examples/strata-terrain-preview/scripts/check-water.mjs +++ b/examples/strata-terrain-preview/scripts/check-water.mjs @@ -48,7 +48,6 @@ try { const flooded = scatterProps( { field, - colors: [], resolution: 17, size: 64, waterLevel: null, diff --git a/examples/strata-terrain-preview/scripts/consumer-world.mjs b/examples/strata-terrain-preview/scripts/consumer-world.mjs index 804b5ec5b3..e8e18920b2 100644 --- a/examples/strata-terrain-preview/scripts/consumer-world.mjs +++ b/examples/strata-terrain-preview/scripts/consumer-world.mjs @@ -5,6 +5,7 @@ import { createReadStream, existsSync, statSync } from "node:fs"; import { createServer } from "node:http"; import { createRequire } from "node:module"; import { extname, join, normalize, resolve } from "node:path"; +import { cookTerrainWorld } from "@threenative/assets"; const TYPES = { ".js": "text/javascript", ".mjs": "text/javascript", ".json": "application/json" }; const MAP = { @@ -17,7 +18,7 @@ const MAP = { }, }; -export async function openConsumerPage({ consumer, repo, port = 5184 }) { +export async function openConsumerPage({ consumer, repo, port = 0 }) { const root = resolve(consumer); const server = createServer((request, response) => { const path = normalize(decodeURIComponent(new URL(request.url, "http://x").pathname)); @@ -37,18 +38,25 @@ export async function openConsumerPage({ consumer, repo, port = 5184 }) { server.once("error", fail); server.listen(port, "127.0.0.1", done); }); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Consumer server has no port"); + const origin = `http://127.0.0.1:${address.port}/`; const { chromium } = createRequire(join(repo, "packages/playtest/package.json"))("playwright"); const browser = await chromium.launch({ headless: true }); const page = await browser.newPage(); + await page.exposeFunction("cookPreparedWorld", async (preparedWorld) => { + const { worldGlb } = await cookTerrainWorld({ preparedWorld }); + return Buffer.from(worldGlb).toString("base64"); + }); const problems = []; page.on("pageerror", (error) => problems.push(error.message)); page.on("console", (message) => message.type() === "error" && problems.push(message.text())); await page.route("**/*", (route) => { - if (route.request().url().startsWith(`http://127.0.0.1:${port}/`)) return route.continue(); + if (route.request().url().startsWith(origin)) return route.continue(); problems.push(`external request ${route.request().url()}`); return route.abort(); }); - await page.goto(`http://127.0.0.1:${port}/`); + await page.goto(origin); return { problems, /** Export one saved document in the page and read the file back with a plain GLTFLoader. */ diff --git a/examples/strata-terrain-preview/scripts/fixtures/consumer-world.mjs b/examples/strata-terrain-preview/scripts/fixtures/consumer-world.mjs index 80a8fc4edc..131bce66e2 100644 --- a/examples/strata-terrain-preview/scripts/fixtures/consumer-world.mjs +++ b/examples/strata-terrain-preview/scripts/fixtures/consumer-world.mjs @@ -1,9 +1,9 @@ // What an ordinary game does with an authored document, in a browser page that only has the packed // `@threenative/terrain`, `three` and a plain GLTFLoader: re-evaluate the saved recipe, dress it in // the game's OWN appearance (1x1 stand-in maps, boxes for models, a quad for water), export the whole -// world, and load the file back with the vanilla loader. It imports no editor or server module. +// world via the Node cook bridge, and load it with the vanilla loader. No editor imports. import { Terrain, applyPlacementOverrides, bakeMesh } from "@threenative/terrain"; -import { exportWorldGLB } from "@threenative/terrain/export"; +import { prepareWorldGLTF } from "@threenative/terrain/export"; import { toGeometry } from "@threenative/terrain/three"; import { BoxGeometry, @@ -63,7 +63,7 @@ export async function exportAndLoad(document, revision, sampleIndices, withBytes time: 0, staleFrames: 0, })); - const output = await exportWorldGLB({ + const prepared = await prepareWorldGLTF({ revision, snapshotTime: 0, state, @@ -72,6 +72,11 @@ export async function exportAndLoad(document, revision, sampleIndices, withBytes transforms, water, }); + const glbBase64 = await window.cookPreparedWorld(prepared.json); + const output = { + report: prepared.report, + bytes: Uint8Array.from(atob(glbBase64), (character) => character.charCodeAt(0)), + }; const bytes = output.bytes.slice().buffer; const gltf = await new Promise((resolve, reject) => new GLTFLoader().parse(bytes, "", resolve, reject), diff --git a/examples/strata-terrain-preview/scripts/fixtures/editor-conflict.ts b/examples/strata-terrain-preview/scripts/fixtures/editor-conflict.ts new file mode 100644 index 0000000000..d8967f4ee4 --- /dev/null +++ b/examples/strata-terrain-preview/scripts/fixtures/editor-conflict.ts @@ -0,0 +1,38 @@ +/** Refuse two sculpt intents and their automatic retries with real server 409s. */ +export function installStaleWrites(): void { + const fetch_ = globalThis.fetch.bind(globalThis); + const writes: Record = {}; + globalThis.fetch = async (input, init) => { + const url = input instanceof Request ? input.url : String(input); + if ( + new URL(url, location.href).pathname === "/terrain-editor/api/document" && + init?.method === "POST" + ) { + const transaction = JSON.parse(String(init.body)); + const stroke = transaction.commands?.find( + (command: { layer?: { name?: string; id?: string } }) => + command.layer?.name === "Sculpt stroke", + )?.layer; + if (!stroke || transaction.commands.length !== 1) + throw new Error("Conflict fixture expected one sculpt intent"); + // Distinct brush positions identify intents even when discard lets a layer ID be reused. + const intent = JSON.stringify(stroke); + writes[intent] = (writes[intent] ?? 0) + 1; + document.body.dataset.commitWrites = JSON.stringify(writes); + // Refuse the first two intents twice; the explicit retry and later sibling may save. + const refuse = Object.keys(writes).indexOf(intent) < 2 && writes[intent] <= 2; + const response = await fetch_(input, { + ...init, + body: JSON.stringify( + refuse ? { ...transaction, baseRevision: "stale-durability-fixture" } : transaction, + ), + }); + if (response.status === 409) + document.body.dataset.staleWriteConflicts = String( + Number(document.body.dataset.staleWriteConflicts ?? 0) + 1, + ); + return response; + } + return fetch_(input, init); + }; +} diff --git a/examples/strata-terrain-preview/scripts/fixtures/kit-game/Coastal.ts b/examples/strata-terrain-preview/scripts/fixtures/kit-game/Coastal.ts deleted file mode 100644 index e86463627b..0000000000 --- a/examples/strata-terrain-preview/scripts/fixtures/kit-game/Coastal.ts +++ /dev/null @@ -1,595 +0,0 @@ -// An ordinary game scene standing in the coastal starter kit. The kit streams the world, textures its -// ground, draws the sea and gives the firs their collision; this file owns the daylight, the camera, the -// player and every number the playtest reads. Nothing here imports the authoring package: the kit was -// copied into `src/terrain/coastal/` and its bake is a build step. The spawn and the views are read from -// the loaded heightfield, placements and sea level, so the scene keeps no coordinate the recipe does not -// make. -import { type ICtx, type IFrameBudgetWindow, Scene } from "@threenative/core"; -import { CharacterBody3D, CollisionShape3D, type IPhysicsContext } from "@threenative/physics"; -import { Object3D, type PerspectiveCamera, Vector3 } from "three"; -import { createLoadingScreen, createSpawnReadiness } from "../render/loading.js"; -import { type GameState, initialState } from "../state.js"; -import { coastalDaylight } from "../terrain/coastal/sky.js"; -import { COLLIDERS, type ICoastalWorld, addCoastal } from "../terrain/coastal/world.js"; - -type Ctx = ICtx; -type Field = ICoastalWorld["field"]; -type Extent = ICoastalWorld["extent"]; - -/** A 1.8 m figure: 0.55 m of half-height plus the 0.35 m radius a tree's clearance is read against. */ -const HALF_HEIGHT = 0.55; -const RADIUS = 0.35; -const MOVE_SPEED = 3; -/** Long enough to cover the spawn ring and be stopped by the tree it is aimed at. */ -const DRIVE_SECONDS = 3; -const EYE = 1.7; -/** Metres from the anchor tree's origin to the spawn: far enough to walk, near enough to measure. */ -const SPAWN_RING = 5; -/** The steepest rise over run, per metre, that the spawn or the walk to the anchor may use. */ -const SPAWN_SLOPE = 0.25; -const WALK_SLOPE = 0.35; -/** Metres above the sea that the spawn, the walk and the edge camera keep. */ -const DRY_HEIGHT = 1; -/** The coastal daylight's sun (`sky.ts`). Its azimuth picks the side the edge camera stands on. */ -const SUN_AZIMUTH = Math.atan2(0.56, 0.55); -const VIEW_NAMES = ["boulders", "edge", "ground", "overview"] as const; -type ViewName = (typeof VIEW_NAMES)[number]; - -interface IPoint { - readonly x: number; - readonly z: number; -} - -/** One placed fir, read from its record: where it stands and the radius its collider uses. */ -interface IProp extends IPoint { - readonly y: number; - readonly radius: number; -} - -interface IView { - readonly at: Vector3; - readonly look: Vector3; -} - -/** Where the scene stands, looks and drives, all read from the loaded world. */ -interface IStand { - readonly anchor: IProp; - readonly spawn: IPoint; - readonly groundY: number; - readonly views: Readonly>; -} - -/** One view's closed frame-budget windows, read by {@link noteFrameBudget}. */ -const viewWindows = new Map(); -let currentView = "ground"; - -/** The engine's own frame meter, grouped by the view that was on screen. */ -export function noteFrameBudget(window: IFrameBudgetWindow): void { - const group = viewWindows.get(currentView); - // A window with no `gpu` resolved no timestamp: absent, not zero. - if (group === undefined || window.gpu === undefined) return; - group.gpuP50.push(window.gpu.p50); - group.gpuP95.push(window.gpu.p95); - group.frameP95.push(window.frame.p95); -} - -/** The middle of a sample set, for a summary that one outlier cannot move. */ -function median(values: readonly number[]): number { - if (values.length === 0) return -1; - const sorted = [...values].sort((a, b) => a - b); - return sorted[Math.floor(sorted.length / 2)] ?? -1; -} - -/** Every placed fir. The kit's only asset is the fir, so every record is one. */ -function readProps(placements: Float32Array): IProp[] { - const fir = COLLIDERS.fir; - if (!fir) throw new Error("Coastal kit: the fir has no collider in COLLIDERS."); - const props: IProp[] = []; - for (let index = 0; index + 8 <= placements.length; index += 8) - props.push({ - x: placements[index] as number, - y: placements[index + 1] as number, - z: placements[index + 2] as number, - radius: fir.sphere * (placements[index + 7] as number), - }); - return props; -} - -function distance(a: IPoint, b: IPoint): number { - return Math.hypot(a.x - b.x, a.z - b.z); -} - -function inside(extent: Extent, point: IPoint, margin: number): boolean { - return ( - point.x > extent.minX + margin && - point.x < extent.minX + extent.sizeX - margin && - point.z > extent.minZ + margin && - point.z < extent.minZ + extent.sizeZ - margin - ); -} - -/** The ground under a point, or undefined when the point lies outside the world's heightfield. */ -function groundAt(field: Field, extent: Extent, x: number, z: number): number | undefined { - return inside(extent, { x, z }, 0) ? field.heightAt(x, z) : undefined; -} - -/** Rise over run at a point, from the heights half a metre either side on each axis. */ -function slopeAt(field: Field, extent: Extent, x: number, z: number): number { - const plusX = groundAt(field, extent, x + 0.5, z); - const minusX = groundAt(field, extent, x - 0.5, z); - const plusZ = groundAt(field, extent, x, z + 0.5); - const minusZ = groundAt(field, extent, x, z - 0.5); - // A slope that reaches past the world's edge is not walkable, so it reads as infinitely steep. - if (plusX === undefined || minusX === undefined || plusZ === undefined || minusZ === undefined) - return Number.POSITIVE_INFINITY; - return Math.hypot(plusX - minusX, plusZ - minusZ); -} - -/** The highest ground within `reach` metres of a point, sampled every 16 m inside the world. */ -function highestNear(field: Field, extent: Extent, x: number, z: number, reach: number): number { - let top = Number.NEGATIVE_INFINITY; - for (let dx = -reach; dx <= reach; dx += 16) - for (let dz = -reach; dz <= reach; dz += 16) { - const ground = groundAt(field, extent, x + dx, z + dz); - if (ground !== undefined) top = Math.max(top, ground); - } - return top; -} - -/** Metres from a point to the nearest other fir's surface. Infinite when there is no other. */ -function clearance(props: readonly IProp[], point: IPoint, anchor: IProp): number { - let nearest = Number.POSITIVE_INFINITY; - for (const other of props) - if (other !== anchor) nearest = Math.min(nearest, distance(other, point) - other.radius); - return nearest; -} - -/** Whether a point is under the sea, past the world's edge, or within `margin` metres above the sea. */ -function wetAt( - field: Field, - extent: Extent, - seaLevel: number, - point: IPoint, - margin: number, -): boolean { - const ground = groundAt(field, extent, point.x, point.z); - return ground === undefined || ground < seaLevel + margin; -} - -/** The walk from the spawn to the anchor climbs gently, stays dry and passes no other fir. */ -function walkable( - field: Field, - extent: Extent, - seaLevel: number, - props: readonly IProp[], - anchor: IProp, - spawn: IPoint, -): boolean { - const length = distance(anchor, spawn); - for (let along = 0; along <= length - 1.5; along += 0.25) { - const point = { - x: spawn.x + ((anchor.x - spawn.x) * along) / length, - z: spawn.z + ((anchor.z - spawn.z) * along) / length, - }; - if (slopeAt(field, extent, point.x, point.z) > WALK_SLOPE) return false; - if (wetAt(field, extent, seaLevel, point, DRY_HEIGHT)) return false; - for (const other of props) - if (other !== anchor && distance(other, point) < other.radius + RADIUS + 0.3) return false; - } - return true; -} - -/** The clearest of 24 points a ring of metres from the anchor that is gentle, dry and walkable. */ -function spawnFor( - field: Field, - extent: Extent, - seaLevel: number, - props: readonly IProp[], - anchor: IProp, -): IPoint | undefined { - let best: IPoint | undefined; - let bestClear = RADIUS + 0.5; - for (let step = 0; step < 24; step += 1) { - const angle = (2 * Math.PI * step) / 24; - const spawn = { - x: anchor.x + SPAWN_RING * Math.cos(angle), - z: anchor.z + SPAWN_RING * Math.sin(angle), - }; - if (wetAt(field, extent, seaLevel, spawn, DRY_HEIGHT)) continue; - if ( - slopeAt(field, extent, spawn.x, spawn.z) > SPAWN_SLOPE || - !walkable(field, extent, seaLevel, props, anchor, spawn) - ) - continue; - const clear = clearance(props, spawn, anchor); - if (clear > bestClear) { - bestClear = clear; - best = spawn; - } - } - return best; -} - -/** The clearest point 25 to 40 m from the anchor, on the sun's side, inside the world and dry. */ -function edgeFor( - field: Field, - extent: Extent, - seaLevel: number, - props: readonly IProp[], - anchor: IProp, -): IPoint { - let best: IPoint | undefined; - let bestClear = Number.NEGATIVE_INFINITY; - for (let step = 0; step < 24; step += 1) - for (const reach of [25, 30, 35, 40]) { - const angle = (2 * Math.PI * step) / 24; - const off = Math.abs( - Math.atan2(Math.sin(angle - SUN_AZIMUTH), Math.cos(angle - SUN_AZIMUTH)), - ); - if (off > Math.PI / 3) continue; - const point = { - x: anchor.x + reach * Math.cos(angle), - z: anchor.z + reach * Math.sin(angle), - }; - if (!inside(extent, point, 5)) continue; - if (wetAt(field, extent, seaLevel, point, DRY_HEIGHT)) continue; - const clear = Math.min(...props.map((prop) => distance(prop, point))); - if (clear > bestClear) { - bestClear = clear; - best = point; - } - } - if (best === undefined) - throw new Error("Coastal kit: no dry edge camera point on the sun's side of the anchor."); - return best; -} - -/** The four fixed views, each placed from the anchor, the spawn and the terrain under them. */ -function viewsFor( - field: Field, - props: readonly IProp[], - extent: Extent, - seaLevel: number, - centre: IPoint, - anchor: IProp, - spawn: IPoint, - away: IPoint, -): IStand["views"] { - const anchorGround = field.heightAt(anchor.x, anchor.z); - const target = new Vector3(anchor.x, anchorGround + 0.9, anchor.z); - const eyeGround = field.heightAt(spawn.x, spawn.z); - const edge = edgeFor(field, extent, seaLevel, props, anchor); - // Beside the centre's side of the anchor, so the camera stays inside the world however the anchor lies. - const overviewX = anchor.x + (anchor.x > centre.x ? -160 : 160); - const overviewZ = anchor.z + (anchor.z > centre.z ? -160 : 160); - // The fir view stands 9 m behind the anchor, on the side away from the spawn. Beside the world's - // edge that side may leave the world, so the camera takes the spawn's side instead. - const sign = inside(extent, { x: anchor.x - away.x * 9, z: anchor.z - away.z * 9 }, 0) ? 1 : -1; - const behind = { x: anchor.x - away.x * 9 * sign, z: anchor.z - away.z * 9 * sign }; - const treeGround = groundAt(field, extent, behind.x, behind.z); - if (treeGround === undefined) - throw new Error("Coastal kit: the fir view has no ground inside the world."); - return { - ground: { - at: new Vector3(spawn.x, eyeGround + EYE, spawn.z), - look: new Vector3(spawn.x + away.x * 20, eyeGround + EYE, spawn.z + away.z * 20), - }, - edge: { - at: new Vector3(edge.x, field.heightAt(edge.x, edge.z) + 6, edge.z), - look: target, - }, - overview: { - at: new Vector3( - overviewX, - highestNear(field, extent, overviewX, overviewZ, 200) + 90, - overviewZ, - ), - look: target, - }, - boulders: { - at: new Vector3(behind.x, treeGround + 3.2, behind.z), - look: new Vector3(anchor.x, anchorGround + 0.6, anchor.z), - }, - }; -} - -/** The stand the scene is written around: the nearest fir to the centre with a walkable, dry spawn. */ -function chooseStand(coastal: ICoastalWorld): IStand { - const { field, extent, seaLevel } = coastal; - const props = readProps(coastal.placements); - const centre = { x: extent.minX + extent.sizeX / 2, z: extent.minZ + extent.sizeZ / 2 }; - // Every fir may anchor the stand. The nearest one to the centre with a walkable spawn wins. - const candidates = props.sort((a, b) => distance(a, centre) - distance(b, centre)); - for (const anchor of candidates) { - // A fir on the shore is no stand: its ring and its walk would start in the sea. - if (wetAt(field, extent, seaLevel, anchor, DRY_HEIGHT + 2)) continue; - const spawn = spawnFor(field, extent, seaLevel, props, anchor); - if (spawn === undefined) continue; - const away = { x: (spawn.x - anchor.x) / SPAWN_RING, z: (spawn.z - anchor.z) / SPAWN_RING }; - return { - anchor, - spawn, - groundY: field.heightAt(spawn.x, spawn.z), - views: viewsFor(field, props, extent, seaLevel, centre, anchor, spawn, away), - }; - } - throw new Error("Coastal kit: no fir on dry ground has a walkable spawn."); -} - -function disposeCoastal(coastal: ICoastalWorld): void { - coastal.world.dispose(); - coastal.colliders.detach(); - coastal.ground.dispose(); - coastal.sea.dispose(); -} - -export class Coastal extends Scene { - static override readonly initialState: GameState = initialState; - - #player: Object3D | undefined; - #body: CharacterBody3D | undefined; - #camera: PerspectiveCamera | undefined; - #view: ViewName = "ground"; - #frames = 0; - #driveLeft = DRIVE_SECONDS; - #driven = 0; - #closest = Number.POSITIVE_INFINITY; - #settled = false; - #coastal: ICoastalWorld | undefined; - #stand: IStand | undefined; - #released = false; - #admission: ReturnType | undefined; - #loading: ReturnType | undefined; - - /** The kit's world streams while the scene loads, so `ready` waits for the cells the spawn needs. */ - override async load(ctx: Ctx): Promise { - for (const view of VIEW_NAMES) viewWindows.set(view, { frameP95: [], gpuP50: [], gpuP95: [] }); - const admission = createSpawnReadiness("Coastal spawn"); - this.#admission = admission; - void admission.promise.catch((error: unknown) => { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - }); - if (ctx.startup.phase !== "ready") - ctx.startup.hold("coastal-spawn", admission.promise, 120_000); - let coastal: ICoastalWorld | undefined; - // A holder rather than a `let`: the progress getter reads it, and the spawn is set once it is known. - const spawnView: { at?: Vector3 } = {}; - this.#loading = createLoadingScreen({ - ...ctx, - startup: { - get progress() { - // The spawn is unknown until the world resolves, so the bar reads the startup alone until then. - const region = spawnView.at && coastal?.world.readinessAt(spawnView.at, 60); - const required = (region?.requiredCells ?? 0) + (region?.requiredTerrainTiles ?? 0); - const loaded = (region?.loadedCells ?? 0) + (region?.loadedTerrainTiles ?? 0); - return Math.min( - 0.99, - (ctx.startup.progress + (required > 0 ? loaded / required : 0)) / 2, - ); - }, - whenReady: () => - Promise.all([ctx.startup.whenReady(), admission.promise]).then(() => undefined), - }, - }); - try { - coastal = await addCoastal(ctx, ctx.camera, undefined, () => !this.#released); - } catch (error) { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - return; - } - this.#coastal = coastal; - if (this.#released) { - disposeCoastal(coastal); - return; - } - let stand: IStand; - try { - stand = chooseStand(coastal); - } catch (error) { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - return; - } - this.#stand = stand; - spawnView.at = stand.views.ground.at; - const player = new Object3D(); - player.position.set(stand.spawn.x, stand.groundY + HALF_HEIGHT + RADIUS + 2.5, stand.spawn.z); - this.#player = player; - // The camera moves to the spawn before the world finishes streaming, so the cells it needs load first. - ctx.camera.position.copy(stand.views.ground.at); - ctx.camera.lookAt(stand.views.ground.look); - ctx.state.set({ - propColliders: coastal.colliders.active, - // The sea counts only while its mesh is in the scene, so the playtest reads what was drawn. - waterLakes: coastal.sea.mesh.parent === null ? 0 : 1, - waterRivers: 0, - worldReady: 0, - }); - } - - override enter(ctx: Ctx): void { - ctx.add(coastalDaylight(ctx.camera)); - if (!this.#coastal || !this.#stand) { - ctx.beforeRender(() => this.#loading?.update()); - return; - } - ctx.add(this.#player as Object3D); - // The body joins the world once its object is in the scene, as the template player does. - this.#body = new CharacterBody3D({ - object: this.#player as Object3D, - physics: ctx.physics, - shape: CollisionShape3D.capsule(HALF_HEIGHT, RADIUS), - }); - const camera = ctx.camera as PerspectiveCamera; - // The kit's sky box and the streamed cells both live far out. The far plane has to clear them. - camera.far = 5000; - camera.updateProjectionMatrix(); - this.#camera = camera; - this.#viewNow(ctx); - ctx.beforeRender(() => { - this.#loading?.update(); - const coastal = this.#coastal; - const stand = this.#stand; - if (!coastal || !stand) return; - const region = coastal.world.readinessAt(stand.views.ground.at, 60); - if (!region) return; - ctx.state.set({ - spawnCellsLoaded: region.loadedCells, - spawnCellsRequired: region.requiredCells, - spawnTerrainLoaded: region.loadedTerrainTiles, - spawnTerrainRequired: region.requiredTerrainTiles, - }); - if (this.#released) return; - this.#admission?.observe( - region.ready && coastal.world.stats().pendingPrewarm === 0, - region.failures, - ); - ctx.state.set({ - worldReady: this.#admission?.ready ? 1 : 0, - loadingError: this.#admission?.error ?? "", - }); - }); - } - - #fail(ctx: Ctx, message: string): void { - if (this.#released || ctx.state.getState().loadingError) return; - this.#admission?.fail(message); - ctx.state.set({ - loadingError: this.#admission?.error ?? message, - worldReady: this.#admission?.ready ? 1 : 0, - }); - } - - override exit(): void { - this.#released = true; - this.#admission?.cancel(); - this.#loading?.finish(); - this.#body?.dispose(); - if (this.#coastal) disposeCoastal(this.#coastal); - } - - override update(ctx: Ctx, dt: number): void { - const body = this.#body; - const player = this.#player; - const stand = this.#stand; - if (body === undefined || player === undefined || stand === undefined) return; - if ( - !this.#admission?.ready || - ctx.startup.phase !== "ready" || - ctx.state.getState().loadingError - ) { - body.velocity.x = 0; - body.velocity.z = 0; - body.moveAndSlide(dt); - return; - } - this.#frames += 1; - - const driving = this.#aim(body, player, stand); - body.moveAndSlide(dt); - if (driving) this.#measureDrive(ctx, player, stand, dt); - if (body.grounded && this.#frames > 30) this.#measureGround(ctx, player, stand.groundY); - - for (const view of VIEW_NAMES) if (ctx.input.justPressed(view)) this.#setView(ctx, view); - this.#placeCamera(); - if (this.#frames % 60 === 0) - ctx.state.set({ - treeInstances: this.#coastal?.world.stats().instances ?? -1, - propColliders: this.#coastal?.colliders.active ?? -1, - }); - ctx.state.set({ - driveMetres: this.#driven, - frames: this.#frames, - viewFrameP95: this.#costs("frameP95"), - viewGpuP50: this.#costs("gpuP50"), - viewGpuP95: this.#costs("gpuP95"), - }); - } - - /** Aims the body at the anchor while the drive lasts. Returns whether the body is driving this frame. */ - #aim(body: CharacterBody3D, player: Object3D, stand: IStand): boolean { - const towards = new Vector3( - stand.anchor.x - player.position.x, - 0, - stand.anchor.z - player.position.z, - ); - const driving = this.#driveLeft > 0 && towards.length() > 0.001; - if (!driving) { - body.velocity.x = 0; - body.velocity.z = 0; - return false; - } - towards.normalize(); - body.velocity.x = towards.x * MOVE_SPEED; - body.velocity.z = towards.z * MOVE_SPEED; - return true; - } - - /** Counts the drive down, and records how far the player went and how close it came to the anchor. */ - #measureDrive(ctx: Ctx, player: Object3D, stand: IStand, dt: number): void { - this.#driveLeft -= dt; - // The body writes its object back after the physics step, so distance is read from the spawn. - this.#driven = Math.hypot(player.position.x - stand.spawn.x, player.position.z - stand.spawn.z); - // The clearance the fir's own collider leaves, sampled every frame it walked. - this.#closest = Math.min( - this.#closest, - Math.hypot(player.position.x - stand.anchor.x, player.position.z - stand.anchor.z), - ); - if (this.#driveLeft <= 0) ctx.state.set({ closestToTree: this.#closest, driveDone: 1 }); - } - - #setView(ctx: Ctx, view: ViewName): void { - if (this.#view === view) return; - this.#view = view; - currentView = view; - this.#placeCamera(); - ctx.state.set({ view }); - } - - #viewNow(ctx: Ctx): void { - this.#view = "ground"; - currentView = "ground"; - this.#placeCamera(); - ctx.state.set({ view: "ground" }); - } - - /** Placed every frame: the engine reconciles the camera, so a pose written once is not a pose. */ - #placeCamera(): void { - const view = this.#stand?.views[this.#view]; - if (view === undefined || this.#camera === undefined) return; - this.#camera.position.copy(view.at); - this.#camera.lookAt(view.look); - } - - /** |foot − terrain|, from the collider the player is standing on. Measured once it has settled. */ - #measureGround(ctx: Ctx, player: Object3D, groundY: number): void { - if (this.#settled) return; - this.#settled = true; - // A ray from above would hit the player's own capsule first, so the ground is read beside it: - // four rays half a metre out, whose mean cancels any planar slope. - let sum = 0; - for (const [dx, dz] of [ - [0.5, 0], - [-0.5, 0], - [0, 0.5], - [0, -0.5], - ] as const) { - const at = { x: player.position.x + dx, z: player.position.z + dz }; - const hit = ctx.physics.directSpaceState.intersectRay({ - from: { ...at, y: player.position.y + 50 }, - to: { ...at, y: groundY - 100 }, - }); - if (hit == null) - throw new Error(`No terrain contact beside the player at ${player.position.toArray()}.`); - sum += hit.position.y; - } - ctx.state.set({ - groundError: Math.abs(player.position.y - HALF_HEIGHT - RADIUS - sum / 4), - }); - } - - #costs(key: "frameP95" | "gpuP50" | "gpuP95"): Record { - const costs: Record = {}; - for (const [view, group] of viewWindows) costs[view] = median(group[key]); - return costs; - } -} diff --git a/examples/strata-terrain-preview/scripts/fixtures/kit-game/Desert.ts b/examples/strata-terrain-preview/scripts/fixtures/kit-game/Desert.ts deleted file mode 100644 index 1fcfd8a903..0000000000 --- a/examples/strata-terrain-preview/scripts/fixtures/kit-game/Desert.ts +++ /dev/null @@ -1,558 +0,0 @@ -// An ordinary game scene standing in the desert starter kit. The kit streams the world, textures its -// ground and gives the boulders their collision; this file owns the daylight, the camera, the player -// and every number the playtest reads. Nothing here imports the authoring package: the kit was copied -// into `src/terrain/desert/` and its bake is a build step. The spawn and the views are read from the -// loaded heightfield and placements, so the scene keeps no coordinate the recipe does not make. -import { type ICtx, type IFrameBudgetWindow, Scene } from "@threenative/core"; -import { CharacterBody3D, CollisionShape3D, type IPhysicsContext } from "@threenative/physics"; -import { Object3D, type PerspectiveCamera, Vector3 } from "three"; -import { createLoadingScreen, createSpawnReadiness } from "../render/loading.js"; -import { type GameState, initialState } from "../state.js"; -import { desertDaylight } from "../terrain/desert/sky.js"; -import { COLLIDERS, type IDesertWorld, addDesert } from "../terrain/desert/world.js"; - -type Ctx = ICtx; -type Field = IDesertWorld["field"]; -type Extent = IDesertWorld["extent"]; - -/** A 1.8 m figure: 0.55 m of half-height plus the 0.35 m radius a boulder's clearance is read against. */ -const HALF_HEIGHT = 0.55; -const RADIUS = 0.35; -const MOVE_SPEED = 3; -/** Long enough to cover the spawn ring and be stopped by the boulder it is aimed at. */ -const DRIVE_SECONDS = 3; -const EYE = 1.7; -/** Metres from the anchor boulder's origin to the spawn: far enough to walk, near enough to measure. */ -const SPAWN_RING = 5; -/** The steepest rise over run, per metre, that the spawn or the walk to the anchor may use. */ -const SPAWN_SLOPE = 0.25; -const WALK_SLOPE = 0.35; -/** The desert daylight's sun (`sky.ts`). Its azimuth picks the side the edge camera stands on. */ -const SUN_AZIMUTH = Math.atan2(0.56, 0.55); -const VIEW_NAMES = ["boulders", "edge", "ground", "overview"] as const; -type ViewName = (typeof VIEW_NAMES)[number]; - -interface IPoint { - readonly x: number; - readonly z: number; -} - -/** One placed boulder, read from its record: where it stands and the radius its collider uses. */ -interface IProp extends IPoint { - readonly y: number; - readonly radius: number; -} - -interface IView { - readonly at: Vector3; - readonly look: Vector3; -} - -/** Where the scene stands, looks and drives, all read from the loaded world. */ -interface IStand { - readonly anchor: IProp; - readonly spawn: IPoint; - readonly groundY: number; - readonly views: Readonly>; -} - -/** One view's closed frame-budget windows, read by {@link noteFrameBudget}. */ -const viewWindows = new Map(); -let currentView = "ground"; - -/** The engine's own frame meter, grouped by the view that was on screen. */ -export function noteFrameBudget(window: IFrameBudgetWindow): void { - const group = viewWindows.get(currentView); - // A window with no `gpu` resolved no timestamp: absent, not zero. - if (group === undefined || window.gpu === undefined) return; - group.gpuP50.push(window.gpu.p50); - group.gpuP95.push(window.gpu.p95); - group.frameP95.push(window.frame.p95); -} - -/** The middle of a sample set, for a summary that one outlier cannot move. */ -function median(values: readonly number[]): number { - if (values.length === 0) return -1; - const sorted = [...values].sort((a, b) => a - b); - return sorted[Math.floor(sorted.length / 2)] ?? -1; -} - -/** Every placed boulder. The kit's only asset is the boulder, so every record is one. */ -function readProps(placements: Float32Array): IProp[] { - const boulder = COLLIDERS.boulder; - if (!boulder) throw new Error("Desert kit: the boulder has no collider in COLLIDERS."); - const props: IProp[] = []; - for (let index = 0; index + 8 <= placements.length; index += 8) - props.push({ - x: placements[index] as number, - y: placements[index + 1] as number, - z: placements[index + 2] as number, - radius: boulder.sphere * (placements[index + 7] as number), - }); - return props; -} - -function distance(a: IPoint, b: IPoint): number { - return Math.hypot(a.x - b.x, a.z - b.z); -} - -function inside(extent: Extent, point: IPoint, margin: number): boolean { - return ( - point.x > extent.minX + margin && - point.x < extent.minX + extent.sizeX - margin && - point.z > extent.minZ + margin && - point.z < extent.minZ + extent.sizeZ - margin - ); -} - -/** The ground under a point, or undefined when the point lies outside the world's heightfield. */ -function groundAt(field: Field, extent: Extent, x: number, z: number): number | undefined { - return inside(extent, { x, z }, 0) ? field.heightAt(x, z) : undefined; -} - -/** Rise over run at a point, from the heights half a metre either side on each axis. */ -function slopeAt(field: Field, extent: Extent, x: number, z: number): number { - const plusX = groundAt(field, extent, x + 0.5, z); - const minusX = groundAt(field, extent, x - 0.5, z); - const plusZ = groundAt(field, extent, x, z + 0.5); - const minusZ = groundAt(field, extent, x, z - 0.5); - // A slope that reaches past the world's edge is not walkable, so it reads as infinitely steep. - if (plusX === undefined || minusX === undefined || plusZ === undefined || minusZ === undefined) - return Number.POSITIVE_INFINITY; - return Math.hypot(plusX - minusX, plusZ - minusZ); -} - -/** The highest ground within `reach` metres of a point, sampled every 16 m inside the world. */ -function highestNear(field: Field, extent: Extent, x: number, z: number, reach: number): number { - let top = Number.NEGATIVE_INFINITY; - for (let dx = -reach; dx <= reach; dx += 16) - for (let dz = -reach; dz <= reach; dz += 16) { - const ground = groundAt(field, extent, x + dx, z + dz); - if (ground !== undefined) top = Math.max(top, ground); - } - return top; -} - -/** Metres from a point to the nearest other boulder's surface. Infinite when there is no other. */ -function clearance(props: readonly IProp[], point: IPoint, anchor: IProp): number { - let nearest = Number.POSITIVE_INFINITY; - for (const other of props) - if (other !== anchor) nearest = Math.min(nearest, distance(other, point) - other.radius); - return nearest; -} - -/** The walk from the spawn to the anchor climbs gently and passes no other boulder. */ -function walkable( - field: Field, - extent: Extent, - props: readonly IProp[], - anchor: IProp, - spawn: IPoint, -): boolean { - const length = distance(anchor, spawn); - for (let along = 0; along <= length - 1.5; along += 0.25) { - const point = { - x: spawn.x + ((anchor.x - spawn.x) * along) / length, - z: spawn.z + ((anchor.z - spawn.z) * along) / length, - }; - if (slopeAt(field, extent, point.x, point.z) > WALK_SLOPE) return false; - for (const other of props) - if (other !== anchor && distance(other, point) < other.radius + RADIUS + 0.3) return false; - } - return true; -} - -/** The clearest of 24 points a ring of metres from the anchor that is gentle, dry and walkable. */ -function spawnFor( - field: Field, - extent: Extent, - props: readonly IProp[], - anchor: IProp, -): IPoint | undefined { - let best: IPoint | undefined; - let bestClear = RADIUS + 0.5; - for (let step = 0; step < 24; step += 1) { - const angle = (2 * Math.PI * step) / 24; - const spawn = { - x: anchor.x + SPAWN_RING * Math.cos(angle), - z: anchor.z + SPAWN_RING * Math.sin(angle), - }; - if ( - slopeAt(field, extent, spawn.x, spawn.z) > SPAWN_SLOPE || - !walkable(field, extent, props, anchor, spawn) - ) - continue; - const clear = clearance(props, spawn, anchor); - if (clear > bestClear) { - bestClear = clear; - best = spawn; - } - } - return best; -} - -/** The clearest point 25 to 40 m from the anchor, on the sun's side and inside the world. */ -function edgeFor(props: readonly IProp[], anchor: IProp, extent: Extent): IPoint { - let best: IPoint | undefined; - let bestClear = Number.NEGATIVE_INFINITY; - for (let step = 0; step < 24; step += 1) - for (const reach of [25, 30, 35, 40]) { - const angle = (2 * Math.PI * step) / 24; - const off = Math.abs( - Math.atan2(Math.sin(angle - SUN_AZIMUTH), Math.cos(angle - SUN_AZIMUTH)), - ); - if (off > Math.PI / 3) continue; - const point = { - x: anchor.x + reach * Math.cos(angle), - z: anchor.z + reach * Math.sin(angle), - }; - if (!inside(extent, point, 5)) continue; - const clear = Math.min(...props.map((prop) => distance(prop, point))); - if (clear > bestClear) { - bestClear = clear; - best = point; - } - } - if (best === undefined) - throw new Error("Desert kit: no edge camera point on the sun's side of the anchor."); - return best; -} - -/** The four fixed views, each placed from the anchor, the spawn and the terrain under them. */ -function viewsFor( - field: Field, - props: readonly IProp[], - extent: Extent, - centre: IPoint, - anchor: IProp, - spawn: IPoint, - away: IPoint, -): IStand["views"] { - const anchorGround = field.heightAt(anchor.x, anchor.z); - const target = new Vector3(anchor.x, anchorGround + 0.9, anchor.z); - const eyeGround = field.heightAt(spawn.x, spawn.z); - const edge = edgeFor(props, anchor, extent); - // Beside the centre's side of the anchor, so the camera stays inside the world however the anchor lies. - const overviewX = anchor.x + (anchor.x > centre.x ? -160 : 160); - const overviewZ = anchor.z + (anchor.z > centre.z ? -160 : 160); - // The boulder view stands 9 m behind the anchor, on the side away from the spawn. Beside the world's - // edge that side may leave the world, so the camera takes the spawn's side instead. - const sign = inside(extent, { x: anchor.x - away.x * 9, z: anchor.z - away.z * 9 }, 0) ? 1 : -1; - const behind = { x: anchor.x - away.x * 9 * sign, z: anchor.z - away.z * 9 * sign }; - const boulderGround = groundAt(field, extent, behind.x, behind.z); - if (boulderGround === undefined) - throw new Error("Desert kit: the boulder view has no ground inside the world."); - return { - ground: { - at: new Vector3(spawn.x, eyeGround + EYE, spawn.z), - look: new Vector3(spawn.x + away.x * 20, eyeGround + EYE, spawn.z + away.z * 20), - }, - edge: { - at: new Vector3(edge.x, field.heightAt(edge.x, edge.z) + 6, edge.z), - look: target, - }, - overview: { - at: new Vector3( - overviewX, - highestNear(field, extent, overviewX, overviewZ, 200) + 90, - overviewZ, - ), - look: target, - }, - boulders: { - at: new Vector3(behind.x, boulderGround + 3.2, behind.z), - look: new Vector3(anchor.x, anchorGround + 0.6, anchor.z), - }, - }; -} - -/** The stand the scene is written around: the nearest boulder to the centre with a walkable spawn. */ -function chooseStand(desert: IDesertWorld): IStand { - const { field, extent } = desert; - const props = readProps(desert.placements); - const centre = { x: extent.minX + extent.sizeX / 2, z: extent.minZ + extent.sizeZ / 2 }; - // Every boulder may anchor the stand. The nearest one to the centre with a walkable spawn wins. - const candidates = props.sort((a, b) => distance(a, centre) - distance(b, centre)); - for (const anchor of candidates) { - const spawn = spawnFor(field, extent, props, anchor); - if (spawn === undefined) continue; - const away = { x: (spawn.x - anchor.x) / SPAWN_RING, z: (spawn.z - anchor.z) / SPAWN_RING }; - return { - anchor, - spawn, - groundY: field.heightAt(spawn.x, spawn.z), - views: viewsFor(field, props, extent, centre, anchor, spawn, away), - }; - } - throw new Error("Desert kit: no boulder in the world has a walkable spawn."); -} - -function disposeDesert(desert: IDesertWorld): void { - desert.world.dispose(); - desert.colliders.detach(); - desert.ground.dispose(); -} - -export class Desert extends Scene { - static override readonly initialState: GameState = initialState; - - #player: Object3D | undefined; - #body: CharacterBody3D | undefined; - #camera: PerspectiveCamera | undefined; - #view: ViewName = "ground"; - #frames = 0; - #driveLeft = DRIVE_SECONDS; - #driven = 0; - #closest = Number.POSITIVE_INFINITY; - #settled = false; - #desert: IDesertWorld | undefined; - #stand: IStand | undefined; - #released = false; - #admission: ReturnType | undefined; - #loading: ReturnType | undefined; - - /** The kit's world streams while the scene loads, so `ready` waits for the cells the spawn needs. */ - override async load(ctx: Ctx): Promise { - for (const view of VIEW_NAMES) viewWindows.set(view, { frameP95: [], gpuP50: [], gpuP95: [] }); - const admission = createSpawnReadiness("Desert spawn"); - this.#admission = admission; - void admission.promise.catch((error: unknown) => { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - }); - if (ctx.startup.phase !== "ready") ctx.startup.hold("desert-spawn", admission.promise, 120_000); - let desert: IDesertWorld | undefined; - // A holder rather than a `let`: the progress getter reads it, and the spawn is set once it is known. - const spawnView: { at?: Vector3 } = {}; - this.#loading = createLoadingScreen({ - ...ctx, - startup: { - get progress() { - // The spawn is unknown until the world resolves, so the bar reads the startup alone until then. - const region = spawnView.at && desert?.world.readinessAt(spawnView.at, 60); - const required = (region?.requiredCells ?? 0) + (region?.requiredTerrainTiles ?? 0); - const loaded = (region?.loadedCells ?? 0) + (region?.loadedTerrainTiles ?? 0); - return Math.min( - 0.99, - (ctx.startup.progress + (required > 0 ? loaded / required : 0)) / 2, - ); - }, - whenReady: () => - Promise.all([ctx.startup.whenReady(), admission.promise]).then(() => undefined), - }, - }); - try { - desert = await addDesert(ctx, ctx.camera, undefined, () => !this.#released); - } catch (error) { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - return; - } - this.#desert = desert; - if (this.#released) { - disposeDesert(desert); - return; - } - let stand: IStand; - try { - stand = chooseStand(desert); - } catch (error) { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - return; - } - this.#stand = stand; - spawnView.at = stand.views.ground.at; - const player = new Object3D(); - player.position.set(stand.spawn.x, stand.groundY + HALF_HEIGHT + RADIUS + 2.5, stand.spawn.z); - this.#player = player; - // The camera moves to the spawn before the world finishes streaming, so the cells it needs load first. - ctx.camera.position.copy(stand.views.ground.at); - ctx.camera.lookAt(stand.views.ground.look); - ctx.state.set({ propColliders: desert.colliders.active, worldReady: 0 }); - } - - override enter(ctx: Ctx): void { - ctx.add(desertDaylight(ctx.camera)); - if (!this.#desert || !this.#stand) { - ctx.beforeRender(() => this.#loading?.update()); - return; - } - ctx.add(this.#player as Object3D); - // The body joins the world once its object is in the scene, as the template player does. - this.#body = new CharacterBody3D({ - object: this.#player as Object3D, - physics: ctx.physics, - shape: CollisionShape3D.capsule(HALF_HEIGHT, RADIUS), - }); - const camera = ctx.camera as PerspectiveCamera; - // The kit's sky box and the streamed cells both live far out. The far plane has to clear them. - camera.far = 5000; - camera.updateProjectionMatrix(); - this.#camera = camera; - this.#viewNow(ctx); - ctx.beforeRender(() => { - this.#loading?.update(); - const desert = this.#desert; - const stand = this.#stand; - if (!desert || !stand) return; - const region = desert.world.readinessAt(stand.views.ground.at, 60); - if (!region) return; - ctx.state.set({ - spawnCellsLoaded: region.loadedCells, - spawnCellsRequired: region.requiredCells, - spawnTerrainLoaded: region.loadedTerrainTiles, - spawnTerrainRequired: region.requiredTerrainTiles, - }); - if (this.#released) return; - this.#admission?.observe( - region.ready && desert.world.stats().pendingPrewarm === 0, - region.failures, - ); - ctx.state.set({ - worldReady: this.#admission?.ready ? 1 : 0, - loadingError: this.#admission?.error ?? "", - }); - }); - } - - #fail(ctx: Ctx, message: string): void { - if (this.#released || ctx.state.getState().loadingError) return; - this.#admission?.fail(message); - ctx.state.set({ - loadingError: this.#admission?.error ?? message, - worldReady: this.#admission?.ready ? 1 : 0, - }); - } - - override exit(): void { - this.#released = true; - this.#admission?.cancel(); - this.#loading?.finish(); - this.#body?.dispose(); - if (this.#desert) disposeDesert(this.#desert); - } - - override update(ctx: Ctx, dt: number): void { - const body = this.#body; - const player = this.#player; - const stand = this.#stand; - if (body === undefined || player === undefined || stand === undefined) return; - if ( - !this.#admission?.ready || - ctx.startup.phase !== "ready" || - ctx.state.getState().loadingError - ) { - body.velocity.x = 0; - body.velocity.z = 0; - body.moveAndSlide(dt); - return; - } - this.#frames += 1; - - const driving = this.#aim(body, player, stand); - body.moveAndSlide(dt); - if (driving) this.#measureDrive(ctx, player, stand, dt); - if (body.grounded && this.#frames > 30) this.#measureGround(ctx, player, stand.groundY); - - for (const view of VIEW_NAMES) if (ctx.input.justPressed(view)) this.#setView(ctx, view); - this.#placeCamera(); - if (this.#frames % 60 === 0) - ctx.state.set({ - boulderInstances: this.#desert?.world.stats().instances ?? -1, - propColliders: this.#desert?.colliders.active ?? -1, - }); - ctx.state.set({ - driveMetres: this.#driven, - frames: this.#frames, - viewFrameP95: this.#costs("frameP95"), - viewGpuP50: this.#costs("gpuP50"), - viewGpuP95: this.#costs("gpuP95"), - }); - } - - /** Aims the body at the anchor while the drive lasts. Returns whether the body is driving this frame. */ - #aim(body: CharacterBody3D, player: Object3D, stand: IStand): boolean { - const towards = new Vector3( - stand.anchor.x - player.position.x, - 0, - stand.anchor.z - player.position.z, - ); - const driving = this.#driveLeft > 0 && towards.length() > 0.001; - if (!driving) { - body.velocity.x = 0; - body.velocity.z = 0; - return false; - } - towards.normalize(); - body.velocity.x = towards.x * MOVE_SPEED; - body.velocity.z = towards.z * MOVE_SPEED; - return true; - } - - /** Counts the drive down, and records how far the player went and how close it came to the anchor. */ - #measureDrive(ctx: Ctx, player: Object3D, stand: IStand, dt: number): void { - this.#driveLeft -= dt; - // The body writes its object back after the physics step, so distance is read from the spawn. - this.#driven = Math.hypot(player.position.x - stand.spawn.x, player.position.z - stand.spawn.z); - // The clearance the boulder's own collider leaves, sampled every frame it walked. - this.#closest = Math.min( - this.#closest, - Math.hypot(player.position.x - stand.anchor.x, player.position.z - stand.anchor.z), - ); - if (this.#driveLeft <= 0) ctx.state.set({ closestToBoulder: this.#closest, driveDone: 1 }); - } - - #setView(ctx: Ctx, view: ViewName): void { - if (this.#view === view) return; - this.#view = view; - currentView = view; - this.#placeCamera(); - ctx.state.set({ view }); - } - - #viewNow(ctx: Ctx): void { - this.#view = "ground"; - currentView = "ground"; - this.#placeCamera(); - ctx.state.set({ view: "ground" }); - } - - /** Placed every frame: the engine reconciles the camera, so a pose written once is not a pose. */ - #placeCamera(): void { - const view = this.#stand?.views[this.#view]; - if (view === undefined || this.#camera === undefined) return; - this.#camera.position.copy(view.at); - this.#camera.lookAt(view.look); - } - - /** |foot − terrain|, from the collider the player is standing on. Measured once it has settled. */ - #measureGround(ctx: Ctx, player: Object3D, groundY: number): void { - if (this.#settled) return; - this.#settled = true; - // A ray from above would hit the player's own capsule first, so the ground is read beside it: - // four rays half a metre out, whose mean cancels any planar slope. - let sum = 0; - for (const [dx, dz] of [ - [0.5, 0], - [-0.5, 0], - [0, 0.5], - [0, -0.5], - ] as const) { - const at = { x: player.position.x + dx, z: player.position.z + dz }; - const hit = ctx.physics.directSpaceState.intersectRay({ - from: { ...at, y: player.position.y + 50 }, - to: { ...at, y: groundY - 100 }, - }); - if (hit == null) - throw new Error(`No terrain contact beside the player at ${player.position.toArray()}.`); - sum += hit.position.y; - } - ctx.state.set({ - groundError: Math.abs(player.position.y - HALF_HEIGHT - RADIUS - sum / 4), - }); - } - - #costs(key: "frameP95" | "gpuP50" | "gpuP95"): Record { - const costs: Record = {}; - for (const [view, group] of viewWindows) costs[view] = median(group[key]); - return costs; - } -} diff --git a/examples/strata-terrain-preview/scripts/fixtures/kit-game/Forest.ts b/examples/strata-terrain-preview/scripts/fixtures/kit-game/Forest.ts deleted file mode 100644 index 7984fb2d68..0000000000 --- a/examples/strata-terrain-preview/scripts/fixtures/kit-game/Forest.ts +++ /dev/null @@ -1,348 +0,0 @@ -// An ordinary game scene standing in the forest starter kit. The kit streams the world, textures -// its ground and gives the firs and boulders their collision; this file owns the daylight, the -// camera, the player and every number the playtest reads. Nothing here imports the authoring -// package: the kit was copied into `src/terrain/forest/` and its bake is a build step. -import { type ICtx, type IFrameBudgetWindow, Scene } from "@threenative/core"; -import { CharacterBody3D, CollisionShape3D, type IPhysicsContext } from "@threenative/physics"; -import { type InstancedMesh, Object3D, type PerspectiveCamera, Vector3 } from "three"; -import { createLoadingScreen, createSpawnReadiness } from "../render/loading.js"; -import { type GameState, initialState } from "../state.js"; -import { forestDaylight } from "../terrain/forest/sky.js"; -import { stand } from "../terrain/forest/stand.js"; -import { addForest } from "../terrain/forest/world.js"; - -type Ctx = ICtx; - -/** A 1.8 m figure: 0.55 m of half-height plus the 0.35 m radius the fir's clearance is read against. */ -const HALF_HEIGHT = 0.55; -const RADIUS = 0.35; -const MOVE_SPEED = 3; -/** Long enough to cover the four metres and be stopped by the trunk it is aimed at. */ -const DRIVE_SECONDS = 3; -const EYE = 1.7; -/** The fir's middle, the point the two close views look at. */ -const TRUNK = new Vector3(stand.fir.x, stand.groundY + 6, stand.fir.z); -/** From the fir towards the player's start; the open side of the stand. */ -const AWAY = new Vector3(stand.spawn.x - stand.fir.x, 0, stand.spawn.z - stand.fir.z).normalize(); - -/** The recipe's lake centre, on the water's level: the lake view looks at it from above the terrain. */ -const LAKE = new Vector3(-70, 13.3, 25); -/** The fixed views the scenario captures. */ -// Eye-height cameras inside a 4.5 m stand sit inside crowns, whose one-sided cards vanish from -// behind; so the close view looks out of the clearing and the edge view stands back from the stand. -const VIEWS = { - ground: { - at: new Vector3(stand.spawn.x, stand.groundY + EYE, stand.spawn.z), - look: new Vector3(stand.spawn.x, stand.groundY + EYE, stand.spawn.z).addScaledVector(AWAY, 20), - }, - edge: { - at: new Vector3(stand.edge.x, stand.groundY + 6, stand.edge.z), - look: TRUNK, - }, - overview: { - at: new Vector3(stand.fir.x + 160, stand.groundY + 220, stand.fir.z + 160), - look: TRUNK, - }, - // Above the tallest ground (about 38 m) and back far enough to frame the whole basin and its shore. - lake: { - at: new Vector3(20, 84, 115), - look: LAKE, - }, -} satisfies Readonly>; -const VIEW_NAMES = Object.keys(VIEWS); - -/** One view's closed frame-budget windows, read by {@link noteFrameBudget}. */ -const viewWindows = new Map(); -let currentView = "ground"; - -/** - * The engine's own frame meter, grouped by the view that was on screen. - * - * `budget` alone cannot answer "what did this stand cost at the overview": a scenario switches - * cameras faster than a window closes, so the one window the game holds belongs to whichever view - * happened to be current. Each view keeps its own windows and the scene publishes the medians. - */ -export function noteFrameBudget(window: IFrameBudgetWindow): void { - const group = viewWindows.get(currentView); - // A window with no `gpu` resolved no timestamp: absent, not zero. - if (group === undefined || window.gpu === undefined) return; - group.gpuP50.push(window.gpu.p50); - group.gpuP95.push(window.gpu.p95); - group.frameP95.push(window.frame.p95); -} - -/** The middle of a sample set, for a summary that one outlier cannot move. */ -function median(values: readonly number[]): number { - if (values.length === 0) return -1; - const sorted = [...values].sort((a, b) => a - b); - return sorted[Math.floor(sorted.length / 2)] ?? -1; -} - -export class Forest extends Scene { - static override readonly initialState: GameState = initialState; - - #player: Object3D | undefined; - #body: CharacterBody3D | undefined; - #camera: PerspectiveCamera | undefined; - #view = "ground"; - #frames = 0; - #driveLeft = DRIVE_SECONDS; - #driven = 0; - #closest = Number.POSITIVE_INFINITY; - #settled = false; - #forest: Awaited> | undefined; - #released = false; - #admission: ReturnType | undefined; - #loading: ReturnType | undefined; - - /** The kit's world streams while the scene loads, so `ready` waits for real streamed cells. */ - override async load(ctx: Ctx): Promise { - for (const view of VIEW_NAMES) viewWindows.set(view, { frameP95: [], gpuP50: [], gpuP95: [] }); - const player = new Object3D(); - player.position.set(stand.spawn.x, stand.groundY + HALF_HEIGHT + RADIUS + 2.5, stand.spawn.z); - // The world streams and picks detail around the camera, as a game whose camera follows its - // player does; the capture views move the camera far from the player. - this.#player = player; - ctx.camera.position.copy(VIEWS.ground.at); - ctx.camera.lookAt(VIEWS.ground.look); - const admission = createSpawnReadiness("Forest spawn"); - this.#admission = admission; - void admission.promise.catch((error: unknown) => { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - }); - if (ctx.startup.phase !== "ready") ctx.startup.hold("forest-spawn", admission.promise, 120_000); - let forest: Awaited> | undefined; - this.#loading = createLoadingScreen({ - ...ctx, - startup: { - get progress() { - const region = forest?.world.readinessAt(VIEWS.ground.at, 60); - const required = (region?.requiredCells ?? 0) + (region?.requiredTerrainTiles ?? 0); - const loaded = (region?.loadedCells ?? 0) + (region?.loadedTerrainTiles ?? 0); - return Math.min( - 0.99, - (ctx.startup.progress + (required > 0 ? loaded / required : 0)) / 2, - ); - }, - whenReady: () => - Promise.all([ctx.startup.whenReady(), admission.promise]).then(() => undefined), - }, - }); - try { - forest = await addForest(ctx, ctx.camera, undefined, () => !this.#released); - } catch (error) { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - return; - } - if (this.#released) { - forest.world.dispose(); - forest.colliders.detach(); - forest.ground.dispose(); - return; - } - this.#forest = forest; - this.#player = player; - ctx.state.set({ - propColliders: forest.colliders.active, - waterLakes: forest.water.lakes, - waterRivers: forest.water.rivers, - worldReady: 0, - }); - } - - override enter(ctx: Ctx): void { - ctx.add(forestDaylight(ctx.camera)); - if (!this.#forest) { - ctx.beforeRender(() => this.#loading?.update()); - return; - } - ctx.add(this.#player as Object3D); - // The body joins the world once its object is in the scene, as the template player does. - this.#body = new CharacterBody3D({ - object: this.#player as Object3D, - physics: ctx.physics, - shape: CollisionShape3D.capsule(HALF_HEIGHT, RADIUS), - }); - const camera = ctx.camera as PerspectiveCamera; - // The kit's sky box and the streamed cells both live far out; the far plane has to clear them. - camera.far = 5000; - camera.updateProjectionMatrix(); - this.#camera = camera; - this.#viewNow(ctx); - ctx.beforeRender(() => { - this.#loading?.update(); - const region = this.#forest?.world.readinessAt(VIEWS.ground.at, 60); - if (!region) return; - ctx.state.set({ - spawnCellsLoaded: region.loadedCells, - spawnCellsRequired: region.requiredCells, - spawnTerrainLoaded: region.loadedTerrainTiles, - spawnTerrainRequired: region.requiredTerrainTiles, - }); - if (this.#released) return; - this.#admission?.observe( - region.ready && this.#forest?.world.stats().pendingPrewarm === 0, - region.failures, - ); - ctx.state.set({ - worldReady: this.#admission?.ready ? 1 : 0, - loadingError: this.#admission?.error ?? "", - }); - }); - } - - #fail(ctx: Ctx, message: string): void { - if (this.#released || ctx.state.getState().loadingError) return; - this.#admission?.fail(message); - ctx.state.set({ - loadingError: this.#admission?.error ?? message, - worldReady: this.#admission?.ready ? 1 : 0, - }); - } - - override exit(): void { - this.#released = true; - this.#admission?.cancel(); - this.#loading?.finish(); - this.#body?.dispose(); - this.#forest?.world.dispose(); - this.#forest?.colliders.detach(); - this.#forest?.ground.dispose(); - } - - override update(ctx: Ctx, dt: number): void { - const body = this.#body; - const player = this.#player; - if (body === undefined || player === undefined) return; - if ( - !this.#admission?.ready || - ctx.startup.phase !== "ready" || - ctx.state.getState().loadingError - ) { - body.velocity.x = 0; - body.velocity.z = 0; - body.moveAndSlide(dt); - return; - } - this.#frames += 1; - - const towards = new Vector3( - stand.fir.x - player.position.x, - 0, - stand.fir.z - player.position.z, - ); - const distance = towards.length(); - const driving = this.#driveLeft > 0 && distance > 0.001; - if (driving) { - towards.normalize(); - body.velocity.x = towards.x * MOVE_SPEED; - body.velocity.z = towards.z * MOVE_SPEED; - } else { - body.velocity.x = 0; - body.velocity.z = 0; - } - body.moveAndSlide(dt); - if (driving) { - this.#driveLeft -= dt; - // The body writes its object back after the physics step, so distance is read from the spawn. - this.#driven = Math.hypot( - player.position.x - stand.spawn.x, - player.position.z - stand.spawn.z, - ); - // The clearance the fir's own capsule leaves, sampled every frame it walked. - this.#closest = Math.min( - this.#closest, - Math.hypot(player.position.x - stand.fir.x, player.position.z - stand.fir.z), - ); - if (this.#driveLeft <= 0) ctx.state.set({ closestToTrunk: this.#closest, driveDone: 1 }); - } - if (body.grounded && this.#frames > 30) this.#measureGround(ctx, player); - - for (const [action, view] of [ - ["ground", "ground"], - ["edge", "edge"], - ["overview", "overview"], - ["lake", "lake"], - ] as const) - if (ctx.input.justPressed(action)) this.#setView(ctx, view); - this.#placeCamera(); - if (this.#frames % 60 === 0) ctx.state.set({ firInstancesDrawn: this.#firInstances(ctx) }); - ctx.state.set({ - driveMetres: this.#driven, - frames: this.#frames, - viewFrameP95: this.#costs("frameP95"), - viewGpuP50: this.#costs("gpuP50"), - viewGpuP95: this.#costs("gpuP95"), - }); - } - - #setView(ctx: Ctx, view: string): void { - if (this.#view === view) return; - this.#view = view; - currentView = view; - this.#placeCamera(); - ctx.state.set({ view }); - } - - #viewNow(ctx: Ctx): void { - this.#view = "ground"; - currentView = "ground"; - this.#placeCamera(); - ctx.state.set({ view: "ground" }); - } - - /** Placed every frame: the engine reconciles the camera, so a pose written once is not a pose. */ - #placeCamera(): void { - const view = (VIEWS as Readonly>)[this.#view]; - if (view === undefined || this.#camera === undefined) return; - this.#camera.position.copy(view.at); - this.#camera.lookAt(view.look); - } - - /** |foot − terrain|, from the collider the player is standing on. Measured once it has settled. */ - #measureGround(ctx: Ctx, player: Object3D): void { - if (this.#settled) return; - this.#settled = true; - // A ray from above would hit the player's own capsule first, so the ground is read beside it: - // four rays half a metre out, whose mean cancels any planar slope. - let sum = 0; - for (const [dx, dz] of [ - [0.5, 0], - [-0.5, 0], - [0, 0.5], - [0, -0.5], - ] as const) { - const at = { x: player.position.x + dx, z: player.position.z + dz }; - const hit = ctx.physics.directSpaceState.intersectRay({ - from: { ...at, y: player.position.y + 50 }, - to: { ...at, y: stand.groundY - 100 }, - }); - if (hit == null) - throw new Error(`No terrain contact beside the player at ${player.position.toArray()}.`); - sum += hit.position.y; - } - ctx.state.set({ - groundError: Math.abs(player.position.y - HALF_HEIGHT - RADIUS - sum / 4), - }); - } - - /** Instances the renderer draws in the fir batches, its own visibility and every ancestor's. */ - #firInstances(ctx: Ctx): number { - let drawn = 0; - ctx.scene.traverse((object) => { - if (!(object as InstancedMesh).isInstancedMesh) return; - const mesh = object as InstancedMesh; - if (!mesh.name.includes("fir") || mesh.count < 1) return; - for (let node: Object3D | null = object; node !== null; node = node.parent) - if (node.visible === false) return; - drawn += mesh.count; - }); - return drawn; - } - - #costs(key: "frameP95" | "gpuP50" | "gpuP95"): Record { - const costs: Record = {}; - for (const [view, group] of viewWindows) costs[view] = median(group[key]); - return costs; - } -} diff --git a/examples/strata-terrain-preview/scripts/fixtures/kit-game/Alpine.ts b/examples/strata-terrain-preview/scripts/fixtures/kit-game/KitScene.ts similarity index 68% rename from examples/strata-terrain-preview/scripts/fixtures/kit-game/Alpine.ts rename to examples/strata-terrain-preview/scripts/fixtures/kit-game/KitScene.ts index 248fa4c1b0..39cf562905 100644 --- a/examples/strata-terrain-preview/scripts/fixtures/kit-game/Alpine.ts +++ b/examples/strata-terrain-preview/scripts/fixtures/kit-game/KitScene.ts @@ -1,19 +1,20 @@ -// An ordinary game scene standing in the alpine starter kit. The kit streams the world, textures its -// ground and gives the boulders their collision; this file owns the daylight, the camera, the player -// and every number the playtest reads. Nothing here imports the authoring package: the kit was copied -// into `src/terrain/alpine/` and its bake is a build step. The spawn and the views are read from the -// loaded heightfield and placements, so the scene keeps no coordinate the recipe does not make. import { type ICtx, type IFrameBudgetWindow, Scene } from "@threenative/core"; import { CharacterBody3D, CollisionShape3D, type IPhysicsContext } from "@threenative/physics"; -import { Object3D, type PerspectiveCamera, Vector3 } from "three"; -import { createLoadingScreen, createSpawnReadiness } from "../render/loading.js"; +import { type InstancedMesh, Object3D, type PerspectiveCamera, Vector3 } from "three"; +import { createLoadingScreen } from "../render/loading.js"; import { type GameState, initialState } from "../state.js"; -import { alpineDaylight } from "../terrain/alpine/sky.js"; -import { COLLIDERS, type IAlpineWorld, addAlpine } from "../terrain/alpine/world.js"; +import { + COLLIDERS, + type IKitWorld, + KIT, + addKit, + forestStand, + kitDaylight, +} from "../terrain/kit.js"; type Ctx = ICtx; -type Field = IAlpineWorld["field"]; -type Extent = IAlpineWorld["extent"]; +type Field = IKitWorld["field"]; +type Extent = IKitWorld["extent"]; /** A 1.8 m figure: 0.55 m of half-height plus the 0.35 m radius a boulder's clearance is read against. */ const HALF_HEIGHT = 0.55; @@ -27,10 +28,13 @@ const SPAWN_RING = 5; /** The steepest rise over run, per metre, that the spawn or the walk to the anchor may use. */ const SPAWN_SLOPE = 0.25; const WALK_SLOPE = 0.35; -/** The alpine daylight's sun (`sky.ts`). Its azimuth picks the side the edge camera stands on. */ +/** The kit daylight's sun (`sky.ts`). Its azimuth picks the side the edge camera stands on. */ const SUN_AZIMUTH = Math.atan2(0.56, 0.55); -const VIEW_NAMES = ["boulders", "edge", "ground", "overview"] as const; -type ViewName = (typeof VIEW_NAMES)[number]; +const VIEW_NAMES = + KIT === "Forest" + ? ["ground", "edge", "overview", "lake"] + : ["boulders", "edge", "ground", "overview"]; +type ViewName = string; interface IPoint { readonly x: number; @@ -53,7 +57,7 @@ interface IStand { readonly anchor: IProp; readonly spawn: IPoint; readonly groundY: number; - readonly views: Readonly>; + readonly views: Readonly> & { readonly ground: IView }; } /** One view's closed frame-budget windows, read by {@link noteFrameBudget}. */ @@ -79,8 +83,9 @@ function median(values: readonly number[]): number { /** Every placed boulder. The kit's only asset is the boulder, so every record is one. */ function readProps(placements: Float32Array): IProp[] { - const boulder = COLLIDERS.boulder; - if (!boulder) throw new Error("Alpine kit: the boulder has no collider in COLLIDERS."); + const boulder = KIT === "Coastal" ? COLLIDERS.fir : COLLIDERS.boulder; + if (!boulder || !("sphere" in boulder)) + throw new Error(`${KIT} kit: the boulder has no collider in COLLIDERS.`); const props: IProp[] = []; for (let index = 0; index + 8 <= placements.length; index += 8) props.push({ @@ -148,6 +153,7 @@ function walkable( props: readonly IProp[], anchor: IProp, spawn: IPoint, + wet: (point: IPoint, margin: number) => boolean, ): boolean { const length = distance(anchor, spawn); for (let along = 0; along <= length - 1.5; along += 0.25) { @@ -156,6 +162,7 @@ function walkable( z: spawn.z + ((anchor.z - spawn.z) * along) / length, }; if (slopeAt(field, extent, point.x, point.z) > WALK_SLOPE) return false; + if (wet(point, 1)) return false; for (const other of props) if (other !== anchor && distance(other, point) < other.radius + RADIUS + 0.3) return false; } @@ -168,6 +175,7 @@ function spawnFor( extent: Extent, props: readonly IProp[], anchor: IProp, + wet: (point: IPoint, margin: number) => boolean, ): IPoint | undefined { let best: IPoint | undefined; let bestClear = RADIUS + 0.5; @@ -177,9 +185,10 @@ function spawnFor( x: anchor.x + SPAWN_RING * Math.cos(angle), z: anchor.z + SPAWN_RING * Math.sin(angle), }; + if (wet(spawn, KIT === "Tundra" ? 5 : 1)) continue; if ( slopeAt(field, extent, spawn.x, spawn.z) > SPAWN_SLOPE || - !walkable(field, extent, props, anchor, spawn) + !walkable(field, extent, props, anchor, spawn, wet) ) continue; const clear = clearance(props, spawn, anchor); @@ -192,7 +201,12 @@ function spawnFor( } /** The clearest point 25 to 40 m from the anchor, on the sun's side and inside the world. */ -function edgeFor(props: readonly IProp[], anchor: IProp, extent: Extent): IPoint { +function edgeFor( + props: readonly IProp[], + anchor: IProp, + extent: Extent, + wet: (point: IPoint, margin: number) => boolean, +): IPoint { let best: IPoint | undefined; let bestClear = Number.NEGATIVE_INFINITY; for (let step = 0; step < 24; step += 1) @@ -207,6 +221,7 @@ function edgeFor(props: readonly IProp[], anchor: IProp, extent: Extent): IPoint z: anchor.z + reach * Math.sin(angle), }; if (!inside(extent, point, 5)) continue; + if (wet(point, KIT === "Tundra" ? 5 : 1)) continue; const clear = Math.min(...props.map((prop) => distance(prop, point))); if (clear > bestClear) { bestClear = clear; @@ -214,7 +229,7 @@ function edgeFor(props: readonly IProp[], anchor: IProp, extent: Extent): IPoint } } if (best === undefined) - throw new Error("Alpine kit: no edge camera point on the sun's side of the anchor."); + throw new Error(`${KIT} kit: no edge camera point on the sun's side of the anchor.`); return best; } @@ -227,11 +242,12 @@ function viewsFor( anchor: IProp, spawn: IPoint, away: IPoint, + wet: (point: IPoint, margin: number) => boolean, ): IStand["views"] { const anchorGround = field.heightAt(anchor.x, anchor.z); const target = new Vector3(anchor.x, anchorGround + 0.9, anchor.z); const eyeGround = field.heightAt(spawn.x, spawn.z); - const edge = edgeFor(props, anchor, extent); + const edge = edgeFor(props, anchor, extent, wet); // Beside the centre's side of the anchor, so the camera stays inside the world however the anchor lies. const overviewX = anchor.x + (anchor.x > centre.x ? -160 : 160); const overviewZ = anchor.z + (anchor.z > centre.z ? -160 : 160); @@ -241,7 +257,7 @@ function viewsFor( const behind = { x: anchor.x - away.x * 9 * sign, z: anchor.z - away.z * 9 * sign }; const boulderGround = groundAt(field, extent, behind.x, behind.z); if (boulderGround === undefined) - throw new Error("Alpine kit: the boulder view has no ground inside the world."); + throw new Error(`${KIT} kit: the boulder view has no ground inside the world.`); return { ground: { at: new Vector3(spawn.x, eyeGround + EYE, spawn.z), @@ -267,33 +283,88 @@ function viewsFor( } /** The stand the scene is written around: the nearest boulder to the centre with a walkable spawn. */ -function chooseStand(alpine: IAlpineWorld): IStand { - const { field, extent } = alpine; - const props = readProps(alpine.placements); +function chooseStand(kit: IKitWorld): IStand { + if (forestStand) return forestViews(); + const wet = (point: IPoint, margin: number): boolean => { + if (kit.seaLevel !== undefined) { + const ground = groundAt(kit.field, kit.extent, point.x, point.z); + return ground === undefined || ground < kit.seaLevel + margin; + } + return (kit.lakes ?? []).some( + (lake) => distance(point, { x: lake.at[0], z: lake.at[1] }) < lake.radius + margin, + ); + }; + const { field, extent } = kit; + const props = readProps(kit.placements); const centre = { x: extent.minX + extent.sizeX / 2, z: extent.minZ + extent.sizeZ / 2 }; // Every boulder may anchor the stand. The nearest one to the centre with a walkable spawn wins. const candidates = props.sort((a, b) => distance(a, centre) - distance(b, centre)); for (const anchor of candidates) { - const spawn = spawnFor(field, extent, props, anchor); + if (wet(anchor, 3)) continue; + const spawn = spawnFor(field, extent, props, anchor, wet); if (spawn === undefined) continue; const away = { x: (spawn.x - anchor.x) / SPAWN_RING, z: (spawn.z - anchor.z) / SPAWN_RING }; return { anchor, spawn, groundY: field.heightAt(spawn.x, spawn.z), - views: viewsFor(field, props, extent, centre, anchor, spawn, away), + views: viewsFor(field, props, extent, centre, anchor, spawn, away, wet), }; } - throw new Error("Alpine kit: no boulder in the world has a walkable spawn."); + throw new Error(`${KIT} kit: no boulder in the world has a walkable spawn.`); } -function disposeAlpine(alpine: IAlpineWorld): void { - alpine.world.dispose(); - alpine.colliders.detach(); - alpine.ground.dispose(); +function forestViews(): IStand { + const stand = forestStand; + if (!stand) throw new Error("Forest kit: missing generated stand."); + const TRUNK = new Vector3(stand.fir.x, stand.groundY + 6, stand.fir.z); + /** From the fir towards the player's start; the open side of the stand. */ + const AWAY = new Vector3(stand.spawn.x - stand.fir.x, 0, stand.spawn.z - stand.fir.z).normalize(); + + /** The recipe's lake centre, on the water's level: the lake view looks at it from above the terrain. */ + const LAKE = new Vector3(-70, 13.3, 25); + /** The fixed views the scenario captures. */ + // Eye-height cameras inside a 4.5 m stand sit inside crowns, whose one-sided cards vanish from + // behind; so the close view looks out of the clearing and the edge view stands back from the stand. + const views = { + ground: { + at: new Vector3(stand.spawn.x, stand.groundY + EYE, stand.spawn.z), + look: new Vector3(stand.spawn.x, stand.groundY + EYE, stand.spawn.z).addScaledVector( + AWAY, + 20, + ), + }, + edge: { + at: new Vector3(stand.edge.x, stand.groundY + 6, stand.edge.z), + look: TRUNK, + }, + overview: { + at: new Vector3(stand.fir.x + 160, stand.groundY + 220, stand.fir.z + 160), + look: TRUNK, + }, + // Above the tallest ground (about 38 m) and back far enough to frame the whole basin and its shore. + lake: { + at: new Vector3(20, 84, 115), + look: LAKE, + }, + }; + return { + anchor: { ...stand.fir, y: stand.groundY, radius: 0.35 }, + spawn: stand.spawn, + groundY: stand.groundY, + views, + }; } -export class Alpine extends Scene { +function disposeKit(kit: IKitWorld): void { + kit.world.dispose(); + kit.colliders.detach(); + kit.ground.dispose(); + kit.water?.dispose(); + kit.sea?.dispose(); +} + +export class KitScene extends Scene { static override readonly initialState: GameState = initialState; #player: Object3D | undefined; @@ -305,30 +376,54 @@ export class Alpine extends Scene { #driven = 0; #closest = Number.POSITIVE_INFINITY; #settled = false; - #alpine: IAlpineWorld | undefined; + #kit: IKitWorld | undefined; #stand: IStand | undefined; #released = false; - #admission: ReturnType | undefined; + #ready = false; + #error = ""; + #settle: ((error?: Error) => void) | undefined; + #timer: ReturnType | undefined; #loading: ReturnType | undefined; /** The kit's world streams while the scene loads, so `ready` waits for the cells the spawn needs. */ override async load(ctx: Ctx): Promise { for (const view of VIEW_NAMES) viewWindows.set(view, { frameP95: [], gpuP50: [], gpuP95: [] }); - const admission = createSpawnReadiness("Alpine spawn"); - this.#admission = admission; - void admission.promise.catch((error: unknown) => { + if (forestStand) { + this.#stand = forestViews(); + ctx.camera.position.copy(this.#stand.views.ground.at); + ctx.camera.lookAt(this.#stand.views.ground.look); + } + const deadlineAt = performance.now() + 120_000; + let settled = false; + const spawn = new Promise((done, fail) => { + this.#settle = (error) => { + if (settled) return; + settled = true; + if (this.#timer !== undefined) clearTimeout(this.#timer); + if (error === undefined) { + if (!this.#released && !this.#error) this.#ready = true; + done(); + } else fail(error); + }; + }); + this.#timer = setTimeout( + () => this.#fail(ctx, `${KIT} spawn admission exceeded 120000 ms`), + 120_000, + ); + void spawn.catch((error: unknown) => { this.#fail(ctx, error instanceof Error ? error.message : String(error)); }); - if (ctx.startup.phase !== "ready") ctx.startup.hold("alpine-spawn", admission.promise, 120_000); - let alpine: IAlpineWorld | undefined; + if (ctx.startup.phase !== "ready") + ctx.startup.hold(`${KIT.toLowerCase()}-spawn`, spawn, 120_000); + let kit: IKitWorld | undefined; // A holder rather than a `let`: the progress getter reads it, and the spawn is set once it is known. - const spawnView: { at?: Vector3 } = {}; + const spawnView: { at?: Vector3 } = { at: this.#stand?.views.ground?.at }; this.#loading = createLoadingScreen({ ...ctx, startup: { get progress() { // The spawn is unknown until the world resolves, so the bar reads the startup alone until then. - const region = spawnView.at && alpine?.world.readinessAt(spawnView.at, 60); + const region = spawnView.at && kit?.world.readinessAt(spawnView.at, 60); const required = (region?.requiredCells ?? 0) + (region?.requiredTerrainTiles ?? 0); const loaded = (region?.loadedCells ?? 0) + (region?.loadedTerrainTiles ?? 0); return Math.min( @@ -336,42 +431,59 @@ export class Alpine extends Scene { (ctx.startup.progress + (required > 0 ? loaded / required : 0)) / 2, ); }, - whenReady: () => - Promise.all([ctx.startup.whenReady(), admission.promise]).then(() => undefined), + whenReady: () => Promise.all([ctx.startup.whenReady(), spawn]).then(() => undefined), }, }); try { - alpine = await addAlpine(ctx, ctx.camera, undefined, () => !this.#released); + kit = await addKit(ctx, ctx.camera, undefined, () => !this.#released); } catch (error) { this.#fail(ctx, error instanceof Error ? error.message : String(error)); return; } - this.#alpine = alpine; + this.#kit = kit; if (this.#released) { - disposeAlpine(alpine); + disposeKit(kit); return; } let stand: IStand; try { - stand = chooseStand(alpine); + stand = chooseStand(kit); } catch (error) { this.#fail(ctx, error instanceof Error ? error.message : String(error)); return; } this.#stand = stand; spawnView.at = stand.views.ground.at; + // Arm once, after the world is attached: the engine owns readiness, its prewarm owns the draw. + const remaining = deadlineAt - performance.now(); + void ( + remaining > 0 + ? Promise.all([ + kit.world.whenSpawnReady(stand.views.ground.at, 60, remaining), + kit.world.prewarmed, + ]) + : Promise.reject(new Error(`${KIT} spawn admission exceeded 120000 ms`)) + ).then( + () => this.#settle?.(), + (error: unknown) => this.#fail(ctx, error instanceof Error ? error.message : String(error)), + ); const player = new Object3D(); player.position.set(stand.spawn.x, stand.groundY + HALF_HEIGHT + RADIUS + 2.5, stand.spawn.z); this.#player = player; // The camera moves to the spawn before the world finishes streaming, so the cells it needs load first. ctx.camera.position.copy(stand.views.ground.at); ctx.camera.lookAt(stand.views.ground.look); - ctx.state.set({ propColliders: alpine.colliders.active, worldReady: 0 }); + ctx.state.set({ + propColliders: kit.colliders.active, + worldReady: 0, + ...(kit.water ? { waterLakes: kit.water.lakes, waterRivers: kit.water.rivers } : {}), + ...(kit.sea ? { waterLakes: 1 } : {}), + }); } override enter(ctx: Ctx): void { - ctx.add(alpineDaylight(ctx.camera)); - if (!this.#alpine || !this.#stand) { + ctx.add(kitDaylight(ctx.camera)); + if (!this.#kit || !this.#stand) { ctx.beforeRender(() => this.#loading?.update()); return; } @@ -390,10 +502,10 @@ export class Alpine extends Scene { this.#viewNow(ctx); ctx.beforeRender(() => { this.#loading?.update(); - const alpine = this.#alpine; + const kit = this.#kit; const stand = this.#stand; - if (!alpine || !stand) return; - const region = alpine.world.readinessAt(stand.views.ground.at, 60); + if (!kit || !stand) return; + const region = kit.world.readinessAt(stand.views.ground.at, 60); if (!region) return; ctx.state.set({ spawnCellsLoaded: region.loadedCells, @@ -402,32 +514,27 @@ export class Alpine extends Scene { spawnTerrainRequired: region.requiredTerrainTiles, }); if (this.#released) return; - this.#admission?.observe( - region.ready && alpine.world.stats().pendingPrewarm === 0, - region.failures, - ); ctx.state.set({ - worldReady: this.#admission?.ready ? 1 : 0, - loadingError: this.#admission?.error ?? "", + worldReady: this.#ready ? 1 : 0, + loadingError: this.#error, }); }); } #fail(ctx: Ctx, message: string): void { - if (this.#released || ctx.state.getState().loadingError) return; - this.#admission?.fail(message); - ctx.state.set({ - loadingError: this.#admission?.error ?? message, - worldReady: this.#admission?.ready ? 1 : 0, - }); + if (this.#released || this.#error || this.#ready) return; + const error = message || `${KIT} spawn failed`; + this.#error = error; + this.#settle?.(new Error(error)); + ctx.state.set({ loadingError: error, worldReady: 0 }); } override exit(): void { this.#released = true; - this.#admission?.cancel(); + this.#settle?.(new Error(`${KIT} spawn cancelled`)); this.#loading?.finish(); this.#body?.dispose(); - if (this.#alpine) disposeAlpine(this.#alpine); + if (this.#kit) disposeKit(this.#kit); } override update(ctx: Ctx, dt: number): void { @@ -435,11 +542,7 @@ export class Alpine extends Scene { const player = this.#player; const stand = this.#stand; if (body === undefined || player === undefined || stand === undefined) return; - if ( - !this.#admission?.ready || - ctx.startup.phase !== "ready" || - ctx.state.getState().loadingError - ) { + if (!this.#ready || ctx.startup.phase !== "ready" || ctx.state.getState().loadingError) { body.velocity.x = 0; body.velocity.z = 0; body.moveAndSlide(dt); @@ -456,8 +559,13 @@ export class Alpine extends Scene { this.#placeCamera(); if (this.#frames % 60 === 0) ctx.state.set({ - boulderInstances: this.#alpine?.world.stats().instances ?? -1, - propColliders: this.#alpine?.colliders.active ?? -1, + [KIT === "Forest" + ? "firInstancesDrawn" + : KIT === "Coastal" + ? "treeInstances" + : "boulderInstances"]: + KIT === "Forest" ? this.#firInstances(ctx) : (this.#kit?.world.stats().instances ?? -1), + propColliders: this.#kit?.colliders.active ?? -1, }); ctx.state.set({ driveMetres: this.#driven, @@ -497,7 +605,15 @@ export class Alpine extends Scene { this.#closest, Math.hypot(player.position.x - stand.anchor.x, player.position.z - stand.anchor.z), ); - if (this.#driveLeft <= 0) ctx.state.set({ closestToBoulder: this.#closest, driveDone: 1 }); + if (this.#driveLeft <= 0) + ctx.state.set({ + [KIT === "Forest" + ? "closestToTrunk" + : KIT === "Coastal" + ? "closestToTree" + : "closestToBoulder"]: this.#closest, + driveDone: 1, + }); } #setView(ctx: Ctx, view: ViewName): void { @@ -550,6 +666,20 @@ export class Alpine extends Scene { }); } + /** Instances the renderer draws in the fir batches, its own visibility and every ancestor's. */ + #firInstances(ctx: Ctx): number { + let drawn = 0; + ctx.scene.traverse((object) => { + if (!(object as InstancedMesh).isInstancedMesh) return; + const mesh = object as InstancedMesh; + if (!mesh.name.includes("fir") || mesh.count < 1) return; + for (let node: Object3D | null = object; node !== null; node = node.parent) + if (node.visible === false) return; + drawn += mesh.count; + }); + return drawn; + } + #costs(key: "frameP95" | "gpuP50" | "gpuP95"): Record { const costs: Record = {}; for (const [view, group] of viewWindows) costs[view] = median(group[key]); diff --git a/examples/strata-terrain-preview/scripts/fixtures/kit-game/Tundra.ts b/examples/strata-terrain-preview/scripts/fixtures/kit-game/Tundra.ts deleted file mode 100644 index 81db17cb41..0000000000 --- a/examples/strata-terrain-preview/scripts/fixtures/kit-game/Tundra.ts +++ /dev/null @@ -1,592 +0,0 @@ -// An ordinary game scene standing in the tundra starter kit. The kit streams the world, textures its -// ground, draws its kettle lakes and rivers and gives the boulders their collision; this file owns the -// daylight, the camera, the player and every number the playtest reads. Nothing here imports the -// authoring package: the kit was copied into `src/terrain/tundra/` and its bake is a build step. The -// spawn and the views are read from the loaded heightfield, placements and lakes, so the scene keeps no -// coordinate the recipe does not make. -import { type ICtx, type IFrameBudgetWindow, Scene } from "@threenative/core"; -import { CharacterBody3D, CollisionShape3D, type IPhysicsContext } from "@threenative/physics"; -import { Object3D, type PerspectiveCamera, Vector3 } from "three"; -import { createLoadingScreen, createSpawnReadiness } from "../render/loading.js"; -import { type GameState, initialState } from "../state.js"; -import { tundraDaylight } from "../terrain/tundra/sky.js"; -import { - COLLIDERS, - type ITundraLake, - type ITundraWorld, - addTundra, -} from "../terrain/tundra/world.js"; - -type Ctx = ICtx; -type Field = ITundraWorld["field"]; -type Extent = ITundraWorld["extent"]; - -/** A 1.8 m figure: 0.55 m of half-height plus the 0.35 m radius a boulder's clearance is read against. */ -const HALF_HEIGHT = 0.55; -const RADIUS = 0.35; -const MOVE_SPEED = 3; -/** Long enough to cover the spawn ring and be stopped by the boulder it is aimed at. */ -const DRIVE_SECONDS = 3; -const EYE = 1.7; -/** Metres from the anchor boulder's origin to the spawn: far enough to walk, near enough to measure. */ -const SPAWN_RING = 5; -/** The steepest rise over run, per metre, that the spawn or the walk to the anchor may use. */ -const SPAWN_SLOPE = 0.25; -const WALK_SLOPE = 0.35; -/** Metres of dry ground the spawn and the edge camera keep from a kettle's shore. */ -const SHORE_MARGIN = 5; -/** The tundra daylight's sun (`sky.ts`). Its azimuth picks the side the edge camera stands on. */ -const SUN_AZIMUTH = Math.atan2(0.56, 0.55); -const VIEW_NAMES = ["boulders", "edge", "ground", "overview"] as const; -type ViewName = (typeof VIEW_NAMES)[number]; - -interface IPoint { - readonly x: number; - readonly z: number; -} - -/** One placed boulder, read from its record: where it stands and the radius its collider uses. */ -interface IProp extends IPoint { - readonly y: number; - readonly radius: number; -} - -interface IView { - readonly at: Vector3; - readonly look: Vector3; -} - -/** Where the scene stands, looks and drives, all read from the loaded world. */ -interface IStand { - readonly anchor: IProp; - readonly spawn: IPoint; - readonly groundY: number; - readonly views: Readonly>; -} - -/** One view's closed frame-budget windows, read by {@link noteFrameBudget}. */ -const viewWindows = new Map(); -let currentView = "ground"; - -/** The engine's own frame meter, grouped by the view that was on screen. */ -export function noteFrameBudget(window: IFrameBudgetWindow): void { - const group = viewWindows.get(currentView); - // A window with no `gpu` resolved no timestamp: absent, not zero. - if (group === undefined || window.gpu === undefined) return; - group.gpuP50.push(window.gpu.p50); - group.gpuP95.push(window.gpu.p95); - group.frameP95.push(window.frame.p95); -} - -/** The middle of a sample set, for a summary that one outlier cannot move. */ -function median(values: readonly number[]): number { - if (values.length === 0) return -1; - const sorted = [...values].sort((a, b) => a - b); - return sorted[Math.floor(sorted.length / 2)] ?? -1; -} - -/** Every placed boulder. The kit's only asset is the boulder, so every record is one. */ -function readProps(placements: Float32Array): IProp[] { - const boulder = COLLIDERS.boulder; - if (!boulder) throw new Error("Tundra kit: the boulder has no collider in COLLIDERS."); - const props: IProp[] = []; - for (let index = 0; index + 8 <= placements.length; index += 8) - props.push({ - x: placements[index] as number, - y: placements[index + 1] as number, - z: placements[index + 2] as number, - radius: boulder.sphere * (placements[index + 7] as number), - }); - return props; -} - -function distance(a: IPoint, b: IPoint): number { - return Math.hypot(a.x - b.x, a.z - b.z); -} - -function inside(extent: Extent, point: IPoint, margin: number): boolean { - return ( - point.x > extent.minX + margin && - point.x < extent.minX + extent.sizeX - margin && - point.z > extent.minZ + margin && - point.z < extent.minZ + extent.sizeZ - margin - ); -} - -/** The ground under a point, or undefined when the point lies outside the world's heightfield. */ -function groundAt(field: Field, extent: Extent, x: number, z: number): number | undefined { - return inside(extent, { x, z }, 0) ? field.heightAt(x, z) : undefined; -} - -/** Rise over run at a point, from the heights half a metre either side on each axis. */ -function slopeAt(field: Field, extent: Extent, x: number, z: number): number { - const plusX = groundAt(field, extent, x + 0.5, z); - const minusX = groundAt(field, extent, x - 0.5, z); - const plusZ = groundAt(field, extent, x, z + 0.5); - const minusZ = groundAt(field, extent, x, z - 0.5); - // A slope that reaches past the world's edge is not walkable, so it reads as infinitely steep. - if (plusX === undefined || minusX === undefined || plusZ === undefined || minusZ === undefined) - return Number.POSITIVE_INFINITY; - return Math.hypot(plusX - minusX, plusZ - minusZ); -} - -/** The highest ground within `reach` metres of a point, sampled every 16 m inside the world. */ -function highestNear(field: Field, extent: Extent, x: number, z: number, reach: number): number { - let top = Number.NEGATIVE_INFINITY; - for (let dx = -reach; dx <= reach; dx += 16) - for (let dz = -reach; dz <= reach; dz += 16) { - const ground = groundAt(field, extent, x + dx, z + dz); - if (ground !== undefined) top = Math.max(top, ground); - } - return top; -} - -/** Metres from a point to the nearest other boulder's surface. Infinite when there is no other. */ -function clearance(props: readonly IProp[], point: IPoint, anchor: IProp): number { - let nearest = Number.POSITIVE_INFINITY; - for (const other of props) - if (other !== anchor) nearest = Math.min(nearest, distance(other, point) - other.radius); - return nearest; -} - -/** Whether a point lies within `margin` metres of a kettle lake's shore. */ -function wetAt(lakes: readonly ITundraLake[], point: IPoint, margin: number): boolean { - return lakes.some( - (lake) => Math.hypot(point.x - lake.at[0], point.z - lake.at[1]) < lake.radius + margin, - ); -} - -/** The walk from the spawn to the anchor climbs gently, stays dry and passes no other boulder. */ -function walkable( - field: Field, - extent: Extent, - props: readonly IProp[], - anchor: IProp, - spawn: IPoint, - lakes: readonly ITundraLake[], -): boolean { - const length = distance(anchor, spawn); - for (let along = 0; along <= length - 1.5; along += 0.25) { - const point = { - x: spawn.x + ((anchor.x - spawn.x) * along) / length, - z: spawn.z + ((anchor.z - spawn.z) * along) / length, - }; - if (slopeAt(field, extent, point.x, point.z) > WALK_SLOPE) return false; - if (wetAt(lakes, point, 1)) return false; - for (const other of props) - if (other !== anchor && distance(other, point) < other.radius + RADIUS + 0.3) return false; - } - return true; -} - -/** The clearest of 24 points a ring of metres from the anchor that is gentle, dry and walkable. */ -function spawnFor( - field: Field, - extent: Extent, - props: readonly IProp[], - anchor: IProp, - lakes: readonly ITundraLake[], -): IPoint | undefined { - let best: IPoint | undefined; - let bestClear = RADIUS + 0.5; - for (let step = 0; step < 24; step += 1) { - const angle = (2 * Math.PI * step) / 24; - const spawn = { - x: anchor.x + SPAWN_RING * Math.cos(angle), - z: anchor.z + SPAWN_RING * Math.sin(angle), - }; - if (wetAt(lakes, spawn, SHORE_MARGIN)) continue; - if ( - slopeAt(field, extent, spawn.x, spawn.z) > SPAWN_SLOPE || - !walkable(field, extent, props, anchor, spawn, lakes) - ) - continue; - const clear = clearance(props, spawn, anchor); - if (clear > bestClear) { - bestClear = clear; - best = spawn; - } - } - return best; -} - -/** The clearest point 25 to 40 m from the anchor, on the sun's side, inside the world and dry. */ -function edgeFor( - props: readonly IProp[], - anchor: IProp, - extent: Extent, - lakes: readonly ITundraLake[], -): IPoint { - let best: IPoint | undefined; - let bestClear = Number.NEGATIVE_INFINITY; - for (let step = 0; step < 24; step += 1) - for (const reach of [25, 30, 35, 40]) { - const angle = (2 * Math.PI * step) / 24; - const off = Math.abs( - Math.atan2(Math.sin(angle - SUN_AZIMUTH), Math.cos(angle - SUN_AZIMUTH)), - ); - if (off > Math.PI / 3) continue; - const point = { - x: anchor.x + reach * Math.cos(angle), - z: anchor.z + reach * Math.sin(angle), - }; - if (!inside(extent, point, 5)) continue; - if (wetAt(lakes, point, SHORE_MARGIN)) continue; - const clear = Math.min(...props.map((prop) => distance(prop, point))); - if (clear > bestClear) { - bestClear = clear; - best = point; - } - } - if (best === undefined) - throw new Error("Tundra kit: no dry edge camera point on the sun's side of the anchor."); - return best; -} - -/** The four fixed views, each placed from the anchor, the spawn and the terrain under them. */ -function viewsFor( - field: Field, - props: readonly IProp[], - extent: Extent, - centre: IPoint, - anchor: IProp, - spawn: IPoint, - away: IPoint, - lakes: readonly ITundraLake[], -): IStand["views"] { - const anchorGround = field.heightAt(anchor.x, anchor.z); - const target = new Vector3(anchor.x, anchorGround + 0.9, anchor.z); - const eyeGround = field.heightAt(spawn.x, spawn.z); - const edge = edgeFor(props, anchor, extent, lakes); - // Beside the centre's side of the anchor, so the camera stays inside the world however the anchor lies. - const overviewX = anchor.x + (anchor.x > centre.x ? -160 : 160); - const overviewZ = anchor.z + (anchor.z > centre.z ? -160 : 160); - // The boulder view stands 9 m behind the anchor, on the side away from the spawn. Beside the world's - // edge that side may leave the world, so the camera takes the spawn's side instead. - const sign = inside(extent, { x: anchor.x - away.x * 9, z: anchor.z - away.z * 9 }, 0) ? 1 : -1; - const behind = { x: anchor.x - away.x * 9 * sign, z: anchor.z - away.z * 9 * sign }; - const boulderGround = groundAt(field, extent, behind.x, behind.z); - if (boulderGround === undefined) - throw new Error("Tundra kit: the boulder view has no ground inside the world."); - return { - ground: { - at: new Vector3(spawn.x, eyeGround + EYE, spawn.z), - look: new Vector3(spawn.x + away.x * 20, eyeGround + EYE, spawn.z + away.z * 20), - }, - edge: { - at: new Vector3(edge.x, field.heightAt(edge.x, edge.z) + 6, edge.z), - look: target, - }, - overview: { - at: new Vector3( - overviewX, - highestNear(field, extent, overviewX, overviewZ, 200) + 90, - overviewZ, - ), - look: target, - }, - boulders: { - at: new Vector3(behind.x, boulderGround + 3.2, behind.z), - look: new Vector3(anchor.x, anchorGround + 0.6, anchor.z), - }, - }; -} - -/** The stand the scene is written around: the nearest boulder to the centre with a walkable, dry spawn. */ -function chooseStand(tundra: ITundraWorld): IStand { - const { field, extent, lakes } = tundra; - const props = readProps(tundra.placements); - const centre = { x: extent.minX + extent.sizeX / 2, z: extent.minZ + extent.sizeZ / 2 }; - // Every boulder may anchor the stand. The nearest one to the centre with a walkable spawn wins. - const candidates = props.sort((a, b) => distance(a, centre) - distance(b, centre)); - for (const anchor of candidates) { - // A boulder on a kettle's shore is no stand: its ring and its walk would start in the water. - if (wetAt(lakes, anchor, 3)) continue; - const spawn = spawnFor(field, extent, props, anchor, lakes); - if (spawn === undefined) continue; - const away = { x: (spawn.x - anchor.x) / SPAWN_RING, z: (spawn.z - anchor.z) / SPAWN_RING }; - return { - anchor, - spawn, - groundY: field.heightAt(spawn.x, spawn.z), - views: viewsFor(field, props, extent, centre, anchor, spawn, away, lakes), - }; - } - throw new Error("Tundra kit: no boulder on dry ground has a walkable spawn."); -} - -function disposeTundra(tundra: ITundraWorld): void { - tundra.world.dispose(); - tundra.colliders.detach(); - tundra.ground.dispose(); - tundra.water.dispose(); -} - -export class Tundra extends Scene { - static override readonly initialState: GameState = initialState; - - #player: Object3D | undefined; - #body: CharacterBody3D | undefined; - #camera: PerspectiveCamera | undefined; - #view: ViewName = "ground"; - #frames = 0; - #driveLeft = DRIVE_SECONDS; - #driven = 0; - #closest = Number.POSITIVE_INFINITY; - #settled = false; - #tundra: ITundraWorld | undefined; - #stand: IStand | undefined; - #released = false; - #admission: ReturnType | undefined; - #loading: ReturnType | undefined; - - /** The kit's world streams while the scene loads, so `ready` waits for the cells the spawn needs. */ - override async load(ctx: Ctx): Promise { - for (const view of VIEW_NAMES) viewWindows.set(view, { frameP95: [], gpuP50: [], gpuP95: [] }); - const admission = createSpawnReadiness("Tundra spawn"); - this.#admission = admission; - void admission.promise.catch((error: unknown) => { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - }); - if (ctx.startup.phase !== "ready") ctx.startup.hold("tundra-spawn", admission.promise, 120_000); - let tundra: ITundraWorld | undefined; - // A holder rather than a `let`: the progress getter reads it, and the spawn is set once it is known. - const spawnView: { at?: Vector3 } = {}; - this.#loading = createLoadingScreen({ - ...ctx, - startup: { - get progress() { - // The spawn is unknown until the world resolves, so the bar reads the startup alone until then. - const region = spawnView.at && tundra?.world.readinessAt(spawnView.at, 60); - const required = (region?.requiredCells ?? 0) + (region?.requiredTerrainTiles ?? 0); - const loaded = (region?.loadedCells ?? 0) + (region?.loadedTerrainTiles ?? 0); - return Math.min( - 0.99, - (ctx.startup.progress + (required > 0 ? loaded / required : 0)) / 2, - ); - }, - whenReady: () => - Promise.all([ctx.startup.whenReady(), admission.promise]).then(() => undefined), - }, - }); - try { - tundra = await addTundra(ctx, ctx.camera, undefined, () => !this.#released); - } catch (error) { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - return; - } - this.#tundra = tundra; - if (this.#released) { - disposeTundra(tundra); - return; - } - let stand: IStand; - try { - stand = chooseStand(tundra); - } catch (error) { - this.#fail(ctx, error instanceof Error ? error.message : String(error)); - return; - } - this.#stand = stand; - spawnView.at = stand.views.ground.at; - const player = new Object3D(); - player.position.set(stand.spawn.x, stand.groundY + HALF_HEIGHT + RADIUS + 2.5, stand.spawn.z); - this.#player = player; - // The camera moves to the spawn before the world finishes streaming, so the cells it needs load first. - ctx.camera.position.copy(stand.views.ground.at); - ctx.camera.lookAt(stand.views.ground.look); - ctx.state.set({ - propColliders: tundra.colliders.active, - waterLakes: tundra.lakes.length, - waterRivers: tundra.rivers, - worldReady: 0, - }); - } - - override enter(ctx: Ctx): void { - ctx.add(tundraDaylight(ctx.camera)); - if (!this.#tundra || !this.#stand) { - ctx.beforeRender(() => this.#loading?.update()); - return; - } - ctx.add(this.#player as Object3D); - // The body joins the world once its object is in the scene, as the template player does. - this.#body = new CharacterBody3D({ - object: this.#player as Object3D, - physics: ctx.physics, - shape: CollisionShape3D.capsule(HALF_HEIGHT, RADIUS), - }); - const camera = ctx.camera as PerspectiveCamera; - // The kit's sky box and the streamed cells both live far out. The far plane has to clear them. - camera.far = 5000; - camera.updateProjectionMatrix(); - this.#camera = camera; - this.#viewNow(ctx); - ctx.beforeRender(() => { - this.#loading?.update(); - const tundra = this.#tundra; - const stand = this.#stand; - if (!tundra || !stand) return; - const region = tundra.world.readinessAt(stand.views.ground.at, 60); - if (!region) return; - ctx.state.set({ - spawnCellsLoaded: region.loadedCells, - spawnCellsRequired: region.requiredCells, - spawnTerrainLoaded: region.loadedTerrainTiles, - spawnTerrainRequired: region.requiredTerrainTiles, - }); - if (this.#released) return; - this.#admission?.observe( - region.ready && tundra.world.stats().pendingPrewarm === 0, - region.failures, - ); - ctx.state.set({ - worldReady: this.#admission?.ready ? 1 : 0, - loadingError: this.#admission?.error ?? "", - }); - }); - } - - #fail(ctx: Ctx, message: string): void { - if (this.#released || ctx.state.getState().loadingError) return; - this.#admission?.fail(message); - ctx.state.set({ - loadingError: this.#admission?.error ?? message, - worldReady: this.#admission?.ready ? 1 : 0, - }); - } - - override exit(): void { - this.#released = true; - this.#admission?.cancel(); - this.#loading?.finish(); - this.#body?.dispose(); - if (this.#tundra) disposeTundra(this.#tundra); - } - - override update(ctx: Ctx, dt: number): void { - const body = this.#body; - const player = this.#player; - const stand = this.#stand; - if (body === undefined || player === undefined || stand === undefined) return; - if ( - !this.#admission?.ready || - ctx.startup.phase !== "ready" || - ctx.state.getState().loadingError - ) { - body.velocity.x = 0; - body.velocity.z = 0; - body.moveAndSlide(dt); - return; - } - this.#frames += 1; - - const driving = this.#aim(body, player, stand); - body.moveAndSlide(dt); - if (driving) this.#measureDrive(ctx, player, stand, dt); - if (body.grounded && this.#frames > 30) this.#measureGround(ctx, player, stand.groundY); - - for (const view of VIEW_NAMES) if (ctx.input.justPressed(view)) this.#setView(ctx, view); - this.#placeCamera(); - if (this.#frames % 60 === 0) - ctx.state.set({ - boulderInstances: this.#tundra?.world.stats().instances ?? -1, - propColliders: this.#tundra?.colliders.active ?? -1, - }); - ctx.state.set({ - driveMetres: this.#driven, - frames: this.#frames, - viewFrameP95: this.#costs("frameP95"), - viewGpuP50: this.#costs("gpuP50"), - viewGpuP95: this.#costs("gpuP95"), - }); - } - - /** Aims the body at the anchor while the drive lasts. Returns whether the body is driving this frame. */ - #aim(body: CharacterBody3D, player: Object3D, stand: IStand): boolean { - const towards = new Vector3( - stand.anchor.x - player.position.x, - 0, - stand.anchor.z - player.position.z, - ); - const driving = this.#driveLeft > 0 && towards.length() > 0.001; - if (!driving) { - body.velocity.x = 0; - body.velocity.z = 0; - return false; - } - towards.normalize(); - body.velocity.x = towards.x * MOVE_SPEED; - body.velocity.z = towards.z * MOVE_SPEED; - return true; - } - - /** Counts the drive down, and records how far the player went and how close it came to the anchor. */ - #measureDrive(ctx: Ctx, player: Object3D, stand: IStand, dt: number): void { - this.#driveLeft -= dt; - // The body writes its object back after the physics step, so distance is read from the spawn. - this.#driven = Math.hypot(player.position.x - stand.spawn.x, player.position.z - stand.spawn.z); - // The clearance the boulder's own collider leaves, sampled every frame it walked. - this.#closest = Math.min( - this.#closest, - Math.hypot(player.position.x - stand.anchor.x, player.position.z - stand.anchor.z), - ); - if (this.#driveLeft <= 0) ctx.state.set({ closestToBoulder: this.#closest, driveDone: 1 }); - } - - #setView(ctx: Ctx, view: ViewName): void { - if (this.#view === view) return; - this.#view = view; - currentView = view; - this.#placeCamera(); - ctx.state.set({ view }); - } - - #viewNow(ctx: Ctx): void { - this.#view = "ground"; - currentView = "ground"; - this.#placeCamera(); - ctx.state.set({ view: "ground" }); - } - - /** Placed every frame: the engine reconciles the camera, so a pose written once is not a pose. */ - #placeCamera(): void { - const view = this.#stand?.views[this.#view]; - if (view === undefined || this.#camera === undefined) return; - this.#camera.position.copy(view.at); - this.#camera.lookAt(view.look); - } - - /** |foot − terrain|, from the collider the player is standing on. Measured once it has settled. */ - #measureGround(ctx: Ctx, player: Object3D, groundY: number): void { - if (this.#settled) return; - this.#settled = true; - // A ray from above would hit the player's own capsule first, so the ground is read beside it: - // four rays half a metre out, whose mean cancels any planar slope. - let sum = 0; - for (const [dx, dz] of [ - [0.5, 0], - [-0.5, 0], - [0, 0.5], - [0, -0.5], - ] as const) { - const at = { x: player.position.x + dx, z: player.position.z + dz }; - const hit = ctx.physics.directSpaceState.intersectRay({ - from: { ...at, y: player.position.y + 50 }, - to: { ...at, y: groundY - 100 }, - }); - if (hit == null) - throw new Error(`No terrain contact beside the player at ${player.position.toArray()}.`); - sum += hit.position.y; - } - ctx.state.set({ - groundError: Math.abs(player.position.y - HALF_HEIGHT - RADIUS - sum / 4), - }); - } - - #costs(key: "frameP95" | "gpuP50" | "gpuP95"): Record { - const costs: Record = {}; - for (const [view, group] of viewWindows) costs[view] = median(group[key]); - return costs; - } -} diff --git a/examples/strata-terrain-preview/scripts/stage-native-assets.mjs b/examples/strata-terrain-preview/scripts/stage-native-assets.mjs index 1283fdec0f..9ad05762a9 100644 --- a/examples/strata-terrain-preview/scripts/stage-native-assets.mjs +++ b/examples/strata-terrain-preview/scripts/stage-native-assets.mjs @@ -15,17 +15,24 @@ import { } from "node:fs"; import { dirname, join, relative } from "node:path"; import { fileURLToPath } from "node:url"; +import { starterAssetsDir } from "@threenative/terrain/kit"; const here = dirname(dirname(fileURLToPath(import.meta.url))); const out = join(here, "assets"); // vite.config.ts publicDir, served at the root. const roots = [ - { dir: join(here, "../../packages/terrain/starter-assets"), mount: "", required: true }, + { dir: await starterAssetsDir(), mount: "", required: true }, + { dir: join(here, "src/world"), mount: "world", required: true }, ]; let linked = 0; function mirror(source, target) { for (const entry of readdirSync(source, { withFileTypes: true })) { + if ( + source === join(here, "src/world") && + !/\.(?:placements\.bin|world\.json|layers\.json|splat\.rgba8)$/u.test(entry.name) + ) + continue; const from = join(source, entry.name); const to = join(target, entry.name); if (entry.isDirectory()) { diff --git a/examples/strata-terrain-preview/scripts/verify-authored-world.mjs b/examples/strata-terrain-preview/scripts/verify-authored-world.mjs index 0a983f67f5..a280010356 100644 --- a/examples/strata-terrain-preview/scripts/verify-authored-world.mjs +++ b/examples/strata-terrain-preview/scripts/verify-authored-world.mjs @@ -10,6 +10,7 @@ import { tmpdir } from "node:os"; import { join, relative, resolve } from "node:path"; import { TerrainEditorController } from "@threenative/terrain/editor"; import { terrainEditor } from "@threenative/terrain/editor/server"; +import { starterAssetsDir } from "@threenative/terrain/kit"; import { createServer } from "vite"; import { advanceFixedStep, @@ -34,7 +35,7 @@ const plugin = terrainEditor({ documentPath: path }); const server = await createServer({ root, configFile: false, - publicDir: resolve("../../packages/terrain/starter-assets"), + publicDir: await starterAssetsDir(), server: { host: "127.0.0.1", port: Number(process.env.EDITOR_PORT ?? 5184), strictPort: true }, plugins: [plugin], optimizeDeps: { exclude: ["@threenative/terrain/editor"] }, @@ -55,7 +56,7 @@ try { "--timeout", "120000", "--artifacts", - "artifacts/playtest/authored-world", + join(temporary, "capture"), ]); let recorded; await withBrowserCapture(config, async (session) => { @@ -80,6 +81,14 @@ try { await advanceFixedStep(page, session.bridge, 2); }; await ready(); + assert.deepEqual( + await page.evaluate(() => [ + document.querySelector("#brush-strength").value, + document.querySelector("#brush-falloff").value, + ]), + ["0.35", "0.65"], + "The shell preserves fractional brush defaults before input", + ); // ---- GUI edits ------------------------------------------------------------------------------ const box = await page.locator("#viewport").boundingBox(); @@ -233,15 +242,20 @@ try { saved.revision, ); - // Placement matrices in the exported file: exact for the hand-posed one, and every id arrives. + // Placement matrices in the exported file: the hand-posed one, component by component within the + // tolerance a GLB round trip costs, and every id arrives. The exported translation is the only + // float32 hop between the two sides, so 2e-4 m is the floor that absorbs its rounding and still + // fails a placement that actually moved. const posed = after.placements.find((p) => p.id === chosen.id); - assert.deepEqual( - posed.matrix.slice(12, 15).map((v) => Math.round(v * 1e4) / 1e4), - [ - after.live.props.find((p) => p.id === chosen.id).position[0], - 55, - after.live.props.find((p) => p.id === chosen.id).position[2], - ].map((v) => Math.round(v * 1e4) / 1e4), + const live = after.live.props.find((p) => p.id === chosen.id); + const exported = posed.matrix.slice(12, 15); + const authored = [live.position[0], 55, live.position[2]]; + assert.equal(exported.length, authored.length, "The exported matrix has no translation"); + exported.forEach((value, axis) => + assert.ok( + Math.abs(value - (authored[axis] ?? 0)) <= 2e-4, + `The exported ${axis} translation is ${value} m, not the authored ${authored[axis]} m`, + ), ); assert.deepEqual( after.placements.map((p) => p.id).sort(), diff --git a/examples/strata-terrain-preview/scripts/verify-consumer.mjs b/examples/strata-terrain-preview/scripts/verify-consumer.mjs index 477ce6af16..e082112681 100644 --- a/examples/strata-terrain-preview/scripts/verify-consumer.mjs +++ b/examples/strata-terrain-preview/scripts/verify-consumer.mjs @@ -15,6 +15,7 @@ import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; import { copyFileSync, + existsSync, mkdirSync, mkdtempSync, readFileSync, @@ -116,7 +117,7 @@ try { const queries = [ ["request", "a walkable island with seeded noise hills, rivers and scattered trees", "Terrain", "@threenative/terrain"], ["mechanic", "procedural heightmap landscape for the game", "Terrain", "@threenative/terrain"], - ["mechanic", "export the terrain as a glb for another three.js project", "exportWorldGLB", "@threenative/terrain/export"], + ["mechanic", "export the terrain as a glb for another three.js project", "prepareWorldGLTF", "@threenative/terrain/export"], ["mechanic", "open the terrain brush and layer GUI around game-owned rendering", "mountTerrainEditor", "@threenative/terrain/editor"], ]; const found = queries.map(([scope, situation, symbol, importPath]) => { @@ -188,7 +189,7 @@ try { for (const required of [ "npm install --save-dev @threenative/terrain", "@threenative/terrain/editor/server", - "exportWorldGLB", + "prepareWorldGLTF", "node_modules/@threenative/terrain/AGENT_GUIDE.md", ]) assert(workflow.includes(required), `The shipped terrain workflow lacks: ${required}`); @@ -470,7 +471,7 @@ try { "stale conflict", "validatePlacementOverrides/applyPlacementOverrides", "toGeometry", - "exportWorldGLB", + "prepareWorldGLTF", ], glbBytes: handoff.glbBytes, polished: { @@ -486,6 +487,62 @@ try { join(consumer, "recipes.json"), JSON.stringify(Object.fromEntries(worldNames.map((name) => [name, recipes[name].toJSON()]))), ); + + // The forest starter's cooked world, copied into this isolated install as the fixture its + // vanilla-loader proof reads. A missing fixture fails instead of silently skipping the proof. + const forestWorld = join(repo, "packages/terrain/starter/forest/world"); + const hasForest = + existsSync(join(forestWorld, "world.glb")) && existsSync(join(forestWorld, "world.json")); + if (!hasForest) + throw new Error( + "Missing cooked forest fixture: run node packages/terrain/starter/forest/bake.mjs first", + ); + if (hasForest) { + copyFileSync(join(forestWorld, "world.glb"), join(consumer, "forest-world.glb")); + copyFileSync(join(forestWorld, "world.json"), join(consumer, "forest-world.json")); + } + + // PRD-583 AC-3: the cooked forest `world.glb` read by the stock three loader in this install, + // inside the same generated script. The loader makes one `InstancedMesh` per source primitive, + // so every child mesh's count is compared to the manifest's logical placement count for its + // species — never a sum of primitive copies — and no species may draw per-tree mesh copies. + const forestBlock = hasForest + ? ` + const manifest = JSON.parse(readFileSync("forest-world.json", "utf8")); + const logical = {}; + for (const cell of manifest.cells) + for (const run of cell.runs) logical[run.asset] = (logical[run.asset] ?? 0) + run.count; + const forestBytes = readFileSync("forest-world.glb"); + const forestScene = await new Promise((resolve, reject) => + new GLTFLoader().parse(forestBytes.buffer.slice(forestBytes.byteOffset, forestBytes.byteOffset + forestBytes.byteLength), "", resolve, reject)); + const bySpecies = {}; + for (const species of Object.keys(logical)) { + const node = forestScene.scene.getObjectByName(species + "_instances"); + if (node === undefined) throw new Error("forest GLB has no instancing node for species " + species); + let primitives = 0; + node.traverse((child) => { + if (!child.isInstancedMesh) return; + primitives++; + if (child.count !== logical[species]) + throw new Error("forest GLB species " + species + ": InstancedMesh count " + child.count + " != placement count " + logical[species]); + }); + if (primitives === 0) throw new Error("forest GLB species " + species + " has no InstancedMesh"); + bySpecies[species] = { placements: logical[species], primitives }; + } + let perTreeCopies = 0; + forestScene.scene.traverse((object) => { + if (object.isMesh && !object.isInstancedMesh && !object.name.startsWith("terrain_")) perTreeCopies++; + }); + if (perTreeCopies > 0) throw new Error("forest GLB draws " + perTreeCopies + " per-tree mesh copies"); + const forest = { + species: bySpecies, + speciesCount: Object.keys(bySpecies).length, + placements: Object.values(logical).reduce((sum, count) => sum + count, 0), + perTreeCopies, + glbBytes: forestBytes.length, + };` + : "\n const forest = null;"; + const author = ` import { createHash } from "node:crypto"; import { readFileSync, writeFileSync } from "node:fs"; @@ -493,6 +550,7 @@ try { import * as editor from "@threenative/terrain/editor"; import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; import { Box3 } from "three"; + Object.assign(globalThis, { self: globalThis }); if (typeof editor.mountTerrainEditor !== "function" || typeof editor.TerrainEditorController !== "function") throw new Error("The packed editor entry does not export the editor"); const recipes = JSON.parse(readFileSync("recipes.json", "utf8")); @@ -530,11 +588,18 @@ try { glbBytes: bytes.length, evaluateMs, exportMs, }); } - console.log(JSON.stringify(report)); +${forestBlock} + console.log(JSON.stringify({ report, forest })); `; writeFileSync(join(consumer, "author.mjs"), author); - const authored = JSON.parse(run("node", ["author.mjs"], { cwd: consumer })); + const authoredResult = JSON.parse(run("node", ["author.mjs"], { cwd: consumer })); + const authored = authoredResult.report; assert.equal(authored.length, worldNames.length); + if (hasForest) { + assert(authoredResult.forest, "the forest GLB fixture was not checked"); + assert.equal(authoredResult.forest.perTreeCopies, 0); + assert(authoredResult.forest.placements > 0); + } for (const entry of authored) { const sha = (heights) => createHash("sha256").update(JSON.stringify(heights)).digest("hex"); assert.equal( @@ -566,6 +631,7 @@ try { optionalTooling: optional, gameHandoff, fullWorlds, + forestWorld: authoredResult.forest, worlds: authored.map((entry) => ({ world: entry.world, heights: entry.heights, diff --git a/examples/strata-terrain-preview/scripts/verify-custom-assets.mjs b/examples/strata-terrain-preview/scripts/verify-custom-assets.mjs index d51f730f13..2ce4d16e46 100644 --- a/examples/strata-terrain-preview/scripts/verify-custom-assets.mjs +++ b/examples/strata-terrain-preview/scripts/verify-custom-assets.mjs @@ -28,6 +28,7 @@ import { import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { deflateSync } from "node:zlib"; +import { starterAssetsDir } from "@threenative/terrain/kit"; import { createServer } from "vite"; import { advanceFixedStep, @@ -303,6 +304,14 @@ export const GROUND_MAPS = { snow: ${JSON.stringify(flat)}, }; export const GROUND_TILE = { dirt: 3.4, grass: 2.6, moss: 3.6, rock: 9, sand: 3.2, snow: 12 }; +// The rock-face pair the temperate stone binds, in the stock table's own two keys, pointing at this +// consumer's own files. Its crag placements are empty because they are owner-local licensed models +// and a consumer that ships none exports none: the loaders read an absent model as procedural stone. +export const ROCKFACE_MAPS = { + diffuse: ${JSON.stringify(one(files.albedo))}, + normal: ${JSON.stringify(one(files.normal))}, +}; +export const WORLD_ROCKS = []; export const PROP_MAPS = { bark: ${JSON.stringify(surface)}, stone: ${JSON.stringify(surface)} }; export const SOIL_MAP = ${JSON.stringify(one(files.albedo))}; export const NEEDLE_ATLAS = ${JSON.stringify(one(files.albedo))}; @@ -393,7 +402,7 @@ try { const server = await createServer({ root, configFile: false, - publicDir: stock ? resolve(root, "../../packages/terrain/starter-assets") : false, + publicDir: stock ? await starterAssetsDir() : false, plugins: stock ? [] : [customArt()], server: { host: "127.0.0.1", port: PORT }, optimizeDeps: { exclude: ["@threenative/terrain/editor"] }, @@ -401,7 +410,7 @@ try { }); try { await server.listen(); - const url = `http://127.0.0.1:${PORT}/`; + const url = `http://127.0.0.1:${PORT}/?showcase=1`; const config = parseStandalonePlaytestArgs([ "--scenario", "playtests/terrain.playtest.json", @@ -413,7 +422,7 @@ try { "--timeout", "180000", "--artifacts", - `artifacts/playtest/custom-${arm}`, + join(temporary, `capture-${arm}`), ]); let observation = {}; await withBrowserCapture(config, async (session) => { @@ -478,6 +487,16 @@ try { } const customSource = customMappingSource(files, servedRoot); + // Every export the game's own table names must be in the consumer's copy of it, or a module that + // imports it fails to link and the run reports that as a browser error rather than as this. The + // names are read from the committed table, so a table that gains one cannot leave this behind. + for (const [, name] of stockSource.matchAll(/^export const (\w+)/gmu)) + assert.match( + customSource, + new RegExp(`^export const ${name}\\b`, "mu"), + `The consumer mapping does not export ${name}, which src/world/terrainAssets.ts exports`, + ); + const stock = await runArm("stock", stockSource, true); const custom = await runArm("custom", customSource, false); diff --git a/examples/strata-terrain-preview/scripts/verify-editor.mjs b/examples/strata-terrain-preview/scripts/verify-editor.mjs index c8fd99d478..b88998e214 100644 --- a/examples/strata-terrain-preview/scripts/verify-editor.mjs +++ b/examples/strata-terrain-preview/scripts/verify-editor.mjs @@ -4,6 +4,7 @@ import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { TerrainEditorController } from "@threenative/terrain/editor"; import { terrainEditor } from "@threenative/terrain/editor/server"; +import { starterAssetsDir } from "@threenative/terrain/kit"; import { createServer } from "vite"; import { advanceFixedStep, @@ -31,7 +32,7 @@ const server = await createServer({ configFile: false, // The game's own starter maps, served as the real dev server serves them: the surface proof binds // imported images into these textures, so they have to be the ones that loaded. - publicDir: resolve("../../packages/terrain/starter-assets"), + publicDir: await starterAssetsDir(), server: { host: "127.0.0.1", port: Number(process.env.EDITOR_PORT ?? 5197) }, plugins: [plugin], optimizeDeps: { exclude: ["@threenative/terrain/editor"] }, @@ -63,7 +64,10 @@ try { if (message.type() === "error") errors.push(message.text()); }); await session.page.waitForFunction( - () => window.strata?.state && !window.strata.busy, + () => + window.strata?.state && + !window.strata.busy && + window.strata.view.inspect().renderedRevision, {}, { timeout: 10000 }, ); @@ -78,7 +82,9 @@ try { }); const start = performance.now(); await session.page.waitForFunction( - (revision) => window.strata?.renderedRevision === revision, + (revision) => + window.strata?.renderedRevision === revision && + window.strata.view.inspect().renderedRevision === revision, accepted.revision, { timeout: 2000 }, ); @@ -419,16 +425,91 @@ try { "Only the deliberately rejected GUI imports may produce a console error", ); }); + // A heavy erosion layer makes the 513 preview build run about 6 s, so the scenario's cancel click + // (which arrives roughly 3 s after the build starts, behind the runner's per-step settling) lands mid-build. + const cancellationFixture = await controller.snapshot(); + await controller.commit({ + baseRevision: cancellationFixture.revision, + commands: [ + { + op: "upsert", + layer: { + id: "worker-cancel-erosion", + type: "erode", + enabled: true, + params: { method: "hydraulic", droplets: 200000, maxSteps: 128 }, + }, + }, + ], + }); const report = await runStandalonePlaytest(config); + const strokeTicks = report.observations?.resourceSeries + ?.filter((sample) => ["brush-apply", "second-stroke-50ms"].includes(sample.label)) + .map(({ label, tick }) => ({ label, tick })); + console.log( + JSON.stringify({ + editorAssertions: report.assertionResults?.filter((result) => + /editor(PersistedStrokes|Announcement|Focus|Tool)/u.test(result.id), + ), + strokeTicks, + }), + ); + assert.equal(strokeTicks?.length, 2, "Both stroke boundaries must be observed"); + assert.equal( + strokeTicks[1].tick - strokeTicks[0].tick, + 3, + "Stroke boundaries must be 50 ms apart in fixed time", + ); + const diskStrokes = JSON.parse(readFileSync(path, "utf8")).recipe.layers.filter( + (layer) => layer.name === "Sculpt stroke", + ).length; + const snapshotStrokes = (await controller.snapshot()).document.recipe.layers.filter( + (layer) => layer.name === "Sculpt stroke", + ).length; + assert.equal(diskStrokes, snapshotStrokes, "world.json must contain every acknowledged stroke"); + const initialStrokes = cancellationFixture.document.recipe.layers.filter( + (layer) => layer.name === "Sculpt stroke", + ).length; + assert.equal( + diskStrokes - initialStrokes, + 4, + "world.json must retain the three keyboard strokes and recovery edit", + ); + console.log(JSON.stringify({ diskStrokes, snapshotStrokes, initialStrokes })); assert(report.assertionResults?.length > 0, "Scenario assertions were not observed"); assert( report.assertionResults.every((result) => result.pass), - JSON.stringify(report.assertionResults), + JSON.stringify({ results: report.assertionResults, diagnostics: report.diagnostics }), ); assert( !report.diagnostics.some((diagnostic) => diagnostic.severity === "error"), JSON.stringify(report.diagnostics), ); + const conflictReport = await runStandalonePlaytest({ + ...config, + scenarioPath: resolve("playtests/editor-conflict.playtest.json"), + url: new URL("conflict.html", activation.editorUrl).href, + artifactDirectory: resolve("artifacts/playtest/editor-conflict"), + }); + assert.deepEqual( + conflictReport.observations?.console + .filter((message) => message.type === "error") + .map((message) => message.text), + Array(4).fill("Failed to load resource: the server responded with a status of 409 (Conflict)"), + "Only the four injected stale writes may produce conflict-lane console errors", + ); + assert(conflictReport.assertionResults?.length > 0, "Conflict assertions were not observed"); + assert( + conflictReport.assertionResults.every((result) => result.pass), + JSON.stringify({ + results: conflictReport.assertionResults, + diagnostics: conflictReport.diagnostics, + }), + ); + assert( + !conflictReport.diagnostics.some((diagnostic) => diagnostic.severity === "error"), + JSON.stringify(conflictReport.diagnostics), + ); } finally { await server.close(); rmSync(temporary, { recursive: true, force: true }); diff --git a/examples/strata-terrain-preview/scripts/verify-game-handoff.mjs b/examples/strata-terrain-preview/scripts/verify-game-handoff.mjs index 6281e93d26..04f5af5829 100644 --- a/examples/strata-terrain-preview/scripts/verify-game-handoff.mjs +++ b/examples/strata-terrain-preview/scripts/verify-game-handoff.mjs @@ -74,12 +74,18 @@ export async function verifyGameHandoff({ repo, temporary, polished, withPage }) ...templateManifest.optionalDependencies, }), ); - const packages = JSON.parse( + const workspacePackages = JSON.parse( execFileSync("pnpm", ["exec", "tsx", "scripts/workspace-packages.ts", "--json"], { cwd: repo, encoding: "utf8", }), - ).filter(({ name }) => dependencies.has(name)); + ); + // `@threenative/assets` (the template's dev-only asset pipeline) depends on `@threenative/terrain`, + // so the terrain tarball is packed to supply the pnpm override for that transitive edge. The game + // itself must not declare or install the authoring package (asserted below). + const packages = workspacePackages.filter( + ({ name }) => dependencies.has(name) || name === "@threenative/terrain", + ); assert(packages.length > 0, "minimal template has no workspace packages to pack"); for (const { name, directory } of packages) { const before = new Set(readdirSync(packs)); @@ -93,10 +99,23 @@ export async function verifyGameHandoff({ repo, temporary, polished, withPage }) pathToFileURL(join(repo, "packages/create-threenative/dist/index.js")).href ); const scaffolded = await createProject( - { install: true, packageSources: archives, target: "handoff-game", template: "minimal" }, + { install: false, packageSources: archives, target: "handoff-game", template: "minimal" }, temporary, ); const game = scaffolded.target; + // `packageSources` writes the terrain source into `dependencies`; the game must not depend on it, + // so it is removed and the install runs with the pnpm override alone. The override still resolves + // the asset pipeline's transitive edge to the local tarball, without linking terrain into the + // game's own tree. + const gameManifestPath = join(game, "package.json"); + const gameManifest = JSON.parse(readFileSync(gameManifestPath, "utf8")); + gameManifest.dependencies = Object.fromEntries( + Object.entries(gameManifest.dependencies ?? {}).filter( + ([name]) => name !== "@threenative/terrain", + ), + ); + writeFileSync(gameManifestPath, `${JSON.stringify(gameManifest, null, 2)}\n`); + run("pnpm", ["install"], { cwd: game }); // --- no authoring package in the game, by its manifest, its install and its source ------------------ const manifest = JSON.parse(readFileSync(join(game, "package.json"), "utf8")); diff --git a/examples/strata-terrain-preview/scripts/verify-sky.mjs b/examples/strata-terrain-preview/scripts/verify-sky.mjs index 765b650095..e4e074fa0a 100644 --- a/examples/strata-terrain-preview/scripts/verify-sky.mjs +++ b/examples/strata-terrain-preview/scripts/verify-sky.mjs @@ -321,6 +321,8 @@ export async function verifySky(session, controller, captures) { swapped.asset.sha256, { timeout: 20000 }, ); + // The hash is published before the decode finishes, so wait for the load itself. + await state("background", "ready"); const replaced = await live(); assert.equal(replaced.images.background.state, "ready"); near( diff --git a/examples/strata-terrain-preview/scripts/verify-starter-kit.mjs b/examples/strata-terrain-preview/scripts/verify-starter-kit.mjs index db0db14911..7ba8494d4c 100644 --- a/examples/strata-terrain-preview/scripts/verify-starter-kit.mjs +++ b/examples/strata-terrain-preview/scripts/verify-starter-kit.mjs @@ -39,7 +39,6 @@ const FIXTURES = join(import.meta.dirname, "fixtures/kit-game"); */ const KITS = { forest: { - scene: "Forest.ts", playtest: "kit.playtest.json", generatedStand: true, water: true, @@ -49,7 +48,6 @@ const KITS = { views: ["edge", "ground", "lake", "overview"], }, alpine: { - scene: "Alpine.ts", playtest: "alpine.playtest.json", generatedStand: false, water: false, @@ -59,7 +57,6 @@ const KITS = { views: ["boulders", "edge", "ground", "overview"], }, desert: { - scene: "Desert.ts", playtest: "desert.playtest.json", generatedStand: false, water: false, @@ -69,7 +66,6 @@ const KITS = { views: ["boulders", "edge", "ground", "overview"], }, tundra: { - scene: "Tundra.ts", playtest: "tundra.playtest.json", generatedStand: false, water: true, @@ -79,7 +75,6 @@ const KITS = { views: ["boulders", "edge", "ground", "overview"], }, coastal: { - scene: "Coastal.ts", playtest: "coastal.playtest.json", generatedStand: false, water: true, @@ -335,7 +330,14 @@ async function main() { await createProject( { install: true, - packageSources: Object.fromEntries(GAME_PACKAGES.map(({ name }) => [name, archives[name]])), + // The terrain tarball is a source here too, not only the later `pnpm add -D`: the minimal + // template's `@threenative/assets` dev dependency now depends on `@threenative/terrain`, + // and the override this writes is what resolves that transitive edge to the local tarball + // instead of the registry (which 404s). The kit's own `pnpm add -D` then moves it to the + // devDependency the README names. + packageSources: Object.fromEntries( + [...GAME_PACKAGES, TERRAIN_PACKAGE].map(({ name }) => [name, archives[name]]), + ), target: "kit-game", template: "minimal", }, @@ -395,11 +397,26 @@ export const stand = { cpSync(join(FIXTURES, "playtests", CONFIG.playtest), join(game, "playtests/kit.playtest.json")); if (process.env.KIT_BEFORE === "1") stripWaterRows(join(game, "playtests/kit.playtest.json")); mkdirSync(join(game, "src/scenes"), { recursive: true }); - cpSync(join(FIXTURES, CONFIG.scene), join(game, "src/scenes", CONFIG.scene)); - // game.ts imports the kit's scene through this one re-export, so the game itself is the same for each kit. + cpSync(join(FIXTURES, "KitScene.ts"), join(game, "src/scenes/KitScene.ts")); + const title = KIT[0].toUpperCase() + KIT.slice(1); writeFileSync( - join(game, "src/scenes/KitScene.ts"), - `export { ${CONFIG.scene.replace(/\.ts$/u, "")} as KitScene, noteFrameBudget } from "./${CONFIG.scene.replace(/\.ts$/u, ".js")}";\n`, + join(game, "src/terrain/kit.ts"), + ` +import { add${title}, COLLIDERS } from "./${KIT}/world.js"; +import { ${KIT}Daylight as kitDaylight } from "./${KIT}/sky.js"; +import type { IStarterWorld } from "@threenative/terrain/starter"; +${CONFIG.generatedStand ? 'import { stand } from "./forest/stand.js";' : ""} +export { COLLIDERS, kitDaylight }; +export const KIT: string = "${title}"; +export const forestStand: { fir: { x: number; z: number }; spawn: { x: number; z: number }; edge: { x: number; z: number }; groundY: number } | undefined = ${CONFIG.generatedStand ? "stand" : "undefined"}; +export interface IKitWorld extends IStarterWorld { + readonly lakes?: readonly { at: readonly [number, number]; radius: number }[]; + readonly seaLevel?: number; + readonly water?: { lakes: number; rivers: number; dispose(): void }; + readonly sea?: { dispose(): void }; +} +export const addKit: (...args: Parameters) => Promise = add${title}; +`, ); cpSync( join(import.meta.dirname, "../src/render/loading.ts"), diff --git a/examples/strata-terrain-preview/scripts/verify-surfaces.mjs b/examples/strata-terrain-preview/scripts/verify-surfaces.mjs index 151f7d7401..8276785134 100644 --- a/examples/strata-terrain-preview/scripts/verify-surfaces.mjs +++ b/examples/strata-terrain-preview/scripts/verify-surfaces.mjs @@ -105,6 +105,15 @@ export async function verifySurfaces(session, controller, captures) { await waitSource("bark.normal", "tilt-normal", tilt.sha256); await waitSource("bark.roughness", "matte-rough", matte.sha256); + await page.waitForFunction( + () => + window.strata.view + .inspectSurfaces() + .filter((r) => ["bark.albedo", "bark.normal", "bark.roughness"].includes(r.input)) + .every((r) => r.gpuFormat !== null), + {}, + { timeout: 15000 }, + ); const mapped = await readings(); const byInput = Object.fromEntries(mapped.map((r) => [r.input, r])); // Numerical meaning: the bytes the texture holds are the file's bytes, unconverted, and each diff --git a/examples/strata-terrain-preview/scripts/verify-world-export.mjs b/examples/strata-terrain-preview/scripts/verify-world-export.mjs index 1fbd0288bf..5a1733c2ec 100644 --- a/examples/strata-terrain-preview/scripts/verify-world-export.mjs +++ b/examples/strata-terrain-preview/scripts/verify-world-export.mjs @@ -6,6 +6,7 @@ import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { TerrainEditorController } from "@threenative/terrain/editor"; import { terrainEditor } from "@threenative/terrain/editor/server"; +import { starterAssetsDir } from "@threenative/terrain/kit"; import { Matrix4, Quaternion, Vector3 } from "three"; import { createServer } from "vite"; import { @@ -26,7 +27,7 @@ try { configFile: false, // The project's own Vite config serves the CC0 starter sets from the terrain package; this // standalone server has to be told, or the export would be proven with no art loaded at all. - publicDir: resolve("../../packages/terrain/starter-assets"), + publicDir: await starterAssetsDir(), server: { host: "127.0.0.1", port: Number(process.env.EDITOR_PORT ?? 5185) }, plugins: [plugin], optimizeDeps: { exclude: ["@threenative/terrain/editor"] }, diff --git a/examples/strata-terrain-preview/src/game.ts b/examples/strata-terrain-preview/src/game.ts index 9c1ec7ba13..db41c07293 100644 --- a/examples/strata-terrain-preview/src/game.ts +++ b/examples/strata-terrain-preview/src/game.ts @@ -10,20 +10,19 @@ import { } from "@threenative/physics"; import { CapsuleGeometry, + Float32BufferAttribute, Mesh, MeshStandardMaterial, OrthographicCamera, PerspectiveCamera, Vector3, } from "three"; -import { BIOMES, type WorldName } from "./render/biomes.js"; -import { - createLoadingScreen, - createSpawnReadiness, - spawnReadinessSnapshot, -} from "./render/loading.js"; +import { BENCHMARK, type ViewName } from "./render/benchmark.js"; +import { BIOMES, type IWorldBiome, type WorldName, measureBiome } from "./render/biomes.js"; +import { createLoadingScreen } from "./render/loading.js"; import { createOcean, createWaterMesh } from "./render/ocean.js"; import { type ICanopyComparison, loadPack, loadSkyLight } from "./render/pack.js"; +import { loadPlacements } from "./render/placements.js"; import { loadPreparedProps } from "./render/prepared.js"; import { createPropSurfaces } from "./render/propMaterials.js"; import { @@ -38,106 +37,12 @@ import { variantFor, } from "./render/props.js"; import { type IRiverWater, WATER_LAYER, createLakes, createRivers } from "./render/river.js"; -import { type IPlacementField, scatterProps } from "./render/scatter.js"; import { type IOutdoorSky, createOutdoorSky, installOutdoorOcclusion } from "./render/sky.js"; -import { type IBakedWorld, createTerrain, paintCanopy } from "./render/terrain.js"; +import { type IBakedWorld, createTerrain } from "./render/terrain.js"; import { preparePropTextures } from "./render/texturePreparation.js"; import { medianOfGrowingSamples as median } from "./viewStatistics.js"; import baked from "./world/baked.json"; -/** - * The fixed benchmark framings, in world metres, per world. - * - * Fixed rather than relative to the player so a capture at seed 73 is the same picture on every - * machine, and so the rubric's "close ground/vegetation view" and "landscape overview" are two - * numbers rather than two moods. `eye` is the height above the terrain under the camera, which is - * what makes "eye height ~1.7 m" true on a slope instead of true only on a flat. - * - * The meadow focus is where the ground is grass with spruces on it and a slope to look along, and - * it is also where the dense grass goes: ground cover is placed around this point, not around the - * origin, because a meadow is a place rather than a texture. - */ -/** Every framing the `V` key can hold, whether or not the current world has one for it. */ -type ViewName = - | "ridge" - | "mesa" - | "plain" - | "horizon-sea" - | "meadow-close" - | "overview" - | "player" - | "river"; - -interface IBenchmarkPose { - /** World x and z of the eye, and the height above the terrain under it. */ - readonly at: readonly [number, number]; - readonly eye: number; - /** Absolute elevation for an overview over surveyed scenery beyond the playable collider. */ - readonly eyeY?: number; - /** World x and z to look at, and the height above the terrain under *that*. */ - readonly look: readonly [number, number]; - readonly lookUp: number; - /** - * An absolute world height to look at instead, for a target with no ground under it: a sea - * horizon is a direction, and thirty metres below the surface three hundred metres out is how a - * hillside tips its view down far enough for the water to be a band rather than a line. - */ - readonly lookY?: number; -} - -interface IBenchmark { - /** Where this world's ground cover is placed. */ - readonly focus: { readonly x: number; readonly z: number }; - readonly poses: Readonly>; - /** The order the `V` key walks, and the framings the playtest screenshots. */ - readonly views: readonly ViewName[]; -} - -const BENCHMARK: Record = { - alpine: { - focus: { x: 180, z: -190 }, - poses: { - ridge: { at: [220, -220], eye: 150, look: [-75, 40], lookUp: 0, lookY: 320 }, - overview: { at: [850, -1000], eye: 0, eyeY: 760, look: [-55, 35], lookUp: 0, lookY: 330 }, - }, - views: ["player", "ridge", "overview"], - }, - desert: { - focus: { x: 170, z: 180 }, - poses: { - mesa: { at: [195, 190], eye: 3.2, look: [-35, -30], lookUp: 20 }, - overview: { at: [200, 200], eye: 165, look: [-40, -30], lookUp: 10 }, - }, - views: ["player", "mesa", "overview"], - }, - tundra: { - focus: { x: 150, z: 160 }, - poses: { - plain: { at: [166, 180], eye: 2.2, look: [-65, -50], lookUp: 2 }, - overview: { at: [180, 180], eye: 62, look: [-20, -50], lookUp: 2 }, - }, - views: ["player", "plain", "overview"], - }, - forest: { - focus: { x: 150, z: -100 }, - poses: { - "meadow-close": { at: [165, -95], eye: 1.7, look: [-70, -180], lookUp: 2.2 }, - overview: { at: [190, 170], eye: 65, look: [-70, 25], lookUp: 5 }, - river: { at: [-145, -120], eye: 1.7, look: [-110, -185], lookUp: 0 }, - }, - views: ["player", "meadow-close", "overview", "river"], - }, - coastal: { - focus: { x: 80, z: 5 }, - poses: { - "meadow-close": { at: [110, -35], eye: 1.7, look: [155, 100], lookUp: 1.5 }, - overview: { at: [-140, -120], eye: 75, look: [160, 115], lookUp: 0 }, - "horizon-sea": { at: [185, 145], eye: 6, look: [220, 450], lookUp: 0, lookY: 1.5 }, - }, - views: ["player", "meadow-close", "overview", "horizon-sea"], - }, -}; - /** The last closed frame-budget window, published into state by the scene. */ const budget = { drawCalls: 0, @@ -193,6 +98,10 @@ const worldLoads = { }; const initialState = { + elevationMin: 0, + elevationMax: 0, + snowFrom: 0, + snowTo: 0, canopyComparison: { normals: "authored", specular: "disabled" } as ICanopyComparison, showcase: false, worldReady: false, @@ -209,6 +118,7 @@ const initialState = { streamingAdmissionMs: 0, streamingAdmissionBacklog: 0, timeToReadyMs: -1, + enterMs: -1, tasksAvailable: false, longestLoadTaskMs: -1, afterFirstFrameTaskMs: -1, @@ -250,6 +160,7 @@ const initialState = { propInstances: 0, propTriangles: 0, preparedLodBaseSpread: 0, + compressedTexturesPrepared: 0, preparedLevelsWithoutSolid: 0, view: "player", windowDrawCalls: 0, @@ -283,67 +194,121 @@ function terrainScene(world: WorldName): new () => Scene { static override readonly initialState = initialState; #data: IBakedWorld | undefined; - override async load(): Promise { + #biome: IWorldBiome | undefined; + #erosionSamples = { flow: 0, deposition: 0 }; + #scatter: Awaited> | undefined; + #terrain: ReturnType | undefined; + #fallbackParts: ReturnType | undefined; + override async load(ctx: TerrainCtx): Promise { this.#data = world === "forest" || world === "coastal" ? baked[world] : (await worldLoads[world]()).default; + this.#biome = measureBiome(BIOMES[world], this.#data.heights); + this.#scatter = await loadPlacements(ctx.assets, world); + // Prepare scenery while the scene loads; enter only attaches it. + this.#terrain = createTerrain(this.#data, ctx.assets, this.#biome); + this.#sky = createOutdoorSky(ctx.camera, this.#biome); + if (world === "coastal") { + this.#ocean = createOcean(); + this.#water = createWaterMesh(this.#ocean, this.#data); + } + this.#lake = createLakes(this.#data.lakes ?? [], this.#terrain.field, world === "forest"); + this.#erosionSamples = { + flow: this.#data.erosion?.flow.filter((value) => value > 0).length ?? 0, + deposition: this.#data.erosion?.deposition.filter((value) => value > 0).length ?? 0, + }; + this.#fallbackParts = buildPropVariants( + undefined, + world === "alpine" ? 2 : world === "tundra" ? 1.4 : undefined, + ); } #player: CharacterBody3D | undefined; #surfaces: { advance: (elapsed: number) => void } | undefined; #elapsed = 0; #sky: IOutdoorSky | undefined; #ocean: ReturnType | undefined; + #water: ReturnType | undefined; #river: IRiverWater | undefined; #lake: IRiverWater | undefined; override enter(ctx: TerrainCtx): void { + const enterStart = performance.now(); ctx.add(ctx.camera); const data = this.#data; if (!data) throw new Error(`World ${world} was not loaded`); // ctx.goto carries state; camera names from the outgoing biome must not carry with it. - ctx.state.set({ view: "player", worldReady: false, loadingError: "" }); + ctx.state.set({ + view: "player", + worldReady: false, + loadingError: "", + spawnCellsRequired: 0, + spawnCellsLoaded: 0, + compressedTexturesPrepared: 0, + }); let released = false; const textureUploads = new AbortController(); let propsStage = "waiting-for-physics"; - let propsSettled = false; let preparation: IPropPreparationProgress | undefined; let completedPropsWork = 0; - const admission = createSpawnReadiness("Strata spawn", 120_000, captureSpawnFailure); + // One scene-local spawn gate: a single deadline taken here, settled once by the world readiness + // join, a failure or teardown. `WorldCells.whenSpawnReady` is the data authority and each + // streamed world's `prewarmed` is the draw gate; no frame polls either. + const deadlineAt = performance.now() + 120_000; + let spawnCells = { required: 0, loaded: 0 }; + let spawnReady = false; + let spawnError = ""; + let spawnSettled = false; + let capturingSpawnFailure = false; + let resolveSpawn: () => void = () => undefined; + let rejectSpawn: (reason: Error) => void = () => undefined; + const spawnPromise = new Promise((done, failed) => { + resolveSpawn = done; + rejectSpawn = failed; + }); + function settleSpawn(reason?: Error): void { + if (spawnSettled || capturingSpawnFailure) return; + spawnSettled = true; + clearTimeout(spawnTimer); + if (reason === undefined) { + spawnReady = true; + resolveSpawn(); + } else { + rejectSpawn(reason); + } + } + function failSpawn(reason: unknown): void { + if (spawnSettled || capturingSpawnFailure) return; + const message = + (reason instanceof Error ? reason.message : String(reason)) || "Strata spawn failed"; + capturingSpawnFailure = true; + try { + captureSpawnFailure(message); + } catch { + // A diagnostic must never replace the admission failure or leave its promise pending. + } + capturingSpawnFailure = false; + spawnError = message; + settleSpawn(new Error(message)); + ctx.state.set({ worldReady: spawnReady, loadingError: spawnError }); + } + const spawnTimer: ReturnType = setTimeout( + () => failSpawn("Strata spawn admission exceeded 120000 ms"), + 120_000, + ); function captureSpawnFailure(reason: string): void { console.error( - `TN_STRATA_SPAWN_FAILURE ${JSON.stringify( - spawnReadinessSnapshot({ - reason, - atMs: performance.now(), - world, - sceneUuid: ctx.scene.uuid, - released, - gate: admission, - stage: propsStage, - propsSettled, - ...(preparation === undefined ? {} : { preparation }), - coverage: props?.readinessAt(spawn), - assets: ctx.assets.progress, - startup: ctx.startup, - }), - )}`, + `TN_STRATA_SPAWN_FAILURE ${JSON.stringify({ + reason: reason.slice(0, 256), + world, + stage: (preparation?.phase ?? propsStage).slice(0, 64), + })}`, ); } - const failSpawn = (reason: unknown): void => { - admission.fail(reason); - ctx.state.set({ worldReady: admission.ready, loadingError: admission.error }); - }; if (ctx.startup.phase !== "ready") ctx.startup.hold( "strata-spawn", - // Core polls this once per rendered frame, so the gate settles under a playtest's - // deterministic clock, which advances no fixed step while startup is held. - () => { - const region = props?.readinessAt(spawn); - admission.observe(region?.ready === true, region?.failures ?? 0); - return admission.ready || admission.error !== ""; - }, + spawnPromise, 120_000, () => completedPropsWork + @@ -361,12 +326,15 @@ function terrainScene(world: WorldName): new () => Scene - Promise.all([ctx.startup.whenReady(), admission.promise]).then(() => undefined), + Promise.all([ctx.startup.whenReady(), spawnPromise]).then(() => undefined), }, }); ctx.afterPhysics(() => loading.update()); - const biome = BIOMES[world]; - const { field, mesh } = createTerrain(data, ctx.assets, biome); + const biome = this.#biome; + if (!biome) throw new Error(`World ${world} elevations were not measured`); + const terrain = this.#terrain; + if (!terrain) throw new Error(`World ${world} terrain was not loaded`); + const { field, mesh } = terrain; ctx.add(mesh); // Scenery: nothing moves the ground, so its transform is composed once instead of every frame. markStatic(mesh); @@ -394,7 +362,8 @@ function terrainScene(world: WorldName): new () => Scene Scene Scene Scene value > 0).length ?? 0; - const erosionDepositSamples = - data.erosion?.deposition.filter((value) => value > 0).length ?? 0; - const propField: IPlacementField = { - world, - colors: data.colors, - erosion: data.erosion, - field, - resolution: data.size === 0 ? 0 : field.rows, - size: data.size, - waterLevel: data.waterLevel, - lakes: data.lakes, - rivers: data.rivers, - }; + const erosionFlowSamples = this.#erosionSamples.flow; + const erosionDepositSamples = this.#erosionSamples.deposition; // Every fixed camera stands in a clearing: a trunk a metre from the lens is a green wall, not a // framing, and the seed decides where trees land, so the eyes are kept open by rule. const river = createRivers( @@ -588,16 +543,14 @@ function terrainScene(world: WorldName): new () => Scene river.dispose() }); } - const scatter = scatterProps( - propField, - BENCHMARK[world].focus, - Object.values(BENCHMARK[world].poses) - .filter((pose) => pose.eye < 20) - .map((pose) => [pose.at[0], pose.at[1], 10] as const), - ); - paintCanopy(mesh, data, scatter.placements); + const scatter = this.#scatter; + if (!scatter || scatter.canopy.length !== data.resolution ** 2) + throw new Error(`World ${world} placements do not match the terrain`); + mesh.geometry.setAttribute("canopy", new Float32BufferAttribute(scatter.canopy, 1)); const fallbackSaplingHeight = world === "alpine" ? 2 : world === "tundra" ? 1.4 : undefined; - let propParts = buildPropVariants(undefined, fallbackSaplingHeight); + const fallbackParts = this.#fallbackParts; + if (!fallbackParts) throw new Error(`World ${world} prop variants were not loaded`); + let propParts = fallbackParts; const flat = flatPropMaterials(); let props: Awaited>; let readyTimePending = false; @@ -606,13 +559,14 @@ function terrainScene(world: WorldName): new () => Scene void) | undefined; ctx.entities.add("props-lifetime", { dispose: () => { released = true; textureUploads.abort(new Error("Strata scene released during texture preparation.")); - admission.cancel(); + settleSpawn(new Error("Strata spawn cancelled")); loading.finish(); surfacesDispose?.(); flat.dispose(); @@ -688,7 +642,7 @@ function terrainScene(world: WorldName): new () => Scene { @@ -730,6 +684,9 @@ function terrainScene(world: WorldName): new () => Scene { + compressedTexturesPrepared = completed; + }, ); if (released) return; propsStage = "streaming"; @@ -770,6 +727,28 @@ function terrainScene(world: WorldName): new () => Scene stream.whenSpawnReady(spawn, 60, remaining))), + ...props.worlds.map((stream) => stream.prewarmed), + ]).then( + ([regions]) => { + if (released || spawnSettled) return; + spawnCells = { + required: regions.reduce((sum, region) => sum + region.requiredCells, 0), + loaded: regions.reduce((sum, region) => sum + region.loadedCells, 0), + }; + settleSpawn(); + }, + (reason) => failSpawn(reason), + ); + } const alpineCrags = world === "alpine" @@ -936,15 +915,12 @@ function terrainScene(world: WorldName): new () => Scene { - propsSettled = true; - }, - (error) => { - propsSettled = true; - if (!released) failSpawn(error); - }, - ); + void work.catch((error) => { + if (!released) { + failSpawn(error); + ctx.state.set({ worldReady: spawnReady, loadingError: spawnError }); + } + }); } frames++; // `props.meshes` walks every streamed world; three walks per fixed step were 8.3 s of a @@ -961,12 +937,8 @@ function terrainScene(world: WorldName): new () => Scene Scene group.gpuMs)) : -1, measuredGpuViewCount: viewGpu.length, tasksAvailable: budget.tasksAvailable, @@ -1019,6 +995,8 @@ function terrainScene(world: WorldName): new () => Scene Scene Scene>; + /** The order the `V` key walks, and the framings the playtest screenshots. */ + readonly views: readonly ViewName[]; +} + +export const BENCHMARK: Record = { + alpine: { + focus: { x: 180, z: -190 }, + poses: { + ridge: { at: [220, -220], eye: 150, look: [-75, 40], lookUp: 0, lookY: 320 }, + overview: { at: [850, -1000], eye: 0, eyeY: 760, look: [-55, 35], lookUp: 0, lookY: 330 }, + }, + views: ["player", "ridge", "overview"], + }, + desert: { + focus: { x: 170, z: 180 }, + poses: { + mesa: { at: [195, 190], eye: 3.2, look: [-35, -30], lookUp: 20 }, + overview: { at: [200, 200], eye: 165, look: [-40, -30], lookUp: 10 }, + }, + views: ["player", "mesa", "overview"], + }, + tundra: { + focus: { x: 150, z: 160 }, + poses: { + plain: { at: [166, 180], eye: 2.2, look: [-65, -50], lookUp: 2 }, + overview: { at: [180, 180], eye: 62, look: [-20, -50], lookUp: 2 }, + }, + views: ["player", "plain", "overview"], + }, + forest: { + focus: { x: 150, z: -100 }, + poses: { + "meadow-close": { at: [165, -95], eye: 1.7, look: [-70, -180], lookUp: 2.2 }, + overview: { at: [190, 170], eye: 65, look: [-70, 25], lookUp: 5 }, + river: { at: [-145, -120], eye: 1.7, look: [-110, -185], lookUp: 0 }, + }, + views: ["player", "meadow-close", "overview", "river"], + }, + coastal: { + focus: { x: 80, z: 5 }, + poses: { + "meadow-close": { at: [110, -35], eye: 1.7, look: [155, 100], lookUp: 1.5 }, + overview: { at: [-140, -120], eye: 75, look: [160, 115], lookUp: 0 }, + "horizon-sea": { at: [185, 145], eye: 6, look: [220, 450], lookUp: 0, lookY: 1.5 }, + }, + views: ["player", "meadow-close", "overview", "horizon-sea"], + }, +}; diff --git a/examples/strata-terrain-preview/src/render/biomes.ts b/examples/strata-terrain-preview/src/render/biomes.ts index a474782a44..12fb29ccc6 100644 --- a/examples/strata-terrain-preview/src/render/biomes.ts +++ b/examples/strata-terrain-preview/src/render/biomes.ts @@ -28,13 +28,26 @@ import { export type WorldName = "forest" | "coastal" | "alpine" | "desert" | "tundra"; type RGB = readonly [number, number, number]; +type HeightBand = + | "alpine" + | "rock" + | "treeline" + | "cap" + | "desertRock" + | "alpineRock" + | "alpineHollow"; +type HeightBands = Readonly>; + export interface IBiome { readonly world: WorldName; readonly grassTint: RGB; readonly stoneTint: RGB; + /** Prop-only tint; terrain rock keeps its own grading. */ + readonly propStoneTint?: RGB; readonly snowTint: RGB; readonly distantStone: RGB; - readonly snow: readonly [number, number, number]; + readonly snowFractions: readonly [number, number, number]; + readonly heightFractions: HeightBands; readonly maps: Record; readonly horizon: "mountain" | "alpine" | "mesa" | "plain"; readonly sun: { @@ -79,7 +92,17 @@ const temperate: IBiome = { stoneTint: [0.48, 0.44, 0.39], snowTint: [1, 1, 1], distantStone: [0.115, 0.105, 0.088], - snow: [145, 195, 0.22], + // Snow and caps belong to the surveyed horizon, beyond the resident height range. + snowFractions: [3.6, 4.8, 0.22], + heightFractions: { + alpine: [0.95, 1.65], + rock: [0.35, 1.8], + treeline: [2.75, 6], + cap: [5.6, 7.75], + desertRock: [0.18, 0.48], + alpineRock: [0.06, 0.14], + alpineHollow: [0.05, 0.12], + }, maps: GROUND_MAPS, horizon: "mountain", sun: { color: 0xffe3b5, intensity: 5.8, direction: [-180, 145, 80], shadowRadius: 1.7 }, @@ -114,6 +137,15 @@ export const BIOMES: Record = { coastal: { ...temperate, world: "coastal", + snowFractions: [1.3, 1.75, 0.22], + heightFractions: { + ...temperate.heightFractions, + alpine: [0.34, 0.6], + rock: [0.12, 0.64], + treeline: [1, 2.15], + cap: [2, 2.8], + }, + propStoneTint: [1, 1, 1], sun: { ...temperate.sun, intensity: 5.2, direction: [-180, 150, -120] }, fill: { ...temperate.fill, intensity: 0.66 }, haze: { ...temperate.haze, density: 0.0008 }, @@ -137,7 +169,14 @@ export const BIOMES: Record = { stoneTint: [0.86, 0.88, 0.9], snowTint: [1.9, 1.93, 2], distantStone: [0.23, 0.225, 0.215], - snow: [50, 180, 0.39], + snowFractions: [0.6, 0.85, 0.39], + heightFractions: { + ...temperate.heightFractions, + alpine: [0.06, 0.1], + rock: [0.02, 0.11], + treeline: [0.3, 0.55], + cap: [0.82, 1], + }, maps: { ...GROUND_MAPS, snow: { ...GROUND_MAPS.snow, normal: "snow_02/snow_02_nor_gl_1k.jpg" }, @@ -173,11 +212,19 @@ export const BIOMES: Record = { desert: { ...temperate, world: "desert", + propStoneTint: [1.08, 0.85, 0.64], horizon: "mesa", grassTint: [1, 1, 1], stoneTint: [1.16, 0.82, 0.55], distantStone: [0.31, 0.205, 0.12], - snow: [10000, 10001, 0.22], + snowFractions: [2, 3, 0.22], + heightFractions: { + ...temperate.heightFractions, + alpine: [0.28, 0.49], + rock: [0.08, 0.52], + treeline: [0.8, 1.8], + cap: [2, 2.8], + }, maps: { ...GROUND_MAPS, grass: { ...GROUND_MAPS.sand, normal: "sand_01/sand_01_nor_gl_1k.jpg" }, @@ -219,12 +266,14 @@ export const BIOMES: Record = { tundra: { ...temperate, world: "tundra", + toneMapping: AgXToneMapping, horizon: "plain", grassTint: [0.83, 0.88, 0.82], stoneTint: [0.7, 0.74, 0.77], snowTint: [1.45, 1.5, 1.58], distantStone: [0.18, 0.2, 0.21], - snow: [12, 36, 0.22], + snowFractions: [0.15, 0.45, 0.22], + heightFractions: { ...temperate.heightFractions, alpine: [0.6, 1.2], rock: [0.06, 1.25] }, maps: { ...GROUND_MAPS, snow: { ...GROUND_MAPS.snow, normal: "snow_02/snow_02_nor_gl_1k.jpg" }, @@ -264,9 +313,56 @@ export const BIOMES: Record = { }, }; +export interface IWorldBiome extends IBiome { + readonly elevationRange: readonly [number, number]; + readonly heightBands: HeightBands; + readonly snowBand: readonly [number, number]; +} + +/** Resolve game-owned fractions once against this world's actual samples. */ +export function measureBiome(biome: IBiome, heights: readonly number[]): IWorldBiome { + if (heights.length === 0) throw new RangeError("Terrain elevation samples are empty"); + let min = Number.POSITIVE_INFINITY; + let max = Number.NEGATIVE_INFINITY; + for (const height of heights) { + if (!Number.isFinite(height)) throw new RangeError("Terrain elevation sample is nonfinite"); + min = Math.min(min, height); + max = Math.max(max, height); + } + const span = max - min; + if (!Number.isFinite(span)) throw new RangeError("Terrain elevation range is nonfinite"); + const resolve = (fraction: number) => { + const height = min + fraction * span; + if (!Number.isFinite(height)) throw new RangeError("Terrain elevation band is nonfinite"); + return height; + }; + return { + ...biome, + elevationRange: [min, max], + heightBands: Object.fromEntries( + Object.entries(biome.heightFractions).map(([key, pair]) => [ + key, + [resolve(pair[0]), resolve(pair[1])] as const, + ]), + ) as HeightBands, + snowBand: [resolve(biome.snowFractions[0]), resolve(biome.snowFractions[1])], + }; +} + +/** A flat world has no elevation transition; never emit equal-edge smoothstep. */ +export function elevationBand( + biome: IWorldBiome, + band: readonly [number, number], + height: Node<"float"> = positionWorld.y, +): Node<"float"> { + return biome.elevationRange[0] === biome.elevationRange[1] + ? float(0) + : smoothstep(band[0], band[1], height); +} + /** Local ground rules; slope keeps steep faces bare and noise breaks the snow's contour line. */ export function biomeWeights( - biome: IBiome, + biome: IWorldBiome, steep: Node<"float">, hollow: Node<"float">, breakup: Node<"float">, @@ -280,19 +376,19 @@ export function biomeWeights( moss: float(0), sand: float(0), rock: stone - .max(smoothstep(25, 65, positionWorld.y).mul(0.48)) + .max(elevationBand(biome, biome.heightBands.desertRock).mul(0.48)) .mul(float(1).sub(smoothstep(0.12, 0.5, hollow).mul(0.35))), snow: float(0), }; // Wind-scoured convex/exposed faces shed snow; sheltered shelves keep it. const exposure = normalWorldGeometry.x.mul(0.65).add(normalWorldGeometry.z.mul(0.4)).max(0); - const retention = smoothstep( - biome.snow[0], - biome.snow[1], + const retention = elevationBand( + biome, + biome.snowBand, positionWorld.y.add(hollow.max(0).mul(biome.world === "alpine" ? 14 : 5)), ).mul( float(1) - .sub(smoothstep(biome.world === "alpine" ? 0.16 : 0.04, biome.snow[2], steep)) + .sub(smoothstep(biome.world === "alpine" ? 0.16 : 0.04, biome.snowFractions[2], steep)) .mul(float(1).sub(exposure.mul(0.28))), ); // A narrow retention threshold leaves wind-shaped shelves and sharp scour edges. @@ -328,15 +424,15 @@ export function biomeWeights( biome.world === "alpine" ? stone .max(0.8) - .max(smoothstep(40, 90, positionWorld.y).mul(0.58)) + .max(elevationBand(biome, biome.heightBands.alpineRock).mul(0.58)) .max( smoothstep(0.12, 0.5, hollow) - .mul(smoothstep(35, 80, positionWorld.y)) + .mul(elevationBand(biome, biome.heightBands.alpineHollow)) .mul(0.45), ) .mul(float(1).sub(smoothstep(0.08, 0.4, hollow).mul(0.4))) : stone, - snow: biome.world === "tundra" ? snow : alpineSnowCover(hollow), + snow: biome.world === "tundra" ? snow : alpineSnowCover(biome, hollow), }; } @@ -367,9 +463,16 @@ export function alpineRockColor(sample: Node<"vec3">): Node<"vec3"> { } /** Snow respects world elevation and upward faces, including tilted instanced scans. */ -export function alpineSnowCover(hollow: Node<"float"> = float(0)): Node<"float"> { +export function alpineSnowCover( + biome: IWorldBiome, + hollow: Node<"float"> = float(0), +): Node<"float"> { const exposure = normalWorldGeometry.x.mul(0.65).add(normalWorldGeometry.z.mul(0.4)).max(0); - const retention = smoothstep(50, 180, positionWorld.y.add(hollow.max(0).mul(110))) + const retention = elevationBand( + biome, + biome.snowBand, + positionWorld.y.add(hollow.max(0).mul(110)), + ) .mul(smoothstep(0.35, 0.72, normalWorldGeometry.y)) .mul(float(1).sub(exposure.mul(0.28))) .mul(float(1).sub(hollow.min(0).negate().mul(0.65))); diff --git a/examples/strata-terrain-preview/src/render/cover.ts b/examples/strata-terrain-preview/src/render/cover.ts index 07e5300d06..3e411945df 100644 --- a/examples/strata-terrain-preview/src/render/cover.ts +++ b/examples/strata-terrain-preview/src/render/cover.ts @@ -1,16 +1,14 @@ -// Mossy boulders and the meadow's ground cover: grass clumps and poppy clusters. +// Weathered boulders and the meadow's ground cover: grass clumps and poppy clusters. // -// All three are appearance, all three are built here, and none of them is a primitive. A -// `SphereGeometry` with vertex noise on it is a ball with lumps; a boulder is a rock with facets -// that catch the sun along their edges, a flattened top where something rolled off it, a base -// buried in the ground so it grows out of the hillside instead of resting on it, and moss on the -// upward faces only, because moss does not grow on a vertical face. +// Rocks use smooth weathering, fracture planes and a flattened contact base. // // Grass is the other thing a cone cannot be. A blade is a tapered curved strip, dark at the root // and light at the tip, and a clump is a handful of them at different angles — which is what makes // a lawn read as depth rather than as a green plane with a texture on it. import { createRandom } from "@threenative/core"; -import { BufferAttribute, BufferGeometry, IcosahedronGeometry, Vector3 } from "three"; +import { Box3, BufferAttribute, BufferGeometry, IcosahedronGeometry, Vector3 } from "three"; +import { ImprovedNoise } from "three/addons/math/ImprovedNoise.js"; +import { mergeVertices } from "three/addons/utils/BufferGeometryUtils.js"; /** How the boulders read, in metres. Sized against a spruce's 12-17 m, not in the abstract. */ export const ROCK = { @@ -20,73 +18,98 @@ export const ROCK = { flatten: 0.62, /** How much of the base sits below the placement point, as a share of its height. */ buried: 0.34, - /** Facet noise, as a share of the radius. Past a third it stops being a rock and becomes a cloud. */ + /** Weathering amplitude as a share of the radius. */ rough: 0.26, - /** How flat the noise is: 1 is a smooth blob, high values are shattered facets. */ - facets: 3, + /** Three.js subdivision detail: 20 * (detail + 1)^2 = 500 triangles. */ + facets: 4, } as const; -/** - * One seeded boulder: a displaced icosphere, flat-shaded, buried at the base. - * - * `IcosahedronGeometry` at detail 2 already has 320 faces, which is the right budget for a rock you - * see from four metres away: enough that the facets read as fracture, few enough that four hundred - * of them are affordable. The displacement is a sum of sines at incommensurate frequencies, so no - * two boulders share a lump pattern, and the whole surface is then `toNonIndexed()` so every facet - * gets its own normal — smooth-shaded noise reads as a melted blob, not as stone. - */ +/** Smooth weathered stone with two softened fracture planes and a broad, flat contact base. */ export function boulder(seed: number): BufferGeometry { const random = createRandom(seed); - const geometry = new IcosahedronGeometry(ROCK.radius, ROCK.facets); + const source = new IcosahedronGeometry(ROCK.radius, ROCK.facets); + // UV seams and the source's per-face normals must not keep identical positions apart. + source.deleteAttribute("uv"); + source.deleteAttribute("normal"); + const geometry = mergeVertices(source); + source.dispose(); const position = geometry.getAttribute("position") as BufferAttribute; - const phase = random() * Math.PI * 2; - const phase2 = random() * Math.PI * 2; - const phase3 = random() * Math.PI * 2; + const noise = new ImprovedNoise(); + const offset = new Vector3(random() * 100, random() * 100, random() * 100); + const stretch = new Vector3( + 0.85 + random() * 0.3, + ROCK.flatten * (0.92 + random() * 0.16), + 0.85 + random() * 0.3, + ); + const cuts = Array.from({ length: 2 }, () => ({ + normal: new Vector3(random() * 2 - 1, 0.25 + random() * 0.65, random() * 2 - 1).normalize(), + distance: ROCK.radius * (0.85 + random() * 0.12), + })); const point = new Vector3(); + const direction = new Vector3(); + const base = -ROCK.radius * stretch.y * 0.88; for (let i = 0; i < position.count; i += 1) { - point.fromBufferAttribute(position, i); - const direction = point.clone().normalize(); - // Three sine lobes at different frequencies: broad swells, a mid facet and a fine chip. - const swell = Math.sin(direction.x * ROCK.facets + phase) * 0.55; - const facet = Math.sin(direction.y * ROCK.facets * 2.3 + phase2) * 0.3; - const chip = Math.sin(direction.z * ROCK.facets * 5.1 + phase3) * 0.15; - const displaced = ROCK.radius * (1 + (swell + facet + chip) * ROCK.rough); - point.copy(direction).multiplyScalar(displaced); - point.y *= ROCK.flatten; - // Flatten the top and round the bottom: a boulder that has sat in a hillside for a century has - // a plane where the ground pushes on it, and a rounded underside where the soil closes under. - if (point.y > 0) point.y = point.y * 0.86; + direction.fromBufferAttribute(position, i).normalize(); + let weathering = 0; + for (let octave = 0; octave < 3; octave += 1) { + const frequency = 1.8 * 2 ** octave; + weathering += + noise.noise( + direction.x * frequency + offset.x, + direction.y * frequency + offset.y, + direction.z * frequency + offset.z, + ) * + 0.62 ** octave; + } + point.copy(direction).multiplyScalar(ROCK.radius * (1 + weathering * ROCK.rough)); + for (const cut of cuts) { + const excess = point.dot(cut.normal) - cut.distance; + // A rounded clamp keeps a fracture from ending in a knife edge. + const bevel = ROCK.radius * 0.12; + const rounded = Math.max(0, 1 - Math.abs(excess) / bevel); + point.addScaledVector( + cut.normal, + -(Math.max(0, excess) + (bevel * rounded * rounded) / 4) * 0.4, + ); + } + point.multiply(stretch); + point.y = Math.max(base, point.y); position.setXYZ(i, point.x, point.y, point.z); } - // `IcosahedronGeometry` is already non-indexed — every facet owns its vertices — so this is a - // copy, and disposing the source here used to dispose the buffer that was about to be drawn. - const flat = geometry.index === null ? geometry : geometry.toNonIndexed(); - if (flat !== geometry) geometry.dispose(); - flat.computeVertexNormals(); - // Bury the base: the placement point is the ground, and the rock has to come out of it. - flat.translate(0, -ROCK.radius * ROCK.flatten * ROCK.buried, 0); - flat.computeBoundingSphere(); - return flat; + // Keep the committed seed's envelope: weathering must not thin the river cluster. + const reference = new IcosahedronGeometry(ROCK.radius, 3); + const vertices = reference.getAttribute("position"); + const bounds = new Box3(); + const phase = offset.clone().multiplyScalar((Math.PI * 2) / 100); + for (let i = 0; i < vertices.count; i++) { + direction.fromBufferAttribute(vertices, i).normalize(); + const swell = Math.sin(direction.x * 3 + phase.x) * 0.55; + const facet = Math.sin(direction.y * 3 * 2.3 + phase.y) * 0.3; + const chip = Math.sin(direction.z * 3 * 5.1 + phase.z) * 0.15; + point.copy(direction).multiplyScalar(ROCK.radius * (1 + (swell + facet + chip) * ROCK.rough)); + point.y *= ROCK.flatten * (point.y > 0 ? 0.86 : 1); + bounds.expandByPoint(point); + } + reference.dispose(); + geometry.computeBoundingBox(); + const size = geometry.boundingBox?.getSize(new Vector3()); + if (!size) throw new Error("Rock has no measured bounds"); + const scale = bounds.getSize(point).divide(size); + geometry.scale(scale.x, scale.y, scale.z); + geometry.computeVertexNormals(); + geometry.translate(0, -ROCK.radius * ROCK.flatten * ROCK.buried, 0); + geometry.computeBoundingBox(); + geometry.computeBoundingSphere(); + return geometry; } -/** - * One grass clump's blade count and height, in metres. Knee-high meadow grass, not a lawn. - * - * Twelve blades, not seven, and wider and taller than the first pass: at eye height a seven-blade - * clump one clump every half metre reads as scattered wires on a green plane, and the count that - * fixes it is the count of blades, not the number of clumps. The curve is deeper too, because a - * straight blade is a line and a curved one catches the sun along its length. - */ +/** Knee-high meadow grass: fourteen slender curved blades per clump. */ export const GRASS = { blades: 14, /** Height range of a clump, in metres. */ height: [0.5, 0.95], - /** - * Blade width range, in metres. A grass blade is two or three millimetres of edge-on leaf and a - * sedge's is nearer fifteen; four centimetres of it, seen from a metre and a half, is a strap of - * agave, which is what the first pass of this lane grew. - */ - width: [0.017, 0.031], + /** Blade width range, in metres, capped against each blade's own height below. */ + width: [0.006, 0.012], /** How far a blade leans from vertical at its tip, as a share of its length. */ bend: 0.72, /** Wind sway at the tip, in metres. */ @@ -133,12 +156,18 @@ class CoverBuffer { corners: readonly Vector3[], normal: Vector3, colors: readonly Vector3[], - weight: number, + weight: number | readonly [number, number, number, number], uvs?: readonly (readonly [number, number])[], ): void { const base = this.sway.length; corners.forEach((point, i) => - this.vertex(point, normal, colors[i] as Vector3, weight, uvs?.[i]), + this.vertex( + point, + normal, + colors[i] as Vector3, + typeof weight === "number" ? weight : (weight[i] as number), + uvs?.[i], + ), ); this.index.push(base, base + 1, base + 2, base, base + 2, base + 3); } @@ -194,10 +223,17 @@ const MEADOW_GREENS = [ * tip. The bend is per blade and the direction is per blade, which is the entire difference between * "grass" and "several identical green triangles". */ -export function grassClump(seed: number): BufferGeometry { +export function grassClump(seed: number, species = 0): BufferGeometry { const random = createRandom(seed); const buffer = new CoverBuffer(); const segments = 3; + // Meadow grass, fine arching fescue and upright sedge share one opaque material. + const habits = [ + { height: 1, width: 1, bend: 1, spread: 0.075 }, + { height: 0.85, width: 0.7, bend: 1.35, spread: 0.11 }, + { height: 0.95, width: 1.1, bend: 0.8, spread: 0.055 }, + ] as const; + const habit = habits[species % habits.length] ?? habits[0]; // Root occlusion: the bottom of a clump sits in its own shade, and a blade that starts at the same // brightness as its tip looks pasted onto the ground. The fade runs over the first third of the // blade, which is about as far down as the neighbours of a clump reach. @@ -215,28 +251,36 @@ export function grassClump(seed: number): BufferGeometry { // grass in the same metre, and a clump that is always `GRASS.height`'s midpoint is the other half // of the lattice. const clumpHeight = 0.62 + random() * 0.62; + const phase = random() * Math.PI * 2; for (let blade = 0; blade < GRASS.blades; blade += 1) { - const azimuth = random() * Math.PI * 2; + const azimuth = phase + blade * 2.399963229728653 + (random() - 0.5) * 0.6; // Per blade, not per clump: a clump whose blades are all one height is a fan, and the outer // blades of a real clump are the ones that have fallen over. - const height = (GRASS.height[0] + random() * (GRASS.height[1] - GRASS.height[0])) * clumpHeight; - const bend = GRASS.bend * (0.35 + random() * 1.1); + const height = + (GRASS.height[0] + random() * (GRASS.height[1] - GRASS.height[0])) * + clumpHeight * + habit.height; + const bend = GRASS.bend * (0.35 + random() * 1.1) * habit.bend; // Blades lean outward from the clump's own centre, so the clump is round, not a flat fan. const outward = new Vector3(Math.cos(azimuth), 0, Math.sin(azimuth)); - const width = GRASS.width[0] + random() * (GRASS.width[1] - GRASS.width[0]); + const width = Math.min( + (GRASS.width[0] + random() * (GRASS.width[1] - GRASS.width[0])) * habit.width, + height * 0.022, + ); const side = new Vector3(-Math.sin(azimuth), 0, Math.cos(azimuth)); + const basal = habit.spread * Math.sqrt(random()); // The blade's spine at `at`: rising, and leaning over further the higher it goes. const spine = (at: number) => new Vector3( - outward.x * height * bend * at * at, - height * at, - outward.z * height * bend * at * at, + outward.x * (basal + height * bend * (0.2 * at + 0.8 * at * at)), + height * (at - 0.3 * at ** 3), + outward.z * (basal + height * bend * (0.2 * at + 0.8 * at * at)), ); for (let i = 0; i < segments; i += 1) { const at0 = i / segments; const at1 = (i + 1) / segments; - const half0 = (width * (1 - at0 * 0.92)) / 2; - const half1 = (width * (1 - at1 * 0.92)) / 2; + const half0 = (width * (0.02 + 0.98 * (1 - at0) ** 1.3)) / 2; + const half1 = (width * (0.02 + 0.98 * (1 - at1) ** 1.3)) / 2; const a = spine(at0).addScaledVector(side, -half0); const b = spine(at0).addScaledVector(side, half0); const c = spine(at1).addScaledVector(side, half1); @@ -257,7 +301,7 @@ export function grassClump(seed: number): BufferGeometry { root.clone().lerp(tip, at1).multiplyScalar(rootAo(at1)), root.clone().lerp(tip, at1).multiplyScalar(rootAo(at1)), ], - at0 * at0, + [at0 * at0, at0 * at0, at1 * at1, at1 * at1], ); } } @@ -302,10 +346,11 @@ export function fernClump(seed: number): BufferGeometry { const phase = random() * Math.PI * 2; const white = new Vector3(1, 1, 1); for (let f = 0; f < fronds; f += 1) { - const azimuth = phase + (f / fronds) * Math.PI * 2 + (random() - 0.5) * 0.5; + const azimuth = phase + f * 2.399963229728653 + (random() - 0.5) * 0.35; const length = FERN.length[0] + random() * (FERN.length[1] - FERN.length[0]); const width = length * FERN.width * (0.8 + random() * 0.4); - const rise = FERN.rise * (0.7 + random() * 0.6); + const young = f / (fronds - 1); + const rise = FERN.rise * (0.7 + random() * 0.4 + young * 0.65); const outward = new Vector3(Math.cos(azimuth), 0, Math.sin(azimuth)); const side = new Vector3(-Math.sin(azimuth), 0, Math.cos(azimuth)); const rect = FROND_RECTS[Math.floor(random() * FROND_RECTS.length)] as readonly number[]; @@ -314,7 +359,7 @@ export function fernClump(seed: number): BufferGeometry { const spine = (at: number) => outward .clone() - .multiplyScalar(length * (0.25 + 0.75 * at) * at) + .multiplyScalar(length * (1 - young * 0.35) * (0.25 + 0.75 * at) * at) .setY(length * rise * (2.2 * at - 1.6 * at * at)); // A frond's own twist: the outer half rolls a little, so the clump is not a set of flat paddles. const roll = (random() - 0.5) * 0.6; @@ -343,7 +388,7 @@ export function fernClump(seed: number): BufferGeometry { [a, b, c, d], normal, [shade(at0), shade(at0), shade(at1), shade(at1)], - at0 * at0, + [at0 * at0, at0 * at0, at1 * at1, at1 * at1], [ [u0, va], [u1, va], @@ -440,7 +485,7 @@ export function poppyCluster( stemGreen.clone().lerp(stemLight, at1), stemGreen.clone().lerp(stemLight, at1), ], - at0 * at0 * 0.8, + [at0 * at0 * 0.8, at0 * at0 * 0.8, at1 * at1 * 0.8, at1 * at1 * 0.8], ); } // Five cupped petals, each a small strip of quads rather than one flat quad. A poppy is a bowl: @@ -449,7 +494,7 @@ export function poppyCluster( // and as a line from anywhere else, which is what the last captures showed. const head = spine(1); const twist = random() * Math.PI * 2; - const radius = POPPY.disc; + const radius = POPPY.disc * (0.8 + random() * 0.4); // The dark centre every poppy has: the ring of stamens around the ovary, which is nearly black // and which is the single detail that makes a red disc read as a flower. const centreR = radius * POPPY.centre; diff --git a/examples/strata-terrain-preview/src/render/editorView.ts b/examples/strata-terrain-preview/src/render/editorView.ts index fb5a475e04..516711c709 100644 --- a/examples/strata-terrain-preview/src/render/editorView.ts +++ b/examples/strata-terrain-preview/src/render/editorView.ts @@ -1,6 +1,7 @@ import { type ICtx, Scene, defineGame } from "@threenative/core"; import { playtest } from "@threenative/core/playtest"; import { + type IPlacement, type ITerrainState, applyPlacementOverrides, bakeMesh, @@ -18,10 +19,14 @@ import { type IViewEnvironment, type IViewerPose, type TerrainEditorController, + editorSurfaceHeight, focusCamera, + landformPose, + pickEditorPlacement, + pickEditorSurface, } from "@threenative/terrain/editor"; import type { IAuthoringDocument } from "@threenative/terrain/editor/server"; -import { type IWorldGLBExport, exportWorldGLB } from "@threenative/terrain/export"; +import { type IWorldGLBExport, prepareWorldGLTF } from "@threenative/terrain/export"; import { Box3, BufferAttribute, @@ -34,7 +39,7 @@ import { Line, LineBasicMaterial, LinearToneMapping, - Matrix4, + type Matrix4, Mesh, type MeshStandardMaterial, NoToneMapping, @@ -46,18 +51,19 @@ import { import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { createEnvironmentImages } from "./environmentImages.js"; import { createImportedModels } from "./imports.js"; -import { landformPose } from "./landforms.js"; import { OCEAN_LOOK, createOcean, createWaterMesh } from "./ocean.js"; import { terrainPalette } from "./palette.js"; import { createPortableGround, createPortableProps } from "./portable.js"; import { createPropSurfaces } from "./propMaterials.js"; +import { createStreamedProps } from "./propStreaming.js"; import { PROP_ASSETS, type PropGroundQuery, buildPropVariants, - createProps, + preparePose, preparePropTransform, readPropTransform, + variantFor, writePropTransform, } from "./props.js"; import { createPropSelection } from "./selection.js"; @@ -95,6 +101,29 @@ const initialState = { renderedRevision: "", renderedCount: 0, renderedFrames: 0, + editorTool: "", + editorFocus: "", + editorRadius: 0, + editorLayerCount: 0, + editorPointCount: 0, + editorLiveRegionCount: 0, + editorLiveMode: "", + editorAnnouncement: "", + editorFocusVisible: false, + editorBrushCaptured: false, + editorPersistedStrokes: -1, + editorPersistenceError: "", + editorUnsentVisible: false, + editorUnsentCount: 0, + editorUnsentText: "", + editorUnsentRetry: false, + editorUnsentDiscard: false, + editorStaleWriteConflicts: 0, + editorCommitWrites: "", + editorWorkerBusy: false, + editorBuildCancelled: false, + editorRebuiltAfterCancel: false, + vertexCount: 0, heightSum: 0, evaluationMs: 0, @@ -147,13 +176,25 @@ export async function createEditorView( // The exported world's water, and the meshes drawn live: one bake, used by both, so what the // editor shows is what the GLB carries rather than two shapes that only agree sometimes. let waterSurfaces: IWaterSurface[] = []; - let props: ReturnType | undefined; + let props: Awaited> | undefined; + let prewarmedProps: typeof props; // The loop's settling frame can tick before the surfaces exist; the view answers when it can. // biome-ignore lint/style/useConst: the settling frame reads this before the line that assigns it. let propSurfaces: Awaited> | undefined; + const initialDisk = await controller.snapshot(); + const initialStrokes = initialDisk.document.recipe.layers.filter( + (layer) => layer.name === "Sculpt stroke", + ).length; + let persistedStrokes = 0; + let persistenceError = ""; + let stateAtCancellation: ITerrainState | null = null; let authoring: IAuthoringDocument | undefined; let renderedRecipe: string | undefined; let groundAt: PropGroundQuery | undefined; + let streamGeneration = 0; + let streamPending = false; + let propsDirty = false; + let disposed = false; let elapsed = 0; let first = true; let hemisphere!: HemisphereLight; @@ -201,6 +242,75 @@ export async function createEditorView( for (const material of materials) material.dispose(); water = undefined; } + async function streamProps( + placements: readonly IPlacement[], + query: PropGroundQuery, + size: number, + ) { + if (!propSurfaces) throw new Error("Prop surfaces are not loaded yet"); + const generation = ++streamGeneration; + streamPending = true; + try { + const next = await createStreamedProps({ + placements, + groundAt: query, + parts: propParts, + materials: propSurfaces.materials, + follow: ctx.camera, + size, + assets: ctx.assets, + // The editor has no virtual shadow maps to draw streamed caster layers. + castShadows: false, + // Live model and texture edits must rebind resources on the next draw. + bundles: false, + whileCurrent: () => !disposed && generation === streamGeneration, + }); + if (!next) return undefined; + if (disposed || generation !== streamGeneration) { + next.dispose(); + return undefined; + } + for (const instance of next.byId.values()) + instance.onTransform = (transform) => { + instance.placement = { ...instance.placement, transform }; + if (propsDirty) return; + propsDirty = true; + queueMicrotask(() => { + propsDirty = false; + if (disposed || !props || !groundAt || !terrain) return; + const placements = [...props.byId.values()].map((instance) => instance.placement); + void streamProps(placements, groundAt, terrain.size) + .then((next) => { + if (next) replaceProps(next); + }) + .catch(streamFailure); + }); + }; + return next; + } finally { + if (generation === streamGeneration) streamPending = false; + } + } + function replaceProps(next: NonNullable): void { + if (props) { + for (const world of props.worlds) ctx.scene.remove(world); + props.dispose(); + } + props = next; + for (const world of props.worlds) ctx.add(world); + void Promise.all(next.worlds.map((world) => world.prewarmed)).then(() => { + if (!disposed && props === next) prewarmedProps = next; + }); + } + function streamFailure(error: unknown): void { + const status = document.getElementById("save-status"); + if (status) status.textContent = "Preview failed · last preview retained"; + const toast = document.getElementById("toast"); + if (toast) { + toast.textContent = String(error); + toast.classList.remove("hidden"); + } + } function readPose(): IViewerPose { // The engine camera is configured perspective at start; its planes are the live ones. const live = ctx.camera as PerspectiveCamera; @@ -247,18 +357,12 @@ export async function createEditorView( const instance = props?.byId.get(id); if (!instance) return undefined; const box = new Box3(); - const world = new Matrix4(); - for (const part of instance.parts) { - part.mesh.updateWorldMatrix(true, false); - part.mesh.geometry.computeBoundingBox(); - const local = part.mesh.geometry.boundingBox; - if (!local) continue; - // One instance matrix, so a nonuniformly scaled prop frames its own geometry. - world.multiplyMatrices( - part.mesh.matrixWorld, - new Matrix4().fromArray(part.mesh.instanceMatrix.array, part.index * 16), - ); - box.union(local.clone().applyMatrix4(world)); + const key = `${instance.placement.asset}:${variantFor(instance.placement, instance.placement.asset)}`; + for (const part of propParts.get(key) ?? []) { + if ((part.level ?? 0) !== 0) continue; + part.geometry.computeBoundingBox(); + const local = part.geometry.boundingBox; + if (local) box.union(local.clone().applyMatrix4(instance.pose)); } return box.isEmpty() ? undefined @@ -290,7 +394,7 @@ export async function createEditorView( ); if (!placed.length) { const layer = authoring?.recipe.layers.find((entry) => entry.id === id); - const pose = layer ? landformPose(layer, terrain.size) : undefined; + const pose = layer ? landformPose(layer, terrain.size, 120) : undefined; if (!pose) return undefined; const size = new Vector3(...pose.scale); const centre = new Vector3(...pose.position); @@ -587,8 +691,71 @@ export async function createEditorView( 0, ) ?? 0, }); - ctx.state.set({ renderedFrames: ctx.state.getState().renderedFrames + 1 }); - if (!requestedRevision || seen === requestedRevision) return; + const focused = document.activeElement as HTMLElement | null; + const cancelled = + document.getElementById("save-status")?.textContent?.startsWith("Build cancelled") ?? + false; + // The editor page publishes its API on `window.strata`; this view reads two fields of it. + const strata = ( + window as { + strata?: { state?: NonNullable; workerBusy?: boolean }; + } + ).strata; + if (cancelled && strata?.state) stateAtCancellation ??= strata.state; + ctx.state.set({ + renderedFrames: ctx.state.getState().renderedFrames + 1, + editorWorkerBusy: strata?.workerBusy ?? false, + editorBuildCancelled: cancelled, + editorRebuiltAfterCancel: !!stateAtCancellation && strata?.state !== stateAtCancellation, + editorTool: document.body.dataset.terrainTool ?? "", + editorUnsentVisible: + !!document.getElementById("not-saved") && !document.getElementById("not-saved")?.hidden, + editorUnsentCount: document.querySelectorAll("#not-saved-list > li").length, + editorUnsentText: document.getElementById("not-saved")?.textContent ?? "", + editorUnsentRetry: !!document.querySelector( + '#not-saved-list button[data-save-action="retry"]', + ), + editorUnsentDiscard: !!document.querySelector( + '#not-saved-list button[data-save-action="discard"]', + ), + editorStaleWriteConflicts: Number(document.body.dataset.staleWriteConflicts ?? 0), + editorCommitWrites: Object.values( + JSON.parse(document.body.dataset.commitWrites ?? "{}"), + ).join(","), + editorPersistedStrokes: persistedStrokes, + editorPersistenceError: persistenceError, + editorBrushCaptured: document.getElementById("viewport")?.hasPointerCapture(1) ?? false, + editorFocus: focused?.dataset.tool ?? focused?.id ?? "", + editorFocusVisible: + !!focused?.matches(":focus-visible") && + Number.parseFloat(getComputedStyle(focused).outlineWidth) > 0, + editorAnnouncement: document.getElementById("editor-announcements")?.textContent ?? "", + editorRadius: Number( + (document.getElementById("brush-radius") as HTMLInputElement | null)?.value ?? 0, + ), + editorLayerCount: authoring?.recipe.layers.length ?? 0, + editorPointCount: Number( + document.getElementById("spline-count")?.textContent?.split(" ")[0] ?? 0, + ), + editorLiveRegionCount: document.querySelectorAll("[aria-live]").length, + editorLiveMode: + document.getElementById("editor-announcements")?.getAttribute("aria-live") ?? "", + }); + const streaming = props?.stats(); + if ( + !requestedRevision || + seen === requestedRevision || + streamPending || + propsDirty || + !streaming || + prewarmedProps !== props || + streaming.loadsInFlight !== 0 || + streaming.loadsQueued !== 0 || + streaming.admissionBacklog !== 0 || + streaming.pendingPrewarm !== 0 || + streaming.failures !== 0 + ) + return; seen = requestedRevision; mesh.userData.revision = seen; ctx.state.set({ renderedRevision: seen }); @@ -631,14 +798,23 @@ export async function createEditorView( // hash-named URLs so a replaced file can never be served from a stale cache. const assetUrl = (asset: { path: string }): string => new URL(`api/assets/${asset.path}`, document.baseURI).href; - const surfaces = createSurfaceBindings(ctx.assets, propSurfaces.inputs, assetUrl, (texture) => { - const backend = ( - ctx.renderer.raw as { - backend?: { get(object: unknown): { texture?: { format?: string } } | undefined }; - } - ).backend; - return backend?.get(texture)?.texture?.format; - }); + const surfaces = createSurfaceBindings( + ctx.assets, + propSurfaces.inputs, + assetUrl, + (texture) => { + const backend = ( + ctx.renderer.raw as { + backend?: { get(object: unknown): { texture?: { format?: string } } | undefined }; + } + ).backend; + return backend?.get(texture)?.texture?.format; + }, + () => { + for (const material of Object.values(propSurfaces?.materials ?? {})) + material.needsUpdate = true; + }, + ); const imported = createImportedModels(ctx.assets, propParts, assetUrl); envImages = createEnvironmentImages(ctx.assets, assetUrl, () => syncImages(environmentOverrides)); const selection = createPropSelection( @@ -649,25 +825,11 @@ export async function createEditorView( () => groundAt, (x, y) => { if (!props || !mesh) return undefined; - const box = ctx.renderer.domElement.getBoundingClientRect(); - const hit = ctx.raycast({ - screen: new Vector2(x - box.left, y - box.top), - targets: [mesh, ...props.meshes], - }); - if (hit?.instanceId === undefined) return undefined; - const id = hit.object.userData.placementIds?.[hit.instanceId]; - return typeof id === "string" ? id : undefined; + return pickEditorPlacement(ctx, mesh, props.meshes, props.byId.values(), x, y); }, (x, z) => { if (!mesh) return undefined; - mesh.geometry.computeBoundingBox(); - const top = mesh.geometry.boundingBox?.max.y; - if (top === undefined) return undefined; - return ctx.raycast({ - origin: new Vector3(x, top + 1, z), - direction: new Vector3(0, -1, 0), - targets: [mesh], - })?.point.y; + return editorSurfaceHeight(ctx, mesh, x, z); }, (target) => cameras.focus({ target }), ); @@ -727,10 +889,21 @@ export async function createEditorView( requestedRevision = revision; } authoring = next; + void controller + .snapshot() + .then((snapshot) => { + persistedStrokes = + snapshot.document.recipe.layers.filter((layer) => layer.name === "Sculpt stroke") + .length - initialStrokes; + }) + .catch((error: unknown) => { + persistenceError = String(error); + }); selection.sync({ document: next, revision, diagnostic: null }, sameRecipe); selection.refresh(); }, update(state): ITerrainState { + const recipe = authoring ? JSON.stringify(authoring.recipe) : undefined; const resolved = applyPlacementOverrides(state, authoring?.placementOverrides ?? {}); const baked = bakeMesh(state, { palette: terrainPalette }); if (!baked.colors) throw new Error("Editor surface colours missing"); @@ -747,78 +920,98 @@ export async function createEditorView( next.geometry.computeBoundingBox(); const top = next.geometry.boundingBox?.max.y; if (top === undefined) throw new Error("Terrain bounds unavailable for prop grounding"); - let nextProps: ReturnType; + let nextGround: PropGroundQuery; try { - const nextGround: PropGroundQuery = (placement, at) => { + nextGround = (placement, at) => { const [x, , z] = at; - const hit = ctx.raycast({ - origin: new Vector3(x, top + 1, z), - direction: new Vector3(0, -1, 0), - targets: [next], - }); + const height = editorSurfaceHeight(ctx, next, x, z, top + 1); const [originalX, originalY, originalZ] = placement.position; return { - height: hit?.point.y ?? null, + height: height ?? null, offset: originalY - sampleHeight(state, originalX, originalZ), }; }; if (!propSurfaces) throw new Error("Prop surfaces are not loaded yet"); - nextProps = createProps(resolved.instances, nextGround, propParts, propSurfaces.materials); - groundAt = nextGround; + for (const placement of resolved.instances) { + const key = `${placement.asset}:${variantFor(placement, placement.asset)}`; + const parts = propParts.get(key); + const foot = + parts?.find((part) => part.role === "bark" || part.role === "stone") ?? parts?.[0]; + if (!foot) throw new Error(`Prop asset '${key}' has no parts`); + preparePose( + foot.geometry, + foot.material ?? propSurfaces.materials[foot.role], + placement, + placement.transform, + nextGround, + ); + } } catch (error) { next.geometry.dispose(); next.material.dispose(); throw error; } - if (data.waterLevel !== null && !sea) sea = ctx.add(createOcean()); - const nextWater = data.waterLevel === null || !sea ? undefined : createWaterMesh(sea, data); - // Water comes out of the evaluated state, never out of the recipe's intent: the ribbon is the - // carved bed and the flooded extent is the mask the evaluator resolved, so a baked surface and - // the live scene are the same body rather than two shapes that agree by accident. - const nextWaterSurfaces = bakeWater(state, (x, z) => sampleHeight(state, x, z)); - for (const previous of waterSurfaces) { - ctx.scene.remove(previous.mesh); - previous.mesh.geometry.dispose(); - (previous.mesh.material as MeshStandardMaterial).dispose(); - } - waterSurfaces = nextWaterSurfaces; - // An ocean body is drawn by the spectral sea above; its baked twin is for the export, because - // a GLB cannot carry a per-frame simulation. Everything else is drawn from the bake, which is - // what puts a river in an editor preview that had none. - for (const entry of waterSurfaces) { - if (state.waters.find((candidate) => candidate.id === entry.id)?.kind === "ocean") continue; - ctx.add(entry.mesh); - } - if (mesh) { - ctx.scene.remove(mesh); - mesh.geometry.dispose(); - mesh.material.dispose(); - } - clearWater(); - if (props) { - ctx.scene.remove(props.object); - props.dispose(); - } - props = nextProps; - ctx.add(props.object); - mesh = next; - ctx.add(mesh); - terrain = state; - if (nextWater) { - water = nextWater; - ctx.add(water); - } - ctx.state.set({ vertexCount: mesh.geometry.getAttribute("position").count }); - const positions = mesh.geometry.getAttribute("position"); - let heightSum = 0; - for (let i = 0; i < positions.count; i++) heightSum += positions.getY(i); - ctx.state.set({ heightSum }); - renderedRecipe = authoring ? JSON.stringify(authoring.recipe) : undefined; - if (first) { - first = false; - frame(); - } - selection.refresh(); + propsDirty = false; + void streamProps(resolved.instances, nextGround, state.size) + .then((nextProps) => { + if (!nextProps) { + next.geometry.dispose(); + next.material.dispose(); + return; + } + if (data.waterLevel !== null && !sea) sea = ctx.add(createOcean()); + const nextWater = + data.waterLevel === null || !sea ? undefined : createWaterMesh(sea, data); + // Water comes out of the evaluated state, never out of the recipe's intent: the ribbon is the + // carved bed and the flooded extent is the mask the evaluator resolved, so a baked surface and + // the live scene are the same body rather than two shapes that agree by accident. + const nextWaterSurfaces = bakeWater(state, (x, z) => sampleHeight(state, x, z)); + for (const previous of waterSurfaces) { + ctx.scene.remove(previous.mesh); + previous.mesh.geometry.dispose(); + (previous.mesh.material as MeshStandardMaterial).dispose(); + } + waterSurfaces = nextWaterSurfaces; + // An ocean body is drawn by the spectral sea above; its baked twin is for the export, because + // a GLB cannot carry a per-frame simulation. Everything else is drawn from the bake, which is + // what puts a river in an editor preview that had none. + for (const entry of waterSurfaces) { + if (state.waters.find((candidate) => candidate.id === entry.id)?.kind === "ocean") + continue; + ctx.add(entry.mesh); + } + if (mesh) { + ctx.scene.remove(mesh); + mesh.geometry.dispose(); + mesh.material.dispose(); + } + clearWater(); + replaceProps(nextProps); + groundAt = nextGround; + mesh = next; + ctx.add(mesh); + terrain = state; + if (nextWater) { + water = nextWater; + ctx.add(water); + } + ctx.state.set({ vertexCount: mesh.geometry.getAttribute("position").count }); + const positions = mesh.geometry.getAttribute("position"); + let heightSum = 0; + for (let i = 0; i < positions.count; i++) heightSum += positions.getY(i); + ctx.state.set({ heightSum }); + renderedRecipe = recipe; + if (first) { + first = false; + frame(); + } + selection.refresh(); + }) + .catch((error: unknown) => { + next.geometry.dispose(); + next.material.dispose(); + streamFailure(error); + }); return resolved; }, // This project's own placeable assets, so the recovered scatter palette is the project's art and @@ -828,11 +1021,7 @@ export async function createEditorView( }, pick(clientX, clientY) { if (!mesh) return null; - const box = ctx.renderer.domElement.getBoundingClientRect(); - const hit = ctx.raycast({ - screen: new Vector2(clientX - box.left, clientY - box.top), - targets: [mesh], - }); + const hit = pickEditorSurface(ctx, [mesh], clientX, clientY); return hit ? [hit.point.x, hit.point.y, hit.point.z] : null; }, setMode(value): void { @@ -842,16 +1031,14 @@ export async function createEditorView( setBrush(value): void { brush.visible = !!value && !!terrain; if (!value || !terrain) return; - const hit = ctx.raycast({ - origin: new Vector3(value.at[0], 10000, value.at[1]), - direction: new Vector3(0, -1, 0), - targets: mesh ? [mesh] : [], - }); - if (!hit) { + const height = mesh + ? editorSurfaceHeight(ctx, mesh, value.at[0], value.at[1], 10000) + : undefined; + if (height === undefined) { brush.visible = false; // engine-override: no terrain target exists; prewarm handles meshes, not this Line. // engine-override: this target-dependent Line is not a mesh handled by prewarm. return; } - brush.position.set(value.at[0], hit.point.y + 0.2, value.at[1]); + brush.position.set(value.at[0], height + 0.2, value.at[1]); brush.scale.setScalar(value.radius); }, showSpline(points): void { @@ -944,24 +1131,24 @@ export async function createEditorView( for (const instance of props.byId.values()) { const model = new Group(); model.name = instance.placement.asset; - for (const part of instance.parts) { + const key = `${instance.placement.asset}:${variantFor(instance.placement, instance.placement.asset)}`; + const parts = propParts.get(key); + if (!parts) throw new Error(`Missing export prop '${key}'`); + for (const part of parts.filter((part) => (part.level ?? 0) === 0)) { // The draw's own name ends in its role (`props:spruce:0:crown`), which is exactly the key // the portable surfaces are chosen by. - const role = part.mesh.name.split(":").at(-1) ?? "crown"; + const role = part.role; // An imported model keeps the materials its file was authored with; only the starter's own // shapes are re-dressed in the portable starter surfaces. - const own = part.mesh.userData.ownMaterial === true ? part.mesh.material : undefined; + const own = part.material; const surface = own ?? portableProps.materials[role]; if (!surface) throw new Error(`No portable surface for prop role '${role}'`); - const draw = new Mesh(part.mesh.geometry, surface); + const draw = new Mesh(part.geometry, surface); draw.name = role; model.add(draw); } models.set(instance.placement.id, model); - instance.mesh.updateWorldMatrix(true, false); - const matrix = new Matrix4(); - instance.mesh.getMatrixAt(instance.index, matrix); - transforms.set(instance.placement.id, matrix.premultiply(instance.mesh.matrixWorld)); + transforms.set(instance.placement.id, instance.pose.clone()); } const geometry = mesh.geometry.clone(); geometry.setAttribute("uv", new BufferAttribute(bakeMesh(terrain).uvs, 2)); @@ -975,7 +1162,7 @@ export async function createEditorView( staleFrames: 0, })); try { - return await exportWorldGLB({ + const prepared = await prepareWorldGLTF({ revision: accepted.revision, snapshotTime: elapsed, state: applyPlacementOverrides(terrain, accepted.document.placementOverrides ?? {}), @@ -984,6 +1171,21 @@ export async function createEditorView( transforms, ...(water.length ? { water } : {}), }); + const response = await fetch(new URL("export", controller.baseUrl), { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ baseRevision: accepted.revision, preparedWorld: prepared.json }), + }); + if (!response.ok) { + const error = (await response.json()) as { error?: string }; + throw new Error(error.error ?? `World cook failed (${response.status})`); + } + return { + name: "world.glb", + mime: "model/gltf-binary", + bytes: new Uint8Array(await response.arrayBuffer()), + report: prepared.report, + }; } finally { geometry.dispose(); material.dispose(); @@ -998,6 +1200,8 @@ export async function createEditorView( evaluationMs = ms; }, dispose(): void { + disposed = true; + streamGeneration++; selection.dispose(); envImages?.dispose(); surfaces.dispose(); diff --git a/examples/strata-terrain-preview/src/render/loading.ts b/examples/strata-terrain-preview/src/render/loading.ts index cd702ff25e..458ed25905 100644 --- a/examples/strata-terrain-preview/src/render/loading.ts +++ b/examples/strata-terrain-preview/src/render/loading.ts @@ -1,4 +1,3 @@ -import type { IAssetLoader, ICtx } from "@threenative/core"; import { type Camera, CanvasTexture, @@ -10,185 +9,8 @@ import { type Scene, type Texture, } from "three"; -import type { IPropPreparationProgress } from "./propProgress.js"; const palette = { skyLow: 0x142321, skyHigh: 0x304636, accent: 0xb6c586 }; -type SpawnStartup = Pick< - ICtx["startup"], - "phase" | "progress" | "compileSettled" | "timeline" | "warmup" ->; - -function startupSnapshot(startup: SpawnStartup) { - const warmup = startup.warmup; - const timeline = startup.timeline; - return { - phase: startup.phase, - progress: startup.progress, - compileSettled: startup.compileSettled, - timeline: { - loadStartedMs: timeline.loadStartedMs, - enteredMs: timeline.enteredMs, - compileSettledMs: timeline.compileSettledMs, - frameworkReadyMs: timeline.frameworkReadyMs, - readyMs: timeline.readyMs, - }, - warmup: warmup && { - status: warmup.status, - attempted: warmup.attempted, - candidates: warmup.candidates, - created: warmup.observed?.created, - failed: warmup.observed?.failed, - pending: warmup.observed?.pending, - }, - }; -} - -function worldSnapshot(world: IPropPreparationProgress["worlds"][number]) { - const stats = world.stats(); - return { - generation: world.uuid.slice(0, 64), - name: world.name.slice(0, 64), - parent: world.parent?.uuid.slice(0, 64) ?? null, - released: world.released, - residentCells: stats.residentCells, - loadedCells: stats.loadedCells, - loadsInFlight: stats.loadsInFlight, - loadsQueued: stats.loadsQueued, - pendingPrewarm: stats.pendingPrewarm, - prewarmMinted: stats.prewarmMinted, - failures: stats.failures, - admission: { - spentMs: stats.admission.spentMs, - backlog: stats.admission.backlog, - deferred: stats.admission.deferred, - }, - }; -} - -/** A bounded value receipt taken by the spawn gate before rejection or scene teardown. */ -export function spawnReadinessSnapshot(input: { - readonly reason: string; - readonly atMs: number; - readonly world: string; - readonly sceneUuid: string; - readonly released: boolean; - readonly gate: { readonly ready: boolean; readonly error: string }; - readonly stage: string; - readonly propsSettled: boolean; - readonly preparation?: IPropPreparationProgress; - readonly coverage?: { - readonly required: number; - readonly loaded: number; - readonly failures: number; - readonly ready: boolean; - }; - readonly assets: IAssetLoader["progress"]; - readonly startup: SpawnStartup; -}) { - const preparation = input.preparation; - return { - version: 1, - reason: input.reason.slice(0, 256), - atMs: input.atMs, - world: input.world.slice(0, 64), - sceneUuid: input.sceneUuid.slice(0, 64), - released: input.released, - gate: { ready: input.gate.ready, error: input.gate.error.slice(0, 256) }, - coverage: input.coverage && { - required: input.coverage.required, - loaded: input.coverage.loaded, - failures: input.coverage.failures, - ready: input.coverage.ready, - }, - preparation: { - stage: input.stage.slice(0, 64), - propsSettled: input.propsSettled, - phase: preparation?.phase, - bucket: preparation?.bucket.slice(0, 64), - added: preparation?.added, - total: preparation?.total, - worldCount: preparation?.worlds.length ?? 0, - prewarmedWorlds: preparation?.prewarmedWorlds ?? 0, - }, - assets: { - requested: input.assets.requested, - settled: input.assets.settled, - requestedBytes: input.assets.requestedBytes, - settledBytes: input.assets.settledBytes, - pendingCount: input.assets.pending.length, - pending: input.assets.pending.slice(0, 4).map((path) => path.slice(0, 128)), - }, - startup: startupSnapshot(input.startup), - worlds: (preparation?.worlds ?? []).slice(0, 4).map(worldSnapshot), - }; -} - -/** A one-shot gameplay gate; streaming continues after its launch region is allowed to evict. */ -export function createSpawnReadiness( - label: string, - budgetMs = 120_000, - beforeFailure?: (reason: string) => void, -) { - if (!label.trim() || !Number.isFinite(budgetMs) || budgetMs <= 0) - throw new Error("Spawn readiness requires a label and positive budget."); - let ready = false; - let error = ""; - let cancelled = false; - let capturingFailure = false; - let resolve: () => void = () => undefined; - let reject: (reason: Error) => void = () => undefined; - const promise = new Promise((done, fail) => { - resolve = done; - reject = fail; - }); - function fail(reason: unknown): void { - if (ready || error || cancelled || capturingFailure) return; - const message = - (reason instanceof Error ? reason.message : String(reason)) || `${label} failed`; - capturingFailure = true; - try { - beforeFailure?.(message); - } catch { - // A diagnostic must never replace the admission failure or leave its promise pending. - } - capturingFailure = false; - error = message; - clearTimeout(timer); - reject(new Error(error)); - } - const timer = setTimeout(() => fail(`${label} admission exceeded ${budgetMs} ms`), budgetMs); - return { - promise, - fail, - get ready() { - return ready; - }, - get error() { - return error; - }, - observe(covered: boolean, failures = 0): void { - if (ready || error || cancelled || capturingFailure) return; - if (failures > 0) { - fail(`${label} assets failed to load`); - return; - } - if (covered) { - ready = true; - clearTimeout(timer); - resolve(); - } - }, - cancel(): void { - if (capturingFailure) return; - clearTimeout(timer); - if (!ready && !error && !cancelled) { - cancelled = true; - reject(new Error(`${label} cancelled`)); - } - }, - }; -} - /* BEGIN THREENATIVE LOADING APPEARANCE */ /** Edit these source constants for the starter's loading look. */ export const loading = { diff --git a/examples/strata-terrain-preview/src/render/ocean.ts b/examples/strata-terrain-preview/src/render/ocean.ts index f016f7b421..7531bcda35 100644 --- a/examples/strata-terrain-preview/src/render/ocean.ts +++ b/examples/strata-terrain-preview/src/render/ocean.ts @@ -14,6 +14,7 @@ import { clamp, color, float, + fwidth, mix, mx_fractal_noise_float, mx_noise_float, @@ -112,6 +113,11 @@ export function createWaterMesh(ocean: SpectralOcean, data: IBakedWorld): Mesh { // Flooding only through >2 m water leaves sheltered lagoons out of the breaking-surf mask. const n = data.resolution; const cell = data.size / (n - 1); + // The surveyed deepest bed sets the tint range and the open-sea continuation. + const deepDepth = data.heights.reduce( + (deepest, height) => Math.max(deepest, level - height), + cell, + ); const distance = Float32Array.from(data.heights, (height) => (height >= level ? 0 : 1e4)); const exposed = new Uint8Array(n * n); const queue: number[] = []; @@ -195,12 +201,15 @@ export function createWaterMesh(ocean: SpectralOcean, data: IBakedWorld): Mesh { roughness: 0.12, }); const shoreUV = (x: Node<"float">, z: Node<"float">) => - vec2(x.div(data.size).add(0.5), z.div(data.size).add(0.5)); + vec2(x.div(data.size).add(0.5), z.div(data.size).add(0.5)) + .mul((n - 1) / n) + .add(0.5 / n); + // Outside the surveyed bed, continue its edge depth before easing into open sea. const inside = (x: Node<"float">, z: Node<"float">) => - float(1).sub(smoothstep(data.size / 2 - 2, data.size / 2 + 2, x.abs().max(z.abs()))); + float(1).sub(smoothstep(data.size / 2, data.size * 0.75, x.abs().max(z.abs()))); const vertexLand = texture(heightTexture, shoreUV(positionLocal.x, positionLocal.z)).r; const vertexDepth = mix( - float(28), + float(deepDepth), float(level).sub(vertexLand), inside(positionLocal.x, positionLocal.z), ); @@ -234,7 +243,7 @@ export function createWaterMesh(ocean: SpectralOcean, data: IBakedWorld): Mesh { ); const shore = texture(heightTexture, shoreUV(positionWorld.x, positionWorld.z)); const local = inside(positionWorld.x, positionWorld.z); - const depth = mix(float(28), positionWorld.y.sub(shore.r), local); + const depth = mix(float(deepDepth), float(level).sub(shore.r), local); const damp = smoothstep(0.1, 2.5, depth); const wind = mx_noise_float( vec3(positionWorld.x.mul(0.07).sub(time.mul(0.04)), positionWorld.z.mul(0.07), time.mul(0.015)), @@ -257,22 +266,44 @@ export function createWaterMesh(ocean: SpectralOcean, data: IBakedWorld): Mesh { const bounced = view.negate().reflect(normal); // The standard material supplies Fresnel and the actual sun's specular lobe; the sky supplies radiance. material.envNode = mix(color(0xb6cbd5), color(0x568fbd), clamp(bounced.y, 0, 1)).mul(1.15); - const water = mix(OCEAN_LOOK.shallow, OCEAN_LOOK.deep, smoothstep(0.4, 9, depth)); - const surf = mx_noise_float( - vec3(positionWorld.x.mul(0.9), positionWorld.z.mul(0.9), time.mul(0.45)), - ) + const water = mix( + OCEAN_LOOK.shallow, + OCEAN_LOOK.deep, + smoothstep(deepDepth * 0.25, deepDepth, depth).pow(2), + ); + const surfPoint = vec3(positionWorld.x, positionWorld.z, time.mul(0.45)); + const broadSurf = mx_noise_float(surfPoint.mul(vec3(0.18, 0.18, 0.3))) + .mul(0.5) + .add(0.5); + const surf = mx_noise_float(surfPoint.mul(vec3(0.9, 0.9, 1))) .mul(0.5) .add(0.5); + // A pixel covering a bubble fades it out; derivative-wide thresholds soften the remaining edges. + const footprint = fwidth(positionWorld.xz).length().mul(0.9); + const antialias = fwidth(surf).max(0.025); + const bubbles = smoothstep( + float(0.5).sub(broadSurf.mul(0.16)).sub(antialias), + float(0.65).sub(broadSurf.mul(0.16)).add(antialias), + surf, + ).mul(float(1).sub(smoothstep(0.35, 1, footprint))); + const shoreReach = float(1) + .sub(smoothstep(3, 12, shore.g)) + .mul(shore.b.mul(0.65).add(0.35)) + .mul(local); const shoreFoam = float(1) - .sub(smoothstep(0.25, 1.8, depth)) + .sub(smoothstep(0.25, 1.8, depth.add(time.mul(0.85).sin().mul(0.18)))) .mul(smoothstep(0.015, 0.08, depth)) - .mul(float(1).sub(smoothstep(1, 5.5, shore.g))) - .mul(smoothstep(0.25, 0.65, surf).mul(0.7).add(0.3)) - .mul(shore.b) - .mul(local) - .mul(0.8); - material.colorNode = mix(water, color(FOAM), shoreFoam); - material.opacityNode = smoothstep(-0.03, 0.35, depth).max(shoreFoam.mul(0.9)); + .mul(bubbles) + .mul(broadSurf.mul(0.6).add(0.25)) + .mul(shoreReach); + const swash = float(1) + .sub(smoothstep(0.1, 2.5, depth)) + .mul(smoothstep(0, 0.12, depth)) + .mul(broadSurf.mul(0.5).add(0.5)) + .mul(shoreReach) + .mul(0.12); + material.colorNode = mix(water, color(FOAM), shoreFoam.add(swash)); + material.opacityNode = smoothstep(0, Math.min(2.5, deepDepth), depth).max(shoreFoam.mul(0.9)); material.roughnessNode = mix( float(0.12).add(smoothstep(70, 700, eyeDistance).mul(0.07)), float(0.7), diff --git a/examples/strata-terrain-preview/src/render/pack.ts b/examples/strata-terrain-preview/src/render/pack.ts index 674b075bb1..e22af1d76a 100644 --- a/examples/strata-terrain-preview/src/render/pack.ts +++ b/examples/strata-terrain-preview/src/render/pack.ts @@ -7,6 +7,7 @@ import { DataUtils, DoubleSide, EquirectangularReflectionMapping, + FrontSide, type Group, type Material, type Mesh, @@ -47,12 +48,14 @@ import { MeshPhysicalNodeMaterial, type Node } from "three/webgpu"; import { GROUND_MAPS, ROCKFACE_MAPS, WORLD_ROCKS } from "../world/terrainAssets.js"; import { BIOMES, + type IWorldBiome, type WorldName, alpineRockAlbedo, alpineRockColor, alpineSnowCover, biomeWeights, desertRockColor, + measureBiome, } from "./biomes.js"; import { type IPropGround, @@ -149,6 +152,7 @@ interface ISurfaceContext { readonly source: MeshStandardMaterial; readonly asset: string; readonly world: WorldName; + readonly biome?: IWorldBiome; readonly stone: boolean; readonly cutout: boolean; readonly canopy: boolean; @@ -206,6 +210,7 @@ function stoneSurface( snowMap?: Texture, ): void { const { source, world } = context; + if (world === "coastal") material.shadowSide = FrontSide; const growth = smoothstep(-0.15, 0.3, mx_noise_float(positionWorld.mul(2.1))); const moss = normalWorldGeometry.y .max(0) @@ -221,16 +226,18 @@ function stoneSurface( moss, ); if (world !== "alpine" && world !== "tundra") return; + const biome = context.biome; + if (!biome) throw new RangeError("Stone snow requires measured terrain elevations"); const snow = world === "alpine" - ? smoothstep(0.12, 0.82, alpineSnowCover()) - : (biomeWeights(BIOMES[world], float(1).sub(normalWorldGeometry.y), float(0), growth) + ? smoothstep(0.12, 0.82, alpineSnowCover(biome)) + : (biomeWeights(biome, float(1).sub(normalWorldGeometry.y), float(0), growth) .snow as Node<"float">); material.colorNode = mix( material.colorNode as Node<"vec3">, world === "alpine" ? (snowMap ? texture(snowMap, positionWorld.xz.div(12)).rgb : vec3(0.82, 0.86, 0.9)).mul( - vec3(...BIOMES.alpine.snowTint), + vec3(...biome.snowTint), ) : vec3(0.84, 0.87, 0.91), snow, @@ -439,6 +446,7 @@ function surface( snowMap?: Texture, skyLight?: Texture, comparison?: ICanopyComparison, + biome?: IWorldBiome, ): MeshPhysicalNodeMaterial { const stone = STONE.has(asset); const cutout = !stone && source.alphaTest > 0; @@ -450,6 +458,7 @@ function surface( source, asset, world, + biome, stone, cutout, canopy, @@ -713,7 +722,11 @@ export async function loadPack( world: WorldName = "forest", ground?: IPropGround, comparison?: ICanopyComparison, + biome?: IWorldBiome, ): Promise { + const measured = biome ?? (ground ? measureBiome(BIOMES[world], ground.heights) : undefined); + if (!measured && (world === "alpine" || world === "tundra")) + throw new RangeError("Stone snow requires measured terrain elevations"); const parts = new Map(); const built: { geometry: BufferGeometry; material: Material }[] = []; const selected = species @@ -767,7 +780,7 @@ export async function loadPack( ), ) : []; - const rockGround = await createRockGround(assets, ground, BIOMES[world]); + const rockGround = await createRockGround(assets, ground, measured); const sky = assets ? await loadSkyLight(assets) : undefined; const loaded = await Promise.all( selected.map(async (one) => { @@ -816,7 +829,7 @@ export async function loadPack( const material = inherited instanceof MeshPhysicalNodeMaterial ? inherited - : surface(source, one.asset, world, rockface[2], sky, comparison); + : surface(source, one.asset, world, rockface[2], sky, comparison, measured); if (stone) rockGround?.apply(material); if (source !== section.material) source.dispose(); built.push({ geometry, material }); diff --git a/examples/strata-terrain-preview/src/render/placements.ts b/examples/strata-terrain-preview/src/render/placements.ts new file mode 100644 index 0000000000..def2dfb224 --- /dev/null +++ b/examples/strata-terrain-preview/src/render/placements.ts @@ -0,0 +1,152 @@ +import type { IAssetLoader } from "@threenative/core"; +import type { IPlacement } from "@threenative/terrain"; +import type { IPropScatter } from "./scatter.js"; + +interface IHeader { + version: 1; + counts: Record; + canopy: number; + /** First eight-float record per asset; order indices follow the record array. */ + offsets: Record; +} + +/** Eight LE floats: position, yaw, scale, normal; an order index preserves identity across asset runs. */ +export function encodePlacements(scatter: IPropScatter, canopy: Float32Array): ArrayBuffer { + const offsets: Record = {}; + let planned = 0; + for (const [asset, count] of Object.entries(scatter.counts)) { + offsets[asset] = planned; + planned += count; + } + if (planned !== scatter.placements.length) throw new Error("Invalid baked placement counts"); + const header: IHeader = { version: 1, counts: scatter.counts, offsets, canopy: canopy.length }; + const json = new TextEncoder().encode(JSON.stringify(header)); + const length = Math.ceil(json.length / 4) * 4; + const total = scatter.placements.length; + const canopyOffset = 4 + length + total * 36; + const buffer = new ArrayBuffer(canopyOffset + canopy.byteLength); + const bytes = new Uint8Array(buffer); + bytes.fill(32, 4, 4 + length); + bytes.set(json, 4); + const view = new DataView(buffer); + view.setUint32(0, length, true); + const seen: Record = {}; + for (const [i, p] of scatter.placements.entries()) { + const index = seen[p.asset] ?? 0; + const identity = `temperate-${p.asset}:${index}`; + const outcrop = p.id === `${identity}:outcrop`; + const first = offsets[p.asset]; + if ( + first === undefined || + (!outcrop && p.id !== identity) || + p.layer !== `temperate-${p.asset}` + ) + throw new Error("Invalid baked placement identity"); + const record = (first as number) + index; + view.setUint32( + 4 + length + total * 32 + record * 4, + i | (p.alignToNormal ? 0x40000000 : 0) | (outcrop ? 0x80000000 : 0), + true, + ); + let offset = 4 + length + record * 32; + seen[p.asset] = index + 1; + for (const value of [...p.position, p.rotation, p.scale, ...p.normal]) { + view.setFloat32(offset, value, true); + offset += 4; + } + } + for (const [i, value] of canopy.entries()) view.setFloat32(canopyOffset + i * 4, value, true); + decodePlacements(buffer); + return buffer; +} + +function invalid(): never { + throw new Error("Invalid baked placement table"); +} + +function readHeader(view: DataView) { + if (view.byteLength < 4) invalid(); + const length = view.getUint32(0, true); + if (length === 0 || length % 4 !== 0 || length > view.byteLength - 4) invalid(); + const header = JSON.parse( + new TextDecoder("utf-8", { fatal: true }).decode( + new Uint8Array(view.buffer, view.byteOffset + 4, length), + ), + ) as IHeader; + if ( + !header || + header.version !== 1 || + !header.counts || + typeof header.counts !== "object" || + Array.isArray(header.counts) || + !Number.isSafeInteger(header.canopy) || + header.canopy < 0 || + !header.offsets || + typeof header.offsets !== "object" || + Array.isArray(header.offsets) + ) + invalid(); + let total = 0; + for (const [asset, count] of Object.entries(header.counts)) { + if (!/^[a-z]+$/.test(asset) || !Number.isSafeInteger(count) || count < 0) invalid(); + if (header.offsets[asset] !== total) invalid(); + total += count; + } + const canopyOffset = 4 + length + total * 36; + if (canopyOffset + header.canopy * 4 !== view.byteLength) invalid(); + return { header, length, total, canopyOffset }; +} + +export function decodePlacements(buffer: ArrayBuffer): IPropScatter & { canopy: Float32Array } { + const view = new DataView(buffer); + const { header, length, total, canopyOffset } = readHeader(view); + const assets = Object.keys(header.counts); + const visited = new Uint8Array(total); + let offset = 4 + length; + const next = (): number => { + const value = view.getFloat32(offset, true); + offset += 4; + if (!Number.isFinite(value)) invalid(); + return value; + }; + const placements: IPlacement[] = new Array(total); + let record = 0; + for (const asset of assets) + for (let index = 0; index < (header.counts[asset] ?? 0); index++) { + const code = view.getUint32(4 + length + total * 32 + record++ * 4, true); + const order = code & 0x3fffffff; + if (order >= total || visited[order]) invalid(); + visited[order] = 1; + const position: [number, number, number] = [next(), next(), next()]; + const rotation = next(); + const scale = next(); + const normal: [number, number, number] = [next(), next(), next()]; + if (scale <= 0) invalid(); + placements[order] = { + asset, + id: `temperate-${asset}:${index}${code & 0x80000000 ? ":outcrop" : ""}`, + layer: `temperate-${asset}`, + alignToNormal: !!(code & 0x40000000), + position, + rotation, + scale, + normal, + }; + } + offset = canopyOffset; + const canopy = Float32Array.from({ length: header.canopy }, next); + if (canopy.some((value) => value < 0 || value > 1)) invalid(); + return { placements, counts: header.counts, canopy }; +} + +export async function loadPlacements( + assets: IAssetLoader, + world: string, +): Promise> { + const path = `world/${world}.placements.bin`; + for (const url of await assets.resolve(path)) { + const response = await fetch(url); + if (response.ok) return decodePlacements(await response.arrayBuffer()); + } + throw new Error(`Missing baked placement table: ${path}`); +} diff --git a/examples/strata-terrain-preview/src/render/propMaterials.ts b/examples/strata-terrain-preview/src/render/propMaterials.ts index 9380b97cb5..f804233946 100644 --- a/examples/strata-terrain-preview/src/render/propMaterials.ts +++ b/examples/strata-terrain-preview/src/render/propMaterials.ts @@ -24,6 +24,7 @@ import { LinearMipmapLinearFilter, type Material, RedFormat, + RepeatWrapping, type Texture, Vector3, } from "three"; @@ -52,6 +53,7 @@ import { pow, renderGroup, rotateUV, + sign, smoothstep, texture, textureSize, @@ -65,6 +67,7 @@ import { import type { Node } from "three/webgpu"; import { MeshStandardNodeMaterial } from "three/webgpu"; import { + DRY_STONE_MAPS, FERN_MAPS, FIR_MAPS, GROUND_MAPS, @@ -78,7 +81,7 @@ import { SOIL_MAP, } from "../world/terrainAssets.js"; import { - type IBiome, + type IWorldBiome, alpineRockAlbedo, alpineRockColor, alpineSnowCover, @@ -221,7 +224,7 @@ const CUTOUT = 0.42; * not: replacing the bark map with one of a different resolution changes nothing about how big a * bark plate is. */ -const TILE = { bark: 1.6, stone: 2.4 } as const; +const TILE = { bark: 1.6, stone: 1.4 } as const; /** * The prepared pine's own cutoff, which is a seventh of the shared one. @@ -248,9 +251,11 @@ const TILE = { bark: 1.6, stone: 2.4 } as const; */ /** Load one starter map, or nothing. A prop still draws without it, on its own colours. */ -async function map(assets: IAssetLoader | undefined, path: string, data: boolean) { +async function map(assets: IAssetLoader | undefined, path: string, data: boolean, repeat = false) { if (assets === undefined) return undefined; - const found = await assets.texture(path, { data }).catch(() => undefined); + const found = await assets + .texture(path, { data, ...(repeat ? { wrap: RepeatWrapping } : {}) }) + .catch(() => undefined); if (found === undefined) return undefined; // Cutout maps need mips and an anisotropy filter: a needle card sampled without them shimmers at // a grazing angle, which is exactly the angle a meadow is viewed from. @@ -420,12 +425,13 @@ function intoGround( export async function createRockGround( assets?: IAssetLoader, ground?: IPropGround, - biome?: IBiome, + biome?: IWorldBiome, ) { - if (!ground) return undefined; + // Beach stones keep their dry surface: the temperate contact band paints grass onto sand. + if (!ground || biome?.world === "coastal" || biome?.world === "tundra") return undefined; const under = groundHeight(ground); const paths = biome?.maps ?? GROUND_MAPS; - const temperate = !biome || biome.world === "forest" || biome.world === "coastal"; + const temperate = !biome || biome.world === "forest"; const rockPaths = temperate ? ROCKFACE_MAPS : paths.rock; const [grass, relief, rock, rockNormal, snow] = await Promise.all([ map(assets, paths.grass.diffuse, false), @@ -439,14 +445,16 @@ export async function createRockGround( found ?? (paths.rock.normal ? map(assets, paths.rock.normal, true) : undefined), ) : undefined, - biome?.world === "alpine" || biome?.world === "tundra" - ? map(assets, paths.snow.diffuse, false) - : undefined, + biome?.world === "alpine" ? map(assets, paths.snow.diffuse, false) : undefined, ]); return { apply(material: MeshStandardNodeMaterial): void { if (!grass || !material.colorNode) return; - const share = groundShare(under.node); + // World-space distance across the side gives a half-metre, noise-broken contact falloff. + const radial = positionWorld.y.sub(under.node).div(length(normalWorldGeometry.xz).max(0.25)); + const share = float(1).sub( + smoothstep(0, 0.5, radial.add(mx_noise_float(positionWorld.mul(1.7)).mul(0.12))), + ); const floor = groundTurf(groundLayer(grass, "grass").rgb, biome); const slope = smoothstep(0.17, 0.33, under.normal.y.oneMinus()); const soil = share; @@ -464,7 +472,9 @@ export async function createRockGround( contact = mix(contact, groundLayer(snow, "snow").rgb.mul(vec3(...biome.snowTint)), cover); } material.colorNode = mix(material.colorNode as Node<"vec3">, contact, soil); - material.aoNode = mix(1, 0.86, share); + material.aoNode = ((material.aoNode as Node<"float"> | null) ?? float(1)).mul( + mix(1, 0.86, share), + ); const lit = material.normalNode ?? (material.normalMap @@ -500,31 +510,46 @@ export async function createRockGround( }; } -function stoneSurface(maps: { diffuse?: Texture; normal?: Texture }) { +function stoneSurface(maps: { diffuse?: Texture; normal?: Texture; arm?: Texture }) { const material = new MeshStandardNodeMaterial({ metalness: 0, roughness: 0.93, side: FrontSide }); const tile = float(TILE.stone); - const weight = abs(normalWorld).normalize(); + const detail = float(1).sub(smoothstep(25, 90, length(cameraPosition.sub(positionWorld)))); + const geometryNormal = normalWorldGeometry; + const axis = abs(geometryNormal).pow(4); + const share = axis.div(axis.x.add(axis.y).add(axis.z)); + const xUV = positionWorld.zy.div(tile); + const yUV = positionWorld.xz.div(tile); + const zUV = positionWorld.xy.div(tile); if (maps.diffuse === undefined) material.colorNode = vec3(0.42, 0.43, 0.38); else { - // Each wall reads its own projection rather than stretching an XZ photograph. - const axis = abs(normalWorldGeometry).pow(4); - const share = axis.div(axis.x.add(axis.y).add(axis.z)); material.colorNode = share.x - .mul(texture(maps.diffuse, positionWorld.zy.div(tile)).rgb) - .add(share.y.mul(texture(maps.diffuse, positionWorld.xz.div(tile)).rgb)) - .add(share.z.mul(texture(maps.diffuse, positionWorld.xy.div(tile)).rgb)); + .mul(texture(maps.diffuse, xUV).rgb) + .add(share.y.mul(texture(maps.diffuse, yUV).rgb)) + .add(share.z.mul(texture(maps.diffuse, zUV).rgb)); } if (maps.normal !== undefined) { - const source = maps.normal; - const x = texture(source, positionWorld.yz.div(tile)).xy.mul(2).sub(1); - const y = texture(source, positionWorld.zx.div(tile)).xy.mul(2).sub(1); - const z = texture(source, positionWorld.xy.div(tile)).xy.mul(2).sub(1); - const relief = weight.x - .mul(vec3(0, x.x, x.y)) - .add(weight.y.mul(vec3(y.y, 0, y.x))) - .add(weight.z.mul(vec3(z.x, z.y, 0))) - .mul(1.15); - material.normalNode = transformNormalToView(normalize(normalWorld.add(relief))); + // GL maps encode +Y in green. Match each tangent component to its colour projection. + const x = texture(maps.normal, xUV).xy.mul(2).sub(1); + const y = texture(maps.normal, yUV).xy.mul(2).sub(1); + const z = texture(maps.normal, zUV).xy.mul(2).sub(1); + const relief = share.x + .mul(vec3(0, x.y, x.x.mul(sign(geometryNormal.x)))) + .add(share.y.mul(vec3(y.x, 0, y.y))) + .add(share.z.mul(vec3(z.x.mul(sign(geometryNormal.z)), z.y, 0))) + .mul(maps.arm ? 0.45 : 0.7) + .mul(detail); + const tangent = relief.sub(geometryNormal.mul(dot(geometryNormal, relief))); + material.normalNode = transformNormalToView(normalize(geometryNormal.add(tangent))); + } + if (maps.arm) { + // One dominant-axis ARM tap shares roughness/AO; broad data needs no three-way blend. + const plane = axis.x + .greaterThan(axis.y) + .and(axis.x.greaterThan(axis.z)) + .select(xUV, axis.y.greaterThan(axis.z).select(yUV, zUV)); + const arm = texture(maps.arm, plane); + material.roughnessNode = arm.g.mul(0.2).add(0.8); + material.aoNode = arm.r.max(0.65); } return material; } @@ -871,7 +896,7 @@ export function skyEnvironment(material: MeshStandardNodeMaterial, sky: Texture) export async function createPropSurfaces( assets?: IAssetLoader, ground?: IPropGround, - biome?: IBiome, + biome?: IWorldBiome, skyLight?: Texture, ): Promise { const seconds = uniform(0).setGroup(renderGroup) as unknown as Node<"float">; @@ -889,29 +914,37 @@ export async function createPropSurfaces( ); const [bark, stone, atlas, relief] = await Promise.all([ surfaceMaps(assets, PROP_MAPS.bark), - surfaceMaps( - assets, - biome?.world === "desert" || biome?.world === "alpine" - ? { - diffuse: biome.maps.rock.diffuse, - normal: biome.maps.rock.normal ?? PROP_MAPS.stone.normal, - roughness: "cliff_side/cliff_side_rough_1k.jpg", - } - : biome?.world === "tundra" + (async () => { + const dry = biome?.world === "coastal" || biome?.world === "desert"; + const set = dry + ? DRY_STONE_MAPS + : biome?.world === "alpine" ? { - diffuse: "lichen_rock/lichen_rock_diff_512.jpg", - normal: "lichen_rock/lichen_rock_nor_gl_512.jpg", - roughness: PROP_MAPS.stone.roughness, + diffuse: biome.maps.rock.diffuse, + normal: biome.maps.rock.normal ?? PROP_MAPS.stone.normal, } - : PROP_MAPS.stone, - ), + : biome?.world === "tundra" + ? { + diffuse: "lichen_rock/lichen_rock_diff_512.jpg", + normal: "lichen_rock/lichen_rock_nor_gl_512.jpg", + } + : PROP_MAPS.stone; + const [diffuse, normal, arm] = await Promise.all([ + map(assets, set.diffuse, false, true), + map(assets, set.normal, true, true), + dry ? map(assets, DRY_STONE_MAPS.arm, true, true) : undefined, + ]); + return { diffuse, normal, arm }; + })(), map(assets, NEEDLE_ATLAS, false), map(assets, NEEDLE_SURFACE, true), ]); - const alpineSnow = - biome?.world === "alpine" ? await map(assets, biome.maps.snow.diffuse, false) : undefined; + const rockSnow = + biome?.world === "alpine" || biome?.world === "tundra" + ? await map(assets, biome.maps.snow.diffuse, false) + : undefined; const textures: Texture[] = [ - ...(alpineSnow ? [alpineSnow] : []), + ...(rockSnow ? [rockSnow] : []), ...(under ? [under.texture] : []), ...(soil ? [soil] : []), ...[fernDiffuse, fernAlpha].filter((found): found is Texture => found !== undefined), @@ -920,6 +953,7 @@ export async function createPropSurfaces( bark.roughness, stone.diffuse, stone.normal, + stone.arm, atlas, relief, ].filter((found): found is Texture => found !== undefined); @@ -1059,6 +1093,21 @@ export async function createPropSurfaces( sway(petalMaterial, seconds, WIND.amplitude.petal); const stoneMaterial = stoneSurface(stone); + // Cast the sun-facing surface: back-face depth leaves a lit strip at the sand intersection. + if (biome?.world === "coastal") stoneMaterial.shadowSide = FrontSide; + if (biome?.propStoneTint && stoneMaterial.colorNode) + stoneMaterial.colorNode = ( + stone.diffuse ? (stoneMaterial.colorNode as Node<"vec3">) : vec3(0.4, 0.35, 0.3) + ).mul(vec3(...biome.propStoneTint)); + if (biome?.propStoneTint && stoneMaterial.colorNode) { + const albedo = stoneMaterial.colorNode as Node<"vec3">; + const far = smoothstep(45, 140, length(cameraPosition.sub(positionWorld))); + const luminance = dot(albedo, vec3(0.2126, 0.7152, 0.0722)); + const ceiling = dot(vec3(...biome.distantStone), vec3(0.2126, 0.7152, 0.0722)); + stoneMaterial.colorNode = albedo.mul( + mix(1, ceiling.div(luminance.max(ceiling).max(0.0001)), far), + ); + } const rockGround = await createRockGround(assets, ground, biome); const materials: IPropMaterials = { bark: barkMaterial, @@ -1085,22 +1134,46 @@ export async function createPropSurfaces( ), ); grassMaterial.emissiveNode = vec3(0); - if (stoneMaterial.colorNode) + if (!biome.propStoneTint && stoneMaterial.colorNode) stoneMaterial.colorNode = (stoneMaterial.colorNode as Node<"vec3">).mul( vec3(...biome.stoneTint), ); } if (biome?.world === "alpine") { const rock = stone.diffuse ? alpineRockAlbedo(stone.diffuse) : vec3(0.42, 0.43, 0.38); - const snow = alpineSnow - ? texture(alpineSnow, positionWorld.xz.div(12)).rgb - : vec3(0.82, 0.86, 0.9); + const snow = rockSnow ? texture(rockSnow, positionWorld.xz.div(12)).rgb : vec3(0.82, 0.86, 0.9); stoneMaterial.colorNode = mix( alpineRockColor(rock), snow.mul(vec3(...biome.snowTint)), - smoothstep(0.12, 0.82, alpineSnowCover()), + smoothstep(0.12, 0.82, alpineSnowCover(biome)), + ); + } + if (biome?.world === "tundra") { + stoneMaterial.colorNode = (stoneMaterial.colorNode as Node<"vec3">).mul(1.35); + const cover = + biomeWeights( + biome, + float(1).sub(normalWorldGeometry.y), + float(0), + mx_noise_float(positionWorld.mul(0.05)), + ).snow ?? float(0); + const snow = rockSnow ? texture(rockSnow, positionWorld.xz.div(12)).rgb : vec3(0.82, 0.86, 0.9); + const side = stoneMaterial.colorNode as Node<"vec3">; + stoneMaterial.emissiveNode = side.mul(0.18).mul(float(1).sub(cover)); + stoneMaterial.colorNode = mix(side, snow.mul(vec3(...biome.snowTint)), cover); + stoneMaterial.normalNode = normalize( + mix( + (stoneMaterial.normalNode as Node<"vec3"> | null) ?? + transformNormalToView(normalWorldGeometry), + transformNormalToView(normalWorldGeometry), + cover, + ), ); + if (skyLight) skyEnvironment(stoneMaterial, skyLight); } + // Albedo-coloured ground bounce keeps unlit beach faces legible without another texture tap. + if (biome?.propStoneTint && stoneMaterial.colorNode) + stoneMaterial.emissiveNode = (stoneMaterial.colorNode as Node<"vec3">).mul(0.07); rockGround?.apply(stoneMaterial); // The alpha-cut, double-sided surfaces: a needle card, a frond, a petal. Without an image-based // light each is a flat paddle, which is the paperboard the engine warns about at scene entry. diff --git a/examples/strata-terrain-preview/src/render/propStreaming.ts b/examples/strata-terrain-preview/src/render/propStreaming.ts index 159f4c077a..0411d2ab18 100644 --- a/examples/strata-terrain-preview/src/render/propStreaming.ts +++ b/examples/strata-terrain-preview/src/render/propStreaming.ts @@ -13,6 +13,7 @@ import type { IPropPreparationProgress } from "./propProgress.js"; import { DRAW_REACH, FADED_ASSETS, + type IPropInstance, type IPropMaterials, type IPropPart, LOD_BANDS, @@ -33,6 +34,8 @@ interface IStreamOptions { readonly follow: IWorldCellsFollow; readonly size: number; readonly horizonDistance?: number; + readonly castShadows?: boolean; + readonly bundles?: boolean; readonly assets?: IAssetLoader; readonly whileCurrent: () => boolean; readonly invalidateShadows?: (region?: IShadowRegion) => void; @@ -98,13 +101,7 @@ export async function createStreamedProps(options: IStreamOptions) { return !report.stopped && options.whileCurrent(); } const records: IRecord[] = []; - const byId = new Map< - string, - ReturnType & { - placement: IPlacement; - pose: ReturnType["matrix"]; - } - >(); + const byId = new Map(); const variantIndices = new Map(); const shapes = new Map(); startPhase("grounding", "", options.placements.length); @@ -143,7 +140,13 @@ export async function createStreamedProps(options: IStreamOptions) { reach = 28 + (reach - 28) * (1 - Math.sqrt(seed)); } records.push({ key, placement, pose, reach, ratio, position, quaternion, scale: scalar }); - byId.set(placement.id, { ...pose, pose: pose.matrix, placement }); + byId.set(placement.id, { + ...pose, + pose: pose.matrix, + placement, + geometry: foot.geometry, + material: foot.material ?? options.materials[foot.role], + }); }, { while: options.whileCurrent, @@ -313,6 +316,7 @@ export async function createStreamedProps(options: IStreamOptions) { data: { manifest, placements: placements.buffer }, placementReach, gpuScene: false, + bundles: options.bundles, adaptiveLod: false, preserveAuthoredParts: true, lodHysteresis: LOD_BANDS.hysteresis, @@ -324,7 +328,7 @@ export async function createStreamedProps(options: IStreamOptions) { bytes: Math.max(32, placements.byteLength), }, shadows: { - cast: bucket.endsWith(":true"), + cast: bucket.endsWith(":true") && options.castShadows !== false, castLevels: 3, receive: true, smallCasterMetres: 0.0001, @@ -389,28 +393,6 @@ export async function createStreamedProps(options: IStreamOptions) { byId, stats, compileSlices, - readinessAt(position: { readonly x: number; readonly z: number }, radius = 60) { - const regions = worlds.map((world) => world.readinessAt(position, radius)); - const required = regions.reduce((n, r) => n + r.requiredCells, 0); - const loaded = regions.reduce((n, r) => n + r.loadedCells, 0); - return { - required, - loaded, - failures: regions.reduce((n, r) => n + r.failures, 0), - ready: warmed.size === worlds.length && regions.every((r) => r.ready), - }; - }, - get ready() { - const s = stats(); - return ( - warmed.size === worlds.length && - s.loadsInFlight === 0 && - s.loadsQueued === 0 && - s.admissionBacklog === 0 && - s.pendingPrewarm === 0 && - s.failures === 0 - ); - }, get meshes() { const meshes: InstancedMesh[] = []; for (const world of worlds) diff --git a/examples/strata-terrain-preview/src/render/props.ts b/examples/strata-terrain-preview/src/render/props.ts index 75a6ea545a..9aceccee87 100644 --- a/examples/strata-terrain-preview/src/render/props.ts +++ b/examples/strata-terrain-preview/src/render/props.ts @@ -1,24 +1,18 @@ // The starter props of the Temperate world: three seeded spruce variants, three boulders, grass -// clumps and poppy patches, each instanced through the engine's existing `InstancedBatch`. +// clumps and poppy patches. `propStreaming.ts` hands their draws to `WorldCells`. // // What lives here and what does not: // // - The shapes, their variants, their materials and their placement rule are all this file's. They // are the picture, and the picture is the game's (AGENTS rule 3). -// - Instancing, transform composition and ground contact are not. `InstancedBatch` collapses the -// draws and `GroundSnap` grounds the model, and both are engine mechanisms this file calls. -// -// Every prop keeps the same contract the editor has always relied on: a placement resolves to a -// prepared matrix, that matrix is written into the instanced mesh by index, and the editor can read -// it back and overwrite it. Adding a variant changes the shape, never that contract. -import { GroundSnap, InstancedBatch, addInSlices, createRandom } from "@threenative/core"; +// - GroundSnap measures ground contact; propStreaming.ts delegates instancing and LOD to WorldCells. +import { GroundSnap, createRandom } from "@threenative/core"; import type { IPlacement, IPlacementOverride } from "@threenative/terrain"; import { type BufferGeometry, DoubleSide, Euler, Group, - type InstancedMesh, type Material, type Matrix4, Mesh, @@ -248,7 +242,11 @@ export function buildPropVariants( continue; } variants.set(`scrub:${i}`, [ - { geometry: grassClump((VARIANTS.seed ^ (i * 0x165667b1)) >>> 0), role: "grass", variant: i }, + { + geometry: grassClump((VARIANTS.seed ^ (i * 0x165667b1)) >>> 0, i), + role: "grass", + variant: i, + }, ]); } for (let i = 0; i < VARIANTS.grass; i += 1) { @@ -259,7 +257,7 @@ export function buildPropVariants( `grass:${i}`, ready ?? [ { - geometry: grassClump((VARIANTS.seed ^ (i * 0xc2b2ae35)) >>> 0), + geometry: grassClump((VARIANTS.seed ^ (i * 0xc2b2ae35)) >>> 0, i), role: "grass", variant: i, }, @@ -342,8 +340,8 @@ export type PropGroundQuery = ( position: [number, number, number], ) => { height: number | null; offset: number }; export interface IPropInstance { - mesh: InstancedMesh; - index: number; + material: Material; + onTransform?: (transform: IPlacementOverride | undefined) => void; placement: IPlacement; pose: Matrix4; geometry: BufferGeometry; @@ -351,8 +349,6 @@ export interface IPropInstance { clearance: number | null; cragBaseClearance?: number; cragRingSamples?: number; - /** Every draw of this placement, so a transform write reaches all of them. */ - parts: { mesh: InstancedMesh; index: number }[]; } export function preparePose( geometry: BufferGeometry, @@ -418,6 +414,22 @@ export function preparePose( : BOULDER_BURIAL) * height; } } + // Bury the flat footprint on the downhill side too; a centre-only snap leaves a visible lip. + if (grounding && !transform && ["boulder", "riverrock", "scree"].includes(placement.asset)) { + const base = geometry.boundingBox?.min.y; + const positions = geometry.getAttribute("position"); + const point = new Vector3(); + let gap = 0; + model.updateMatrix(); + for (let i = 0; i < positions.count; i++) { + if (Math.abs(positions.getY(i) - (base ?? Number.POSITIVE_INFINITY)) > 1e-5) continue; + point.fromBufferAttribute(positions, i).applyMatrix4(model.matrix); + const height = groundAt(placement, point.toArray()).height; + if (height === null) throw new Error(`Missing ground under rock '${placement.id}'`); + gap = Math.max(gap, point.y - height + 0.08); + } + model.position.y -= gap; + } let cragBaseClearance: number | undefined; let cragRingSamples: number | undefined; if ( @@ -481,30 +493,23 @@ export function preparePropTransform( transform: IPlacementOverride | undefined, groundAt: PropGroundQuery, ) { - const { material } = instance.mesh; - if (Array.isArray(material)) - throw new Error(`Prop '${instance.placement.id}' has a multi-material draw`); - return preparePose(instance.geometry, material, instance.placement, transform, groundAt); + return { + ...preparePose(instance.geometry, instance.material, instance.placement, transform, groundAt), + transform, + }; } -let poseVersion = 0; - export function writePropTransform( instance: IPropInstance, prepared: ReturnType, ): void { + const changed = + !instance.pose.equals(prepared.matrix) || instance.grounding !== prepared.grounding; instance.pose.copy(prepared.matrix); - poseVersion++; - // A spruce is a trunk draw and a crown draw, so a transform write has to reach both or the tree - // comes apart when the editor drags it. - for (const part of instance.parts) { - part.mesh.setMatrixAt(part.index, prepared.matrix); - part.mesh.instanceMatrix.needsUpdate = true; - part.mesh.computeBoundingSphere(); - } instance.grounding = prepared.grounding; instance.clearance = prepared.clearance; instance.cragBaseClearance = prepared.cragBaseClearance; instance.cragRingSamples = prepared.cragRingSamples; + if (changed) instance.onTransform?.(prepared.transform); } export function readPropTransform(instance: IPropInstance): IPlacementOverride { const matrix = instance.pose; @@ -597,350 +602,3 @@ export function flatPropMaterials(): IPropMaterials & { dispose: () => void } { }, }; } - -/** One variant's placements, their poses, and the draws each detail level of it owns. */ -interface IVariantGroup { - readonly entries: { instance: IPropInstance; pose: Matrix4 }[]; - /** This variant's `asset:variant` key, which a per-variant draw reach is named by. */ - readonly key: string; - /** One mesh per level per role, so a level is one draw and the matrix write reaches all of them. */ - readonly levels: Map; - /** Which level each placement drew last frame. The hysteresis reads it and writes it. */ - readonly state: Uint8Array; - readonly asset: string; - readonly members: number[][]; - readonly slots: Int32Array; -} - -/** - * Which detail level a placement draws at, and when it is allowed to change its mind. - * - * A step down happens at the band for the level it is leaving, and a step back up only once the - * camera has come a fifth further inside it. Without that slack a camera sitting on a boundary - * assigns a different level to the same tree on alternate frames, which reads as the tree - * flickering rather than as a change of detail. - * - * The bands are indexed by level rather than picked from a pair, because a variant may have two - * levels or three: the pine has a far cross-card as well as a mid mesh, and a two-band ladder - * would leave the third level unreachable or reachable at the wrong distance. - */ -export function levelFor(distance: number, current: number, levelCount: number): number { - let level = current; - const bands = [LOD_BANDS.mid, LOD_BANDS.far]; - while (level + 1 < levelCount && distance > (bands[level] ?? LOD_BANDS.far)) level++; - while (level > 0 && distance < (bands[level - 1] ?? LOD_BANDS.mid) * (1 - LOD_BANDS.hysteresis)) - level--; - return level; -} - -/** - * Place every prop and build one instanced mesh per (asset, variant, level, role). - * - * The grouping is what bounds the draw count: a forest of 200 spruces across three variants is six - * draws, not six hundred, because every copy of a variant shares one geometry and one material and - * differs only by its matrix. A variant with more than one detail level owns one mesh per level per - * role, and {@link setPropLevels} refills them from the camera each frame; a variant with one never - * touches its matrices again. - */ -function* propWork( - placements: readonly IPlacement[], - groundAt: PropGroundQuery, - parts: Map, - materials: IPropMaterials, -) { - const object = new Group(); - const meshes: InstancedMesh[] = []; - const byId = new Map(); - - // Group by variant first, then place once per group: the poses a group shares are the same - // matrices written into every one of its draws. - const groups = new Map(); - for (const placement of placements) { - const key = `${placement.asset}:${variantFor(placement, placement.asset)}`; - const chosen = parts.get(key); - if (!chosen) throw new Error(`Prop asset '${key}' was never built`); - // Grounding measures the prop's own lowest point, so it measures the part that reaches the - // ground: the trunk of a spruce, not its crown, and the body of a boulder. - const foot = chosen.find((part) => part.role === "bark" || part.role === "stone") ?? chosen[0]; - if (!foot) throw new Error(`Prop asset '${key}' has no parts`); - const pose = preparePose( - foot.geometry, - materials[foot.role], - placement, - placement.transform, - groundAt, - ); - const entries = groups.get(key) ?? []; - entries.push({ - instance: { - clearance: pose.clearance, - cragBaseClearance: pose.cragBaseClearance, - cragRingSamples: pose.cragRingSamples, - grounding: pose.grounding, - index: 0, - // Filled in below once this group's meshes exist; the trunk draw is the one the editor - // picks and measures against. - mesh: undefined as unknown as InstancedMesh, - parts: [], - placement, - pose: pose.matrix, - geometry: foot.geometry, - }, - pose: pose.matrix, - }); - groups.set(key, entries); - yield; - } - - function dispose(): void { - for (const mesh of meshes) mesh.dispose(); - object.clear(); - byId.clear(); - groups.clear(); - banded.length = 0; - } - /** Every variant with more than one detail level, which is the only thing that refills. */ - const banded: IVariantGroup[] = []; - let completed = false; - try { - for (const [key, entries] of groups) { - const chosen = parts.get(key); - if (!chosen) throw new Error(`Prop asset '${key}' was never built`); - // Which role the editor treats as the prop's body: the part that touches the ground, because - // that is the one whose bounds centre the selection gizmo on the tree rather than on its leaves. - const bodyRole = chosen.some((part) => part.role === "bark") - ? "bark" - : chosen.some((part) => part.role === "stone") - ? "stone" - : chosen[0]?.role; - const levels = [...new Set(chosen.map((part) => part.level ?? 0))].sort((a, b) => a - b); - const byLevel = new Map(); - for (const level of levels) { - const levelParts = chosen.filter((part) => (part.level ?? 0) === level); - for (const part of levelParts) { - const batch = new InstancedBatch({ - geometry: part.geometry, - // A pack species wears the material it was authored with; everything else wears the - // starter's surface for its role, which is what the lit surfaces later swap in. - material: part.material ?? materials[part.role], - }); - for (const entry of entries) batch.add(entry.pose); - const mesh = batch.build({ - name: `props:${key}:${part.role}`, - parent: object, - // Crowns and bark cast; grass does too, because its own shadow is most of its depth. The - // poppy's petals and stems do not, at five centimetres they only cost the shadow pass — - // and neither does the undergrowth that grew under the canopy, for the same reason one - // order of magnitude up: a thousand seedlings and shrubs drawn a second time into a - // shadow map is the single largest thing the pass could be asked to do, and none of it - // reaches the ground the shadow is cast on. - castShadow: - part.role !== "petal" && - part.role !== "stem" && - !NO_SHADOW_ASSETS.has(key.split(":")[0] ?? ""), - receiveShadow: true, - }); - if (!mesh) throw new Error(`Empty prop batch '${key}:${part.role}'`); - mesh.userData.placementIds = entries.map((entry) => entry.instance.placement.id); - // Recorded rather than inferred later: the surface swap has to leave a pack species alone, - // and "does the role name appear in the lit set" is not the same question. - mesh.userData.ownMaterial = part.material !== undefined; - mesh.userData.body = part.role === bodyRole; - meshes.push(mesh); - const list = byLevel.get(level) ?? []; - list.push(mesh); - byLevel.set(level, list); - entries.forEach((entry, index) => { - entry.instance.parts.push({ index, mesh }); - if (part.role === bodyRole) { - entry.instance.mesh = mesh; - entry.instance.index = index; - } - }); - } - } - // Every level's mesh was filled with every placement at build time, so the bounding sphere - // `InstancedBatch` computed is already the union over the levels — the same bound, correct - // for whichever subset a frame happens to draw, and computed once. - banded.push({ - asset: key.split(":")[0] ?? "", - key, - entries, - levels: byLevel, - state: new Uint8Array(entries.length), - members: levels.map((level) => (level === 0 ? entries.map((_, i) => i) : [])), - slots: Int32Array.from(entries.map((_, i) => i)), - }); - for (const entry of entries) { - if (entry.instance.mesh === undefined) - throw new Error(`Prop '${entry.instance.placement.id}' built no body draw`); - entry.instance.parts = (byLevel.get(0) ?? []).map((mesh) => ({ - mesh, - index: entry.instance.index, - })); - const body = byLevel.get(0)?.find((mesh) => mesh.userData.body); - if (body) entry.instance.mesh = body; - byId.set(entry.instance.placement.id, entry.instance); - } - for (const [level, list] of byLevel) - for (const mesh of list) { - mesh.count = level === 0 ? entries.length : 0; - if (level !== 0) mesh.userData.placementIds = []; - } - yield; - } - const levelCount = new Map(); - - // Swap-delete only band crossings. Stable members retain their GPU buffer slot and editor - // handle; initial bounds cover every possible member, so moving the eye never rebuilds them. - const seenFrom = new Vector3(Number.NaN, 0, 0); - let seenPoseVersion = poseVersion; - const setLevels = (camera: Vector3): void => { - // The existing 0.75 m camera slack and authored hysteresis remain unchanged. - const edited = seenPoseVersion !== poseVersion; - if (!edited && seenFrom.distanceToSquared(camera) < 0.75 ** 2) return; - seenFrom.copy(camera); - seenPoseVersion = poseVersion; - const cx = camera.x; - const cy = camera.y; - const cz = camera.z; - for (const group of banded) { - const levels = levelCount.get(group) ?? 0; - const dirty = new Set(); - // Per group, so the walk below is three reads and arithmetic. `entries.entries()` allocated a - // pair per placement and the fade test allocated a literal array per placement, which on a - // forest of this size is half a million short-lived arrays on every camera move. - const poses = group.entries; - const reach = VARIANT_REACH[group.key] ?? DRAW_REACH[group.asset]; - const thins = reach !== undefined && FADED_ASSETS.has(group.asset); - const fadeEnd = reach ?? 0; - const bindSlot = (index: number, level: number, slot: number): void => { - const entry = group.entries[index]; - if (!entry) throw new Error(`Missing prop slot '${group.key}:${index}'`); - entry.instance.parts = []; - for (const mesh of group.levels.get(level) ?? []) { - mesh.setMatrixAt(slot, entry.pose); - mesh.userData.placementIds[slot] = entry.instance.placement.id; - entry.instance.parts.push({ mesh, index: slot }); - if (mesh.userData.body) { - entry.instance.mesh = mesh; - entry.instance.index = slot; - } - dirty.add(mesh); - } - group.slots[index] = slot; - }; - for (let index = 0; index < poses.length; index++) { - // The pose's own translation rather than `setFromMatrixPosition`: a Matrix4 decompose per - // placement is what made this the most expensive function in a walk. - const entry = poses[index]; - if (!entry) throw new Error(`Missing prop placement '${group.key}:${index}'`); - const elements = entry.pose.elements; - const dx = elements[12] - cx; - const dy = elements[13] - cy; - const dz = elements[14] - cz; - const distance = Math.sqrt(dx * dx + dy * dy + dz * dz); - let visible = reach === undefined || distance <= reach; - if (visible && thins) { - const fade = Math.max(0, (distance - 28) / (fadeEnd - 28)); - const seed = (Math.imul(index + 1, 2654435761) >>> 0) / 4294967296; - visible = seed <= (1 - fade) ** 2; - } - const previous = group.state[index] ?? 0; - const level = levelFor(distance, previous, levels); - const slot = group.slots[index] ?? -1; - if (slot >= 0 === visible && (!visible || previous === level)) continue; - if (slot >= 0) { - const members = group.members[previous]; - if (!members) throw new Error(`Missing prop band '${group.key}:${previous}'`); - const last = members.pop(); - if (last !== undefined && last !== index) { - members[slot] = last; - bindSlot(last, previous, slot); - } - for (const mesh of group.levels.get(previous) ?? []) { - mesh.userData.placementIds.length = members.length; - dirty.add(mesh); - } - } - entry.instance.parts = []; - group.slots[index] = -1; - if (visible) { - const members = group.members[level]; - if (!members) throw new Error(`Missing prop band '${group.key}:${level}'`); - bindSlot(index, level, members.length); - members.push(index); - group.state[index] = level; - } - } - for (const [level, list] of group.levels) { - for (const mesh of list) { - mesh.count = group.members[level]?.length ?? 0; - if (dirty.has(mesh)) mesh.instanceMatrix.needsUpdate = true; - // The build's sphere already bounds ALL placements, not just this band's subset. - // A transform edit may extend it; camera movement cannot. - if (edited) { - const sphere = mesh.boundingSphere; - if (sphere) - for (const entry of group.entries) { - if (mesh.geometry.boundingSphere === null) mesh.geometry.computeBoundingSphere(); - const local = mesh.geometry.boundingSphere?.clone().applyMatrix4(entry.pose); - if (local) sphere.union(local); - } - } - } - } - } - }; - for (const group of banded) levelCount.set(group, group.levels.size); - completed = true; - return { object, meshes, byId, dispose, setLevels }; - } finally { - if (!completed) dispose(); - } -} - -export function createProps( - placements: readonly IPlacement[], - groundAt: PropGroundQuery, - parts: Map, - materials: IPropMaterials, -) { - const work = propWork(placements, groundAt, parts, materials); - for (;;) { - const step = work.next(); - if (step.done) return step.value; - } -} - -export async function createPropsInSlices( - placements: readonly IPlacement[], - groundAt: PropGroundQuery, - parts: Map, - materials: IPropMaterials, - whileCurrent?: () => boolean, -) { - const work = propWork(placements, groundAt, parts, materials); - const groups = new Set( - placements.map((placement) => `${placement.asset}:${variantFor(placement, placement.asset)}`), - ); - try { - const report = await addInSlices( - Array.from({ length: placements.length + groups.size }), - () => { - work.next(); - }, - { while: whileCurrent }, - ); - if (report.stopped) { - work.return(undefined as never); - return undefined; - } - const result = work.next(); - if (!result.done) throw new Error("Prop build did not finish its scheduled work"); - return result.value; - } catch (error) { - work.return(undefined as never); - throw error; - } -} diff --git a/examples/strata-terrain-preview/src/render/scatter.ts b/examples/strata-terrain-preview/src/render/scatter.ts index 30b8631fae..4bc13dfbfc 100644 --- a/examples/strata-terrain-preview/src/render/scatter.ts +++ b/examples/strata-terrain-preview/src/render/scatter.ts @@ -23,9 +23,8 @@ const tundraCover = (x: number, z: number): number => tundraNoise.noise(x * 0.11, 19, z * 0.11) * 0.15, ); -/** A field the placement rule reads: the world's own baked colours plus its sampled geometry. */ +/** A field the placement rule reads: the world's baked transport and sampled geometry. */ export interface IPlacementField { - readonly colors: readonly number[]; readonly erosion?: IBakedWorld["erosion"]; readonly field: Heightfield; readonly resolution: number; diff --git a/examples/strata-terrain-preview/src/render/selection.ts b/examples/strata-terrain-preview/src/render/selection.ts index 56413d4bd3..bf85bcb826 100644 --- a/examples/strata-terrain-preview/src/render/selection.ts +++ b/examples/strata-terrain-preview/src/render/selection.ts @@ -1,26 +1,16 @@ import type { ICtx } from "@threenative/core"; +import type { IPlacementOverride, Layer } from "@threenative/terrain"; import { - type IPlacementOverride, - type Layer, - validatePlacementOverrides, -} from "@threenative/terrain"; -import type { TerrainEditorController } from "@threenative/terrain/editor"; + type TerrainEditorController, + createEditorSelection, + editorPlacementCenter, + editorPointToClient, + landformPose, +} from "@threenative/terrain/editor"; import type { IEditorSnapshot } from "@threenative/terrain/editor/server"; -import { - BufferGeometry, - Euler, - Line, - LineBasicMaterial, - Mesh, - Object3D, - PerspectiveCamera, - Quaternion, - Vector2, - Vector3, -} from "three"; +import { BufferGeometry, Line, LineBasicMaterial, Mesh, Object3D, Vector3 } from "three"; import type { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { TransformControls } from "three/addons/controls/TransformControls.js"; -import { landformPose, transformedLandform } from "./landforms.js"; import { type IPropInstance, type PropGroundQuery, @@ -64,7 +54,7 @@ export function createPropSelection(

Y translation disables grounding. Escape cancels the current drag. XYZ rotations apply to props.

-

`; +

`; const style = document.createElement("style"); style.textContent = "#placement-inspector [hidden]{display:none!important}body[data-terrain-tool=select] .brush-section>.slider-label,body[data-terrain-tool=select] .brush-section>input[type=range],body[data-terrain-tool=select] .brush-section>.advanced{display:none}.right-sidebar[data-selecting=true]>.inspector:not(#placement-inspector){display:none}.right-sidebar[data-selecting=true]>.layer-list{flex:none;max-height:170px}#placement-inspector{overflow-y:auto;min-height:0;flex:1}#placement-inspector h3{margin-bottom:12px}#placement-inspector fieldset{border:0;padding:0;margin:0 0 12px}#placement-inspector legend{font-size:10px;margin-bottom:6px}.transform-vector{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.transform-vector label{font-size:10px}.transform-vector input{width:100%}#placement-status{margin-top:10px;overflow-wrap:anywhere}"; @@ -86,39 +76,48 @@ export function createPropSelection( const groundingLabel = control("#placement-grounding-label"); const fields = [...form.querySelectorAll("[data-transform-group]")]; const abort = new AbortController(); - let snapshot: IEditorSnapshot | undefined; - let selected: string | undefined; - let selectedLayer: string | undefined; - let anchor = 0; - let layerDraft: Layer | undefined; - let layerHistory: { before: Layer; after: Layer } | undefined; - let active = false; - let drag: - | { id: string; base: IEditorSnapshot; grounding: boolean; layer?: Layer; anchor: number } - | undefined; - let cancelled = false; - let saving = false; - let draft: { id: string; transform: IPlacementOverride } | undefined; - let history: { id: string; before?: IPlacementOverride; after?: IPlacementOverride } | undefined; + const selection = createEditorSelection({ + gizmo, + proxy, + controller, + surface, + refreshPose, + drawFootprint, + message, + defaultFootprintSize: 120, + grounding: () => grounding.checked, + setSaving: (value) => { + form.inert = value; + }, + selectedChanged: (id, isLayer) => { + list.value = id ? `${isLayer ? "landform" : "placement"}:${id}` : ""; + }, + setNavigation: (value) => { + orbit.enabled = value; + }, + restorePlacement(id, value) { + const instance = instances()?.get(id); + const query = ground(); + if (instance && query) + writePropTransform(instance, preparePropTransform(instance, value, query)); + }, + previewPlacement(id, value) { + const instance = instances()?.get(id); + const query = ground(); + if (!instance || !query) throw new Error("Selected placement is unavailable"); + writePropTransform(instance, preparePropTransform(instance, value, query)); + }, + }); function center(id: string): Vector3 | undefined { const instance = instances()?.get(id); if (!instance) return undefined; const pose = readPropTransform(instance); - instance.mesh.geometry.computeBoundingSphere(); - const point = instance.mesh.geometry.boundingSphere?.center.clone() ?? new Vector3(); - return point - .multiply(new Vector3().fromArray(pose.scale)) - .applyQuaternion(new Quaternion().fromArray(pose.quaternion)) - .add(new Vector3().fromArray(pose.position)); + instance.geometry.computeBoundingSphere(); + const point = instance.geometry.boundingSphere?.center.clone() ?? new Vector3(); + return editorPlacementCenter(point, pose); } function pixel(point: Vector3): [number, number] { - point.project(ctx.camera); - const box = ctx.renderer.domElement.getBoundingClientRect(); - const value = new Vector2( - ((point.x + 1) * box.width) / 2 + box.left, - ((1 - point.y) * box.height) / 2 + box.top, - ); - return [value.x, value.y]; + return editorPointToClient(point, ctx.camera, ctx.renderer.domElement.getBoundingClientRect()); } /** One framing path: the validated focus operation the camera panel and a controller share. */ function focus(target: { kind: "prop" | "region"; id: string }): { diagnostic: string | null } { @@ -128,7 +127,7 @@ export function createPropSelection( status.textContent = text; } function drawFootprint(layer: Layer): void { - const pose = landformPose(layer, snapshot?.document.recipe.config.size ?? 0); + const pose = landformPose(layer, selection.snapshot?.document.recipe.config.size ?? 0, 120); if (!pose) return; const vertices = pose.rectangular ? [ @@ -155,12 +154,14 @@ export function createPropSelection( footprint.geometry.dispose(); if (points[0]) points.push(points[0].clone()); footprint.geometry = new BufferGeometry().setFromPoints(points); - footprint.visible = active; + footprint.visible = selection.active; } function refreshPose(): void { - const layer = snapshot?.document.recipe.layers.find((l) => l.id === selectedLayer); + const layer = selection.snapshot?.document.recipe.layers.find( + (l) => l.id === selection.selectedLayer, + ); const landformTransform = layer - ? landformPose(layer, snapshot?.document.recipe.config.size ?? 0) + ? landformPose(layer, selection.snapshot?.document.recipe.config.size ?? 0, 120) : undefined; const landform = !!landformTransform && !!layer; title.textContent = landform ? "Recipe landform" : "Individual placement"; @@ -170,27 +171,10 @@ export function createPropSelection( fields.forEach((input, i) => { input.disabled = landform && (i === 3 || i === 5); }); - gizmo.showX = !landform || gizmo.getMode() !== "rotate"; - gizmo.showZ = !landform || gizmo.getMode() !== "rotate"; if (landform) { form.hidden = false; - anchor = - (surface(landformTransform.position[0], landformTransform.position[2]) ?? 0) - - landformTransform.position[1]; - proxy.position.fromArray(landformTransform.position); - proxy.position.y += anchor; - proxy.quaternion.copy(landformTransform.quaternion); - proxy.scale.fromArray(landformTransform.scale); - proxy.updateMatrixWorld(true); - if (active) gizmo.attach(proxy); - const values = [ - ...landformTransform.position, - 0, - (-2 * Math.atan2(landformTransform.quaternion.y, landformTransform.quaternion.w) * 180) / - Math.PI, - 0, - ...landformTransform.scale, - ]; + const values = selection.applyPose(layer); + if (!values) throw new Error("Missing landform pose"); fields.forEach((input, i) => { const value = values[i]; if (value === undefined) throw new Error("Landform field mismatch"); @@ -203,27 +187,18 @@ export function createPropSelection( } footprint.visible = false; // engine-override: no landform is selected. - const instance = selected ? instances()?.get(selected) : undefined; + const instance = selection.selected ? instances()?.get(selection.selected) : undefined; form.hidden = !instance; if (!instance) { - gizmo.detach(); - clearance.textContent = selected + selection.applyPose(); + clearance.textContent = selection.selected ? "Placement no longer evaluates; override retained." : "Select a prop."; return; } const pose = readPropTransform(instance); - proxy.position.fromArray(pose.position); - proxy.quaternion.fromArray(pose.quaternion); - proxy.scale.fromArray(pose.scale); - proxy.updateMatrixWorld(true); - if (active) gizmo.attach(proxy); - const angles = new Euler().setFromQuaternion(proxy.quaternion, "XYZ"); - const values = [ - ...pose.position, - ...[angles.x, angles.y, angles.z].map((v) => (v * 180) / Math.PI), - ...pose.scale, - ]; + const values = selection.applyPose(undefined, pose); + if (!values) throw new Error("Missing placement pose"); fields.forEach((input, index) => { const value = values[index]; if (value === undefined) throw new Error("Placement transform field mismatch"); @@ -232,159 +207,6 @@ export function createPropSelection( grounding.checked = pose.grounding; clearance.textContent = `Measured clearance: ${instance.clearance === null ? "unknown (outside terrain)" : `${instance.clearance.toFixed(4)} m`} · grounding ${pose.grounding ? "on" : "overridden"}`; } - function restore(): void { - if (selectedLayer) { - refreshPose(); - return; - } - const instance = selected ? instances()?.get(selected) : undefined; - const query = ground(); - if (instance && query) - writePropTransform( - instance, - preparePropTransform( - instance, - snapshot?.document.placementOverrides?.[instance.placement.id], - query, - ), - ); - refreshPose(); - } - function preview(id: string, transform: IPlacementOverride): void { - const value = validatePlacementOverrides({ [id]: transform })[id]; - const instance = instances()?.get(id); - const query = ground(); - if (!value || !instance || !query) throw new Error("Selected placement is unavailable"); - writePropTransform(instance, preparePropTransform(instance, value, query)); - draft = { id, transform: value }; - refreshPose(); - } - async function save( - id: string, - value: IPlacementOverride | undefined, - base = snapshot, - remember = true, - ): Promise { - if (saving || !base) { - message("Wait for the current transform save."); - return; - } - saving = true; - form.inert = true; - gizmo.enabled = false; - const overrides = { ...base.document.placementOverrides }; - if (value) overrides[id] = value; - else delete overrides[id]; - try { - const next = await controller.commit({ - baseRevision: base.revision, - document: { ...base.document, placementOverrides: overrides }, - }); - if (remember) { - history = { id, before: base.document.placementOverrides?.[id], after: value }; - layerHistory = undefined; - } - if (!snapshot || snapshot.revision === base.revision || snapshot.revision === next.revision) - snapshot = next; - draft = undefined; - restore(); - message(`Saved ${next.revision.slice(0, 8)} · one transform transaction`); - } catch (error) { - message( - `${error instanceof Error ? error.message : String(error)} · draft retained; apply again to rebase explicitly.`, - ); - try { - snapshot = await controller.snapshot(); - restore(); - } catch { - message("Connection interrupted · transform draft retained."); - } - } finally { - saving = false; - form.inert = false; - gizmo.enabled = active; - } - } - async function saveLayer(layer: Layer, base = snapshot, remember = true): Promise { - if (saving || !base) return; - saving = true; - form.inert = true; - gizmo.enabled = false; - try { - const before = base.document.recipe.layers.find((l) => l.id === layer.id); - const next = await controller.commit({ - baseRevision: base.revision, - commands: [{ op: "upsert", layer }], - }); - if (remember && before) { - layerHistory = { before, after: layer }; - history = undefined; - } - if (!snapshot || snapshot.revision === base.revision || snapshot.revision === next.revision) - snapshot = next; - layerDraft = undefined; - message(`Saved ${next.revision.slice(0, 8)} · one landform transaction`); - } catch (error) { - message(`${String(error)} · landform draft retained; apply again to rebase explicitly.`); - snapshot = await controller.snapshot(); - } finally { - saving = false; - form.inert = false; - gizmo.enabled = active; - refreshPose(); - } - } - function select(id: string | undefined, isLayer = false): void { - if (gizmo.dragging || saving) return; - if (selected !== id) { - restore(); - draft = undefined; - } - selectedLayer = isLayer ? id : undefined; - selected = selectedLayer ? undefined : id; - layerDraft = undefined; - list.value = id ? `${isLayer ? "landform" : "placement"}:${id}` : ""; - refreshPose(); - } - async function undo(): Promise { - if (saving) return; - if (selectedLayer && layerHistory) { - const entry = layerHistory; - const current = await controller.snapshot(); - const layer = current.document.recipe.layers.find((l) => l.id === entry.after.id); - const keys = ["at", "radius", "size", "rotation", "scale", "offset"]; - const after = entry.after.params as Record; - const before = entry.before.params as Record; - const params = { ...layer?.params } as Record; - if ( - !layer || - layer.type !== entry.after.type || - keys.some((key) => JSON.stringify(params[key]) !== JSON.stringify(after[key])) - ) { - message("Undo conflict: another actor changed this landform; their edit is retained."); - return; - } - for (const key of keys) { - if (before[key] === undefined) delete params[key]; - else params[key] = before[key]; - } - await saveLayer({ ...layer, params } as Layer, current, false); - if (!layerDraft) layerHistory = undefined; - return; - } - if (!history) return; - const entry = history; - const current = await controller.snapshot(); - if ( - JSON.stringify(current.document.placementOverrides?.[entry.id]) !== - JSON.stringify(entry.after) - ) { - message("Undo conflict: another actor changed this placement; their edit is retained."); - return; - } - await save(entry.id, entry.before, current, false); - if (!draft) history = undefined; - } panel.addEventListener( "click", (event) => { @@ -396,12 +218,16 @@ export function createPropSelection( for (const button of panel.querySelectorAll("[data-transform-mode]")) button.classList.toggle("active", button.dataset.transformMode === mode); } - if (target?.id === "placement-reset" && selected) void save(selected, undefined); + if (target?.id === "placement-reset" && selection.selected) + void selection.save(selection.selected, undefined); if (target?.id === "placement-focus") { - if (selectedLayer) message(focus({ kind: "region", id: selectedLayer }).diagnostic ?? ""); - else if (selected) message(focus({ kind: "prop", id: selected }).diagnostic ?? ""); + if (selection.selectedLayer) + message(focus({ kind: "region", id: selection.selectedLayer }).diagnostic ?? ""); + else if (selection.selected) + message(focus({ kind: "prop", id: selection.selected }).diagnostic ?? ""); } - if (target?.id === "placement-undo") void undo().catch((error) => message(String(error))); + if (target?.id === "placement-undo") + void selection.undo().catch((error) => message(String(error))); }, { signal: abort.signal }, ); @@ -409,7 +235,7 @@ export function createPropSelection( "change", () => { const isLayer = list.value.startsWith("landform:"); - select(list.value ? list.value.slice(isLayer ? 9 : 10) : undefined, isLayer); + selection.select(list.value ? list.value.slice(isLayer ? 9 : 10) : undefined, isLayer); }, { signal: abort.signal }, ); @@ -417,201 +243,61 @@ export function createPropSelection( "submit", (event) => { event.preventDefault(); - if (selectedLayer && snapshot) { - const layer = snapshot.document.recipe.layers.find((l) => l.id === selectedLayer); - if (!layer) return; - try { - const values = fields.map((field) => Number(field.value)); - const [ - x = Number.NaN, - y = Number.NaN, - z = Number.NaN, - , - ry = Number.NaN, - , - sx = Number.NaN, - sy = Number.NaN, - sz = Number.NaN, - ] = values; - const position = new Vector3(x, y + anchor, z); - const rotation = new Quaternion().setFromAxisAngle( - new Vector3(0, 1, 0), - (-ry * Math.PI) / 180, - ); - const next = layerDraft - ? ({ - ...layer, - params: { - ...layer.params, - ...Object.fromEntries( - Object.entries(layerDraft.params).filter(([key]) => - ["at", "radius", "size", "rotation", "scale", "offset"].includes(key), - ), - ), - }, - } as Layer) - : transformedLandform(layer, position, rotation, new Vector3(sx, sy, sz), anchor); - layerDraft = next; - void saveLayer(next); - } catch (error) { - message(String(error)); - } - return; - } - if (!selected) return; - try { - const values = fields.map((field) => Number(field.value)); - const position = values.slice(0, 3) as IPlacementOverride["position"]; - const rotation = values.slice(3, 6).map((v) => (v * Math.PI) / 180); - const quaternion = new Quaternion() - .setFromEuler(new Euler(rotation[0], rotation[1], rotation[2], "XYZ")) - .toArray(); - const scale = values.slice(6, 9) as IPlacementOverride["scale"]; - const value = - draft?.id === selected - ? draft.transform - : { position, quaternion, scale, grounding: grounding.checked }; - preview(selected, value); - void save(selected, value); - } catch (error) { - message(String(error)); - } + selection.applyFields( + fields.map((field) => Number(field.value)), + grounding.checked, + ); }, { signal: abort.signal }, ); form.addEventListener( "input", () => { - draft = undefined; - layerDraft = undefined; + selection.clearDrafts(); }, { signal: abort.signal }, ); - gizmo.addEventListener("dragging-changed", ({ value }) => { - orbit.enabled = !value; - }); - gizmo.addEventListener("mouseDown", () => { - const id = selectedLayer ?? selected; - if (!id || !snapshot || saving) { - cancelled = true; - gizmo.pointerUp(null); - return; - } - if (selectedLayer && gizmo.getMode() === "rotate" && gizmo.axis !== "Y") { - cancelled = true; - gizmo.pointerUp(null); - message("Only Y rotation is supported: a heightfield cannot overhang."); - return; - } - drag = { - id, - base: snapshot, - layer: snapshot.document.recipe.layers.find((l) => l.id === selectedLayer), - anchor, - grounding: - grounding.checked && !(gizmo.getMode() === "translate" && gizmo.axis?.includes("Y")), - }; - cancelled = false; - }); - gizmo.addEventListener("objectChange", () => { - if (!drag || cancelled) return; - try { - if (drag.layer) { - layerDraft = transformedLandform( - drag.layer, - proxy.position, - proxy.quaternion, - proxy.scale, - drag.anchor, - ); - drawFootprint(layerDraft); - return; - } - preview(drag.id, { - position: proxy.position.toArray(), - quaternion: proxy.quaternion.clone().normalize().toArray(), - scale: proxy.scale.toArray(), - grounding: drag.grounding, - }); - } catch (error) { - message(String(error)); - cancelled = true; - restore(); - } - }); - gizmo.addEventListener("mouseUp", () => { - const gesture = drag; - drag = undefined; - if (!cancelled && gesture?.layer && layerDraft) { - void saveLayer(layerDraft, gesture.base); - return; - } - if (cancelled || !gesture || !draft) { - restore(); - return; - } - void save(gesture.id, draft.transform, gesture.base); - }); ctx.renderer.domElement.addEventListener( "pointerdown", (event) => { - if (!active || event.button !== 0 || event.altKey || gizmo.axis || gizmo.dragging) return; - select(pick(event.clientX, event.clientY)); + if (!selection.canPick(event)) return; + selection.select(pick(event.clientX, event.clientY)); }, { signal: abort.signal }, ); window.addEventListener( "keydown", (event) => { - if (!active || ["INPUT", "TEXTAREA", "SELECT"].includes((event.target as Element).tagName)) + if ( + !selection.active || + (event.key !== "Escape" && + ["INPUT", "TEXTAREA", "SELECT"].includes((event.target as Element).tagName)) + ) return; if (event.key === "Escape") { - cancelled = true; - draft = undefined; - layerDraft = undefined; - gizmo.reset(); - restore(); - message("Drag cancelled · no transaction"); + selection.cancel(); } if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "z") { event.preventDefault(); - void undo().catch((error) => message(String(error))); + void selection.undo().catch((error) => message(String(error))); } }, { signal: abort.signal }, ); return { - selectLayer(id: string): void { - const layer = snapshot?.document.recipe.layers.find((entry) => entry.id === id); - if (active && layer && landformPose(layer, snapshot?.document.recipe.config.size ?? 0)) - select(id, true); - }, + selectLayer: selection.selectLayer, setActive(enabled: boolean): void { - active = enabled; sidebar.dataset.selecting = String(enabled); panel.style.display = enabled ? "block" : "none"; - gizmo.enabled = enabled; - if (!enabled) { - cancelled = true; - draft = undefined; - layerDraft = undefined; - gizmo.pointerUp(null); - footprint.visible = false; // engine-override: selection tooling is inactive. - gizmo.reset(); - restore(); - gizmo.detach(); - orbit.enabled = true; - } else refreshPose(); - }, - sync(next: IEditorSnapshot, matchesPreview: boolean): void { - snapshot = next; - if (matchesPreview && !drag && !saving) restore(); + if (!enabled) footprint.visible = false; // engine-override: selection tooling is inactive. + selection.setActive(enabled); }, + sync: selection.sync, refresh(): void { const entries = [...(instances()?.values() ?? [])]; list.replaceChildren( new Option("Select a placement or landform", ""), - ...(snapshot?.document.recipe.layers + ...(selection.snapshot?.document.recipe.layers .filter((layer) => ["stamp", "paste", "heightmap"].includes(layer.type)) .map( (layer) => new Option(`Landform · ${layer.name ?? layer.id}`, `landform:${layer.id}`), @@ -624,13 +310,13 @@ export function createPropSelection( ), ), ); - list.value = selectedLayer - ? `landform:${selectedLayer}` - : selected - ? `placement:${selected}` + list.value = selection.selectedLayer + ? `landform:${selection.selectedLayer}` + : selection.selected + ? `placement:${selection.selected}` : ""; orphans.replaceChildren(); - for (const id of Object.keys(snapshot?.document.placementOverrides ?? {})) { + for (const id of Object.keys(selection.snapshot?.document.placementOverrides ?? {})) { if (instances()?.has(id)) continue; const row = document.createElement("div"); const label = document.createElement("p"); @@ -638,46 +324,30 @@ export function createPropSelection( const remove = document.createElement("button"); remove.textContent = "Remove override"; remove.onclick = () => { - void save(id, undefined); + void selection.save(id, undefined); }; const reassign = document.createElement("button"); reassign.textContent = "Reassign to selected"; - reassign.onclick = async () => { - if (!selected || !snapshot || saving) return; - const current = snapshot; - const value = current.document.placementOverrides?.[id]; - if (!value || current.document.placementOverrides?.[selected]) { - message("Choose a placement without an existing override."); - return; - } - const overrides = { ...current.document.placementOverrides, [selected]: value }; - delete overrides[id]; - try { - await controller.commit({ - baseRevision: current.revision, - document: { ...current.document, placementOverrides: overrides }, - }); - } catch (error) { - message(String(error)); - } + reassign.onclick = () => { + void selection.reassign(id); }; row.append(label, remove, reassign); orphans.append(row); } - if (!drag && !saving) refreshPose(); + if (!selection.drag && !selection.saving) refreshPose(); }, inspect() { return { - selected, - selectedLayer, - landformDraft: layerDraft, + selected: selection.selected, + selectedLayer: selection.selectedLayer, + landformDraft: selection.layerDraft, footprintVisible: footprint.visible, rotationAxes: { x: gizmo.showX, y: gizmo.showY, z: gizmo.showZ }, mode: gizmo.getMode(), dragging: gizmo.dragging, orbitEnabled: orbit.enabled, - draft, - saving, + draft: selection.draft, + saving: selection.saving, axis: gizmo.axis, }; }, @@ -710,6 +380,7 @@ export function createPropSelection( }, dispose(): void { abort.abort(); + selection.dispose(); gizmo.dispose(); ctx.scene.remove(proxy, gizmo.getHelper(), footprint); footprint.geometry.dispose(); diff --git a/examples/strata-terrain-preview/src/render/sky.ts b/examples/strata-terrain-preview/src/render/sky.ts index 810be0966f..57f1142352 100644 --- a/examples/strata-terrain-preview/src/render/sky.ts +++ b/examples/strata-terrain-preview/src/render/sky.ts @@ -114,13 +114,19 @@ export function createOutdoorSky(camera: Object3D, biome?: IBiome): IOutdoorSky const sun = new DirectionalLight(sunColor, biome?.sun.intensity ?? SUN.intensity); sun.name = "temperate-sun"; sun.castShadow = true; - sun.shadow.normalBias = 0.035; sun.shadow.radius = biome?.sun.shadowRadius ?? BIOMES.forest.sun.shadowRadius; if (omitted.has("sun")) sun.intensity = 0; const farShadows = biome?.world === "alpine" || biome?.world === "desert"; + const mapSize = farShadows ? 4096 : 2048; + // Coastal stones showed a lit rim at their base. One fine texel of normal bias ties the contact + // offset to shadow resolution. Other worlds keep their judged bias until close-ups prove a change. + const tightContacts = biome?.world === "coastal"; + sun.shadow.normalBias = tightContacts ? (2 * rig.shadowExtents[0]) / mapSize : 0.035; const shadows = new VirtualShadowNode(sun, { clipExtents: [...rig.shadowExtents], - mapSize: farShadows ? 4096 : 2048, + mapSize, + // engine-override: footprint-wide slope bias detaches low-relief contacts such as beach stones. + ...(tightContacts ? { receiverPlaneBias: false } : {}), ...(farShadows ? { refreshStep: [0.2, 0.125] } : {}), }); // Fade the outer cascade before its cached window ends, including its refresh trail. diff --git a/examples/strata-terrain-preview/src/render/surfaces.ts b/examples/strata-terrain-preview/src/render/surfaces.ts index f01f80b6cc..5e91788a8c 100644 --- a/examples/strata-terrain-preview/src/render/surfaces.ts +++ b/examples/strata-terrain-preview/src/render/surfaces.ts @@ -50,6 +50,7 @@ export function createSurfaceBindings( inputs: Record, urlOf: (asset: IProjectAsset) => string, gpuFormat: (texture: Texture) => string | undefined = () => undefined, + onChange?: () => void, ) { const bindings = new Map(); for (const [input, texture] of Object.entries(inputs)) @@ -72,6 +73,7 @@ export function createSurfaceBindings( binding.texture.source = binding.original; binding.texture.dispose(); binding.texture.needsUpdate = true; + onChange?.(); binding.assetId = null; binding.sha256 = null; binding.url = null; @@ -97,6 +99,7 @@ export function createSurfaceBindings( binding.texture.colorSpace = wanted === "srgb" ? SRGBColorSpace : NoColorSpace; binding.texture.dispose(); binding.texture.needsUpdate = true; + onChange?.(); if (previous && previous !== url) assets.release("texture", previous); binding.assetId = asset.id; binding.sha256 = asset.sha256; diff --git a/examples/strata-terrain-preview/src/render/terrain.ts b/examples/strata-terrain-preview/src/render/terrain.ts index aef05e8961..c1ae6dac58 100644 --- a/examples/strata-terrain-preview/src/render/terrain.ts +++ b/examples/strata-terrain-preview/src/render/terrain.ts @@ -1,5 +1,5 @@ import type { IAssetLoader } from "@threenative/core"; -import { Heightfield } from "@threenative/core/world"; +import { Heightfield, loadTerrainSplat } from "@threenative/core/world"; import { createSegmentIndex } from "@threenative/terrain"; import { type BufferGeometry, @@ -9,11 +9,13 @@ import { Float32BufferAttribute, HalfFloatType, LinearFilter, + MathUtils, Mesh, MeshStandardMaterial, RGBAFormat, RepeatWrapping, type Texture, + Vector3, } from "three"; import { abs, @@ -43,7 +45,7 @@ import { } from "three/tsl"; import type { Node } from "three/webgpu"; import { MeshStandardNodeMaterial } from "three/webgpu"; -import { GROUND_MAPS, GROUND_TILE, type LayerKey, ROCKFACE_MAPS } from "../world/terrainAssets.js"; +import { GROUND_TILE, type LayerKey, ROCKFACE_MAPS } from "../world/terrainAssets.js"; import { alpineRockColor, alpineRockTap, desertRockColor } from "./biomes.js"; // The ground's look, and every number in it, lives in this game: which surface covers which // height and slope, how many metres one texture tile spans, and how the blend edges break. The @@ -57,7 +59,14 @@ import { alpineRockColor, alpineRockTap, desertRockColor } from "./biomes.js"; // disagree with the geometry. // // Texture budget: six albedos + four normals + curvature + two shadow levels × two = 15/16. -import { BIOMES, type IBiome, biomeWeights } from "./biomes.js"; +import { + BIOMES, + type IBiome, + type IWorldBiome, + biomeWeights, + elevationBand, + measureBiome, +} from "./biomes.js"; import { createHorizonGeometry } from "./horizon.js"; import { loadSkyLight, skyIrradiance } from "./pack.js"; import { @@ -108,17 +117,11 @@ export interface IGroundCurvature { readonly deposits: Node<"float">; } -/** The layers that cover ground, ordered so the heavier surface blends over the base one. */ -const LAYERS: readonly LayerKey[] = ["dirt", "moss", "sand", "rock", "snow"]; - interface ILayerMaps { diffuse: Texture; normal?: Texture; } -/** Where the snow line sits, in metres, and the slope above which it cannot settle. */ -const SNOW = { from: 145, to: 195, sheds: 0.22 }; - /** * The beach band, in metres above the bake's own sea level. * @@ -493,12 +496,17 @@ export function paintCanopy( * The ground material: one lit surface whose colour is a blend of six PBR layers, blended in weight * order outwards from the base surface. */ -export function createGroundMaterial( +export async function createGroundMaterial( data: IBakedWorld, - maps: Partial>, + assets: IAssetLoader, curvature: IGroundCurvature, biome?: IBiome, -): MeshStandardNodeMaterial { +): Promise { + const measured = + biome && "heightBands" in biome + ? (biome as IWorldBiome) + : measureBiome(biome ?? BIOMES.forest, data.heights); + const maps: Partial> = {}; const material = new MeshStandardNodeMaterial({ metalness: 0, roughness: 0.94 }); material.userData.biome = biome?.world ?? "forest"; material.userData.erosion = data.erosion !== undefined; @@ -531,7 +539,11 @@ export function createGroundMaterial( const flow = curvature.flow; const sediment = deposits; const rib = smoothstep(-0.02, -0.45, hollow); - const alpine = smoothstep(38, 66, positionWorld.y.add(breakUp.mul(5))); + const alpine = elevationBand( + measured, + measured.heightBands.alpine, + positionWorld.y.add(breakUp.mul(5)), + ); // No recorded sea level means an inland world, and an inland world has no beach. const sand = data.waterLevel === null @@ -572,14 +584,14 @@ export function createGroundMaterial( // scarp's foot where the ground is steep and the debris has not arrived yet. // Soil holds on ordinary hills; only scarps expose bedrock (34–48 degrees). rock: smoothstep(0.17, 0.33, steep.add(rib.mul(0.04)).add(breakUp.mul(0.025))) - .mul(mix(0.65, 1, smoothstep(12, 70, positionWorld.y))) + .mul(mix(0.65, 1, elevationBand(measured, measured.heightBands.rock))) .mul(oneMinus(smoothstep(0.15, 0.8, hollow).mul(0.25))), - snow: smoothstep(float(SNOW.from), float(SNOW.to), positionWorld.y).mul( - smoothstep(SNOW.sheds, 0.06, steep), + snow: elevationBand(measured, measured.snowBand).mul( + smoothstep(measured.snowFractions[2], 0.06, steep), ), }; - if (biome) Object.assign(weights, biomeWeights(biome, steep, hollow, breakUp)); + Object.assign(weights, biomeWeights(measured, steep, hollow, breakUp)); // Needle litter and crown shade under the placed stands, as a multiplicative floor tint. let forestFloor: Node<"float"> = float(0); // Transport, slope and shelter choose the splat; noise only frays the material's edge. @@ -650,7 +662,6 @@ export function createGroundMaterial( const albedoOf = (key: LayerKey): Node<"vec4"> => { const { diffuse } = layer(key); - held.add(diffuse); const flat = groundLayer(diffuse, key); if (biome?.world === "tundra" && (key === "grass" || key === "dirt" || key === "moss")) { // The same rotated two-scale floor and walls continue across the resident edge. @@ -715,7 +726,10 @@ export function createGroundMaterial( .sub(mx_noise_float(positionWorld.mul(0.05)).mul(0.5)) .abs(), ), - ).mul(smoothstep(-0.1, 0.15, mx_noise_float(positionWorld.mul(0.65)))); + ) + .mul(smoothstep(-0.1, 0.15, mx_noise_float(positionWorld.mul(0.65)))) + // Subpixel wrack fragments read as brown loops from the overview. + .mul(oneMinus(smoothstep(60, 120, positionView.length()))); const sandColor = otherBiome ? blended.rgb : mix(blended.rgb, vec3(dot(blended.rgb, vec3(0.2126, 0.7152, 0.0722))), 0.55).mul( @@ -738,7 +752,6 @@ export function createGroundMaterial( crevice: float(1), tilt: key === "sand" ? sandRipples() : key === "snow" ? microGrain().mul(0.4) : vec3(0), }; - held.add(source); if (key === "rock") { const walls = { crevice: float(1), tilt: stoneRelief(source, biome) }; return { @@ -779,139 +792,177 @@ export function createGroundMaterial( : blended; }; - const grassRelief = reliefOf("grass", 0.38); - let albedo: Node<"vec4"> = vec4(groundTurf(albedoOf("grass").rgb, biome, hollow), 1); - // Three scales of relief on the meadow, not two: a metre of detail normal under the player's feet, - // the tile's own scale at reading distance, and a decimetre of grain so the ground nearest the eye - // is not smooth between the blades. The finest is a noise field rather than a texture, because a - // third sampler on the grass layer is a third of the budget for detail nobody can name. - let normal = grassRelief.tilt - .mul(weights.grass) - .mul(biome?.world === "alpine" ? mix(1, 0.05, smoothstep(45, 130, positionView.length())) : 1) - .add(microGrain().mul(oneMinus(smoothstep(12, 60, positionView.length())))); - if (biome?.world === "desert") - normal = normal.add(sandRipples(vec2(0.53, 0.85)).mul(weights.grass)); - // The crevice term follows the surface the eye is actually looking at, so it is blended by the - // same weights as the colour rather than applied to every layer at once. let snowCover: Node<"float"> = float(0); - for (const key of LAYERS) { - const weight = weights[key]; - // A surface takes the ground over once it *is* most of the ground. Blending every layer by a - // share of the running total instead leaves a beach a third sand, a third grass and a third - // dirt, which is mud with a texture on it. - const surface = albedoOf(key); - const relief = reliefOf(key, key === "rock" ? 0.52 : key === "snow" ? 0.18 : 0.32); - // No displacement maps in these starter sets: normal relief breaks the blend edge, - // while slope, elevation and curvature determine which surface belongs here. - const reliefHeight = relief.crevice.sub(0.8).mul(0.12).add(breakUp.mul(0.045)); - // The photograph's relief and metre-scale breakup cut pockets into the soil/rock boundary. - const rockHeight = dot(surface.rgb, vec3(0.2126, 0.7152, 0.0722)) - .sub(0.1) - .mul(0.7) - .add(mx_noise_float(positionWorld.mul(0.65)).mul(0.16)); - const over = - key === "snow" && otherBiome - ? weight.mul(float(1).add(breakUp.mul(0.025))).clamp(0, 1) - : key === "rock" - ? smoothstep(0.34, 0.51, weight.add(rockHeight)) - : smoothstep(0.12, 0.82, weight.add(reliefHeight)); - if (key === "snow") snowCover = over; - albedo = mix(albedo, surface, over); - normal = mix(normal, relief.tilt, over); - } - // Concavity occludes sky bounce, leaving direct sunlight physically separate. - material.aoNode = mix(float(1), float(0.88), smoothstep(0.2, 0.85, hollow)); - - // 50–200 metre vegetation tones survive texture mipmapping in the overview. - const dryness = otherBiome - ? smoothstep( - -0.18, - 0.22, - mx_fractal_noise_float(positionWorld.mul(0.006), 3) - .sub(hollow.mul(0.16)) - .add(alpine.mul(0.06)), - ) - : max(deposits.mul(0.7), drainage.mul(smoothstep(0.015, 0.12, slope)).mul(0.4)); - const tone = otherBiome - ? mix(vec3(0.92, 0.94, 0.91), vec3(1.08, 1.03, 0.95), dryness) - : mix(vec3(0.9, 0.95, 0.84), vec3(1.03, 1.04, 0.95), dryness); - const vegetation = oneMinus(max(max(weights.rock, weights.snow), weights.sand)); - const continuation = smoothstep(data.size / 2 + 60, data.size / 2 + 280, outside); - const strata = mx_fractal_noise_float(positionWorld.mul(vec3(0.018, 0.035, 0.018)), 3); - // Reuse the resident maps at massif scale: steep faces project onto their own axes. - const forest = triplanarAlbedo(layer("grass").diffuse, "grass", 12) - .rgb.mul(MEADOW) - .mul(mix(0.5, 0.85, breakUp.add(0.5))); - const crag = ( - biome?.world === "coastal" - ? triplanarAlbedo(layer("rock").diffuse, "rock", 6) - : mix( - triplanarAlbedo(layer("rock").diffuse, "rock", 2.6), - triplanarAlbedo(layer("rock").diffuse, "rock", 14), - patch.mul(0.25).add(0.15), - ) - ).rgb - .mul(vec3(0.62, 0.66, 0.67)) - .mul(strata.mul(0.35).add(1)); - const treeline = smoothstep(110, 240, positionWorld.y.add(breakUp.mul(24))); - const face = smoothstep(0.14, 0.3, steep.add(strata.mul(0.055))); - const cap = smoothstep(225, 310, positionWorld.y.add(breakUp.mul(28))).mul( - oneMinus(smoothstep(0.18, 0.42, steep)), - ); - const snow = triplanarAlbedo(layer("snow").diffuse, "snow", 8).rgb; - const mountain = mix(mix(forest, crag, max(treeline, face)), snow, cap); - material.colorNode = mix( - albedo.rgb.mul(mix(vec3(1), tone, vegetation)), - otherBiome ? albedo.rgb : mountain, - continuation, - ) - .mul(mix(vec3(1), vec3(0.42, 0.46, 0.42), curvature.wetBank)) - .mul(mix(vec3(1), FOREST_FLOOR, forestFloor)); - material.roughnessNode = mix( - mix( - otherBiome ? 0.94 : mix(0.88, 0.98, dryness), - float(0.91).sub( - smoothstep(0.48, 0.68, mx_noise_float(positionWorld.mul(60))) - .mul(oneMinus(smoothstep(3, 14, positionView.length()))) - .mul(0.5), - ), - snowCover, - ), - 0.48, - curvature.wetBank, - ); - if (!otherBiome) { - material.roughnessNode = mix( - material.roughnessNode as Node<"float">, - 0.52, - sand.mul(wetness()), - ); - // A little albedo-coloured bounce keeps unlit rock faces legible without changing the sky rig. - material.emissiveNode = (material.colorNode as Node<"vec3">).mul( - weights.rock.max(continuation).mul(0.08), - ); - } - // Distant faces resolve broad rock strata, rather than subpixel meadow normals. - const rockNormal = layer("rock").normal; - const mountainTilt = - rockNormal === undefined - ? vec3(0) - : triplanarRelief(rockNormal, "rock", 8).tilt.mul(face).mul(oneMinus(cap)).mul(0.32); - // Distant alpine rock previously discarded all normal relief; snow weights stay unchanged. - const distantTilt = biome?.world === "alpine" ? mountainTilt.mul(weights.rock) : mountainTilt; - const tilt = biome?.world === "tundra" ? normal : mix(normal, distantTilt, continuation); - // Detail is tangential; it must not rotate the whole hillside towards a fixed diagonal. - const tangent = tilt.sub(normalWorldGeometry.mul(dot(normalWorldGeometry, tilt))); - const crystals = microGrain() - .mul(snowCover) - .mul(oneMinus(smoothstep(4, 22, positionView.length()))); - material.normalNode = transformNormalToView( - normalize(normalWorldGeometry.add(tangent).add(crystals)), - ); + const samples: Partial; relief: IRelief }>> = {}; material.addEventListener("dispose", () => { for (const source of held) source.dispose(); }); - return material; + try { + // Owner-local crags remain optional; the package always carries the portable CC0 fallback. + const rockPaths = otherBiome ? biome.maps.rock : ROCKFACE_MAPS; + if (rockPaths.diffuse.startsWith("temperate/")) { + const diffuse = await get(assets, rockPaths.diffuse, false); + if (diffuse) { + held.add(diffuse); + const normal = rockPaths.normal ? await get(assets, rockPaths.normal, true) : undefined; + if (normal) held.add(normal); + maps.rock = { diffuse, normal }; + } + } + return (await loadTerrainSplat({ + assets, + url: `world/${biome?.world ?? "forest"}.world.json`, + layerSample: ({ layer: descriptor, diffuse, normal: normalMap }) => { + const key = descriptor.id as LayerKey; + maps[key] ??= { diffuse, normal: normalMap }; + const surface = albedoOf(key); + const relief = reliefOf( + key, + key === "grass" ? 0.38 : key === "rock" ? 0.52 : key === "snow" ? 0.18 : 0.32, + ); + samples[key] = { surface, relief }; + if (key !== "grass") return { color: surface.rgb, normal: relief.tilt }; + let normal = relief.tilt + .mul(weights.grass) + .mul( + biome?.world === "alpine" + ? mix(1, 0.05, smoothstep(45, 130, positionView.length())) + : 1, + ) + .add(microGrain().mul(oneMinus(smoothstep(12, 60, positionView.length())))); + if (biome?.world === "desert") + normal = normal.add(sandRipples(vec2(0.53, 0.85)).mul(weights.grass)); + return { color: groundTurf(surface.rgb, biome, hollow), normal }; + }, + // Geometry, transport and curvature choose placement; the packaged masks remain authored data. + layerWeight: (_masked, { layer: descriptor }) => { + const key = descriptor.id as LayerKey; + const weight = weights[key]; + const sample = samples[key]; + if (!sample) throw new RangeError(`Ground layer '${key}' was not sampled`); + const { surface, relief } = sample; + // No displacement maps in these starter sets: normal relief breaks the blend edge, + // while slope, elevation and curvature determine which surface belongs here. + const reliefHeight = relief.crevice.sub(0.8).mul(0.12).add(breakUp.mul(0.045)); + // The photograph's relief and metre-scale breakup cut pockets into the soil/rock boundary. + const rockHeight = dot(surface.rgb, vec3(0.2126, 0.7152, 0.0722)) + .sub(0.1) + .mul(0.7) + .add(mx_noise_float(positionWorld.mul(0.65)).mul(0.16)); + const over = + key === "snow" && otherBiome + ? weight.mul(float(1).add(breakUp.mul(0.025))).clamp(0, 1) + : key === "rock" + ? smoothstep(0.34, 0.51, weight.add(rockHeight)) + : smoothstep(0.12, 0.82, weight.add(reliefHeight)); + if (key === "snow") snowCover = over; + return over; + }, + materialFactory: ({ color, normal }) => { + const albedo = vec4(color, 1); + // Concavity occludes sky bounce, leaving direct sunlight physically separate. + material.aoNode = mix(float(1), float(0.88), smoothstep(0.2, 0.85, hollow)); + + // 50–200 metre vegetation tones survive texture mipmapping in the overview. + const dryness = otherBiome + ? smoothstep( + -0.18, + 0.22, + mx_fractal_noise_float(positionWorld.mul(0.006), 3) + .sub(hollow.mul(0.16)) + .add(alpine.mul(0.06)), + ) + : max(deposits.mul(0.7), drainage.mul(smoothstep(0.015, 0.12, slope)).mul(0.4)); + const tone = otherBiome + ? mix(vec3(0.92, 0.94, 0.91), vec3(1.08, 1.03, 0.95), dryness) + : mix(vec3(0.9, 0.95, 0.84), vec3(1.03, 1.04, 0.95), dryness); + const vegetation = oneMinus(max(max(weights.rock, weights.snow), weights.sand)); + const continuation = smoothstep(data.size / 2 + 60, data.size / 2 + 280, outside); + const strata = mx_fractal_noise_float(positionWorld.mul(vec3(0.018, 0.035, 0.018)), 3); + // Reuse the resident maps at massif scale: steep faces project onto their own axes. + const forest = triplanarAlbedo(layer("grass").diffuse, "grass", 12) + .rgb.mul(MEADOW) + .mul(mix(0.5, 0.85, breakUp.add(0.5))); + const crag = ( + biome?.world === "coastal" + ? triplanarAlbedo(layer("rock").diffuse, "rock", 6) + : mix( + triplanarAlbedo(layer("rock").diffuse, "rock", 2.6), + triplanarAlbedo(layer("rock").diffuse, "rock", 14), + patch.mul(0.25).add(0.15), + ) + ).rgb + .mul(vec3(0.62, 0.66, 0.67)) + .mul(strata.mul(0.35).add(1)); + const treeline = elevationBand( + measured, + measured.heightBands.treeline, + positionWorld.y.add(breakUp.mul(24)), + ); + const face = smoothstep(0.14, 0.3, steep.add(strata.mul(0.055))); + const cap = elevationBand( + measured, + measured.heightBands.cap, + positionWorld.y.add(breakUp.mul(28)), + ).mul(oneMinus(smoothstep(0.18, 0.42, steep))); + const snow = triplanarAlbedo(layer("snow").diffuse, "snow", 8).rgb; + const mountain = mix(mix(forest, crag, max(treeline, face)), snow, cap); + material.colorNode = mix( + albedo.rgb.mul(mix(vec3(1), tone, vegetation)), + otherBiome ? albedo.rgb : mountain, + continuation, + ) + .mul(mix(vec3(1), vec3(0.42, 0.46, 0.42), curvature.wetBank)) + .mul(mix(vec3(1), FOREST_FLOOR, forestFloor)); + material.roughnessNode = mix( + mix( + otherBiome ? 0.94 : mix(0.88, 0.98, dryness), + float(0.91).sub( + smoothstep(0.48, 0.68, mx_noise_float(positionWorld.mul(60))) + .mul(oneMinus(smoothstep(3, 14, positionView.length()))) + .mul(0.5), + ), + snowCover, + ), + 0.48, + curvature.wetBank, + ); + if (!otherBiome) { + material.roughnessNode = mix( + material.roughnessNode as Node<"float">, + 0.52, + sand.mul(wetness()), + ); + // A little albedo-coloured bounce keeps unlit rock faces legible without changing the sky rig. + material.emissiveNode = (material.colorNode as Node<"vec3">).mul( + weights.rock.max(continuation).mul(0.08), + ); + } + // Distant faces resolve broad rock strata, rather than subpixel meadow normals. + const rockNormal = layer("rock").normal; + const mountainTilt = + rockNormal === undefined + ? vec3(0) + : triplanarRelief(rockNormal, "rock", 8).tilt.mul(face).mul(oneMinus(cap)).mul(0.32); + // Distant alpine rock previously discarded all normal relief; snow weights stay unchanged. + const distantTilt = + biome?.world === "alpine" ? mountainTilt.mul(weights.rock) : mountainTilt; + const tilt = biome?.world === "tundra" ? normal : mix(normal, distantTilt, continuation); + // Detail is tangential; it must not rotate the whole hillside towards a fixed diagonal. + const tangent = tilt.sub(normalWorldGeometry.mul(dot(normalWorldGeometry, tilt))); + const crystals = microGrain() + .mul(snowCover) + .mul(oneMinus(smoothstep(4, 22, positionView.length()))); + material.normalNode = transformNormalToView( + normalize(normalWorldGeometry.add(tangent).add(crystals)), + ); + return material; + }, + })) as MeshStandardNodeMaterial; + } catch (error) { + material.dispose(); + throw error; + } } /** @@ -973,6 +1024,26 @@ export function createTerrain( groundNormals.getZ(edge), ); } + if (biome?.world === "tundra") { + // The snow mask reads normals: blend the collar too, rather than only its boundary vertices. + const perimeter = (data.resolution - 1) * 4; + const normal = new Vector3(); + const boundary = new Vector3(); + for (let vertex = perimeter; vertex < edgePositions.count; vertex++) { + const distance = + Math.max(Math.abs(edgePositions.getX(vertex)), Math.abs(edgePositions.getZ(vertex))) - + data.size / 2; + if (distance >= 40) break; + normal + .fromBufferAttribute(edgeNormals, vertex) + .lerp( + boundary.fromBufferAttribute(edgeNormals, vertex % perimeter), + 1 - MathUtils.smoothstep(distance, 0, 40), + ) + .normalize(); + edgeNormals.setXYZ(vertex, normal.x, normal.y, normal.z); + } + } mesh.userData.horizonSeamGap = horizonSeamGap; mesh.userData.horizonSeamSamples = (data.resolution - 1) * 4; horizonGeometry.setAttribute( @@ -986,19 +1057,17 @@ export function createTerrain( mesh.add(horizon); geometry.addEventListener("dispose", () => horizon.geometry.dispose()); - const curvature = buildCurvature(data, field); + const curvature = buildCurvature(data, field, biome); + let disposed = false; + geometry.addEventListener("dispose", () => { + disposed = true; + }); if (assets !== undefined) - void loadGroundMaps( - assets, - biome?.world === "forest" || biome?.world === "coastal" || biome === undefined - ? { ...(biome?.maps ?? GROUND_MAPS), rock: ROCKFACE_MAPS, dirt: BIOMES.alpine.maps.dirt } - : biome.maps, - ) - .then((maps) => createGroundMaterial(data, maps, curvature, biome)) + void createGroundMaterial(data, assets, curvature, biome) .then((ground) => { // Anything but the flat placeholder means the scene already moved on; a material nothing // draws holds GPU memory until its textures are released. - if (mesh.material !== material) return ground.dispose(); + if (disposed || mesh.material !== material) return ground.dispose(); mesh.material = ground; horizon.material = ground; material.dispose(); @@ -1034,7 +1103,7 @@ export function createTerrain( * * One curvature binding plus six albedos, four normals and four shadow maps is fifteen. */ -function buildCurvature(data: IBakedWorld, field: Heightfield): IGroundCurvature { +function buildCurvature(data: IBakedWorld, field: Heightfield, biome?: IBiome): IGroundCurvature { const resolution = data.resolution; const heights = data.heights; const spacing = data.size / (resolution - 1); @@ -1140,7 +1209,10 @@ function buildCurvature(data: IBakedWorld, field: Heightfield): IGroundCurvature const sampled = texture(cooked, uv); // A clamped wet boundary texel must not become a stripe across the continuation mesh. const outside = max(abs(positionWorld.x), abs(positionWorld.z)); - const resident = oneMinus(smoothstep(data.size / 2 - spacing * 2, data.size / 2, outside)); + const collar = biome?.world === "tundra" ? 40 : 0; + const resident = oneMinus( + smoothstep(data.size / 2 - Math.max(collar, spacing * 2), data.size / 2 + collar, outside), + ); return { node: sampled.r, texture: cooked, @@ -1150,38 +1222,6 @@ function buildCurvature(data: IBakedWorld, field: Heightfield): IGroundCurvature }; } -/** - * Every starter map the ground binds, keyed by layer. - * - * Albedo is colour data and everything else is linear, so the loader is told which is each: a - * normal map read as sRGB bends its own channels and the ground loses the relief it was cooked with. - */ -async function loadGroundMaps( - assets: IAssetLoader, - pathsByLayer = GROUND_MAPS, -): Promise>> { - const layers = await Promise.all( - Object.entries(pathsByLayer).map(async ([key, paths]) => { - // A layer without its albedo has no place in the blend, so the set comes back without it. - let diffuse = await get(assets, paths.diffuse, false); - let chosen = paths; - if (diffuse === undefined && paths.diffuse.startsWith("temperate/")) { - chosen = GROUND_MAPS[key as LayerKey]; - diffuse = await get(assets, chosen.diffuse, false); - } - if (diffuse === undefined) return undefined; - const maps: ILayerMaps = { diffuse }; - for (const [slot, path] of Object.entries(chosen)) { - if (slot === "diffuse") continue; - const found = await get(assets, path, true); - if (found !== undefined) maps[slot as "normal"] = found; - } - return [key, maps] as const; - }), - ); - return Object.fromEntries(layers.filter((entry) => entry !== undefined)); -} - async function get( assets: IAssetLoader, path: string, diff --git a/examples/strata-terrain-preview/src/render/texturePreparation.ts b/examples/strata-terrain-preview/src/render/texturePreparation.ts index 9933fa68ea..6c01acb308 100644 --- a/examples/strata-terrain-preview/src/render/texturePreparation.ts +++ b/examples/strata-terrain-preview/src/render/texturePreparation.ts @@ -52,27 +52,81 @@ function materialInputs(materials: Iterable, textures: Set): return nodes; } -function sampledTextures(nodes: Node[], textures: Set): void { +function sampledTextures(nodes: Node[], textures: Set): Node[] { const seen = new Set(); + const sampledNodes: Node[] = []; for (const node of nodes) { if (seen.has(node)) continue; seen.add(node); const sampled = node as Node & { isTextureNode?: boolean; value?: Texture }; - if (sampled.isTextureNode && sampled.value?.isTexture) textures.add(sampled.value); + if (sampled.isTextureNode && sampled.value?.isTexture) { + textures.add(sampled.value); + sampledNodes.push(node); + } for (const child of node.getChildren()) nodes.push(child); } + return sampledNodes; +} + +/** Points every borrowed material map and TSL texture node at its prepared replacement. */ +function applyReplacements( + materials: Iterable, + sampledNodes: Node[], + replacements: Map, +): number { + if (replacements.size === 0) return 0; + const applied = new Set(); + for (const material of new Set(materials)) { + const surface = material as Material & + Partial & + Record; + for (const key of mapInputs) { + const value = surface[key] as Texture | undefined; + const prepared = value?.isTexture === true ? replacements.get(value) : undefined; + if (value?.isTexture && prepared?.isTexture && prepared !== value) { + surface[key] = prepared; + if ((value as Texture & { isCompressedTexture?: boolean }).isCompressedTexture) + applied.add(value); + } + } + } + for (const node of sampledNodes) { + const sampled = node as Node & { value?: Texture }; + if (sampled.value === undefined) continue; + const prepared = replacements.get(sampled.value); + if (prepared?.isTexture && prepared !== sampled.value) { + if ((sampled.value as Texture & { isCompressedTexture?: boolean }).isCompressedTexture) + applied.add(sampled.value); + sampled.value = prepared; + } + } + return applied.size; } -/** Borrow actual material texture identities; asset loading continues to own their lifetime. */ +/** + * Borrow actual material texture identities; asset loading continues to own their lifetime. The + * renderer prepares copies it owns and this call swaps every map and TSL texture node reference + * for its prepared replacement, so a compressed material samples uploaded GPU data. The numeric + * completion count, progress callback and errors keep the renderer's contract. Compressed completion + * reports distinct compressed inputs actually replaced after preparation succeeds. + */ export async function preparePropTextures( renderer: Pick, materials: Iterable, signal?: AbortSignal, onProgress?: (completed: number) => void, + onCompressedPrepared?: (completed: number) => void, ): Promise { + // Snapshot the iterable once so a generator is not consumed before replacements are applied. + const materialList = [...new Set(materials)]; const textures = new Set(); - sampledTextures(materialInputs(materials, textures), textures); + const nodes = materialInputs(materialList, textures); + const sampledNodes = sampledTextures(nodes, textures); if (!renderer.prepareTextures) throw new Error("Renderer lacks bounded prop texture preparation."); - return renderer.prepareTextures(textures, signal, onProgress); + const replacements = new Map(); + const completed = await renderer.prepareTextures(textures, signal, onProgress, replacements); + const compressedPrepared = applyReplacements(materialList, sampledNodes, replacements); + onCompressedPrepared?.(compressedPrepared); + return completed; } diff --git a/examples/strata-terrain-preview/src/world/terrainAssets.ts b/examples/strata-terrain-preview/src/world/terrainAssets.ts index e21c01ab14..994b7ce215 100644 --- a/examples/strata-terrain-preview/src/world/terrainAssets.ts +++ b/examples/strata-terrain-preview/src/world/terrainAssets.ts @@ -91,6 +91,13 @@ export interface ISurfaceMaps { readonly roughness: string; } +/** CC0 dry stone for beach and desert props; ARM uses AO in R and roughness in G. */ +export const DRY_STONE_MAPS = { + diffuse: "rock_boulder_dry/rock_boulder_dry_diff_512.jpg", + normal: "rock_boulder_dry/rock_boulder_dry_nor_gl_512.jpg", + arm: "rock_boulder_dry/rock_boulder_dry_arm_512.jpg", +} as const; + /** The bark and stone the props bind, and the soil the bases of both fade into. */ export const PROP_MAPS: Record<"bark" | "stone", ISurfaceMaps> = { bark: { diff --git a/examples/strata-terrain-preview/terrain-editor/conflict.html b/examples/strata-terrain-preview/terrain-editor/conflict.html new file mode 100644 index 0000000000..83a676bc07 --- /dev/null +++ b/examples/strata-terrain-preview/terrain-editor/conflict.html @@ -0,0 +1,6 @@ + +Strata · Save conflict fixture

Loading project terrain…

diff --git a/examples/strata-terrain-preview/vite.config.ts b/examples/strata-terrain-preview/vite.config.ts index 57eed9f9a1..bdf90b20b5 100644 --- a/examples/strata-terrain-preview/vite.config.ts +++ b/examples/strata-terrain-preview/vite.config.ts @@ -1,7 +1,8 @@ -import { createReadStream, existsSync, statSync } from "node:fs"; +import { createReadStream, existsSync, readFileSync, statSync } from "node:fs"; import type { IncomingMessage, ServerResponse } from "node:http"; import { fileURLToPath } from "node:url"; import { terrainEditor } from "@threenative/terrain/editor/server"; +import { starterAssetsDir } from "@threenative/terrain/kit"; import { type Plugin, defineConfig } from "vite"; /** @@ -27,6 +28,7 @@ import { type Plugin, defineConfig } from "vite"; */ function preparedAssets(): Plugin { const roots: { mount: string; dir: string; flat: boolean }[] = [ + { dir: "./src/world", flat: true, mount: "/world" }, { dir: "./local-assets/temperate", flat: false, mount: "/temperate" }, { dir: "./local-assets/temperate/basis", flat: true, mount: "/basis" }, { dir: "./local-assets/prepared", flat: true, mount: "/prepared" }, @@ -78,6 +80,15 @@ function preparedAssets(): Plugin { name: "strata-prepared-assets", configureServer: serve, configurePreviewServer: serve, + generateBundle() { + for (const world of ["forest", "coastal", "alpine", "desert", "tundra"]) + for (const suffix of ["placements.bin", "world.json", "layers.json", "splat.rgba8"]) + this.emitFile({ + type: "asset", + fileName: `world/${world}.${suffix}`, + source: readFileSync(new URL(`./src/world/${world}.${suffix}`, import.meta.url)), + }); + }, }; } @@ -85,7 +96,7 @@ export default defineConfig({ // The starter PBR sets are the terrain package's prepared art, served to the game rather than // duplicated into this example's `public/`: `/leafy_grass/…` reaches the same bytes in dev and in // a build, and `ctx.assets` loads them by that path. - publicDir: fileURLToPath(new URL("../../packages/terrain/starter-assets", import.meta.url)), + publicDir: await starterAssetsDir(), plugins: [ terrainEditor({ documentPath: fileURLToPath(new URL("./terrain/world.json", import.meta.url)), diff --git a/package.json b/package.json index cbfe3b1ae8..9af9944a9a 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "test:tone": "sh scripts/xvfb.sh tsx scripts/verify-tone-exposure.ts", "test:vfx-gallery": "pnpm --filter @threenative/playtest build && pnpm --filter @threenative/core build && pnpm --filter vfx-gallery build && tsx scripts/vfx-gallery-visual.ts", "typecheck": "tsc --noEmit -p tsconfig.json && pnpm -r --if-present run typecheck", + "verify:forest-playtest": "sh scripts/xvfb.sh tsx scripts/verify-forest-playtest.ts", "verify:golden-path": "sh scripts/xvfb.sh tsx scripts/verify-golden-path.ts", "visuals": "sh scripts/xvfb.sh tsx scripts/visual-gate.ts", "visuals:ab": "sh scripts/xvfb.sh tsx scripts/visual-ab.ts", @@ -100,6 +101,7 @@ "@gltf-transform/extensions": "catalog:", "@gltf-transform/functions": "catalog:", "@playwright/test": "1.62.1", + "@threenative/assets": "workspace:*", "@types/node": "20.19.43", "@types/pngjs": "6.0.5", "@vitest/coverage-v8": "catalog:", diff --git a/packages/assets/__tests__/compile.spec.ts b/packages/assets/__tests__/compile.spec.ts index feac77a076..788b03410b 100644 --- a/packages/assets/__tests__/compile.spec.ts +++ b/packages/assets/__tests__/compile.spec.ts @@ -111,6 +111,124 @@ async function singleImageFixtureGlb(gridDepth = 3): Promise { } describe("compileAssets", () => { + it("lists a cooked terrain GLB byte-identically without invoking model passes", async () => { + const root = await makeTempDir("threenative-cooked-world-"); + await mkdir(path.join(root, "assets/worlds/forest"), { recursive: true }); + const doc = await buildFixtureDocument(); + doc.getRoot().getAsset().extras = { tnTerrainWorld: 1 }; + const cooked = Buffer.from(await new NodeIO().writeBinary(doc)); + await writeFile(path.join(root, "assets/worlds/forest/world.glb"), cooked); + await writeFile(path.join(root, "assets/ordinary.glb"), await buildFixtureGlb()); + const apply = vi.fn((input: Buffer) => input); + await compileAssets({ + cwd: root, + passes: [{ name: "encode-probe", appliesTo: ["model"], apply }], + }); + const manifest = JSON.parse( + await readFile(path.join(root, "public/assets.manifest.json"), "utf8"), + ); + const entry = manifest.entries["worlds/forest/world.glb"]; + expect(entry).toMatchObject({ kind: "model", bytes: cooked.length, passes: [] }); + expect(await readFile(path.join(root, "public", entry.output))).toEqual(cooked); + expect(apply.mock.calls).toHaveLength(1); + expect(manifest.entries["ordinary.glb"].passes).toEqual(["encode-probe"]); + const malformed = Buffer.from(cooked); + malformed.writeUInt32LE(cooked.length + 4, 8); + await writeFile(path.join(root, "assets/worlds/forest/world.glb"), malformed); + await expect(compileAssets({ cwd: root, passes: [] })).rejects.toThrow(/GLB/); + }); + + it.each([ + { platform: "desktop", models: "none" }, + { platform: "android", models: "none" }, + { platform: "ios", models: "none" }, + { platform: "desktop", models: undefined }, + ] as const)( + "normalizes cooked terrain layout on $platform (models: $models) without reauthoring or reusing web output", + async ({ platform, models }) => { + const root = await makeTempDir("threenative-cooked-world-native-"); + await mkdir(path.join(root, "assets")); + const doc = buildFixtureDocument(); + const material = doc.getRoot().listMaterials()[0]; + if (!material) throw new Error("fixture must contain a material"); + doc.createMaterial().copy(material); + doc.getRoot().getAsset().extras = { tnTerrainWorld: 1 }; + const io = new NodeIO().registerExtensions(ALL_EXTENSIONS); + const cooked = Buffer.from(await io.writeBinary(doc)); + await writeFile(path.join(root, "assets/world.glb"), cooked); + const config = { models, textures: "none", audio: "none", lod: false } as const; + const readEntry = async () => { + const manifest = JSON.parse( + await readFile(path.join(root, "public/assets.manifest.json"), "utf8"), + ); + return manifest.entries["world.glb"]; + }; + await compileAssets({ cwd: root, config, platform: "web" }); + const web = await readEntry(); + expect(await readFile(path.join(root, "public", web.output))).toEqual(cooked); + const native = await compileAssets({ cwd: root, config, platform }); + const entry = await readEntry(); + expect(native.skipped).toBe(0); + expect(entry.output).not.toBe(web.output); + expect(entry.passes).toEqual(["model"]); + expect(native.passCosts.find((row) => row.pass === "model")).toMatchObject({ + ranInputs: 1, + cachedInputs: 0, + status: "ran", + assets: [{ logicalPath: "world.glb", durationMs: expect.any(Number) }], + }); + expect(native.receipt?.outputs.find((output) => output.path === entry.output)?.producer).toBe( + "model", + ); + const output = await readFile(path.join(root, "public", entry.output)); + const json = (await io.binaryToJSON(output)).json; + const original = (await io.binaryToJSON(cooked)).json; + expect(json.materials).toEqual(original.materials); + expect(json.asset.extras).toEqual(original.asset.extras); + const accessors = json.accessors ?? []; + for (const mesh of json.meshes ?? []) { + for (const primitive of mesh.primitives ?? []) { + const attributes = Object.values(primitive.attributes ?? {}); + expect(new Set(attributes.map((index) => accessors[index]?.bufferView)).size).toBe( + attributes.length, + ); + } + } + const restored = await io.readBinary(output); + expect( + restored + .getRoot() + .listTextures() + .map((texture) => Array.from(texture.getImage() ?? [])), + ).toEqual( + doc + .getRoot() + .listTextures() + .map((texture) => Array.from(texture.getImage() ?? [])), + ); + const geometry = (document: typeof doc) => + document + .getRoot() + .listMeshes() + .map((mesh) => + mesh.listPrimitives().map((primitive) => ({ + attributes: primitive.listAttributes().map((attribute) => attribute.getArray()), + indices: primitive.getIndices()?.getArray(), + })), + ); + expect(geometry(restored)).toEqual(geometry(doc)); + const cached = await compileAssets({ cwd: root, config, platform }); + expect(cached.skipped).toBe(1); + expect(cached.passCosts.find((row) => row.pass === "model")).toMatchObject({ + ranInputs: 0, + cachedInputs: 1, + status: "cached", + assets: [], + }); + expect(await readEntry()).toEqual(entry); + }, + ); + it("should omit an excluded model from the manifest and report saved bytes", async () => { const root = await makeTempDir("threenative-exclude-"); await mkdir(path.join(root, "assets/unused"), { recursive: true }); diff --git a/packages/assets/__tests__/lightmap-bake.spec.ts b/packages/assets/__tests__/lightmap-bake.spec.ts index fd0d4f3a41..102a204c75 100644 --- a/packages/assets/__tests__/lightmap-bake.spec.ts +++ b/packages/assets/__tests__/lightmap-bake.spec.ts @@ -77,8 +77,8 @@ function blockerScene(receiverUv: readonly number[]): Document { } describe("bakeStaticLightmap", () => { - it("casts blocker occlusion instead of writing a flat fill", () => { - const result = bakeStaticLightmap(blockerScene([0, 0, 1, 0, 1, 1, 0, 1]), 16, 16, 0); + it("casts blocker occlusion instead of writing a flat fill", async () => { + const result = await bakeStaticLightmap(blockerScene([0, 0, 1, 0, 1, 1, 0, 1]), 16, 16, 0); const left = result.pixels[(8 * 16 + 3) * 4]; const right = result.pixels[(8 * 16 + 12) * 4]; @@ -87,8 +87,8 @@ describe("bakeStaticLightmap", () => { expect(right).toBe(255); }); - it("dilates populated chart texels into empty borders", () => { - const result = bakeStaticLightmap( + it("dilates populated chart texels into empty borders", async () => { + const result = await bakeStaticLightmap( blockerScene([0.25, 0.25, 0.75, 0.25, 0.75, 0.75, 0.25, 0.75]), 16, 16, diff --git a/packages/assets/__tests__/simplify-pool.spec.ts b/packages/assets/__tests__/simplify-pool.spec.ts new file mode 100644 index 0000000000..03e86c9de0 --- /dev/null +++ b/packages/assets/__tests__/simplify-pool.spec.ts @@ -0,0 +1,90 @@ +import { MeshoptSimplifier } from "meshoptimizer"; +import { TorusKnotGeometry } from "three"; +import { describe, expect, it } from "vitest"; +import { createSimplifyPool } from "../src/lod/simplify-pool.js"; + +describe("terrain LOD CPU pool", () => { + it("rejects queued targets when disposed", async () => { + const pool = createSimplifyPool(1); + const jobs = Array.from({ length: 3 }, () => + pool.simplify( + new Uint32Array([0, 1, 2]), + new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), + 3, + new Float32Array(), + 0, + [], + null, + 3, + 0, + [], + ), + ); + const settled = Promise.allSettled(jobs); + await pool.dispose(); + expect((await settled).map(({ status }) => status)).toEqual([ + "rejected", + "rejected", + "rejected", + ]); + }); + it("matches serial indices and errors, in target order, across reused workers", async () => { + await MeshoptSimplifier.ready; + const geometry = new TorusKnotGeometry(1, 0.4, 64, 16); + const positions = Float32Array.from(geometry.getAttribute("position").array); + const normals = Float32Array.from(geometry.getAttribute("normal").array); + const indices = Uint32Array.from(geometry.getIndex()?.array ?? []); + const pool = createSimplifyPool(2); + try { + const args = [0.002, 0.006, 0.02, 0.06].map( + (error): Parameters => [ + indices, + positions, + 3, + normals, + 3, + [1, 1, 1], + null, + 3, + error, + ["LockBorder"], + ], + ); + const expected = args.map((args) => MeshoptSimplifier.simplifyWithAttributes(...args)); + expect(await Promise.all(args.map((args) => pool.simplify(...args)))).toEqual(expected); + expect(await Promise.all(args.map((args) => pool.simplify(...args)))).toEqual(expected); + } finally { + geometry.dispose(); + await pool.dispose(); + } + }); + + it("rejects invalid input without stranding later jobs", async () => { + const pool = createSimplifyPool(1); + const indices = new Uint32Array([0, 1, 2]); + const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]); + try { + await expect( + pool.simplify(indices, positions, 3, new Float32Array(), 0, [], null, 3, -1, []), + ).rejects.toThrow("Assertion failed"); + const [result] = await pool.simplify( + indices, + positions, + 3, + new Float32Array(), + 0, + [], + null, + 3, + 0, + [], + ); + expect(result).toEqual(indices); + } finally { + await pool.dispose(); + } + await expect( + pool.simplify(indices, positions, 3, new Float32Array(), 0, [], null, 3, 0, []), + ).rejects.toThrow("exited"); + }); +}); diff --git a/packages/assets/__tests__/terrain-editor-cook.spec.ts b/packages/assets/__tests__/terrain-editor-cook.spec.ts new file mode 100644 index 0000000000..b6caefac4b --- /dev/null +++ b/packages/assets/__tests__/terrain-editor-cook.spec.ts @@ -0,0 +1,86 @@ +import { Document, NodeIO } from "@gltf-transform/core"; +import { describe, expect, it } from "vitest"; +import { cookTerrainWorld } from "../src/world/terrain-world.js"; + +async function prepared() { + const doc = new Document(); + const buffer = doc.createBuffer(); + const positions = doc + .createAccessor() + .setType("VEC3") + .setArray(new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0])) + .setBuffer(buffer); + const texture = doc + .createTexture() + .setImage(new Uint8Array([1, 2, 3])) + .setMimeType("image/png"); + const mesh = doc + .createMesh() + .addPrimitive( + doc + .createPrimitive() + .setAttribute("POSITION", positions) + .setMaterial(doc.createMaterial().setBaseColorTexture(texture)), + ); + const terrain = doc.createNode("terrain").setMesh(mesh); + const prop = doc + .createNode("prop") + .setMesh(mesh) + .setTranslation([12, 100, -4]) + .setScale([2, 0.5, 1.5]) + .setExtras({ placementId: "pine:0", assetId: "pine" }); + const water = doc.createNode("water:river").setMesh(mesh); + const root = doc + .createNode("terrain-world") + .setExtras({ terrainRevision: "a".repeat(64) }) + .addChild(terrain) + .addChild(prop) + .addChild(water); + const scene = doc.createScene().addChild(root); + doc.getRoot().setDefaultScene(scene); + const encoded = await new NodeIO().writeJSON(doc); + // Match Three's browser JSON serialization, including embedded resources. + for (const entry of [...(encoded.json.buffers ?? []), ...(encoded.json.images ?? [])]) { + if (!entry.uri) continue; + entry.uri = `data:${"mimeType" in entry ? entry.mimeType : "application/octet-stream"};base64,${Buffer.from(encoded.resources[entry.uri] ?? []).toString("base64")}`; + } + return encoded.json as unknown as Record; +} + +describe("portable terrain editor cook", () => { + it("keeps ordinary placement transforms, water, shared meshes and embedded maps", async () => { + const { worldGlb } = await cookTerrainWorld({ preparedWorld: await prepared() }); + const json = (await new NodeIO().binaryToJSON(worldGlb)).json; + expect(json.extensionsRequired ?? []).not.toContain("EXT_mesh_gpu_instancing"); + expect(json.nodes?.find((node) => node.extras?.placementId === "pine:0")?.translation).toEqual([ + 12, 100, -4, + ]); + expect(json.nodes?.find((node) => node.name === "prop")?.scale).toEqual([2, 0.5, 1.5]); + expect(json.nodes?.some((node) => node.name === "water:river")).toBe(true); + expect(json.nodes?.find((node) => node.name === "terrain-world")?.extras?.terrainRevision).toBe( + "a".repeat(64), + ); + expect(json.meshes).toHaveLength(1); + expect(json.images).toHaveLength(1); + expect(json.images?.[0]?.uri).toBeUndefined(); + }); + it("rejects external resources, empty content and nonportable required extensions", async () => { + const json = await prepared(); + await expect( + cookTerrainWorld({ + preparedWorld: { ...json, buffers: [{ uri: "file:///etc/passwd", byteLength: 36 }] }, + }), + ).rejects.toThrow("embedded"); + await expect( + cookTerrainWorld({ + preparedWorld: { ...json, images: [{ uri: "http://localhost/private" }] }, + }), + ).rejects.toThrow("embedded"); + await expect(cookTerrainWorld({ preparedWorld: {} })).rejects.toThrow("nonempty"); + await expect( + cookTerrainWorld({ + preparedWorld: { ...json, extensionsRequired: ["EXT_mesh_gpu_instancing"] }, + }), + ).rejects.toThrow("nonportable"); + }); +}); diff --git a/packages/assets/__tests__/terrain-world.spec.ts b/packages/assets/__tests__/terrain-world.spec.ts new file mode 100644 index 0000000000..2dbd32de95 --- /dev/null +++ b/packages/assets/__tests__/terrain-world.spec.ts @@ -0,0 +1,1358 @@ +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import { readFile, rm, stat, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { + type Accessor, + Document, + type GLTF, + type Buffer as GltfBuffer, + type Mesh as GltfMesh, + type Material, + MathUtils, + NodeIO, +} from "@gltf-transform/core"; +import { + ALL_EXTENSIONS, + type InstancedMesh as GltfInstancedMesh, + KHRMeshQuantization, + KHRTextureTransform, + type Transform, +} from "@gltf-transform/extensions"; +import { + copyToDocument, + createDefaultPropertyResolver, + transformMesh, +} from "@gltf-transform/functions"; +import { + type ITerrainState, + Terrain, + applyPlacementOverrides, + bakeTerrain, + bakeWorldPackage, + decodeRAW16, + sampleHeight, +} from "@threenative/terrain"; +import { PNG } from "pngjs"; +import { + BufferAttribute, + BufferGeometry, + type Group, + InstancedMesh, + Matrix4, + Mesh, + PerspectiveCamera, + Quaternion, + Sphere, + Texture, + Vector3, +} from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { makeTempDir } from "../../../test-support/temp-dir.js"; +import { createAssetLoader } from "../../core/src/assets.js"; +import { INSTANCED_LOD_MAX_PIXEL_ERROR } from "../../core/src/instanced-batch-lod.js"; +import { + DISCRETE_LOD_DEFAULT_HYSTERESIS, + conservativeViewDepth, + selectLodLevel, +} from "../../core/src/model-lod.js"; +import { type IWorldPackage, validateWorldPackage } from "../../core/src/world-package.js"; +import { stackLayers } from "../../core/src/world-terrain-splat.js"; +import { createGltfReader, readGltfDocument } from "../src/gltf-io.js"; +import { primitiveTriangleCount } from "../src/lod/eligibility.js"; +import { type DiscreteLod, TNDiscreteLod, TN_DISCRETE_LOD } from "../src/lod/extension.js"; +import { decodeImageBytes } from "../src/passes/decode-image.js"; +import { unpackGlb } from "../src/passes/shared-images.js"; +import { cookTerrainWorld } from "../src/world/terrain-world.js"; + +// GLTFLoader reads the global `self` while it parses. Node has none. +Object.assign(globalThis, { self: globalThis }); + +// A deterministic IO failure injected for one sibling path, so a real stat error can be told +// apart from an absent sibling without touching permissions or the platform. +const fsProbe = vi.hoisted(() => ({ + statFailure: undefined as { path: string; error: Error } | undefined, +})); + +vi.mock("node:fs/promises", async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + stat: async (...args: Parameters) => { + if (fsProbe.statFailure && String(args[0]) === fsProbe.statFailure.path) { + throw fsProbe.statFailure.error; + } + return original.stat(...args); + }, + }; +}); + +/** A real evaluated terrain. The fixed seed fixes every scatter position. */ +function evaluatedTerrain(assets: readonly string[] = ["fir"]): ITerrainState { + let terrain = new Terrain({ resolution: 33, seed: 73, size: 256 }); + for (const asset of assets) { + terrain = terrain.scatter({ id: `${asset}s`, asset, count: 40, minDistance: 3 }); + } + const state = terrain.evaluate(); + if (state.instances.length === 0) throw new Error("fixture terrain has no placements"); + return state; +} + +function io(): NodeIO { + return new NodeIO().registerExtensions([...ALL_EXTENSIONS]); +} + +/** One triangle with normals and UVs. Its vertices sit in the unit square on the XZ plane. */ +function triangle(doc: Document, buffer: GltfBuffer, material: Material) { + return doc + .createPrimitive() + .setAttribute( + "POSITION", + doc + .createAccessor() + .setType("VEC3") + .setArray(new Float32Array([0, 0, 0, 1, 0, 0, 0, 0, 1])) + .setBuffer(buffer), + ) + .setAttribute( + "NORMAL", + doc + .createAccessor() + .setType("VEC3") + .setArray(new Float32Array([0, 1, 0, 0, 1, 0, 0, 1, 0])) + .setBuffer(buffer), + ) + .setAttribute( + "TEXCOORD_0", + doc + .createAccessor() + .setType("VEC2") + .setArray(new Float32Array([0, 0, 1, 0, 0, 1])) + .setBuffer(buffer), + ) + .setIndices( + doc + .createAccessor() + .setType("SCALAR") + .setArray(new Uint16Array([0, 1, 2])) + .setBuffer(buffer), + ) + .setMaterial(material); +} + +/** Two triangles, an unused vertex, and two primitives that share one derived-level accessor. */ +async function smallLodSpecies( + mode: "shared" | "shared-chain" | "mirror" | "unused" | "bad-index" | "bad-level" = "shared", +): Promise { + const doc = new Document(); + const buffer = doc.createBuffer(); + const extension = doc.createExtension(TNDiscreteLod); + const positions = doc + .createAccessor() + .setType("VEC3") + .setArray(new Float32Array([0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 2, 3, 4])) + .setBuffer(buffer); + const level = doc + .createAccessor() + .setType("SCALAR") + .setArray( + Uint32Array.from(mode === "bad-level" ? [3, 2] : mode === "unused" ? [4, 2, 1] : [3, 2, 1]), + ) + .setBuffer(buffer); + const chain = extension + .createDiscreteLod() + .setCounts([1]) + .setErrors([0.1]) + .setAbsoluteErrors([0.1]) + .setBaselineTriangles(2) + .setLod0Triangles(2) + .setErrorScale(1) + .addIndices(level); + const mesh = doc.createMesh("fir"); + for (let part = 0; part < 2; part++) { + const indices = + mode === "bad-index" + ? [99, 2, 1, 1, 2, 0] + : part === 0 + ? [3, 2, 1, 1, 2, 0] + : [0, 1, 2, 2, 1, 3]; + const primitive = doc + .createPrimitive() + .setAttribute("POSITION", positions) + .setIndices( + doc + .createAccessor() + .setType("SCALAR") + .setArray(Uint32Array.from(indices)) + .setBuffer(buffer), + ) + .setMaterial(doc.createMaterial(`part-${part}`)); + primitive.setExtension( + TN_DISCRETE_LOD, + part === 0 || mode === "shared-chain" ? chain : chain.clone(), + ); + mesh.addPrimitive(primitive); + } + doc.createScene().addChild( + doc + .createNode("fir") + .setMesh(mesh) + .setScale(mode === "mirror" ? [-1, 1, 1] : [1, 1, 1]), + ); + return io().registerExtensions([TNDiscreteLod]).writeBinary(doc); +} + +/** A static species with one mesh of one primitive. */ +async function simpleSpecies(): Promise { + const doc = new Document(); + const buffer = doc.createBuffer(); + const scene = doc.createScene("Scene"); + const mesh = doc + .createMesh("rock") + .addPrimitive(triangle(doc, buffer, doc.createMaterial("rock"))); + scene.addChild(doc.createNode("rockNode").setMesh(mesh)); + return io().writeBinary(doc); +} + +/** One triangle whose normals are KHR_mesh_quantization snorm16, as a quantized cooked model stores them. */ +async function quantizedNormalSpecies(): Promise { + const doc = new Document(); + doc.createExtension(KHRMeshQuantization).setRequired(true); + const buffer = doc.createBuffer(); + const primitive = triangle(doc, buffer, doc.createMaterial("fir")); + primitive + .getAttribute("NORMAL") + ?.setArray(new Int16Array([0, 32767, 0, 0, 32767, 0, 0, 32767, 0])) + .setNormalized(true); + const mesh = doc.createMesh("fir").addPrimitive(primitive); + doc + .createScene("Scene") + .addChild(doc.createNode("firNode").setMesh(mesh).setTranslation([5, 0, 0])); + return io().writeBinary(doc); +} + +/** A static species with two meshes. The trunk is textured and sits 5 m along +X in its node. */ +async function twoMeshSpecies(): Promise<{ glb: Uint8Array; png: Uint8Array }> { + const doc = new Document(); + const buffer = doc.createBuffer(); + const scene = doc.createScene("Scene"); + const png = PNG.sync.write(new PNG({ width: 2, height: 2 })); + const texture = doc.createTexture("bark").setImage(png).setMimeType("image/png"); + const bark = doc.createMaterial("bark").setBaseColorTexture(texture); + const trunk = doc.createMesh("trunk").addPrimitive(triangle(doc, buffer, bark)); + const crown = doc + .createMesh("crown") + .addPrimitive(triangle(doc, buffer, doc.createMaterial("leaf"))); + scene.addChild(doc.createNode("trunkNode").setMesh(trunk).setTranslation([5, 0, 0])); + scene.addChild(doc.createNode("crownNode").setMesh(crown)); + return { glb: await io().writeBinary(doc), png }; +} + +/** A static species whose one textured mesh is reused by two nodes, at X=0 and X=5. */ +async function sharedMeshSpecies(): Promise<{ glb: Uint8Array; png: Uint8Array }> { + const doc = new Document(); + const buffer = doc.createBuffer(); + const scene = doc.createScene("Scene"); + const png = PNG.sync.write(new PNG({ width: 2, height: 2 })); + const texture = doc.createTexture("shared").setImage(png).setMimeType("image/png"); + const material = doc.createMaterial("shared").setBaseColorTexture(texture); + const mesh = doc.createMesh("shared").addPrimitive(triangle(doc, buffer, material)); + scene.addChild(doc.createNode("node0").setMesh(mesh).setTranslation([0, 0, 0])); + scene.addChild(doc.createNode("node1").setMesh(mesh).setTranslation([5, 0, 0])); + return { glb: await io().writeBinary(doc), png }; +} + +// glTF wrap modes for the samplers that each material keeps. +const REPEAT: GLTF.TextureWrapMode = 10497; +const CLAMP_TO_EDGE: GLTF.TextureWrapMode = 33071; + +/** A static species whose one textured mesh samples with its own wrap mode and UV offset. */ +async function texturedSpecies( + wrapS: GLTF.TextureWrapMode, + offset: [number, number], +): Promise<{ glb: Uint8Array; png: Uint8Array }> { + const doc = new Document(); + const buffer = doc.createBuffer(); + const scene = doc.createScene("Scene"); + const png = PNG.sync.write(new PNG({ width: 2, height: 2 })); + const texture = doc.createTexture("bark").setImage(png).setMimeType("image/png"); + const bark = doc.createMaterial("bark").setBaseColorTexture(texture); + const transform = doc.createExtension(KHRTextureTransform).createTransform().setOffset(offset); + const info = bark.getBaseColorTextureInfo(); + assert.ok(info); + info.setWrapS(wrapS).setExtension("KHR_texture_transform", transform); + const mesh = doc.createMesh("bark").addPrimitive(triangle(doc, buffer, bark)); + scene.addChild(doc.createNode("barkNode").setMesh(mesh)); + return { glb: await io().writeBinary(doc), png }; +} + +/** A species with a skin. The cook must refuse it by name. */ +async function skinnedSpecies(): Promise { + const doc = new Document(); + const buffer = doc.createBuffer(); + const scene = doc.createScene("Scene"); + const joint = doc.createNode("root"); + const skin = doc.createSkin("rig").addJoint(joint); + const mesh = doc + .createMesh("rigged") + .addPrimitive(triangle(doc, buffer, doc.createMaterial("rigged"))); + scene.addChild(joint); + scene.addChild(doc.createNode("rigged").setMesh(mesh).setSkin(skin)); + return io().writeBinary(doc); +} + +/** A species with a scene but no mesh at all. The cook must refuse it by name. */ +async function noMeshSpecies(): Promise { + const doc = new Document(); + const scene = doc.createScene("Scene"); + scene.addChild(doc.createNode("prop")); + return io().writeBinary(doc); +} + +/** A species whose one mesh has no primitive, so it draws nothing. The cook must refuse it. */ +async function emptyMeshSpecies(): Promise { + const doc = new Document(); + const scene = doc.createScene("Scene"); + scene.addChild(doc.createNode("emptyNode").setMesh(doc.createMesh("empty"))); + return io().writeBinary(doc); +} + +/** Parses a GLB with the vanilla three loader, the same reader a game uses. */ +function loadWorld(glb: Uint8Array): Promise { + return new Promise((resolve, reject) => { + new GLTFLoader().parse(glb.slice().buffer, "", (gltf) => resolve(gltf.scene), reject); + }); +} + +/** Every scene of a cooked GLB, with the default scene and each scene's instancing nodes. */ +function loadScenes(glb: Uint8Array): Promise<{ scene: Group; scenes: Group[] }> { + return new Promise((resolve, reject) => { + new GLTFLoader().parse( + glb.slice().buffer, + "", + (gltf) => resolve({ scene: gltf.scene, scenes: gltf.scenes as Group[] }), + reject, + ); + }); +} + +/** The instancing nodes directly in a loaded scene, each with its instance count. */ +function instancedNodes(scene: Group): { name: string; instances: number }[] { + const found: { name: string; instances: number }[] = []; + scene.traverse((object) => { + if (object instanceof InstancedMesh) found.push({ name: object.name, instances: object.count }); + }); + return found.sort((a, b) => a.name.localeCompare(b.name)); +} + +describe("cookTerrainWorld", () => { + let dir: string; + + beforeEach(async () => { + dir = await makeTempDir("strata-terrain-world-"); + }); + + afterEach(async () => { + vi.unstubAllGlobals(); + await rm(dir, { recursive: true, force: true }); + }); + + it("preserves every relative layer texture byte-for-byte and marks terrain nodes", async () => { + const image = PNG.sync.write(new PNG({ width: 2, height: 2 })); + await writeFile(path.join(dir, "authored.png"), image); + const cooked = await cookTerrainWorld({ + state: new Terrain({ resolution: 33, size: 256 }).evaluate(), + species: {}, + out: path.join(dir, "world"), + materials: { + texturesDir: dir, + layers: [ + { + id: "base", + diff: "authored.png", + nrmBytes: image, + diffMimeType: "image/png", + nrmMimeType: "image/png", + ormBytes: image, + h: "authored.png", + }, + { id: "rock", diff: "authored.png", nrm: "authored.png" }, + ], + }, + extraLayers: { table: "terrain-table.json" }, + }); + for (const id of ["base", "rock"]) + for (const channel of id === "base" ? ["diff", "nrm", "orm", "h"] : ["diff", "nrm"]) + expect(await readFile(path.join(dir, "world", "textures", `${id}_${channel}.jpg`))).toEqual( + Buffer.from(image), + ); + const doc = await io().readBinary(cooked.worldGlb); + for (const node of doc.getRoot().getDefaultScene()?.listChildren() ?? []) { + expect(node.getExtras()).toEqual({ tnTerrainSurface: "world.json" }); + expect(node.getMesh()?.getExtras()).toEqual({ tnTerrainSurface: "world.json" }); + } + }); + + it("gives each map kind one lossless size and encoding across layers so the splat arrays stack", async () => { + const sharp = (await import("sharp")).default; + const noise = { type: "gaussian", mean: 128, sigma: 40 } as const; + const jpeg = (size: number) => + sharp({ create: { width: size, height: size, channels: 3, background: "#808040", noise } }) + .jpeg() + .toBuffer(); + // The forest's shape scaled down: rock authored at half the size of grass, dirt and moss. + const [large, small] = await Promise.all([jpeg(64), jpeg(32)]); + const largeRgba = new Uint8Array(await sharp(large).ensureAlpha().raw().toBuffer()); + const ids = ["grass", "dirt", "rock", "moss"]; + const kinds = { diff: "diffBytes", nrm: "nrmBytes", orm: "ormBytes", h: "hBytes" } as const; + await cookTerrainWorld({ + state: new Terrain({ resolution: 33, size: 256 }).evaluate(), + species: {}, + out: path.join(dir, "world"), + materials: { + layers: ids.map((id) => { + const bytes = id === "rock" ? small : large; + return { id, diffBytes: bytes, nrmBytes: bytes, ormBytes: bytes, hBytes: bytes }; + }), + }, + }); + for (const kind of Object.keys(kinds)) { + const maps = await Promise.all( + ids.map(async (id) => { + const name = `${id}_${kind}.jpg`; + const bytes = await readFile(path.join(dir, "world", "textures", name)); + const decoded = await decodeImageBytes(bytes, name); + const texture = new Texture(); + texture.image = { width: decoded.width, height: decoded.height } as never; + return { id, bytes, decoded, texture }; + }), + ); + for (const { id, bytes, decoded } of maps) { + expect([decoded.width, decoded.height, decoded.data.length]).toEqual([64, 64, 64 * 64 * 4]); + // One lossless PNG encoding for the whole kind; larger maps keep every decoded pixel. + expect([...bytes.subarray(1, 4)]).toEqual([0x50, 0x4e, 0x47]); + if (id !== "rock") expect(decoded.data).toEqual(largeRgba); + } + const copies: number[] = []; + const renderer = { + raw: { + initTexture: () => undefined, + copyTextureToTexture: (_s: unknown, _d: unknown, _r: unknown, at: { z: number }) => + copies.push(at.z), + }, + }; + const stacked = stackLayers( + maps.map((map) => map.texture), + renderer as never, + ); + expect((stacked?.image as { depth?: number } | undefined)?.depth).toBe(4); + expect(copies).toEqual([0, 1, 2, 3]); + } + }); + + it("names a missing non-base texture before writing a package", async () => { + await expect( + cookTerrainWorld({ + state: new Terrain({ resolution: 33, size: 256 }).evaluate(), + species: {}, + out: path.join(dir, "world"), + materials: { + texturesDir: dir, + layers: [ + { id: "base", diffBytes: PNG.sync.write(new PNG({ width: 2, height: 2 })) }, + { id: "rock", diff: "missing-rock.jpg" }, + ], + }, + }), + ).rejects.toThrow(/missing-rock\.jpg/u); + await expect(stat(path.join(dir, "world"))).rejects.toMatchObject({ code: "ENOENT" }); + }); + + it("cooks the real forest with index-only species chains and measures projected-error submission", async () => { + const kit = path.resolve("packages/terrain/starter/forest"); + const assets = path.resolve("packages/terrain/starter-assets"); + const recipe = JSON.parse(await readFile(path.join(kit, "recipe.json"), "utf8")); + for (const layer of recipe.recipe.layers) { + const data = layer.params?.data; + if (data?.raw16 === undefined) continue; + data.values = Array.from( + decodeRAW16(await readFile(path.join(kit, data.raw16)), data).values, + (value) => Math.round(value * 10) / 10, + ); + } + const table = JSON.parse(await readFile(path.join(kit, "assets.json"), "utf8")) as Record< + string, + { + near: string; + mid: string; + midDistance: number; + bounds: { min: [number, number, number]; max: [number, number, number] }; + maxDistance: number; + } + >; + const species = Object.fromEntries( + Object.entries(table).map(([id, spec]) => [ + id, + { + glb: path.join(assets, spec.near), + bounds: spec.bounds, + maxDistance: spec.maxDistance, + lods: [{ glb: path.join(assets, spec.mid), distance: spec.midDistance }], + }, + ]), + ); + const state = applyPlacementOverrides( + Terrain.fromJSON(recipe.recipe).evaluate(), + recipe.placementOverrides ?? {}, + ); + const surface = JSON.parse(await readFile(path.join(kit, "surface.json"), "utf8")); + const cooked = await cookTerrainWorld({ + state, + species, + out: dir, + materials: { + layers: [ + { + id: surface.base.id, + diff: path.join(assets, surface.sources[surface.base.id].diff), + nrm: path.join(assets, surface.sources[surface.base.id].nrm), + diffMimeType: "image/jpeg", + nrmMimeType: "image/jpeg", + }, + ], + }, + extraLayers: { table: "terrain-table.json" }, + }); + expect(unpackGlb(Buffer.from(cooked.worldGlb)).json.asset.extras).toEqual({ + tnTerrainWorld: 1, + }); + // The production ctx.assets.model entry must attach chains in a multi-scene cooked world. + const fetchImage = globalThis.fetch; + vi.stubGlobal("createImageBitmap", async () => ({ width: 1, height: 1, close() {} })); + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const url = String(input); + if (url.startsWith("blob:")) return fetchImage(input, init); + if (url.endsWith("assets.manifest.json")) return new Response(null, { status: 404 }); + return new Response(await readFile(path.join(dir, "world.glb"))); + }); + const loader = createAssetLoader({ basePath: "" }); + const loaded = await loader.model<{ scene: Group }>("worlds/forest/world.glb"); + const partitions: InstancedMesh[] = []; + loaded.scene.traverse((object) => { + if (object instanceof InstancedMesh && object.parent instanceof InstancedMesh) + partitions.push(object); + }); + expect(partitions.length).toBeGreaterThan(0); + loader.clear(); + const document = await new NodeIO() + .registerExtensions([...ALL_EXTENSIONS, TNDiscreteLod]) + .readBinary(cooked.worldGlb); + const camera = new PerspectiveCamera(60, 16 / 9, 0.1, 2000); + camera.position.set(256, 40, 600); + camera.lookAt(256, 10, 256); + camera.updateMatrixWorld(true); + let base = 0; + let submitted = 0; + let terrain = 0; + const rows: Record = {}; + for (const node of document.getRoot().getDefaultScene()?.listChildren() ?? []) { + const mesh = node.getMesh(); + if (mesh === null) continue; + const id = node.getName().replace(/_instances$/, ""); + const spec = species[id]; + if (spec === undefined) { + terrain += mesh.listPrimitives().reduce((sum, p) => sum + primitiveTriangleCount(p), 0); + continue; + } + const bytes = Buffer.from(await readFile(spec.glb)); + const source = await readGltfDocument(await createGltfReader(bytes), bytes); + const expected = new Document(); + const originals: ReturnType[] = []; + source + .getRoot() + .getDefaultScene() + ?.traverse((node) => { + const mesh = node.getMesh(); + if (mesh === null) return; + const copied = copyToDocument( + expected, + source, + [mesh], + createDefaultPropertyResolver(expected, source), + ).get(mesh) as GltfMesh | undefined; + assert.ok(copied); + // gltf-transform's transformMesh zeroes snorm normals, so the expectation transforms floats. + for (const primitive of copied.listPrimitives()) { + const normal = primitive.getAttribute("NORMAL"); + if (normal?.getNormalized() !== true) continue; + const type = normal.getComponentType(); + normal + .setArray( + Float32Array.from((normal.getArray() ?? []) as ArrayLike, (v) => + MathUtils.decodeNormalizedInt(v, type), + ), + ) + .setNormalized(false); + } + transformMesh(copied, node.getWorldMatrix()); + originals.push(...copied.listPrimitives()); + }); + const decodedHash = (attribute: Accessor | null): string => { + assert.ok(attribute); + const values = new Float32Array(attribute.getCount() * attribute.getElementSize()); + for (let i = 0; i < attribute.getCount(); i++) + values.set(attribute.getElement(i, []), i * attribute.getElementSize()); + return createHash("sha256").update(new Uint8Array(values.buffer)).digest("hex"); + }; + const placements = state.instances.filter((item) => item.asset === id); + const batch = node.getExtension("EXT_mesh_gpu_instancing"); + assert.ok(batch); + const translations = batch.getAttribute("TRANSLATION"); + const rotations = batch.getAttribute("ROTATION"); + const scales = batch.getAttribute("SCALE"); + assert.ok(translations && rotations && scales); + expect(translations.getCount()).toBe(placements.length); + const levels: number[][] = []; + let speciesBase = 0; + let speciesSubmitted = 0; + for (const [index, primitive] of mesh.listPrimitives().entries()) { + const original = originals[index]; + assert.ok(original); + for (const semantic of original.listSemantics()) { + expect(decodedHash(primitive.getAttribute(semantic)), `${id} decoded ${semantic}`).toBe( + decodedHash(original.getAttribute(semantic)), + ); + } + expect(primitive.getIndices()?.getArray()).toEqual(original.getIndices()?.getArray()); + expect(primitive.getMaterial()?.getAlphaMode()).toBe( + original.getMaterial()?.getAlphaMode(), + ); + expect(primitive.getMaterial()?.getBaseColorFactor()).toEqual( + original.getMaterial()?.getBaseColorFactor(), + ); + const chain = primitive.getExtension(TN_DISCRETE_LOD); + const counts = [primitiveTriangleCount(primitive), ...(chain?.getCounts() ?? [])]; + levels.push(counts); + const geometry = new BufferGeometry().setAttribute( + "position", + new BufferAttribute(primitive.getAttribute("POSITION")?.getArray() as Float32Array, 3), + ); + geometry.computeBoundingBox(); + assert.ok(geometry.boundingBox); + const size = geometry.boundingBox.getSize(new Vector3()); + // The simplifier's scale is the largest decoded extent, including the source node scale. + expect(chain?.getErrorScale(), `${id} transformed error scale`).toBeCloseTo( + Math.max(size.x, size.y, size.z), + 4, + ); + expect(chain?.getSharedVertexBuffers()).toBe(true); + geometry.computeBoundingSphere(); + assert.ok(geometry.boundingSphere); + for (let i = 0; i < translations.getCount(); i++) { + const matrix = new Matrix4().compose( + new Vector3().fromArray(translations.getElement(i, [])), + new Quaternion().fromArray(rotations.getElement(i, [])), + new Vector3().fromArray(scales.getElement(i, [])), + ); + const sphere = new Sphere().copy(geometry.boundingSphere).applyMatrix4(matrix); + const view = conservativeViewDepth(camera, sphere.center, sphere.radius, camera.near); + const level = selectLodLevel( + [0, ...(chain?.getAbsoluteErrors() ?? [])], + 0, + INSTANCED_LOD_MAX_PIXEL_ERROR / Math.max(matrix.getMaxScaleOnAxis(), Number.EPSILON), + DISCRETE_LOD_DEFAULT_HYSTERESIS, + [{ camera, viewportHeight: 1080, ...view }], + ); + speciesSubmitted += counts[level] ?? counts[0] ?? 0; + speciesBase += counts[0] ?? 0; + } + geometry.dispose(); + } + rows[id] = { + instances: placements.length, + levels, + base: speciesBase, + submitted: speciesSubmitted, + }; + base += speciesBase; + submitted += speciesSubmitted; + } + console.log( + "FOREST_LOD_CENSUS", + JSON.stringify({ + pose: [256, 40, 600], + lookAt: [256, 10, 256], + viewport: [1920, 1080], + terrain, + base: base + terrain, + submitted: submitted + terrain, + species: rows, + }), + ); + expect(Object.keys(rows).sort()).toEqual(["boulder", "fir", "fir-c"]); + for (const node of document.getRoot().getDefaultScene()?.listChildren() ?? []) { + if (!node.getName().endsWith("_instances")) continue; + expect( + node + .getMesh() + ?.listPrimitives() + .every((p) => p.getExtension(TN_DISCRETE_LOD) !== null), + ).toBe(true); + } + expect(base + terrain).toBe(505_387_464); + expect(submitted).toBeLessThan(base); + }, 120_000); + + it("writes the real terrain chunks and one instancing node for the species", async () => { + const state = evaluatedTerrain(); + const result = await cookTerrainWorld({ + state, + species: { fir: { glb: await simpleSpecies() } }, + out: dir, + }); + + const lod0 = bakeTerrain(state, { lodSteps: [1] }).lods[0]; + assert.ok(lod0); + const chunks = lod0.chunks; + expect(chunks.length).toBeGreaterThan(0); + const scene = await loadWorld(result.worldGlb); + const terrainNames: string[] = []; + const instanceCounts: number[] = []; + scene.traverse((object) => { + if (object instanceof InstancedMesh) instanceCounts.push(object.count); + else if (object instanceof Mesh && object.name.startsWith("terrain_")) + terrainNames.push(object.name); + }); + expect(terrainNames.sort()).toEqual(chunks.map((chunk) => chunk.name).sort()); + expect(instanceCounts).toEqual([state.instances.length]); + + for (const file of [ + "world.glb", + "world.json", + "heightmap.u16", + "placements.bin", + "splat.rgba", + "models/fir.glb", + ]) { + expect((await stat(path.join(dir, file))).size).toBeGreaterThan(0); + } + }); + + it("writes the splat and placement files exactly as bakeWorldPackage bakes them", async () => { + const state = evaluatedTerrain(); + const result = await cookTerrainWorld({ + state, + species: { fir: { glb: await simpleSpecies() } }, + out: dir, + }); + + const baked = bakeWorldPackage(state, { assets: result.manifest.assets }); + const splat = new Uint8Array(await readFile(path.join(dir, "splat.rgba"))); + expect(splat.some((byte) => byte !== 0)).toBe(true); + expect(splat).toEqual(baked.files["splat.rgba"]); + expect(new Uint8Array(await readFile(path.join(dir, "placements.bin")))).toEqual( + baked.files["placements.bin"], + ); + }); + + it("keeps one instancing node per species when its source has two meshes", async () => { + const state = evaluatedTerrain(); + const species = await twoMeshSpecies(); + const result = await cookTerrainWorld({ + state, + species: { fir: { glb: species.glb } }, + out: dir, + }); + + expect(result.instancedMeshCount).toBe(1); + expect(result.placementCounts.fir).toBe(state.instances.length); + const scene = await loadWorld(result.worldGlb); + const instanceCounts: number[] = []; + scene.traverse((object) => { + if (object instanceof InstancedMesh) instanceCounts.push(object.count); + }); + // One InstancedMesh per primitive; the two source meshes flatten into one glTF mesh. + expect(instanceCounts).toEqual([state.instances.length, state.instances.length]); + }); + + it("keeps source translation, texture bytes and vertex attributes when it flattens a species", async () => { + const species = await twoMeshSpecies(); + const result = await cookTerrainWorld({ + state: evaluatedTerrain(), + species: { fir: { glb: species.glb } }, + out: dir, + }); + + // The trunk's child translation becomes model-space bounds: x runs from 0 to 6. + const fir = result.manifest.assets.fir; + assert.ok(fir); + expect(fir.bounds).toEqual({ min: [0, 0, 0], max: [6, 0, 1] }); + + const doc = await io().readBinary(result.worldGlb); + const textured = doc + .getRoot() + .listMaterials() + .find((m) => m.getBaseColorTexture() !== null); + assert.ok(textured); + const baseColorTexture = textured.getBaseColorTexture(); + assert.ok(baseColorTexture); + const image = baseColorTexture.getImage(); + assert.ok(image); + expect(new Uint8Array(image)).toEqual(new Uint8Array(species.png)); + const instancedNode = doc + .getRoot() + .listNodes() + .find((node) => node.getExtension("EXT_mesh_gpu_instancing") !== null); + assert.ok(instancedNode); + const instancedMesh = instancedNode.getMesh(); + assert.ok(instancedMesh); + const primitive = instancedMesh.listPrimitives().find((p) => p.getMaterial() === textured); + assert.ok(primitive); + const positionAttr = primitive.getAttribute("POSITION"); + assert.ok(positionAttr); + const positions = positionAttr.getArray() as Float32Array; + expect(Math.min(...positions.filter((_, i) => i % 3 === 0))).toBe(5); + expect(primitive.getAttribute("NORMAL")).not.toBeNull(); + expect(primitive.getAttribute("TEXCOORD_0")).not.toBeNull(); + expect(primitive.getMode()).toBe(4); // TRIANGLES + }); + + it("keeps the unit length of quantized species normals through the flatten", async () => { + const result = await cookTerrainWorld({ + state: evaluatedTerrain(), + species: { fir: { glb: await quantizedNormalSpecies() } }, + out: dir, + }); + const doc = await io().readBinary(result.worldGlb); + const normals = doc + .getRoot() + .listNodes() + .find((node) => node.getExtension("EXT_mesh_gpu_instancing") !== null) + ?.getMesh() + ?.listPrimitives()[0] + ?.getAttribute("NORMAL"); + assert.ok(normals); + for (let i = 0; i < normals.getCount(); i += 1) { + const [x = 0, y = 0, z = 0] = normals.getElement(i, [0, 0, 0]); + expect(Math.hypot(x, y, z)).toBeCloseTo(1, 2); + } + }); + + it.each(["shared", "shared-chain", "mirror"] as const)( + "preserves each shared LOD accessor's triangle through %s compaction", + async (mode) => { + const glb = await smallLodSpecies(mode); + const result = await cookTerrainWorld({ + state: evaluatedTerrain(), + species: { fir: { glb, lods: [{ distance: 40, glb }] } }, + out: dir, + }); + const doc = await io().registerExtensions([TNDiscreteLod]).readBinary(result.worldGlb); + const primitives = + doc + .getRoot() + .getDefaultScene() + ?.listChildren() + .find((node) => node.getName() === "fir_instances") + ?.getMesh() + ?.listPrimitives() ?? []; + expect(primitives).toHaveLength(2); + for (const primitive of primitives) { + const positions = primitive.getAttribute("POSITION"); + const indices = primitive + .getExtension(TN_DISCRETE_LOD) + ?.getIndices()[0] + ?.getArray(); + assert.ok(positions && indices); + expect(positions.getCount()).toBe(4); + const corners = Array.from(indices as ArrayLike, (index) => + positions.getElement(index, [0, 0, 0]), + ); + expect(corners).toEqual( + mode === "mirror" + ? [ + [-1, 0, 0], + [0, 0, 1], + [-1, 0, 1], + ] + : [ + [1, 0, 1], + [0, 0, 1], + [1, 0, 0], + ], + ); + } + }, + ); + + it.each([ + ["unused", "indexes a vertex that its LOD0 does not draw"], + ["bad-index", "out of range"], + ["bad-level", "not a triangle list"], + ] as const)("rejects %s indices before flattening can conceal them", async (mode, message) => { + const glb = await smallLodSpecies(mode); + await expect( + cookTerrainWorld({ + state: evaluatedTerrain(), + species: { fir: { glb, lods: [{ distance: 40, glb }] } }, + out: dir, + }), + ).rejects.toThrow(message); + }); + + it("stores identical species textures once and keeps each material's sampler and UV offset", async () => { + const repeat = await texturedSpecies(REPEAT, [0.25, 0]); + const clamp = await texturedSpecies(CLAMP_TO_EDGE, [0, 0.5]); + const state = evaluatedTerrain(["fir", "pine"]); + const result = await cookTerrainWorld({ + state, + species: { + fir: { glb: repeat.glb, lods: [{ distance: 45, glb: repeat.glb }] }, + pine: { glb: clamp.glb }, + }, + out: dir, + }); + + const doc = await io().readBinary(result.worldGlb); + const images = doc.getRoot().listTextures(); + expect(images).toHaveLength(1); + const textured = doc + .getRoot() + .listMaterials() + .filter((m) => m.getBaseColorTexture() !== null); + expect(textured).toHaveLength(3); + for (const material of textured) { + const texture = material.getBaseColorTexture(); + assert.ok(texture); + expect(texture).toBe(images[0]); + expect(texture.getMimeType()).toBe("image/png"); + const image = texture.getImage(); + assert.ok(image); + expect(new Uint8Array(image)).toEqual(new Uint8Array(repeat.png)); + } + + // The image is shared, but each material keeps its own wrap mode and UV offset. + const samplers = textured.map((material) => { + const info = material.getBaseColorTextureInfo(); + assert.ok(info); + const transform = info.getExtension("KHR_texture_transform"); + return JSON.stringify([info.getWrapS(), transform?.getOffset()]); + }); + expect(samplers.sort()).toEqual( + [ + JSON.stringify([REPEAT, [0.25, 0]]), + JSON.stringify([REPEAT, [0.25, 0]]), + JSON.stringify([CLAMP_TO_EDGE, [0, 0.5]]), + ].sort(), + ); + + const count = (asset: string) => state.instances.filter((item) => item.asset === asset).length; + expect(result.placementCounts).toEqual({ fir: count("fir"), pine: count("pine") }); + const scene = await loadWorld(result.worldGlb); + const instanceCounts: number[] = []; + scene.traverse((object) => { + if (object instanceof InstancedMesh) instanceCounts.push(object.count); + }); + expect(instanceCounts.sort((a, b) => a - b)).toEqual( + [count("fir"), count("pine")].sort((a, b) => a - b), + ); + }); + + it("flattens one shared mesh at each node's own transform", async () => { + const species = await sharedMeshSpecies(); + const state = evaluatedTerrain(); + const result = await cookTerrainWorld({ + state, + species: { fir: { glb: species.glb } }, + out: dir, + }); + + expect(result.instancedMeshCount).toBe(1); + expect(result.placementCounts.fir).toBe(state.instances.length); + + const doc = await io().readBinary(result.worldGlb); + const instancedNode = doc + .getRoot() + .listNodes() + .find((node) => node.getExtension("EXT_mesh_gpu_instancing") !== null); + assert.ok(instancedNode); + const instancedMesh = instancedNode.getMesh(); + assert.ok(instancedMesh); + const primitives = instancedMesh.listPrimitives(); + // Both source nodes survive as their own primitive: one translated to x=0, one to x=5. + expect(primitives.length).toBe(2); + const minX = primitives.map((p) => { + const positionAttr = p.getAttribute("POSITION"); + assert.ok(positionAttr); + return Math.min(...(positionAttr.getArray() as Float32Array).filter((_, i) => i % 3 === 0)); + }); + expect(minX.sort((a, b) => a - b)).toEqual([0, 5]); + + const textured = doc + .getRoot() + .listMaterials() + .find((m) => m.getBaseColorTexture() !== null); + assert.ok(textured); + const baseColorTexture = textured.getBaseColorTexture(); + assert.ok(baseColorTexture); + const image = baseColorTexture.getImage(); + assert.ok(image); + expect(new Uint8Array(image)).toEqual(new Uint8Array(species.png)); + }); + + it("writes explicit LOD files and authored bounds without changing them", async () => { + const near = await simpleSpecies(); + const lod = await simpleSpecies(); + const result = await cookTerrainWorld({ + state: evaluatedTerrain(), + species: { + fir: { + glb: near, + bounds: { min: [-1, 0, -1], max: [1, 2, 1] }, + lods: [{ distance: 45, glb: lod }], + }, + }, + out: dir, + }); + + expect(result.manifest.assets.fir).toMatchObject({ + glb: "models/fir.glb", + bounds: { min: [-1, 0, -1], max: [1, 2, 1] }, + lods: [{ distance: 45, glb: "models/fir_lod1.glb" }], + }); + expect(new Uint8Array(await readFile(path.join(dir, "models", "fir.glb")))).toEqual(near); + expect(new Uint8Array(await readFile(path.join(dir, "models", "fir_lod1.glb")))).toEqual(lod); + }); + + it("fails before writing when a placement names a species that is not in the map", async () => { + await expect( + cookTerrainWorld({ state: evaluatedTerrain(), species: {}, out: dir }), + ).rejects.toThrow(/species 'fir'/); + await expect(stat(path.join(dir, "world.glb"))).rejects.toThrow(); + }); + + it("fails before writing when a placement has a non-finite position", async () => { + const state = evaluatedTerrain(); + const [first, ...rest] = state.instances; + assert.ok(first); + const broken = { + ...state, + instances: [ + { + ...first, + position: [Number.NaN, first.position[1], first.position[2]] as [number, number, number], + }, + ...rest, + ], + }; + await expect( + cookTerrainWorld({ + state: broken, + species: { fir: { glb: await simpleSpecies() } }, + out: dir, + }), + ).rejects.toThrow(/non-finite/); + await expect(stat(path.join(dir, "world.glb"))).rejects.toThrow(); + }); + + it("refuses a skinned species by name before writing", async () => { + await expect( + cookTerrainWorld({ + state: evaluatedTerrain(), + species: { fir: { glb: await skinnedSpecies() } }, + out: dir, + }), + ).rejects.toThrow(/species 'fir' .*skin/); + await expect(stat(path.join(dir, "world.glb"))).rejects.toThrow(); + }); + + it("refuses a meshless or empty-draw species by name before writing", async () => { + // Positive placement counts and zero source bounds must not hide a species with nothing to draw. + for (const glb of [await noMeshSpecies(), await emptyMeshSpecies()]) { + await expect( + cookTerrainWorld({ state: evaluatedTerrain(), species: { fir: { glb } }, out: dir }), + ).rejects.toThrow(/species 'fir' .*empty draw/); + } + await expect(stat(path.join(dir, "world.glb"))).rejects.toThrow(); + }); + + it("refuses an authored LOD that declares a skin, same as the near model", async () => { + await expect( + cookTerrainWorld({ + state: evaluatedTerrain(), + species: { + fir: { + glb: await simpleSpecies(), + lods: [{ distance: 40, glb: await skinnedSpecies() }], + }, + }, + out: dir, + }), + ).rejects.toThrow(/species 'fir' .*skin/); + await expect(stat(path.join(dir, "world.glb"))).rejects.toThrow(); + }); + + it("discovers an authored -mid sibling before falling back to simplification", async () => { + const nearPath = path.join(dir, "pine.glb"); + const midPath = path.join(dir, "pine-mid.glb"); + const near = await simpleSpecies(); + const mid = await twoMeshSpecies(); + await writeFile(nearPath, near); + await writeFile(midPath, mid.glb); + + const result = await cookTerrainWorld({ + state: evaluatedTerrain(), + species: { fir: { glb: nearPath } }, + out: dir, + }); + + expect(result.manifest.assets.fir?.lods).toHaveLength(1); + expect(result.manifest.assets.fir?.lods?.[0]?.glb).toBe("models/fir_lod1.glb"); + // The written level is the sibling's bytes, not a simplification of the near model. + expect(new Uint8Array(await readFile(path.join(dir, "models", "fir_lod1.glb")))).toEqual( + new Uint8Array(mid.glb), + ); + }); + + it("propagates a real IO error from a sibling probe instead of treating it as absent", async () => { + const nearPath = path.join(dir, "oak.glb"); + await writeFile(nearPath, await simpleSpecies()); + const sibling = path.join(dir, "oak-mid.glb"); + const ioError = Object.assign(new Error("injected EIO reading sibling"), { code: "EIO" }); + fsProbe.statFailure = { path: sibling, error: ioError }; + try { + const caught = await cookTerrainWorld({ + state: evaluatedTerrain(), + species: { fir: { glb: nearPath } }, + out: dir, + }).then( + () => null, + (error: unknown) => error, + ); + // The exact error reaches the caller; it is not swallowed as a missing sibling. + expect(caught).toBe(ioError); + await expect(stat(path.join(dir, "world.glb"))).rejects.toThrow(); + } finally { + fsProbe.statFailure = undefined; + } + }); + + it("names a placement that is mirrored in Z as a contact failure", async () => { + const state = evaluatedTerrain(); + const spacing = state.size / (state.resolution - 1); + // A deterministic north-to-south ramp makes mirroring change the ground by half the map size. + state.height.set( + Array.from(state.height, (_, i) => Math.floor(i / state.resolution) * spacing), + ); + const first = state.instances[0]; + assert.ok(first); + const item = { + ...first, + position: [0, state.size / 4, -state.size / 4] as [number, number, number], + }; + const mirrored = { + ...item, + position: [item.position[0], item.position[1], -item.position[2]] as [number, number, number], + }; + const error = await cookTerrainWorld({ + state: { ...state, instances: [mirrored] }, + species: { fir: { glb: await simpleSpecies() } }, + out: dir, + }).then( + () => null, + (caught: Error) => caught, + ); + expect(error?.message).toContain( + `Placement '${item.id}' of species 'fir' contact check failed`, + ); + expect(error?.message).toContain("mirrored-Z"); + }); + + it("checks ground contact against the quantized heightmap, not the horizontal spacing", async () => { + const state = evaluatedTerrain(); + // A real relief, so one uint16 height step is a meaningful, non-zero tolerance. + const spacing = state.size / (state.resolution - 1); + state.height.set( + Array.from(state.height, (_, i) => Math.floor(i / state.resolution) * spacing), + ); + const species = { fir: { glb: await simpleSpecies() } }; + let min = Number.POSITIVE_INFINITY; + let max = Number.NEGATIVE_INFINITY; + for (const height of state.height) { + min = Math.min(min, height); + max = Math.max(max, height); + } + // One uint16 step of the heightmap range the bake writes. + const quantStep = (max - min) / 65_535; + const first = state.instances[0]; + assert.ok(first); + const groundY = sampleHeight(state, first.position[0], first.position[2]); + const at = (delta: number): [number, number, number] => [ + first.position[0], + groundY + delta, + first.position[2], + ]; + const posed = (delta: number, grounding: boolean): ITerrainState => ({ + ...state, + instances: [ + { + ...first, + transform: { + position: at(delta), + quaternion: [0, 0, 0, 1], + scale: [1, 1, 1], + grounding, + }, + }, + ], + }); + + // A sub-texel hop passes: below one uint16 height step. + await expect( + cookTerrainWorld({ + state: posed(quantStep * 0.25, true), + species, + out: path.join(dir, "sub"), + }), + ).resolves.toBeDefined(); + + // A real offset above one height step fails by name, with no output written. + const error = await cookTerrainWorld({ + state: posed(quantStep * 4, true), + species, + out: path.join(dir, "lift"), + }).then( + () => null, + (caught: Error) => caught, + ); + expect(error?.message).toContain( + `Placement '${first.id}' of species 'fir' contact check failed`, + ); + await expect(stat(path.join(dir, "lift", "world.glb"))).rejects.toThrow(); + + // The authored grounding override is honored: the same lifted pose is skipped. + await expect( + cookTerrainWorld({ + state: posed(quantStep * 4, false), + species, + out: path.join(dir, "override"), + }), + ).resolves.toBeDefined(); + }); + + it("writes a world package core's validator accepts, with a GLB and a LOD per species", async () => { + const result = await cookTerrainWorld({ + state: evaluatedTerrain(), + species: { fir: { glb: await simpleSpecies() } }, + out: dir, + }); + + const manifest = JSON.parse( + await readFile(path.join(dir, "world.json"), "utf8"), + ) as IWorldPackage; + const placements = new Uint8Array(await readFile(path.join(dir, "placements.bin"))); + const heightmap = new Uint8Array(await readFile(path.join(dir, "heightmap.u16"))); + const validation = validateWorldPackage(manifest, { + placementsByteLength: placements.byteLength, + heightmapByteLength: heightmap.byteLength, + }); + expect(result.worldPackage?.manifest).toBe(result.manifest); + expect(result.worldPackage?.files["heightmap.u16"]).toEqual(heightmap); + expect(result.worldPackage?.files["placements.bin"]).toEqual(placements); + expect(validation.errors).toEqual([]); + expect(validation.ok).toBe(true); + + // Every species lists a model, at least one LOD, and the bytes the manifest names exist. + for (const [id, asset] of Object.entries(manifest.assets)) { + expect(asset.glb).toBe(`models/${id}.glb`); + expect(asset.lods?.length ?? 0).toBeGreaterThanOrEqual(1); + } + for (const asset of Object.values(result.manifest.assets)) { + expect((await stat(path.join(dir, asset.glb))).size).toBeGreaterThan(0); + for (const lod of asset.lods ?? []) + expect((await stat(path.join(dir, lod.glb))).size).toBeGreaterThan(0); + } + }); + + it("keeps the node count and all-scene census constant from 1 000 to 10 000 placements", async () => { + const base = evaluatedTerrain(); + const withPlacements = (count: number): ITerrainState => ({ + ...base, + instances: Array.from({ length: count }, (_, i) => { + const placement = base.instances[i % base.instances.length]; + assert.ok(placement); + return { ...placement, id: `fir_${i}` }; + }), + }); + const species = { fir: { glb: await simpleSpecies() } }; + + const small = await cookTerrainWorld({ + state: withPlacements(1_000), + species, + out: path.join(dir, "1k"), + }); + const large = await cookTerrainWorld({ + state: withPlacements(10_000), + species, + out: path.join(dir, "10k"), + }); + + expect(large.nodeCount).toBe(small.nodeCount); + expect(small.instancedMeshCount).toBe(1); + expect(large.instancedMeshCount).toBe(1); + expect(large.placementCounts.fir).toBe(10_000); + + // The authoritative walk: the default scene plus the generated alternate-LOD scene. + const census = await loadScenes(large.worldGlb); + expect(census.scenes.map((scene) => scene.name)).toEqual(["TerrainWorld", "fir_lod1"]); + const allNodes = census.scenes.flatMap((scene) => instancedNodes(scene)); + expect(allNodes).toEqual([ + { name: "fir_instances", instances: 10_000 }, + { name: "fir_lod1_instances", instances: 10_000 }, + ]); + // The default scene carries the near LOD only. + expect(instancedNodes(census.scene)).toEqual([{ name: "fir_instances", instances: 10_000 }]); + }); + + it("holds near nodes in the default scene and one node per species-and-LOD pair in named scenes", async () => { + const state = evaluatedTerrain(["fir", "boulder"]); + const near = await simpleSpecies(); + const authoredLod = await simpleSpecies(); + const result = await cookTerrainWorld({ + state, + species: { + fir: { glb: near }, // generated LOD + boulder: { glb: near, lods: [{ distance: 45, glb: authoredLod }] }, // authored LOD + }, + out: dir, + }); + + const counts: Record = { + fir: state.instances.filter((i) => i.asset === "fir").length, + boulder: state.instances.filter((i) => i.asset === "boulder").length, + }; + + const census = await loadScenes(result.worldGlb); + // One default scene plus one named scene per alternate LOD. + expect(census.scenes.map((scene) => scene.name)).toEqual([ + "TerrainWorld", + "fir_lod1", + "boulder_lod1", + ]); + // Default scene: only the near nodes, each rendering every placement once. + expect(instancedNodes(census.scene)).toEqual([ + { name: "boulder_instances", instances: counts.boulder }, + { name: "fir_instances", instances: counts.fir }, + ]); + // Every species-and-LOD pair is exactly one node, with all logical placements. + expect(instancedNodes(census.scenes[1] as Group)).toEqual([ + { name: "fir_lod1_instances", instances: counts.fir }, + ]); + expect(instancedNodes(census.scenes[2] as Group)).toEqual([ + { name: "boulder_lod1_instances", instances: counts.boulder }, + ]); + // The compatibility stat counts the default scene only; the all-scene walk is authoritative. + expect(result.instancedMeshCount).toBe(2); + }); +}); diff --git a/packages/assets/package.json b/packages/assets/package.json index 77da8576cb..c94d4b250d 100644 --- a/packages/assets/package.json +++ b/packages/assets/package.json @@ -49,6 +49,7 @@ "@gltf-transform/core": "catalog:", "@gltf-transform/extensions": "catalog:", "@gltf-transform/functions": "catalog:", + "@threenative/terrain": "workspace:*", "@wasm-audio-decoders/ogg-vorbis": "0.1.20", "draco3dgltf": "^1.5.7", "jpeg-js": "0.4.4", diff --git a/packages/assets/src/compile.ts b/packages/assets/src/compile.ts index d73f1d77bc..83be10b7c7 100644 --- a/packages/assets/src/compile.ts +++ b/packages/assets/src/compile.ts @@ -2215,9 +2215,10 @@ function recordRanTimings( /** Fails closed: every pass must account for every input, ran or cached, or nothing is emitted. */ function assertCompletePassCosts( costInputs: ReadonlyMap, - inputCount: number, + inputsForPass: (pass: string) => number, ): void { for (const [pass, record] of costInputs) { + const inputCount = inputsForPass(pass); if (record.ranInputs + record.cachedInputs !== inputCount) { throw new Error( `TN_ASSETS_PASS_COST_INCOMPLETE: pass '${pass}' accounts for ${record.ranInputs} ran + ${record.cachedInputs} cached of ${inputCount} input(s); refusing to emit a cost report with a hole in it.`, @@ -2538,13 +2539,24 @@ export async function compileAssets( * source path, so a shared output would silently serve it the canonical member's policy. */ const modelSources = logicals.filter((logical) => classify(logical) === "model"); + const cookedWorlds = new Set(); + for (const logical of modelSources) { + if (path.extname(logical).toLowerCase() !== ".glb") continue; + try { + if (unpackGlb(await readSource(logical)).json.asset.extras?.tnTerrainWorld === 1) { + cookedWorlds.add(logical); + } + } catch (error) { + throw new Error(`TN_ASSETS_MODEL_UNREADABLE: '${logical}': ${messageOf(error)}`); + } + } const perAssetLod = typeof layout.lod === "object" && layout.lod !== null && layout.lod.overrides !== undefined ? new Set(Object.keys(layout.lod.overrides)) : new Set(); const canonicalOf = await groupModelSources( layout.sourceRoot, - modelSources.filter((logical) => !perAssetLod.has(logical)), + modelSources.filter((logical) => !perAssetLod.has(logical) && !cookedWorlds.has(logical)), readSource, ); @@ -2603,8 +2615,24 @@ export async function compileAssets( }; const entries: Record = {}; const receiptOutputs: IBakeReceiptOutput[] = []; + // Cooked terrain owns its geometry and look; native only needs a portable buffer layout. + const terrainPasses = + options.platform === undefined || options.platform === "web" + ? [] + : [ + modelPass({ + vertexLayout: "separate", + compact: false, + passes: { dedup: false, meshopt: false, prune: false, quantize: false, reorder: false }, + textures: "none", + virtual: "none", + lod: false, + }), + ]; + const terrainPassNames = terrainPasses.map((pass) => pass.name); + const costInputs = new Map(); - for (const name of passNames) + for (const name of new Set([...passNames, ...terrainPassNames])) costInputs.set(name, { cachedInputs: 0, ranInputs: 0, timings: [] }); const healthInputs: IAssetHealthInput[] = []; @@ -2675,7 +2703,7 @@ export async function compileAssets( receiptOutputs.push({ bytes: entry.bytes, path: entry.output, - producer: passNames.join("+"), + producer: entry.passes.join("+"), source: logical, }); for (const output of auxiliary) receiptOutputs.push({ ...output, source: logical }); @@ -2744,12 +2772,20 @@ export async function compileAssets( ? createPassPool(concurrency, activePassSpecs, layout.outputRoot) : undefined; - /** One cooked source, published as itself. */ + /** Publish authored data with the target's vertex layout. */ const cookOne = async (logical: string): Promise => { const input = await readSource(logical); + const terrainWorld = cookedWorlds.has(logical); const digest = createHash("sha256") .update(input) - .update(passConfigurations[classify(logical)], "utf8") + .update( + terrainWorld + ? terrainPasses.length === 0 + ? "terrain-world-v1" + : "terrain-world-native-separate-v2" + : passConfigurations[classify(logical)], + "utf8", + ) .digest("hex"); // The output name carries the digest of the input bytes and the whole pass configuration, // so a previous entry under that exact name, with every file it declares still on disk, is @@ -2770,7 +2806,7 @@ export async function compileAssets( ? await readFile(path.join(layout.outputRoot, previousEntry.output)) : input; bookkeep(logical, measured, reused, reusable, undefined); - recordCachedInputs(costInputs, passNames); + recordCachedInputs(costInputs, terrainWorld ? terrainPassNames : passNames); skipped += 1; return { auxiliary: reusable, @@ -2779,8 +2815,9 @@ export async function compileAssets( measured, }; } - const applied = - pool === undefined + const applied = terrainWorld + ? await applyPasses(terrainPasses, input, logical) + : pool === undefined ? await applyPasses(activePasses, input, logical) : await pool.run(logical, input); recordRanTimings(costInputs, logical, applied.timings); @@ -2793,7 +2830,7 @@ export async function compileAssets( bytes: applied.buffer.length, kind: classify(logical), output: outputNameFor(logical, digest.slice(0, 8), applied.extension), - passes: [...passNames], + passes: [...(terrainWorld ? terrainPassNames : passNames)], ...auxiliaryFields, ...(extensions === undefined ? {} : { extensions }), ...(applied.entry === undefined @@ -2999,7 +3036,12 @@ export async function compileAssets( // The report runs unconditionally — it is the one place a user learns why their game is // slow. Only declared targets can fail it. - assertCompletePassCosts(costInputs, logicals.length); + assertCompletePassCosts( + costInputs, + (pass) => + (passNames.includes(pass) ? logicals.length - cookedWorlds.size : 0) + + (terrainPassNames.includes(pass) ? cookedWorlds.size : 0), + ); const passCosts = buildPassCostRows(costInputs); const report = await runHealthReport(healthInputs, layout.targets); for (const line of formatHealthReport(report)) console.log(line); diff --git a/packages/assets/src/index.ts b/packages/assets/src/index.ts index 1fc00ba3ba..11340db53f 100644 --- a/packages/assets/src/index.ts +++ b/packages/assets/src/index.ts @@ -258,3 +258,20 @@ export type { IDedupeCensus, IMaterialBucket, IMaterialState } from "./content/d */ export { censusDocument, materialStateOf, totalCensus } from "./content/census.js"; export type { IContentCensus } from "./content/census.js"; +/** + * Bake evaluated terrain and static species into an instanced GLB and streaming world package. + * @situation bake a terrain world headlessly into a GLB with instanced trees and rocks + * @situation generate a forest world for my game + * @example await cookTerrainWorld({ state, species, out: "assets/world" }); + */ +export { cookTerrainWorld } from "./world/terrain-world.js"; +export type { + ICookTerrainWorldMaterialLayer, + ICookTerrainWorldOptions, + ICookTerrainWorldResult, + IPreparedTerrainWorldOptions, + ISpeciesLODSpec, + ISpeciesSpec, + IWorldPackageAsset, + IWorldPackageManifest, +} from "./world/terrain-world.js"; diff --git a/packages/assets/src/lod/eligibility.ts b/packages/assets/src/lod/eligibility.ts index d9ff657510..dc52d6ef3d 100644 --- a/packages/assets/src/lod/eligibility.ts +++ b/packages/assets/src/lod/eligibility.ts @@ -136,6 +136,14 @@ function finitePositions(primitive: Primitive): boolean { for (let index = 0; index < array.length; index += 1) { if (!Number.isFinite(array[index] as number)) return false; } + // Complete, unnormalized vectors of finite components necessarily have finite bounds. + if ( + !position.getNormalized() && + position.getType() === "VEC3" && + array.length > 0 && + array.length % 3 === 0 + ) + return true; const min = position.getMin([0, 0, 0]); const max = position.getMax([0, 0, 0]); return min.every(Number.isFinite) && max.every(Number.isFinite); @@ -187,25 +195,27 @@ function nonManifold(primitive: Primitive): boolean { if (indices === null) return false; const array = indices.getArray(); if (array === null) return false; - const seen = new Map(); + const edges = new Float64Array(array.length); + let written = 0; + const vertices = primitive.getAttribute("POSITION")?.getCount() ?? 0; for (let index = 0; index + 2 < array.length; index += 3) { const a = array[index] as number; const b = array[index + 1] as number; const c = array[index + 2] as number; if (a === b || b === c || a === c) continue; - for (const [u, v] of [ - [a, b], - [b, c], - [c, a], - ] as const) { + for (let edge = 0; edge < 3; edge++) { + const u = edge === 0 ? a : edge === 1 ? b : c; + const v = edge === 0 ? b : edge === 1 ? c : a; const low = Math.min(u, v); const high = Math.max(u, v); - const key = low * 0x1_0000_0000 + high; - const count = (seen.get(key) ?? 0) + 1; - if (count > 2) return true; - seen.set(key, count); + edges[written++] = low * vertices + high; } } + // Numeric typed-array sorting avoids a boxed Map entry for every distinct edge. + const sorted = edges.subarray(0, written).sort(); + for (let index = 2; index < sorted.length; index++) { + if (sorted[index] === sorted[index - 2]) return true; + } return false; } diff --git a/packages/assets/src/lod/generate.ts b/packages/assets/src/lod/generate.ts index 3d2f08815a..30366c8237 100644 --- a/packages/assets/src/lod/generate.ts +++ b/packages/assets/src/lod/generate.ts @@ -6,6 +6,7 @@ // reconstructed, and the authored GLB is never overwritten. import { createHash } from "node:crypto"; +import { MathUtils } from "@gltf-transform/core"; import type { Accessor, Document, @@ -35,6 +36,7 @@ import { classifyPrimitive, primitiveTriangleCount, } from "./eligibility.js"; +import type { Simplify } from "./simplify-pool.js"; export type { DiscreteLodSkipReason, LodMinTrianglesScope } from "./eligibility.js"; import { @@ -556,12 +558,11 @@ function positionsOf(primitive: Primitive): Float32Array | null { if (array === null) return null; if (array instanceof Float32Array && !position.getNormalized()) return array; const floats = new Float32Array(position.getCount() * 3); - const element: number[] = [0, 0, 0]; - for (let vertex = 0; vertex < position.getCount(); vertex += 1) { - position.getElement(vertex, element); - floats[vertex * 3] = element[0] as number; - floats[vertex * 3 + 1] = element[1] as number; - floats[vertex * 3 + 2] = element[2] as number; + const normalized = position.getNormalized(); + const componentType = position.getComponentType(); + for (let index = 0; index < floats.length; index += 1) { + const value = array[index] as number; + floats[index] = normalized ? MathUtils.decodeNormalizedInt(value, componentType) : value; } return floats; } @@ -607,16 +608,22 @@ function packAttributes(primitive: Primitive, vertexCount: number): IAttributePa for (const { accessor } of accessors) stride += accessor.getElementSize(); if (stride === 0) return { data: new Float32Array(0), stride: 0, weights: [] }; const data = new Float32Array(vertexCount * stride); - const element: number[] = new Array(stride).fill(0); - for (let vertex = 0; vertex < vertexCount; vertex += 1) { - let offset = 0; - for (const { accessor } of accessors) { - accessor.getElement(vertex, element); - const size = accessor.getElementSize(); - for (let component = 0; component < size; component += 1) - data[vertex * stride + offset + component] = element[component] as number; - offset += size; + let offset = 0; + for (const { accessor } of accessors) { + const size = accessor.getElementSize(); + const array = accessor.getArray(); + if (array === null) throw new Error("LOD attribute has no array"); + const normalized = accessor.getNormalized(); + const componentType = accessor.getComponentType(); + for (let vertex = 0; vertex < vertexCount; vertex += 1) { + for (let component = 0; component < size; component += 1) { + const value = array[vertex * size + component] as number; + data[vertex * stride + offset + component] = normalized + ? MathUtils.decodeNormalizedInt(value, componentType) + : value; + } } + offset += size; } return { data, stride, weights: new Array(stride).fill(1) }; } @@ -635,6 +642,13 @@ interface IGeneratedChain { readonly vertexCopies?: readonly ICardCopies[]; } +/** Snapshot once per primitive; worker targets read the same bytes without cloning them each time. */ +function sharedBuffer(array: Float32Array | Uint32Array): SharedArrayBuffer { + const buffer = new SharedArrayBuffer(array.byteLength); + new Uint8Array(buffer).set(new Uint8Array(array.buffer, array.byteOffset, array.byteLength)); + return buffer; +} + /** * Every level is derived from the same LOD0 reference — never from the previous level — so the * reported error is relative to the authored geometry and a change to one target cannot disturb @@ -646,6 +660,7 @@ async function generateChain( maxLevels: number, errorTargets: readonly number[], minSaving: number, + simplify?: Simplify, ): Promise { const position = primitive.getAttribute("POSITION"); if (position === null) return null; @@ -658,28 +673,29 @@ async function generateChain( const scale = MeshoptSimplifier.getScale(positions, 3); const attributes = packAttributes(primitive, vertexCount); - const candidates: { error: number; indices: Uint32Array; triangles: number }[] = []; - for (const targetError of errorTargets) { - // target_index_count 3 is the smallest triangle list; the error target is the binding - // constraint, which is what makes this an error-driven chain rather than a ratio ladder. - const [simplified, error] = MeshoptSimplifier.simplifyWithAttributes( - indices, - positions, - 3, - attributes.data, - attributes.stride, - attributes.weights, - null, - 3, - targetError, - ["LockBorder"], - ); - candidates.push({ - error, - indices: Uint32Array.from(simplified), - triangles: Math.floor(simplified.length / 3), - }); - } + const inputIndices = simplify ? new Uint32Array(sharedBuffer(indices)) : indices; + const inputPositions = simplify ? new Float32Array(sharedBuffer(positions)) : positions; + const inputAttributes = simplify + ? new Float32Array(sharedBuffer(attributes.data)) + : attributes.data; + const candidates = await Promise.all( + errorTargets.map(async (targetError) => { + // Each target derives from LOD0; the worker path changes only where the same call runs. + const [simplified, error] = await (simplify ?? MeshoptSimplifier.simplifyWithAttributes)( + inputIndices, + inputPositions, + 3, + inputAttributes, + attributes.stride, + attributes.weights, + null, + 3, + targetError, + ["LockBorder"], + ); + return { error, indices: simplified, triangles: Math.floor(simplified.length / 3) }; + }), + ); const kept = selectDiscreteLevels(candidates, maxLevels, lod0Triangles, minSaving); if (kept.length === 0) return null; const keptSet = new Set(kept); @@ -1367,10 +1383,13 @@ export async function generateDiscreteLod( sourceDigest: string, legacy: ILodLegacyFlags = {}, now: () => number = () => Date.now(), + constraints: { readonly preserveVertices?: boolean; readonly simplify?: Simplify } = {}, ): Promise { const started = now(); const policy = resolveLodPolicy(lod, logicalPath, legacy); - const fingerprint = policy.fingerprint.generation; + const fingerprint = constraints.preserveVertices + ? lodFingerprint({ generation: policy.fingerprint.generation, preserveVertices: true }) + : policy.fingerprint.generation; // Before every early return: a `BLEND` needle card is refused a chain by rule, so the conversion // is what makes one possible at all — and an asset can opt into it with `enabled: false`. const cutout = policy.cutout @@ -1438,9 +1457,10 @@ export async function generateDiscreteLod( policy.generation.maxLevels, policy.generation.errorTargets, policy.generation.minSaving, + constraints.simplify, ); const thinned = - chain === null + chain === null && !constraints.preserveVertices ? generateCardsChain(primitive, policy.generation.maxLevels, policy.generation.minSaving) : null; chain ??= thinned; diff --git a/packages/assets/src/lod/simplify-pool.ts b/packages/assets/src/lod/simplify-pool.ts new file mode 100644 index 0000000000..b2e8db068d --- /dev/null +++ b/packages/assets/src/lod/simplify-pool.ts @@ -0,0 +1,103 @@ +import { Worker } from "node:worker_threads"; +import type { MeshoptSimplifier } from "meshoptimizer"; +import { defaultConcurrency } from "../worker-pool.js"; + +type SimplifyArgs = Parameters; +type SimplifyResult = ReturnType; +export type Simplify = (...args: SimplifyArgs) => Promise; + +/** Independent error targets use identical inputs; merge in target order, never arrival order. */ +export function createSimplifyPool(targets: number): { + simplify: Simplify; + dispose: () => Promise; +} { + const workers = Array.from({ length: Math.min(targets, defaultConcurrency()) }, () => { + const worker = new Worker( + `const { parentPort, workerData } = require('node:worker_threads'); + import(workerData).then(async ({ MeshoptSimplifier }) => { + await MeshoptSimplifier.ready; + parentPort.on('message', (args) => { + try { + const result = MeshoptSimplifier.simplifyWithAttributes(...args); + parentPort.postMessage({ result }, [result[0].buffer]); + } catch (error) { + parentPort.postMessage({ error: error.message }); + } + }); + }).catch(error => { throw error; });`, + { eval: true, workerData: import.meta.resolve("meshoptimizer/simplifier") }, + ); + let failure: Error | undefined; + let rejectPending: ((error: Error) => void) | undefined; + worker.on("error", (error) => { + failure = error; + rejectPending?.(error); + }); + worker.on("exit", () => { + failure ??= new Error("Terrain LOD worker exited before replying"); + rejectPending?.(failure); + }); + let tail: Promise = Promise.resolve(); + const run = (args: SimplifyArgs): Promise => { + const job = tail.then( + () => + new Promise((resolve, reject) => { + if (failure) return reject(failure); + const onMessage = (reply: { result?: SimplifyResult; error?: string }) => { + rejectPending = undefined; + if (reply.result) resolve(reply.result); + else reject(new Error(reply.error ?? "Terrain LOD worker returned no result")); + }; + rejectPending = (error) => { + worker.off("message", onMessage); + rejectPending = undefined; + reject(error); + }; + worker.once("message", onMessage); + worker.postMessage(args); + }), + ); + tail = job.catch(() => {}); + return job; + }; + return { worker, run }; + }); + const available = [...workers]; + const queued: { + args: SimplifyArgs; + resolve: (result: SimplifyResult) => void; + reject: (error: Error) => void; + }[] = []; + let disposed = false; + const dispatch = () => { + while (available.length && queued.length) { + const slot = available.shift(); + const job = queued.shift(); + if (!slot || !job) break; + void slot + .run(job.args) + .then(job.resolve, job.reject) + .finally(() => { + if (disposed) return; + available.push(slot); + dispatch(); + }); + } + }; + return { + // Give the next target to a free worker; slow targets must not hold a private queue. + simplify: (...args) => + new Promise((resolve, reject) => { + if (disposed) return reject(new Error("Terrain LOD worker exited before replying")); + if (!workers.length) return reject(new Error("No terrain LOD workers")); + queued.push({ args, resolve, reject }); + dispatch(); + }), + dispose: async () => { + disposed = true; + for (const job of queued.splice(0)) + job.reject(new Error("Terrain LOD worker exited before replying")); + await Promise.all(workers.map(({ worker }) => worker.terminate())); + }, + }; +} diff --git a/packages/assets/src/passes/audio-pcm.ts b/packages/assets/src/passes/audio-pcm.ts index 10f8540560..f9b6697432 100644 --- a/packages/assets/src/passes/audio-pcm.ts +++ b/packages/assets/src/passes/audio-pcm.ts @@ -1,6 +1,3 @@ -import { OggVorbisDecoder } from "@wasm-audio-decoders/ogg-vorbis"; -import { createOggEncoder } from "wasm-media-encoders"; - /** * Decoding and encoding for the audio pass, in-process through WASM. * @@ -88,6 +85,7 @@ export async function decodeAudioBytes(bytes: Buffer, logicalPath: string): Prom } async function decodeVorbis(bytes: Buffer, logicalPath: string): Promise { + const { OggVorbisDecoder } = await import("@wasm-audio-decoders/ogg-vorbis"); const decoder = new OggVorbisDecoder(); await decoder.ready; try { @@ -254,6 +252,7 @@ export async function encodeVorbis( `TN_ASSETS_AUDIO_CHANNELS: '${logicalPath}' reached the encoder with ${String(channels.length)} channel(s); Vorbis encoding here handles mono and stereo.`, ); } + const { createOggEncoder } = await import("wasm-media-encoders"); const encoder = await createOggEncoder(); encoder.configure({ channels: channels.length, diff --git a/packages/assets/src/passes/lightmap-bake.ts b/packages/assets/src/passes/lightmap-bake.ts index e6b4abd8b0..75c23107ee 100644 --- a/packages/assets/src/passes/lightmap-bake.ts +++ b/packages/assets/src/passes/lightmap-bake.ts @@ -1,7 +1,6 @@ import type { Document, Node, Primitive } from "@gltf-transform/core"; import type { Light } from "@gltf-transform/extensions"; import { BufferAttribute, BufferGeometry, DoubleSide, Ray, Vector3 } from "three"; -import { MeshBVH } from "three-mesh-bvh"; type Vec2 = readonly [number, number]; type Vec3 = readonly [number, number, number]; @@ -235,7 +234,8 @@ interface IOcclusionIndex { hits(origin: Vec3, direction: Vec3, maximum: number): boolean; } -function buildOcclusionIndex(triangles: readonly ITriangle[]): IOcclusionIndex { +async function buildOcclusionIndex(triangles: readonly ITriangle[]): Promise { + const { MeshBVH } = await import("three-mesh-bvh"); const geometry = new BufferGeometry(); geometry.setAttribute( "position", @@ -412,12 +412,12 @@ function dilate( return filled; } -export function bakeStaticLightmap( +export async function bakeStaticLightmap( document: Document, width: number, height: number, padding: number, -): ILightmapBakeResult { +): Promise { const nodes = reachableNodes(document); const triangles = sceneTriangles(document, nodes); const lights = sceneLights(nodes); @@ -426,7 +426,7 @@ export function bakeStaticLightmap( "TN_ASSETS_LIGHTMAP_LIGHTS_MISSING: the static scene contains no punctual light.", ); } - const occlusion = buildOcclusionIndex(triangles); + const occlusion = await buildOcclusionIndex(triangles); const raster = rasterize(triangles, lights, occlusion, width, height); occlusion.dispose(); const validTexels = raster.valid.reduce((sum, value) => sum + (value === 1 ? 1 : 0), 0); diff --git a/packages/assets/src/passes/lightmap.ts b/packages/assets/src/passes/lightmap.ts index 55b22f76f5..9e7f377ec0 100644 --- a/packages/assets/src/passes/lightmap.ts +++ b/packages/assets/src/passes/lightmap.ts @@ -3,8 +3,7 @@ import { createRequire } from "node:module"; import { type Accessor, type Document, NodeIO } from "@gltf-transform/core"; import { ALL_EXTENSIONS } from "@gltf-transform/extensions"; -import { Api } from "xatlasjs/dist/node/api.mjs"; -import createXAtlasModule from "xatlasjs/dist/node/xatlas.js"; +import type { Api } from "xatlasjs/dist/node/api.mjs"; import type { IAssetPass, IAssetPassOutput } from "../compile.js"; import { bakeStaticLightmap } from "./lightmap-bake.js"; import { encodeLinearRgbaKtx2 } from "./texture.js"; @@ -82,6 +81,10 @@ async function readDocument(input: Buffer, logicalPath: string): Promise>> { + const [{ Api }, { default: createXAtlasModule }] = await Promise.all([ + import("xatlasjs/dist/node/api.mjs"), + import("xatlasjs/dist/node/xatlas.js"), + ]); const XAtlas = Api(createXAtlasModule); const wasmPath = createRequire(import.meta.url).resolve("xatlasjs/dist/node/xatlas.wasm"); let loaded = (): void => {}; @@ -250,7 +253,7 @@ async function bakeUv2( { blockAlign: true, padding, resolution: atlasSize, texelsPerUnit: 0 }, ); writeAtlas(document, rows, atlas); - const bake = bakeStaticLightmap(document, atlas.width, atlas.height, padding); + const bake = await bakeStaticLightmap(document, atlas.width, atlas.height, padding); const lightmap = await encodeLinearRgbaKtx2(bake.pixels, bake.width, bake.height, astc); return { auxiliaryOutputs: [ diff --git a/packages/assets/src/passes/model.ts b/packages/assets/src/passes/model.ts index 3aa9259f7d..285e65c400 100644 --- a/packages/assets/src/passes/model.ts +++ b/packages/assets/src/passes/model.ts @@ -912,13 +912,13 @@ export function modelPass(options: IModelPassOptions = {}): IAssetPass { options.lod === undefined || options.lod === false || options.lod === "none" ? undefined : options.lod; - const geometryActive = - options.vertexLayout !== undefined || + const transformsActive = Object.values(enabled).some(Boolean) || options.simplify !== undefined || lodOptions !== undefined || virtualOptions !== undefined || compactRequested(options.compact); + const geometryActive = options.vertexLayout !== undefined || transformsActive; if (!geometryActive && textureOptions === undefined) return input; const document = await readDocument(input, logicalPath); @@ -938,6 +938,15 @@ export function modelPass(options: IModelPassOptions = {}): IAssetPass { extension.dispose(); } const source = reachableStats(document.getRoot()); + if (!transformsActive && textureOptions === undefined) { + const { buffer, extensions, verified } = await writeAndVerify( + document, + logicalPath, + options.vertexLayout, + ); + assertNoDrift(source, reachableStats(verified), logicalPath); + return { buffer, entry: { extensions } }; + } const jointFloor = jointComponentSizes(document.getRoot()); // Fixed order: dedup → prune → simplify → reorder → quantize → textures → meshopt. diff --git a/packages/assets/src/passes/shared-images.ts b/packages/assets/src/passes/shared-images.ts index d922f2e353..5ef7f5e4d1 100644 --- a/packages/assets/src/passes/shared-images.ts +++ b/packages/assets/src/passes/shared-images.ts @@ -338,17 +338,35 @@ export function unpackGlb(buffer: Buffer): { if (buffer.length < 20 || buffer.readUInt32LE(0) !== GLB_MAGIC) { throw new Error("TN_ASSETS_MODEL_UNREADABLE: not a GLB container."); } + if (buffer.readUInt32LE(4) !== 2 || buffer.readUInt32LE(8) !== buffer.length) { + throw new Error("TN_ASSETS_MODEL_UNREADABLE: invalid GLB version or length."); + } let offset = 12; let json: GLTF.IGLTF | undefined; let bin: Uint8Array | undefined; while (offset + 8 <= buffer.length) { const length = buffer.readUInt32LE(offset); const type = buffer.readUInt32LE(offset + 4); + if ( + length % 4 !== 0 || + offset + 8 + length > buffer.length || + (offset === 12 && type !== CHUNK_JSON) + ) { + throw new Error("TN_ASSETS_MODEL_UNREADABLE: invalid GLB chunk."); + } const chunk = buffer.subarray(offset + 8, offset + 8 + length); if (type === CHUNK_JSON) json = JSON.parse(chunk.toString("utf8")) as GLTF.IGLTF; else if (type === CHUNK_BIN) bin = chunk; offset += 8 + length; } + if ( + offset !== buffer.length || + json === null || + typeof json !== "object" || + json?.asset?.version !== "2.0" + ) { + throw new Error("TN_ASSETS_MODEL_UNREADABLE: invalid GLB JSON or chunk length."); + } if (json === undefined) throw new Error("TN_ASSETS_MODEL_UNREADABLE: GLB has no JSON chunk."); return { bin, json }; } diff --git a/packages/assets/src/world/terrain-world.ts b/packages/assets/src/world/terrain-world.ts new file mode 100644 index 0000000000..ae4dcd1cc9 --- /dev/null +++ b/packages/assets/src/world/terrain-world.ts @@ -0,0 +1,788 @@ +import { createHash } from "node:crypto"; +import { mkdir, readFile, stat, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { + Document, + Extension, + type GLTF, + type Buffer as GltfBuffer, + MathUtils, + type Mesh, + type Node, + NodeIO, + type Primitive, + PropertyType, + type Scene, + type mat4, +} from "@gltf-transform/core"; +import { ALL_EXTENSIONS, EXTMeshGPUInstancing } from "@gltf-transform/extensions"; +import { + compactPrimitive, + copyToDocument, + createDefaultPropertyResolver, + dedup, + getBounds, + prune, + transformPrimitive, + unpartition, +} from "@gltf-transform/functions"; +import { bakeTerrain, bakeWorldPackage, decodeRAW16, sampleHeight } from "@threenative/terrain"; +import type { IPlacement, ITerrainState } from "@threenative/terrain"; +import { createGltfReader, readGltfDocument } from "../gltf-io.js"; +import { type DiscreteLod, TNDiscreteLod, TN_DISCRETE_LOD } from "../lod/extension.js"; +import { LOD_ERROR_TARGETS, generateDiscreteLod, validateDiscreteLod } from "../lod/generate.js"; +import { createSimplifyPool } from "../lod/simplify-pool.js"; +import { modelPass } from "../passes/model.js"; + +/** The world.json contract, taken from the shared baker rather than restated here. */ +export type IWorldPackageManifest = ReturnType["manifest"]; +/** One streamed species entry, taken from the shared baker's own option type. */ +export type IWorldPackageAsset = NonNullable; + +export interface ISpeciesLODSpec { + readonly glb?: string | Uint8Array; + readonly distance: number; +} + +export interface ISpeciesSpec { + readonly glb: string | Uint8Array; + readonly bounds?: { + readonly min: readonly [number, number, number]; + readonly max: readonly [number, number, number]; + }; + readonly lods?: readonly ISpeciesLODSpec[]; + readonly maxDistance?: number; +} + +export interface ICookTerrainWorldMaterialLayer { + readonly id: string; + readonly diff?: string | Uint8Array; + readonly nrm?: string | Uint8Array; + readonly orm?: string | Uint8Array; + readonly h?: string | Uint8Array; + readonly diffBytes?: Uint8Array; + readonly nrmBytes?: Uint8Array; + readonly ormBytes?: Uint8Array; + readonly hBytes?: Uint8Array; + readonly diffMimeType?: string; + readonly nrmMimeType?: string; +} + +export interface ICookTerrainWorldOptions { + readonly state: ITerrainState; + readonly species: Readonly>; + readonly out: string; + readonly materials?: { + readonly layers?: readonly ICookTerrainWorldMaterialLayer[]; + readonly texturesDir?: string; + }; + readonly cellSize?: number; + readonly extraLayers?: Readonly>; +} + +export interface ICookTerrainWorldResult { + readonly worldGlb: Uint8Array; + readonly manifest: IWorldPackageManifest; + /** Reuse the validated binary package instead of baking it again for companion files. */ + readonly worldPackage?: ReturnType; + readonly placementCounts: Readonly>; + /** Every node in the document, across the default and every alternate-LOD scene. */ + readonly nodeCount: number; + /** + * Instancing nodes in the default `TerrainWorld` scene only, so an existing consumer keeps + * reading one node per species. Alternate LODs live in their own named scenes and are not + * counted here; walk `getRoot().listScenes()` for the authoritative all-scene census. + */ + readonly instancedMeshCount: number; +} + +interface ILayerMap { + readonly layer: ICookTerrainWorldMaterialLayer; + readonly index: number; + readonly channel: "diff" | "nrm" | "orm" | "h"; + bytes: Uint8Array; + /** Set once `normalizeKind` re-encodes the map. */ + mimeType?: string; +} + +/** + * One kind of map (diff, nrm, orm or h) at one size and one input encoding across every layer, so + * `loadTerrainSplat` can stack it into one array: one texture binding and one sampler instead of + * one per layer. This normalizes inputs only. The asset compiler still picks each texture's codec + * from its content, so a compiled kind can still mix codecs, and a mixed kind does not stack. + * A kind whose maps already share size, format and alpha keeps its authored bytes. Otherwise every + * map, the largest included, becomes lossless PNG at the largest width and height: decoded pixels + * kept, smaller maps resampled up, never down. + */ +async function normalizeKind(maps: readonly ILayerMap[]): Promise { + if (maps.length < 2) return; + const sharp = (await import("sharp")).default; + const sizes = await Promise.all(maps.map((map) => sharp(map.bytes).metadata())); + const signature = (size: (typeof sizes)[number]) => + `${String(size.width)}x${String(size.height)} ${size.format} ${String(size.hasAlpha)}`; + if (sizes.every((size) => signature(size) === signature(sizes[0] as (typeof sizes)[number]))) + return; + const width = Math.max(...sizes.map((size) => size.width)); + const height = Math.max(...sizes.map((size) => size.height)); + const alpha = sizes.some((size) => size.hasAlpha); + await Promise.all( + maps.map(async (map, i) => { + let image = sharp(map.bytes); + if (sizes[i]?.width !== width || sizes[i]?.height !== height) + image = image.resize(width, height, { fit: "fill" }); + if (alpha) image = image.ensureAlpha(); + map.bytes = new Uint8Array(await image.png().toBuffer()); + map.mimeType = "image/png"; + }), + ); +} + +/** A placement as a world-package record: `item.transform` when authored, else the scatter pose. */ +function poseOf(item: IPlacement): { + position: [number, number, number]; + quaternion: [number, number, number, number]; + scale: [number, number, number]; + grounding: boolean; +} { + if (item.transform !== undefined) { + const { position, quaternion, scale, grounding = true } = item.transform; + return { + position: [position[0], position[1], position[2]], + quaternion: [quaternion[0], quaternion[1], quaternion[2], quaternion[3]], + scale: [scale[0], scale[1], scale[2]], + grounding, + }; + } + return { + position: [item.position[0], item.position[1], item.position[2]], + quaternion: [0, Math.sin(item.rotation / 2), 0, Math.cos(item.rotation / 2)], + scale: [item.scale, item.scale, item.scale], + grounding: true, + }; +} + +async function resolveBytes(input: string | Uint8Array): Promise { + if (typeof input === "string") { + return readFile(input); + } + return input; +} + +/** A species id becomes an output filename, so refuse anything that is not a bare name. */ +const SAFE_SPECIES_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/; + +/** Source world-space bounds when the species authors none: geometry plus its node world matrices. */ +function sourceBounds(speciesDoc: Document): { + min: [number, number, number]; + max: [number, number, number]; +} { + const root = speciesDoc.getRoot(); + const scene = root.getDefaultScene() ?? root.listScenes()[0] ?? null; + if (scene) { + const { min, max } = getBounds(scene); + if (min.every(Number.isFinite) && max.every(Number.isFinite)) { + return { min: [min[0], min[1], min[2]], max: [max[0], max[1], max[2]] }; + } + } + return { min: [0, 0, 0], max: [0, 0, 0] }; +} + +/** The mesh-bearing nodes under a species' default scene, in traversal order. */ +function meshNodesOf(speciesDoc: Document): { node: Node; mesh: Mesh }[] { + const root = speciesDoc.getRoot(); + const scene = root.getDefaultScene() ?? root.listScenes()[0] ?? null; + const meshNodes: { node: Node; mesh: Mesh }[] = []; + scene?.traverse((node: Node) => { + const mesh = node.getMesh(); + if (mesh) meshNodes.push({ node, mesh }); + }); + return meshNodes; +} + +/** + * Refuse a species that cannot instance, by name, before a byte is written: a deformer, an + * animation replay, or an actually-empty draw (no mesh, no primitive, or no positioned vertices). + * A positive placement count and zero source bounds must not hide a species with nothing to draw. + */ +function assertInstancable(speciesId: string, speciesDoc: Document): void { + if (speciesDoc.getRoot().listSkins().length > 0) { + throw new Error( + `cookTerrainWorld: species '${speciesId}' declares a skin; only static species can be instanced.`, + ); + } + if (speciesDoc.getRoot().listAnimations().length > 0) { + throw new Error( + `cookTerrainWorld: species '${speciesId}' declares an animation; only static species can be instanced.`, + ); + } + const meshNodes = meshNodesOf(speciesDoc); + const primitives = meshNodes.flatMap(({ mesh }) => mesh.listPrimitives()); + const positioned = primitives.some((primitive) => { + const positions = primitive.getAttribute("POSITION")?.getArray(); + return positions !== undefined && positions !== null && positions.length > 0; + }); + if (meshNodes.length === 0 || primitives.length === 0 || !positioned) { + throw new Error( + `cookTerrainWorld: species '${speciesId}' has no instancable mesh geometry (empty draw); a static species must ship a positioned primitive.`, + ); + } +} + +/** Parse one species or LOD GLB. The same bytes are parsed exactly once per level. */ +async function parseSpecies(bytes: Uint8Array): Promise { + const input = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength); + const reader = await createGltfReader(input); + const doc = await readGltfDocument(reader, input); + validateDiscreteLod(doc.getRoot()); + return doc; +} + +/** + * Authored sibling LODs beside a path species: `-mid.glb`, else `-lod1.glb`, where + * `` drops one trailing detail marker (`-near`, `-lod1`, `-lod2`, …) from the file name. + * Returns nothing when the caller authored `lods` explicitly or no sibling exists; the caller + * then falls back to `modelPass({ simplify })`. + */ +async function authoredSiblingLods(glbPath: string): Promise<{ distance: number; glb: string }[]> { + const dir = path.dirname(glbPath); + const base = path.basename(glbPath).replace(/\.glb$/i, ""); + const root = base.replace(/-(near|hi|lod\d+)$/i, ""); + for (const candidate of [`${root}-mid.glb`, `${root}-lod1.glb`]) { + const file = path.join(dir, candidate); + if (file === glbPath) continue; + try { + await stat(file); + return [{ distance: 40, glb: file }]; + } catch (error) { + // Only a missing sibling means "try the next name"; any other IO error is real. + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + } + } + return []; +} + +function isExtensionConstructor(ctor: unknown): ctor is new (doc: Document) => Extension { + return typeof ctor === "function" && ctor.prototype instanceof Extension; +} + +/** + * `transformMesh` writes a normalized-integer NORMAL back through `decodeNormalizedInt` instead of + * `encodeNormalizedInt`, so a quantized model's normals all become zero: no light reaches the + * prop and screen-space occlusion reads it as fully occluded. Float normals survive its transform. + */ +function dequantizeNormals(mesh: Mesh): void { + for (const primitive of mesh.listPrimitives()) { + const normal = primitive.getAttribute("NORMAL"); + const source = normal?.getArray(); + if (normal === null || source == null || !normal.getNormalized()) continue; + const type = normal.getComponentType(); + const decoded = new Float32Array(source.length); + for (let index = 0; index < source.length; index++) + decoded[index] = MathUtils.decodeNormalizedInt(source[index] as number, type); + normal.setArray(decoded).setNormalized(false); + } +} + +const UNUSED_VERTEX = 0xffffffff; + +/** gltf-transform's compaction plan: vertices renumbered in the order the LOD0 indices reach them. */ +function firstReferencePlan(primitive: Primitive): { remap: Uint32Array; count: number } { + const vertexCount = primitive.getAttribute("POSITION")?.getCount() ?? 0; + const indices = primitive.getIndices()?.getArray(); + const remap = new Uint32Array(vertexCount).fill(UNUSED_VERTEX); + let count = 0; + if (indices == null) + for (let vertex = 0; vertex < vertexCount; vertex += 1) remap[vertex] = count++; + else + for (const index of indices) { + if (!Number.isInteger(index) || index < 0 || index >= vertexCount) + throw new Error(`Species LOD0 index ${index} is out of range for ${vertexCount} vertices.`); + if (remap[index] === UNUSED_VERTEX) remap[index] = count++; + } + return { remap, count }; +} + +/** + * `transformMesh`, in place. It compacts each primitive first, renumbering the vertices in the + * order the LOD0 indices first reach them, and reverses LOD0's winding under a mirroring matrix. + * The index-only TN_discrete_lod levels were baked against the source numbering and are an + * extension it does not know, so it left them indexing other vertices: every far tree drew cones + * and slivers. The same plan and the same winding reach every level here. + */ +function transformWithLodChain(primitive: Primitive, matrix: mat4): void { + const { remap, count } = firstReferencePlan(primitive); + const chain = primitive.getExtension(TN_DISCRETE_LOD); + if (chain !== null) { + // Each primitive has its own compaction order even when its authored chain/accessors are shared. + const owned = chain.clone(); + for (const level of chain.getIndices()) owned.swap(level, level.clone()); + primitive.setExtension(TN_DISCRETE_LOD, owned); + } + compactPrimitive(primitive, remap, count); + const [a, b, c, , d, e, f, , g, h, i] = matrix; + const mirrored = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g) < 0; + for (const level of primitive.getExtension(TN_DISCRETE_LOD)?.getIndices() ?? []) { + const source = level.getArray() ?? []; + const carried = new Uint32Array(source.length); + for (let index = 0; index < source.length; index += 1) { + // A mirror swaps each triangle's first and last corner, as LOD0's own reversal does. + const corner = mirrored ? index + 2 - 2 * (index % 3) : index; + carried[index] = remap[source[corner] as number] ?? UNUSED_VERTEX; + } + if (carried.includes(UNUSED_VERTEX)) + throw new Error("A TN_discrete_lod level indexes a vertex that its LOD0 does not draw."); + level.setArray(carried); + } + transformPrimitive(primitive, matrix); +} + +/** + * One `EXT_mesh_gpu_instancing` node for one species-and-LOD pair, holding every logical + * placement. Source meshes, materials, textures and each source node's world transform are + * preserved by the fresh-copy-per-node path: one resolver per node keeps shared geometry private. + */ +function addInstancedSpeciesNode( + doc: Document, + buffer: GltfBuffer, + speciesDoc: Document, + items: IPlacement[], + targetScene: Scene, + instancingExt: EXTMeshGPUInstancing, + nodeName: string, +): void { + const count = items.length; + const translations = new Float32Array(count * 3); + const rotations = new Float32Array(count * 4); + const scales = new Float32Array(count * 3); + for (const [i, item] of items.entries()) { + const pose = poseOf(item); + translations.set(pose.position, i * 3); + rotations.set(pose.quaternion, i * 4); + scales.set(pose.scale, i * 3); + } + + const batch = instancingExt + .createInstancedMesh() + .setAttribute( + "TRANSLATION", + doc.createAccessor().setType("VEC3").setArray(translations).setBuffer(buffer), + ) + .setAttribute( + "ROTATION", + doc.createAccessor().setType("VEC4").setArray(rotations).setBuffer(buffer), + ) + .setAttribute("SCALE", doc.createAccessor().setType("VEC3").setArray(scales).setBuffer(buffer)); + + // Any extension the source uses must exist on the target document before its properties copy. + for (const sourceExtension of speciesDoc.getRoot().listExtensionsUsed()) { + const ctor = sourceExtension.constructor; + if (!isExtensionConstructor(ctor)) { + throw new TypeError( + `Invalid species extension constructor: ${sourceExtension.extensionName}`, + ); + } + doc.createExtension(ctor); + } + + const combined = doc.createMesh(`${nodeName}_mesh`); + for (const { node, mesh } of meshNodesOf(speciesDoc)) { + const resolve = createDefaultPropertyResolver(doc, speciesDoc); + const targetMesh = copyToDocument(doc, speciesDoc, [mesh], resolve).get(mesh) as Mesh; + const matrix = node.getWorldMatrix(); + dequantizeNormals(targetMesh); + for (const primitive of targetMesh.listPrimitives()) transformWithLodChain(primitive, matrix); + // Index-only levels share the transformed vertices; their local errors must follow them. + const scale = Math.max( + Math.hypot(matrix[0], matrix[1], matrix[2]), + Math.hypot(matrix[4], matrix[5], matrix[6]), + Math.hypot(matrix[8], matrix[9], matrix[10]), + ); + for (const primitive of targetMesh.listPrimitives()) { + const chain = primitive.getExtension(TN_DISCRETE_LOD); + chain + ?.setErrorScale(chain.getErrorScale() * scale) + .setAbsoluteErrors(chain.getAbsoluteErrors().map((error) => error * scale)); + } + for (const primitive of targetMesh.listPrimitives()) { + targetMesh.removePrimitive(primitive); + combined.addPrimitive(primitive); + } + } + + const instancedNode = doc + .createNode(nodeName) + .setMesh(combined) + .setExtension("EXT_mesh_gpu_instancing", batch); + targetScene.addChild(instancedNode); +} + +/** A portable editor scene with embedded data URIs, already frozen by the game. */ +export interface IPreparedTerrainWorldOptions { + readonly preparedWorld: Record; +} +export function cookTerrainWorld( + options: IPreparedTerrainWorldOptions, +): Promise<{ worldGlb: Uint8Array }>; +export function cookTerrainWorld( + options: ICookTerrainWorldOptions, +): Promise; +export async function cookTerrainWorld( + options: ICookTerrainWorldOptions | IPreparedTerrainWorldOptions, +): Promise { + if ("preparedWorld" in options) { + const json = options.preparedWorld as Partial; + if (json.asset?.version !== "2.0" || !json.scenes?.length || !json.meshes?.length) + throw new Error("Prepared world requires a nonempty glTF 2.0 scene"); + if ( + json.cameras?.length || + json.animations?.length || + json.extensionsRequired?.some((name) => !name.startsWith("KHR_")) + ) + throw new Error("Prepared world contains nonportable scene content"); + for (const resource of [...(json.buffers ?? []), ...(json.images ?? [])]) { + if (resource.uri && !/^data:[^,]*;base64,[A-Za-z0-9+/]*={0,2}$/.test(resource.uri)) + throw new Error("Prepared world resources must be embedded base64 data URIs"); + } + const io = new NodeIO().registerExtensions(ALL_EXTENSIONS); + const doc = await io.readJSON({ json: { ...json, asset: json.asset }, resources: {} }); + if ( + !doc + .getRoot() + .listScenes() + .some((scene) => scene.listChildren().length) + ) + throw new Error("Prepared world has no scene nodes"); + await doc.transform(unpartition(), dedup({ propertyTypes: [PropertyType.TEXTURE] })); + return { worldGlb: await io.writeBinary(doc) }; + } + const { state, species, out } = options; + if (!state || typeof state.size !== "number" || typeof state.resolution !== "number") { + throw new Error("cookTerrainWorld: state with size and resolution is required"); + } + if (!state.height || state.height.length !== state.resolution * state.resolution) { + throw new Error("cookTerrainWorld: state.height length does not match resolution * resolution"); + } + + // A species id becomes an output filename, so every key must be a safe bare name. + for (const speciesId of Object.keys(species)) { + if (!SAFE_SPECIES_ID.test(speciesId)) { + throw new Error( + `cookTerrainWorld: species id '${speciesId}' is not a safe output filename; use letters, digits, '.', '_' or '-'.`, + ); + } + } + + // Every placement must name a species the caller supplied, before anything is read or written. + for (const item of state.instances) { + if (species[item.asset] === undefined) { + throw new Error( + `cookTerrainWorld: placement '${item.id}' names species '${item.asset}', which is not in the species map.`, + ); + } + } + + // Build every species' manifest entry (and its LOD bytes) before the shared bake. Each species + // model and each of its LODs is parsed exactly once here: the same documents later flatten into + // their instancing nodes, and every level is refused by name if it cannot instance. + const assets: Record = {}; + const speciesFiles: { + speciesId: string; + lod0: Uint8Array; + nearDoc: Document; + lods: { distance: number; glb: Uint8Array; filename: string; doc: Document }[]; + }[] = []; + + const pool = createSimplifyPool(LOD_ERROR_TARGETS.length); + try { + const tasks = Object.entries(species).map(async ([speciesId, spec]) => { + const lod0 = await resolveBytes(spec.glb); + const nearDoc = await parseSpecies(lod0); + assertInstancable(speciesId, nearDoc); + + // Authored LODs first: explicit `spec.lods`, then a sibling `-mid`/`-lod1` file beside a path + // species. Only when neither exists does simplification invent a level. + const authored = + spec.lods && spec.lods.length > 0 + ? spec.lods + : typeof spec.glb === "string" + ? await authoredSiblingLods(spec.glb) + : []; + + const lods: { distance: number; glb: Uint8Array; filename: string; doc: Document }[] = []; + for (const [i, level] of authored.entries()) { + if (level.glb === undefined) continue; + const bytes = await resolveBytes(level.glb); + const doc = await parseSpecies(bytes); + assertInstancable(speciesId, doc); + lods.push({ + distance: level.distance, + glb: bytes, + filename: `${speciesId}_lod${i + 1}.glb`, + doc, + }); + } + + if (lods.length === 0) { + const pass = modelPass({ + simplify: { ratio: 0.5 }, + textures: "none", + passes: { meshopt: false, prune: true, dedup: true }, + }); + const simplified = await pass.apply(Buffer.from(lod0), `${speciesId}_lod1.glb`); + const bytes = Uint8Array.from(Buffer.isBuffer(simplified) ? simplified : simplified.buffer); + const doc = await parseSpecies(bytes); + assertInstancable(speciesId, doc); + lods.push({ distance: 40, glb: bytes, filename: `${speciesId}_lod1.glb`, doc }); + } + + // The combined world's near meshes need their own index-only chain; streamed files stay authored. + await generateDiscreteLod( + nearDoc, + { cutout: false }, + `${speciesId}.glb`, + createHash("sha256").update(lod0).digest("hex"), + {}, + undefined, + { preserveVertices: true, simplify: pool.simplify }, + ); + + const asset: IWorldPackageAsset = { + glb: `models/${speciesId}.glb`, + bounds: spec.bounds ?? sourceBounds(nearDoc), + lods: lods.map((level) => ({ + distance: level.distance, + glb: `models/${level.filename}`, + })), + ...(spec.maxDistance === undefined ? {} : { maxDistance: spec.maxDistance }), + }; + return { speciesId, lod0, nearDoc, lods, asset }; + }); + const prepared = await Promise.all(tasks); + // Promise.all preserves source order; completion order must not change GLB or manifest bytes. + for (const { asset, ...file } of prepared) { + speciesFiles.push(file); + assets[file.speciesId] = asset; + } + } finally { + await pool.dispose(); + } + + // Fail closed before any glTF work or output write: the shared baker validates finite heights, + // known assets and uniform poses, and hands back the exact binary files this cook writes. + const worldPackage = bakeWorldPackage(state, { + assets, + ...(options.cellSize === undefined ? {} : { cellSize: options.cellSize }), + ...(options.extraLayers === undefined ? {} : { layers: options.extraLayers }), + }); + const { manifest, files } = worldPackage; + + // Contact check against the bytes the bake actually wrote, not the float grid: decode the + // quantized uint16 heightmap back to metres and sample that. Tolerance is one uint16 step of the + // written range plus float32 rounding — a sub-texel hop passes; a real offset above one step, + // and a mirrored-Z placement that reads the wrong row, both fail. `grounding: false` is an + // authored override and is skipped honestly. + const heightmapBytes = files[manifest.terrain.heightmap]; + if (heightmapBytes === undefined) { + throw new Error("cookTerrainWorld: the world package wrote no heightmap to sample."); + } + const decoded = decodeRAW16(heightmapBytes, { + width: manifest.terrain.columns, + height: manifest.terrain.rows, + min: manifest.terrain.heightMin, + max: manifest.terrain.heightMax, + }); + const decodedGrid = { + height: decoded.values, + resolution: manifest.terrain.columns, + size: state.size, + }; + const terrainRange = manifest.terrain.heightMax - manifest.terrain.heightMin; + const heightQuantStep = terrainRange / 65_535; + // 2^-23 is float32's machine epsilon; the sampled and decoded values share that rounding. + const tolerance = heightQuantStep + terrainRange * 1.2e-7; + for (const item of state.instances) { + const pose = poseOf(item); + if (!pose.grounding) continue; + const groundY = sampleHeight(decodedGrid, pose.position[0], pose.position[2]); + const difference = Math.abs(pose.position[1] - groundY); + if (Number.isFinite(difference) && difference > tolerance) { + throw new Error( + `Placement '${item.id}' of species '${item.asset}' contact check failed: base Y=${pose.position[1]} misses terrain ground Y=${groundY} by ${difference.toFixed(4)}m (> tolerance ${tolerance.toFixed(4)}m). Check for mirrored-Z or invalid coordinates.`, + ); + } + } + + // Setup glTF document. The default scene holds only the near LOD of every species; each + // alternate LOD gets its own named scene, so no two LODs render at once. + const doc = new Document(); + doc.getRoot().getAsset().extras = { tnTerrainWorld: 1 }; + const scene = doc.createScene("TerrainWorld"); + const buffer = doc.createBuffer(); + const instancingExt = doc.createExtension(EXTMeshGPUInstancing); + + // Terrain mesh chunks, always from the shared baker's LOD0. + const chunks = bakeTerrain(state, { lodSteps: [1] }).lods[0]?.chunks ?? []; + + // Preserve authored maps separately; the GLB retains its vanilla first-layer fallback. + const textureFiles = new Map(); + const terrainMaterial = doc.createMaterial("TerrainMaterial"); + const channels = ["diff", "nrm", "orm", "h"] as const; + const maps: ILayerMap[] = []; + for (const [index, layer] of (options.materials?.layers ?? []).entries()) { + if (!/^[a-zA-Z0-9_-]+$/u.test(layer.id)) + throw new Error(`cookTerrainWorld: invalid texture layer id '${layer.id}'.`); + for (const channel of channels) { + let bytes = layer[`${channel}Bytes`]; + const source = layer[channel]; + if (bytes === undefined && source !== undefined) { + if (typeof source === "string" && options.materials?.texturesDir !== undefined) { + const dir = path.resolve(options.materials.texturesDir); + const filename = path.resolve(dir, source); + const relative = path.relative(dir, filename); + if ( + relative === ".." || + relative.startsWith(`..${path.sep}`) || + path.isAbsolute(relative) + ) + throw new Error(`cookTerrainWorld: texture '${source}' escapes texturesDir '${dir}'.`); + bytes = await resolveBytes(filename); + } else bytes = await resolveBytes(source); + } + if (bytes !== undefined) maps.push({ layer, index, channel, bytes }); + } + } + for (const channel of channels) + await normalizeKind(maps.filter((map) => map.channel === channel)); + for (const { layer, index, channel, bytes, mimeType } of maps) { + textureFiles.set(`${layer.id}_${channel}.jpg`, bytes); + if (index === 0 && (channel === "diff" || channel === "nrm")) { + const texture = doc + .createTexture(channel === "diff" ? "TerrainBaseDiff" : "TerrainBaseNrm") + .setImage(bytes) + .setMimeType(mimeType ?? layer[`${channel}MimeType`] ?? "image/jpeg"); + if (channel === "diff") terrainMaterial.setBaseColorTexture(texture); + else terrainMaterial.setNormalTexture(texture); + } + } + + // Add chunk meshes + for (let c = 0; c < chunks.length; c++) { + const chunk = chunks[c]; + if (!chunk) continue; + const mesh = doc.createMesh(chunk.name ?? `terrain_chunk_${c}`); + const prim = doc.createPrimitive(); + prim.setMaterial(terrainMaterial); + prim.setAttribute( + "POSITION", + doc.createAccessor().setType("VEC3").setArray(chunk.positions).setBuffer(buffer), + ); + prim.setAttribute( + "NORMAL", + doc.createAccessor().setType("VEC3").setArray(chunk.normals).setBuffer(buffer), + ); + prim.setAttribute( + "TEXCOORD_0", + doc.createAccessor().setType("VEC2").setArray(chunk.uvs).setBuffer(buffer), + ); + prim.setIndices( + doc.createAccessor().setType("SCALAR").setArray(chunk.indices).setBuffer(buffer), + ); + mesh.addPrimitive(prim); + const node = doc.createNode(chunk.name ?? `terrain_node_${c}`).setMesh(mesh); + if (manifest.terrain.layers?.table !== undefined) { + const pointer = { tnTerrainSurface: "world.json" }; + mesh.setExtras(pointer); + node.setExtras(pointer); + } + scene.addChild(node); + } + + // Group placements by species + const placementsBySpecies = new Map(); + const placementCounts: Record = {}; + for (const item of state.instances) { + let list = placementsBySpecies.get(item.asset); + if (!list) { + list = []; + placementsBySpecies.set(item.asset, list); + } + list.push(item); + placementCounts[item.asset] = (placementCounts[item.asset] ?? 0) + 1; + } + + // Near LOD: one instancing node per species in the default scene, every logical placement one + // instance, so the node count stays constant while the instance count grows. + for (const { speciesId, nearDoc } of speciesFiles) { + const items = placementsBySpecies.get(speciesId) ?? []; + if (items.length === 0) continue; + addInstancedSpeciesNode( + doc, + buffer, + nearDoc, + items, + scene, + instancingExt, + `${speciesId}_instances`, + ); + } + + // Alternate LODs: one instancing node per species-and-LOD pair, each in its own named scene, + // with the same placements as the near node. glTF renders only the default scene, so no two + // LODs draw at once. + for (const { speciesId, lods } of speciesFiles) { + const items = placementsBySpecies.get(speciesId) ?? []; + if (items.length === 0) continue; + for (const [i, level] of lods.entries()) { + const lodScene = doc.createScene(`${speciesId}_lod${i + 1}`); + addInstancedSpeciesNode( + doc, + buffer, + level.doc, + items, + lodScene, + instancingExt, + `${speciesId}_lod${i + 1}_instances`, + ); + } + } + + doc.getRoot().setDefaultScene(scene); + + await doc.transform(unpartition()); + // Keep solid-texture removal off: species materials are authored, and a flat image is not ours to drop. + await doc.transform(prune({ keepSolidTextures: true })); + // Store byte-identical textures once. Each material keeps its own sampler and UV transform. + await doc.transform(dedup({ propertyTypes: [PropertyType.TEXTURE] })); + + const writer = new NodeIO().registerExtensions([...ALL_EXTENSIONS, TNDiscreteLod]); + const worldGlb = await writer.writeBinary(doc); + + // Collect in the original write order so even overlapping species filenames keep last-write wins. + const outputs = new Map([["world.glb", worldGlb]]); + for (const [name, bytes] of textureFiles) outputs.set(path.join("textures", name), bytes); + for (const [name, bytes] of Object.entries(files)) outputs.set(name, bytes); + for (const { speciesId, lod0, lods } of speciesFiles) { + outputs.set(path.join("models", `${speciesId}.glb`), lod0); + for (const level of lods) outputs.set(path.join("models", level.filename), level.glb); + } + outputs.set("world.json", `${JSON.stringify(manifest, null, 2)}\n`); + await mkdir(out, { recursive: true }); + await mkdir(path.join(out, "models"), { recursive: true }); + if (textureFiles.size > 0) await mkdir(path.join(out, "textures"), { recursive: true }); + await Promise.all([...outputs].map(([name, bytes]) => writeFile(path.join(out, name), bytes))); + + const finalNodes = doc.getRoot().listNodes(); + let instancedMeshCount = 0; + for (const child of scene.listChildren()) { + if (child.getExtension("EXT_mesh_gpu_instancing") !== null) instancedMeshCount++; + } + + return { + worldGlb, + manifest, + worldPackage, + placementCounts, + nodeCount: finalNodes.length, + instancedMeshCount, + }; +} diff --git a/packages/core/__tests__/constraints.spec.ts b/packages/core/__tests__/constraints.spec.ts index ba1d356ca4..0cbfea562b 100644 --- a/packages/core/__tests__/constraints.spec.ts +++ b/packages/core/__tests__/constraints.spec.ts @@ -78,6 +78,33 @@ describe("core constraints", () => { // `render/foliage-alpha.ts`'s, and a cutoff a part names is the part's own. The word // "light" the generic filter trips on is inside `loadsInFlight`. file !== "world-cells.ts" && + // These exact seams carry the formerly allowed WorldCells code: package surface identity, + // load flight counts and numeric contracts. The same appearance checks below cover each. + file !== "world-cell-data.ts" && + file !== "world-cell-prewarm.ts" && + file !== "world-cell-chunks.ts" && + file !== "world-cell-impostors.ts" && + file !== "world-cell-models.ts" && + file !== "world-cell-render.ts" && + // The bundle-safety seam moved out of `world-cells.ts` unchanged. It reads a material's + // version, transparency, visibility, transmission and node graph to decide whether a draw + // can be recorded. It constructs, sets and colours none of them. The generic filter + // matches "material" in its names; the appearance checks below apply to this file. + file !== "world-bundle-safety.ts" && + // The load limiter queues model loads and bounds how many run at once. It constructs no + // material, light, colour or shader and reads no appearance property. The generic filter + // matches "light" inside "flight", as in "in flight" and "inFlight". + file !== "world-load-limiter.ts" && + // The shared batch merges many cells into one InstancedMesh. It transports game-owned + // material identity: a batch draws the material its caller hands it, by reference. It + // constructs no material and reads no appearance property. The generic filter matches + // "material" for that identity, not for a look the module chooses. + file !== "world-cell-batches.ts" && + // The chunk shadow seam builds position-only depth proxies for a merged chunk. Each proxy + // takes the world's own material for a side by reference. It constructs no material, + // light or colour and reads no appearance property. The generic filter matches "material" + // in its names. The assertions below apply to this file. + file !== "world-chunk-shadow.ts" && // The cell proxy decides whether a baked HLOD stand-in may replace a cell's source draws. // It reads a material's identity and whether it is opaque, standard and untransformed to // make that call, and constructs, configures and colours none. The assertions below keep @@ -172,7 +199,13 @@ describe("core constraints", () => { file !== "profiling/render-trap-audit.ts" && file !== "index.ts", ) - .map((file) => withoutComments(readFileSync(path.join(sourceDirectory, file), "utf8"))) + .map((file) => + // Type-only re-exports transport vocabulary without choosing runtime appearance. + withoutComments(readFileSync(path.join(sourceDirectory, file), "utf8")).replace( + /export\s+type\s*\{[^}]*\}\s*from\s*["'][^"']+["'];?/gu, + " ", + ), + ) .join("\n"); expect(source).not.toMatch(/material|light|tonemapping|postprocessing|\.wgsl/iu); @@ -419,19 +452,33 @@ describe("core constraints", () => { ); expect(clusteredBatch.match(/#[0-9a-f]{6}\b|0x[0-9a-f]{6}\b/giu)).toBeNull(); - // `world-cells.ts` is exempted on the same terms as `instanced-batch.ts`: streaming a package - // means naming the geometry and surface its GLBs carry and handing both to `InstancedBatch` by - // reference. It constructs no material, light or colour and reads no property that describes - // how anything looks. The assertions below are what keep that true. - const worldCells = readFileSync(path.join(sourceDirectory, "world-cells.ts"), "utf8"); - expect(worldCells).not.toMatch( - /new\s+\w*(Material|Light)|new\s+Color|tonemapping|postprocessing|\.wgsl/iu, - ); - // A property read, not the `Array.prototype.map` call the package's bookkeeping uses. - expect(worldCells).not.toMatch( - /\.(color|map|roughness|metalness|emissive|opacity|envMap)\b(?!\s*\()/iu, - ); - expect(worldCells.match(/#[0-9a-f]{6}\b|0x[0-9a-f]{6}\b/giu)).toBeNull(); + // `world-cells.ts`, `world-bundle-safety.ts`, `world-load-limiter.ts`, `world-cell-batches.ts` + // and `world-chunk-shadow.ts` are exempted on the same terms as `instanced-batch.ts`: none + // constructs a material, light or colour, and none reads a property that describes how + // anything looks. The assertions below are what keep that true. + for (const file of [ + "world-cells.ts", + "world-cell-data.ts", + "world-cell-prewarm.ts", + "world-cell-chunks.ts", + "world-cell-impostors.ts", + "world-cell-models.ts", + "world-cell-render.ts", + "world-bundle-safety.ts", + "world-load-limiter.ts", + "world-cell-batches.ts", + "world-chunk-shadow.ts", + ]) { + const cellSource = readFileSync(path.join(sourceDirectory, file), "utf8"); + expect(cellSource).not.toMatch( + /new\s+\w*(Material|Light)|new\s+Color|tonemapping|postprocessing|\.wgsl/iu, + ); + // A property read, not the `Array.prototype.map` call the package's bookkeeping uses. + expect(cellSource).not.toMatch( + /\.(color|map|roughness|metalness|emissive|opacity|envMap)\b(?!\s*\()/iu, + ); + expect(cellSource.match(/#[0-9a-f]{6}\b|0x[0-9a-f]{6}\b/giu)).toBeNull(); + } // `world-cell-proxy.ts` is exempted on the same terms as `world-cells.ts`: it reads whether a // surface is safe to replace and builds nothing that describes how anything looks. diff --git a/packages/core/__tests__/fixtures/compressed-upload.ktx2 b/packages/core/__tests__/fixtures/compressed-upload.ktx2 new file mode 100644 index 0000000000..73e17fed9c Binary files /dev/null and b/packages/core/__tests__/fixtures/compressed-upload.ktx2 differ diff --git a/packages/core/__tests__/fixtures/generated-three-patch.patch.br b/packages/core/__tests__/fixtures/generated-three-patch.patch.br new file mode 100644 index 0000000000..f6f770de7f Binary files /dev/null and b/packages/core/__tests__/fixtures/generated-three-patch.patch.br differ diff --git a/packages/core/__tests__/fixtures/geometry-cache-predecessors.json.br b/packages/core/__tests__/fixtures/geometry-cache-predecessors.json.br new file mode 100644 index 0000000000..8723bce8e1 Binary files /dev/null and b/packages/core/__tests__/fixtures/geometry-cache-predecessors.json.br differ diff --git a/packages/core/__tests__/fixtures/legacy-three-patch.json.br b/packages/core/__tests__/fixtures/legacy-three-patch.json.br new file mode 100644 index 0000000000..1225c56326 Binary files /dev/null and b/packages/core/__tests__/fixtures/legacy-three-patch.json.br differ diff --git a/packages/core/__tests__/frame-budget.spec.ts b/packages/core/__tests__/frame-budget.spec.ts index 7f42a3af6d..33fa61765a 100644 --- a/packages/core/__tests__/frame-budget.spec.ts +++ b/packages/core/__tests__/frame-budget.spec.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from "vitest"; +import { capabilityDetail } from "../../engine-mcp/src/index.js"; import { FRAME_BUDGET_MARKER, FRAME_BUDGET_PHASES, @@ -199,6 +200,12 @@ function parseWindow(line: string): IFrameBudgetWindow { } describe("FrameBudget", () => { + it("documents the default 8 ms addInSlices budget in capability detail", () => { + const detail = capabilityDetail("addInSlices", "packages/create-threenative/capabilities.json"); + expect(detail.summary).toContain("8 ms default budget"); + expect(detail.overrides?.join(" ")).toContain("8 ms budget unless the game gives `sliceSize`"); + }); + it("reports Long Tasks separately from presentation gaps and disconnects on disposal", () => { let deliver!: (list: { getEntries(): { startTime: number; duration: number }[] }) => void; const disconnect = vi.fn(); @@ -230,6 +237,7 @@ describe("FrameBudget", () => { afterFirstFrameMs: 75, }); budget.dispose(); + budget.dispose(); expect(disconnect).toHaveBeenCalledOnce(); } finally { vi.unstubAllGlobals(); diff --git a/packages/core/__tests__/game.spec.ts b/packages/core/__tests__/game.spec.ts index acb047fe25..46fbd7a461 100644 --- a/packages/core/__tests__/game.spec.ts +++ b/packages/core/__tests__/game.spec.ts @@ -12,6 +12,7 @@ import { import { describe, expect, it, vi } from "vitest"; import { type IGamePlatformSource, defineGame } from "../src/game.js"; import { InputMap } from "../src/input.js"; +import { onLaunchFailure, resetLaunchFailures } from "../src/launch-diagnostics.js"; import type { IRenderPerformanceSample } from "../src/loop.js"; import { type ICtx, Scene } from "../src/scene.js"; import { UI_INTENT_MESSAGE, UI_READY_INTENT, connectUiBridge } from "../src/ui-bridge.js"; @@ -92,6 +93,60 @@ describe("IGame", () => { } }); + it.each([false, true])( + "reports a manifest rejection during Scene.load (transition: %s)", + async (transition) => { + resetLaunchFailures(); + const message = + "Asset 'worlds/forest/world.glb' is not listed in the asset manifest 'assets.manifest.json'."; + vi.stubGlobal( + "fetch", + vi.fn( + async () => + new Response( + JSON.stringify({ version: 1, entries: { "other.glb": { output: "other.glb" } } }), + ), + ), + ); + const failure = vi.fn(); + const off = onLaunchFailure(failure); + const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined); + class MissingWorld extends Scene { + override async load(ctx: ICtx): Promise { + await ctx.assets.model("worlds/forest/world.glb"); + } + } + class Boot extends Scene { + override enter(ctx: ICtx): void { + void ctx.goto("world"); + } + } + const game = defineGame({ + initialState: {}, + renderer: renderer(testCanvas()), + scenes: { boot: Boot, world: MissingWorld }, + start: transition ? "boot" : "world", + }); + try { + if (transition) { + await game.start(); + await Promise.resolve(); + } else { + await expect(game.start()).rejects.toThrow(message); + } + await vi.waitFor(() => + expect(failure).toHaveBeenCalledWith({ kind: "load-failed", message }), + ); + expect(consoleError).toHaveBeenCalledWith(`TN_STARTUP_FAILED: ${message}`); + } finally { + game.stop(); + off(); + consoleError.mockRestore(); + resetLaunchFailures(); + } + }, + ); + it.each([false, true])( "releases the UI bridge when renderer startup fails (host: %s)", async (host) => { diff --git a/packages/core/__tests__/generate-three-patch.spec.ts b/packages/core/__tests__/generate-three-patch.spec.ts new file mode 100644 index 0000000000..21057f930e --- /dev/null +++ b/packages/core/__tests__/generate-three-patch.spec.ts @@ -0,0 +1,274 @@ +import { existsSync } from "node:fs"; +import { cp, mkdir, readFile, writeFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { brotliDecompressSync } from "node:zlib"; +import { describe, expect, it } from "vitest"; +import { makeTempDir } from "../../../test-support/temp-dir.js"; + +const generatorPath = "../scripts/generate-three-patch.mjs"; +const { assemblePatches, blobHash, generateThreePatch, parseSourceDeltaPaths, validatePin } = + (await import(generatorPath)) as { + assemblePatches(options: { + stockDir: string; + patchedDir: string; + pin: { baseFinalHashes: Record }; + }): Promise<{ basePatch: string; upgradePatch: string }>; + blobHash(contents: string | Uint8Array): string; + generateThreePatch(options: { + packageRoot: string; + archive?: string; + check?: boolean; + planner?: () => Promise<{ basePatch: string; upgradePatch: string }>; + }): Promise<{ status: "written" | "matched" }>; + parseSourceDeltaPaths(sourceDelta: string): string[]; + validatePin(pin: unknown): Record; + }; + +const realPatchDir = join(dirname(fileURLToPath(import.meta.url)), "..", "three-patch"); + +async function tempRoot(): Promise { + return makeTempDir("tn-test-gen-patch-"); +} + +async function readRealPin(): Promise> { + return JSON.parse(await readFile(join(realPatchDir, "pin.json"), "utf8")); +} + +async function copyRealInputs(packageRoot: string): Promise { + await mkdir(join(packageRoot, "three-patch"), { recursive: true }); + await cp(join(realPatchDir, "pin.json"), join(packageRoot, "three-patch", "pin.json")); + await cp( + join(realPatchDir, "source-delta.patch"), + join(packageRoot, "three-patch", "source-delta.patch"), + ); +} + +/** Builds stock and patched trees holding exactly the pinned target paths. */ +async function makeScopedTrees( + root: string, + pin: { baseFinalHashes: Record }, +): Promise<{ stockDir: string; patchedDir: string }> { + const stockDir = join(root, "stock"); + const patchedDir = join(root, "patched"); + for (const rel of Object.keys(pin.baseFinalHashes)) { + await mkdir(dirname(join(stockDir, rel)), { recursive: true }); + await mkdir(dirname(join(patchedDir, rel)), { recursive: true }); + await writeFile(join(stockDir, rel), `// stock ${rel}\n`, "utf8"); + await writeFile(join(patchedDir, rel), `// patched ${rel}\n`, "utf8"); + } + return { patchedDir, stockDir }; +} + +describe("generateThreePatch validation", () => { + it("rejects when pin.json or source-delta.patch is missing", async () => { + const root = await tempRoot(); + const packageRoot = join(root, "core"); + await mkdir(join(packageRoot, "three-patch"), { recursive: true }); + await expect(generateThreePatch({ packageRoot })).rejects.toThrow( + /missing pin\.json or source-delta\.patch/u, + ); + }); + + it("rejects an invalid pin commit or version", async () => { + const root = await tempRoot(); + const packageRoot = join(root, "core"); + await mkdir(join(packageRoot, "three-patch"), { recursive: true }); + await writeFile( + join(packageRoot, "three-patch", "pin.json"), + JSON.stringify({ version: "0.185.0", commit: "badcommit" }), + "utf8", + ); + await writeFile(join(packageRoot, "three-patch", "source-delta.patch"), "", "utf8"); + await expect(generateThreePatch({ packageRoot })).rejects.toThrow(/unsupported pin/u); + }); + + it("rejects a pin with an unsafe path, bad hash, or duplicate pair", async () => { + const pin = (await readRealPin()) as Record; + const pair = (path: string, oldHash: string) => ({ + path, + oldHash, + newHash: "a".repeat(40), + }); + expect(() => validatePin(pin)).not.toThrow(); + expect(() => + validatePin({ ...pin, frozenCompatPairs: [pair("../evil.js", "b".repeat(40))] }), + ).toThrow(/unsafe compat path/u); + expect(() => validatePin({ ...pin, frozenCompatPairs: [pair("src/a.js", "nothex")] })).toThrow( + /invalid compat hash/u, + ); + const duplicate = pair("src/a.js", "c".repeat(40)); + expect(() => validatePin({ ...pin, frozenCompatPairs: [duplicate, { ...duplicate }] })).toThrow( + /duplicate compat pair/u, + ); + expect(() => validatePin({ ...pin, baseFinalHashes: { "src/ok.js": "nothex" } })).toThrow( + /invalid final hash/u, + ); + expect(() => validatePin({ ...pin, baseFinalHashes: {} })).toThrow(/non-empty/u); + }); + + it("accepts a source delta only when every path is a safe src/ path", () => { + expect( + parseSourceDeltaPaths("diff --git a/src/a.js b/src/a.js\n--- a/src/a.js\n+++ b/src/a.js\n"), + ).toEqual(["src/a.js"]); + expect(() => parseSourceDeltaPaths("")).toThrow(/empty/u); + expect(() => + parseSourceDeltaPaths( + "diff --git a/build/a.js b/build/a.js\n--- a/build/a.js\n+++ b/build/a.js\n", + ), + ).toThrow(/safe src\//u); + expect(() => + parseSourceDeltaPaths( + "diff --git a/src/../escape.js b/src/../escape.js\n--- a/src/../escape.js\n+++ b/src/../escape.js\n", + ), + ).toThrow(/safe src\//u); + expect(() => + parseSourceDeltaPaths("diff --git a/src/a.js b/src/b.js\n--- a/src/a.js\n+++ b/src/b.js\n"), + ).toThrow(/rename/u); + }); +}); + +describe("assemblePatches", () => { + it("scopes the diff to the pinned targets and never emits installed dependencies", async () => { + const root = await tempRoot(); + const pin = (await readRealPin()) as { baseFinalHashes: Record }; + const { stockDir, patchedDir } = await makeScopedTrees(root, pin); + await mkdir(join(patchedDir, "node_modules/pkg"), { recursive: true }); + await writeFile( + join(patchedDir, "node_modules/pkg/index.js"), + "module.exports = {};\n", + "utf8", + ); + await writeFile(join(patchedDir, "package.json"), "{}\n", "utf8"); + await writeFile(join(patchedDir, "package-lock.json"), "{}\n", "utf8"); + await writeFile(join(patchedDir, "README.md"), "# three\n", "utf8"); + + const { basePatch } = await assemblePatches({ patchedDir, pin, stockDir }); + const headers = basePatch.match(/^diff --git /gmu) ?? []; + expect(headers.length).toBe(Object.keys(pin.baseFinalHashes).length); + expect(basePatch).not.toContain("node_modules"); + expect(basePatch).not.toContain("diff --git a/package.json"); + expect(basePatch).not.toContain("b/package-lock.json"); + expect(basePatch).not.toContain("b/README.md"); + }); + + it("reflects the actual build result and is deterministic", async () => { + const root = await tempRoot(); + const pin = (await readRealPin()) as { baseFinalHashes: Record }; + const { stockDir, patchedDir } = await makeScopedTrees(root, pin); + const target = "build/three.cjs"; + + await writeFile(join(patchedDir, target), "console.log(1);\n", "utf8"); + const first = await assemblePatches({ patchedDir, pin, stockDir }); + const repeat = await assemblePatches({ patchedDir, pin, stockDir }); + expect(first.basePatch).toBe(repeat.basePatch); + expect(first.upgradePatch).toBe(repeat.upgradePatch); + + await writeFile(join(patchedDir, target), "console.log(2);\n", "utf8"); + const second = await assemblePatches({ patchedDir, pin, stockDir }); + expect(second.basePatch).not.toBe(first.basePatch); + const firstJson = JSON.parse(first.upgradePatch); + const secondJson = JSON.parse(second.upgradePatch); + expect(secondJson.generatedFinalHashes[target]).toBe(blobHash("console.log(2);\n")); + expect(secondJson.generatedFinalHashes[target]).not.toBe( + firstJson.generatedFinalHashes[target], + ); + }); + + it("covers all pinned targets and preserves every legacy compat pair", async () => { + const root = await tempRoot(); + const pin = (await readRealPin()) as { + baseFinalHashes: Record; + frozenCompatPairs: unknown[]; + }; + const { stockDir, patchedDir } = await makeScopedTrees(root, pin); + const rel = "src/renderers/common/Renderer.js"; + await writeFile(join(patchedDir, rel), "// current renderer\n", "utf8"); + + const { basePatch, upgradePatch } = await assemblePatches({ patchedDir, pin, stockDir }); + expect(Object.keys(pin.baseFinalHashes)).toHaveLength(31); + expect(pin.frozenCompatPairs).toHaveLength(64); + + const json = JSON.parse(upgradePatch); + expect(Object.keys(json.generatedFinalHashes)).toHaveLength( + Object.keys(pin.baseFinalHashes).length, + ); + expect(json.acceptedLegacyCompatPairs).toHaveLength(64); + expect(Object.keys(json.acceptedLegacyFinalHashes)).toHaveLength( + Object.keys(pin.baseFinalHashes).length, + ); + expect(json.generatedFinalHashes[rel]).toBe(blobHash("// current renderer\n")); + expect(json.acceptedLegacyFinalHashes[rel]).toBe(pin.baseFinalHashes[rel]); + + const bundled = JSON.parse( + brotliDecompressSync(Buffer.from(json.bundleBrotliBase64, "base64")).toString("utf8"), + ); + expect(Object.keys(bundled)).toHaveLength(Object.keys(pin.baseFinalHashes).length); + expect(basePatch).toContain( + "diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js", + ); + }); +}); + +describe("generateThreePatch artifacts", () => { + it("writes both artifacts on success", async () => { + const root = await tempRoot(); + const packageRoot = join(root, "core"); + await copyRealInputs(packageRoot); + const planner = async () => ({ basePatch: "BASE\n", upgradePatch: "UP\n" }); + + const result = await generateThreePatch({ packageRoot, planner }); + expect(result.status).toBe("written"); + expect(await readFile(join(packageRoot, "patches", "three@0.185.1.patch"), "utf8")).toBe( + "BASE\n", + ); + expect( + await readFile(join(packageRoot, "patches", "three@0.185.1-prd269-upgrade.patch"), "utf8"), + ).toBe("UP\n"); + }); + + it("rejects drift in check mode and leaves both destinations unchanged", async () => { + const root = await tempRoot(); + const packageRoot = join(root, "core"); + await copyRealInputs(packageRoot); + const baseDest = join(packageRoot, "patches", "three@0.185.1.patch"); + const upgradeDest = join(packageRoot, "patches", "three@0.185.1-prd269-upgrade.patch"); + await mkdir(join(packageRoot, "patches"), { recursive: true }); + await writeFile(baseDest, "OLD\n", "utf8"); + await writeFile(upgradeDest, "OLDUP\n", "utf8"); + const planner = async () => ({ basePatch: "NEW\n", upgradePatch: "NEWUP\n" }); + + await expect(generateThreePatch({ packageRoot, planner, check: true })).rejects.toThrow( + /drift/u, + ); + expect(await readFile(baseDest, "utf8")).toBe("OLD\n"); + expect(await readFile(upgradeDest, "utf8")).toBe("OLDUP\n"); + }); + + it("matches in check mode when the generated text equals the committed text", async () => { + const root = await tempRoot(); + const packageRoot = join(root, "core"); + await copyRealInputs(packageRoot); + const baseDest = join(packageRoot, "patches", "three@0.185.1.patch"); + const upgradeDest = join(packageRoot, "patches", "three@0.185.1-prd269-upgrade.patch"); + await mkdir(join(packageRoot, "patches"), { recursive: true }); + await writeFile(baseDest, "SAME\n", "utf8"); + await writeFile(upgradeDest, "SAMEUP\n", "utf8"); + const planner = async () => ({ basePatch: "SAME\n", upgradePatch: "SAMEUP\n" }); + + const result = await generateThreePatch({ packageRoot, planner, check: true }); + expect(result.status).toBe("matched"); + }); + + it("does not write either destination when the archive is missing", async () => { + const root = await tempRoot(); + const packageRoot = join(root, "core"); + await copyRealInputs(packageRoot); + const baseDest = join(packageRoot, "patches", "three@0.185.1.patch"); + + await expect( + generateThreePatch({ archive: join(root, "absent.tar.gz"), packageRoot }), + ).rejects.toThrow(/archive not found/u); + expect(existsSync(baseDest)).toBe(false); + }); +}); diff --git a/packages/core/__tests__/gpu-timestamp-resolve-cadence.spec.ts b/packages/core/__tests__/gpu-timestamp-resolve-cadence.spec.ts index cc1cad1962..8c0bf12eee 100644 --- a/packages/core/__tests__/gpu-timestamp-resolve-cadence.spec.ts +++ b/packages/core/__tests__/gpu-timestamp-resolve-cadence.spec.ts @@ -226,9 +226,8 @@ async function runWindows(options: { }> { const canvas = testCanvas(); const passes = options.passes ?? 6; - // Windows close on simulated ticks. Converting 300 presented frames at this frame rate keeps each - // window at the frame count the sampler is tested against. - const reportEvery = Math.round((300 * options.frameMs) / (1000 / 60)); + // Live-clock windows close on presented frames, independently of simulation tick rate. + const reportEvery = 300; // Three 300-frame windows plus a margin, so the third window closes at every frame rate. const presented = 930; let presentedFrame = 0; diff --git a/packages/core/__tests__/instance-velocity-upload.spec.ts b/packages/core/__tests__/instance-velocity-upload.spec.ts index 7f002d4850..86d450409c 100644 --- a/packages/core/__tests__/instance-velocity-upload.spec.ts +++ b/packages/core/__tests__/instance-velocity-upload.spec.ts @@ -163,6 +163,29 @@ describe("instance matrix uploads at the actual draw boundary", () => { f.tracker.clear(); }); + it("retains direct Three history when a dynamic array changes without a version bump", () => { + const f = fixture(true, true, false); + const initial = f.draw().current; + f.crowd.setMatrixAt(1, new Matrix4().makeTranslation(3, 0, -4)); + expect(f.draw().previous).toEqual(initial); + expect(f.draw().previous).toEqual(f.crowd.instanceMatrix.array); + }); + + it("does not upload unchanged scheduled history after its first committed draw", () => { + const f = fixture(false); + f.draw(); + f.tracker.commit(f.scene); + f.tracker.update(f.scene); + const initial = f.draw().updates; + f.tracker.commit(f.scene); + for (let draw = 0; draw < 4; draw += 1) { + f.tracker.update(f.scene); + expect(f.draw().updates).toBe(initial); + f.tracker.commit(f.scene); + } + f.tracker.clear(); + }); + it("allocates no history and performs no uploads on unchanged temporal-off draws", () => { const f = fixture(false, false); expect(f.previous).toBeUndefined(); diff --git a/packages/core/__tests__/mcp-install.spec.ts b/packages/core/__tests__/mcp-install.spec.ts index 53c652ce57..e504533a79 100644 --- a/packages/core/__tests__/mcp-install.spec.ts +++ b/packages/core/__tests__/mcp-install.spec.ts @@ -1,5 +1,5 @@ import { execFileSync, spawn } from "node:child_process"; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, readFileSync, symlinkSync, writeFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { describe, expect, it } from "vitest"; @@ -34,6 +34,7 @@ describe("mergeMcpServers", () => { "threenative-sculpt", "threenative-engine", "threenative-blender", + "threenative-terrain", ]); }); @@ -107,18 +108,42 @@ describe("MCP_SERVERS", () => { expect(MCP_PACKAGES.blender?.version).toBe(blender.version); }); - // The four are ordered, not merely present: a host renders the table in declaration order and + // The five are ordered, not merely present: a host renders the table in declaration order and // the golden-path snapshot probes them by index. A server appended in the wrong place is the // kind of change that passes every set-shaped assertion and moves what a user sees. - it("should declare four ThreeNative servers", () => { + it("should declare five ThreeNative servers", () => { expect(Object.keys(MCP_SERVERS)).toEqual([ "threenative-assets", "threenative-sculpt", "threenative-engine", "threenative-blender", + "threenative-terrain", ]); }); + // Terrain is not bundled and not a core dependency: bundling it would close a cycle + // (core -> assets -> terrain -> core). The shim resolves the project's `@threenative/terrain` + // bin, so the npx pin must name that package at its current version. + it("launches terrain from @threenative/terrain without depending on it", () => { + const manifest = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8")) as { + dependencies?: Record; + devDependencies?: Record; + peerDependencies?: Record; + }; + for (const table of [ + manifest.dependencies, + manifest.devDependencies, + manifest.peerDependencies, + ]) { + expect(table?.["@threenative/terrain"]).toBeUndefined(); + } + const terrain = JSON.parse( + readFileSync(path.resolve("packages/terrain/package.json"), "utf8"), + ) as { bin: string; name: string; version: string }; + expect(MCP_PACKAGES.terrain).toEqual({ name: terrain.name, version: terrain.version }); + expect(terrain.bin).toBe("./dist/mcp/server.js"); + }); + // The npx fallback in `launch.mjs` names the package `MCP_PACKAGES` declares, so a shim whose // package is undeclared spawns `npx undefined@undefined` on a machine that has not installed it. it("declares the package every shim launches", () => { @@ -474,58 +499,74 @@ describe("the sculpt server the engine ships", () => { expect(manifest.dependencies[sculpt.name]).toBe(sculpt.version); }); - it("serves the 0.2 rig and animation gates through the shipped launch shim", async () => { - const child = spawn(process.execPath, [path.join(packageRoot, "mcp", "sculpt.mjs")], { - cwd: packageRoot, - stdio: ["pipe", "pipe", "inherit"], - }); - const lines: string[] = []; - let buffer = ""; - child.stdout.on("data", (chunk: Buffer) => { - buffer += chunk.toString(); - const parts = buffer.split("\n"); - buffer = parts.pop() ?? ""; - lines.push(...parts.filter(Boolean)); - }); - const send = (message: object) => child.stdin.write(`${JSON.stringify(message)}\n`); - const reply = async (id: number) => { - for (let waited = 0; waited < 20_000; waited += 50) { - const found = lines.map((line) => JSON.parse(line)).find((m) => m.id === id); - if (found) return found; - await new Promise((resolve) => setTimeout(resolve, 50)); + it.each([ + { + shim: "sculpt", + tools: [ + "sculpt_action_design", + "sculpt_clip_measure", + "sculpt_mesh_parity", + "sculpt_rig_gate", + "sculpt_rig_reference", + "sculpt_rig_spec_gate", + "sculpt_skin_condition", + ], + }, + { shim: "terrain", tools: ["terrain_generate", "terrain_patch"] }, + ])( + "serves the tools through the shipped $shim launch shim", + async ({ shim, tools }) => { + const cwd = shim === "terrain" ? project("terrain-game") : packageRoot; + if (shim === "terrain") { + mkdirSync(path.join(cwd, "node_modules", "@threenative"), { recursive: true }); + symlinkSync( + path.resolve(packageRoot, "../terrain"), + path.join(cwd, "node_modules", "@threenative", "terrain"), + ); } - throw new Error(`no reply to request ${id}`); - }; - - try { - send({ - id: 1, - jsonrpc: "2.0", - method: "initialize", - params: { - capabilities: {}, - clientInfo: { name: "mcp-install-spec", version: "0" }, - protocolVersion: "2025-06-18", - }, + const child = spawn(process.execPath, [path.join(packageRoot, "mcp", `${shim}.mjs`)], { + cwd, + stdio: ["pipe", "pipe", "inherit"], }); - await reply(1); - send({ jsonrpc: "2.0", method: "notifications/initialized" }); - send({ id: 2, jsonrpc: "2.0", method: "tools/list" }); - const listed = (await reply(2)) as { result: { tools: { name: string }[] } }; - - expect(listed.result.tools.map((tool) => tool.name)).toEqual( - expect.arrayContaining([ - "sculpt_action_design", - "sculpt_clip_measure", - "sculpt_mesh_parity", - "sculpt_rig_gate", - "sculpt_rig_reference", - "sculpt_rig_spec_gate", - "sculpt_skin_condition", - ]), - ); - } finally { - child.kill(); - } - }, 30_000); + const lines: string[] = []; + let buffer = ""; + child.stdout.on("data", (chunk: Buffer) => { + buffer += chunk.toString(); + const parts = buffer.split("\n"); + buffer = parts.pop() ?? ""; + lines.push(...parts.filter(Boolean)); + }); + const send = (message: object) => child.stdin.write(`${JSON.stringify(message)}\n`); + const reply = async (id: number) => { + for (let waited = 0; waited < 20_000; waited += 50) { + const found = lines.map((line) => JSON.parse(line)).find((m) => m.id === id); + if (found) return found; + await new Promise((resolve) => setTimeout(resolve, 50)); + } + throw new Error(`no reply to request ${id}`); + }; + + try { + send({ + id: 1, + jsonrpc: "2.0", + method: "initialize", + params: { + capabilities: {}, + clientInfo: { name: "mcp-install-spec", version: "0" }, + protocolVersion: "2025-06-18", + }, + }); + await reply(1); + send({ jsonrpc: "2.0", method: "notifications/initialized" }); + send({ id: 2, jsonrpc: "2.0", method: "tools/list" }); + const listed = (await reply(2)) as { result: { tools: { name: string }[] } }; + + expect(listed.result.tools.map((tool) => tool.name)).toEqual(expect.arrayContaining(tools)); + } finally { + child.kill(); + } + }, + 30_000, + ); }); diff --git a/packages/core/__tests__/model-lod-loader.spec.ts b/packages/core/__tests__/model-lod-loader.spec.ts index af6ac230e1..a1bca257d7 100644 --- a/packages/core/__tests__/model-lod-loader.spec.ts +++ b/packages/core/__tests__/model-lod-loader.spec.ts @@ -1,7 +1,19 @@ -import { type Box3, Mesh, type Object3D, PerspectiveCamera, Scene, type Sphere } from "three"; +import { + type Box3, + type BufferGeometry, + InstancedMesh, + Matrix4, + Mesh, + type Object3D, + PerspectiveCamera, + Raycaster, + Scene, + type Sphere, + Vector3, +} from "three"; import { afterEach, describe, expect, it, vi } from "vitest"; import { createAssetLoader } from "../src/assets.js"; -import { TN_DISCRETE_LOD, baseGeometryOf, updateModelLods } from "../src/model-lod.js"; +import { TN_DISCRETE_LOD, baseGeometryOf, lodChainOf, updateModelLods } from "../src/model-lod.js"; // PRD-377 §5/§6 — the real front door. A GLB carrying `TN_discrete_lod` is loaded through // `createAssetLoader` (which fetches the bytes, decides the decoder set from `extensionsUsed`, @@ -17,13 +29,16 @@ function padTo4(buffer: Buffer, fill: number): Buffer { const QUADS = 32; const LOD0_TRIANGLES = QUADS * 2; const COARSE_TRIANGLES = QUADS / 2; +// The two instanced placements: index 0 sits near the camera, index 1 far from it. +const NEAR_Z = 95; +const FAR_Z = 0; /** A grid strip of `QUADS` quads: `2 * QUADS` LOD0 triangles, half of them in the derived level. */ function gridIndices(triangles: number): number[] { const indices: number[] = []; for (let quad = 0; quad < triangles / 2; quad += 1) { const a = quad * 2; - indices.push(a, a + 1, a + 2, a + 1, a + 3, a + 2); + indices.push(a, a + 2, a + 1, a + 1, a + 2, a + 3); } return indices; } @@ -33,7 +48,14 @@ function gridIndices(triangles: number): number[] { * `TN_discrete_lod` extension on the primitive. Written by hand so the loader — not the asset * package the pipeline owns — is what this test exercises. */ -function cookedGlb(options: { copies?: boolean } = {}): Buffer { +function cookedGlb( + options: { + copies?: boolean; + instances?: boolean; + mixedParts?: boolean; + alternateScene?: boolean; + } = {}, +): Buffer { const lod0Vertices = (QUADS + 1) * 2; let positions = new Float32Array(lod0Vertices * 3); for (let vertex = 0; vertex <= QUADS; vertex += 1) { @@ -58,11 +80,17 @@ function cookedGlb(options: { copies?: boolean } = {}): Buffer { positions = grown; levelIndices = levelIndices.map((vertex) => lod0Vertices + source.indexOf(vertex)); } + // Two placements: index 0 near the camera, index 1 far from it. Their z is the fixture's identity. + const instanced = options.instances === true; + const translations = new Float32Array([0, 0, NEAR_Z, 0, 0, FAR_Z]); const binPadded = padTo4( Buffer.concat([ Buffer.from(positions.buffer, positions.byteOffset, positions.byteLength), Buffer.from(baseIndices.buffer, baseIndices.byteOffset, baseIndices.byteLength), Buffer.from(levelIndices.buffer, levelIndices.byteOffset, levelIndices.byteLength), + ...(instanced + ? [Buffer.from(translations.buffer, translations.byteOffset, translations.byteLength)] + : []), ]), 0, ); @@ -80,6 +108,7 @@ function cookedGlb(options: { copies?: boolean } = {}): Buffer { }, { bufferView: 1, componentType: 5125, count: baseIndices.length, type: "SCALAR" }, { bufferView: 2, componentType: 5125, count: levelIndices.length, type: "SCALAR" }, + ...(instanced ? [{ bufferView: 3, componentType: 5126, count: 2, type: "VEC3" }] : []), ], asset: { version: "2.0" }, bufferViews: [ @@ -91,9 +120,18 @@ function cookedGlb(options: { copies?: boolean } = {}): Buffer { byteOffset: positionBytes + baseBytes, target: 34963, }, + ...(instanced + ? [ + { + buffer: 0, + byteLength: translations.byteLength, + byteOffset: positionBytes + baseBytes + levelIndices.byteLength, + }, + ] + : []), ], buffers: [{ byteLength: binPadded.length }], - extensionsUsed: [TN_DISCRETE_LOD], + extensionsUsed: instanced ? [TN_DISCRETE_LOD, "EXT_mesh_gpu_instancing"] : [TN_DISCRETE_LOD], meshes: [ { primitives: [ @@ -110,17 +148,26 @@ function cookedGlb(options: { copies?: boolean } = {}): Buffer { sharedVertexBuffers: true, strategy: "discrete", }, - }, + } as Record, indices: 1, mode: 4, }, ], }, ], - nodes: [{ mesh: 0 }], + nodes: (instanced + ? [{ extensions: { EXT_mesh_gpu_instancing: { attributes: { TRANSLATION: 3 } } }, mesh: 0 }] + : [{ mesh: 0 }]) as Array<{ mesh: number; extensions?: Record }>, scene: 0, scenes: [{ nodes: [0] }], }; + const primitives = json.meshes[0]?.primitives; + if (options.alternateScene === true) { + json.nodes.push({ mesh: 0 }); + json.scenes.push({ nodes: [1] }); + } + if (options.mixedParts === true && primitives?.[0] !== undefined) + primitives.push({ ...primitives[0], extensions: {} }); const jsonPadded = padTo4(Buffer.from(JSON.stringify(json)), 0x20); const total = 12 + 8 + jsonPadded.length + 8 + binPadded.length; const out = Buffer.alloc(total); @@ -168,11 +215,86 @@ function firstMesh(root: Object3D): Mesh { return found; } +function firstInstancedMesh(root: Object3D): InstancedMesh { + let found: InstancedMesh | undefined; + root.traverse((object) => { + if (found === undefined && object instanceof InstancedMesh) found = object; + }); + if (found === undefined) throw new Error("the loaded scene holds no instanced mesh"); + return found; +} + +/** The geometry each placement draws this frame, keyed by its z translation (the fixture's identity). */ +function rungsByZ(mesh: InstancedMesh): Map { + const rungs = new Map(); + const matrix = new Matrix4(); + const partitions = mesh.children.filter( + (child): child is InstancedMesh => child instanceof InstancedMesh, + ); + if (partitions.length === 0) { + for (let index = 0; index < mesh.count; index += 1) { + mesh.getMatrixAt(index, matrix); + rungs.set(matrix.elements[14] as number, mesh.geometry); + } + return rungs; + } + for (const partition of partitions) { + for (let index = 0; index < partition.count; index += 1) { + partition.getMatrixAt(index, matrix); + rungs.set(matrix.elements[14] as number, partition.geometry); + } + } + return rungs; +} + +/** A manifest entry carrying the runtime budget a cook resolves, as `assets.manifest.json` holds it. */ +function manifestFor(runtime: { hysteresis: number; maxPixelError: number }): unknown { + return { + entries: { + "forest.glb": { + kind: "model", + lod: { generated: 1, preset: "quality", runtime }, + output: "forest.abc123.glb", + }, + }, + version: 1, + }; +} + +/** Loads the instanced fixture through the real loader and runs one selection from a far camera. */ +async function selectInstanced(manifest?: unknown): Promise<{ + base: BufferGeometry; + drawn: Map; + instanced: InstancedMesh; + scene: Scene; +}> { + stubFetch(cookedGlb({ instances: true }), manifest); + const loader = createAssetLoader({ basePath: "" }); + const value = await loader.model<{ scene: Object3D }>("forest.glb"); + const instanced = firstInstancedMesh(value.scene); + const base = baseGeometryOf(instanced); + const scene = new Scene(); + scene.add(value.scene); + const camera = new PerspectiveCamera(60, 1, 0.1, 1000); + camera.position.set(0, 0, 100); + camera.updateMatrixWorld(true); + updateModelLods(scene, camera, 1080); + return { base, drawn: rungsByZ(instanced), instanced, scene }; +} + afterEach(() => { vi.unstubAllGlobals(); }); describe("createAssetLoader with a cooked TN_discrete_lod model", () => { + it("attaches instanced partitions when the cooked model has alternate LOD scenes", async () => { + stubFetch(cookedGlb({ instances: true, alternateScene: true })); + const loader = createAssetLoader({ basePath: "" }); + const value = await loader.model<{ scene: Object3D }>("forest.glb"); + const mesh = firstInstancedMesh(value.scene); + expect(mesh.children.filter((child) => child instanceof InstancedMesh)).toHaveLength(2); + loader.clear(); + }); it("attaches the chain and selects by camera through the normal load path", async () => { stubFetch(cookedGlb()); const loader = createAssetLoader({ basePath: "" }); @@ -300,4 +422,76 @@ describe("createAssetLoader with a cooked TN_discrete_lod model", () => { expect(updateModelLods(scene, camera, 1080)).toBe(LOD0_TRIANGLES); expect(mesh.geometry.index?.count).toBe(LOD0_TRIANGLES * 3); }, 120_000); + + it("gives each imported instance the rung its own distance selects", async () => { + const { base, drawn } = await selectInstanced(); + // The near placement keeps LOD0 while the far one takes the coarse level: two placements, two rungs. + expect(drawn.size).toBe(2); + expect(drawn.get(NEAR_Z)).toBe(base); + expect(drawn.get(FAR_Z)).not.toBe(base); + expect(drawn.get(FAR_Z)?.index?.count).toBe(COARSE_TRIANGLES * 3); + }, 120_000); + + it("keeps the authored instance slots, count, ray hits and geometry ownership", async () => { + const { base, instanced, scene } = await selectInstanced(); + const levels = lodChainOf(base)?.levels ?? []; + expect(levels.length).toBe(2); + expect(instanced.count).toBe(2); + const matrix = new Matrix4(); + instanced.getMatrixAt(0, matrix); + expect(matrix.elements[14]).toBe(NEAR_Z); + instanced.getMatrixAt(1, matrix); + expect(matrix.elements[14]).toBe(FAR_Z); + // A ray down the z axis crosses both placements; the hits report their public ids, nearest first. + scene.updateMatrixWorld(true); + const raycaster = new Raycaster(new Vector3(0.23, 0, 200), new Vector3(0, 0, -1)); + expect(raycaster.intersectObject(instanced).map((hit) => hit.instanceId)).toEqual([0, 1]); + // Disposal releases the partitions; the chain's geometries stay owned by the loader. + const disposals = levels.map((geometry) => vi.spyOn(geometry, "dispose")); + instanced.dispatchEvent({ type: "dispose" }); + for (const spy of disposals) expect(spy).not.toHaveBeenCalled(); + expect(instanced.geometry).toBe(base); + expect(instanced.children.some((child) => child instanceof InstancedMesh)).toBe(false); + }, 120_000); + + it("honours the manifest's pixel budget for each imported instance", async () => { + // A 0.1 px budget refines the far placement, which the default 4 px budget coarsens. + const { base, drawn } = await selectInstanced( + manifestFor({ hysteresis: 0.15, maxPixelError: 0.1 }), + ); + expect(drawn.get(NEAR_Z)).toBe(base); + expect(drawn.get(FAR_Z)).toBe(base); + }, 120_000); + + it("honours the manifest's hysteresis for each imported instance", async () => { + // Coarsening needs the error under (1 - hysteresis) * budget: 10% of 1 px keeps the far placement at LOD0. + const { base, drawn } = await selectInstanced( + manifestFor({ hysteresis: 0.9, maxPixelError: 1 }), + ); + expect(drawn.get(FAR_Z)).toBe(base); + }, 120_000); + + it("recovers each imported primitive's own extension in a multi-part instanced node", async () => { + stubFetch(cookedGlb({ instances: true, mixedParts: true })); + const value = await createAssetLoader({ basePath: "" }).model<{ scene: Object3D }>( + "forest.glb", + ); + const parts: InstancedMesh[] = []; + value.scene.traverse((object) => { + if (object instanceof InstancedMesh && !(object.parent instanceof InstancedMesh)) + parts.push(object); + }); + expect(parts).toHaveLength(2); + const scene = new Scene(); + scene.add(value.scene); + const camera = new PerspectiveCamera(60, 1, 0.1, 1000); + camera.position.z = 100; + camera.updateMatrixWorld(true); + updateModelLods(scene, camera, 1080); + const chained = parts[0] as InstancedMesh; + const unchained = parts[1] as InstancedMesh; + expect(rungsByZ(chained).get(FAR_Z)?.index?.count).toBe(COARSE_TRIANGLES * 3); + expect(unchained.children).toHaveLength(0); + expect(rungsByZ(unchained).get(FAR_Z)?.index?.count).toBe(LOD0_TRIANGLES * 3); + }, 120_000); }); diff --git a/packages/core/__tests__/render-pass-budget.spec.ts b/packages/core/__tests__/render-pass-budget.spec.ts index 96d7dcec97..b644f732bf 100644 --- a/packages/core/__tests__/render-pass-budget.spec.ts +++ b/packages/core/__tests__/render-pass-budget.spec.ts @@ -211,6 +211,114 @@ describe("render pass budget", () => { expect(budget.nextGpuFrame()).toBeUndefined(); }); + it("attributes authored colour and depth through a fullscreen pipeline to main", () => { + const world = { name: "World" }; + const fullscreen = { name: "Fullscreen" }; + const camera = {}; + const reflectionCamera = {}; + const pool = { queryOffsets: new Map(), timestamps: new Map() }; + let allocation = 0; + let depth = false; + const raw = { + backend: { timestampQueryPool: { render: pool } }, + info: { render: { drawCalls: 0, triangles: 0 } }, + render(scene: { name: string }, view: object): void { + let ms: number; + if (scene === fullscreen) { + raw.render(world, camera); + ms = 0.2; + } else if (scene.name === "Shadow Map") ms = 2; + else if (scene.name === "Reflector") ms = 3; + else if (depth) ms = 1; + else { + expect(view).toBe(camera); + depth = true; + raw.render(world, camera); + depth = false; + world.name = "Shadow Map"; + raw.render(world, camera); + world.name = "Reflector"; + raw.render(world, reflectionCamera); + world.name = "World"; + ms = 6; + } + const uid = `r:0:${++allocation}:f7`; + pool.queryOffsets.set(uid, pool.queryOffsets.size * 2); + pool.timestamps.set(uid, ms); + raw.info.render.drawCalls++; + raw.info.render.triangles++; + }, + }; + const budget = RenderPassBudget.install(raw) as RenderPassBudget; + budget.beginFrame(); + RenderPassBudget.renderWorld(raw, world, camera, () => raw.render(fullscreen, {})); + const gpu = budget.nextGpuFrame(); + expect(gpu).toEqual({ frame: 7, main: 7, shadow: 2, shadowPasses: 1, total: 12.2 }); + expect((gpu?.total ?? 0) - (gpu?.main ?? 0) - (gpu?.shadow ?? 0)).toBeCloseTo(3.2); + expect(budget.passes().filter((pass) => pass.kind === "main")).toHaveLength(2); + }); + + it("restores reentrant world scopes after throws and keeps standalone attribution", () => { + const raw = fakeRenderer(); + const budget = RenderPassBudget.install(raw) as RenderPassBudget; + const outer = { ...MAIN, name: "Outer" }; + const inner = { ...MAIN, name: "Inner" }; + const camera = {}; + const wrongCamera = {}; + const sentinel = new Error("render failed"); + budget.beginFrame(); + RenderPassBudget.renderWorld(raw, outer, camera, () => { + raw.render(outer, camera); + expect(() => + RenderPassBudget.renderWorld(raw, inner, camera, () => { + raw.render(inner, camera); + raw.render(outer, camera); + throw sentinel; + }), + ).toThrow(sentinel); + raw.render(outer, camera); + raw.render(outer, wrongCamera); + outer.name = "Shadow Map"; + raw.render(outer, camera); + outer.name = "Reflector"; + raw.render(outer, camera); + outer.name = "Outer"; + }); + raw.render(inner, wrongCamera); + expect(budget.passes().map((pass) => pass.kind)).toEqual([ + "main", + "main", + "nested", + "main", + "nested", + "shadow", + "reflection", + "main", + ]); + expect(() => + RenderPassBudget.renderWorld(raw, outer, camera, () => { + throw sentinel; + }), + ).toThrow(sentinel); + raw.render(inner, camera); + expect(budget.passes().at(-1)?.kind).toBe("main"); + }); + + it("preserves an uninstrumented world draw and its exception", () => { + const target = {}; + let draws = 0; + const sentinel = new Error("render failed"); + RenderPassBudget.renderWorld(target, {}, {}, () => { + draws++; + }); + expect(draws).toBe(1); + expect(() => + RenderPassBudget.renderWorld(target, {}, {}, () => { + throw sentinel; + }), + ).toThrow(sentinel); + }); + it("leaves the frame absent until its recorded shadow pass resolves, never a partial total", () => { // The defect: a shadow pass is recorded but its uid never lands in `timestamps`, and the reading // forwarded `total: 5.5` — only the main pass — as the frame's cost. A partial sum is not a whole diff --git a/packages/core/__tests__/renderer-texture-preparation.spec.ts b/packages/core/__tests__/renderer-texture-preparation.spec.ts index f6021eb386..34248da8aa 100644 --- a/packages/core/__tests__/renderer-texture-preparation.spec.ts +++ b/packages/core/__tests__/renderer-texture-preparation.spec.ts @@ -1,6 +1,24 @@ -import { CompressedTexture, DataTexture, RGBA_BPTC_Format } from "three"; +import { readFileSync } from "node:fs"; +import { + CompressedArrayTexture, + CompressedTexture, + DataTexture, + ExternalTexture, + MirroredRepeatWrapping, + NearestFilter, + NoColorSpace, + RGBAFormat, + RGBA_ASTC_5x4_Format, + RGBA_BPTC_Format, + RGBA_S3TC_DXT1_Format, + RGBA_S3TC_DXT5_Format, + SRGBColorSpace, + Source, + type Texture, +} from "three"; import { afterEach, expect, it, vi } from "vitest"; import { createRenderer } from "../src/renderer.js"; +import { transcodeKtx2ToBc7 } from "./support/basis-transcode.js"; afterEach(() => vi.unstubAllGlobals()); @@ -24,122 +42,1433 @@ function compressed(): CompressedTexture { return value; } -it("forwards original compressed Texture identities once and awaits their preparation gate", async () => { - vi.stubGlobal("navigator", { gpu: {} }); - let release = () => {}; - const gate = new Promise((resolve) => { - release = resolve; +/** Transcodes the committed real KTX2 fixture to BC7 through the installed Basis transcoder. */ +async function realCompressed(): Promise<{ + texture: CompressedTexture; + mipmaps: { data: Uint8Array; width: number; height: number }[]; +}> { + const bytes = readFileSync(new URL("./fixtures/compressed-upload.ktx2", import.meta.url)); + const transcoded = await transcodeKtx2ToBc7(bytes); + const texture = new CompressedTexture( + transcoded.mipmaps.map((mip) => ({ data: mip.data, width: mip.width, height: mip.height })), + transcoded.width, + transcoded.height, + RGBA_BPTC_Format, + ); + texture.needsUpdate = true; + return { texture, mipmaps: transcoded.mipmaps }; +} + +interface IWrite { + bytes: number; + destination: { + texture?: unknown; + mipLevel: number; + origin: { x: number; y: number; z: number }; + }; + data: Uint8Array; + layout?: { offset: number; bytesPerRow: number; rowsPerImage: number }; + extent?: { width: number; height: number; depthOrArrayLayers: number }; +} + +function mockDevice(options: { feature?: boolean } = {}) { + const writes: IWrite[] = []; + const created: { descriptor: object; destroy: () => void }[] = []; + const destroy = vi.fn(); + const writeTexture = vi.fn( + ( + destination: IWrite["destination"], + data: Uint8Array, + layout?: IWrite["layout"], + extent?: IWrite["extent"], + ) => { + writes.push({ bytes: data.byteLength, destination, data: data.slice(), layout, extent }); + }, + ); + const createTexture = vi.fn((descriptor: object) => { + const texture = { descriptor, destroy }; + created.push(texture); + return texture; }); - const prepare = vi.fn((_texture: unknown, _options: object) => gate); - const renderer = await createRenderer({ + const features = { has: vi.fn(() => options.feature ?? true) }; + const device = { queue: { writeTexture }, createTexture, features }; + return { createTexture, created, destroy, device, features, writeTexture, writes }; +} + +async function webgpuRenderer(extra: Record = {}) { + vi.stubGlobal("navigator", { gpu: {} }); + return createRenderer({ canvas: canvas(), webgpuFactory: () => ({ domElement: canvas(), - prepareTextureAsync: prepare, render: () => {}, setSize: () => {}, + ...extra, }), }); +} + +function reassemble(writes: IWrite[], mipLevel: number): Uint8Array { + const parts = writes + .filter((write) => write.destination.mipLevel === mipLevel) + .map((w) => w.data); + const joined = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0)); + let offset = 0; + for (const part of parts) { + joined.set(part, offset); + offset += part.byteLength; + } + return joined; +} + +it("prepares the real compressed fixture and republishes every transcoded mip byte-for-byte", async () => { + const { device, writes, created } = mockDevice(); + const renderer = await webgpuRenderer({ + backend: { device }, + }); try { - const texture = compressed(); + const { texture, mipmaps } = await realCompressed(); + const originalSource = texture.source; + const originalVersion = originalSource.version; + texture.wrapS = MirroredRepeatWrapping; + texture.magFilter = NearestFilter; + texture.colorSpace = SRGBColorSpace; + texture.offset.set(0.1, 0.2); + texture.repeat.set(2, 3); + texture.rotation = 0.3; + texture.userData = { label: "borrowed source" }; const ordinary = new DataTexture(new Uint8Array(4), 1, 1); - let complete = false; - const pending = renderer.prepareTextures?.([texture, texture, ordinary]).then((count) => { - complete = true; - return count; + const replacements = new Map(); + const count = await renderer.prepareTextures?.( + [texture, texture, ordinary], + undefined, + undefined, + replacements, + ); + expect(count).toBe(1); + const prepared = replacements.get(texture) as ExternalTexture; + expect(prepared).toBeInstanceOf(ExternalTexture); + expect(prepared).not.toBe(texture); + expect(texture.source).toBe(originalSource); + expect(originalSource.version).toBe(originalVersion); + expect(prepared.source).not.toBe(originalSource); + expect(prepared.source.data).toBe(originalSource.data); + expect(prepared.sourceTexture).toBe(created[0]); + expect(prepared.wrapS).toBe(texture.wrapS); + expect(prepared.magFilter).toBe(texture.magFilter); + expect(prepared.colorSpace).toBe(texture.colorSpace); + expect(prepared.offset.toArray()).toEqual([0.1, 0.2]); + expect(prepared.repeat.toArray()).toEqual([2, 3]); + expect(prepared.rotation).toBe(0.3); + expect(prepared.userData).toEqual(texture.userData); + expect(prepared.userData).not.toBe(texture.userData); + expect(mipmaps.length).toBe(7); + for (let mip = 0; mip < mipmaps.length; mip++) + expect(reassemble(writes, mip)).toEqual(mipmaps[mip]?.data); + expect(writes.every((write) => write.bytes <= 65_536)).toBe(true); + } finally { + renderer.dispose(); + } +}); + +it("keeps the numeric contract and leaves the caller map empty without a replacement sink", async () => { + const { device } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const count = await renderer.prepareTextures?.([compressed()]); + expect(count).toBe(1); + } finally { + renderer.dispose(); + } +}); + +it("fails closed when no public GPU device is reachable", async () => { + const renderer = await webgpuRenderer(); + try { + await expect(renderer.prepareTextures?.([compressed()])).rejects.toThrow( + /bounded|unsupported|preparation/i, + ); + } finally { + renderer.dispose(); + } +}); + +it("reports only completed unique preparation and leaves the map unpublished after a failure", async () => { + const { device, writeTexture } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + const observations: number[] = []; + const first = compressed(); + const second = compressed(); + try { + writeTexture.mockImplementationOnce(() => { + throw new Error("write failed"); }); + const replacements = new Map(); + await expect( + renderer.prepareTextures?.( + [first, first, second], + undefined, + (completed) => observations.push(completed), + replacements, + ), + ).rejects.toThrow("write failed"); + expect(observations).toEqual([]); + expect(replacements.size).toBe(0); + } finally { + renderer.dispose(); + } +}); + +it("cancels in-flight preparation when the renderer is disposed", async () => { + const { device } = mockDevice(); + Object.assign(device, { + pushErrorScope: vi.fn(), + popErrorScope: vi.fn(() => new Promise(() => {})), + }); + const renderer = await webgpuRenderer({ backend: { device } }); + const pending = renderer.prepareTextures?.([compressed()]); + const rejected = expect(pending).rejects.toThrow(/disposed/i); + await Promise.resolve(); + renderer.dispose(); + await rejected; +}); + +it("destroys a pending GPU allocation immediately when disposed before the write settles", async () => { + let releaseValidation: (value: unknown) => void = () => {}; + const validation = new Promise((resolve) => { + releaseValidation = resolve; + }); + const destroy = vi.fn(); + const device = { + queue: { writeTexture: vi.fn() }, + features: { has: vi.fn(() => true) }, + createTexture: vi.fn(() => ({ destroy })), + pushErrorScope: vi.fn(), + popErrorScope: vi.fn(() => validation), + }; + const renderer = await webgpuRenderer({ backend: { device } }); + const texture = compressed(); + const replacements = new Map(); + const pending = renderer.prepareTextures?.([texture], undefined, undefined, replacements); + const rejected = expect(pending).rejects.toThrow(/disposed/i); + await Promise.resolve(); + // The GPU texture exists while the deferred write validation is still pending. + expect(device.createTexture).toHaveBeenCalledTimes(1); + expect(destroy).not.toHaveBeenCalled(); + renderer.dispose(); + // Dispose releases the still-pending allocation immediately, before the write settles. + expect(destroy).toHaveBeenCalledTimes(1); + expect(replacements.size).toBe(0); + releaseValidation(null); + await rejected; + // The late write settlement must not destroy the allocation a second time. + expect(destroy).toHaveBeenCalledTimes(1); +}); + +it("releases a cold original's pending allocation when the original is disposed", async () => { + let releaseValidation: (value: unknown) => void = () => {}; + const validation = new Promise((resolve) => { + releaseValidation = resolve; + }); + const destroy = vi.fn(); + const device = { + queue: { writeTexture: vi.fn() }, + features: { has: vi.fn(() => true) }, + createTexture: vi.fn(() => ({ destroy })), + pushErrorScope: vi.fn(), + popErrorScope: vi.fn(() => validation), + }; + const renderer = await webgpuRenderer({ backend: { device } }); + const texture = compressed(); + const replacements = new Map(); + const pending = renderer.prepareTextures?.([texture], undefined, undefined, replacements); + try { await Promise.resolve(); - expect(prepare).toHaveBeenCalledTimes(1); - expect(prepare.mock.calls[0]?.[0]).toBe(texture); - expect(prepare.mock.calls[0]?.[1]).toMatchObject({ budgetMs: 2, maxBytesPerWrite: 65_536 }); - expect(complete).toBe(false); + expect(device.createTexture).toHaveBeenCalledTimes(1); + expect(destroy).not.toHaveBeenCalled(); + // The original disposes while its write validation is still pending. + texture.dispose(); + // Disposal releases the pending allocation before the validation settles. + expect(destroy).toHaveBeenCalledTimes(1); + expect(replacements.size).toBe(0); + releaseValidation(null); + await expect(pending).rejects.toThrow(/disposed/i); + // The late validation settlement must not destroy the allocation a second time. + expect(destroy).toHaveBeenCalledTimes(1); + expect(replacements.size).toBe(0); + } finally { + releaseValidation(null); + renderer.dispose(); + await pending?.catch(() => undefined); + } +}); + +it("publishes no replacement when onProgress disposes the renderer", async () => { + const { device } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + const replacements = new Map(); + try { + await expect( + renderer.prepareTextures?.([compressed()], undefined, () => renderer.dispose(), replacements), + ).rejects.toThrow(/disposed/i); + expect(replacements.size).toBe(0); + } finally { + renderer.dispose(); + } +}); + +it("rejects an unsupported format before allocating any GPU texture", async () => { + const { device, createTexture } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const texture = new CompressedTexture( + [{ data: new Uint8Array(16), width: 4, height: 4 }], + 4, + 4, + RGBAFormat as never, + ); + texture.needsUpdate = true; + await expect(renderer.prepareTextures?.([texture])).rejects.toThrow(/unsupported/i); + expect(createTexture).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } +}); + +it("rejects a format the device does not support before allocating", async () => { + const { device, createTexture } = mockDevice({ feature: false }); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + await expect(renderer.prepareTextures?.([compressed()])).rejects.toThrow( + /texture-compression-bc/, + ); + expect(createTexture).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } +}); + +it("returns the same owned replacement for a repeated call and keeps the old version valid on refresh", async () => { + const { device, destroy } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const { texture } = await realCompressed(); + const first = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, first); + const prepared = first.get(texture) as ExternalTexture; + // A repeated call that hands back the published replacement resolves to the same identity. + const repeated = new Map(); + await renderer.prepareTextures?.([prepared], undefined, undefined, repeated); + expect(repeated.get(prepared)).toBe(prepared); + // A version refresh publishes a new replacement and leaves the previous one valid. + texture.version += 1; + const refreshed = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, refreshed); + expect(refreshed.get(texture)).not.toBe(prepared); + expect(destroy).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } +}); + +it("destroys an allocated replacement exactly once when invalidated before commit", async () => { + const destroys: ReturnType[] = []; + const device = { + queue: { writeTexture: vi.fn() }, + features: { has: vi.fn(() => true) }, + createTexture: vi.fn(() => { + const destroy = vi.fn(); + destroys.push(destroy); + return { destroy }; + }), + }; + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const { texture } = await realCompressed(); + const committed = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, committed); + const prepared = committed.get(texture); + expect(prepared).toBeInstanceOf(ExternalTexture); + expect(destroys).toHaveLength(1); + expect(destroys[0]).not.toHaveBeenCalled(); + + // Invalidate during the write, after allocation and before publication. + texture.version += 1; + device.queue.writeTexture.mockImplementationOnce(() => { + texture.version += 1; + }); + const invalidated = new Map(); + await expect( + renderer.prepareTextures?.([texture], undefined, undefined, invalidated), + ).rejects.toThrow(/invalidated/i); + // The allocated-but-uncommitted replacement is destroyed exactly once. + expect(destroys).toHaveLength(2); + expect(destroys[1]).toHaveBeenCalledTimes(1); + // The prior committed version is untouched and the caller map is unchanged. + expect(destroys[0]).not.toHaveBeenCalled(); + expect(invalidated.size).toBe(0); + expect(committed.get(texture)).toBe(prepared); + } finally { + renderer.dispose(); + } +}); + +it("releases the fresh allocation exactly once when a refreshed copy throws", async () => { + const destroys: ReturnType[] = []; + const device = { + queue: { writeTexture: vi.fn() }, + features: { has: vi.fn(() => true) }, + createTexture: vi.fn(() => { + const destroy = vi.fn(); + destroys.push(destroy); + return { destroy }; + }), + }; + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const { texture } = await realCompressed(); + const committed = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, committed); + const prepared = committed.get(texture); + expect(prepared).toBeInstanceOf(ExternalTexture); + expect(destroys).toHaveLength(1); + expect(destroys[0]).not.toHaveBeenCalled(); + + // A circular `userData` makes `Texture.copy`'s JSON round-trip throw on the refreshed version. + texture.userData.self = texture.userData; + texture.version += 1; + const refreshed = new Map(); + await expect( + renderer.prepareTextures?.([texture], undefined, undefined, refreshed), + ).rejects.toThrow(/circular/i); + // The freshly allocated GPU texture is destroyed exactly once; the committed version survives. + expect(destroys).toHaveLength(2); + expect(destroys[1]).toHaveBeenCalledTimes(1); + expect(destroys[0]).not.toHaveBeenCalled(); + expect(refreshed.size).toBe(0); + expect(committed.get(texture)).toBe(prepared); + } finally { + renderer.dispose(); + } +}); + +/** Waits one macrotask so queued microtasks, including the lane's first write, have run. */ +function flush(): Promise { + return new Promise((resolve) => setTimeout(resolve, 0)); +} + +/** + * A device whose write validation stays pending until `release`. Each allocation records its own + * destroy mock, so a test can tell which GPU texture a rejection released. + */ +function heldDevice() { + const destroys: ReturnType[] = []; + let validation: Promise = Promise.resolve(null); + let settle: (value: unknown) => void = () => {}; + const device = { + queue: { writeTexture: vi.fn() }, + features: { has: vi.fn(() => true) }, + createTexture: vi.fn(() => { + const destroy = vi.fn(); + destroys.push(destroy); + return { destroy }; + }), + pushErrorScope: vi.fn(), + popErrorScope: vi.fn(() => validation), + }; + return { + device, + destroys, + hold: () => { + validation = new Promise((resolve) => { + settle = resolve; + }); + }, + release: () => settle(null), + }; +} + +type IMip = CompressedTexture["mipmaps"][number]; + +/** + * Source edits that leave `texture.version` unchanged. Each one must reject a refresh that is + * still running and must replace a completed cached version. + */ +const SOURCE_CHANGES: ReadonlyArray void]> = [ + [ + "source.version", + (texture) => { + texture.source.version += 1; + }, + ], + [ + "image width", + (texture) => { + (texture.image as { width: number }).width = 8; + }, + ], + [ + "mip width", + (texture) => { + (texture.mipmaps[0] as { width: number }).width = 2; + }, + ], + [ + "image object", + (texture) => { + texture.image = { width: 4, height: 4 }; + }, + ], + [ + "mip object", + (texture) => { + texture.mipmaps[0] = { ...(texture.mipmaps[0] as IMip) }; + }, + ], + [ + "mip data", + (texture) => { + (texture.mipmaps[0] as IMip).data = new Uint8Array(16); + }, + ], + [ + "mip array", + (texture) => { + texture.mipmaps = [...texture.mipmaps]; + }, + ], + [ + "source object", + (texture) => { + texture.source = new Source(texture.image); + }, + ], +]; + +/** The UV and matrix state that `Texture.copy` carries into a replacement. */ +function uvState(texture: Texture): number[] { + return [ + texture.offset.x, + texture.offset.y, + texture.repeat.x, + texture.repeat.y, + texture.rotation, + ...texture.matrix.elements, + ]; +} + +/** UV and matrix edits. None of them sets `needsUpdate`, so the snapshot must detect each one. */ +const UV_CHANGES: ReadonlyArray void]> = [ + [ + "offset", + (texture) => { + texture.offset.x = 0.25; + }, + ], + [ + "repeat", + (texture) => { + texture.repeat.set(2, 3); + }, + ], + [ + "rotation", + (texture) => { + texture.rotation = 0.5; + }, + ], + [ + "manual matrix", + (texture) => { + texture.matrixAutoUpdate = false; + texture.matrix.elements[6] = 0.25; + }, + ], +]; + +/** A readiness edit with no version bump. The snapshot must reject it during a refresh. */ +const READY_CHANGES: ReadonlyArray void]> = [ + [ + "source.dataReady", + (texture) => { + texture.source.dataReady = false; + }, + ], +]; + +it.each([...SOURCE_CHANGES, ...UV_CHANGES, ...READY_CHANGES])( + "rejects a refresh whose %s changes while its write validation is deferred", + async (_name, change) => { + const held = heldDevice(); + const renderer = await webgpuRenderer({ backend: { device: held.device } }); + const texture = compressed(); + const committed = new Map(); + let pending: Promise | undefined; + try { + await renderer.prepareTextures?.([texture], undefined, undefined, committed); + const cached = committed.get(texture); + held.hold(); + texture.version += 1; + const refreshed = new Map(); + pending = renderer.prepareTextures?.([texture], undefined, undefined, refreshed); + const rejected = expect(pending).rejects.toThrow(/invalidated/i); + await flush(); + expect(held.destroys).toHaveLength(2); + // The source changes without a version bump while the refresh waits on validation. + change(texture); + held.release(); + await rejected; + expect(held.destroys[1]).toHaveBeenCalledTimes(1); + expect(held.destroys[0]).not.toHaveBeenCalled(); + expect(refreshed.size).toBe(0); + expect(committed.get(texture)).toBe(cached); + } finally { + held.release(); + renderer.dispose(); + await pending?.catch(() => undefined); + } + }, +); + +it.each(UV_CHANGES)( + "publishes a fresh replacement after a cached %s edit without needsUpdate", + async (_name, change) => { + const { device, created } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const texture = compressed(); + const first = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, first); + const cached = first.get(texture) as Texture; + const before = uvState(texture); + change(texture); + const refreshed = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, refreshed); + expect(refreshed.get(texture)).not.toBe(cached); + expect(uvState(refreshed.get(texture) as Texture)).toEqual(uvState(texture)); + // The replacement published before the edit keeps the value it was prepared with. + expect(uvState(cached)).toEqual(before); + expect(created).toHaveLength(2); + } finally { + renderer.dispose(); + } + }, +); + +it("refreshes the GPU descriptor after cached colorSpace and format edits without needsUpdate", async () => { + const { device, created } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const texture = compressed(); + texture.colorSpace = SRGBColorSpace; + await renderer.prepareTextures?.([texture]); + texture.colorSpace = NoColorSpace; + const linear = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, linear); + texture.format = RGBA_S3TC_DXT5_Format; + await renderer.prepareTextures?.([texture]); + const formats = created.map((entry) => (entry.descriptor as { format: string }).format); + expect(formats).toEqual(["bc7-rgba-unorm-srgb", "bc7-rgba-unorm", "bc3-rgba-unorm"]); + expect((linear.get(texture) as Texture).colorSpace).toBe(NoColorSpace); + } finally { + renderer.dispose(); + } +}); + +it("drains every popped validation before a synchronous write failure rejects the refresh", async () => { + // Each pop returns its own pending validation, so the test releases scopes one at a time. + const scopes: ((value: unknown) => void)[] = []; + const destroys: ReturnType[] = []; + let clock = 0; + const now = vi.spyOn(performance, "now").mockImplementation(() => clock); + let holding = false; + let writes = 0; + let failAt = 0; + const device = { + queue: { + writeTexture: vi.fn(() => { + writes += 1; + // Each write spends the 2 ms budget, so every tile starts a new batch. + clock += 3; + if (writes === failAt) throw new Error("write rejected synchronously"); + }), + }, + features: { has: vi.fn(() => true) }, + createTexture: vi.fn(() => { + const destroy = vi.fn(); + destroys.push(destroy); + return { destroy }; + }), + pushErrorScope: vi.fn(), + popErrorScope: vi.fn(() => + holding + ? new Promise((resolve) => { + scopes.push(resolve); + }) + : Promise.resolve(null), + ), + }; + const renderer = await webgpuRenderer({ backend: { device } }); + const { texture } = await realCompressed(); + const queued = compressed(); + const committed = new Map(); + const refreshed = new Map(); + const queuedMap = new Map(); + const release = () => { + holding = false; + for (const settle of scopes.splice(0)) settle(null); + }; + let pending: Promise | undefined; + let queuedPending: Promise | undefined; + try { + await renderer.prepareTextures?.([texture], undefined, undefined, committed); + const cached = committed.get(texture); + holding = true; + failAt = writes + 2; + texture.version += 1; + const outcome = vi.fn(); + pending = renderer.prepareTextures?.([texture], undefined, undefined, refreshed); + pending?.then( + () => outcome("resolved"), + (error: unknown) => outcome("rejected", error), + ); + queuedPending = renderer.prepareTextures?.([queued], undefined, undefined, queuedMap); + await vi.waitFor(() => expect(writes).toBeGreaterThanOrEqual(failAt)); + await flush(); + // Two batches hold four scopes. The failing write must not release the lane early. + expect(outcome).not.toHaveBeenCalled(); + expect(device.createTexture).toHaveBeenCalledTimes(2); + scopes.shift()?.(null); + await flush(); + expect(outcome).not.toHaveBeenCalled(); + expect(device.createTexture).toHaveBeenCalledTimes(2); release(); - await expect(pending).resolves.toBe(1); + await expect(pending).rejects.toThrow("write rejected synchronously"); + expect(refreshed.size).toBe(0); + expect(destroys[1]).toHaveBeenCalledTimes(1); + expect(destroys[0]).not.toHaveBeenCalled(); + expect(committed.get(texture)).toBe(cached); + await queuedPending; + expect(device.createTexture).toHaveBeenCalledTimes(3); + expect(queuedMap.size).toBe(1); } finally { release(); renderer.dispose(); + await Promise.allSettled([pending, queuedPending]); + now.mockRestore(); } }); -it("fails closed when a WebGPU renderer lacks the bounded preparation seam", async () => { - vi.stubGlobal("navigator", { gpu: {} }); - const sync = vi.fn(); - const renderer = await createRenderer({ - canvas: canvas(), - webgpuFactory: () => ({ - domElement: canvas(), - initTexture: sync, - render: () => {}, - setSize: () => {}, +const INCONSISTENT_DIMENSIONS = new Set(["image width", "mip width"]); + +it.each(SOURCE_CHANGES.filter(([name]) => !INCONSISTENT_DIMENSIONS.has(name)))( + "replaces a completed cached texture when its %s changes without a version bump", + async (_name, change) => { + const held = heldDevice(); + const renderer = await webgpuRenderer({ backend: { device: held.device } }); + try { + const texture = compressed(); + const first = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, first); + const cached = first.get(texture); + change(texture); + const second = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, second); + expect(second.get(texture)).toBeInstanceOf(ExternalTexture); + expect(second.get(texture)).not.toBe(cached); + // The completed version keeps its GPU texture; only the replacement is new. + expect(held.destroys).toHaveLength(2); + expect(held.destroys[0]).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } + }, +); + +// A width that disagrees with the base mip is an inconsistent texture, not a new version. +it.each(SOURCE_CHANGES.filter(([name]) => INCONSISTENT_DIMENSIONS.has(name)))( + "rejects a completed cached texture when its %s no longer matches its mip dimensions", + async (_name, change) => { + const held = heldDevice(); + const renderer = await webgpuRenderer({ backend: { device: held.device } }); + try { + const texture = compressed(); + const first = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, first); + change(texture); + const second = new Map(); + await expect( + renderer.prepareTextures?.([texture], undefined, undefined, second), + ).rejects.toThrow(/Invalid compressed texture dimensions/); + // The mismatch fails before allocation, so the committed GPU texture stays the only one. + expect(second.size).toBe(0); + expect(held.destroys).toHaveLength(1); + expect(held.destroys[0]).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } + }, +); + +it("captures the snapshot at job start so a source change made while queued rejects its write", async () => { + const held = heldDevice(); + const renderer = await webgpuRenderer({ backend: { device: held.device } }); + const blocker = compressed(); + const queued = compressed(); + const queuedMap = new Map(); + let blocked: Promise | undefined; + let pending: Promise | undefined; + try { + held.hold(); + blocked = renderer.prepareTextures?.([blocker]); + await flush(); + pending = renderer.prepareTextures?.([queued], undefined, undefined, queuedMap); + const rejected = expect(pending).rejects.toThrow(/invalidated/i); + // The job already holds its snapshot; this edit lands before the queued write starts. + queued.source.version += 1; + held.release(); + await rejected; + expect(held.destroys).toHaveLength(1); + expect(queuedMap.size).toBe(0); + await blocked; + } finally { + held.release(); + renderer.dispose(); + await Promise.allSettled([blocked, pending]); + } +}); + +it("rejects a new-device request behind an old-device job and prepares on the new device after release", async () => { + const oldDevice = heldDevice(); + const newDevice = heldDevice(); + const backend = { device: oldDevice.device }; + const renderer = await webgpuRenderer({ backend }); + const texture = compressed(); + const committed = new Map(); + let refresh: Promise | undefined; + let replacement: Promise | undefined; + let replacementError: unknown; + try { + await renderer.prepareTextures?.([texture], undefined, undefined, committed); + const cached = committed.get(texture); + oldDevice.hold(); + texture.version += 1; + const refreshed = new Map(); + refresh = renderer.prepareTextures?.([texture], undefined, undefined, refreshed); + const refreshRejected = expect(refresh).rejects.toThrow(/invalidated/i); + await flush(); + expect(oldDevice.destroys).toHaveLength(2); + + // The device changes while the old job waits on validation. + backend.device = newDevice.device; + const replaced = new Map(); + replacement = renderer + .prepareTextures?.([texture], undefined, undefined, replaced) + ?.catch((error: unknown) => { + replacementError = error; + }); + await flush(); + // The request fails at once. It neither joins the old job nor allocates on the new device. + expect((replacementError as Error | undefined)?.message).toMatch(/another device/); + expect(replaced.size).toBe(0); + expect(newDevice.device.createTexture).not.toHaveBeenCalled(); + + oldDevice.release(); + await refreshRejected; + // The old job's fresh allocation is destroyed once. Its committed version stays alive. + expect(oldDevice.destroys[1]).toHaveBeenCalledTimes(1); + expect(oldDevice.destroys[0]).not.toHaveBeenCalled(); + expect(refreshed.size).toBe(0); + expect(committed.get(texture)).toBe(cached); + + const next = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, next); + const prepared = next.get(texture); + expect(prepared).toBeInstanceOf(ExternalTexture); + expect(newDevice.device.createTexture).toHaveBeenCalledTimes(1); + // A repeated call on the new device reuses the version it just committed. + const again = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, again); + expect(again.get(texture)).toBe(prepared); + expect(newDevice.device.createTexture).toHaveBeenCalledTimes(1); + } finally { + oldDevice.release(); + renderer.dispose(); + await Promise.allSettled([refresh, replacement]); + } +}); + +it("rejects a pending reuse whose source changed without a version bump", async () => { + const held = heldDevice(); + const renderer = await webgpuRenderer({ backend: { device: held.device } }); + const texture = compressed(); + let pending: Promise | undefined; + let reused: Promise | undefined; + let reusedError: unknown; + try { + held.hold(); + const first = new Map(); + pending = renderer.prepareTextures?.([texture], undefined, undefined, first); + const rejected = expect(pending).rejects.toThrow(/invalidated/i); + await flush(); + expect(held.destroys).toHaveLength(1); + // The source changes without a version bump while the first write waits on validation. + texture.source.version += 1; + const second = new Map(); + reused = renderer + .prepareTextures?.([texture], undefined, undefined, second) + ?.catch((error: unknown) => { + reusedError = error; + }); + await flush(); + // The stale job is not reused, so the second request fails before validation releases. + expect((reusedError as Error | undefined)?.message).toMatch(/changed during preparation/); + expect(second.size).toBe(0); + expect(held.destroys).toHaveLength(1); + held.release(); + await rejected; + expect(held.destroys[0]).toHaveBeenCalledTimes(1); + } finally { + held.release(); + renderer.dispose(); + await Promise.allSettled([pending, reused]); + } +}); + +it("destroys a pending allocation at the first device loss and rejects later calls", async () => { + const held = heldDevice(); + let lose: (info: unknown) => void = () => {}; + Object.assign(held.device, { + lost: new Promise((resolve) => { + lose = resolve; + }), + }); + const renderer = await webgpuRenderer({ backend: { device: held.device } }); + const texture = compressed(); + let pending: Promise | undefined; + try { + held.hold(); + const map = new Map(); + pending = renderer.prepareTextures?.([texture], undefined, undefined, map); + const rejected = expect(pending).rejects.toThrow(/Device lost during texture preparation/); + await flush(); + expect(held.destroys).toHaveLength(1); + lose({ reason: "unknown", message: "test device lost" }); + await flush(); + // The allocation is destroyed at loss, while validation is still held. + expect(held.destroys[0]).toHaveBeenCalledTimes(1); + held.release(); + await rejected; + expect(held.destroys[0]).toHaveBeenCalledTimes(1); + const later = new Map(); + await expect( + renderer.prepareTextures?.([compressed()], undefined, undefined, later), + ).rejects.toThrow(/Device lost during texture preparation/); + expect(later.size).toBe(0); + expect(held.device.createTexture).toHaveBeenCalledTimes(1); + } finally { + held.release(); + renderer.dispose(); + await Promise.allSettled([pending]); + } +}); + +it("invalidates the whole lane when a second observed device is lost", async () => { + const first = heldDevice(); + const second = heldDevice(); + let loseSecond: (info: unknown) => void = () => {}; + Object.assign(first.device, { lost: new Promise(() => {}) }); + Object.assign(second.device, { + lost: new Promise((resolve) => { + loseSecond = resolve; }), }); + const backend = { device: first.device }; + const renderer = await webgpuRenderer({ backend }); + const texture = compressed(); try { + await renderer.prepareTextures?.([texture], undefined, undefined, new Map()); + expect(first.destroys).toHaveLength(1); + backend.device = second.device; + await renderer.prepareTextures?.([texture], undefined, undefined, new Map()); + expect(second.destroys).toHaveLength(1); + loseSecond({ reason: "unknown", message: "second device lost" }); + await flush(); + // The lane owns one allocation per device. Each is destroyed exactly once. + expect(first.destroys[0]).toHaveBeenCalledTimes(1); + expect(second.destroys[0]).toHaveBeenCalledTimes(1); await expect(renderer.prepareTextures?.([compressed()])).rejects.toThrow( - /bounded|unsupported/i, + /Device lost during texture preparation/, ); - expect(sync).not.toHaveBeenCalled(); } finally { renderer.dispose(); } }); -it("reports only completed unique texture preparation, leaving pending work uncredited", async () => { - vi.stubGlobal("navigator", { gpu: {} }); - const releases: (() => void)[] = []; - const renderer = await createRenderer({ - canvas: canvas(), - webgpuFactory: () => ({ - domElement: canvas(), - prepareTextureAsync: () => - new Promise((resolve) => { - releases.push(resolve); - }), - render: () => {}, - setSize: () => {}, - }), +/** The legacy chain: 320x64 halving to 5x1. */ +const LEGACY_CHAIN: ReadonlyArray = [ + [320, 64], + [160, 32], + [80, 16], + [40, 8], + [20, 4], + [10, 2], + [5, 1], +]; + +const LEGACY_FORMATS = [ + { name: "bc1-rgba-unorm", format: RGBA_S3TC_DXT1_Format, bw: 4, bh: 4, bb: 8 }, + { name: "bc7-rgba-unorm", format: RGBA_BPTC_Format, bw: 4, bh: 4, bb: 16 }, + { name: "astc-5x4-unorm", format: RGBA_ASTC_5x4_Format, bw: 5, bh: 4, bb: 16 }, +]; + +it.each(LEGACY_FORMATS)( + "preserves every block, mip tail and layer for $name through prepareTextures", + async ({ name, format, bw, bh, bb }) => { + const { device, writes, created } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + // The legacy chain: 320x64 halving to 5x1, three layers, each payload at a nonzero offset. + const mips = LEGACY_CHAIN.map(([width, height]) => { + const length = Math.ceil(width / bw) * Math.ceil(height / bh) * bb * 3; + const backing = new Uint8Array(length + 11); + const data = backing.subarray(11); + for (let i = 0; i < data.length; i++) data[i] = (i * 37 + width) % 251; + return { width, height, data }; + }); + const texture = new CompressedArrayTexture(mips, 320, 64, 3, format); + texture.needsUpdate = true; + const replacements = new Map(); + await expect( + renderer.prepareTextures?.([texture], undefined, undefined, replacements), + ).resolves.toBe(1); + expect(replacements.get(texture)).toBeInstanceOf(ExternalTexture); + expect(created).toHaveLength(1); + expect(created[0]?.descriptor).toMatchObject({ + size: { width: 320, height: 64, depthOrArrayLayers: 3 }, + format: name, + mipLevelCount: 7, + }); + + const rebuilt = mips.map((mip) => new Uint8Array(mip.data.length)); + const coverage = mips.map((mip) => new Uint8Array(mip.data.length)); + for (const write of writes) { + const { mipLevel, origin } = write.destination; + const { layout, extent } = write; + const mip = mips[mipLevel]; + const target = rebuilt[mipLevel]; + const covered = coverage[mipLevel]; + if (!mip || !target || !covered || !layout || !extent) throw new Error("Unexpected write"); + // Every write stays inside the byte budget and the block-rounded physical mip. + expect(write.bytes).toBeLessThanOrEqual(65_536); + expect(origin.x % bw).toBe(0); + expect(origin.y % bh).toBe(0); + expect(extent.width % bw).toBe(0); + expect(extent.height % bh).toBe(0); + expect(origin.x + extent.width).toBeLessThanOrEqual(Math.ceil(mip.width / bw) * bw); + expect(origin.y + extent.height).toBeLessThanOrEqual(Math.ceil(mip.height / bh) * bh); + expect(origin.z).toBeLessThan(3); + expect(extent.depthOrArrayLayers).toBe(1); + const stride = Math.ceil(mip.width / bw) * bb; + const rows = Math.ceil(mip.height / bh); + expect(layout.bytesPerRow).toBe(stride); + expect(layout.rowsPerImage).toBe(rows); + const widthBytes = (extent.width / bw) * bb; + for (let row = 0; row < extent.height / bh; row++) { + const offset = + origin.z * stride * rows + (origin.y / bh + row) * stride + (origin.x / bw) * bb; + target.set(write.data.subarray(row * stride, row * stride + widthBytes), offset); + for (let i = offset; i < offset + widthBytes; i++) covered[i] = (covered[i] ?? 0) + 1; + } + } + expect(rebuilt).toEqual(mips.map((mip) => mip.data)); + expect(coverage.every((mip) => mip.every((count) => count === 1))).toBe(true); + expect(new Set(writes.map((write) => write.destination.texture)).size).toBe(1); + } finally { + renderer.dispose(); + } + }, +); + +it("rejects a mip whose size is not the halved base before any GPU allocation", async () => { + const { device, createTexture } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + // Mip 1 must be 4x4 for an 8x8 base. This mip repeats the base size. + const texture = new CompressedTexture( + [ + { data: new Uint8Array(64), width: 8, height: 8 }, + { data: new Uint8Array(64), width: 8, height: 8 }, + ], + 8, + 8, + RGBA_BPTC_Format, + ); + texture.needsUpdate = true; + await expect(renderer.prepareTextures?.([texture])).rejects.toThrow( + /Invalid compressed texture dimensions/, + ); + expect(createTexture).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } +}); + +it("rejects a mip chain longer than the base dimensions allow before any GPU allocation", async () => { + const { device, createTexture } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + // A 4x4 base holds three levels. The fourth 1x1 level is excess, and each size is correct. + const texture = new CompressedTexture( + [4, 2, 1, 1].map((size) => ({ data: new Uint8Array(16), width: size, height: size })), + 4, + 4, + RGBA_BPTC_Format, + ); + texture.needsUpdate = true; + await expect(renderer.prepareTextures?.([texture])).rejects.toThrow( + /Invalid compressed texture mip chain/, + ); + expect(createTexture).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } +}); + +it("rejects an unaligned block-compressed base before allocating when unaligned support is absent", async () => { + const createTexture = vi.fn(() => ({ destroy: vi.fn() })); + // Only the unaligned capability is missing. Every other feature the format needs is present. + const features = { has: vi.fn((name: string) => name !== "texture-compression-unaligned") }; + const renderer = await webgpuRenderer({ + backend: { device: { queue: { writeTexture: vi.fn() }, features, createTexture } }, }); - const observations: number[] = []; - const first = compressed(); - const pending = renderer.prepareTextures?.([first, first, compressed()], undefined, (completed) => - observations.push(completed), + try { + // A 5x5 base spans two block columns and two block rows of 4x4 BC7 blocks. + const texture = new CompressedTexture( + [{ data: new Uint8Array(64), width: 5, height: 5 }], + 5, + 5, + RGBA_BPTC_Format, + ); + texture.needsUpdate = true; + await expect(renderer.prepareTextures?.([texture])).rejects.toThrow(/block-aligned/); + expect(features.has).toHaveBeenCalledWith("texture-compression-unaligned"); + expect(createTexture).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } +}); + +it("prepares an unaligned block-compressed base when the device reports unaligned support", async () => { + const { device, created, writes } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const texture = new CompressedTexture( + [{ data: new Uint8Array(64), width: 5, height: 5 }], + 5, + 5, + RGBA_BPTC_Format, + ); + texture.needsUpdate = true; + const replacements = new Map(); + await expect( + renderer.prepareTextures?.([texture], undefined, undefined, replacements), + ).resolves.toBe(1); + expect(created).toHaveLength(1); + expect(writes).not.toHaveLength(0); + // The rounded physical extent is 8x8, so every write stays inside it. + for (const { destination, extent } of writes) { + expect(destination.origin.x + (extent?.width ?? 0)).toBeLessThanOrEqual(8); + expect(destination.origin.y + (extent?.height ?? 0)).toBeLessThanOrEqual(8); + } + } finally { + renderer.dispose(); + } +}); + +/** A 4x4 BC7 array with one mip of `layers` layers. Surplus layers beyond `depth` are allowed. */ +function layered(depth: number, layers = 4): CompressedArrayTexture { + const value = new CompressedArrayTexture( + [{ data: new Uint8Array(16 * layers), width: 4, height: 4 }], + 4, + 4, + depth, + RGBA_BPTC_Format, ); + value.needsUpdate = true; + return value; +} + +it("rejects truncated array layer data before allocating or publishing", async () => { + const { device, createTexture, writeTexture } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); try { - expect(observations).toEqual([]); - releases[0]?.(); - await Promise.resolve(); - await Promise.resolve(); - expect(observations).toEqual([1]); - releases[1]?.(); - await expect(pending).resolves.toBe(2); - expect(observations).toEqual([1, 2]); + // Depth three needs three 16-byte layers. The mip holds only two. + const replacements = new Map(); + await expect( + renderer.prepareTextures?.([layered(3, 2)], undefined, undefined, replacements), + ).rejects.toThrow(/Invalid compressed texture layer data/); + expect(createTexture).not.toHaveBeenCalled(); + expect(writeTexture).not.toHaveBeenCalled(); + expect(replacements.size).toBe(0); } finally { - for (const release of releases) release(); renderer.dispose(); } }); -it("cancels preparation when the renderer is disposed", async () => { - vi.stubGlobal("navigator", { gpu: {} }); - const prepare = vi.fn( - (_texture: unknown, options: { signal: AbortSignal }) => - new Promise((_resolve, reject) => { - options.signal.addEventListener("abort", () => reject(options.signal.reason), { - once: true, - }); +it("rejects an image byte count beyond the safe integer range before allocating", async () => { + const { device, createTexture } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + // One 2^40-square BC7 layer needs 2^80 bytes. The data is tiny, so only the count is unsafe. + const side = 2 ** 40; + const texture = new CompressedTexture( + [{ data: new Uint8Array(16), width: side, height: side }], + side, + side, + RGBA_BPTC_Format, + ); + texture.needsUpdate = true; + await expect(renderer.prepareTextures?.([texture])).rejects.toThrow( + /Invalid compressed texture layer data/, + ); + expect(createTexture).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } +}); + +it("invalidates a fresh array job when its depth changes after allocation", async () => { + const destroys: ReturnType[] = []; + const scopes: ((value: unknown) => void)[] = []; + let holding = false; + const device = { + queue: { writeTexture: vi.fn() }, + features: { has: vi.fn(() => true) }, + createTexture: vi.fn(() => { + const destroy = vi.fn(); + destroys.push(destroy); + return { destroy }; + }), + pushErrorScope: vi.fn(), + popErrorScope: vi.fn(() => + holding + ? new Promise((resolve) => { + scopes.push(resolve); + }) + : Promise.resolve(null), + ), + }; + const release = () => { + holding = false; + for (const settle of scopes.splice(0)) settle(null); + }; + const renderer = await webgpuRenderer({ backend: { device } }); + const texture = layered(3); + const committed = new Map(); + const refreshed = new Map(); + let pending: Promise | undefined; + try { + await renderer.prepareTextures?.([texture], undefined, undefined, committed); + const prepared = committed.get(texture); + // Hold the next job's validation so the depth change lands after its allocation. + holding = true; + texture.version += 1; + pending = renderer.prepareTextures?.([texture], undefined, undefined, refreshed); + await vi.waitFor(() => expect(scopes.length).toBeGreaterThan(0)); + texture.image.depth = 2; + release(); + await expect(pending).rejects.toThrow(/invalidated/i); + // The fresh allocation is destroyed once. The committed replacement stays alive and published. + expect(destroys).toHaveLength(2); + expect(destroys[1]).toHaveBeenCalledTimes(1); + expect(destroys[0]).not.toHaveBeenCalled(); + expect(refreshed.size).toBe(0); + expect(committed.get(texture)).toBe(prepared); + } finally { + release(); + await pending?.catch(() => undefined); + renderer.dispose(); + } +}); + +it("publishes a fresh replacement after a committed depth change to valid data", async () => { + const { device, created, destroy } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const texture = layered(3); + const first = new Map(); + await renderer.prepareTextures?.([texture], undefined, undefined, first); + // Four layers back the new depth, so the changed payload is valid. + texture.image.depth = 4; + const refreshed = new Map(); + await expect( + renderer.prepareTextures?.([texture], undefined, undefined, refreshed), + ).resolves.toBe(1); + expect(created).toHaveLength(2); + expect(created[1]?.descriptor).toMatchObject({ size: { depthOrArrayLayers: 4 } }); + expect(refreshed.get(texture)).not.toBe(first.get(texture)); + // The prior replacement stays valid until its lifetime ends. + expect(destroy).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } +}); + +it("fails closed after a committed depth change without layer data", async () => { + const { device, created, destroy } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const texture = layered(3); + await renderer.prepareTextures?.([texture]); + // Depth five needs five layers, and the mip holds four. + texture.image.depth = 5; + const refreshed = new Map(); + await expect( + renderer.prepareTextures?.([texture], undefined, undefined, refreshed), + ).rejects.toThrow(/Invalid compressed texture layer data/); + // No replacement is allocated, and the prior one keeps its GPU texture. + expect(created).toHaveLength(1); + expect(destroy).not.toHaveBeenCalled(); + expect(refreshed.size).toBe(0); + } finally { + renderer.dispose(); + } +}); + +it("rejects a 2D compressed texture that declares image depth before allocating", async () => { + const { device, createTexture } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + // The 2D constructor sets no depth. A declared depth above one would drop layers silently. + const texture = compressed(); + Object.assign(texture.image, { depth: 4 }); + await expect(renderer.prepareTextures?.([texture])).rejects.toThrow( + /cannot carry image depth above 1/, + ); + expect(createTexture).not.toHaveBeenCalled(); + } finally { + renderer.dispose(); + } +}); + +async function rejectsDeclaredDepth(depth: unknown, message: RegExp): Promise { + const { device, createTexture, writeTexture } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const texture = compressed(); + Object.assign(texture.image, { depth }); + const replacements = new Map(); + await expect( + renderer.prepareTextures?.([texture], undefined, undefined, replacements), + ).rejects.toThrow(message); + expect(createTexture).not.toHaveBeenCalled(); + expect(writeTexture).not.toHaveBeenCalled(); + expect(replacements.size).toBe(0); + } finally { + renderer.dispose(); + } +} + +// A 2D texture accepts only an undefined or 1 depth. Invalid values must not suggest an array class. +it.each([Number.NaN, 0, -1, Number.NEGATIVE_INFINITY, 0.5, null])( + "rejects a 2D compressed texture declaring invalid image depth %s before allocating", + (depth) => + rejectsDeclaredDepth( + depth, + /^Invalid compressed 2D texture depth\. Only undefined or 1 is valid\.$/, + ), +); + +it.each([Number.POSITIVE_INFINITY, 1.5])( + "rejects a 2D compressed texture declaring oversized image depth %s before allocating", + (depth) => rejectsDeclaredDepth(depth, /cannot carry image depth above 1/), +); + +it.each([undefined, 1])( + "prepares a 2D compressed texture declaring image depth %s as one layer", + async (depth) => { + const { device, createTexture } = mockDevice(); + const renderer = await webgpuRenderer({ backend: { device } }); + try { + const texture = compressed(); + Object.assign(texture.image, { depth }); + await expect(renderer.prepareTextures?.([texture])).resolves.toBe(1); + expect(createTexture).toHaveBeenCalledTimes(1); + expect(createTexture.mock.calls[0]?.[0]).toMatchObject({ size: { depthOrArrayLayers: 1 } }); + } finally { + renderer.dispose(); + } + }, +); + +it("rejects a cancelled caller at once and starts the next allocation after every popped scope settles", async () => { + // Each pop returns its own deferred validation, so the test releases scopes one at a time. + const scopes: ((value: unknown) => void)[] = []; + const destroys: ReturnType[] = []; + let holding = true; + let clock = 0; + const now = vi.spyOn(performance, "now").mockImplementation(() => clock); + const device = { + queue: { + writeTexture: vi.fn(() => { + // Each write spends the 2 ms budget, so every tile starts a new batch. + clock += 3; }), - ); - const renderer = await createRenderer({ - canvas: canvas(), - webgpuFactory: () => ({ - domElement: canvas(), - prepareTextureAsync: prepare, - render: () => {}, - setSize: () => {}, + }, + features: { has: vi.fn(() => true) }, + createTexture: vi.fn(() => { + const destroy = vi.fn(); + destroys.push(destroy); + return { destroy }; }), - }); - const pending = renderer.prepareTextures?.([compressed()]); - const rejected = expect(pending).rejects.toThrow(/disposed/i); - await Promise.resolve(); - renderer.dispose(); - await rejected; + pushErrorScope: vi.fn(), + popErrorScope: vi.fn(() => + holding + ? new Promise((resolve) => { + scopes.push(resolve); + }) + : Promise.resolve(null), + ), + }; + const renderer = await webgpuRenderer({ backend: { device } }); + const { texture } = await realCompressed(); + const queued = compressed(); + const controller = new AbortController(); + const first = new Map(); + const next = new Map(); + const release = () => { + holding = false; + for (const settle of scopes.splice(0)) settle(null); + }; + let pending: Promise | undefined; + let queuedPending: Promise | undefined; + try { + pending = renderer.prepareTextures?.([texture], controller.signal, undefined, first); + queuedPending = renderer.prepareTextures?.([queued], undefined, undefined, next); + // Four batches of two pops each hold eight scopes. The lane then waits on the first batch. + await vi.waitFor(() => expect(scopes).toHaveLength(8)); + controller.abort(new Error("caller cancelled")); + // The caller rejects at once, while every popped scope is still pending. + await expect(pending).rejects.toThrow("caller cancelled"); + expect(device.createTexture).toHaveBeenCalledTimes(1); + // Settling one scope is not enough. The first batch still waits on its partner scope. + scopes.shift()?.(null); + await flush(); + expect(device.createTexture).toHaveBeenCalledTimes(1); + expect(next.size).toBe(0); + // Settling every remaining scope lets the lane finish and start the queued allocation. + release(); + await queuedPending; + expect(device.createTexture).toHaveBeenCalledTimes(2); + expect(destroys[0]).toHaveBeenCalledTimes(1); + expect(destroys[1]).not.toHaveBeenCalled(); + expect(first.size).toBe(0); + expect(next.size).toBe(1); + } finally { + release(); + renderer.dispose(); + await Promise.allSettled([pending, queuedPending]); + now.mockRestore(); + } }); diff --git a/packages/core/__tests__/renderer.spec.ts b/packages/core/__tests__/renderer.spec.ts index 3697d5b943..82abe6debd 100644 --- a/packages/core/__tests__/renderer.spec.ts +++ b/packages/core/__tests__/renderer.spec.ts @@ -2,7 +2,11 @@ import { Object3D, PerspectiveCamera, Scene } from "three"; import { pass } from "three/tsl"; import { RenderPipeline } from "three/webgpu"; import { describe, expect, it, vi } from "vitest"; +import { defineGame } from "../src/game.js"; +import { RenderChain } from "../src/render/chain.js"; import { createRenderer, prewarm } from "../src/renderer.js"; +import { RESOLUTION_SCALER, ResolutionScaler } from "../src/resolution-scaler.js"; +import { Scene as GameScene, type ICtx } from "../src/scene.js"; function testCanvas(): HTMLCanvasElement { const canvas = new EventTarget() as EventTarget & Partial; @@ -15,6 +19,73 @@ function testCanvas(): HTMLCanvasElement { } describe("createRenderer", () => { + it("keeps the desktop temporalUpscale floor for a TRAA chain without lifting it for velocity", async () => { + let frame: FrameRequestCallback | undefined; + let chain: RenderChain | undefined; + vi.stubGlobal("navigator", { gpu: {} }); + vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => { + frame = callback; + return 1; + }); + vi.stubGlobal("cancelAnimationFrame", () => {}); + const pipeline = vi.spyOn(RenderPipeline.prototype, "render").mockImplementation(() => {}); + const observe = vi.spyOn(ResolutionScaler.prototype, "observe"); + class TemporalScene extends GameScene { + override enter(ctx: ICtx): void { + chain = new RenderChain(ctx.renderer, { + input: pass(ctx.scene, ctx.camera), + stages: [{ name: "traa", build: (input) => input }], + request: { tier: "high", stages: ["traa"], velocity: { perObject: true } }, + report: () => {}, + }); + } + } + const canvas = testCanvas(); + const game = defineGame({ + initialState: {}, + scenes: { TemporalScene }, + start: "TemporalScene", + frameBudget: { reportEvery: 2, report: () => {} }, + renderer: { + canvas, + webgpuFactory: () => ({ + domElement: canvas, + init: async () => {}, + render: () => {}, + setSize: () => {}, + dispose: () => {}, + }), + }, + }); + try { + await game.start(); + expect(chain?.applied.stages).toEqual(["traa"]); + expect(chain?.applied.velocity.provisioned).toBe(true); + if (!frame) throw new Error("No game frame scheduled"); + for (let i = 1; i <= 10; i++) frame(i * 16.7); + const scaler = observe.mock.contexts[0]; + expect(scaler).toBeInstanceOf(ResolutionScaler); + if (!(scaler instanceof ResolutionScaler)) throw new Error("Game did not observe its scaler"); + for (let i = 0; i < 30; i++) { + scaler.observe({ + fps: 5 / scaler.scale ** 2, + gpuMs: 200 * scaler.scale ** 2, + gpuAgeFrames: 0, + presented: { p50: 200, p95: 200, p99: 200, max: 200 }, + }); + expect(scaler.scale).toBeGreaterThanOrEqual(RESOLUTION_SCALER.desktopFloorScale); + } + expect(scaler.scale).toBe(RESOLUTION_SCALER.desktopFloorScale); + expect(scaler.atFloor).toBe(true); + } finally { + chain?.dispose(); + game.stop(); + observe.mockRestore(); + pipeline.mockRestore(); + vi.unstubAllGlobals(); + } + }); + it("keeps its browser-created canvas layout independent of drawing-buffer scale", async () => { const canvas = testCanvas(); const style = { width: "", height: "" }; diff --git a/packages/core/__tests__/support/basis-transcode.ts b/packages/core/__tests__/support/basis-transcode.ts new file mode 100644 index 0000000000..31854a0efa --- /dev/null +++ b/packages/core/__tests__/support/basis-transcode.ts @@ -0,0 +1,136 @@ +import { readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname } from "node:path"; +import { basisTranscoderPaths } from "../../../../test-support/three-basis.js"; + +// Official KTX2Loader.TranscoderFormat.BC7_M5. Pinned here so the CPU helper never imports the +// loader's private worker source; the enum value is part of the loader's public constants. +const BC7_M5 = 7; + +interface IBasisKTX2File { + isValid(): boolean; + isUASTC(): boolean; + isETC1S(): boolean; + isHDR(): boolean; + getWidth(): number; + getHeight(): number; + getLayers(): number; + getLevels(): number; + getFaces(): number; + getImageLevelInfo( + mip: number, + layer: number, + face: number, + ): { origWidth: number; origHeight: number; width: number; height: number }; + getImageTranscodedSizeInBytes(mip: number, layer: number, face: number, format: number): number; + startTranscoding(): boolean; + transcodeImage( + dst: Uint8Array, + mip: number, + layer: number, + face: number, + format: number, + quality: number, + r: number, + g: number, + ): boolean; + close(): void; + delete(): void; +} + +interface IBasisModule { + KTX2File: new (data: Uint8Array) => IBasisKTX2File; + initializeBasis(): void; +} + +export interface ITranscodedMip { + readonly data: Uint8Array; + readonly width: number; + readonly height: number; +} + +export interface ITranscodedTexture { + readonly mipmaps: ITranscodedMip[]; + readonly width: number; + readonly height: number; +} + +let modulePromise: Promise | undefined; + +/** + * Loads the installed Basis transcoder with its real WASM binary through the public Emscripten + * factory. The transcoder JavaScript ships as a CommonJS-compatible module, so a scoped + * `new Function` evaluation gives the factory without a Worker or a bundled copy. + */ +function loadBasis(): Promise { + modulePromise ??= (async () => { + const { javascriptPath, wasmPath } = basisTranscoderPaths(); + const source = readFileSync(javascriptPath, "utf8"); + const evaluate = new Function( + "module", + "exports", + "require", + "__filename", + "__dirname", + source, + ); + const shell: { exports: unknown } = { exports: {} }; + evaluate( + shell, + shell.exports, + createRequire(import.meta.url), + javascriptPath, + dirname(javascriptPath), + ); + const factory = shell.exports as (config: { + wasmBinary: Uint8Array; + onRuntimeInitialized?: () => void; + }) => void; + const wasmBinary = readFileSync(wasmPath); + const module: IBasisModule & { wasmBinary: Uint8Array; onRuntimeInitialized?: () => void } = { + wasmBinary, + } as unknown as IBasisModule & { wasmBinary: Uint8Array }; + await new Promise((resolve) => { + module.onRuntimeInitialized = resolve; + factory(module); + }); + module.initializeBasis(); + return module; + })(); + return modulePromise; +} + +/** + * Transcodes a Basis-compressed KTX2 container to BC7 through the installed official transcoder. + * Every mip level is transcoded; the caller gets the real compressed bytes, not a stub. + */ +export async function transcodeKtx2ToBc7(bytes: Uint8Array): Promise { + const basis = await loadBasis(); + const file = new basis.KTX2File(bytes); + try { + if (!file.isValid()) throw new Error("Basis transcode: invalid or unsupported .ktx2 file."); + if (!file.isUASTC()) throw new Error("Basis transcode: fixture is not UASTC."); + const width = file.getWidth(); + const height = file.getHeight(); + const levels = file.getLevels(); + if (!width || !height || !levels) throw new Error("Basis transcode: invalid texture."); + if (!file.startTranscoding()) throw new Error("Basis transcode: startTranscoding failed."); + const mipmaps: ITranscodedMip[] = []; + for (let mip = 0; mip < levels; mip++) { + const levelInfo = file.getImageLevelInfo(mip, 0, 0); + const size = file.getImageTranscodedSizeInBytes(mip, 0, 0, BC7_M5); + const data = new Uint8Array(size); + const status = file.transcodeImage(data, mip, 0, 0, BC7_M5, 0, -1, -1); + if (!status) throw new Error(`Basis transcode: transcodeImage failed at mip ${mip}.`); + mipmaps.push({ + data, + width: levels > 1 ? levelInfo.origWidth : levelInfo.width, + height: levels > 1 ? levelInfo.origHeight : levelInfo.height, + }); + } + return { mipmaps, width, height }; + } finally { + file.close(); + file.delete(); + } +} diff --git a/packages/core/__tests__/three-compressed-upload.spec.ts b/packages/core/__tests__/three-compressed-upload.spec.ts index 0617d2a298..2eaf695344 100644 --- a/packages/core/__tests__/three-compressed-upload.spec.ts +++ b/packages/core/__tests__/three-compressed-upload.spec.ts @@ -1,888 +1,111 @@ +import { readFileSync } from "node:fs"; import { createRequire } from "node:module"; import { dirname, resolve } from "node:path"; -import { pathToFileURL } from "node:url"; -import { CompressedArrayTexture, CompressedTexture, RGBA_BPTC_Format } from "three"; -import Renderer from "three/src/renderers/common/Renderer.js"; -// @ts-expect-error Three does not declare its internal texture manager. -import Textures from "three/src/renderers/common/Textures.js"; -// @ts-expect-error Three does not declare its internal texture uploader. -import WebGPUTextureUtils from "three/src/renderers/webgpu/utils/WebGPUTextureUtils.js"; -import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; - -afterEach(() => vi.restoreAllMocks()); - -describe.each(["source", "webgpu bundle", "nodes bundle"])( - "%s compressed upload lifecycle", - (mode) => { - let TextureManager = Textures; - let TextureUtils = WebGPUTextureUtils; - let prepareTexture = Reflect.get(Renderer.prototype, "prepareTextureAsync") as ( - texture: CompressedTexture, - options: { signal?: AbortSignal }, - ) => Promise; - beforeAll(async () => { - if (mode === "source") return; - const root = - process.env.THREE_UPLOAD_TEST_ROOT ?? - resolve(dirname(createRequire(import.meta.url).resolve("three")), ".."); - const url = pathToFileURL( - resolve( - root, - "build", - mode === "webgpu bundle" ? "three.webgpu.js" : "three.webgpu.nodes.js", - ), - ).href; - const { WebGPURenderer } = await import(/* @vite-ignore */ url); - const renderer = new WebGPURenderer({ canvas: { width: 1, height: 1 } }); - renderer.backend.init = async () => {}; - vi.stubGlobal("self", { requestAnimationFrame: () => 0, cancelAnimationFrame: () => {} }); - try { - await renderer.init(); - TextureManager = renderer._textures.constructor; - TextureUtils = renderer.backend.textureUtils.constructor; - prepareTexture = renderer.prepareTextureAsync; - renderer._animation.stop(); - } finally { - vi.unstubAllGlobals(); +import { brotliDecompressSync } from "node:zlib"; +import { + CompressedTexture, + ExternalTexture, + RGBA_ASTC_5x4_Format, + RGBA_BPTC_Format, + type Texture, +} from "three"; +import { afterEach, expect, it, vi } from "vitest"; +import { createRenderer } from "../src/renderer.js"; + +afterEach(() => vi.unstubAllGlobals()); + +it.each([ + { format: RGBA_BPTC_Format, width: 1024, blockWidth: 4, gpuFormat: "bc7-rgba-unorm" }, + { format: RGBA_ASTC_5x4_Format, width: 1000, blockWidth: 5, gpuFormat: "astc-5x4-unorm" }, +])( + "prepareTextures uploads $gpuFormat through bounded writes without the private Three lane", + async ({ format, width, blockWidth, gpuFormat }) => { + const height = 1024; + const bytesPerRow = (width / blockWidth) * 16; + const data = Uint8Array.from({ length: bytesPerRow * (height / 4) }, (_, i) => i % 251); + const restored = new Uint8Array(data.length); + const destroy = vi.fn(); + const backing = { destroy }; + const createTexture = vi.fn(() => backing); + const writeTexture = vi.fn((destination, bytes, layout, extent) => { + expect(bytes.byteLength).toBeLessThanOrEqual(65_536); + expect(destination.texture).toBe(backing); + expect(destination.mipLevel).toBe(0); + expect(destination.origin.z).toBe(0); + const rowBytes = (extent.width / blockWidth) * 16; + for (let row = 0; row < extent.height / 4; row++) { + const offset = layout.offset + row * layout.bytesPerRow; + restored.set( + bytes.subarray(offset, offset + rowBytes), + (destination.origin.y / 4 + row) * bytesPerRow + (destination.origin.x / blockWidth) * 16, + ); } }); - - function fixture(format = "bc7-rgba-unorm") { - let clock = 0; - const writes: Array<{ - bytes: number; - mip: number; - origin: { x: number; y: number; z: number }; - extent: { width: number; height: number }; - data: Uint8Array; - stride: number; - gpuTexture: object; - }> = []; - const gpuData = new WeakMap>(); - const get = (texture: object) => { - let data = gpuData.get(texture); - if (!data) { - data = {}; - gpuData.set(texture, data); - } - return data; - }; - const device = { - queue: { - writeTexture: ( - destination: { - mipLevel: number; - origin: { x: number; y: number; z: number }; - texture: object; - }, - data: Uint8Array, - layout: { bytesPerRow: number }, - extent: { width: number; height: number }, - ) => { - clock += 0.75; - writes.push({ - bytes: data.byteLength, - mip: destination.mipLevel, - origin: { ...destination.origin }, - extent: { ...extent }, - data: data.slice(), - stride: layout.bytesPerRow, - gpuTexture: destination.texture, - }); + const canvas = Object.assign(new EventTarget(), { clientWidth: 320, clientHeight: 180 }); + vi.stubGlobal("navigator", { gpu: {} }); + const renderer = await createRenderer({ + canvas: canvas as HTMLCanvasElement, + webgpuFactory: () => ({ + domElement: canvas as HTMLCanvasElement, + render() {}, + setSize() {}, + backend: { + device: { + createTexture, + queue: { writeTexture }, + features: new Set(["texture-compression-bc", "texture-compression-astc"]), }, }, - }; - const has = (texture: object) => gpuData.has(texture); - const utils = new TextureUtils({ device, get, has }); - const create = vi.fn( - ( - texture: object, - options: { width: number; height: number; levels: number; depth: number }, - ) => { - Object.assign(get(texture), { - texture: { destroy: vi.fn() }, - textureDescriptorGPU: { - format, - size: { - width: options.width, - height: options.height, - depthOrArrayLayers: options.depth, - }, - mipLevelCount: options.levels, - }, - }); - }, + }), + }); + const texture = new CompressedTexture([{ data, width, height }], width, height, format); + texture.needsUpdate = true; + const replacements = new Map(); + try { + expect(await renderer.prepareTextures?.([texture], undefined, undefined, replacements)).toBe( + 1, ); - const backend = { - device, - get, - has, - createTexture: create, - createDefaultTexture: (texture: object) => - Object.assign(get(texture), { texture: { destroy: vi.fn() } }), - updateTexture: (texture: object, options: object) => utils.updateTexture(texture, options), - updateTextureAsync: (texture: object, options: object) => - utils.updateTextureAsync(texture, options), - destroyTexture: vi.fn((texture: object) => { - const data = gpuData.get(texture); - if (data) (data.texture as { destroy(): void }).destroy(); - gpuData.delete(texture); - }), - }; - const info = { createTexture: vi.fn(), destroyTexture: vi.fn() }; - const manager = new TextureManager({}, backend, info); - const mipmaps = [2048, 1024, 512].map((size) => ({ - width: size, - height: size, - data: new Uint8Array((size / 4) ** 2 * 16), - })); - const texture = new CompressedTexture(mipmaps, 2048, 2048, RGBA_BPTC_Format); - texture.needsUpdate = true; - texture.onUpdate = vi.fn(); - vi.spyOn(performance, "now").mockImplementation(() => clock); - return { backend, clock: () => clock, create, get, info, manager, texture, utils, writes }; - } - - it("reproduces whole compressed mip submissions through the real common texture lifecycle", () => { - const f = fixture(); - f.manager.updateTexture(f.texture); - expect(f.writes.map((write) => write.bytes)).toEqual([4_194_304, 1_048_576, 262_144]); - expect(f.manager.get(f.texture).version).toBe(f.texture.version); - expect(f.get(f.texture).version).toBe(f.texture.version); - expect(f.texture.onUpdate).toHaveBeenCalledTimes(1); - }); - - async function firstBatch() { - await Promise.resolve(); - await Promise.resolve(); - } - - function delayedScopes(f: ReturnType) { - const stack: string[] = []; - const pops: Array<{ - resolve(error: null | { message: string }): void; - reject(error: Error): void; - }> = []; - Object.assign(f.backend.device, { - pushErrorScope: (filter: string) => stack.push(filter), - popErrorScope: () => { - expect(stack.pop()).toBe(pops.length % 2 === 0 ? "validation" : "out-of-memory"); - return new Promise((resolve, reject) => { - pops.push({ resolve, reject }); - }); - }, + expect(createTexture).toHaveBeenCalledExactlyOnceWith({ + size: { width, height, depthOrArrayLayers: 1 }, + format: gpuFormat, + mipLevelCount: 1, + dimension: "2d", + usage: 6, }); - return { stack, pops }; + const prepared = replacements.get(texture) as ExternalTexture; + expect(prepared).toBeInstanceOf(ExternalTexture); + expect(prepared.sourceTexture).toBe(backing); + expect(writeTexture.mock.calls.length).toBeGreaterThan(1); + expect(restored).toEqual(data); + } finally { + renderer.dispose(); } - - function prepare(f: ReturnType, texture = f.texture, options = {}) { - return prepareTexture.call({ _initialized: true, _textures: f.manager }, texture, options); - } - - it("pipelines at most four bounded batches through Renderer and awaits all validation before completion", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - f.texture.mipmaps = [{ width: 1024, height: 1024, data: new Uint8Array(1_048_576) }]; - const scopes = delayedScopes(f); - let complete = false; - const pending = prepare(f).then(() => { - complete = true; - }); - await firstBatch(); - await vi.runAllTimersAsync(); - expect(f.writes).toHaveLength(12); - expect(scopes.pops).toHaveLength(8); - expect(scopes.stack).toEqual([]); - expect(complete).toBe(false); - expect(f.writes.every((write) => write.bytes <= 65_536)).toBe(true); - // The oldest batch releases one slot; later results cannot bypass the cap. - scopes.pops[0]?.resolve(null); - scopes.pops[1]?.resolve(null); - await vi.runAllTimersAsync(); - expect(f.writes).toHaveLength(15); - expect(scopes.pops).toHaveLength(10); - scopes.pops[2]?.resolve(null); - scopes.pops[3]?.resolve(null); - await vi.runAllTimersAsync(); - expect(f.writes).toHaveLength(16); - expect(scopes.pops).toHaveLength(12); - for (const pop of scopes.pops.slice(5)) pop.resolve(null); - await vi.runAllTimersAsync(); - expect(complete).toBe(false); - expect(f.get(f.texture).version).not.toBe(f.texture.version); - expect(f.manager.get(f.texture).version).not.toBe(f.texture.version); - expect(f.texture.onUpdate).not.toHaveBeenCalled(); - scopes.pops[4]?.resolve(null); - await pending; - expect(f.get(f.texture).version).toBe(f.texture.version); - expect(f.manager.get(f.texture).version).toBe(f.texture.version); - expect(f.texture.onUpdate).toHaveBeenCalledOnce(); - expect(f.writes.reduce((sum, write) => sum + write.bytes, 0)).toBe(1_048_576); - } finally { - vi.useRealTimers(); - } - }); - - it("awaits and collects both scope failures before rejecting public preparation", async () => { - const f = fixture(); - const scopes = delayedScopes(f); - const texture = smallTexture(); - let settled = false; - const pending = prepare(f, texture).catch((error: Error) => { - settled = true; - return error; - }); - await firstBatch(); - scopes.pops[0]?.reject(new Error("validation scope rejected")); - for (let i = 0; i < 30; i++) await Promise.resolve(); - expect(settled).toBe(false); - scopes.pops[1]?.resolve({ message: "out of memory" }); - const error = await pending; - expect(error).toBeInstanceOf(AggregateError); - expect((error as AggregateError).errors.map((failure: Error) => failure.message)).toEqual([ - "validation scope rejected", - "out of memory", - ]); - expect(texture.onUpdate).not.toHaveBeenCalled(); - expect(f.manager.get(texture).version).toBeUndefined(); - expect(f.get(texture).version).toBeUndefined(); - }); - - it("drains every issued batch after a late error and keeps the next texture queued", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const scopes = delayedScopes(f); - let settled = false; - const pending = prepare(f).catch((error: Error) => { - settled = true; - return error; - }); - const queued = smallTexture(); - const later = prepare(f, queued); - await firstBatch(); - await vi.runAllTimersAsync(); - expect(scopes.pops).toHaveLength(8); - const writes = f.writes.length; - scopes.pops[6]?.resolve({ message: "late validation error" }); - scopes.pops[7]?.reject(new Error("late out of memory")); - for (const pop of scopes.pops.slice(0, 5)) pop.resolve(null); - await vi.runAllTimersAsync(); - expect(settled).toBe(false); - expect(f.create).toHaveBeenCalledTimes(1); - expect(f.writes).toHaveLength(writes); - scopes.pops[5]?.resolve(null); - const error = await pending; - expect(error).toBeInstanceOf(AggregateError); - expect((error as AggregateError).errors).toHaveLength(2); - expect(f.texture.onUpdate).not.toHaveBeenCalled(); - expect(f.get(f.texture).version).toBeUndefined(); - await vi.runAllTimersAsync(); - expect(f.create).toHaveBeenCalledTimes(2); - for (const pop of scopes.pops.slice(8)) pop.resolve(null); - await vi.runAllTimersAsync(); - await later; - expect(queued.onUpdate).toHaveBeenCalledOnce(); - } finally { - vi.useRealTimers(); - } - }); - - it.each(["cancel", "dispose", "version"])( - "keeps pipelined validation inert after %s", - async (reason) => { - vi.useFakeTimers(); - try { - const f = fixture(); - const scopes = delayedScopes(f); - const controller = new AbortController(); - const pending = prepare(f, f.texture, { signal: controller.signal }); - const rejected = expect(pending).rejects.toThrow(/cancel|disposed|changed|invalidated/i); - rejected.catch(() => {}); - await firstBatch(); - await vi.runAllTimersAsync(); - expect(scopes.pops).toHaveLength(8); - const writes = f.writes.length; - if (reason === "cancel") controller.abort(new Error("cancelled")); - if (reason === "dispose") f.texture.dispose(); - if (reason === "version") f.texture.needsUpdate = true; - for (const pop of scopes.pops) pop.resolve(null); - await vi.runAllTimersAsync(); - await rejected; - expect(f.writes).toHaveLength(writes); - expect(f.texture.onUpdate).not.toHaveBeenCalled(); - if (f.backend.has(f.texture)) expect(f.get(f.texture).version).toBeUndefined(); - expect(scopes.stack).toEqual([]); - } finally { - vi.useRealTimers(); - } - }, - ); - - it("keeps the backend lane until cancelled validation drains while rejecting the caller promptly", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const scopes = delayedScopes(f); - const controller = new AbortController(); - const first = prepare(f, f.texture, { signal: controller.signal }); - const rejected = expect(first).rejects.toThrow("caller cancelled"); - const queued = smallTexture(); - const later = prepare(f, queued); - await firstBatch(); - await vi.runAllTimersAsync(); - expect(scopes.pops).toHaveLength(8); - controller.abort(new Error("caller cancelled")); - await rejected; - await vi.runAllTimersAsync(); - expect(f.create).toHaveBeenCalledTimes(1); - const writes = f.writes.length; - for (const pop of scopes.pops.slice(0, 7)) pop.resolve(null); - await vi.runAllTimersAsync(); - expect(f.create).toHaveBeenCalledTimes(1); - expect(f.writes).toHaveLength(writes); - scopes.pops[7]?.resolve(null); - await vi.runAllTimersAsync(); - expect(f.create).toHaveBeenCalledTimes(2); - expect(f.texture.onUpdate).not.toHaveBeenCalled(); - for (const pop of scopes.pops.slice(8)) pop.resolve(null); - await vi.runAllTimersAsync(); - await later; - expect(queued.onUpdate).toHaveBeenCalledOnce(); - } finally { - vi.useRealTimers(); - } - }); - - it.each([ - ["bc1-rgba-unorm", 4, 4, 8], - ["bc7-rgba-unorm", 4, 4, 16], - ["astc-5x4-unorm", 5, 4, 16], - ])( - "preserves every block, mip tail and layer for %s with bounded views", - async (format, bw, bh, bb) => { - vi.useFakeTimers(); - try { - const f = fixture(format); - const sizes: Array<[number, number]> = [ - [320, 64], - [160, 32], - [80, 16], - [40, 8], - [20, 4], - [10, 2], - [5, 1], - ]; - const mips = sizes.map(([width, height]) => { - const length = Math.ceil(width / bw) * Math.ceil(height / bh) * bb * 3; - const backing = new Uint8Array(length + 11); - const data = backing.subarray(11); - for (let i = 0; i < data.length; i++) data[i] = (i * 37 + width) % 251; - return { width, height, data }; - }); - const texture = new CompressedArrayTexture(mips, 320, 64, 3, RGBA_BPTC_Format); - texture.needsUpdate = true; - const pending = f.manager.updateTextureAsync(texture, { - budgetMs: 2, - maxBytesPerWrite: 48, - }); - await vi.runAllTimersAsync(); - await pending; - const rebuilt = mips.map((mip) => new Uint8Array(mip.data.length)); - const coverage = mips.map((mip) => new Uint8Array(mip.data.length)); - for (const write of f.writes) { - expect(write.bytes).toBeLessThanOrEqual(48); - expect(write.origin.x % bw).toBe(0); - expect(write.origin.y % bh).toBe(0); - expect(write.extent.width % bw).toBe(0); - expect(write.extent.height % bh).toBe(0); - const mip = mips[write.mip]; - if (!mip) throw new Error("Unexpected mip index"); - const stride = Math.ceil(mip.width / bw) * bb; - const rows = Math.ceil(mip.height / bh); - const widthBytes = (write.extent.width / bw) * bb; - for (let row = 0; row < write.extent.height / bh; row++) { - const offset = - write.origin.z * stride * rows + - (write.origin.y / bh + row) * stride + - (write.origin.x / bw) * bb; - rebuilt[write.mip]?.set( - write.data.subarray(row * write.stride, row * write.stride + widthBytes), - offset, - ); - for (let i = offset; i < offset + widthBytes; i++) { - const covered = coverage[write.mip]; - if (!covered) throw new Error("Unexpected coverage mip"); - covered[i] = (covered[i] ?? 0) + 1; - } - } - } - expect(rebuilt).toEqual(mips.map((mip) => mip.data)); - expect(coverage.every((mip) => mip.every((count) => count === 1))).toBe(true); - expect(new Set(f.writes.map((write) => write.gpuTexture)).size).toBe(1); - } finally { - vi.useRealTimers(); - } - }, - ); - - it("deduplicates one Texture while one caller aborts and another retains the gate", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const controller = new AbortController(); - const first = f.manager.updateTextureAsync(f.texture, { signal: controller.signal }); - const second = f.manager.updateTextureAsync(f.texture); - const rejected = expect(first).rejects.toThrow("caller cancelled"); - await firstBatch(); - controller.abort(new Error("caller cancelled")); - await rejected; - await vi.runAllTimersAsync(); - await second; - expect(f.create).toHaveBeenCalledTimes(1); - expect(f.texture.onUpdate).toHaveBeenCalledTimes(1); - expect(f.writes.reduce((sum, write) => sum + write.bytes, 0)).toBe(5_505_024); - } finally { - vi.useRealTimers(); - } - }); - - it("serializes distinct Texture clones sharing a Source and keeps distinct GPU identities", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const clone = f.texture.clone(); - expect(clone.source).toBe(f.texture.source); - const first = f.manager.updateTextureAsync(f.texture); - const second = f.manager.updateTextureAsync(clone); - await firstBatch(); - expect(f.create).toHaveBeenCalledTimes(1); - await vi.runAllTimersAsync(); - await Promise.all([first, second]); - expect(f.create).toHaveBeenCalledTimes(2); - expect(new Set(f.writes.map((write) => write.gpuTexture)).size).toBe(2); - } finally { - vi.useRealTimers(); - } - }); - - it.each(["last caller", "texture dispose", "manager dispose", "device loss"])( - "rejects %s promptly without late writes or completion", - async (reason) => { - vi.useFakeTimers(); - try { - const f = fixture(); - let lose = () => {}; - Object.assign(f.backend.device, { - lost: new Promise((resolve) => { - lose = resolve; - }), - }); - const controller = new AbortController(); - const pending = f.manager.updateTextureAsync(f.texture, { signal: controller.signal }); - const rejected = expect(pending).rejects.toThrow(); - await firstBatch(); - const count = f.writes.length; - if (reason === "last caller") controller.abort(new Error("last caller")); - if (reason === "texture dispose") f.texture.dispose(); - if (reason === "manager dispose") f.manager.dispose(); - if (reason === "device loss") lose(); - await rejected; - await vi.runAllTimersAsync(); - expect(f.writes).toHaveLength(count); - expect(f.texture.onUpdate).not.toHaveBeenCalled(); - if (reason.includes("dispose")) expect(f.manager.has(f.texture)).toBe(false); - else expect(f.manager.get(f.texture).version).not.toBe(f.texture.version); - } finally { - vi.useRealTimers(); - } - }, - ); - - it("rejects a version change and never commits the new version from the old upload", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const pending = f.manager.updateTextureAsync(f.texture); - const rejected = expect(pending).rejects.toThrow(/changed|invalidated/i); - await firstBatch(); - f.texture.needsUpdate = true; - await vi.runAllTimersAsync(); - await rejected; - expect(f.manager.get(f.texture).version).not.toBe(f.texture.version); - expect(f.get(f.texture).version).not.toBe(f.texture.version); - expect(f.texture.onUpdate).not.toHaveBeenCalled(); - } finally { - vi.useRealTimers(); - } - }); - - it("does not overwrite onUpdate's next version or accept a warm in-place upload", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const version = f.texture.version; - f.texture.onUpdate = vi.fn(() => { - f.texture.needsUpdate = true; - }); - const pending = f.manager.updateTextureAsync(f.texture); - await vi.runAllTimersAsync(); - await pending; - expect(f.manager.get(f.texture).version).toBe(version); - expect(f.get(f.texture).version).toBe(version); - expect(f.texture.version).toBe(version + 1); - const count = f.writes.length; - await expect(f.manager.updateTextureAsync(f.texture)).rejects.toThrow(/cold/i); - expect(f.writes).toHaveLength(count); - } finally { - vi.useRealTimers(); - } - }); - - it("pops validation scopes before yielding and rejects queued validation errors", async () => { - const f = fixture(); - const scopes: string[] = []; - const pop = vi.fn(() => { - scopes.pop(); - return Promise.resolve({ message: "invalid compressed write" }); - }); - Object.assign(f.backend.device, { - pushErrorScope: (scope: string) => scopes.push(scope), - popErrorScope: pop, - }); - await expect(f.manager.updateTextureAsync(f.texture)).rejects.toThrow( - "invalid compressed write", - ); - expect(scopes).toEqual([]); - expect(pop).toHaveBeenCalledTimes(2); - expect(f.texture.onUpdate).not.toHaveBeenCalled(); - expect(f.manager.get(f.texture).version).not.toBe(f.texture.version); - }); - - it("advances delayed scope results and host yields without a world submission or overlay scope capture", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const scopes: string[] = []; - const delayed: Array<(error: null) => void> = []; - let pops = 0; - let overlayElapsed = 0; - const submit = vi.fn(); - const copy = vi.fn(() => { - expect(scopes).toEqual([]); - overlayElapsed += 40; - }); - Object.assign(f.backend.device.queue, { submit, copyExternalImageToTexture: copy }); - Object.assign(f.backend.device, { - pushErrorScope: (filter: string) => scopes.push(filter), - popErrorScope: () => { - expect(scopes.pop()).toBe(pops % 2 === 0 ? "validation" : "out-of-memory"); - return pops++ < 2 - ? new Promise((resolve) => delayed.push(resolve)) - : Promise.resolve(null); - }, - }); - vi.spyOn(performance, "now").mockImplementation(() => f.clock() + overlayElapsed); - const queued = smallTexture(); - let completed = false; - const pending = f.manager.updateTextureAsync(f.texture).then(() => { - completed = true; - }); - const later = f.manager.updateTextureAsync(queued); - await firstBatch(); - expect(f.writes).toHaveLength(3); - expect(scopes).toEqual([]); - expect(delayed).toHaveLength(2); - expect(f.create).toHaveBeenCalledTimes(1); - f.utils._copyImageToTexture( - { width: 1000, height: 48 }, - {}, - { size: { width: 1000, height: 48 } }, - 0, - false, - false, - ); - await vi.runAllTimersAsync(); - expect(completed).toBe(false); - expect(f.writes).toHaveLength(12); - for (const resolve of delayed) resolve(null); - for (let i = 0; i < 20; i++) await Promise.resolve(); - // The actual next boundary is the host yield; no render/submit callback releases it. - expect(completed).toBe(false); - expect(f.create).toHaveBeenCalledTimes(1); - await vi.runAllTimersAsync(); - await Promise.all([pending, later]); - expect(f.writes.reduce((sum, write) => sum + write.bytes, 0)).toBe(5_505_040); - expect(f.manager.get(f.texture).version).toBe(f.texture.version); - expect(f.manager.get(queued).version).toBe(queued.version); - expect(f.texture.onUpdate).toHaveBeenCalledOnce(); - expect(queued.onUpdate).toHaveBeenCalledOnce(); - expect(copy).toHaveBeenCalledOnce(); - expect(submit).not.toHaveBeenCalled(); - expect(scopes).toEqual([]); - } finally { - vi.useRealTimers(); - } - }); - - it("rejects replacement of a mip without committing captured data as current", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const pending = f.manager.updateTextureAsync(f.texture); - const rejected = expect(pending).rejects.toThrow(/changed/i); - await firstBatch(); - f.texture.mipmaps[0] = { width: 2048, height: 2048, data: new Uint8Array(4_194_304) }; - await vi.runAllTimersAsync(); - await rejected; - expect(f.texture.onUpdate).not.toHaveBeenCalled(); - } finally { - vi.useRealTimers(); - } - }); - - it("rejects new preparation after device loss before any additional write", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - let lose = () => {}; - Object.assign(f.backend.device, { - lost: new Promise((resolve) => { - lose = resolve; - }), - }); - const first = f.manager.updateTextureAsync(f.texture); - const rejected = expect(first).rejects.toThrow(/lost/i); - await firstBatch(); - lose(); - await rejected; - const count = f.writes.length; - await expect(f.manager.updateTextureAsync(f.texture.clone())).rejects.toThrow(/lost/i); - await vi.runAllTimersAsync(); - expect(f.writes).toHaveLength(count); - } finally { - vi.useRealTimers(); - } - }); - - it("retains a synchronous write failure while observing rejected scope-pop promises", async () => { - const f = fixture(); - Object.assign(f.backend.device, { - pushErrorScope: vi.fn(), - popErrorScope: vi.fn(() => Promise.reject(new Error("scope pop failed"))), - }); - vi.spyOn(f.backend.device.queue, "writeTexture").mockImplementation(() => { - throw new Error("original write failed"); - }); - await expect(f.manager.updateTextureAsync(f.texture)).rejects.toThrow( - "original write failed", - ); - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(f.texture.onUpdate).not.toHaveBeenCalled(); - }); - - it("keeps ownership established and completion unpublished at the first bounded host yield", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const pending = f.manager.updateTextureAsync(f.texture, { - budgetMs: 2, - maxBytesPerWrite: 65_536, - }); - await Promise.resolve(); - await Promise.resolve(); - expect(f.create).toHaveBeenCalledTimes(1); - expect(f.info.createTexture).toHaveBeenCalledTimes(1); - expect(f.manager.get(f.texture).initialized).toBe(true); - expect(f.manager.get(f.texture).version).not.toBe(f.texture.version); - expect(f.get(f.texture).version).not.toBe(f.texture.version); - expect(f.texture.onUpdate).not.toHaveBeenCalled(); - expect(f.writes.length).toBeGreaterThan(0); - expect(f.writes.every((write) => write.bytes <= 65_536)).toBe(true); - expect(f.clock()).toBeLessThanOrEqual(2.75); - const writes = f.writes.length; - expect(() => f.manager.updateTexture(f.texture)).toThrow(/pending/i); - expect(f.writes).toHaveLength(writes); - await vi.runAllTimersAsync(); - await pending; - expect(f.manager.get(f.texture).version).toBe(f.texture.version); - expect(f.get(f.texture).version).toBe(f.texture.version); - expect(f.texture.onUpdate).toHaveBeenCalledTimes(1); - expect(f.create).toHaveBeenCalledTimes(1); - expect(f.writes.reduce((sum, write) => sum + write.bytes, 0)).toBe(5_505_024); - } finally { - vi.useRealTimers(); - } - }); - - function smallTexture() { - const texture = new CompressedTexture( - [{ width: 4, height: 4, data: new Uint8Array(16) }], - 4, - 4, - RGBA_BPTC_Format, - ); - texture.needsUpdate = true; - texture.onUpdate = vi.fn(); - return texture; - } - - it("rejects queued readiness changes before allocation or completion", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const first = f.manager.updateTextureAsync(f.texture); - const queued = smallTexture(); - const pending = f.manager.updateTextureAsync(queued); - const rejected = expect(pending).rejects.toThrow(/ready|changed|invalidated/i); - rejected.catch(() => {}); - queued.source.dataReady = false; - await vi.runAllTimersAsync(); - await first; - await rejected; - expect(f.create).toHaveBeenCalledTimes(1); - expect(queued.onUpdate).not.toHaveBeenCalled(); - expect(f.manager.get(queued).version).toBeUndefined(); - } finally { - vi.useRealTimers(); - } - }); - - it("retires pending allocations and listeners exactly once on manager disposal", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const pending = f.manager.updateTextureAsync(f.texture); - const rejected = expect(pending).rejects.toThrow(/disposed/i); - await firstBatch(); - const gpu = f.get(f.texture).texture as { destroy: ReturnType }; - const remove = vi.spyOn(f.texture, "removeEventListener"); - f.manager.dispose(); - await rejected; - f.texture.dispose(); - await vi.runAllTimersAsync(); - expect(gpu.destroy).toHaveBeenCalledTimes(1); - expect(f.backend.destroyTexture).toHaveBeenCalledTimes(1); - expect(f.info.destroyTexture).toHaveBeenCalledTimes(1); - expect(remove).toHaveBeenCalledWith("dispose", expect.any(Function)); - expect(f.backend.has(f.texture)).toBe(false); - expect(f.manager.has(f.texture)).toBe(false); - } finally { - vi.useRealTimers(); - } - }); - - it("rejects backend deletion during final validation without recreating GPU data", async () => { - const f = fixture(); - const texture = smallTexture(); - const pops: Array<(error: null) => void> = []; - Object.assign(f.backend.device, { - pushErrorScope: vi.fn(), - popErrorScope: vi.fn(() => new Promise((resolve) => pops.push(resolve))), - }); - const pending = f.manager.updateTextureAsync(texture); - const rejected = expect(pending).rejects.toThrow(/invalidated/i); - rejected.catch(() => {}); - await firstBatch(); - expect(pops).toHaveLength(2); - f.backend.destroyTexture(texture); - for (const resolve of pops) resolve(null); - await rejected; - expect(f.backend.has(texture)).toBe(false); - expect(f.manager.get(texture).version).toBeUndefined(); - expect(texture.onUpdate).not.toHaveBeenCalled(); - }); - - it("bounds the aggregate lane across cheap textures using measured host yields", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const timer = vi.spyOn(globalThis, "setTimeout"); - const textures = Array.from({ length: 20 }, smallTexture); - const pending = textures.map((texture) => - f.manager.updateTextureAsync(texture, { budgetMs: 2, maxBytesPerWrite: 65_536 }), - ); - for (let i = 0; i < 300; i++) await Promise.resolve(); - expect(f.writes).toHaveLength(3); - expect(f.clock()).toBeLessThanOrEqual(2.75); - expect(timer).toHaveBeenCalledTimes(1); - await vi.runAllTimersAsync(); - await Promise.all(pending); - expect(f.writes).toHaveLength(20); - expect(timer).toHaveBeenCalledTimes(6); - expect( - textures.every((texture) => f.manager.get(texture).version === texture.version), - ).toBe(true); - } finally { - vi.restoreAllMocks(); - vi.useRealTimers(); - } - }); - - it("cancels disposal while queued before allocation and removes its request listener", async () => { - vi.useFakeTimers(); - try { - const f = fixture(); - const first = f.manager.updateTextureAsync(f.texture); - const queued = smallTexture(); - const remove = vi.spyOn(queued, "removeEventListener"); - const pending = f.manager.updateTextureAsync(queued); - const rejected = expect(pending).rejects.toThrow(/disposed/i); - rejected.catch(() => {}); - queued.dispose(); - await vi.runAllTimersAsync(); - await first; - await rejected; - expect(f.create).toHaveBeenCalledTimes(1); - expect(f.backend.has(queued)).toBe(false); - expect(queued.onUpdate).not.toHaveBeenCalled(); - expect(remove).toHaveBeenCalledWith("dispose", expect.any(Function)); - } finally { - vi.useRealTimers(); - } - }); - it("rejects synchronous initialization that skipped unready content", async () => { - const f = fixture(); - f.texture.source.dataReady = false; - f.manager.updateTexture(f.texture); - expect(f.writes).toHaveLength(0); - expect(f.manager.get(f.texture).version).toBe(f.texture.version); - await expect(f.manager.updateTextureAsync(f.texture)).rejects.toThrow(/ready|content/i); - expect(f.writes).toHaveLength(0); - }); - - it("rejects a current default placeholder as prepared compressed content", async () => { - const f = fixture(); - const texture = new CompressedTexture( - [{ width: 4, height: 4, data: new Uint8Array(16) }], - 4, - 4, - RGBA_BPTC_Format, - ); - f.manager.updateTexture(texture); - expect(f.manager.get(texture).isDefaultTexture).toBe(true); - expect(f.writes).toHaveLength(0); - await expect(f.manager.updateTextureAsync(texture)).rejects.toThrow(/ready|content/i); - }); - - it("accepts genuinely uploaded current content without allocation or writes", async () => { - const f = fixture(); - f.manager.updateTexture(f.texture); - const writes = f.writes.length; - await f.manager.updateTextureAsync(f.texture); - expect(f.writes).toHaveLength(writes); - expect(f.create).toHaveBeenCalledTimes(1); - expect(f.texture.onUpdate).toHaveBeenCalledTimes(1); - }); - - it("does not accept restored readiness as proof of a skipped upload", async () => { - const f = fixture(); - f.texture.source.dataReady = false; - f.manager.updateTexture(f.texture); - f.texture.source.dataReady = true; - await expect(f.manager.updateTextureAsync(f.texture)).rejects.toThrow(/content/i); - expect(f.writes).toHaveLength(0); - }); + expect(destroy).toHaveBeenCalledTimes(1); }, ); + +// renderer-texture-preparation.spec.ts proves prepareTextures with real transcoded mip data. +// This guard pins that proof to an installed Three.js and generated patches without the old lane. +it("keeps the private compressed upload lane out of generated patches and installed Three.js", () => { + const patchRoot = new URL("../patches/", import.meta.url); + const base = readFileSync(new URL("three@0.185.1.patch", patchRoot), "utf8"); + const upgrade = readFileSync(new URL("three@0.185.1-prd269-upgrade.patch", patchRoot), "utf8"); + const targetTexts = brotliDecompressSync( + Buffer.from(JSON.parse(upgrade).bundleBrotliBase64, "base64"), + ).toString("utf8"); + const root = resolve(dirname(createRequire(import.meta.url).resolve("three")), ".."); + const installed = [ + "src/renderers/common/Renderer.js", + "src/renderers/common/Textures.js", + "src/renderers/webgpu/WebGPUBackend.js", + "src/renderers/webgpu/utils/WebGPUTextureUtils.js", + "build/three.webgpu.js", + "build/three.webgpu.nodes.js", + ].map((file) => readFileSync(resolve(root, file), "utf8")); + for (const text of [base, upgrade, targetTexts, ...installed]) + expect( + text.match( + /prepareTextureAsync|updateTextureAsync|_uploadTail|_pendingUploads|uploadError|_waitForUpload|_uploadSlice|_uploadDisposed/, + )?.[0], + ).toBeUndefined(); +}); diff --git a/packages/core/__tests__/three-patch-compat.spec.ts b/packages/core/__tests__/three-patch-compat.spec.ts new file mode 100644 index 0000000000..1c5e4e09cc --- /dev/null +++ b/packages/core/__tests__/three-patch-compat.spec.ts @@ -0,0 +1,340 @@ +import { execFile } from "node:child_process"; +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { brotliCompressSync, brotliDecompressSync } from "node:zlib"; +import { afterEach, describe, expect, it } from "vitest"; +import { makeTempDir } from "../../../test-support/temp-dir.js"; + +// @ts-expect-error -- the postinstall helper is plain ESM with no type declarations. +import { applyThreePatch } from "../scripts/apply-three-patch.mjs"; + +const testDir = dirname(fileURLToPath(import.meta.url)); +const patchDir = join(resolve("packages/core"), "patches"); + +const UPGRADE_TEXT = readFileSync(join(patchDir, "three@0.185.1-prd269-upgrade.patch"), "utf8"); +const BASE_PATCH = readFileSync(join(patchDir, "three@0.185.1.patch"), "utf8"); +const UPGRADE = JSON.parse(UPGRADE_TEXT) as { + acceptedLegacyCompatPairs: { path: string; oldHash: string; newHash: string }[]; + acceptedLegacyFinalHashes: Record; + bundleBrotliBase64: string; + generatedFinalHashes: Record; + version: number; +}; + +// Frozen, test-only copy of the legacy unified migration and the exact bytes the previous release +// shipped. It is the only stable reference for the predecessor states a consumer can hold. +const FIXTURE = JSON.parse( + brotliDecompressSync( + readFileSync(join(testDir, "fixtures", "legacy-three-patch.json.br")), + ).toString("utf8"), +) as { + originalBasePatch: string; + originalUpgradePatch: string; + targetTexts: Record; +}; + +const GEOMETRY_CACHE_PREDECESSORS = JSON.parse( + brotliDecompressSync( + readFileSync(join(testDir, "fixtures", "geometry-cache-predecessors.json.br")), + ).toString("utf8"), +) as Record; + +const run = promisify(execFile); +const roots: string[] = []; + +afterEach(async () => { + for (const root of roots.splice(0)) await rm(root, { force: true, recursive: true }); +}); + +async function tempRoot(): Promise { + const root = await makeTempDir("threenative-three-compat-"); + roots.push(root); + return root; +} + +function blobHash(contents: string): string { + return createHash("sha1") + .update(`blob ${Buffer.byteLength(contents)}\0`) + .update(contents) + .digest("hex"); +} + +/** A full, registered Three.js tree at the exact bytes the previous release shipped. */ +async function installedTree(): Promise<{ + packageRoot: string; + threeRoot: string; +}> { + const root = await tempRoot(); + const packageRoot = join(root, "core"); + const threeRoot = join(root, "three"); + await mkdir(join(packageRoot, "patches"), { recursive: true }); + await writeFile(join(packageRoot, "patches", "three@0.185.1.patch"), BASE_PATCH, "utf8"); + await writeFile( + join(packageRoot, "patches", "three@0.185.1-prd269-upgrade.patch"), + UPGRADE_TEXT, + "utf8", + ); + await mkdir(threeRoot, { recursive: true }); + await writeFile( + join(threeRoot, "package.json"), + `${JSON.stringify({ name: "three", version: "0.185.1" })}\n`, + "utf8", + ); + for (const [rel, contents] of Object.entries(FIXTURE.targetTexts)) { + await mkdir(dirname(join(threeRoot, rel)), { recursive: true }); + await writeFile(join(threeRoot, rel), contents, "utf8"); + } + return { packageRoot, threeRoot }; +} + +/** Invert the frozen legacy hunks to materialise the exact predecessor bytes for one entry. */ +async function legacyPredecessor(path: string, oldHash: string): Promise { + const recorded = GEOMETRY_CACHE_PREDECESSORS[path]; + if (recorded !== undefined && blobHash(recorded) === oldHash) return recorded; + const root = await tempRoot(); + const file = join(root, path); + await mkdir(dirname(file), { recursive: true }); + const targetText = FIXTURE.targetTexts[path]; + expect(targetText).toBeDefined(); + await writeFile(file, targetText as string, "utf8"); + const block = FIXTURE.originalUpgradePatch.split(/(?=^diff --git )/mu).find((section) => { + const candidate = /^diff --git a\/(\S+) /mu.exec(section)?.[1]; + const hash = /^index ([a-f0-9]{40})\.\./mu.exec(section)?.[1]; + return candidate === path && hash === oldHash; + }); + const patchFile = join(root, "one.patch"); + const baseBlock = FIXTURE.originalBasePatch + .split(/(?=^diff --git )/mu) + .find((section) => section.startsWith(`diff --git a/${path} `)); + expect(block ?? baseBlock, `no frozen hunk for ${path} ${oldHash}`).toBeDefined(); + await writeFile(patchFile, block ?? baseBlock ?? "", "utf8"); + await run("patch", ["--reverse", "--batch", "--fuzz=0", "--silent", "-p1", "-i", patchFile], { + cwd: root, + }); + if (block === undefined) { + const generatedPatch = brotliDecompressSync( + readFileSync(join(testDir, "fixtures", "generated-three-patch.patch.br")), + ).toString("utf8"); + const generatedBlock = generatedPatch + .split(/(?=^diff --git )/mu) + .find((section) => section.startsWith(`diff --git a/${path} `)); + expect(generatedBlock).toBeDefined(); + await writeFile(patchFile, generatedBlock ?? ""); + await run("patch", ["--batch", "--fuzz=0", "--silent", "-p1", "-i", patchFile], { cwd: root }); + } + const contents = await readFile(file, "utf8"); + expect(blobHash(contents), `predecessor ${path} ${oldHash}`).toBe(oldHash); + return contents; +} + +const STOCK_TEXT = "const alpha = 1;\n"; +const TARGET_TEXT = "const alpha = 1; // threenative\n"; +const TARGET_BLOB = blobHash(TARGET_TEXT); +const STOCK_BLOB = blobHash(STOCK_TEXT); +const COMMIT = "2431a09f46f34c560bc8e44b33be0e567723d5b9"; + +const TINY_BASE = `diff --git a/src/thing.js b/src/thing.js +index ${STOCK_BLOB}..${TARGET_BLOB} 100644 +--- a/src/thing.js ++++ b/src/thing.js +@@ -1,1 +1,1 @@ +-const alpha = 1; ++const alpha = 1; // threenative +`; + +function tinyUpgrade(overrides: Record = {}): string { + const json = { + version: 2, + generator: "scripts/generate-three-patch.mjs", + pin: { commit: COMMIT, version: "0.185.1" }, + acceptedLegacyCompatPairs: [ + { path: "src/thing.js", oldHash: blobHash("old\n"), newHash: TARGET_BLOB }, + ], + acceptedLegacyFinalHashes: { "src/thing.js": TARGET_BLOB }, + generatedFinalHashes: { "src/thing.js": TARGET_BLOB }, + bundleBrotliBase64: brotliCompressSync( + Buffer.from(JSON.stringify({ "src/thing.js": TARGET_TEXT })), + ).toString("base64"), + ...overrides, + }; + return `${JSON.stringify(json)}\n`; +} + +async function tinyTree( + upgradeText: string, + baseText = TINY_BASE, +): Promise<{ + packageRoot: string; + threeRoot: string; +}> { + const root = await tempRoot(); + const packageRoot = join(root, "core"); + const threeRoot = join(root, "three"); + await mkdir(join(packageRoot, "patches"), { recursive: true }); + await writeFile(join(packageRoot, "patches", "three@0.185.1.patch"), baseText, "utf8"); + await writeFile( + join(packageRoot, "patches", "three@0.185.1-prd269-upgrade.patch"), + upgradeText, + "utf8", + ); + await mkdir(join(threeRoot, "src"), { recursive: true }); + await writeFile( + join(threeRoot, "package.json"), + `${JSON.stringify({ name: "three", version: "0.185.1" })}\n`, + "utf8", + ); + await writeFile(join(threeRoot, "src", "thing.js"), STOCK_TEXT, "utf8"); + return { packageRoot, threeRoot }; +} + +describe("the versioned JSON upgrade patch", () => { + it("frozen fixture matches the shipped final hashes for all registered targets", () => { + expect(Object.keys(FIXTURE.targetTexts).sort()).toEqual( + Object.keys(UPGRADE.acceptedLegacyFinalHashes).sort(), + ); + for (const [rel, hash] of Object.entries(UPGRADE.acceptedLegacyFinalHashes)) { + expect(blobHash(FIXTURE.targetTexts[rel] ?? ""), rel).toBe(hash); + } + }); + + it("recognises every registered compat predecessor and lands on the generated target", async () => { + const { packageRoot, threeRoot } = await installedTree(); + for (const pair of UPGRADE.acceptedLegacyCompatPairs) { + const predecessor = await legacyPredecessor(pair.path, pair.oldHash); + await writeFile(join(threeRoot, pair.path), predecessor, "utf8"); + await expect(applyThreePatch({ packageRoot, threeRoot }), pair.oldHash).resolves.toBe( + pair.oldHash === UPGRADE.generatedFinalHashes[pair.path] ? "unchanged" : "patched", + ); + expect( + blobHash(await readFile(join(threeRoot, pair.path), "utf8")), + `${pair.path} ${pair.oldHash}`, + ).toBe(UPGRADE.generatedFinalHashes[pair.path]); + await expect(applyThreePatch({ packageRoot, threeRoot })).resolves.toBe("unchanged"); + } + }); + + it("migrates all 30 previously shipped finals to the generated target", async () => { + const { packageRoot, threeRoot } = await installedTree(); + await expect(applyThreePatch({ packageRoot, threeRoot })).resolves.toBe("patched"); + for (const rel of Object.keys(UPGRADE.generatedFinalHashes)) { + expect(blobHash(await readFile(join(threeRoot, rel), "utf8")), rel).toBe( + UPGRADE.generatedFinalHashes[rel], + ); + } + await expect(applyThreePatch({ packageRoot, threeRoot })).resolves.toBe("unchanged"); + }); + + it("refuses one custom byte and writes no recognised file", async () => { + const { packageRoot, threeRoot } = await installedTree(); + const edited = join(threeRoot, "src/renderers/common/Renderer.js"); + const untouched = join(threeRoot, "build/three.webgpu.js"); + await writeFile(edited, `${await readFile(edited, "utf8")} `, "utf8"); + const before = await readFile(untouched, "utf8"); + await expect(applyThreePatch({ packageRoot, threeRoot })).rejects.toThrow( + /TN_THREE_PATCH_PARTIAL/, + ); + expect(await readFile(edited, "utf8")).toBe( + `${FIXTURE.targetTexts["src/renderers/common/Renderer.js"]} `, + ); + expect(await readFile(untouched, "utf8")).toBe(before); + }); + + it("rejects an unsupported version, schema drift, and a duplicate compat pair", async () => { + const pair = { + path: "src/thing.js", + oldHash: blobHash("old\n"), + newHash: TARGET_BLOB, + }; + const cases: [string, Record, RegExp][] = [ + ["version", { version: 1 }, /unsupported upgrade version/u], + ["schema", { acceptedLegacyCompatPairs: "nope" }, /non-empty array/u], + ["pin", { pin: {} }, /pin must name/u], + ["duplicate", { acceptedLegacyCompatPairs: [pair, { ...pair }] }, /duplicate compat pair/u], + ["unsafe", { acceptedLegacyFinalHashes: { "../evil.js": TARGET_BLOB } }, /unsafe/u], + [ + "compat-final", + { + acceptedLegacyCompatPairs: [ + { path: "src/thing.js", oldHash: pair.oldHash, newHash: "d".repeat(40) }, + ], + }, + /does not match its accepted final hash/u, + ], + ]; + for (const [name, override, expected] of cases) { + const { packageRoot, threeRoot } = await tinyTree(tinyUpgrade(override)); + await expect(applyThreePatch({ packageRoot, threeRoot }), name).rejects.toThrow(expected); + } + }); + + it("rejects mismatched payloads, bad base64, a decompression bomb, and a missing target", async () => { + const cases: [string, Record, RegExp][] = [ + [ + "mismatched payload hash", + { + bundleBrotliBase64: brotliCompressSync( + Buffer.from(JSON.stringify({ "src/thing.js": "const alpha = 1; // other\n" })), + ).toString("base64"), + }, + /does not match its generated hash/u, + ], + ["bad base64", { bundleBrotliBase64: "not base64!!" }, /not valid base64/u], + [ + "decompression bomb", + { + bundleBrotliBase64: brotliCompressSync( + Buffer.from(JSON.stringify({ "src/thing.js": "a".repeat(9 * 1024 * 1024) })), + ).toString("base64"), + }, + /bounded Brotli payload/u, + ], + ["missing target", { generatedFinalHashes: { "src/other.js": TARGET_BLOB } }, /base patch/u], + ]; + for (const [name, override, expected] of cases) { + const { packageRoot, threeRoot } = await tinyTree(tinyUpgrade(override)); + await expect(applyThreePatch({ packageRoot, threeRoot }), name).rejects.toThrow(expected); + } + }); + + it("rejects a wrong pin even when the consumer already holds the current target", async () => { + const cases: [string, { commit: string; version: string }][] = [ + ["wrong version", { commit: COMMIT, version: "999.0.0" }], + ["wrong commit", { commit: "0".repeat(40), version: "0.185.1" }], + ]; + for (const [name, pin] of cases) { + const { packageRoot, threeRoot } = await tinyTree(tinyUpgrade({ pin })); + await writeFile(join(threeRoot, "src", "thing.js"), TARGET_TEXT, "utf8"); + await expect(applyThreePatch({ packageRoot, threeRoot }), name).rejects.toThrow( + /pin must name/u, + ); + } + }); + + it("rejects a bundle missing the target for a registered predecessor when already current", async () => { + const { packageRoot, threeRoot } = await tinyTree( + tinyUpgrade({ + bundleBrotliBase64: brotliCompressSync(Buffer.from("{}")).toString("base64"), + }), + ); + await writeFile(join(threeRoot, "src", "thing.js"), TARGET_TEXT, "utf8"); + await expect(applyThreePatch({ packageRoot, threeRoot })).rejects.toThrow( + /missing the target/u, + ); + }); + + it("refuses a stock hunk whose new text does not match the patch index blob", async () => { + const corrupted = TINY_BASE.replace( + "+const alpha = 1; // threenative\n", + "+const alpha = 1; // tampered\n", + ); + const { packageRoot, threeRoot } = await tinyTree(tinyUpgrade(), corrupted); + await expect(applyThreePatch({ packageRoot, threeRoot })).rejects.toThrow( + /does not match the base patch index blob/u, + ); + await expect(readFile(join(threeRoot, "src", "thing.js"), "utf8")).resolves.toBe(STOCK_TEXT); + }); +}); diff --git a/packages/core/__tests__/three-patch-retained.spec.ts b/packages/core/__tests__/three-patch-retained.spec.ts new file mode 100644 index 0000000000..6a238c6a06 --- /dev/null +++ b/packages/core/__tests__/three-patch-retained.spec.ts @@ -0,0 +1,176 @@ +import { execFile } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { dirname, join, resolve } from "node:path"; +import { pathToFileURL } from "node:url"; +import { promisify } from "node:util"; +import { Data3DTexture, DepthTexture, EventDispatcher } from "three"; +import { afterAll, afterEach, beforeAll, expect, it, vi } from "vitest"; +import { makeTempDir } from "../../../test-support/temp-dir.js"; + +const threeRoot = resolve(dirname(createRequire(import.meta.url).resolve("three")), ".."); +let root: string; +let utilsModule: Awaited>; +let texturesModule: Awaited>; + +async function importModule(file: string) { + return import(/* @vite-ignore */ pathToFileURL(join(root, file)).href); +} + +beforeAll(async () => { + root = await makeTempDir("threenative-retained-patch-"); + const files = [ + "src/renderers/common/Textures.js", + "src/renderers/webgpu/utils/WebGPUTextureUtils.js", + ]; + for (const file of files) { + await mkdir(dirname(join(root, file)), { recursive: true }); + await cp(join(threeRoot, file), join(root, file)); + } + await writeFile(join(root, "package.json"), '{"type":"module"}\n'); + if (process.env.TN_THREE_PATCH_STOCK === "1") { + const patch = readFileSync(new URL("../patches/three@0.185.1.patch", import.meta.url), "utf8") + .split(/(?=^diff --git )/mu) + .filter((block) => files.includes(/^diff --git a\/(\S+) /mu.exec(block)?.[1] ?? "")) + .join(""); + const inverse = join(root, "stock.patch"); + await writeFile(inverse, patch); + await promisify(execFile)( + "patch", + ["--reverse", "--batch", "--fuzz=0", "--silent", "-p1", "-i", inverse], + { cwd: root }, + ); + for (const file of files) { + const block = patch + .split(/(?=^diff --git )/mu) + .find((section) => section.startsWith(`diff --git a/${file} `)); + const expected = /^index ([a-f0-9]{40})/mu.exec(block ?? "")?.[1]; + // @ts-expect-error -- the generator is plain ESM with no type declarations. + const { blobHash } = await import("../scripts/generate-three-patch.mjs"); + expect(blobHash(await readFile(join(root, file), "utf8"))).toBe(expected); + } + } + // Keep the two observed files isolated; resolve their unchanged dependencies from the pin. + for (const file of files) { + const source = await readFile(join(root, file), "utf8"); + await writeFile( + join(root, file), + source.replace( + /from (['"])(\.[^'"]+)\1/gu, + (_match, quote: string, relative: string) => + `from ${quote}${pathToFileURL(resolve(dirname(join(threeRoot, file)), relative)).href}${quote}`, + ), + ); + } + utilsModule = await importModule("src/renderers/webgpu/utils/WebGPUTextureUtils.js"); + texturesModule = await importModule("src/renderers/common/Textures.js"); +}); + +afterAll(async () => { + if (root) await rm(root, { recursive: true, force: true }); +}); +afterEach(() => vi.unstubAllGlobals()); + +function backendData() { + const records = new Map>(); + return { + get(key: object) { + let data = records.get(key); + if (!data) { + data = {}; + records.set(key, data); + } + return data; + }, + delete(key: object) { + records.delete(key); + }, + }; +} + +it("retains distinct depth buffers across sample-count switches and destroys them on resize", () => { + const canvas = Object.assign(new EventDispatcher<{ resize: object }>(), { + depthTexture: new DepthTexture(1, 1), + }); + const renderer = { currentSamples: 4, reversedDepthBuffer: false, getCanvasTarget: () => canvas }; + let width = 16; + const backend = { + ...backendData(), + renderer, + getDrawingBufferSize: () => ({ width, height: 16 }), + utils: { getSampleCount: (samples: number) => samples || 1 }, + }; + const utils = new utilsModule.default(backend); + const create = vi.fn((texture: object) => { + backend.get(texture).texture = { destroy: vi.fn() }; + }); + utils.createTexture = create; + const four = utils.getDepthBuffer(true, false); + renderer.currentSamples = 0; + const one = utils.getDepthBuffer(true, false); + renderer.currentSamples = 4; + expect(utils.getDepthBuffer(true, false)).toBe(four); + expect(one).not.toBe(four); + expect(create).toHaveBeenCalledTimes(2); + canvas.dispatchEvent({ type: "resize" }); + expect(four.destroy).toHaveBeenCalledTimes(1); + expect(one.destroy).toHaveBeenCalledTimes(1); + width = 32; + expect(utils.getDepthBuffer(true, false)).not.toBe(four); + utils.dispose(); +}); + +it("omits render-attachment usage for sampled 3D textures while preserving attachment overrides", () => { + vi.stubGlobal("GPUTextureUsage", { + TEXTURE_BINDING: 1, + COPY_DST: 2, + COPY_SRC: 4, + STORAGE_BINDING: 8, + RENDER_ATTACHMENT: 16, + }); + const createTexture = vi.fn((_descriptor: { dimension: string; usage: number }) => ({})); + const backend = { + ...backendData(), + device: { createTexture }, + utils: { getTextureSampleData: () => ({ samples: 1, primarySamples: 1, isMSAA: false }) }, + }; + const utils = new utilsModule.default(backend); + for (const needsMipmaps of [false, true]) { + const texture = new Data3DTexture(new Uint8Array(32), 2, 2, 2); + texture.internalFormat = "rgba8unorm" as unknown as typeof texture.internalFormat; + utils.createTexture(texture, { width: 2, height: 2, depth: 2, needsMipmaps }); + expect(createTexture.mock.calls.at(-1)?.[0]).toMatchObject({ + dimension: "3d", + usage: needsMipmaps ? 23 : 7, + }); + } +}); + +it("invalidates every cached bind group before recreating a destroyed render-target texture", () => { + const groups = [{}, {}]; + const backend = { + ...backendData(), + destroyTexture: vi.fn(), + createTexture: vi.fn(), + createSampler: vi.fn(), + destroySampler: vi.fn(), + }; + const manager = new texturesModule.default({}, backend, { memory: { textures: 0 } }); + const texture = new DepthTexture(16, 16); + texture.needsUpdate = true; + Object.assign(manager.get(texture), { initialized: true, version: 0, bindGroups: groups }); + for (const group of groups) + Object.assign(backend.get(group), { groups: ["stale"], versions: [1], unrelated: "keep" }); + backend.createTexture.mockImplementation(() => { + for (const group of groups) + expect(backend.get(group)).toEqual({ + groups: undefined, + versions: undefined, + unrelated: "keep", + }); + }); + manager.updateTexture(texture); + expect(backend.destroyTexture).toHaveBeenCalledWith(texture); + expect(backend.createTexture).toHaveBeenCalledTimes(1); +}); diff --git a/packages/core/__tests__/three-patch-upgrade.spec.ts b/packages/core/__tests__/three-patch-upgrade.spec.ts index b65f92b059..756e60f6d9 100644 --- a/packages/core/__tests__/three-patch-upgrade.spec.ts +++ b/packages/core/__tests__/three-patch-upgrade.spec.ts @@ -1,14 +1,41 @@ import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; -import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; -import { join, resolve } from "node:path"; +import { readFileSync } from "node:fs"; +import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; +import { brotliDecompressSync } from "node:zlib"; import { afterEach, describe, expect, it } from "vitest"; import { makeTempDir } from "../../../test-support/temp-dir.js"; // @ts-expect-error -- the postinstall helper is plain ESM with no type declarations. import { applyThreePatch } from "../scripts/apply-three-patch.mjs"; +const testDir = dirname(fileURLToPath(import.meta.url)); + +// A frozen, test-only copy of the legacy unified migration and the exact files the previous +// release shipped. Production no longer carries this format, so the fixture is the only stable +// reference for the migration the reader must still recognise. +const FIXTURE = JSON.parse( + brotliDecompressSync( + readFileSync(join(testDir, "fixtures", "legacy-three-patch.json.br")), + ).toString("utf8"), +) as { + originalBasePatch: string; + originalUpgradePatch: string; + targetTexts: Record; +}; + +const GENERATED_FINAL_HASHES = ( + JSON.parse( + readFileSync( + join(resolve("packages/core"), "patches", "three@0.185.1-prd269-upgrade.patch"), + "utf8", + ), + ) as { generatedFinalHashes: Record } +).generatedFinalHashes; + const roots: string[] = []; afterEach(async () => { @@ -210,14 +237,19 @@ async function previousInstalledPackage(priorBlobs: Record, crlf const root = await tempRoot(); const threeRoot = join(root, "three"); const packageRoot = resolve("packages/core"); - await cp(join(packageRoot, "node_modules/three"), threeRoot, { - dereference: true, - recursive: true, - }); - const migration = await readFile( - join(packageRoot, "patches/three@0.185.1-prd269-upgrade.patch"), + await mkdir(threeRoot, { recursive: true }); + await writeFile( + join(threeRoot, "package.json"), + `${JSON.stringify({ name: "three", version: THREE_VERSION })}\n`, "utf8", ); + // Start every registered target at the exact bytes the previous release shipped. + for (const [file, contents] of Object.entries(FIXTURE.targetTexts)) { + await mkdir(dirname(join(threeRoot, file)), { recursive: true }); + await writeFile(join(threeRoot, file), contents, "utf8"); + } + // Invert the frozen legacy hunks to reach the older state this group represents. + const migration = FIXTURE.originalUpgradePatch; const blocks = migration.split(/(?=^diff --git )/mu); const selected: string[] = []; for (const [file, initialHash] of Object.entries(priorBlobs)) { @@ -266,8 +298,8 @@ describe.each([ const { packageRoot, threeRoot } = await previousInstalledPackage(priorBlobs, crlf); await expect(applyThreePatch({ packageRoot, threeRoot })).resolves.toBe("patched"); for (const file of Object.keys(priorBlobs)) { - expect(await readFile(join(threeRoot, file), "utf8")).toBe( - await readFile(join(packageRoot, "node_modules/three", file), "utf8"), + expect(blobHash(await readFile(join(threeRoot, file), "utf8"))).toBe( + GENERATED_FINAL_HASHES[file], ); } await expect(applyThreePatch({ packageRoot, threeRoot })).resolves.toBe("unchanged"); @@ -291,6 +323,53 @@ describe.each([ }); }); +async function stockInstalledPackage() { + const { packageRoot, threeRoot } = await previousInstalledPackage({}); + const inverse = join(await tempRoot(), "stock.patch"); + await writeFile(inverse, FIXTURE.originalBasePatch); + await promisify(execFile)( + "patch", + ["--reverse", "--batch", "--fuzz=0", "--silent", "-p1", "-i", inverse], + { cwd: threeRoot }, + ); + return { packageRoot, threeRoot }; +} + +describe("generated patch installer regressions", () => { + it("upgrades the frozen Phase 1 generated patch to every current target hash", async () => { + const { packageRoot, threeRoot } = await stockInstalledPackage(); + const oldPatch = brotliDecompressSync( + readFileSync(join(testDir, "fixtures", "generated-three-patch.patch.br")), + ).toString("utf8"); + const patchFile = join(await tempRoot(), "old.patch"); + await writeFile(patchFile, oldPatch); + await promisify(execFile)( + "patch", + ["--batch", "--fuzz=0", "--silent", "-p1", "-i", patchFile], + { cwd: threeRoot }, + ); + for (const block of oldPatch.split(/(?=^diff --git )/mu)) { + const rel = /^diff --git a\/(\S+) /mu.exec(block)?.[1]; + const hash = /^index [a-f0-9]{40}\.\.([a-f0-9]{40}) /mu.exec(block)?.[1]; + if (rel !== undefined && hash !== undefined) + expect(blobHash(await readFile(join(threeRoot, rel), "utf8")), rel).toBe(hash); + } + await expect(applyThreePatch({ packageRoot, threeRoot })).resolves.toBe("patched"); + for (const [rel, hash] of Object.entries(GENERATED_FINAL_HASHES)) + expect(blobHash(await readFile(join(threeRoot, rel), "utf8")), rel).toBe(hash); + await expect(applyThreePatch({ packageRoot, threeRoot })).resolves.toBe("unchanged"); + }); + + it("classifies stock Batch.js as stock despite its deletion-only trailing blank hunk", async () => { + const { packageRoot, threeRoot } = await stockInstalledPackage(); + await expect(applyThreePatch({ packageRoot, threeRoot })).resolves.toBe("patched"); + const rel = "src/nodes/accessors/Batch.js"; + expect(blobHash(await readFile(join(threeRoot, rel), "utf8"))).toBe( + GENERATED_FINAL_HASHES[rel], + ); + }); +}); + it("follows exact predecessor hashes and rejects a cyclic migration without writing", async () => { const { packageRoot, threeRoot, target } = await fixture(STOCK); const transition = (before: readonly string[], after: readonly string[]) => diff --git a/packages/core/__tests__/three-shadow-override-cache.spec.ts b/packages/core/__tests__/three-shadow-override-cache.spec.ts index 6b1b932fdb..8e6273dd80 100644 --- a/packages/core/__tests__/three-shadow-override-cache.spec.ts +++ b/packages/core/__tests__/three-shadow-override-cache.spec.ts @@ -1,4 +1,11 @@ -import { BoxGeometry, type BufferGeometry, Camera, Mesh, Scene } from "three"; +import { + BoxGeometry, + type BufferGeometry, + Camera, + Float32BufferAttribute, + Mesh, + Scene, +} from "three"; // @ts-expect-error Three's private renderer module has no public declaration; this test must exercise it directly. import RenderObjects from "three/src/renderers/common/RenderObjects.js"; import { @@ -272,6 +279,46 @@ describe("three shadow override material cache", () => { }); describe("three RenderObjects source invalidation", () => { + it("rebuilds cached morph bindings when geometry is replaced and the old geometry is disposed", () => { + const probe = renderObjectsProbe(); + const source = probe.makeSource(0); + const object = probe.makeObject(source); + const oldGeometry = object.geometry; + oldGeometry.morphAttributes.position = [ + new Float32BufferAttribute( + new Float32Array(oldGeometry.getAttribute("position").count * 3), + 3, + ), + ]; + oldGeometry.morphTargetsRelative = true; + object.updateMorphTargets(); + const cached = probe.get(object, source); + Reflect.set( + Reflect.get(cached, "_nodes"), + "getForRender", + (renderObject: IRenderObjectProbe) => ({ + geometry: renderObject.geometry, + createBindings: () => [{ geometry: renderObject.geometry }], + }), + ); + const oldBuilder = Reflect.get(cached, "getNodeBuilderState").call(cached); + const oldBindings = Reflect.get(cached, "getBindings").call(cached); + const dispose = vi.spyOn(cached, "dispose"); + object.geometry = oldGeometry.clone(); + object.updateMorphTargets(); + oldGeometry.dispose(); + + const refreshed = probe.get(object, source); + const builder = Reflect.get(refreshed, "getNodeBuilderState").call(refreshed); + const bindings = Reflect.get(refreshed, "getBindings").call(refreshed); + expect(refreshed).not.toBe(cached); + expect(dispose).toHaveBeenCalledOnce(); + expect(builder).not.toBe(oldBuilder); + expect(bindings).not.toBe(oldBindings); + expect(builder.geometry).toBe(object.geometry); + expect(bindings[0].geometry).toBe(object.geometry); + }); + it("does not validate an unchanged source and override pair", () => { const probe = renderObjectsProbe(); const source = probe.makeSource(0.5); diff --git a/packages/core/__tests__/warmup.spec.ts b/packages/core/__tests__/warmup.spec.ts index e5bac209b0..d2a2a0d6a3 100644 --- a/packages/core/__tests__/warmup.spec.ts +++ b/packages/core/__tests__/warmup.spec.ts @@ -536,6 +536,33 @@ describe("scene warm-up", () => { expect(report.passes).toEqual(["main", "reflection"]); }); + test("should walk a shared node subgraph once when it looks for a reflector", async () => { + // A diamond chain: every node lists the next one twice, so a walk without a visited set makes + // 2^20 calls. TSL graphs share nodes like this all over a terrain material. + let calls = 0; + const children = (list: unknown[]) => { + calls += 1; + return list; + }; + let tail: unknown = { isNode: true, getChildren: () => children([]) }; + for (let level = 0; level < 20; level += 1) { + const next = tail; + tail = { isNode: true, getChildren: () => children([next, next]) }; + } + const material = { isNodeMaterial: true, colorNode: tail }; + const renderer = { + compileAsync: () => Promise.resolve(), + render: () => undefined, + raw: { shadowMap: { enabled: true } }, + }; + const scene = group("scene", [{ children: [], material, name: "ground" }]); + const report = await warmUpScene(renderer as never, scene as never, {} as never, { + yieldFrame: () => Promise.resolve(), + }); + expect(report.passes).toEqual(["main"]); + expect(calls).toBeLessThanOrEqual(21); + }); + test("should not render passes when renderPasses is false", async () => { const render = vi.fn(); const renderer = { diff --git a/packages/core/__tests__/world-cell-prewarm-gate.spec.ts b/packages/core/__tests__/world-cell-prewarm-gate.spec.ts new file mode 100644 index 0000000000..17489c6bee --- /dev/null +++ b/packages/core/__tests__/world-cell-prewarm-gate.spec.ts @@ -0,0 +1,52 @@ +import { Box3, BoxGeometry, MeshBasicMaterial, Object3D } from "three"; +import { describe, expect, it } from "vitest"; +import type { IAssetState } from "../src/world-cell-models.js"; +import { CellPrewarm, type ICellPrewarmHost } from "../src/world-cell-prewarm.js"; + +function host(built: (key: string) => boolean): ICellPrewarmHost { + const parent = new Object3D(); + return { + released: () => false, + parent: () => parent, + invalidateShadows: () => undefined, + backlog: () => 0, + keysForShadow: () => false, + hasShared: built, + hasRetired: () => false, + runMax: () => 1, + residentCells: () => 1, + extentBounds: () => new Box3(), + smallCaster: () => false, + dressMesh: () => undefined, + receiveShadow: () => false, + adoptGpu: () => undefined, + setShared: () => undefined, + attach: () => undefined, + casts: () => false, + wideOwed: () => false, + followCluster: () => "0_0", + wideShape: (_asset, _level, _part, fallback) => fallback, + }; +} + +const asset = { + id: "boulder", + levels: [[{ geometry: new BoxGeometry(), material: new MeshBasicMaterial() }]], +} as unknown as IAssetState; + +describe("CellPrewarm gate", () => { + it("settles when every key an asset asks for is already built", async () => { + const prewarm = new CellPrewarm(host(() => true)); + prewarm.queuePrewarm(asset); + for (let update = 0; update < 4; update += 1) prewarm.drainPrewarm(); + expect(prewarm.queued).toBe(0); + expect(prewarm.settled).toBe(true); + await expect(prewarm.prewarmed).resolves.toBeUndefined(); + }); + + it("stays open until an asset asks, so an early idle update cannot settle it", () => { + const prewarm = new CellPrewarm(host(() => true)); + for (let update = 0; update < 4; update += 1) prewarm.drainPrewarm(); + expect(prewarm.settled).toBe(false); + }); +}); diff --git a/packages/core/__tests__/world-cells-data.spec.ts b/packages/core/__tests__/world-cells-data.spec.ts index fb0b44463d..068fff2479 100644 --- a/packages/core/__tests__/world-cells-data.spec.ts +++ b/packages/core/__tests__/world-cells-data.spec.ts @@ -694,3 +694,223 @@ it("runs an owed stationary-camera refilter after the last byte-identical public world.dispose(); } }); + +async function spawnWorld( + follow: Vector3, + loadModel: (url: string) => Promise = async () => + new Group().add(new Mesh(new BoxGeometry(1, 2, 1), new MeshBasicMaterial())), +): Promise { + const assets = createAssetLoader(); + vi.spyOn(assets, "resolve").mockResolvedValue([]); + return WorldCells.load({ + url: "world.json", + assets, + surface: new MeshBasicMaterial(), + follow: { position: follow }, + ring: 0, + terrain: false, + data: { manifest, placements }, + budgets: { bytes: 128, instances: 4, residentCells: 2 }, + loadModel, + }); +} +async function untilReady(world: WorldCells, position: { x: number; z: number }): Promise { + for (let frame = 0; frame < 100; frame++) { + world.update(); + await Promise.resolve(); + if (world.readinessAt(position).ready) return; + } + throw new Error("Spawn region did not become ready within 100 updates"); +} + +it("resolves whenSpawnReady immediately when the spawn region is already loaded", async () => { + const world = await spawnWorld(new Vector3(32, 0, 32)); + try { + await untilReady(world, { x: 32, z: 32 }); + await expect(world.whenSpawnReady({ x: 32, z: 32 })).resolves.toMatchObject({ + ready: true, + failures: 0, + loadedCells: 1, + requiredCells: 1, + }); + } finally { + world.dispose(); + } +}); + +it("settles a whenSpawnReady waiter on a later update that admits the spawn region", async () => { + const world = await spawnWorld(new Vector3(32, 0, 32)); + const clearSpy = vi.spyOn(globalThis, "clearTimeout"); + try { + expect(world.readinessAt({ x: 32, z: 32 }).ready).toBe(false); + const ready = world.whenSpawnReady({ x: 32, z: 32 }); + let settled = false; + void ready.then(() => { + settled = true; + }); + await Promise.resolve(); + await Promise.resolve(); + expect(settled).toBe(false); + expect(world.readinessAt({ x: 32, z: 32 }).loadedCells).toBe(0); + for (let frame = 0; frame < 100 && !settled; frame++) { + world.update(); + await Promise.resolve(); + if (settled) expect(world.readinessAt({ x: 32, z: 32 }).ready).toBe(true); + } + expect(settled).toBe(true); + await expect(ready).resolves.toMatchObject({ + ready: true, + failures: 0, + requiredCells: 1, + loadedCells: 1, + }); + expect(clearSpy).toHaveBeenCalled(); + } finally { + clearSpy.mockRestore(); + world.dispose(); + } +}); + +it("rejects a whenSpawnReady waiter when its spawn asset fails to load", async () => { + const world = await spawnWorld(new Vector3(32, 0, 32), async () => { + throw new Error("boom"); + }); + try { + const ready = world.whenSpawnReady({ x: 32, z: 32 }); + let settled = false; + void ready.catch(() => { + settled = true; + }); + for (let frame = 0; frame < 100 && !settled; frame++) { + world.update(); + await Promise.resolve(); + } + await expect(ready).rejects.toThrow("WorldCells spawn assets failed to load"); + expect(world.readinessAt({ x: 32, z: 32 }).failures).toBeGreaterThan(0); + } finally { + world.dispose(); + } +}); + +it("rejects whenSpawnReady on a disposed world without arming a deadline", async () => { + const world = await spawnWorld(new Vector3(32, 0, 32)); + world.dispose(); + vi.useFakeTimers(); + try { + await expect(world.whenSpawnReady({ x: 32, z: 32 })).rejects.toThrow("WorldCells disposed"); + expect(vi.getTimerCount()).toBe(0); + } finally { + vi.useRealTimers(); + } +}); + +it("settles a waiting whenSpawnReady when the world is disposed and clears its deadline", async () => { + const world = await spawnWorld(new Vector3(32, 0, 32)); + vi.useFakeTimers(); + try { + const ready = world.whenSpawnReady({ x: 224, z: 32 }); + const expectation = expect(ready).rejects.toThrow("WorldCells spawn cancelled"); + expect(vi.getTimerCount()).toBe(1); + world.dispose(); + await expectation; + expect(vi.getTimerCount()).toBe(0); + } finally { + vi.useRealTimers(); + world.dispose(); + } +}); + +it("settles every concurrent whenSpawnReady waiter and clears each deadline", async () => { + const world = await spawnWorld(new Vector3(32, 0, 32)); + try { + const first = world.whenSpawnReady({ x: 32, z: 32 }); + const second = world.whenSpawnReady({ x: 32, z: 32 }); + const third = world.whenSpawnReady({ x: 32, z: 32 }); + for (let frame = 0; frame < 100; frame++) { + expect(() => world.update()).not.toThrow(); + await Promise.resolve(); + if (world.readinessAt({ x: 32, z: 32 }).ready) break; + } + const settlements = await Promise.all([first, second, third]); + for (const readiness of settlements) expect(readiness.ready).toBe(true); + } finally { + expect(() => world.dispose()).not.toThrow(); + } +}); + +it("rejects a whenSpawnReady that exceeds its finite deadline and clears the timer", async () => { + const world = await spawnWorld(new Vector3(32, 0, 32)); + vi.useFakeTimers(); + try { + const ready = world.whenSpawnReady({ x: 224, z: 32 }, 0, 500); + const expectation = expect(ready).rejects.toThrow("WorldCells spawn exceeded 500 ms"); + expect(vi.getTimerCount()).toBe(1); + await vi.advanceTimersByTimeAsync(500); + await expectation; + expect(vi.getTimerCount()).toBe(0); + } finally { + vi.useRealTimers(); + world.dispose(); + } +}); + +it.each([ + [{ x: Number.NaN, z: 32 }, 0, 1], + [{ x: 32, z: Number.POSITIVE_INFINITY }, 0, 1], + [{ x: 32, z: 32 }, -1, 1], + [{ x: 32, z: 32 }, Number.NaN, 1], + [{ x: 32, z: 32 }, 0, 0], + [{ x: 32, z: 32 }, 0, Number.POSITIVE_INFINITY], + [{ x: 32, z: 32 }, 0, 2 ** 31], +])("rejects malformed whenSpawnReady inputs %#", async (position, radius, timeoutMs) => { + const world = await spawnWorld(new Vector3(32, 0, 32)); + try { + await expect(world.whenSpawnReady(position, radius, timeoutMs)).rejects.toThrow( + /WorldCells spawn readiness requires/, + ); + } finally { + world.dispose(); + } +}); + +it("snapshots the spawn coordinates taken at whenSpawnReady call time", async () => { + const world = await spawnWorld(new Vector3(224, 0, 32)); + try { + const spawn = { x: 32, z: 32 }; + const ready = world.whenSpawnReady(spawn); + let settled = false; + void ready.then( + () => { + settled = true; + }, + () => { + settled = true; + }, + ); + spawn.x = 224; + world.update(); + await Promise.resolve(); + expect(settled).toBe(false); + const expectation = expect(ready).rejects.toThrow("WorldCells spawn cancelled"); + world.dispose(); + await expectation; + } finally { + world.dispose(); + } +}); + +it("resolves a whenSpawnReady once and keeps that success after the region is evicted", async () => { + const follow = new Vector3(32, 0, 32); + const world = await spawnWorld(follow); + try { + await untilReady(world, { x: 32, z: 32 }); + const ready = world.whenSpawnReady({ x: 32, z: 32 }); + const resolved = await ready; + follow.x = 224; + await settle(world); + expect(world.stats().evictions).toBeGreaterThan(0); + await expect(ready).resolves.toBe(resolved); + } finally { + world.dispose(); + } +}); diff --git a/packages/core/__tests__/world-cells-race.spec.ts b/packages/core/__tests__/world-cells-race.spec.ts new file mode 100644 index 0000000000..54df75bf98 --- /dev/null +++ b/packages/core/__tests__/world-cells-race.spec.ts @@ -0,0 +1,298 @@ +import { + BufferAttribute, + BufferGeometry, + Group, + Mesh, + MeshBasicMaterial, + type Object3D, + PerspectiveCamera, + Scene, +} from "three"; +import { describe, expect, it, vi } from "vitest"; +import type { IRendererLike } from "../src/renderer.js"; +import { type IWorldPackage, WorldCells } from "../src/world.js"; + +const SEEDS = 1000; +const STEPS = 18; +const CELLS = 4; +const CELL_SIZE = 32; +const DRAIN_ROUNDS = 32; + +interface ITotals { + completed: number; + loads: number; + geometriesDisposed: number; + disposedWhilePending: number; + lateLoads: number; + evictions: number; + reentries: number; + hlodSeeds: number; + refcountSeeds: number; +} + +/** Mulberry32: one seed fixes every decision a scenario makes. */ +function random(seed: number): () => number { + let state = seed; + return () => { + state += 0x6d2b79f5; + let t = Math.imul(state ^ (state >>> 15), state | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} +function geometry(triangles: number): BufferGeometry { + const result = new BufferGeometry(); + result.setAttribute( + "position", + new BufferAttribute(new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), 3), + ); + result.setIndex( + new BufferAttribute(new Uint16Array(Array.from({ length: triangles * 3 }, (_, i) => i % 3)), 1), + ); + return result; +} +function model(triangles: number): Group { + const result = new Group(); + result.add(new Mesh(geometry(triangles), new MeshBasicMaterial())); + return result; +} +function manifest(): IWorldPackage { + return { + version: 1, + extent: { minX: 0, minZ: 0, sizeX: CELL_SIZE * CELLS, sizeZ: CELL_SIZE }, + cellSize: CELL_SIZE, + terrain: { + heightmap: "height.u16", + columns: CELLS + 1, + rows: 2, + spacing: 32, + heightMin: 0, + heightMax: 2, + }, + placements: "placements.bin", + assets: { stone: { glb: "stone.glb", lods: [], bounds: { min: [0, 0, 0], max: [1, 1, 0] } } }, + cells: Array.from({ length: CELLS }, (_, x) => ({ + x, + z: 0, + runs: [{ asset: "stone", offset: 2 * x, count: 2 }], + proxy: { + glb: `proxy${x}.glb`, + scope: "cell", + triangles: 1, + materialGroups: 1, + error: 0.1, + bounds: { min: [x * CELL_SIZE + 10, 0, 10], max: [x * CELL_SIZE + 20, 1, 10] }, + }, + })), + }; +} +// Two placements per cell, eight floats each: position, identity quaternion, unit scale. +function placements(): ArrayBuffer { + const buffer = new ArrayBuffer(CELLS * 2 * 8 * Float32Array.BYTES_PER_ELEMENT); + const result = new Float32Array(buffer); + for (let i = 0; i < CELLS * 2; i++) { + const x = Math.floor(i / 2) * CELL_SIZE + (i % 2 === 0 ? 10 : 19); + result.set([x, 0, 10, 0, 0, 0, 1, 1], i * 8); + } + return buffer; +} +const camera = new PerspectiveCamera(60, 1, 0.1, 2000); +function aim(x: number, z: number, near: boolean): void { + camera.position.set(x, 1, near ? z - 4 : z + 600); + camera.lookAt(x, 1, z); + camera.updateMatrixWorld(true); +} +async function flush(): Promise { + for (let i = 0; i < 8; i++) await Promise.resolve(); + await new Promise((resolve) => setImmediate(resolve)); +} + +async function scenario(seed: number, totals: ITotals): Promise { + const next = random(seed); + const pick = (count: number): number => Math.floor(next() * count); + const follow = { position: { x: 16, z: 16 } }; + // Loader and compile completions are held here until the schedule resolves them. + const pendingLoads: Array<() => void> = []; + const pendingCompiles: Array<() => void> = []; + const disposals: Array<() => number> = []; + let near = false; + let disposed = false; + + const renderer = { + info: { render: { drawCalls: 0 } }, + domElement: { height: 1080 }, + kind: "webgl", + raw: {}, + compileAsync: () => + new Promise((done) => { + pendingCompiles.push(() => done()); + }), + uploadAttributes: () => 1, + } as unknown as IRendererLike; + const loadModel = (url: string): Promise => { + totals.loads += 1; + const proxy = /proxy(\d+)\.glb$/.exec(url); + const result = model(proxy ? 1 : 2); + if (proxy) result.position.set(Number(proxy[1]) * CELL_SIZE + 10, 0, 10); + const dispose = vi.spyOn((result.children[0] as Mesh).geometry, "dispose"); + disposals.push(() => dispose.mock.calls.length); + return new Promise((done) => { + pendingLoads.push(() => done(result)); + }); + }; + + const world = await WorldCells.load({ + url: "/world/world.json", + surface: new MeshBasicMaterial(), + follow, + ring: 0, + budgets: { bytes: 10_000_000, instances: 100, residentCells: 2 }, + terrain: false, + gpuScene: false, + bundles: false, + adaptiveLod: false, + data: { manifest: manifest(), placements: placements() }, + loadModel, + }); + new Scene().add(world); + + const settle = async (queue: Array<() => void>): Promise => { + if (queue.length === 0) return; + const [run] = queue.splice(pick(queue.length), 1); + if (disposed && queue === pendingLoads) totals.lateLoads += 1; + run?.(); + await flush(); + }; + const drain = async (): Promise => { + for (let guard = 0; guard < 10_000; guard++) { + const fromLoad = pendingLoads.length > 0; + const run = fromLoad ? pendingLoads.shift() : pendingCompiles.shift(); + if (run === undefined) return; + if (disposed && fromLoad) totals.lateLoads += 1; + run(); + await flush(); + } + throw new Error("drain never emptied the pending queue"); + }; + const pump = async (): Promise => { + aim(follow.position.x, follow.position.z, near); + world.update(renderer, camera); + await flush(); + }; + + let residents = new Set(); + const evicted = new Set(); + let hlodSeen = false; + let refcountSeen = false; + let residentBytesSeen = false; + const observe = (): void => { + const stats = world.stats(); + const now = new Set(stats.residentKeys); + for (const key of residents) if (!now.has(key)) evicted.add(key); + for (const key of now) if (!residents.has(key) && evicted.has(key)) totals.reentries += 1; + residents = now; + if (stats.hlod.bytes > 0) hlodSeen = true; + residentBytesSeen ||= stats.residentBytes > 0; + if (Object.values(world.assetRefCounts()).some((count) => count > 0)) refcountSeen = true; + }; + + // One residency pass before the schedule, so every seed starts a load that the schedule can race. + await pump(); + observe(); + + const disposeAt = 2 + pick(STEPS - 4); + for (let step = 0; step < STEPS; step++) { + const roll = next(); + if (step === disposeAt) { + const stats = world.stats(); + if (pendingLoads.length > 0 || stats.loadsInFlight > 0 || stats.loadsQueued > 0) + totals.disposedWhilePending += 1; + world.dispose(); + disposed = true; + } else if (!disposed && roll < 0.2) { + const cell = pick(CELLS + 1); + follow.position.x = cell < CELLS ? cell * CELL_SIZE + 16 : -1000; + follow.position.z = cell < CELLS ? 16 : -1000; + } else if (!disposed && roll < 0.3) { + near = next() < 0.4; + } else if (!disposed && roll < 0.55) { + await pump(); + } else { + await settle(roll < 0.8 ? pendingLoads : pendingCompiles); + } + observe(); + } + + if (!disposed) { + for (let round = 0; round < DRAIN_ROUNDS; round++) { + await pump(); + await drain(); + const stats = world.stats(); + if (stats.loadsInFlight === 0 && stats.loadsQueued === 0) break; + } + expect(world.stats().loadsInFlight, "admission settles before dispose").toBe(0); + world.dispose(); + disposed = true; + } + await drain(); + + const final = world.stats(); + expect(disposals.length, "loader calls observed").toBeGreaterThan(0); + expect(pendingLoads.length + pendingCompiles.length, "nothing pending after drain").toBe(0); + expect(residentBytesSeen, "resident bytes observed before teardown").toBe(true); + expect(final.residentBytes, "resident bytes").toBe(0); + expect(final.residentCells, "resident cells").toBe(0); + expect(final.instances, "resident instances").toBe(0); + expect(final.residentKeys, "resident keys").toEqual([]); + expect(final.hlod.bytes, "HLOD bytes").toBe(0); + expect(final.hlod.pending, "HLOD pending").toBe(0); + expect(final.loadsInFlight, "loads in flight").toBe(0); + expect(final.loadsQueued, "loads queued").toBe(0); + expect(final.failures, "counted teardown failures").toBe(0); + expect( + Object.values(world.assetRefCounts()).filter((count) => count !== 0), + "asset refcounts left after dispose", + ).toEqual([]); + for (const calls of disposals) expect(calls(), "geometry dispose calls").toBe(1); + + totals.geometriesDisposed += disposals.filter((calls) => calls() > 0).length; + totals.evictions += final.evictions; + totals.completed += 1; + if (hlodSeen) totals.hlodSeeds += 1; + if (refcountSeen) totals.refcountSeeds += 1; +} + +describe("WorldCells load, eviction, dispose and re-entry interleavings", () => { + it("ends every seeded interleaving with no resident bytes, nothing resident, no HLOD bytes or refcounts, and each geometry disposed once", async () => { + const totals: ITotals = { + completed: 0, + loads: 0, + geometriesDisposed: 0, + disposedWhilePending: 0, + lateLoads: 0, + evictions: 0, + reentries: 0, + hlodSeeds: 0, + refcountSeeds: 0, + }; + const failures: string[] = []; + for (let seed = 1; seed <= SEEDS; seed++) { + try { + await scenario(seed, totals); + } catch (error) { + failures.push(`seed ${seed}: ${error instanceof Error ? error.message : String(error)}`); + } + } + console.info(`world-cells-race totals: ${JSON.stringify(totals)}`); + expect(failures.length, failures.slice(0, 20).join("\n")).toBe(0); + expect(totals.completed).toBe(SEEDS); + expect(totals.loads, "observed loader calls").toBeGreaterThan(0); + expect(totals.geometriesDisposed, "observed geometry disposals").toBeGreaterThan(0); + expect(totals.disposedWhilePending, "dispose while a load was pending").toBeGreaterThan(0); + expect(totals.lateLoads, "loads completing after dispose").toBeGreaterThan(0); + expect(totals.evictions, "evictions").toBeGreaterThan(0); + expect(totals.reentries, "re-entries after eviction").toBeGreaterThan(0); + expect(totals.hlodSeeds, "seeds with HLOD bytes observed").toBeGreaterThan(0); + expect(totals.refcountSeeds, "seeds with positive refcounts observed").toBeGreaterThan(0); + }, 50_000); +}); diff --git a/packages/core/__tests__/world-cells-terrain.spec.ts b/packages/core/__tests__/world-cells-terrain.spec.ts new file mode 100644 index 0000000000..8ab6634907 --- /dev/null +++ b/packages/core/__tests__/world-cells-terrain.spec.ts @@ -0,0 +1,146 @@ +import { BoxGeometry, Group, Mesh, MeshBasicMaterial, type Object3D } from "three"; +import { describe, expect, it, vi } from "vitest"; +import { Terrain, bakeWorldPackage } from "../../../packages/terrain/src/index.js"; +import type { IAssetLoader } from "../src/assets.js"; +import { type IWorldPackage, WorldCells } from "../src/world.js"; + +/** Single asset declaration for the test terrain */ +const ASSETS = { + fir: { + bounds: { max: [3, 14, 3] as const, min: [-3, 0, -3] as const }, + glb: "models/fir.glb", + lods: [{ distance: 50, glb: "models/fir-mid.glb" }], + maxDistance: 300, + }, +}; + +function makeModel(): Object3D { + const group = new Group(); + group.add(new Mesh(new BoxGeometry(1, 1, 1), new MeshBasicMaterial())); + return group; +} + +function cacheLoader(): IAssetLoader { + return { + audio: () => Promise.reject(new Error("unused")), + clear: () => {}, + model: (() => + Promise.resolve({ scene: makeModel() } as unknown as T)) as IAssetLoader["model"], + progress: { pending: [], requested: 0, requestedBytes: 0, settled: 0, settledBytes: 0 }, + release: () => true, + resolve: (p) => Promise.resolve([p]), + resolved: new Map(), + texture: () => Promise.reject(new Error("unused")), + }; +} + +describe("WorldCells baked terrain package integration", () => { + it("loads a real baked 3x3-cell package and verifies resident cells and instances during walk", async () => { + let world: WorldCells | undefined; + try { + // 192m extent with cellSize 64 -> exactly 3x3 cells (extent -96 to 96) + const terrain = new Terrain({ resolution: 65, seed: 123, size: 192 }) + .scatter({ id: "firs", asset: "fir", count: 90, minDistance: 2 }) + .evaluate(); + + const baked = bakeWorldPackage(terrain, { assets: ASSETS, cellSize: 64 }); + const manifest = baked.manifest as IWorldPackage; + + expect(manifest.cells).toHaveLength(9); + const expectedCells = new Map(); + for (const cell of manifest.cells) { + const count = cell.runs.reduce((acc, run) => acc + run.count, 0); + expectedCells.set(`${cell.x}:${cell.z}`, count); + } + + // Verify every cell has placements + expect(expectedCells.size).toBe(9); + const totalInstances = Array.from(expectedCells.values()).reduce((a, b) => a + b, 0); + expect(totalInstances).toBe(terrain.instances.length); + + const files = baked.files; + vi.stubGlobal( + "fetch", + vi.fn(async (url: unknown) => { + const raw = String(url); + const filename = raw.replace(/^\//u, "").replace(/^world\//u, ""); + if (filename.endsWith("world.json")) { + return { + ok: true, + json: async () => manifest, + arrayBuffer: async () => new TextEncoder().encode(JSON.stringify(manifest)).buffer, + }; + } + const data = files[filename]; + if (data) { + const buffer = (data as Uint8Array).buffer.slice( + (data as Uint8Array).byteOffset, + (data as Uint8Array).byteOffset + (data as Uint8Array).byteLength, + ); + return { + ok: true, + arrayBuffer: async () => buffer, + json: async () => JSON.parse(new TextDecoder().decode(buffer)), + }; + } + return { ok: false, status: 404 }; + }), + ); + + const follow = { position: { x: -64, z: -64 } }; + world = await WorldCells.load({ + url: "world/world.json", + assets: cacheLoader(), + follow, + surface: new MeshBasicMaterial(), + budgets: { bytes: 1_000_000_000, instances: 1_000_000, residentCells: 64 }, + ring: 0, + lodHysteresis: 0, + gpuScene: false, + impostors: false, + admissionBudgetMs: 10_000, + freshMeshesPerUpdate: 100, + }); + + const drainWorld = async (activeWorld: WorldCells) => { + for (let pass = 0; pass < 200; pass += 1) { + await new Promise((resolve) => setTimeout(resolve, 0)); + activeWorld.update(); + const stats = activeWorld.stats(); + if (stats.admission.backlog === 0 && stats.loadsInFlight === 0) return; + } + throw new Error("Timed out waiting for WorldCells to settle drain within bounds"); + }; + + // Walk across cells from (0, 0) to (2, 2) + // Chebyshev distance > ring + 1 (0 + 1 = 1) evicts leaving cells. + // From (0, 0): + // At (0, 0): resident [0:0] + // Move to (1, 1): distance from (0, 0) is max(1, 1) = 1 <= 1 (kept by hysteresis) -> [0:0, 1:1] + // Move to (2, 2): distance from (0, 0) is max(2, 2) = 2 > 1 -> (0, 0) evicted; distance from (1, 1) is 1 <= 1 -> [1:1, 2:2] + const waypoints: Array<{ x: number; z: number; expectedKeys: string[] }> = [ + { x: -64, z: -64, expectedKeys: ["0:0"] }, + { x: 0, z: 0, expectedKeys: ["0:0", "1:1"] }, + { x: 64, z: 64, expectedKeys: ["1:1", "2:2"] }, + ]; + + for (const { x, z, expectedKeys } of waypoints) { + follow.position.x = x; + follow.position.z = z; + await drainWorld(world); + + const stats = world.stats(); + expect(new Set(stats.residentKeys)).toEqual(new Set(expectedKeys)); + expect(stats.residentCells).toBe(expectedKeys.length); + const expectedInstanceCount = expectedKeys.reduce( + (sum, k) => sum + (expectedCells.get(k) ?? 0), + 0, + ); + expect(stats.instances).toBe(expectedInstanceCount); + } + } finally { + world?.dispose(); + vi.unstubAllGlobals(); + } + }); +}); diff --git a/packages/core/__tests__/world-impostor-bake.spec.ts b/packages/core/__tests__/world-impostor-bake.spec.ts index d80f96b529..8f98148dce 100644 --- a/packages/core/__tests__/world-impostor-bake.spec.ts +++ b/packages/core/__tests__/world-impostor-bake.spec.ts @@ -18,6 +18,7 @@ import { type IImpostorRawRenderer, IMPOSTOR_FRAME_PIXELS, IMPOSTOR_VIEWS, + IMPOSTOR_VIEW_GRID, VIEW_DIRECTIONS, WorldImpostorAtlas, WorldImpostorBaker, @@ -196,20 +197,17 @@ describe("WorldImpostorBaker octahedral frame", () => { expect(VIEW_DIRECTIONS).toHaveLength(IMPOSTOR_VIEWS); for (const direction of VIEW_DIRECTIONS) expect(direction.length()).toBeCloseTo(1, 6); expect(octahedralViewOf(0, 0).z).toBeLessThan(0); - expect(octahedralViewOf(3, 3).z).toBeLessThan(0); + expect(octahedralViewOf(IMPOSTOR_VIEW_GRID - 1, IMPOSTOR_VIEW_GRID - 1).z).toBeLessThan(0); }); it("blends the three corners of a view's own cell with weight one at interior points", () => { - for (const [ix, iy] of [ - [1, 1], - [2, 1], - [1, 2], - [2, 2], - ] as const) { - const blend = impostorViewWeights(octahedralViewOf(ix, iy)); - const dominant = blend.weights.indexOf(Math.max(...blend.weights)); - expect(blend.nearest[dominant]).toBe(iy * 4 + ix); - expect(blend.weights[dominant]).toBeCloseTo(1, 5); + for (let iy = 1; iy < IMPOSTOR_VIEW_GRID - 1; iy += 1) { + for (let ix = 1; ix < IMPOSTOR_VIEW_GRID - 1; ix += 1) { + const blend = impostorViewWeights(octahedralViewOf(ix, iy)); + const dominant = blend.weights.indexOf(Math.max(...blend.weights)); + expect(blend.nearest[dominant]).toBe(iy * IMPOSTOR_VIEW_GRID + ix); + expect(blend.weights[dominant]).toBeCloseTo(1, 5); + } } }); @@ -220,7 +218,8 @@ describe("WorldImpostorBaker octahedral frame", () => { let previous: readonly number[] = []; let sawThirdCorner = false; for (let step = 0; step <= 600; step += 1) { - const t = -1 / 3 + (2 / 3) * (step / 600); + const cell = Math.floor((IMPOSTOR_VIEW_GRID - 2) / 2); + const t = (2 * (cell + step / 600)) / (IMPOSTOR_VIEW_GRID - 1) - 1; const direction = new Vector3(t, t, 1 - 2 * Math.abs(t)).normalize(); const blend = impostorViewWeights(direction); const sum = blend.weights[0] + blend.weights[1] + blend.weights[2]; @@ -252,11 +251,11 @@ describe("WorldImpostorBaker octahedral frame", () => { for (let index = 0; index < 3; index += 1) { const layer = blend.nearest[index] as number; const weight = blend.weights[index] as number; - gx += weight * (layer % 4); - gy += weight * Math.floor(layer / 4); + gx += weight * (layer % IMPOSTOR_VIEW_GRID); + gy += weight * Math.floor(layer / IMPOSTOR_VIEW_GRID); } - expect(gx).toBeCloseTo((t * 0.5 + 0.5) * 3, 5); - expect(gy).toBeCloseTo((s * 0.5 + 0.5) * 3, 5); + expect(gx).toBeCloseTo((t * 0.5 + 0.5) * (IMPOSTOR_VIEW_GRID - 1), 5); + expect(gy).toBeCloseTo((s * 0.5 + 0.5) * (IMPOSTOR_VIEW_GRID - 1), 5); } }); @@ -305,14 +304,19 @@ describe("WorldImpostorAtlas", () => { expect(atlas.target.textures[0]?.name).toBe("output"); expect(atlas.target.textures[1]?.name).toBe("normal"); expect(atlas.color.generateMipmaps).toBe(true); - // 8 levels of 128x128x16 RGBA8, two attachments: 2 * 64 * 21845. - expect(atlas.bytes).toBe(2_796_160); + // Two RGBA8 attachments, every view, and the full mip chain down to 1x1. + let bytes = 0; + for (let level = 0; level <= Math.floor(Math.log2(atlas.pixels)); level += 1) { + const edge = Math.max(1, atlas.pixels >> level); + bytes += 2 * edge * edge * IMPOSTOR_VIEWS * 4; + } + expect(atlas.bytes).toBe(bytes); atlas.dispose(); }); }); describe("WorldImpostorBaker", () => { - it("captures all sixteen views, stages every source part, and regenerates mips only last", () => { + it("captures every view, stages every source part, and regenerates mips only last", () => { const sourceMap = new Texture(); const source = new MeshStandardMaterial({ alphaTest: 0.4, map: sourceMap }); const geometry = boxedGeometry(new Vector3(-1, -1, -1), new Vector3(1, 1, 1)); diff --git a/packages/core/__tests__/world-impostor-surface.spec.ts b/packages/core/__tests__/world-impostor-surface.spec.ts index 2bd16d04b5..80b11469fd 100644 --- a/packages/core/__tests__/world-impostor-surface.spec.ts +++ b/packages/core/__tests__/world-impostor-surface.spec.ts @@ -286,7 +286,7 @@ describe("instanceSyncEvent", () => { }); describe("WorldImpostorSurface atlas count", () => { - it("uses all sixteen octahedral layers the baker wrote", () => { + it("uses every octahedral layer the baker wrote", () => { const atlas = new WorldImpostorAtlas(8); const image = atlas.target.textures[0]?.image as { depth?: number } | undefined; expect(image?.depth).toBe(IMPOSTOR_VIEWS); diff --git a/packages/core/__tests__/world-impostors.spec.ts b/packages/core/__tests__/world-impostors.spec.ts index 49dff6a4b7..2a6e7823d4 100644 --- a/packages/core/__tests__/world-impostors.spec.ts +++ b/packages/core/__tests__/world-impostors.spec.ts @@ -52,12 +52,27 @@ import { IMPOSTOR_FAR_CULL_ATTRIBUTE, WorldImpostorSurface, } from "../src/render/world-impostor-surface.js"; -import { type IImpostorRawRenderer, WorldImpostorAtlas } from "../src/render/world-impostor.js"; +import { + type IImpostorRawRenderer, + IMPOSTOR_FRAME_PIXELS, + IMPOSTOR_VIEWS, + WorldImpostorAtlas, +} from "../src/render/world-impostor.js"; import type { IRendererLike } from "../src/renderer.js"; import { type IKernelInput, WorldGpuScene, cullAndSelect } from "../src/world-gpu-scene.js"; import { type IWorldAsset, type IWorldRun, cellPlacements } from "../src/world-package.js"; import { type IWorldPackage, WorldCells } from "../src/world.js"; +// Two full-mip RGBA8 attachments per atlas, one layer per view. +const atlasBytes = + 2 * + Array.from( + { length: Math.floor(Math.log2(IMPOSTOR_FRAME_PIXELS)) + 1 }, + (_, level) => Math.max(1, IMPOSTOR_FRAME_PIXELS >> level) ** 2 * IMPOSTOR_VIEWS * 4, + ).reduce((sum, bytes) => sum + bytes, 0); +const atlasBudget = 128 * 1024 * 1024; +const atlasCapacity = Math.floor(atlasBudget / atlasBytes); + // Exercise the installed renderer's actual upload decision; its private module has no declarations. interface ITestAttributes { update(attribute: BufferAttribute | InterleavedBufferAttribute, type: number): void; @@ -498,9 +513,9 @@ function liveWideCasters(world: WorldCells, asset: string): InstancedMesh[] { return meshes; } -/** Drive the bake's sixteen views to completion, one per render-cadence update. */ +/** Drive every bake view to completion, one per render-cadence update. */ function runBake(world: WorldCells, renderer: IRendererLike): void { - for (let view = 0; view < 16; view += 1) world.update(renderer); + for (let view = 0; view < IMPOSTOR_VIEWS; view += 1) world.update(renderer); } // Impostors are opt-in (PRD-473 AC-3): this suite launches with the opt-in so the default path under @@ -545,7 +560,7 @@ describe("WorldCells automatic impostors", () => { expect(impostor.assets).toBe(1); expect(impostor.pending).toBe(0); expect(impostor.terminalTriangles).toBe(2); - expect(impostor.atlasBytes).toBe(2_796_160); + expect(impostor.atlasBytes).toBe(atlasBytes); // The bake is independent of the GPU-driven main pass: this renderer is WebGPU but carries no // GPU-scene features, so the scene stays off while the atlas still lands on the CPU path. expect(world.stats().gpuScene.on).toBe(false); @@ -597,13 +612,19 @@ describe("WorldCells automatic impostors", () => { expect(world.stats().impostor.pending).toBe(1); const refilters = world.stats().levelRefilters; const sources = scene.placements.map((placement) => ({ ...placement })); - // Offset the cadence so the first check lands on the exact update that finishes the 16-view bake. - for (let dispatch = 0; dispatch < 14; dispatch += 1) scene.dispatch(renderer, camera); - for (let view = 0; view < 15; view += 1) { + // Match WorldGpuScene's cadence so a check lands on the exact update finishing the full bake. + const validationEvery = 30; + const dispatchOffset = (validationEvery - (IMPOSTOR_VIEWS % validationEvery)) % validationEvery; + for (let dispatch = 0; dispatch < dispatchOffset; dispatch += 1) + scene.dispatch(renderer, camera); + for (let view = 1; view < IMPOSTOR_VIEWS; view += 1) { + const compared = scene.validation.compared; world.update(renderer, camera); await flush(); + if (view === IMPOSTOR_VIEWS - 1) expect(scene.validation.compared).toBeGreaterThan(compared); } await flush(); + expect(world.stats().impostor.assets).toBe(1); // Drain any post-publication admission: the regression used to enqueue two real CPU jobs. world.update(renderer, camera); @@ -644,7 +665,8 @@ describe("WorldCells automatic impostors", () => { ]; for (const corruption of corruptions) { scene.slot("pine", { ...published, ...corruption }); - for (let dispatch = 0; dispatch < 30; dispatch += 1) scene.dispatch(renderer, camera); + for (let dispatch = 0; dispatch < validationEvery; dispatch += 1) + scene.dispatch(renderer, camera); await flush(); expect(scene.validation.verdict).toBe("mismatch"); expect(scene.validation.meshMismatched).toBeGreaterThan(0); @@ -1198,47 +1220,47 @@ describe("WorldCells automatic impostors", () => { world.dispose(); }); - it("bounds the default cache at 48 active atlases and keeps the refused asset's source LODs", async () => { - const count = 49; + it("bounds the active atlas cache at the default byte budget and keeps the refused asset's source LODs", async () => { + const count = atlasCapacity + 1; stubManifestFetch(manyAlphaAssets(count)); const world = await loadPine(treeLoader()); // Settle the loads with no renderer: every asset is adopted and queued, and the first bake - // begins but cannot advance, so all 49 want an atlas before any lands. + // begins but cannot advance, so every asset wants an atlas before any lands. await flushed(world); const dispose = vi.spyOn(WorldImpostorAtlas.prototype, "dispose"); const stub = rendererStub(); const raw = stub.raw as unknown as { render: () => void }; const render = vi.spyOn(raw, "render"); - const budget = 128 * 1024 * 1024; - for (let pass = 0; pass < (count + 1) * 16 + 64; pass += 1) { + const budget = atlasBudget; + for (let pass = 0; pass < (count + 1) * IMPOSTOR_VIEWS + 64; pass += 1) { world.update(stub); await flush(1); } const impostor = world.stats().impostor; expect(world.stats().failures).toBe(0); - // 48 atlases fit in the default budget; the 49th is refused rather than overrunning it, and no + // The budget holds exactly atlasCapacity atlases; the next is refused, and no // atlas is disposed while a live asset still borrows it (that would have made a surface read a - // destroyed atlas). One bake per landed asset: 16 capture calls each. - expect(impostor.assets).toBe(48); + // destroyed atlas). One bake per landed asset: IMPOSTOR_VIEWS capture calls each. + expect(impostor.assets).toBe(atlasCapacity); expect(impostor.pending).toBe(0); - expect(impostor.atlasBytes).toBe(48 * 2_796_160); + expect(impostor.atlasBytes).toBe(atlasCapacity * atlasBytes); expect(impostor.atlasBytes).toBeLessThanOrEqual(budget); expect(impostor.budgetBytes).toBe(budget); - expect(render).toHaveBeenCalledTimes(48 * 16); + expect(render).toHaveBeenCalledTimes(atlasCapacity * IMPOSTOR_VIEWS); expect(dispose).not.toHaveBeenCalled(); // A landed asset appended its terminal level; the refused one kept every source level and // appended none. expect(partsOf(world, "tree_0", 1, 0).length).toBeGreaterThan(0); - expect(partsOf(world, "tree_48", 1, 0).length).toBe(0); - expect(partsOf(world, "tree_48", 0, 0).length).toBeGreaterThan(0); - expect(partsOf(world, "tree_48", 0, 1).length).toBeGreaterThan(0); + expect(partsOf(world, `tree_${atlasCapacity}`, 1, 0).length).toBe(0); + expect(partsOf(world, `tree_${atlasCapacity}`, 0, 0).length).toBeGreaterThan(0); + expect(partsOf(world, `tree_${atlasCapacity}`, 0, 1).length).toBeGreaterThan(0); // Teardown releases each owned atlas exactly once, no more and no fewer. world.dispose(); - expect(dispose).toHaveBeenCalledTimes(48); + expect(dispose).toHaveBeenCalledTimes(atlasCapacity); }); it("shares one bake between two entries over the same cooked LOD0 with different metadata", async () => { @@ -1250,19 +1272,19 @@ describe("WorldCells automatic impostors", () => { const stub = rendererStub(); const raw = stub.raw as unknown as { render: () => void }; const render = vi.spyOn(raw, "render"); - for (let pass = 0; pass < 3 * 16 + 32; pass += 1) { + for (let pass = 0; pass < 3 * IMPOSTOR_VIEWS + 32; pass += 1) { world.update(stub); await flush(1); } // Both entries landed a terminal level, but from ONE atlas: the second re-checked the completed // cache when it was dequeued and reused the first bake instead of building a second surface on - // a disposed atlas. One bake is 16 capture calls, not 32. + // a disposed atlas. The capture count must equal exactly one full bake. const impostor = world.stats().impostor; expect(world.stats().failures).toBe(0); expect(impostor.assets).toBe(2); - expect(impostor.atlasBytes).toBe(2_796_160); - expect(render).toHaveBeenCalledTimes(16); + expect(impostor.atlasBytes).toBe(atlasBytes); + expect(render).toHaveBeenCalledTimes(IMPOSTOR_VIEWS); expect(dispose).not.toHaveBeenCalled(); expect(partsOf(world, "alpha_a", 2, 0).length).toBeGreaterThan(0); expect(partsOf(world, "alpha_b", 1, 0).length).toBeGreaterThan(0); @@ -1545,7 +1567,7 @@ describe("WorldCells far impostor residency", () => { // Every near source mesh is gone and the asset itself has been released, but the far mesh still // draws every original root from an atlas the far aggregate holds its own user on. expect(nearPineLive(world)).toBe(0); - expect(world.stats().impostor.atlasBytes).toBe(2_796_160); + expect(world.stats().impostor.atlasBytes).toBe(atlasBytes); const far = farMeshes(world)[0] as InstancedMesh; expect(farLive(far)).toBe(799); expect(dispose).not.toHaveBeenCalled(); @@ -1607,7 +1629,7 @@ describe("WorldCells far impostor residency", () => { await flushed(world); runBake(world, rendererStub()); await flushed(world); - expect(world.stats().impostor.atlasBytes).toBe(2_796_160); + expect(world.stats().impostor.atlasBytes).toBe(atlasBytes); const sourceDisposes = sourceTextures.map((texture) => vi.spyOn(texture, "dispose")); for (const dispose of sourceDisposes) expect(dispose).not.toHaveBeenCalled(); @@ -1621,7 +1643,7 @@ describe("WorldCells far impostor residency", () => { await flushed(world); expect(farMeshes(world).length).toBe(1); - expect(world.stats().impostor.atlasBytes).toBe(2_796_160); + expect(world.stats().impostor.atlasBytes).toBe(atlasBytes); expect(sourceDisposes.length).toBeGreaterThan(0); for (const dispose of sourceDisposes) expect(dispose).toHaveBeenCalledTimes(1); expect(unrelatedDispose).not.toHaveBeenCalled(); @@ -1834,7 +1856,7 @@ describe("WorldCells far impostor residency", () => { await flushed(world); const stub = rendererStub(); - for (let pass = 0; pass < 4 * 16 + 96; pass += 1) { + for (let pass = 0; pass < 4 * IMPOSTOR_VIEWS + 96; pass += 1) { world.update(stub); await flush(1); } @@ -1854,7 +1876,7 @@ describe("WorldCells far impostor residency", () => { expect(oak).toBeDefined(); expect(farLive(oak as InstancedMesh)).toBe(1); // Its temporary source is released the moment the retried aggregate lands. - expect(world.stats().impostor.atlasBytes).toBe(2 * 2_796_160); + expect(world.stats().impostor.atlasBytes).toBe(2 * atlasBytes); world.dispose(); }); @@ -1952,7 +1974,7 @@ describe("WorldCells far impostor residency", () => { expect(farMeshes(world).length).toBe(0); const stub = rendererStub(); - for (let pass = 0; pass < 4 * 16 + 96; pass += 1) { + for (let pass = 0; pass < 4 * IMPOSTOR_VIEWS + 96; pass += 1) { world.update(stub); await flush(1); } @@ -1973,12 +1995,12 @@ describe("WorldCells far impostor residency", () => { for (const geometry of oakGeometries) expect(vi.mocked(geometry.dispose).mock.calls.length).toBe(1); // Two distinct atlases (pine + oak) held after oak's source is gone. - expect(world.stats().impostor.atlasBytes).toBe(2 * 2_796_160); + expect(world.stats().impostor.atlasBytes).toBe(2 * atlasBytes); world.dispose(); }); it("keeps a far-pinned atlas out of the LRU when the cache is full", async () => { - const count = 49; + const count = atlasCapacity + 1; const pine = manifest.assets.pine; if (pine === undefined) throw new Error("the committed package has no pine asset."); const assets: Record = {}; @@ -1996,18 +2018,18 @@ describe("WorldCells far impostor residency", () => { const dispose = vi.spyOn(WorldImpostorAtlas.prototype, "dispose"); const stub = rendererStub(); - for (let pass = 0; pass < (count + 2) * 24 + 128; pass += 1) { + for (let pass = 0; pass < (count + 2) * (IMPOSTOR_VIEWS + 8) + 128; pass += 1) { world.update(stub); await flush(1); } const stats = world.stats().impostor; expect(world.stats().failures).toBe(0); - // 48 atlases fit; the 49th is refused. Every one of the 48 is pinned by a far aggregate, so the + // Exactly atlasCapacity atlases fit; the next is refused. Each is pinned by a far aggregate, so the // LRU finds nothing inactive to evict and no atlas is disposed under a live far surface. - expect(stats.atlasBytes).toBe(48 * 2_796_160); - expect(stats.far.aggregates).toBe(48); - expect(stats.far.instances).toBe(48); + expect(stats.atlasBytes).toBe(atlasCapacity * atlasBytes); + expect(stats.far.aggregates).toBe(atlasCapacity); + expect(stats.far.instances).toBe(atlasCapacity); expect(dispose).not.toHaveBeenCalled(); world.dispose(); }); @@ -2149,7 +2171,7 @@ describe("WorldCells far impostor cohorts", () => { // Roots/cardinality: two canonical ids, two placement roots, one aggregate and one bake. expect(mesh.count).toBe(2); expect(world.stats().impostor.assets).toBe(2); - expect(world.stats().impostor.atlasBytes).toBe(2_796_160); + expect(world.stats().impostor.atlasBytes).toBe(atlasBytes); world.dispose(); }); @@ -2190,7 +2212,7 @@ describe("WorldCells far impostor cohorts", () => { }); await flushed(world); const stub = rendererStub(); - for (let pass = 0; pass < 4 * 16 + 96; pass += 1) { + for (let pass = 0; pass < 4 * IMPOSTOR_VIEWS + 96; pass += 1) { world.update(stub); await flush(1); } diff --git a/packages/core/__tests__/world-terrain-splat.spec.ts b/packages/core/__tests__/world-terrain-splat.spec.ts index 4205bc97d0..4b82837aa5 100644 --- a/packages/core/__tests__/world-terrain-splat.spec.ts +++ b/packages/core/__tests__/world-terrain-splat.spec.ts @@ -1,7 +1,23 @@ -import { CompressedArrayTexture, CompressedTexture, DataArrayTexture, Texture } from "three"; -import { MeshStandardNodeMaterial } from "three/webgpu"; +import { + CompressedArrayTexture, + CompressedTexture, + DataArrayTexture, + Mesh, + type MeshStandardMaterial, + Texture, +} from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { float, vec3 } from "three/tsl"; +import { MeshStandardNodeMaterial, type Node } from "three/webgpu"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { loadTerrainSplat, stackLayers } from "../src/world-terrain-splat.js"; +import type { IAssetLoader } from "../src/assets.js"; +import { + type ITerrainSplatMaterialContext, + type ITerrainSplatSample, + applyTerrainSurface, + loadTerrainSplat, + stackLayers, +} from "../src/world-terrain-splat.js"; /** Every layer states how it responds to light: an `orm` map, or the two numbers themselves. */ const table = { @@ -112,7 +128,7 @@ function world(layers: Record | undefined): unknown { }; } -function served(files: Record): { assets: never; requested: string[] } { +function served(files: Record): { assets: IAssetLoader; requested: string[] } { const requested: string[] = []; vi.stubGlobal( "fetch", @@ -131,7 +147,7 @@ function served(files: Record): { assets: never; requested: str return bitmap(4, 4); }, }; - return { assets: assets as never, requested }; + return { assets: assets as unknown as IAssetLoader, requested }; } /** An uncompressed layer of a given size, as the asset loader hands one back (an ImageBitmap). */ @@ -181,6 +197,81 @@ afterEach(() => { vi.unstubAllGlobals(); }); +describe("applyTerrainSurface", () => { + it("replaces marked terrain only, shares the layered surface and disposes the fallback once without DOM", async () => { + const { assets, requested } = served({ + "world/world.json": world({ splat: "terrain/splat.rgba8", table: "terrain/layers.json" }), + "world/terrain/layers.json": table, + "world/terrain/splat.rgba8": new Uint8Array(2 * 4 * 4 * 4), + }); + vi.stubGlobal("document", undefined); + vi.stubGlobal("window", undefined); + vi.stubGlobal("self", globalThis); + const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 0, 1]); + const json = JSON.stringify({ + asset: { version: "2.0" }, + scene: 0, + scenes: [{ nodes: [0, 1, 3] }], + buffers: [{ byteLength: 36 }], + bufferViews: [{ buffer: 0, byteLength: 36 }], + accessors: [ + { + bufferView: 0, + componentType: 5126, + count: 3, + type: "VEC3", + min: [0, 0, 0], + max: [1, 0, 1], + }, + ], + materials: [{ name: "fallback" }, { name: "prop" }], + meshes: [0, 1].map((material) => ({ + primitives: [{ attributes: { POSITION: 0 }, material }], + })), + nodes: [ + { name: "terrain", mesh: 0, extras: { tnTerrainSurface: "world.json" } }, + { name: "group", children: [2], extras: { tnTerrainSurface: "world.json" } }, + { name: "child", mesh: 0 }, + { name: "prop", mesh: 1 }, + ], + }); + const chunk = new TextEncoder().encode(json.padEnd(Math.ceil(json.length / 4) * 4, " ")); + const glb = new Uint8Array(20 + chunk.length + 8 + positions.byteLength); + const header = new DataView(glb.buffer); + [0x46546c67, 2, glb.length, chunk.length, 0x4e4f534a].forEach((value, index) => + header.setUint32(index * 4, value, true), + ); + glb.set(chunk, 20); + header.setUint32(20 + chunk.length, positions.byteLength, true); + header.setUint32(24 + chunk.length, 0x004e4942, true); + glb.set(new Uint8Array(positions.buffer), 28 + chunk.length); + const root = (await new GLTFLoader().parseAsync(glb.buffer, "")).scene; + const terrain = root.getObjectByName("terrain") as Mesh; + const child = root.getObjectByName("child") as Mesh; + const prop = root.getObjectByName("prop") as Mesh; + const fallback = terrain.material as MeshStandardMaterial; + const dispose = vi.spyOn(fallback, "dispose"); + const propMaterial = prop.material; + await applyTerrainSurface(root, { assets, url: "world/world.glb" }); + expect(terrain.material).toBeInstanceOf(MeshStandardNodeMaterial); + expect(child.material).toBe(terrain.material); + expect(prop.material).toBe(propMaterial); + expect(dispose).toHaveBeenCalledTimes(1); + expect(requested).toContain("world/terrain/tex/grass_diff.jpg"); + }); + + it("rejects malformed surface pointers before loading or replacing materials", async () => { + const { assets } = served({}); + const root = new Mesh(); + const material = root.material; + root.userData.tnTerrainSurface = "../world.json"; + await expect(applyTerrainSurface(root, { assets, url: "world/world.glb" })).rejects.toThrow( + /pointer/u, + ); + expect(root.material).toBe(material); + }); +}); + describe("loadTerrainSplat", () => { it("builds the package's splat surface from its own table, planes and texture sets", async () => { const { assets, requested } = served({ @@ -206,6 +297,267 @@ describe("loadTerrainSplat", () => { ); }); + it("uses package-relative map paths while retaining unnamed-map defaults", async () => { + const { assets, requested } = served({ + "world/world.json": world({ splat: "terrain/splat.rgba8", table: "terrain/layers.json" }), + "world/terrain/layers.json": { + ...table, + base: { + ...table.base, + maps: { diffuse: "art/moss_diff_1k.jpg", normal: "art/moss_nor_gl_1k.jpg" }, + }, + layers: table.layers.map((layer) => + layer.id === "grass" + ? { + ...layer, + height: true, + maps: { orm: "art/grass_arm_1k.jpg", height: "art/grass_height.png" }, + } + : layer, + ), + }, + "world/terrain/splat.rgba8": new Uint8Array(2 * 4 * 4 * 4), + }); + await loadTerrainSplat({ assets, url: "world/world.json" }); + expect(requested.sort()).toEqual( + [ + "world/art/moss_diff_1k.jpg", + "world/art/moss_nor_gl_1k.jpg", + "world/art/grass_arm_1k.jpg", + "world/art/grass_height.png", + "world/terrain/tex/litter_diff.jpg", + "world/terrain/tex/grass_diff.jpg", + "world/terrain/tex/grass_nrm.jpg", + "world/terrain/tex/rock_diff.jpg", + ].sort(), + ); + }); + + it.each(["", "/absolute.jpg", "https://example.test/map.jpg", 42, null])( + "rejects malformed explicit map paths (%s)", + async (diffuse) => { + const { assets } = served({ + "world/world.json": world({ splat: "terrain/splat.rgba8", table: "terrain/layers.json" }), + "world/terrain/layers.json": { ...table, base: { ...table.base, maps: { diffuse } } }, + "world/terrain/splat.rgba8": new Uint8Array(2 * 4 * 4 * 4), + }); + await expect(loadTerrainSplat({ assets, url: "world/world.json" })).rejects.toThrow( + /package-relative path/u, + ); + }, + ); + + it.each([null, "art/moss.jpg", [], 42])("rejects malformed map containers (%s)", async (maps) => { + const { assets } = served({ + "world/world.json": world({ splat: "terrain/splat.rgba8", table: "terrain/layers.json" }), + "world/terrain/layers.json": { ...table, base: { ...table.base, maps } }, + "world/terrain/splat.rgba8": new Uint8Array(2 * 4 * 4 * 4), + }); + await expect(loadTerrainSplat({ assets, url: "world/world.json" })).rejects.toThrow( + /maps needs a path object/u, + ); + }); + + it("propagates missing explicit maps instead of retrying the default map", async () => { + const { assets } = served({ + "world/world.json": world({ splat: "terrain/splat.rgba8", table: "terrain/layers.json" }), + "world/terrain/layers.json": { + ...table, + base: { ...table.base, maps: { diffuse: "missing.jpg" } }, + }, + "world/terrain/splat.rgba8": new Uint8Array(2 * 4 * 4 * 4), + }); + const texture = vi.spyOn(assets, "texture").mockImplementation(async (path) => { + if (path === "world/missing.jpg") throw new Error("Missing explicit terrain map"); + return bitmap(4, 4); + }); + await expect(loadTerrainSplat({ assets, url: "world/world.json" })).rejects.toThrow( + "Missing explicit terrain map", + ); + expect(texture).not.toHaveBeenCalledWith("world/terrain/tex/moss_diff.jpg", expect.anything()); + }); + + it("blends precomputed game samples once and hands the result to the game's material factory", async () => { + const { assets } = served({ + "world/world.json": world({ splat: "terrain/splat.rgba8", table: "terrain/layers.json" }), + "world/terrain/layers.json": { + ...table, + layers: table.layers.map((layer) => ({ ...layer, height: layer.id === "rock" })), + }, + "world/terrain/splat.rgba8": new Uint8Array(2 * 4 * 4 * 4), + }); + const maps: Texture[] = []; + vi.spyOn(assets, "texture").mockImplementation(async () => { + const map = bitmap(4, 4); + maps.push(map); + return map; + }); + const dispose = vi.spyOn(Texture.prototype, "dispose"); + const copier = copyingRenderer(); + const seen: string[] = []; + const samples: ITerrainSplatSample[] = []; + const weights = [float(0.25), float(0.5), float(0.75)]; + const result = new MeshStandardNodeMaterial(); + let blended: ITerrainSplatMaterialContext | undefined; + const material = await loadTerrainSplat({ + assets, + renderer: copier as never, + url: "world/world.json", + layerSample: ({ layer, index, diffuse, normal, orm, height }) => { + seen.push(layer.id); + expect(diffuse).toBeInstanceOf(Texture); + expect(normal !== undefined).toBe(layer.normal === true); + expect(orm !== undefined).toBe(layer.orm === true); + expect(height !== undefined).toBe(layer.id === "rock"); + const sample = { + color: vec3(index + 1), + normal: normal === undefined ? undefined : vec3(index + 0.1), + orm: orm === undefined ? undefined : vec3(index + 0.2), + }; + samples.push(sample); + return sample; + }, + layerWeight: (_weight, { index, height }) => { + expect(seen).toEqual(["moss", "litter", "grass", "rock"]); + expect(height !== undefined).toBe(index === 2); + return weights[index] as Node<"float">; + }, + materialFactory: (context) => { + blended = context; + return result; + }, + }); + expect(material).toBe(result); + expect(copier.layers).toEqual([]); + expect(material.userData.TN_TERRAIN_SPLAT).toBe( + "layers=4 loadedSamplerUpperBound=9 stacked=0 sampling=custom", + ); + // Inspect the actual shader graph: the engine owns ordered composition of every channel. + const field = (node: object, key: string) => { + let current = node; + while (Reflect.get(current, "isVarNode") === true) current = Reflect.get(current, "node"); + return Reflect.get(current, key); + }; + const outerColor = field(blended?.color as object, "aNode"); + expect(field(outerColor, "method")).toBe("mix"); + expect(field(outerColor, "bNode")).toBe(samples[3]?.color); + expect(field(outerColor, "cNode")).toBe(weights[2]); + expect(field(field(outerColor, "aNode"), "bNode")).toBe(samples[2]?.color); + expect(field(field(field(outerColor, "aNode"), "aNode"), "aNode")).toBe(samples[0]?.color); + expect(field(blended?.normal as object, "aNode")).toBe(samples[0]?.normal); + expect(field(blended?.normal as object, "bNode")).toBe(samples[2]?.normal); + expect(field(blended?.normal as object, "cNode")).toBe(weights[1]); + expect(field(field(blended?.orm as object, "aNode"), "bNode")).toBe(samples[2]?.orm); + const rockOrm = field(blended?.orm as object, "bNode"); + expect(field(rockOrm, "value").toArray()).toEqual([1, 0.6, 0.2]); + expect(dispose).not.toHaveBeenCalled(); + for (const map of maps) expect(map.version).toBe(1); + material.dispose(); + expect(dispose).toHaveBeenCalledTimes(maps.length + 1); + material.dispose(); + expect(dispose).toHaveBeenCalledTimes(maps.length + 1); + }); + + it("propagates a final material factory failure", async () => { + const { assets } = served({ + "world/world.json": world({ splat: "terrain/splat.rgba8", table: "terrain/layers.json" }), + "world/terrain/layers.json": table, + "world/terrain/splat.rgba8": new Uint8Array(2 * 4 * 4 * 4), + }); + await expect( + loadTerrainSplat({ + assets, + url: "world/world.json", + materialFactory: () => { + throw new Error("Authored material failed"); + }, + }), + ).rejects.toThrow("Authored material failed"); + }); + + it("releases factory-only stacked arrays without disposing their source maps twice", async () => { + const { assets } = served({ + "world/world.json": world({ splat: "terrain/splat.rgba8", table: "terrain/layers.json" }), + "world/terrain/layers.json": table, + "world/terrain/splat.rgba8": new Uint8Array(2 * 4 * 4 * 4), + }); + const sources: Array> = []; + vi.spyOn(assets, "texture").mockImplementation(async () => { + const map = bitmap(4, 4); + const disposed = vi.fn(); + map.addEventListener("dispose", disposed); + sources.push(disposed); + return map; + }); + const dispose = vi.spyOn(Texture.prototype, "dispose"); + const material = await loadTerrainSplat({ + assets, + renderer: copyingRenderer() as never, + url: "world/world.json", + materialFactory: () => new MeshStandardNodeMaterial(), + }); + expect(sources).toHaveLength(7); + for (const disposed of sources) expect(disposed).toHaveBeenCalledTimes(1); + expect(dispose).toHaveBeenCalledTimes(7); + material.dispose(); + // Three arrays plus the splat are released; their seven input images were released by stacking. + expect(dispose).toHaveBeenCalledTimes(11); + for (const disposed of sources) expect(disposed).toHaveBeenCalledTimes(1); + }); + + it.each(["sample", "weight", "factory"])( + "releases custom-owned images when %s composition throws", + async (phase) => { + const { assets } = served({ + "world/world.json": world({ splat: "terrain/splat.rgba8", table: "terrain/layers.json" }), + "world/terrain/layers.json": table, + "world/terrain/splat.rgba8": new Uint8Array(2 * 4 * 4 * 4), + }); + const shared = bitmap(4, 4); + vi.spyOn(assets, "texture").mockResolvedValue(shared); + const dispose = vi.spyOn(Texture.prototype, "dispose"); + const fail = () => { + throw new Error("Authored composition failed"); + }; + await expect( + loadTerrainSplat({ + assets, + url: "world/world.json", + layerSample: phase === "sample" ? fail : () => ({ color: vec3(0.5) }), + layerWeight: phase === "weight" ? fail : undefined, + materialFactory: phase === "factory" ? fail : undefined, + }), + ).rejects.toThrow("Authored composition failed"); + expect(dispose).toHaveBeenCalledTimes(2); // one shared loaded image and the owned splat + }, + ); + + it("releases late custom-owned loads after another image fails", async () => { + const { assets } = served({ + "world/world.json": world({ splat: "terrain/splat.rgba8", table: "terrain/layers.json" }), + "world/terrain/layers.json": table, + "world/terrain/splat.rgba8": new Uint8Array(2 * 4 * 4 * 4), + }); + let finish!: (map: Texture) => void; + const late = new Promise((resolve) => { + finish = resolve; + }); + vi.spyOn(assets, "texture") + .mockRejectedValueOnce(new Error("Missing image")) + .mockReturnValue(late); + const dispose = vi.spyOn(Texture.prototype, "dispose"); + await expect( + loadTerrainSplat({ + assets, + url: "world/world.json", + layerSample: () => ({ color: vec3(0.5) }), + }), + ).rejects.toThrow("Missing image"); + expect(dispose).toHaveBeenCalledTimes(1); + finish(bitmap(4, 4)); + await vi.waitFor(() => expect(dispose).toHaveBeenCalledTimes(2)); + }); + it("blends a layer's own map at the game's far tile, by distance, when the table asks", async () => { // A repeated ground map reads as a grid at grazing angles; the same map at a much larger tile, // mixed in with distance, breaks the period. Sizes and distances are the table's, not ours. diff --git a/packages/core/capabilities.json b/packages/core/capabilities.json index d84efdd9c0..f5ee62434d 100644 --- a/packages/core/capabilities.json +++ b/packages/core/capabilities.json @@ -84,6 +84,23 @@ "supersedes": [], "symbol": "compileAssets" }, + { + "aliases": [], + "constraints": [], + "example": "await cookTerrainWorld({ state, species, out: \"assets/world\" });", + "importPath": "@threenative/assets", + "kind": "function", + "overrides": [], + "package": "@threenative/assets", + "signature": "export function cookTerrainWorld( options: IPreparedTerrainWorldOptions, ): Promise< { … }", + "situations": [ + "bake a terrain world headlessly into a GLB with instanced trees and rocks", + "generate a forest world for my game" + ], + "summary": "Bake evaluated terrain and static species into an instanced GLB and streaming world package.", + "supersedes": [], + "symbol": "cookTerrainWorld" + }, { "aliases": [], "constraints": [ @@ -552,7 +569,7 @@ "add hundreds of built objects to the scene without one multi-second frame", "stream a detail tier in behind a loading curtain without the page looking hung" ], - "summary": "Attach hundreds of built objects to the scene in slices, presenting a frame between each.", + "summary": "Attach hundreds of built objects to the scene in slices with an 8 ms default budget, presenting a frame between each.", "supersedes": [], "symbol": "addInSlices" }, @@ -3814,6 +3831,24 @@ "supersedes": [], "symbol": "subscribeUiState" }, + { + "aliases": [], + "constraints": [ + "url names the loaded GLB; tnTerrainSurface extras name a relative world package JSON" + ], + "example": "await applyTerrainSurface(world.scene, { assets: ctx.assets, renderer: ctx.renderer, url: \"world/world.glb\" });", + "importPath": "@threenative/core/world", + "kind": "function", + "overrides": [ + "every appearance input comes from the package table or loadTerrainSplat options" + ], + "package": "@threenative/core", + "signature": "export async function applyTerrainSurface( root: Object3D, options: ILoadTerrainSplatOptions, ): Promise { … }", + "situations": ["apply the authored layered terrain surface after loading a cooked world GLB"], + "summary": "Apply a cooked GLB's package-authored layered surface to its marked terrain meshes. `url` is the loaded GLB URL; extras pointers are relative to it. Uses the same asset/fetch path as loadTerrainSplat on web and native. The caller owns the assigned materials' lifetime.", + "supersedes": [], + "symbol": "applyTerrainSurface" + }, { "aliases": [], "constraints": [ @@ -4041,6 +4076,7 @@ "constraints": [ "surface is the game's; this class creates no material, colour or geometry", "budgets are hard caps that report pressure instead of over-committing", + "`whenSpawnReady(position, radius, timeoutMs)` snapshots a fixed region and settles from existing updates or disposal; it does not drive streaming, and `prewarmed` remains the separate draw gate", "model loads are bounded by `concurrency` (default 12) across every resident cell, not per cell", "refilters are bounded by `rebuildsPerUpdate` (default 16) per update, nearest cell first", "admission is bounded by `admissionBudgetMs` (default 2) per update across every path, plus at most one unit each for terrain and props; while props are queued terrain takes at most half, so neither starves the other, and a deferred cell keeps drawing what it has", @@ -7121,7 +7157,7 @@ "overrides": [], "package": "@threenative/terrain", "requires": ["npm i @threenative/terrain"], - "signature": "export async function makeExport( state: ITerrainState, recipe: ITerrainDocument, kind: ExportKind, ): Promise { … }", + "signature": "export function makeExport( state: ITerrainState | null, recipe: ITerrainDocument, kind: \"project\", ): Promise;", "situations": ["export a terrain recipe and its numerical runtime data"], "summary": "Recovered numerical/project exports; GLB/runtime outputs contain terrain only.", "supersedes": [], @@ -7284,6 +7320,72 @@ "supersedes": [], "symbol": "checkEnvironmentAssets" }, + { + "aliases": [], + "constraints": [ + "the caller supplies the TransformControls, proxy, controller and every drawing callback" + ], + "example": "const selection = createEditorSelection({ gizmo, proxy, controller, grounding, surface, defaultFootprintSize, refreshPose, drawFootprint, restorePlacement, previewPlacement, setSaving, selectedChanged, setNavigation, message });", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function createEditorSelection( { … }", + "situations": ["drive terrain-editor selection, gizmo drags, undo and saved transactions"], + "summary": "Gesture and transaction mechanics; the caller owns the controls, proxy and all rendering.", + "supersedes": [], + "symbol": "createEditorSelection" + }, + { + "aliases": [], + "constraints": [ + "the point is multiplied in place; the pose needs finite position, quaternion and scale" + ], + "example": "const centre = editorPlacementCenter(localCentre.clone(), placement);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function editorPlacementCenter(point: Vector3, pose: IPlacementOverride): Vector3 { … }", + "situations": ["find the world-space centre of a placed terrain-editor instance"], + "summary": "Transform the caller's measured geometry centre into the evaluated placement's world space.", + "supersedes": [], + "symbol": "editorPlacementCenter" + }, + { + "aliases": [], + "constraints": ["the point is projected in place; pass a clone to keep the original"], + "example": "const [x, y] = editorPointToClient(point.clone(), camera, canvas.getBoundingClientRect());", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function editorPointToClient( point: Vector3, camera: Camera, box: { … }", + "situations": ["convert a terrain-editor world point to viewport client pixels"], + "summary": "Project an injected world point to client pixels in the caller's viewport.", + "supersedes": [], + "symbol": "editorPointToClient" + }, + { + "aliases": [], + "constraints": [ + "the supplied mesh and raycast determine the surface; missing intersections return undefined" + ], + "example": "const height = editorSurfaceHeight(ctx, terrainMesh, x, z);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function editorSurfaceHeight( ctx: { … }", + "situations": ["query terrain-editor surface elevation at world X/Z coordinates"], + "summary": "Query the rendered heightfield, including its triangulation rather than an interpolated bake.", + "supersedes": [], + "symbol": "editorSurfaceHeight" + }, { "aliases": [], "constraints": ["framing only; an unknown target returns a named diagnostic and no camera"], @@ -7341,6 +7443,23 @@ "supersedes": [], "symbol": "inspectSpatial" }, + { + "aliases": [], + "constraints": [ + "the caller supplies world and default footprint sizes; unsupported layers return undefined" + ], + "example": "const pose = landformPose(layer, terrain.size, footprintSize);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function landformPose(layer: Layer, worldSize: number, defaultSize: number) { … }", + "situations": ["derive a terrain-editor gizmo pose from a landform layer"], + "summary": "Recipe footprints are heights over X/Z, never independently transformed solids.", + "supersedes": [], + "symbol": "landformPose" + }, { "aliases": [], "constraints": [ @@ -7358,6 +7477,40 @@ "supersedes": [], "symbol": "mountTerrainEditor" }, + { + "aliases": [], + "constraints": [ + "the caller supplies the raycast context, terrain mesh, instanced meshes and evaluated placements" + ], + "example": "const id = pickEditorPlacement(ctx, terrainMesh, meshes, placements, event.clientX, event.clientY);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function pickEditorPlacement( ctx: Parameters[0], terrain: Mesh, meshes: readonly Object3D[], placements: Iterable< { … }", + "situations": [ + "pick a placed instance in the terrain editor from viewport client coordinates" + ], + "summary": "A foreground terrain hit occludes placements; matching uses the evaluated world position.", + "supersedes": [], + "symbol": "pickEditorPlacement" + }, + { + "aliases": [], + "constraints": ["the caller supplies the raycast implementation and ordered targets"], + "example": "const hit = pickEditorSurface(ctx, [terrainMesh], event.clientX, event.clientY);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function pickEditorSurface( ctx: { … }", + "situations": ["pick a terrain-editor surface from viewport client coordinates"], + "summary": "Convert viewport client coordinates once; the installed picker owns ray intersection.", + "supersedes": [], + "symbol": "pickEditorSurface" + }, { "aliases": [], "constraints": ["authoring data only; non-finite, malformed and out-of-extent queries throw"], @@ -7464,6 +7617,23 @@ "supersedes": [], "symbol": "TerrainEditorController" }, + { + "aliases": [], + "constraints": [ + "finite coordinates and positive scales are required; X/Z rotations that overhang throw" + ], + "example": "const next = transformedLandform(layer, position, quaternion, scale, surfaceHeight);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function transformedLandform( layer: Layer, position: Vector3, quaternion: Quaternion, scale: Vector3, anchor: number, ): Layer { … }", + "situations": ["apply a terrain-editor gizmo transform to a landform layer"], + "summary": "Convert a terrain-editor gizmo pose into a heightfield landform footprint.", + "supersedes": [], + "symbol": "transformedLandform" + }, { "aliases": [], "constraints": [ @@ -7636,24 +7806,86 @@ { "aliases": [], "constraints": [ - "browser authoring (FileReader/canvas); caller supplies all appearance and coherent baked water; root and /three remain headless" + "browser authoring FileReader/canvas; all appearance and coherent water come from the game" ], - "example": "const output = await exportWorldGLB({ revision, snapshotTime: 0, state, terrain, assets, transforms });", + "example": "const prepared = await prepareWorldGLTF(input);", "importPath": "@threenative/terrain/export", "kind": "function", - "overrides": [ - "actual static models, final matrices and baked PBR maps are supplied by the game" + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export async function prepareWorldGLTF(input: IWorldGLBInput): Promise< { … }", + "situations": [ + "export the terrain as a glb for another three.js project via the headless cook", + "prepare the terrain editor's resolved world for headless portable GLB cooking" + ], + "summary": "Prepare the game's validated, frozen portable scene; the server cook owns GLB encoding.", + "supersedes": [], + "symbol": "prepareWorldGLTF" + }, + { + "aliases": [], + "constraints": ["Node authoring only; recipes, asset tables and sky paths stay in the game"], + "example": "await bakeKit(import.meta.url, { sky: \"hdri/game-sky.hdr\" });", + "importPath": "@threenative/terrain/kit", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export async function bakeKit( url: string, { … }", + "situations": ["bake a copied terrain starter kit into its offline world package"], + "summary": "Bake a copied kit, optionally using the caller's installed world cook.", + "supersedes": [], + "symbol": "bakeKit" + }, + { + "aliases": [], + "constraints": [ + "Node authoring only; verifies archive size and SHA256 before atomic extraction" ], + "example": "const assets = await starterAssetsDir();", + "importPath": "@threenative/terrain/kit", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export async function starterAssetsDir(): Promise { … }", + "situations": ["resolve terrain kit models and textures for offline baking"], + "summary": "Resolve shared starter assets locally, or download the package's pinned archive on demand.", + "supersedes": [], + "symbol": "starterAssetsDir" + }, + { + "aliases": [], + "constraints": ["Runtime subpath; the game supplies every appearance and collision choice"], + "example": "await addStarterWorld(ctx, follow, spec);", + "importPath": "@threenative/terrain/starter", + "kind": "function", + "overrides": [], "package": "@threenative/terrain", "requires": ["npm i -D @threenative/terrain"], - "signature": "export async function exportWorldGLB(input: IWorldGLBInput): Promise { … }", + "signature": "export async function addStarterWorld { -+ -+ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; -+ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; -+ -+ } ); -+ -+ return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); - - } - -@@ -30412,7 +30421,7 @@ class RenderObject { - * - * @return {string} The geometry cache key. - */ -- getGeometryCacheKey() { -+ getGeometryCacheKey( useMorphTargetIds = true ) { - - const { geometry } = this; - -@@ -30440,12 +30449,14 @@ class RenderObject { - const targets = geometry.morphAttributes[ name ]; - - cacheKey += 'morph-' + name + ','; -+ if ( ! useMorphTargetIds ) cacheKey += targets.length + ',' + geometry.morphTargetsRelative + ','; - - for ( let i = 0, l = targets.length; i < l; i ++ ) { - - const attribute = targets[ i ]; - -- cacheKey += attribute.id + ','; -+ cacheKey += useMorphTargetIds ? attribute.id + ',' : -+ attribute.count + ',' + attribute.itemSize + ','; - - } - -@@ -34249,6 +34260,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34398,7 +34413,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34562,7 +34586,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34572,7 +34605,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34589,6 +34622,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34621,9 +34665,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34801,6 +34970,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40515,7 +40686,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40536,6 +40707,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40557,17 +40730,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40748,6 +40937,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -41054,6 +41245,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41066,6 +41258,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41408,6 +41611,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44676,6 +44880,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44730,8 +44974,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44762,7 +45006,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45831,8 +46075,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45868,9 +46112,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61391,6 +61635,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62384,6 +62640,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76680,6 +76944,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76896,7 +77163,12 @@ class WebGPUTextureUtils { - - } - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && -+ texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; -+ -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76969,6 +77241,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -77009,7 +77297,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -77034,8 +77322,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77058,64 +77346,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -+ let cache = this._depthBuffers.get( depthTexture ); - -- return backend.get( depthTexture ).texture; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -- -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77270,6 +77553,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77343,6 +77763,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79269,7 +79691,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79293,7 +79715,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79835,7 +80257,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80265,7 +80687,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80521,6 +80944,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80543,7 +80968,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80647,7 +81077,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -82145,7 +82575,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82316,7 +82759,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82354,7 +82797,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82871,6 +83314,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82885,7 +83337,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84140,6 +84592,19 @@ class WebGPUBackend extends Backend { - */ - this.gpu = null; - -+ /** -+ * A reference to the adapter this device was requested from. -+ * -+ * The adapter owns the wire instance every operation in flight completes on. Chromium -+ * collects an adapter the page stops referencing and the instance goes with it, so every -+ * operation still running completes with a shutdown the page reads as -+ * 'A valid external Instance reference no longer exists.' and presentation stops for. -+ * -+ * @type {?GPUAdapter} -+ * @default null -+ */ -+ this.adapter = null; -+ - /** - * A reference to the device. - * -@@ -84256,6 +84721,8 @@ class WebGPUBackend extends Backend { - - const adapter = this.gpu !== null ? await this.gpu.requestAdapter( adapterOptions ) : null; - -+ this.adapter = adapter; -+ - if ( adapter === null ) { - - throw new Error( 'THREE.WebGPUBackend: Unable to create WebGPU adapter.' ); -@@ -84481,7 +84948,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84508,6 +84977,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86180,7 +86652,7 @@ class WebGPUBackend extends Backend { - bindGroupLayoutKeys, - utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), - utils.getPrimitiveTopology( object, material ), -- renderObject.getGeometryCacheKey(), -+ renderObject.getGeometryCacheKey( false ), - renderObject.clippingContextCacheKey - ].join(); - -@@ -86248,6 +86720,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index 5c5ef3c06885c724c1c2288295e7e68483ee1f6e..5975e257a43fe139b69c249d8c8c1edfe91e12a0 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -18539,7 +18540,7 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { - // Synchronization of dynamic buffer updates per frame - if ( interleavedMatrix !== null || interleavedColor !== null ) { - -- OnFrameUpdate( () => { -+ OnBeforeFrameUpdate( () => { - - if ( interleavedMatrix !== null ) { - -@@ -18580,12 +18581,28 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { - - const instancedMesh = builder.object; - -- OnObjectUpdate( ( { object } ) => { -+ const updatePrevious = ( object, array ) => { -+ -+ const { previousInstanceMatrix } = _previousInstanceMatrices.get( object ); -+ previousInstanceMatrix.array.set( array ); -+ previousInstanceMatrix.needsUpdate = true; -+ const interleaved = _matrixBuffers.get( previousInstanceMatrix ); -+ if ( interleaved !== undefined ) interleaved.needsUpdate = true; -+ -+ }; - -- const previousInstanceData = _previousInstanceMatrices.get( object ); -+ // Scheduled history must reach the GPU in this draw, before the geometry uploads. -+ OnBeforeObjectUpdate( ( { object } ) => { - - const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ]; -- previousInstanceData.previousInstanceMatrix.array.set( scheduledPrevious ?? matrices.array ); -+ if ( scheduledPrevious !== undefined ) updatePrevious( object, scheduledPrevious ); -+ -+ } ); -+ -+ // Without a scheduler, retain Three's draw-time snapshot for the next draw. -+ OnObjectUpdate( ( { object } ) => { -+ -+ if ( object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ] === undefined ) updatePrevious( object, matrices.array ); - - } ); - -@@ -18844,7 +18861,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve - - } - -- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); -+ // The previous bones and world matrix must use the same frame's bind inverse. -+ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { -+ -+ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; -+ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; -+ -+ } ); -+ -+ return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); - - } - -@@ -30396,7 +30421,7 @@ class RenderObject { - * - * @return {string} The geometry cache key. - */ -- getGeometryCacheKey() { -+ getGeometryCacheKey( useMorphTargetIds = true ) { - - const { geometry } = this; - -@@ -30424,12 +30449,14 @@ class RenderObject { - const targets = geometry.morphAttributes[ name ]; - - cacheKey += 'morph-' + name + ','; -+ if ( ! useMorphTargetIds ) cacheKey += targets.length + ',' + geometry.morphTargetsRelative + ','; - - for ( let i = 0, l = targets.length; i < l; i ++ ) { - - const attribute = targets[ i ]; - -- cacheKey += attribute.id + ','; -+ cacheKey += useMorphTargetIds ? attribute.id + ',' : -+ attribute.count + ',' + attribute.itemSize + ','; - - } - -@@ -34233,6 +34260,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34382,7 +34413,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34546,7 +34586,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34556,7 +34605,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34573,6 +34622,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34605,9 +34665,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34785,6 +34970,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40499,7 +40686,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40520,6 +40707,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40541,17 +40730,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40732,6 +40937,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -41038,6 +41245,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41050,6 +41258,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41392,6 +41611,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44660,6 +44880,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44714,8 +44974,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44746,7 +45006,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45815,8 +46075,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45852,9 +46112,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61375,6 +61635,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62368,6 +62640,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -63161,8 +63441,8 @@ class Renderer { - // attribute scan - because each of those answers for itself, and a step left out here - // cannot: a group a node added after this object's first draw still has to be created, and - // the pipeline reads that group's layout whatever this draw decided to do with it. -- this._geometries.updateForRender( renderObject ); - this._nodes.updateBefore( renderObject ); -+ this._geometries.updateForRender( renderObject ); - this._nodes.updateForRender( renderObject ); - this._bindings.updateForRender( renderObject ); - -@@ -76664,6 +76944,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76880,12 +77163,12 @@ class WebGPUTextureUtils { - - } - -- // Compute-only 3D storage has no attachment or render-pass mipmap consumer. -- // Requesting the unused attachment usage rejects the texture on wgpu-native. -- const computeOnlyStorage3D = dimension === GPUTextureDimension.ThreeD && texture.isStorageTexture === true && -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && - texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! computeOnlyStorage3D ) { -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76958,6 +77241,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -76998,7 +77297,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -77023,8 +77322,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77047,64 +77346,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- } -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -- -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- return depthTextureGPU; -- -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77259,6 +77553,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77332,6 +77763,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79258,7 +79691,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79282,7 +79715,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79824,7 +80257,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80254,7 +80687,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80510,6 +80944,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80532,7 +80968,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80636,7 +81077,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -81391,6 +81832,15 @@ class WebGPUAttributeUtils { - buffer.unmap(); - - bufferData.buffer = buffer; -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ -+ } else if ( bufferData.version < bufferAttribute.version || -+ ( bufferAttribute.usage === DynamicDrawUsage && bufferData.uploadCall !== backend.renderer.info.calls ) ) { -+ -+ // A rebuilt shader can create a fresh attribute wrapper for this existing buffer. -+ // Synchronize its dirty bytes before Attributes records the wrapper as current. -+ this.updateAttribute( attribute ); - - } - -@@ -81411,6 +81861,11 @@ class WebGPUAttributeUtils { - const bufferData = backend.get( bufferAttribute ); - const buffer = backend.get( bufferAttribute ).buffer; - -+ // A fresh wrapper may already have uploaded this shared buffer in the same render or compute call. -+ // Do not repeat that write after its partial ranges have been consumed. -+ if ( bufferData.version === bufferAttribute.version && -+ ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; -+ - let array = bufferAttribute.array; - - const itemSize = bufferData._itemSize; -@@ -81492,6 +81947,9 @@ class WebGPUAttributeUtils { - - } - -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ - } - - /** -@@ -82117,7 +82575,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82288,7 +82759,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82326,7 +82797,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82843,6 +83314,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82857,7 +83337,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84468,7 +84948,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84495,6 +84977,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86167,7 +86652,7 @@ class WebGPUBackend extends Backend { - bindGroupLayoutKeys, - utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), - utils.getPrimitiveTopology( object, material ), -- renderObject.getGeometryCacheKey(), -+ renderObject.getGeometryCacheKey( false ), - renderObject.clippingContextCacheKey - ].join(); - -@@ -86235,6 +86720,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index 7aa62adb9e78ba8f0a67709d3141bf93c67419cc..5975e257a43fe139b69c249d8c8c1edfe91e12a0 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -18539,7 +18540,7 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { - // Synchronization of dynamic buffer updates per frame - if ( interleavedMatrix !== null || interleavedColor !== null ) { - -- OnFrameUpdate( () => { -+ OnBeforeFrameUpdate( () => { - - if ( interleavedMatrix !== null ) { - -@@ -18580,12 +18581,28 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { - - const instancedMesh = builder.object; - -- OnObjectUpdate( ( { object } ) => { -+ const updatePrevious = ( object, array ) => { -+ -+ const { previousInstanceMatrix } = _previousInstanceMatrices.get( object ); -+ previousInstanceMatrix.array.set( array ); -+ previousInstanceMatrix.needsUpdate = true; -+ const interleaved = _matrixBuffers.get( previousInstanceMatrix ); -+ if ( interleaved !== undefined ) interleaved.needsUpdate = true; -+ -+ }; - -- const previousInstanceData = _previousInstanceMatrices.get( object ); -+ // Scheduled history must reach the GPU in this draw, before the geometry uploads. -+ OnBeforeObjectUpdate( ( { object } ) => { - - const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ]; -- previousInstanceData.previousInstanceMatrix.array.set( scheduledPrevious ?? matrices.array ); -+ if ( scheduledPrevious !== undefined ) updatePrevious( object, scheduledPrevious ); -+ -+ } ); -+ -+ // Without a scheduler, retain Three's draw-time snapshot for the next draw. -+ OnObjectUpdate( ( { object } ) => { -+ -+ if ( object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ] === undefined ) updatePrevious( object, matrices.array ); - - } ); - -@@ -18844,7 +18861,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve - - } - -- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); -+ // The previous bones and world matrix must use the same frame's bind inverse. -+ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { -+ -+ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; -+ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; -+ -+ } ); -+ -+ return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); - - } - -@@ -30396,7 +30421,7 @@ class RenderObject { - * - * @return {string} The geometry cache key. - */ -- getGeometryCacheKey() { -+ getGeometryCacheKey( useMorphTargetIds = true ) { - - const { geometry } = this; - -@@ -30424,12 +30449,14 @@ class RenderObject { - const targets = geometry.morphAttributes[ name ]; - - cacheKey += 'morph-' + name + ','; -+ if ( ! useMorphTargetIds ) cacheKey += targets.length + ',' + geometry.morphTargetsRelative + ','; - - for ( let i = 0, l = targets.length; i < l; i ++ ) { - - const attribute = targets[ i ]; - -- cacheKey += attribute.id + ','; -+ cacheKey += useMorphTargetIds ? attribute.id + ',' : -+ attribute.count + ',' + attribute.itemSize + ','; - - } - -@@ -34233,6 +34260,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34382,7 +34413,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34546,7 +34586,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34556,7 +34605,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34573,6 +34622,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34605,9 +34665,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34785,6 +34970,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40499,7 +40686,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40520,6 +40707,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40541,17 +40730,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40732,6 +40937,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -41038,6 +41245,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41050,6 +41258,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41392,6 +41611,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44660,6 +44880,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44714,8 +44974,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44746,7 +45006,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45815,8 +46075,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45852,9 +46112,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61375,6 +61635,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62368,6 +62640,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -63161,8 +63441,8 @@ class Renderer { - // attribute scan - because each of those answers for itself, and a step left out here - // cannot: a group a node added after this object's first draw still has to be created, and - // the pipeline reads that group's layout whatever this draw decided to do with it. -- this._geometries.updateForRender( renderObject ); - this._nodes.updateBefore( renderObject ); -+ this._geometries.updateForRender( renderObject ); - this._nodes.updateForRender( renderObject ); - this._bindings.updateForRender( renderObject ); - -@@ -76664,6 +76944,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76880,7 +77163,12 @@ class WebGPUTextureUtils { - - } - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && -+ texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; -+ -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76953,6 +77241,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -76993,7 +77297,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -77018,8 +77322,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77042,64 +77346,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -- -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- let format, type; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77254,6 +77553,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77327,6 +77763,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79253,7 +79691,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79277,7 +79715,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79819,7 +80257,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80249,7 +80687,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80505,6 +80944,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80527,7 +80968,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80631,7 +81077,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -81386,6 +81832,15 @@ class WebGPUAttributeUtils { - buffer.unmap(); - - bufferData.buffer = buffer; -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ -+ } else if ( bufferData.version < bufferAttribute.version || -+ ( bufferAttribute.usage === DynamicDrawUsage && bufferData.uploadCall !== backend.renderer.info.calls ) ) { -+ -+ // A rebuilt shader can create a fresh attribute wrapper for this existing buffer. -+ // Synchronize its dirty bytes before Attributes records the wrapper as current. -+ this.updateAttribute( attribute ); - - } - -@@ -81406,6 +81861,11 @@ class WebGPUAttributeUtils { - const bufferData = backend.get( bufferAttribute ); - const buffer = backend.get( bufferAttribute ).buffer; - -+ // A fresh wrapper may already have uploaded this shared buffer in the same render or compute call. -+ // Do not repeat that write after its partial ranges have been consumed. -+ if ( bufferData.version === bufferAttribute.version && -+ ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; -+ - let array = bufferAttribute.array; - - const itemSize = bufferData._itemSize; -@@ -81487,6 +81947,9 @@ class WebGPUAttributeUtils { - - } - -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ - } - - /** -@@ -82112,7 +82575,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82283,7 +82759,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82321,7 +82797,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82838,6 +83314,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82852,7 +83337,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84107,6 +84592,19 @@ class WebGPUBackend extends Backend { - */ - this.gpu = null; - -+ /** -+ * A reference to the adapter this device was requested from. -+ * -+ * The adapter owns the wire instance every operation in flight completes on. Chromium -+ * collects an adapter the page stops referencing and the instance goes with it, so every -+ * operation still running completes with a shutdown the page reads as -+ * 'A valid external Instance reference no longer exists.' and presentation stops for. -+ * -+ * @type {?GPUAdapter} -+ * @default null -+ */ -+ this.adapter = null; -+ - /** - * A reference to the device. - * -@@ -84223,6 +84721,8 @@ class WebGPUBackend extends Backend { - - const adapter = this.gpu !== null ? await this.gpu.requestAdapter( adapterOptions ) : null; - -+ this.adapter = adapter; -+ - if ( adapter === null ) { - - throw new Error( 'THREE.WebGPUBackend: Unable to create WebGPU adapter.' ); -@@ -84448,7 +84948,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84475,6 +84977,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86147,7 +86652,7 @@ class WebGPUBackend extends Backend { - bindGroupLayoutKeys, - utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), - utils.getPrimitiveTopology( object, material ), -- renderObject.getGeometryCacheKey(), -+ renderObject.getGeometryCacheKey( false ), - renderObject.clippingContextCacheKey - ].join(); - -@@ -86215,6 +86720,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index bdffb9b4069f7beafa85913b155e92d3e83f12d0..5975e257a43fe139b69c249d8c8c1edfe91e12a0 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -18860,7 +18861,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve - - } - -- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); -+ // The previous bones and world matrix must use the same frame's bind inverse. -+ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { -+ -+ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; -+ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; -+ -+ } ); -+ -+ return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); - - } - -@@ -30412,7 +30421,7 @@ class RenderObject { - * - * @return {string} The geometry cache key. - */ -- getGeometryCacheKey() { -+ getGeometryCacheKey( useMorphTargetIds = true ) { - - const { geometry } = this; - -@@ -30440,12 +30449,14 @@ class RenderObject { - const targets = geometry.morphAttributes[ name ]; - - cacheKey += 'morph-' + name + ','; -+ if ( ! useMorphTargetIds ) cacheKey += targets.length + ',' + geometry.morphTargetsRelative + ','; - - for ( let i = 0, l = targets.length; i < l; i ++ ) { - - const attribute = targets[ i ]; - -- cacheKey += attribute.id + ','; -+ cacheKey += useMorphTargetIds ? attribute.id + ',' : -+ attribute.count + ',' + attribute.itemSize + ','; - - } - -@@ -34249,6 +34260,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34398,7 +34413,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34562,7 +34586,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34572,7 +34605,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34589,6 +34622,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34621,9 +34665,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34801,6 +34970,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40515,7 +40686,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40536,6 +40707,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40557,17 +40730,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40748,6 +40937,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -41054,6 +41245,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41066,6 +41258,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41408,6 +41611,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44676,6 +44880,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44730,8 +44974,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44762,7 +45006,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45831,8 +46075,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45868,9 +46112,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61391,6 +61635,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62384,6 +62640,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76680,6 +76944,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76896,7 +77163,12 @@ class WebGPUTextureUtils { - - } - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && -+ texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; -+ -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76969,6 +77241,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -77009,7 +77297,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -77034,8 +77322,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77058,64 +77346,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -- -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- let format, type; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77270,6 +77553,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77343,6 +77763,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79269,7 +79691,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79293,7 +79715,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79835,7 +80257,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80265,7 +80687,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80521,6 +80944,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80543,7 +80968,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80647,7 +81077,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -81402,6 +81832,15 @@ class WebGPUAttributeUtils { - buffer.unmap(); - - bufferData.buffer = buffer; -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ -+ } else if ( bufferData.version < bufferAttribute.version || -+ ( bufferAttribute.usage === DynamicDrawUsage && bufferData.uploadCall !== backend.renderer.info.calls ) ) { -+ -+ // A rebuilt shader can create a fresh attribute wrapper for this existing buffer. -+ // Synchronize its dirty bytes before Attributes records the wrapper as current. -+ this.updateAttribute( attribute ); - - } - -@@ -81422,6 +81861,11 @@ class WebGPUAttributeUtils { - const bufferData = backend.get( bufferAttribute ); - const buffer = backend.get( bufferAttribute ).buffer; - -+ // A fresh wrapper may already have uploaded this shared buffer in the same render or compute call. -+ // Do not repeat that write after its partial ranges have been consumed. -+ if ( bufferData.version === bufferAttribute.version && -+ ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; -+ - let array = bufferAttribute.array; - - const itemSize = bufferData._itemSize; -@@ -81503,6 +81947,9 @@ class WebGPUAttributeUtils { - - } - -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ - } - - /** -@@ -82128,7 +82575,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82299,7 +82759,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82337,7 +82797,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82854,6 +83314,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82868,7 +83337,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84123,6 +84592,19 @@ class WebGPUBackend extends Backend { - */ - this.gpu = null; - -+ /** -+ * A reference to the adapter this device was requested from. -+ * -+ * The adapter owns the wire instance every operation in flight completes on. Chromium -+ * collects an adapter the page stops referencing and the instance goes with it, so every -+ * operation still running completes with a shutdown the page reads as -+ * 'A valid external Instance reference no longer exists.' and presentation stops for. -+ * -+ * @type {?GPUAdapter} -+ * @default null -+ */ -+ this.adapter = null; -+ - /** - * A reference to the device. - * -@@ -84239,6 +84721,8 @@ class WebGPUBackend extends Backend { - - const adapter = this.gpu !== null ? await this.gpu.requestAdapter( adapterOptions ) : null; - -+ this.adapter = adapter; -+ - if ( adapter === null ) { - - throw new Error( 'THREE.WebGPUBackend: Unable to create WebGPU adapter.' ); -@@ -84464,7 +84948,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84491,6 +84977,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86163,7 +86652,7 @@ class WebGPUBackend extends Backend { - bindGroupLayoutKeys, - utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), - utils.getPrimitiveTopology( object, material ), -- renderObject.getGeometryCacheKey(), -+ renderObject.getGeometryCacheKey( false ), - renderObject.clippingContextCacheKey - ].join(); - -@@ -86231,6 +86720,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index efd0e16719cc7bbef1fbc18c241948e25eb85881..5975e257a43fe139b69c249d8c8c1edfe91e12a0 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -30420,7 +30421,7 @@ class RenderObject { - * - * @return {string} The geometry cache key. - */ -- getGeometryCacheKey() { -+ getGeometryCacheKey( useMorphTargetIds = true ) { - - const { geometry } = this; - -@@ -30448,12 +30449,14 @@ class RenderObject { - const targets = geometry.morphAttributes[ name ]; - - cacheKey += 'morph-' + name + ','; -+ if ( ! useMorphTargetIds ) cacheKey += targets.length + ',' + geometry.morphTargetsRelative + ','; - - for ( let i = 0, l = targets.length; i < l; i ++ ) { - - const attribute = targets[ i ]; - -- cacheKey += attribute.id + ','; -+ cacheKey += useMorphTargetIds ? attribute.id + ',' : -+ attribute.count + ',' + attribute.itemSize + ','; - - } - -@@ -34257,6 +34260,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34406,7 +34413,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34570,7 +34586,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34580,7 +34605,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34597,6 +34622,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34629,9 +34665,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34809,6 +34970,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40523,7 +40686,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40544,6 +40707,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40565,17 +40730,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40756,6 +40937,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -41062,6 +41245,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41074,6 +41258,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41416,6 +41611,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44684,6 +44880,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44738,8 +44974,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44770,7 +45006,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45839,8 +46075,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45876,9 +46112,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61399,6 +61635,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62392,6 +62640,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76688,6 +76944,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76904,7 +77163,12 @@ class WebGPUTextureUtils { - - } - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && -+ texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; -+ -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76977,6 +77241,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -77017,7 +77297,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -77042,8 +77322,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77066,64 +77346,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- } -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -- -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -+ if ( buffer === undefined ) { - -- } -- -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77278,6 +77553,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77351,6 +77763,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79277,7 +79691,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79301,7 +79715,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79843,7 +80257,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80273,7 +80687,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80529,6 +80944,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80551,7 +80968,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80655,7 +81077,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -82153,7 +82575,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82324,7 +82759,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82362,7 +82797,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82879,6 +83314,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82893,7 +83337,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84148,6 +84592,19 @@ class WebGPUBackend extends Backend { - */ - this.gpu = null; - -+ /** -+ * A reference to the adapter this device was requested from. -+ * -+ * The adapter owns the wire instance every operation in flight completes on. Chromium -+ * collects an adapter the page stops referencing and the instance goes with it, so every -+ * operation still running completes with a shutdown the page reads as -+ * 'A valid external Instance reference no longer exists.' and presentation stops for. -+ * -+ * @type {?GPUAdapter} -+ * @default null -+ */ -+ this.adapter = null; -+ - /** - * A reference to the device. - * -@@ -84264,6 +84721,8 @@ class WebGPUBackend extends Backend { - - const adapter = this.gpu !== null ? await this.gpu.requestAdapter( adapterOptions ) : null; - -+ this.adapter = adapter; -+ - if ( adapter === null ) { - - throw new Error( 'THREE.WebGPUBackend: Unable to create WebGPU adapter.' ); -@@ -84489,7 +84948,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84516,6 +84977,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86188,7 +86652,7 @@ class WebGPUBackend extends Backend { - bindGroupLayoutKeys, - utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), - utils.getPrimitiveTopology( object, material ), -- renderObject.getGeometryCacheKey(), -+ renderObject.getGeometryCacheKey( false ), - renderObject.clippingContextCacheKey - ].join(); - -@@ -86256,6 +86720,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index 1245f0da9bb6a0f731b8c75a64490d7c91238700..95669d28b54250a3857dcb7d1b1b0e30dd19e0cb 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -18846,7 +18847,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve - - } - -- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); -+ // The previous bones and world matrix must use the same frame's bind inverse. -+ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { -+ -+ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; -+ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; -+ -+ } ); -+ -+ return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); - - } - -@@ -34205,6 +34214,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34354,7 +34367,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34518,7 +34540,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34528,7 +34559,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34545,6 +34576,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34577,9 +34619,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34757,6 +34924,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40470,7 +40639,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40491,6 +40660,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40512,17 +40683,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40703,6 +40890,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -41009,6 +41198,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41021,6 +41211,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41363,6 +41564,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44631,6 +44833,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44685,8 +44927,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44717,7 +44959,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45786,8 +46028,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45823,9 +46065,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61346,6 +61588,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62339,6 +62593,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76624,6 +76886,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76840,7 +77105,12 @@ class WebGPUTextureUtils { - - } - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && -+ texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; -+ -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76913,6 +77183,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -76953,7 +77239,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -76978,8 +77264,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77002,64 +77288,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -+ let cache = this._depthBuffers.get( depthTexture ); - -- return backend.get( depthTexture ).texture; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -- -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77214,6 +77495,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77287,6 +77705,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79213,7 +79633,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79237,7 +79657,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79779,7 +80199,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80208,7 +80628,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80464,6 +80885,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80486,7 +80909,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80587,7 +81015,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -81342,6 +81770,15 @@ class WebGPUAttributeUtils { - buffer.unmap(); - - bufferData.buffer = buffer; -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ -+ } else if ( bufferData.version < bufferAttribute.version || -+ ( bufferAttribute.usage === DynamicDrawUsage && bufferData.uploadCall !== backend.renderer.info.calls ) ) { -+ -+ // A rebuilt shader can create a fresh attribute wrapper for this existing buffer. -+ // Synchronize its dirty bytes before Attributes records the wrapper as current. -+ this.updateAttribute( attribute ); - - } - -@@ -81362,6 +81799,11 @@ class WebGPUAttributeUtils { - const bufferData = backend.get( bufferAttribute ); - const buffer = backend.get( bufferAttribute ).buffer; - -+ // A fresh wrapper may already have uploaded this shared buffer in the same render or compute call. -+ // Do not repeat that write after its partial ranges have been consumed. -+ if ( bufferData.version === bufferAttribute.version && -+ ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; -+ - let array = bufferAttribute.array; - - const itemSize = bufferData._itemSize; -@@ -81443,6 +81885,9 @@ class WebGPUAttributeUtils { - - } - -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ - } - - /** -@@ -82070,7 +82515,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82241,7 +82699,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82279,7 +82737,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82793,6 +83251,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82807,7 +83274,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84393,7 +84860,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84420,6 +84889,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86156,6 +86628,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index 1b3a6e96291af0077f7f247040e657cf4aa0cb33..95669d28b54250a3857dcb7d1b1b0e30dd19e0cb 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -34213,6 +34214,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34362,7 +34367,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34526,7 +34540,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34536,7 +34559,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34553,6 +34576,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34585,9 +34619,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34765,6 +34924,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40478,7 +40639,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40499,6 +40660,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40520,17 +40683,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40711,6 +40890,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -41017,6 +41198,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41029,6 +41211,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41371,6 +41564,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44639,6 +44833,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44693,8 +44927,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44725,7 +44959,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45794,8 +46028,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45831,9 +46065,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61354,6 +61588,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62347,6 +62593,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76632,6 +76886,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76848,7 +77105,12 @@ class WebGPUTextureUtils { - - } - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && -+ texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; -+ -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76921,6 +77183,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -76961,7 +77239,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -76986,8 +77264,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77010,64 +77288,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -- -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- let format, type; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -- -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77222,6 +77495,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77295,6 +77705,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79221,7 +79633,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79245,7 +79657,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79787,7 +80199,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80216,7 +80628,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80472,6 +80885,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80494,7 +80909,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80595,7 +81015,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -82095,7 +82515,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82266,7 +82699,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82304,7 +82737,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82818,6 +83251,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82832,7 +83274,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84418,7 +84860,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84445,6 +84889,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86181,6 +86628,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index 3d47c1151d563f1e0ae2bb4b92edd057fe0df2b3..95669d28b54250a3857dcb7d1b1b0e30dd19e0cb 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -18539,7 +18540,7 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { - // Synchronization of dynamic buffer updates per frame - if ( interleavedMatrix !== null || interleavedColor !== null ) { - -- OnFrameUpdate( () => { -+ OnBeforeFrameUpdate( () => { - - if ( interleavedMatrix !== null ) { - -@@ -18580,11 +18581,28 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { - - const instancedMesh = builder.object; - -- OnObjectUpdate( ( { object } ) => { -+ const updatePrevious = ( object, array ) => { -+ -+ const { previousInstanceMatrix } = _previousInstanceMatrices.get( object ); -+ previousInstanceMatrix.array.set( array ); -+ previousInstanceMatrix.needsUpdate = true; -+ const interleaved = _matrixBuffers.get( previousInstanceMatrix ); -+ if ( interleaved !== undefined ) interleaved.needsUpdate = true; -+ -+ }; - -- const previousInstanceData = _previousInstanceMatrices.get( object ); -+ // Scheduled history must reach the GPU in this draw, before the geometry uploads. -+ OnBeforeObjectUpdate( ( { object } ) => { - -- previousInstanceData.previousInstanceMatrix.array.set( matrices.array ); -+ const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ]; -+ if ( scheduledPrevious !== undefined ) updatePrevious( object, scheduledPrevious ); -+ -+ } ); -+ -+ // Without a scheduler, retain Three's draw-time snapshot for the next draw. -+ OnObjectUpdate( ( { object } ) => { -+ -+ if ( object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ] === undefined ) updatePrevious( object, matrices.array ); - - } ); - -@@ -18829,7 +18847,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve - - } - -- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); -+ // The previous bones and world matrix must use the same frame's bind inverse. -+ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { -+ -+ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; -+ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; -+ -+ } ); -+ -+ return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); - - } - -@@ -34188,6 +34214,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34337,7 +34367,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34501,7 +34540,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34511,7 +34559,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34528,6 +34576,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34560,9 +34619,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34740,6 +34924,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40453,7 +40639,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40474,6 +40660,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40495,17 +40683,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40686,6 +40890,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -40992,6 +41198,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41004,6 +41211,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41346,6 +41564,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44614,6 +44833,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44668,8 +44927,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44700,7 +44959,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45769,8 +46028,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45806,9 +46065,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61329,6 +61588,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62322,6 +62593,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -63104,8 +63383,8 @@ class Renderer { - // attribute scan - because each of those answers for itself, and a step left out here - // cannot: a group a node added after this object's first draw still has to be created, and - // the pipeline reads that group's layout whatever this draw decided to do with it. -- this._geometries.updateForRender( renderObject ); - this._nodes.updateBefore( renderObject ); -+ this._geometries.updateForRender( renderObject ); - this._nodes.updateForRender( renderObject ); - this._bindings.updateForRender( renderObject ); - -@@ -76607,6 +76886,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76823,12 +77105,12 @@ class WebGPUTextureUtils { - - } - -- // Compute-only 3D storage has no attachment or render-pass mipmap consumer. -- // Requesting the unused attachment usage rejects the texture on wgpu-native. -- const computeOnlyStorage3D = dimension === GPUTextureDimension.ThreeD && texture.isStorageTexture === true && -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && - texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! computeOnlyStorage3D ) { -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76901,6 +77183,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -76941,7 +77239,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -76966,8 +77264,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -76990,64 +77288,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- } -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -- -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- return depthTextureGPU; -- -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77202,6 +77495,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77275,6 +77705,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79201,7 +79633,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79225,7 +79657,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79767,7 +80199,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80196,7 +80628,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80452,6 +80885,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80474,7 +80909,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80575,7 +81015,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -81330,6 +81770,15 @@ class WebGPUAttributeUtils { - buffer.unmap(); - - bufferData.buffer = buffer; -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ -+ } else if ( bufferData.version < bufferAttribute.version || -+ ( bufferAttribute.usage === DynamicDrawUsage && bufferData.uploadCall !== backend.renderer.info.calls ) ) { -+ -+ // A rebuilt shader can create a fresh attribute wrapper for this existing buffer. -+ // Synchronize its dirty bytes before Attributes records the wrapper as current. -+ this.updateAttribute( attribute ); - - } - -@@ -81350,6 +81799,11 @@ class WebGPUAttributeUtils { - const bufferData = backend.get( bufferAttribute ); - const buffer = backend.get( bufferAttribute ).buffer; - -+ // A fresh wrapper may already have uploaded this shared buffer in the same render or compute call. -+ // Do not repeat that write after its partial ranges have been consumed. -+ if ( bufferData.version === bufferAttribute.version && -+ ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; -+ - let array = bufferAttribute.array; - - const itemSize = bufferData._itemSize; -@@ -81431,6 +81885,9 @@ class WebGPUAttributeUtils { - - } - -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ - } - - /** -@@ -82058,7 +82515,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82229,7 +82699,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82267,7 +82737,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82781,6 +83251,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82795,7 +83274,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84381,7 +84860,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84408,6 +84889,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86144,6 +86628,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index 81c24b2e05e5047e1a02410a2056949d39b1a05c..95669d28b54250a3857dcb7d1b1b0e30dd19e0cb 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -18846,7 +18847,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve - - } - -- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); -+ // The previous bones and world matrix must use the same frame's bind inverse. -+ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { -+ -+ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; -+ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; -+ -+ } ); -+ -+ return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); - - } - -@@ -34205,6 +34214,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34354,7 +34367,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34518,7 +34540,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34528,7 +34559,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34545,6 +34576,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34577,9 +34619,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34757,6 +34924,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40470,7 +40639,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40491,6 +40660,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40512,17 +40683,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40703,6 +40890,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -41009,6 +41198,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41021,6 +41211,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41363,6 +41564,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44631,6 +44833,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44685,8 +44927,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44717,7 +44959,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45786,8 +46028,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45823,9 +46065,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61346,6 +61588,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62339,6 +62593,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76624,6 +76886,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76840,7 +77105,12 @@ class WebGPUTextureUtils { - - } - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && -+ texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; -+ -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76913,6 +77183,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -76953,7 +77239,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -76978,8 +77264,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77002,64 +77288,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -- -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- let format, type; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- return depthTextureGPU; -- -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77214,6 +77495,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77287,6 +77705,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79213,7 +79633,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79237,7 +79657,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79779,7 +80199,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80208,7 +80628,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80464,6 +80885,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80486,7 +80909,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80587,7 +81015,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -82087,7 +82515,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82258,7 +82699,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82296,7 +82737,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82810,6 +83251,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82824,7 +83274,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84410,7 +84860,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84437,6 +84889,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86173,6 +86628,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index b8a7b2fd0a2e95911e7e12fd5674592107f012de..95669d28b54250a3857dcb7d1b1b0e30dd19e0cb 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -18539,7 +18540,7 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { - // Synchronization of dynamic buffer updates per frame - if ( interleavedMatrix !== null || interleavedColor !== null ) { - -- OnFrameUpdate( () => { -+ OnBeforeFrameUpdate( () => { - - if ( interleavedMatrix !== null ) { - -@@ -18580,11 +18581,28 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { - - const instancedMesh = builder.object; - -- OnObjectUpdate( ( { object } ) => { -+ const updatePrevious = ( object, array ) => { -+ -+ const { previousInstanceMatrix } = _previousInstanceMatrices.get( object ); -+ previousInstanceMatrix.array.set( array ); -+ previousInstanceMatrix.needsUpdate = true; -+ const interleaved = _matrixBuffers.get( previousInstanceMatrix ); -+ if ( interleaved !== undefined ) interleaved.needsUpdate = true; -+ -+ }; - -- const previousInstanceData = _previousInstanceMatrices.get( object ); -+ // Scheduled history must reach the GPU in this draw, before the geometry uploads. -+ OnBeforeObjectUpdate( ( { object } ) => { - -- previousInstanceData.previousInstanceMatrix.array.set( matrices.array ); -+ const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ]; -+ if ( scheduledPrevious !== undefined ) updatePrevious( object, scheduledPrevious ); -+ -+ } ); -+ -+ // Without a scheduler, retain Three's draw-time snapshot for the next draw. -+ OnObjectUpdate( ( { object } ) => { -+ -+ if ( object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ] === undefined ) updatePrevious( object, matrices.array ); - - } ); - -@@ -18829,7 +18847,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve - - } - -- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); -+ // The previous bones and world matrix must use the same frame's bind inverse. -+ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { -+ -+ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; -+ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; -+ -+ } ); -+ -+ return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); - - } - -@@ -34188,6 +34214,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34337,7 +34367,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34501,7 +34540,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34511,7 +34559,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34528,6 +34576,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34560,9 +34619,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34740,6 +34924,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40453,7 +40639,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40474,6 +40660,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40495,17 +40683,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40686,6 +40890,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -40992,6 +41198,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41004,6 +41211,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41346,6 +41564,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44614,6 +44833,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44668,8 +44927,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44700,7 +44959,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45769,8 +46028,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45806,9 +46065,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61329,6 +61588,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62322,6 +62593,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -63104,8 +63383,8 @@ class Renderer { - // attribute scan - because each of those answers for itself, and a step left out here - // cannot: a group a node added after this object's first draw still has to be created, and - // the pipeline reads that group's layout whatever this draw decided to do with it. -- this._geometries.updateForRender( renderObject ); - this._nodes.updateBefore( renderObject ); -+ this._geometries.updateForRender( renderObject ); - this._nodes.updateForRender( renderObject ); - this._bindings.updateForRender( renderObject ); - -@@ -76607,6 +76886,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76823,7 +77105,12 @@ class WebGPUTextureUtils { - - } - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && -+ texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; -+ -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76896,6 +77183,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -76936,7 +77239,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -76961,8 +77264,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -76985,64 +77288,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -+ let cache = this._depthBuffers.get( depthTexture ); - -- return backend.get( depthTexture ).texture; -- -- } -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -- -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -- -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77197,6 +77495,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77270,6 +77705,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79196,7 +79633,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79220,7 +79657,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79762,7 +80199,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80191,7 +80628,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80447,6 +80885,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80469,7 +80909,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80570,7 +81015,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -81325,6 +81770,15 @@ class WebGPUAttributeUtils { - buffer.unmap(); - - bufferData.buffer = buffer; -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ -+ } else if ( bufferData.version < bufferAttribute.version || -+ ( bufferAttribute.usage === DynamicDrawUsage && bufferData.uploadCall !== backend.renderer.info.calls ) ) { -+ -+ // A rebuilt shader can create a fresh attribute wrapper for this existing buffer. -+ // Synchronize its dirty bytes before Attributes records the wrapper as current. -+ this.updateAttribute( attribute ); - - } - -@@ -81345,6 +81799,11 @@ class WebGPUAttributeUtils { - const bufferData = backend.get( bufferAttribute ); - const buffer = backend.get( bufferAttribute ).buffer; - -+ // A fresh wrapper may already have uploaded this shared buffer in the same render or compute call. -+ // Do not repeat that write after its partial ranges have been consumed. -+ if ( bufferData.version === bufferAttribute.version && -+ ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; -+ - let array = bufferAttribute.array; - - const itemSize = bufferData._itemSize; -@@ -81426,6 +81885,9 @@ class WebGPUAttributeUtils { - - } - -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ - } - - /** -@@ -82053,7 +82515,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82224,7 +82699,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82262,7 +82737,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82776,6 +83251,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82790,7 +83274,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84376,7 +84860,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84403,6 +84889,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86139,6 +86628,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/src/nodes/accessors/Instance.js b/src/nodes/accessors/Instance.js -index ed00e75ba4fd7b49343cfada26eacdef8940a4f4..18e2b7943393fd07b7989b1e3a5231a61ca24187 100644 ---- a/src/nodes/accessors/Instance.js -+++ b/src/nodes/accessors/Instance.js -@@ -1,6 +1,6 @@ - - import { vec3, mat4, Fn } from '../tsl/TSLBase.js'; --import { OnFrameUpdate, OnObjectUpdate } from '../utils/EventNode.js'; -+import { OnBeforeFrameUpdate, OnBeforeObjectUpdate, OnObjectUpdate } from '../utils/EventNode.js'; - import { normalLocal, transformNormal } from './Normal.js'; - import { positionLocal, positionPrevious } from './Position.js'; - import { varyingProperty } from '../core/PropertyNode.js'; -@@ -177,7 +177,7 @@ export const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder - // Synchronization of dynamic buffer updates per frame - if ( interleavedMatrix !== null || interleavedColor !== null ) { - -- OnFrameUpdate( () => { -+ OnBeforeFrameUpdate( () => { - - if ( interleavedMatrix !== null ) { - -@@ -218,12 +218,28 @@ export const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder - - const instancedMesh = builder.object; - -- OnObjectUpdate( ( { object } ) => { -+ const updatePrevious = ( object, array ) => { -+ -+ const { previousInstanceMatrix } = _previousInstanceMatrices.get( object ); -+ previousInstanceMatrix.array.set( array ); -+ previousInstanceMatrix.needsUpdate = true; -+ const interleaved = _matrixBuffers.get( previousInstanceMatrix ); -+ if ( interleaved !== undefined ) interleaved.needsUpdate = true; - -- const previousInstanceData = _previousInstanceMatrices.get( object ); -+ }; -+ -+ // Scheduled history must reach the GPU in this draw, before the geometry uploads. -+ OnBeforeObjectUpdate( ( { object } ) => { - - const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ]; -- previousInstanceData.previousInstanceMatrix.array.set( scheduledPrevious ?? matrices.array ); -+ if ( scheduledPrevious !== undefined ) updatePrevious( object, scheduledPrevious ); -+ -+ } ); -+ -+ // Without a scheduler, retain Three's draw-time snapshot for the next draw. -+ OnObjectUpdate( ( { object } ) => { -+ -+ if ( object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ] === undefined ) updatePrevious( object, matrices.array ); - - } ); - -diff --git a/src/nodes/accessors/Skinning.js b/src/nodes/accessors/Skinning.js -index aaeea633a8ece291be5faf868e312867ddad01b6..76303e765bc29853cfd7f139dd3ec76545b70798 100644 ---- a/src/nodes/accessors/Skinning.js -+++ b/src/nodes/accessors/Skinning.js -@@ -117,7 +117,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve - - } - -- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); -+ // The previous bones and world matrix must use the same frame's bind inverse. -+ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { -+ -+ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; -+ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; -+ -+ } ); -+ -+ return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); - - } - -diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js -index 89b33efb80ca1dcb9db931071dfda62ffdba3526..b1293bfbe2ccf0583711813c625ddd5c5ba78620 100644 ---- a/src/renderers/common/Renderer.js -+++ b/src/renderers/common/Renderer.js -@@ -1937,6 +1937,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -2930,6 +2942,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -3723,8 +3743,8 @@ class Renderer { - // attribute scan - because each of those answers for itself, and a step left out here - // cannot: a group a node added after this object's first draw still has to be created, and - // the pipeline reads that group's layout whatever this draw decided to do with it. -- this._geometries.updateForRender( renderObject ); - this._nodes.updateBefore( renderObject ); -+ this._geometries.updateForRender( renderObject ); - this._nodes.updateForRender( renderObject ); - this._bindings.updateForRender( renderObject ); - -diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js -index 69b8b67fdeec5be24de34d8382beda9020642f77..b86e40b357823cf3919d9fdf3c2554f14bcd9931 100644 ---- a/src/renderers/webgpu/WebGPUBackend.js -+++ b/src/renderers/webgpu/WebGPUBackend.js -@@ -110,6 +110,19 @@ class WebGPUBackend extends Backend { - */ - this.gpu = null; - -+ /** -+ * A reference to the adapter this device was requested from. -+ * -+ * The adapter owns the wire instance every operation in flight completes on. Chromium -+ * collects an adapter the page stops referencing and the instance goes with it, so every -+ * operation still running completes with a shutdown the page reads as -+ * 'A valid external Instance reference no longer exists.' and presentation stops for. -+ * -+ * @type {?GPUAdapter} -+ * @default null -+ */ -+ this.adapter = null; -+ - /** - * A reference to the device. - * -@@ -226,6 +239,8 @@ class WebGPUBackend extends Backend { - - const adapter = this.gpu !== null ? await this.gpu.requestAdapter( adapterOptions ) : null; - -+ this.adapter = adapter; -+ - if ( adapter === null ) { - - throw new Error( 'THREE.WebGPUBackend: Unable to create WebGPU adapter.' ); -@@ -451,7 +466,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -478,6 +495,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -2150,7 +2170,7 @@ class WebGPUBackend extends Backend { - bindGroupLayoutKeys, - utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), - utils.getPrimitiveTopology( object, material ), -- renderObject.getGeometryCacheKey(), -+ renderObject.getGeometryCacheKey( false ), - renderObject.clippingContextCacheKey - ].join(); - -@@ -2218,6 +2238,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/src/renderers/webgpu/utils/WebGPUAttributeUtils.js b/src/renderers/webgpu/utils/WebGPUAttributeUtils.js -index 83bbe189915c8450c4e14108e6a3f4ed98c4b016..16bd72d0c0f1d693481efc98140a0f1b73905149 100644 ---- a/src/renderers/webgpu/utils/WebGPUAttributeUtils.js -+++ b/src/renderers/webgpu/utils/WebGPUAttributeUtils.js -@@ -3,6 +3,7 @@ import { submit } from './WebGPUUtils.js'; - import GPUBufferDescriptor from '../descriptors/GPUBufferDescriptor.js'; - import GPUCommandEncoderDescriptor from '../descriptors/GPUCommandEncoderDescriptor.js'; - -+import { DynamicDrawUsage } from '../../../constants.js'; - import { Float16BufferAttribute } from '../../../core/BufferAttribute.js'; - import { isTypedArray, error } from '../../../utils.js'; - -@@ -174,6 +175,15 @@ class WebGPUAttributeUtils { - buffer.unmap(); - - bufferData.buffer = buffer; -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ -+ } else if ( bufferData.version < bufferAttribute.version || -+ ( bufferAttribute.usage === DynamicDrawUsage && bufferData.uploadCall !== backend.renderer.info.calls ) ) { -+ -+ // A rebuilt shader can create a fresh attribute wrapper for this existing buffer. -+ // Synchronize its dirty bytes before Attributes records the wrapper as current. -+ this.updateAttribute( attribute ); - - } - -@@ -194,6 +204,11 @@ class WebGPUAttributeUtils { - const bufferData = backend.get( bufferAttribute ); - const buffer = backend.get( bufferAttribute ).buffer; - -+ // A fresh wrapper may already have uploaded this shared buffer in the same render or compute call. -+ // Do not repeat that write after its partial ranges have been consumed. -+ if ( bufferData.version === bufferAttribute.version && -+ ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; -+ - let array = bufferAttribute.array; - - const itemSize = bufferData._itemSize; -@@ -275,6 +290,9 @@ class WebGPUAttributeUtils { - - } - -+ bufferData.version = bufferAttribute.version; -+ bufferData.uploadCall = backend.renderer.info.calls; -+ - } - - /** -diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js -index ff8ee2dbace2816105aa7152891c04f2333f33e8..57c1eb62f1e0de063f22e08ec879e31d2a3240b6 100644 ---- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js -+++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js -@@ -41,6 +41,7 @@ import { - Compatibility - } from '../../../constants.js'; - import { CubeTexture } from '../../../textures/CubeTexture.js'; -+import { DepthTexture } from '../../../textures/DepthTexture.js'; - import { Texture } from '../../../textures/Texture.js'; - import { warn, error } from '../../../utils.js'; - -@@ -143,6 +144,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -359,7 +363,12 @@ class WebGPUTextureUtils { - - } - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && -+ texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; -+ -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -432,6 +441,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -472,7 +497,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -497,8 +522,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -521,64 +546,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -- -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- let format, type; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- return depthTextureGPU; -+ if ( buffer === undefined ) { - -- } -- -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -733,6 +753,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -806,6 +963,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index 9166d6f2dd1ac1505fa27b625f1976dff6b8ebc1..5975e257a43fe139b69c249d8c8c1edfe91e12a0 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -30420,7 +30421,7 @@ class RenderObject { - * - * @return {string} The geometry cache key. - */ -- getGeometryCacheKey() { -+ getGeometryCacheKey( useMorphTargetIds = true ) { - - const { geometry } = this; - -@@ -30448,12 +30449,14 @@ class RenderObject { - const targets = geometry.morphAttributes[ name ]; - - cacheKey += 'morph-' + name + ','; -+ if ( ! useMorphTargetIds ) cacheKey += targets.length + ',' + geometry.morphTargetsRelative + ','; - - for ( let i = 0, l = targets.length; i < l; i ++ ) { - - const attribute = targets[ i ]; - -- cacheKey += attribute.id + ','; -+ cacheKey += useMorphTargetIds ? attribute.id + ',' : -+ attribute.count + ',' + attribute.itemSize + ','; - - } - -@@ -34257,6 +34260,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34406,7 +34413,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34570,7 +34586,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34580,7 +34605,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34597,6 +34622,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34629,9 +34665,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34809,6 +34970,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40523,7 +40686,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40544,6 +40707,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40565,17 +40730,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40756,6 +40937,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -41062,6 +41245,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41074,6 +41258,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41416,6 +41611,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44684,6 +44880,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44738,8 +44974,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44770,7 +45006,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45839,8 +46075,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45876,9 +46112,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61399,6 +61635,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62392,6 +62640,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76688,6 +76944,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76904,12 +77163,12 @@ class WebGPUTextureUtils { - - } - -- // Compute-only 3D storage has no attachment or render-pass mipmap consumer. -- // Requesting the unused attachment usage rejects the texture on wgpu-native. -- const computeOnlyStorage3D = dimension === GPUTextureDimension.ThreeD && texture.isStorageTexture === true && -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && - texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! computeOnlyStorage3D ) { -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76982,6 +77241,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -77022,7 +77297,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -77047,8 +77322,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77071,64 +77346,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -- -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- let format, type; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -- -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77283,6 +77553,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77356,6 +77763,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79282,7 +79691,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79306,7 +79715,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79848,7 +80257,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80278,7 +80687,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80534,6 +80944,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80556,7 +80968,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80660,7 +81077,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -82158,7 +82575,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82329,7 +82759,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82367,7 +82797,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82884,6 +83314,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82898,7 +83337,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84509,7 +84948,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84536,6 +84977,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86208,7 +86652,7 @@ class WebGPUBackend extends Backend { - bindGroupLayoutKeys, - utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), - utils.getPrimitiveTopology( object, material ), -- renderObject.getGeometryCacheKey(), -+ renderObject.getGeometryCacheKey( false ), - renderObject.clippingContextCacheKey - ].join(); - -@@ -86276,6 +86720,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index 9e7c7f74be1191b761711bd70ee59cc033e586cb..95669d28b54250a3857dcb7d1b1b0e30dd19e0cb 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -12307,7 +12307,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -@@ -12958,7 +12959,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -@@ -34213,6 +34214,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34362,7 +34367,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34526,7 +34540,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34536,7 +34559,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34553,6 +34576,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34585,9 +34619,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34765,6 +34924,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -40478,7 +40639,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -40499,6 +40660,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -40520,17 +40683,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -40711,6 +40890,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -41017,6 +41198,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -41029,6 +41211,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -41371,6 +41564,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -@@ -44639,6 +44833,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44693,8 +44927,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44725,7 +44959,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45794,8 +46028,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45831,9 +46065,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -61354,6 +61588,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -62347,6 +62593,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76632,6 +76886,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76848,12 +77105,12 @@ class WebGPUTextureUtils { - - } - -- // Compute-only 3D storage has no attachment or render-pass mipmap consumer. -- // Requesting the unused attachment usage rejects the texture on wgpu-native. -- const computeOnlyStorage3D = dimension === GPUTextureDimension.ThreeD && texture.isStorageTexture === true && -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && - texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! computeOnlyStorage3D ) { -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76926,6 +77183,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -76966,7 +77239,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -76991,8 +77264,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77015,64 +77288,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -- -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- let format, type; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -- -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77227,6 +77495,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77300,6 +77705,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -79226,7 +79633,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -79250,7 +79657,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79792,7 +80199,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -80221,7 +80628,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -80477,6 +80885,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -80499,7 +80909,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -80600,7 +81015,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -@@ -82100,7 +82515,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -82271,7 +82699,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -82309,7 +82737,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -@@ -82823,6 +83251,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82837,7 +83274,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84423,7 +84860,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84450,6 +84889,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86186,6 +86628,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/src/nodes/accessors/TextureNode.js b/src/nodes/accessors/TextureNode.js -index f886ed21397ab39d6fdbc58e5ac57729f1871f8a..1a138b1a055d0867e7686531930d4d557e1a20df 100644 ---- a/src/nodes/accessors/TextureNode.js -+++ b/src/nodes/accessors/TextureNode.js -@@ -524,7 +524,7 @@ class TextureNode extends UniformNode { - - } else if ( this.sampler === false ) { - -- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); -+ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); - - } else if ( levelSnippet ) { - -diff --git a/src/nodes/accessors/TextureSizeNode.js b/src/nodes/accessors/TextureSizeNode.js -index d4668281657ef8b1acad761e0d86a0716909eee4..43eccaa01bcb12ea6e02c94232d5ee76bcac0d01 100644 ---- a/src/nodes/accessors/TextureSizeNode.js -+++ b/src/nodes/accessors/TextureSizeNode.js -@@ -57,7 +57,8 @@ class TextureSizeNode extends Node { - const textureProperty = this.textureNode.build( builder, 'property' ); - const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); - -- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); -+ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; -+ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); - - } - -diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js -index cc827a55cb82a6325296dcc5c55a4246f4f70aed..3cdf66cfe0760066bc062bf7ce3f3a8000eb2b0d 100644 ---- a/src/nodes/display/PassNode.js -+++ b/src/nodes/display/PassNode.js -@@ -95,7 +95,7 @@ class PassMultipleTextureNode extends PassTextureNode { - * @param {string} textureName - The output texture name. - * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. - */ -- constructor( passNode, textureName, previousTexture = false ) { -+ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { - - // null is passed to the super call since this class does not - // use an external texture for rendering pass data into. Instead -@@ -116,6 +116,8 @@ class PassMultipleTextureNode extends PassTextureNode { - * @type {boolean} - */ - this.previousTexture = previousTexture; -+ this.isRawTextureNode = rawTexture; -+ if ( rawTexture ) this.setSampler( false ); - - /** - * This flag can be used for type testing. -@@ -137,17 +139,33 @@ class PassMultipleTextureNode extends PassTextureNode { - - } - -+ getUniformHash( builder ) { -+ -+ const hash = super.getUniformHash( builder ); -+ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; -+ -+ } -+ - setup( builder ) { - - this.updateTexture(); - -+ if ( this.isRawTextureNode ) { -+ -+ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); -+ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); -+ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); -+ -+ } -+ - return super.setup( builder ); - - } - - clone() { - -- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); -+ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); - newNode.uvNode = this.uvNode; - newNode.levelNode = this.levelNode; - newNode.biasNode = this.biasNode; -@@ -328,6 +346,8 @@ class PassNode extends TempNode { - * @type {Object} - */ - this._textureNodes = {}; -+ this._rawTextureNodes = {}; -+ this._rawTexturesDisposed = false; - - /** - * A dictionary holding the internal depth nodes. -@@ -634,6 +654,7 @@ class PassNode extends TempNode { - this._previousTextures[ name ] = texture; - - this._textureNodes[ name ].updateTexture(); -+ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); - this._previousTextureNodes[ name ].updateTexture(); - - } -@@ -646,6 +667,17 @@ class PassNode extends TempNode { - * @param {string} [name='output'] - The output name to get the texture node for. - * @return {TextureNode} The texture node. - */ -+ getRawTextureNode( name = 'output' ) { -+ -+ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); -+ let node = this._rawTextureNodes[ name ]; -+ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); -+ node.updateTexture(); -+ return node; -+ -+ } -+ - getTextureNode( name = 'output' ) { - - let textureNode = this._textureNodes[ name ]; -@@ -988,6 +1020,7 @@ class PassNode extends TempNode { - */ - dispose() { - -+ this._rawTexturesDisposed = true; - this.renderTarget.dispose(); - - } -diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js -index 51590d5a23a93365e5cd9fb0360678750270d5bf..b1293bfbe2ccf0583711813c625ddd5c5ba78620 100644 ---- a/src/renderers/common/Renderer.js -+++ b/src/renderers/common/Renderer.js -@@ -1937,6 +1937,18 @@ class Renderer { - - } - -+ /** -+ * Releases an exclusively owned attribute through the backend and memory accounting. -+ * Wait for asynchronous readers before releasing storage shared by compute dispatches. -+ * @param {BufferAttribute} attribute - The owned attribute to release. -+ * @return {?Object} The removed attribute data, or null when absent/uninitialized. -+ */ -+ deleteAttribute( attribute ) { -+ -+ return this._attributes === null ? null : this._attributes.delete( attribute ); -+ -+ } -+ - /** - * Can be used to transfer buffer data from a storage buffer attribute - * from the GPU to the CPU in context of compute shaders. -@@ -2930,6 +2942,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js -index 2acc832a628f43adac49614dad8e593ef058c398..1cbc9b3e02c9832d69a8aadb3a6003392b443c8d 100644 ---- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js -+++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js -@@ -723,7 +723,7 @@ class WGSLNodeBuilder extends NodeBuilder { - * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @return {string} The WGSL snippet. - */ -- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { -+ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { - - if ( levelSnippet === null ) levelSnippet = '0u'; - -@@ -747,7 +747,7 @@ class WGSLNodeBuilder extends NodeBuilder { - const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; - const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; - const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); -- const indexType = ( backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; -+ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; - snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; - - if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -1289,7 +1289,7 @@ class WGSLNodeBuilder extends NodeBuilder { - - // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) - // Also textureGather always need sampler. -- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; -+ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); - - if ( needsSampler ) { - -@@ -1719,7 +1719,8 @@ ${ flowData.code } - - for ( const { name, property, type } of builtins.values() ) { - -- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); -+ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; -+ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); - - } - -@@ -1975,6 +1976,8 @@ ${ flowData.code } - getVaryings( shaderStage ) { - - const snippets = []; -+ const policy = this.context.sampleInterpolation; -+ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; - - if ( shaderStage === 'vertex' ) { - -@@ -1997,7 +2000,12 @@ ${ flowData.code } - - let attributesSnippet = `@location( ${ varyingIndex ++ } )`; - -- if ( varying.interpolationType ) { -+ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { -+ -+ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); -+ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; -+ -+ } else if ( varying.interpolationType ) { - - const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; - -@@ -2101,7 +2109,7 @@ ${ flowData.code } - - const { primarySamples } = backend.utils.getTextureSampleData( texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { - - multisampled = '_multisampled'; - -diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js -index a5da7ca09f746c0b2859ef173df663ccdc851ebc..c479d0a88794b01bca531e7cbfb780cf93fc4721 100644 ---- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js -+++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js -@@ -327,7 +327,20 @@ class WebGPUBindingUtils { - - let resourceGPU; - -- if ( textureData.externalTexture !== undefined ) { -+ if ( binding.textureNode?.isRawTextureNode === true ) { -+ -+ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); -+ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; -+ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); -+ if ( textureData.rawTextureViewSource !== raw ) { -+ -+ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); -+ textureData.rawTextureViewSource = raw; -+ -+ } -+ resourceGPU = textureData.rawTextureView; -+ -+ } else if ( textureData.externalTexture !== undefined ) { - - resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); - -@@ -498,7 +511,7 @@ class WebGPUBindingUtils { - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - -- if ( primarySamples > 1 ) { -+ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { - - texture.multisampled = true; - -@@ -536,7 +549,7 @@ class WebGPUBindingUtils { - - } else if ( type === FloatType ) { - -- if ( this.backend.hasFeature( 'float32-filterable' ) ) { -+ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index 941ec68ef0b8174a633054043edb94d9d3d58f5e..5975e257a43fe139b69c249d8c8c1edfe91e12a0 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -30421,7 +30421,7 @@ class RenderObject { - * - * @return {string} The geometry cache key. - */ -- getGeometryCacheKey() { -+ getGeometryCacheKey( useMorphTargetIds = true ) { - - const { geometry } = this; - -@@ -30449,12 +30449,14 @@ class RenderObject { - const targets = geometry.morphAttributes[ name ]; - - cacheKey += 'morph-' + name + ','; -+ if ( ! useMorphTargetIds ) cacheKey += targets.length + ',' + geometry.morphTargetsRelative + ','; - - for ( let i = 0, l = targets.length; i < l; i ++ ) { - - const attribute = targets[ i ]; - -- cacheKey += attribute.id + ','; -+ cacheKey += useMorphTargetIds ? attribute.id + ',' : -+ attribute.count + ',' + attribute.itemSize + ','; - - } - -@@ -34258,6 +34260,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34407,7 +34413,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34571,7 +34586,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34581,7 +34605,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34598,6 +34622,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34630,9 +34665,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34810,6 +34970,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -44718,6 +44880,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44772,8 +44974,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44804,7 +45006,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45873,8 +46075,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45910,9 +46112,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -62438,6 +62640,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76734,6 +76944,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76950,12 +77163,12 @@ class WebGPUTextureUtils { - - } - -- // Compute-only 3D storage has no attachment or render-pass mipmap consumer. -- // Requesting the unused attachment usage rejects the texture on wgpu-native. -- const computeOnlyStorage3D = dimension === GPUTextureDimension.ThreeD && texture.isStorageTexture === true && -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && - texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! computeOnlyStorage3D ) { -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -77028,6 +77241,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -77068,7 +77297,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -77093,8 +77322,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77117,64 +77346,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -- -- } -- -- // -+ let cache = this._depthBuffers.get( depthTexture ); - -- const depthTextureGPU = backend.get( depthTexture ).texture; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77329,6 +77553,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77402,6 +77763,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -82951,6 +83314,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82965,7 +83337,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84576,7 +84948,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84603,6 +84977,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86275,7 +86652,7 @@ class WebGPUBackend extends Backend { - bindGroupLayoutKeys, - utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), - utils.getPrimitiveTopology( object, material ), -- renderObject.getGeometryCacheKey(), -+ renderObject.getGeometryCacheKey( false ), - renderObject.clippingContextCacheKey - ].join(); - -@@ -86343,6 +86720,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index 57f69c17352b936a9eca118a9801805c95c401b6..95669d28b54250a3857dcb7d1b1b0e30dd19e0cb 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -34214,6 +34214,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34363,7 +34367,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34527,7 +34540,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34537,7 +34559,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34554,6 +34576,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34586,9 +34619,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34766,6 +34924,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -44673,6 +44833,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ - - const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); - -+const _shadowUniforms = /*@__PURE__*/ new WeakMap(); -+ -+/** -+ * Returns the uniform `create` makes for this shadow and name, and makes it only once. -+ * -+ * A filter body runs once per node builder. A uniform that the body makes directly is a new node -+ * for each material, and a new render-group node gives each material its own copy of the shared -+ * render group: a buffer that each material compares and writes on each render call. One node per -+ * shadow lets every material that reads it share one render-group buffer. -+ * -+ * @private -+ * @param {LightShadow} shadow - The shadow that owns the uniform. -+ * @param {string} name - The uniform's name within that shadow. -+ * @param {Function} create - Makes the uniform the first time. -+ * @return {Node} The uniform node. -+ */ -+const getShadowUniform = ( shadow, name, create ) => { -+ -+ let uniforms = _shadowUniforms.get( shadow ); -+ -+ if ( uniforms === undefined ) { -+ -+ uniforms = new Map(); -+ _shadowUniforms.set( shadow, uniforms ); -+ -+ } -+ -+ let node = uniforms.get( name ); -+ -+ if ( node === undefined ) { -+ -+ node = create(); -+ uniforms.set( name, node ); -+ -+ } -+ -+ return node; -+ -+}; -+ - /** - * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map - * with a binary `[0,1]` result. -@@ -44727,8 +44927,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - - }; - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - const radiusScaled = radius.mul( texelSize.x ); -@@ -44759,7 +44959,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, - */ - const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { - -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = vec2( 1 ).div( mapSize ); - -@@ -45828,8 +46028,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) - */ - const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { - -- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); -- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); -+ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); -+ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); - - const texelSize = radius.div( mapSize.x ); - -@@ -45865,9 +46065,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo - const shadowPositionAbs = shadowPosition.abs().toConst(); - const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); - -- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); -- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); -- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); -+ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); -+ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); -+ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); - - const result = float( 1.0 ).toVar(); - -@@ -62393,6 +62593,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76678,6 +76886,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -76894,12 +77105,12 @@ class WebGPUTextureUtils { - - } - -- // Compute-only 3D storage has no attachment or render-pass mipmap consumer. -- // Requesting the unused attachment usage rejects the texture on wgpu-native. -- const computeOnlyStorage3D = dimension === GPUTextureDimension.ThreeD && texture.isStorageTexture === true && -+ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for -+ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. -+ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && - texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; - -- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! computeOnlyStorage3D ) { -+ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - -@@ -76972,6 +77183,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -77012,7 +77239,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -77037,8 +77264,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77061,64 +77288,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -- -- return backend.get( depthTexture ).texture; -- -- } -- -- // -+ let cache = this._depthBuffers.get( depthTexture ); - -- const depthTextureGPU = backend.get( depthTexture ).texture; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77273,6 +77495,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77346,6 +77705,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -82890,6 +83251,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82904,7 +83274,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84490,7 +84860,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84517,6 +84889,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86253,6 +86628,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index ef853fd6f7382a8c7ff66cda43471344aeb20770..5975e257a43fe139b69c249d8c8c1edfe91e12a0 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -30421,7 +30421,7 @@ class RenderObject { - * - * @return {string} The geometry cache key. - */ -- getGeometryCacheKey() { -+ getGeometryCacheKey( useMorphTargetIds = true ) { - - const { geometry } = this; - -@@ -30449,12 +30449,14 @@ class RenderObject { - const targets = geometry.morphAttributes[ name ]; - - cacheKey += 'morph-' + name + ','; -+ if ( ! useMorphTargetIds ) cacheKey += targets.length + ',' + geometry.morphTargetsRelative + ','; - - for ( let i = 0, l = targets.length; i < l; i ++ ) { - - const attribute = targets[ i ]; - -- cacheKey += attribute.id + ','; -+ cacheKey += useMorphTargetIds ? attribute.id + ',' : -+ attribute.count + ',' + attribute.itemSize + ','; - - } - -@@ -34258,6 +34260,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34407,7 +34413,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34571,7 +34586,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34581,7 +34605,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34598,6 +34622,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34630,9 +34665,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34810,6 +34970,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -62478,6 +62640,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76774,6 +76944,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -77068,6 +77241,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -77108,7 +77297,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -77133,8 +77322,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77157,64 +77346,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -+ let cache = this._depthBuffers.get( depthTexture ); - -- return backend.get( depthTexture ).texture; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -- -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77369,6 +77553,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77442,6 +77763,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -82991,6 +83314,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -83005,7 +83337,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84616,7 +84948,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84643,6 +84977,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86315,7 +86652,7 @@ class WebGPUBackend extends Backend { - bindGroupLayoutKeys, - utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), - utils.getPrimitiveTopology( object, material ), -- renderObject.getGeometryCacheKey(), -+ renderObject.getGeometryCacheKey( false ), - renderObject.clippingContextCacheKey - ].join(); - -@@ -86383,6 +86720,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index e7956e565579aa6ec550ca001336d5a0d76c652a..95669d28b54250a3857dcb7d1b1b0e30dd19e0cb 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -34214,6 +34214,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34363,7 +34367,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34527,7 +34540,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34537,7 +34559,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34554,6 +34576,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34586,9 +34619,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34766,6 +34924,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -62433,6 +62593,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -76718,6 +76886,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -77012,6 +77183,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -77052,7 +77239,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -77077,8 +77264,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -77101,64 +77288,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -+ let cache = this._depthBuffers.get( depthTexture ); - -- return backend.get( depthTexture ).texture; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -- -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -- -- return depthTextureGPU; -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- } -+ if ( buffer === undefined ) { - -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -77313,6 +77495,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77386,6 +77705,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -@@ -82930,6 +83251,15 @@ class WebGPUPipelineUtils { - - _renderPipelineDescriptor.reset(); - -+ // Close the scope before yielding so unrelated render commands cannot enter it. -+ // Handle rejection immediately even if the pipeline promise is still pending. -+ const errorScopePromise = device.popErrorScope().catch( ( err ) => { -+ -+ asyncError ??= err; -+ return null; -+ -+ } ); -+ - if ( pipelinePromise !== null ) { - - try { -@@ -82944,7 +83274,7 @@ class WebGPUPipelineUtils { - - } - -- const errorScope = await device.popErrorScope(); -+ const errorScope = await errorScopePromise; - - if ( errorScope !== null || asyncError !== null ) { - -@@ -84530,7 +84860,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84557,6 +84889,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -86293,6 +86628,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js -index 730ce0ccd0f56d70c4a7abe7c514c5077d544d1e..b1293bfbe2ccf0583711813c625ddd5c5ba78620 100644 ---- a/src/renderers/common/Renderer.js -+++ b/src/renderers/common/Renderer.js -@@ -2942,6 +2942,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js -index 927e3a1ad5a4665d77238ffbab4c57a603b86828..b86e40b357823cf3919d9fdf3c2554f14bcd9931 100644 ---- a/src/renderers/webgpu/WebGPUBackend.js -+++ b/src/renderers/webgpu/WebGPUBackend.js -@@ -466,7 +466,9 @@ class WebGPUBackend extends Backend { - - let descriptor = canvasData.descriptor; - -- if ( descriptor === undefined || canvasData.samples !== samples ) { -+ if ( descriptor === undefined || canvasData.samples !== samples || -+ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || -+ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { - - descriptor = new GPURenderPassDescriptor(); - descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -493,6 +495,9 @@ class WebGPUBackend extends Backend { - - canvasData.descriptor = descriptor; - canvasData.samples = samples; -+ canvasData.depth = renderer.depth; -+ canvasData.stencil = renderer.stencil; -+ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - -@@ -2165,7 +2170,7 @@ class WebGPUBackend extends Backend { - bindGroupLayoutKeys, - utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), - utils.getPrimitiveTopology( object, material ), -- renderObject.getGeometryCacheKey(), -+ renderObject.getGeometryCacheKey( false ), - renderObject.clippingContextCacheKey - ].join(); - -@@ -2233,6 +2238,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js -index c46f6ab0e06d93f124c9fc62ece8859529461d5e..57c1eb62f1e0de063f22e08ec879e31d2a3240b6 100644 ---- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js -+++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js -@@ -41,6 +41,7 @@ import { - Compatibility - } from '../../../constants.js'; - import { CubeTexture } from '../../../textures/CubeTexture.js'; -+import { DepthTexture } from '../../../textures/DepthTexture.js'; - import { Texture } from '../../../textures/Texture.js'; - import { warn, error } from '../../../utils.js'; - -@@ -143,6 +144,9 @@ class WebGPUTextureUtils { - */ - this._samplerCache = new Map(); - -+ // Own default-canvas depth variants until resize, explicit disposal or teardown. -+ this._depthBuffers = new Map(); -+ - } - - /** -@@ -437,6 +441,22 @@ class WebGPUTextureUtils { - destroyTexture( texture, isDefaultTexture = false ) { - - const backend = this.backend; -+ -+ const depthBuffers = this._depthBuffers.get( texture ); -+ -+ if ( depthBuffers !== undefined ) { -+ -+ this._depthBuffers.delete( texture ); -+ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); -+ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); -+ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); -+ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; -+ backend.delete( texture ); -+ return; -+ -+ } -+ - const textureData = backend.get( texture ); - - if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -477,7 +497,7 @@ class WebGPUTextureUtils { - const colorTexture = canvasTarget.colorTexture; - const colorTextureData = backend.get( colorTexture ); - -- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { -+ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { - - return colorTextureData.texture; - -@@ -502,8 +522,8 @@ class WebGPUTextureUtils { - - // - -- colorTexture.source.width = width; -- colorTexture.source.height = height; -+ colorTexture.image.width = width; -+ colorTexture.image.height = height; - colorTexture.samples = samples; - - colorTextureData.texture = colorBuffer; -@@ -526,64 +546,59 @@ class WebGPUTextureUtils { - const canvasTarget = backend.renderer.getCanvasTarget(); - const { width, height } = backend.getDrawingBufferSize(); - const samples = backend.renderer.currentSamples; -- -+ const sampleCount = backend.utils.getSampleCount( samples ); - const depthTexture = canvasTarget.depthTexture; -+ const format = stencil ? DepthStencilFormat : DepthFormat; -+ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); - -- if ( depthTexture.width === width && -- depthTexture.height === height && -- depthTexture.samples === samples && -- depthTexture.depth === depth && -- depthTexture.stencil === stencil ) { -+ let cache = this._depthBuffers.get( depthTexture ); - -- return backend.get( depthTexture ).texture; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - -- } -- -- // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -- -- let format, type; -- -- if ( stencil ) { -+ this.destroyTexture( depthTexture ); -+ cache = undefined; - -- format = DepthStencilFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; -+ } - -- } else if ( depth ) { -+ if ( cache === undefined ) { - -- format = DepthFormat; -- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; -+ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; -+ this._depthBuffers.set( depthTexture, cache ); -+ canvasTarget.addEventListener( 'resize', cache.onDispose ); -+ canvasTarget.addEventListener( 'dispose', cache.onDispose ); -+ depthTexture.addEventListener( 'dispose', cache.onDispose ); - - } - -- if ( depthTextureGPU !== undefined ) { -+ // Output and direct overlay passes alternate effective samples 4 and 1. -+ // Retain at most those two buffers for this canvas size and depth format. -+ let buffer = cache.textures.get( sampleCount ); - -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { -+ if ( buffer === undefined ) { - -- return depthTextureGPU; -- -- } -- -- this.destroyTexture( depthTexture ); -+ buffer = new DepthTexture( width, height, type ); -+ buffer.name = 'depthBuffer'; -+ buffer.format = format; -+ buffer.samples = sampleCount; -+ this.createTexture( buffer, { width, height } ); -+ cache.textures.set( sampleCount, buffer ); - - } - -- // recreate -- -+ // Preserve the canonical texture identity and its currently selected GPU data. - depthTexture.name = 'depthBuffer'; - depthTexture.format = format; - depthTexture.type = type; - depthTexture.image.width = width; - depthTexture.image.height = height; - depthTexture.samples = samples; -+ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); - -- this.createTexture( depthTexture, { width, height } ); -- -- return backend.get( depthTexture ).texture; -+ return backend.get( buffer ).texture; - - } - -+ - /** - * Uploads the updated texture data to the GPU. - * -@@ -738,6 +753,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -811,6 +963,8 @@ class WebGPUTextureUtils { - */ - dispose() { - -+ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); -+ - this._samplerCache.clear(); - - } -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index 5975e257a43fe139b69c249d8c8c1edfe91e12a0..ee394e98d798be749776cce1fa3e043fb9951769 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -61436,6 +61436,7 @@ class Renderer { - // - - renderContext.camera = camera; -+ renderContext._tnOutputOwner = scene === this._tnOutputOwner ? scene : null; - this.backend.beginRender( renderContext ); - - // process render lists -@@ -61448,13 +61449,22 @@ class Renderer { - opaque: opaqueObjects - } = renderList; - -+ let renderComplete = false; -+ try { -+ - if ( bundles.length > 0 ) this._renderBundles( bundles, sceneRef, lightsNode ); - if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode ); - if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode ); - -- // finish render pass -+ renderComplete = true; -+ -+ } finally { -+ -+ try { -+ -+ if ( renderComplete || this.backend.get( renderContext )._tnDepth ) this.backend.finishRender( renderContext ); - -- this.backend.finishRender( renderContext ); -+ } finally { - - // restore render tree - -@@ -61469,6 +61479,9 @@ class Renderer { - - this._callDepth --; - -+ } -+ } -+ - if ( frameBufferTarget !== null ) { - - this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel ); -@@ -77246,6 +77259,7 @@ class WebGPUTextureUtils { - - if ( depthBuffers !== undefined ) { - -+ depthBuffers._tnPrivateDepth?.texture.destroy(); - this._depthBuffers.delete( texture ); - depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); - depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -@@ -81831,6 +81845,9 @@ class WebGPUAttributeUtils { - - buffer.unmap(); - -+ bufferData._tnFullTriangle = backend.device.features?.has( 'transient-attachments' ) && attribute.itemSize === 3 && attribute.count === 3 && attribute.normalized === false && array instanceof Float32Array && array.length === 9 && ! attribute.isInterleavedBufferAttribute && ! attribute.isStorageBufferAttribute && [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ].every( ( value, i ) => array[ i ] === value ); -+ bufferData._tnTriangleVersion = bufferAttribute.version; -+ bufferData._tnTriangleBuffer = buffer; - bufferData.buffer = buffer; - bufferData.version = bufferAttribute.version; - bufferData.uploadCall = backend.renderer.info.calls; -@@ -81866,6 +81883,8 @@ class WebGPUAttributeUtils { - if ( bufferData.version === bufferAttribute.version && - ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; - -+ bufferData._tnFullTriangle = false; -+ - let array = bufferAttribute.array; - - const itemSize = bufferData._itemSize; -@@ -82427,6 +82446,7 @@ class WebGPUBindingUtils { - - } - -+ bindingsData._tnBindings = this._tnGroups?.get( bindGroupGPU ); - bindingsData.group = bindGroupGPU; - - } -@@ -82446,6 +82466,7 @@ class WebGPUBindingUtils { - - const updateRanges = binding.updateRanges; - -+ - if ( updateRanges.length === 0 ) { - - device.queue.writeBuffer( -@@ -82499,6 +82520,15 @@ class WebGPUBindingUtils { - - } - -+ // Keep bytes actually sent to the GPU, including partial writes; never certify mutable CPU values. -+ if ( backend.device.features?.has( 'transient-attachments' ) && binding.uniforms?.some( u => u.itemSize === 16 ) ) { -+ -+ const data = backend.get( binding ); -+ const values = new Float32Array( array.buffer, array.byteOffset, array.byteLength / 4 ); -+ if ( updateRanges.length === 0 ) data._tnUniformBytes = values.slice(); -+ else if ( data._tnUniformBytes ) for ( const range of updateRanges ) data._tnUniformBytes.set( values.subarray( range.start, range.start + range.count ), range.start ); -+ -+ } - } - - /** -@@ -82662,6 +82692,12 @@ class WebGPUBindingUtils { - } - - const bindGroupGPU = device.createBindGroup( _bindGroupDescriptor ); -+ if ( device.features?.has( 'transient-attachments' ) ) { -+ -+ this._tnGroups ||= new WeakMap(); -+ this._tnGroups.set( bindGroupGPU, bindGroup.bindings.map( binding => ( { binding, buffer: backend.get( binding ).buffer, uniforms: binding.uniforms?.map( u => ( { name: u.name, offset: u.offset, itemSize: u.itemSize } ) ) } ) ) ); -+ -+ } - - _bindGroupDescriptor.reset(); - -@@ -83263,6 +83299,16 @@ class WebGPUPipelineUtils { - - } - -+ if ( device.features?.has( 'transient-attachments' ) ) { -+ -+ // Capture the actual immutable GPU descriptor and compiled programs, after graph updates. -+ const positionIndex = renderObject.getAttributes().indexOf( geometry.attributes.position ); -+ const positionLayout = vertexBuffers.find( layout => layout.attributes.some( a => a.shaderLocation === positionIndex ) ); -+ const positionSlot = positionLayout?.attributes.find( a => a.shaderLocation === positionIndex ); -+ pipelineData._tnDepthProof = depthStencil.depthWriteEnabled === true && depthStencil.depthCompare === 'less-equal' && ! depthStencil.depthBias && ! depthStencil.depthBiasSlopeScale && ! _renderPipelineDescriptor.multisample.alphaToCoverageEnabled && ( _renderPipelineDescriptor.multisample.mask === undefined || _renderPipelineDescriptor.multisample.mask === 0xffffffff ) && primitiveState.topology === 'triangle-list' && ( primitiveState.cullMode === 'none' || ( primitiveState.cullMode === 'back' && primitiveState.frontFace === 'ccw' ) ) && positionLayout?.arrayStride === 12 && positionLayout.stepMode === 'vertex' && positionSlot?.format === 'float32x3' && positionSlot.offset === 0 ? { material, version: material.version, positionIndex, vertex: backend.get( vertexProgram )._tnShaderCode, fragment: backend.get( fragmentProgram )._tnShaderCode } : null; -+ -+ } -+ - // create pipeline - - device.pushErrorScope( 'validation' ); -@@ -83276,6 +83322,7 @@ class WebGPUPipelineUtils { - if ( promises === null ) { - - pipelineData.pipeline = device.createRenderPipeline( _renderPipelineDescriptor ); -+ if ( pipelineData._tnDepthProof ) pipelineData._tnDepthProof.pipeline = pipelineData.pipeline; - - _renderPipelineDescriptor.reset(); - -@@ -85296,6 +85343,144 @@ class WebGPUBackend extends Backend { - * - * @param {RenderContext} renderContext - The render context. - */ -+ // Only the renderer-owned presentation scene may speculate. Canonical textures stay public. -+ _isFullTransientViewport( context ) { -+ -+ const v = context.viewportValue; -+ return ! context.scissor && ( ! context.viewport || ( v.x === 0 && v.y === 0 && v.width === context.width && v.height === context.height && v.minDepth === 0 && v.maxDepth === 1 ) ); -+ -+ } -+ -+ _beginTransientDepth( context, descriptor ) { -+ -+ const original = descriptor.depthStencilAttachment; -+ if ( ! this.device.features?.has( 'transient-attachments' ) || ! context._tnOutputOwner || context.textures !== null || ! context.depth || context.stencil || context.camera?.isArrayCamera || this.renderer.xr?.isPresenting || this.renderer.reversedDepthBuffer || context.occlusionQueryCount !== 0 || ! this._isFullTransientViewport( context ) || original?.depthLoadOp !== 'clear' || original.depthClearValue !== 1 || original.depthStoreOp !== 'store' ) return descriptor; -+ -+ const samples = this.utils.getSampleCountRenderContext( context ); -+ if ( samples <= 1 ) return descriptor; -+ const canonical = this.renderer.getCanvasTarget().depthTexture; -+ const cache = this.textureUtils._depthBuffers.get( canonical ); -+ const format = this.get( canonical ).format; -+ if ( cache === undefined || ! format ) return descriptor; -+ if ( cache._tnPrivateDepth === undefined ) { -+ -+ const texture = this.device.createTexture( { label: 'tn-private-output-depth', size: { width: context.width, height: context.height }, format, sampleCount: samples, usage: GPUTextureUsage.RENDER_ATTACHMENT, _tnPassLocalMSAA: true } ); -+ cache._tnPrivateDepth = { texture, used: false }; -+ -+ } -+ const source = cache._tnPrivateDepth; -+ const attachment = { ...original, view: source.texture.createView(), depthLoadOp: 'clear', depthClearValue: 1, depthStoreOp: 'discard' }; -+ if ( format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'clear', stencilClearValue: 0, stencilStoreOp: 'discard' } ); -+ this.get( context )._tnDepth = { original: { ...original }, source, cache, canonical, originalGPU: this.get( canonical ).texture, colorGPU: this.get( this.renderer.getCanvasTarget().colorTexture ).texture, active: true, logical: 1 }; -+ return { ...descriptor, colorAttachments: descriptor.colorAttachments.map( a => ( { ...a } ) ), depthStencilAttachment: attachment }; -+ -+ } -+ -+ _finishTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const state = data._tnDepth; -+ if ( ! state?.active ) return; -+ state.active = false; -+ const attachment = { ...state.original, depthLoadOp: 'clear', depthClearValue: state.logical, depthStoreOp: 'store' }; -+ // A combined texture's original stencil is never changed by this depth-only compatibility clear. -+ if ( attachment.stencilLoadOp !== undefined || this.get( state.canonical ).format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'load', stencilStoreOp: 'store', stencilReadOnly: false } ); -+ data.encoder.beginRenderPass( { colorAttachments: [], depthStencilAttachment: attachment } ).end(); -+ data.descriptor.depthStencilAttachment = { ...state.original, depthLoadOp: 'load' }; -+ -+ } -+ -+ _restoreTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ if ( ! data._tnDepth?.active ) return; -+ data.currentPass.end(); -+ this._finishTransientDepth( context ); -+ this._resumeTransientDepth( context ); -+ -+ } -+ -+ _resumeTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const descriptor = { ...data.descriptor, timestampWrites: undefined, colorAttachments: data.descriptor.colorAttachments.map( a => ( { ...a, loadOp: 'load' } ) ) }; -+ data.descriptor = descriptor; -+ data.currentPass = data.encoder.beginRenderPass( descriptor ); -+ data.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; -+ if ( context.viewport ) this.updateViewport( context ); -+ if ( context.scissor ) this.updateScissor( context ); -+ -+ } -+ -+ _releaseTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const state = data._tnDepth; -+ if ( state && ! state.source.used && state.cache._tnPrivateDepth === state.source ) { -+ -+ state.source.texture.destroy(); -+ delete state.cache._tnPrivateDepth; -+ -+ } -+ delete data._tnDepth; -+ -+ } -+ -+ // Separate encoders must observe the pending canonical contents in queue order. -+ _flushTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ if ( ! data._tnDepth ) return false; -+ this._restoreTransientDepth( context ); -+ data.currentPass.end(); -+ submit( this.device, data.encoder.finish() ); -+ this._releaseTransientDepth( context ); -+ data.currentPass = null; -+ return true; -+ -+ } -+ -+ _validateTransientDepth( renderObject, params, vertexBuffers ) { -+ -+ const { object, context, material, pipeline } = renderObject; -+ const proof = this.get( pipeline )._tnDepthProof; -+ const position = object.geometry.attributes.position; -+ const uploaded = position && this.get( position ); -+ if ( object !== context._tnOutputOwner || ! this._isFullTransientViewport( context ) || ! proof || proof.pipeline !== this.get( pipeline ).pipeline || proof.material !== material || proof.version !== material.version || material.vertexNode !== null || material.positionNode !== null || material.depthNode !== null || material.depthTest !== true || material.depthWrite !== true || material.depthFunc !== 3 || material.alphaToCoverage || material.polygonOffset || material.stencilWrite || params.vertexCount !== 3 || params.firstVertex !== 0 || params.instanceCount !== 1 || renderObject.geometry !== object.geometry || renderObject.getIndex() !== null || object.geometry.index !== null || object.isBatchedMesh || object.isInstancedMesh || ! vertexBuffers.includes( position ) || renderObject.getIndirect() !== null || ! uploaded?._tnFullTriangle || uploaded._tnTriangleVersion !== position.version || uploaded._tnTriangleBuffer !== uploaded.buffer || position.array.length !== 9 || ! [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ].every( ( n, i ) => position.array[ i ] === n ) ) return false; -+ if ( proof.vertex !== this.get( pipeline.vertexProgram )._tnShaderCode || proof.fragment !== this.get( pipeline.fragmentProgram )._tnShaderCode ) return false; -+ if ( /\b(discard|frag_depth|sample_mask|clip_distances)\b/.test( proof.vertex + proof.fragment ) ) return false; -+ // Admit only Three's shipped projection chain. Custom code anywhere in the vertex stage falls back. -+ const code = proof.vertex.replace( /\/\/[^\n]*/g, '' ).replace( /\s+/g, '' ); -+ if ( ( code.match( /fn/g ) || [] ).length !== 1 ) return false; -+ const main = /@vertexfnmain\((.*?)\)->VaryingsStruct\{(.*)\}$/.exec( code ); -+ if ( ! main || ! /^(@location\(\d+\)(position:vec3|uv:vec2),?)+$/.test( main[ 1 ] ) || ! main[ 1 ].includes( '@location(' + proof.positionIndex + ')position:vec3' ) ) return false; -+ let body = main[ 2 ]; -+ // UV passthrough affects colour only; no other extra vertex operation is accepted. -+ body = body.replace( /varyings\.(v_uv|nodeVarying\d+)=uv;/g, '' ); -+ const chain = /^modelViewMatrix=\(render\.cameraViewMatrix\*object\.(nodeUniform\d+)\);positionLocal=position;v_positionView=\(modelViewMatrix\*vec4\(positionLocal,1\.0\)\)\.xyz;(VERTEX_nodeVar\d+)=\(render\.cameraProjectionMatrix\*vec4\(v_positionView,1\.0\)\);VERTEX_v_modelViewProjection=\2;varyings\.builtinClipSpace=VERTEX_v_modelViewProjection;returnvaryings;$/; -+ const match = chain.exec( body ); -+ if ( ! match ) return false; -+ const matrices = new Map(); -+ for ( const group of renderObject.getBindings() ) { -+ -+ const groupData = this.get( group ); -+ if ( ! groupData._tnBindings || ! groupData._tnBindings.every( ( b, i ) => b.binding === group.bindings[ i ] && b.buffer === this.get( b.binding ).buffer ) ) return false; -+ for ( const entry of groupData._tnBindings ) { -+ -+ const binding = entry.binding; -+ const snapshot = this.get( binding )._tnUniformBytes; -+ if ( ! snapshot ) continue; -+ for ( const uniform of entry.uniforms || [] ) if ( uniform.itemSize === 16 ) matrices.set( group.name + '.' + uniform.name, Array.from( snapshot.slice( uniform.offset, uniform.offset + 16 ) ) ); -+ -+ } -+ -+ } -+ const identity = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; -+ const projection = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 ]; -+ return [ [ 'render.cameraViewMatrix', identity ], [ 'object.' + match[ 1 ], identity ], [ 'render.cameraProjectionMatrix', projection ] ].every( ( [ name, expected ] ) => matrices.get( name )?.every( ( value, i ) => value === expected[ i ] ) ); -+ -+ } -+ - beginRender( renderContext ) { - - const renderContextData = this.get( renderContext ); -@@ -85448,10 +85633,15 @@ class WebGPUBackend extends Backend { - - // - -+ descriptor = this._beginTransientDepth( renderContext, descriptor ); -+ - _commandEncoderDescriptor.label = 'renderContext_' + renderContext.id; - const encoder = device.createCommandEncoder( _commandEncoderDescriptor ); - _commandEncoderDescriptor.reset(); - -+ renderContextData.descriptor = descriptor; -+ renderContextData.encoder = encoder; -+ - // Layered render targets: prepare bundle encoders for each camera in the array camera. - - if ( this._isRenderCameraDepthArray( renderContext ) === true ) { -@@ -85745,6 +85935,8 @@ class WebGPUBackend extends Backend { - - } - -+ this._finishTransientDepth( renderContext ); -+ - if ( occlusionQueryCount > 0 ) { - - const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results -@@ -85787,7 +85979,15 @@ class WebGPUBackend extends Backend { - - } - -- submit( this.device, renderContextData.encoder.finish() ); -+ try { -+ -+ submit( this.device, renderContextData.encoder.finish() ); -+ -+ } finally { -+ -+ this._releaseTransientDepth( renderContext ); -+ -+ } - - - // -@@ -85881,6 +86081,8 @@ class WebGPUBackend extends Backend { - */ - updateViewport( renderContext ) { - -+ if ( ! this._isFullTransientViewport( renderContext ) ) this._restoreTransientDepth( renderContext ); -+ - const { currentPass } = this.get( renderContext ); - const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; - -@@ -85895,6 +86097,8 @@ class WebGPUBackend extends Backend { - */ - updateScissor( renderContext ) { - -+ this._restoreTransientDepth( renderContext ); -+ - const { currentPass } = this.get( renderContext ); - const { x, y, width, height } = renderContext.scissorValue; - -@@ -85936,6 +86140,9 @@ class WebGPUBackend extends Backend { - */ - clear( color, depth, stencil, renderTargetContext = null ) { - -+ const active = this.renderer._currentRenderContext; -+ const interrupted = active && ( renderTargetContext === active || ( renderTargetContext === null && active.textures === null ) ) && this._flushTransientDepth( active ); -+ - const device = this.device; - const renderer = this.renderer; - -@@ -86064,6 +86271,12 @@ class WebGPUBackend extends Backend { - currentPass.end(); - - submit( device, encoder.finish() ); -+ if ( interrupted ) { -+ -+ this.get( active ).encoder = device.createCommandEncoder(); -+ this._resumeTransientDepth( active ); -+ -+ } - - } - -@@ -86392,6 +86605,12 @@ class WebGPUBackend extends Backend { - } else { - - passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 ); -+ if ( renderContextData._tnDepth?.active ) { -+ -+ renderContextData._tnDepth.logical = 0; -+ renderContextData._tnDepth.source.used = true; -+ -+ } - - } - -@@ -86427,6 +86646,19 @@ class WebGPUBackend extends Backend { - // vertex buffers - - const vertexBuffers = renderObject.getVertexBuffers(); -+ if ( renderContextData._tnDepth?.active ) { -+ -+ if ( this._validateTransientDepth( renderObject, drawParams, vertexBuffers ) ) { -+ -+ // _draw records the constant only after the actual GPU draw is encoded. -+ -+ } else { -+ -+ this._restoreTransientDepth( context ); -+ -+ } -+ -+ } - - if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) { - -@@ -86763,7 +86995,24 @@ class WebGPUBackend extends Backend { - */ - async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { - -- return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); -+ const active = this.renderer._currentRenderContext; -+ const state = active && this.get( active )._tnDepth; -+ const sourceGPU = state && this.get( texture ).texture; -+ const interrupted = state && ( sourceGPU === state.originalGPU || sourceGPU === state.colorGPU ) && this._flushTransientDepth( active ); -+ try { -+ -+ return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); -+ -+ } finally { -+ -+ if ( interrupted ) { -+ -+ this.get( active ).encoder = this.device.createCommandEncoder(); -+ this._resumeTransientDepth( active ); -+ -+ } -+ -+ } - - } - -@@ -86827,6 +87076,8 @@ class WebGPUBackend extends Backend { - _shaderModuleDescriptor.label = program.stage + ( program.name !== '' ? `_${ program.name }` : '' ); - _shaderModuleDescriptor.code = program.code; - -+ programGPU._tnShaderCode = program.code; -+ - programGPU.module = { - module: this.device.createShaderModule( _shaderModuleDescriptor ), - entryPoint: 'main' -@@ -86974,6 +87225,7 @@ class WebGPUBackend extends Backend { - _bufferDescriptor.reset(); - - uniformBufferData.buffer = bufferGPU; -+ if ( this.device.features?.has( 'transient-attachments' ) && uniformBuffer.uniforms?.some( u => u.itemSize === 16 ) ) uniformBufferData._tnUniformBytes = new Float32Array( byteLength / 4 ); - - } - -@@ -87159,6 +87411,10 @@ class WebGPUBackend extends Backend { - */ - copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { - -+ const active = this.renderer._currentRenderContext; -+ const state = active && this.get( active )._tnDepth; -+ const interrupted = state && [ srcTexture, dstTexture ].some( texture => { const gpu = this.get( texture ).texture; return gpu === state.originalGPU || gpu === state.colorGPU; } ) && this._flushTransientDepth( active ); -+ - let dstX = 0; - let dstY = 0; - let dstZ = 0; -@@ -87240,6 +87496,13 @@ class WebGPUBackend extends Backend { - - submit( this.device, encoder.finish() ); - -+ if ( interrupted ) { -+ -+ this.get( active ).encoder = this.device.createCommandEncoder(); -+ this._resumeTransientDepth( active ); -+ -+ } -+ - if ( dstLevel === 0 && dstTexture.generateMipmaps ) { - - this.textureUtils.generateMipmaps( dstTexture ); -@@ -87257,6 +87520,8 @@ class WebGPUBackend extends Backend { - */ - copyFramebufferToTexture( texture, renderContext, rectangle ) { - -+ this._restoreTransientDepth( renderContext ); -+ - const renderContextData = this.get( renderContext ); - - let sourceGPU = null; -@@ -87883,7 +88148,20 @@ class RenderPipeline { - const currentXR = renderer.xr.enabled; - renderer.xr.enabled = false; - -- this._quadMesh.render( renderer ); -+ const previousOwner = renderer._tnOutputOwner; -+ renderer._tnOutputOwner = currentXR === false ? this._quadMesh : null; -+ try { -+ -+ this._quadMesh.render( renderer ); -+ -+ } finally { -+ -+ renderer._tnOutputOwner = previousOwner; -+ renderer.xr.enabled = currentXR; -+ renderer.toneMapping = toneMapping; -+ renderer.outputColorSpace = outputColorSpace; -+ -+ } - - renderer.xr.enabled = currentXR; - -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index 95669d28b54250a3857dcb7d1b1b0e30dd19e0cb..a0fab1c7c37cfaf270d9ce36dd3d91453ae6a44c 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -61389,6 +61389,7 @@ class Renderer { - // - - renderContext.camera = camera; -+ renderContext._tnOutputOwner = scene === this._tnOutputOwner ? scene : null; - this.backend.beginRender( renderContext ); - - // process render lists -@@ -61401,13 +61402,22 @@ class Renderer { - opaque: opaqueObjects - } = renderList; - -+ let renderComplete = false; -+ try { -+ - if ( bundles.length > 0 ) this._renderBundles( bundles, sceneRef, lightsNode ); - if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode ); - if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode ); - -- // finish render pass -+ renderComplete = true; -+ -+ } finally { -+ -+ try { -+ -+ if ( renderComplete || this.backend.get( renderContext )._tnDepth ) this.backend.finishRender( renderContext ); - -- this.backend.finishRender( renderContext ); -+ } finally { - - // restore render tree - -@@ -61422,6 +61432,9 @@ class Renderer { - - this._callDepth --; - -+ } -+ } -+ - if ( frameBufferTarget !== null ) { - - this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel ); -@@ -77188,6 +77201,7 @@ class WebGPUTextureUtils { - - if ( depthBuffers !== undefined ) { - -+ depthBuffers._tnPrivateDepth?.texture.destroy(); - this._depthBuffers.delete( texture ); - depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); - depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -@@ -81769,6 +81783,9 @@ class WebGPUAttributeUtils { - - buffer.unmap(); - -+ bufferData._tnFullTriangle = backend.device.features?.has( 'transient-attachments' ) && attribute.itemSize === 3 && attribute.count === 3 && attribute.normalized === false && array instanceof Float32Array && array.length === 9 && ! attribute.isInterleavedBufferAttribute && ! attribute.isStorageBufferAttribute && [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ].every( ( value, i ) => array[ i ] === value ); -+ bufferData._tnTriangleVersion = bufferAttribute.version; -+ bufferData._tnTriangleBuffer = buffer; - bufferData.buffer = buffer; - bufferData.version = bufferAttribute.version; - bufferData.uploadCall = backend.renderer.info.calls; -@@ -81804,6 +81821,8 @@ class WebGPUAttributeUtils { - if ( bufferData.version === bufferAttribute.version && - ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; - -+ bufferData._tnFullTriangle = false; -+ - let array = bufferAttribute.array; - - const itemSize = bufferData._itemSize; -@@ -82367,6 +82386,7 @@ class WebGPUBindingUtils { - - } - -+ bindingsData._tnBindings = this._tnGroups?.get( bindGroupGPU ); - bindingsData.group = bindGroupGPU; - - } -@@ -82386,6 +82406,7 @@ class WebGPUBindingUtils { - - const updateRanges = binding.updateRanges; - -+ - if ( updateRanges.length === 0 ) { - - device.queue.writeBuffer( -@@ -82439,6 +82460,15 @@ class WebGPUBindingUtils { - - } - -+ // Keep bytes actually sent to the GPU, including partial writes; never certify mutable CPU values. -+ if ( backend.device.features?.has( 'transient-attachments' ) && binding.uniforms?.some( u => u.itemSize === 16 ) ) { -+ -+ const data = backend.get( binding ); -+ const values = new Float32Array( array.buffer, array.byteOffset, array.byteLength / 4 ); -+ if ( updateRanges.length === 0 ) data._tnUniformBytes = values.slice(); -+ else if ( data._tnUniformBytes ) for ( const range of updateRanges ) data._tnUniformBytes.set( values.subarray( range.start, range.start + range.count ), range.start ); -+ -+ } - } - - /** -@@ -82602,6 +82632,12 @@ class WebGPUBindingUtils { - } - - const bindGroupGPU = device.createBindGroup( _bindGroupDescriptor ); -+ if ( device.features?.has( 'transient-attachments' ) ) { -+ -+ this._tnGroups ||= new WeakMap(); -+ this._tnGroups.set( bindGroupGPU, bindGroup.bindings.map( binding => ( { binding, buffer: backend.get( binding ).buffer, uniforms: binding.uniforms?.map( u => ( { name: u.name, offset: u.offset, itemSize: u.itemSize } ) ) } ) ) ); -+ -+ } - - _bindGroupDescriptor.reset(); - -@@ -83200,6 +83236,16 @@ class WebGPUPipelineUtils { - - } - -+ if ( device.features?.has( 'transient-attachments' ) ) { -+ -+ // Capture the actual immutable GPU descriptor and compiled programs, after graph updates. -+ const positionIndex = renderObject.getAttributes().indexOf( geometry.attributes.position ); -+ const positionLayout = vertexBuffers.find( layout => layout.attributes.some( a => a.shaderLocation === positionIndex ) ); -+ const positionSlot = positionLayout?.attributes.find( a => a.shaderLocation === positionIndex ); -+ pipelineData._tnDepthProof = depthStencil.depthWriteEnabled === true && depthStencil.depthCompare === 'less-equal' && ! depthStencil.depthBias && ! depthStencil.depthBiasSlopeScale && ! _renderPipelineDescriptor.multisample.alphaToCoverageEnabled && ( _renderPipelineDescriptor.multisample.mask === undefined || _renderPipelineDescriptor.multisample.mask === 0xffffffff ) && primitiveState.topology === 'triangle-list' && ( primitiveState.cullMode === 'none' || ( primitiveState.cullMode === 'back' && primitiveState.frontFace === 'ccw' ) ) && positionLayout?.arrayStride === 12 && positionLayout.stepMode === 'vertex' && positionSlot?.format === 'float32x3' && positionSlot.offset === 0 ? { material, version: material.version, positionIndex, vertex: backend.get( vertexProgram )._tnShaderCode, fragment: backend.get( fragmentProgram )._tnShaderCode } : null; -+ -+ } -+ - // create pipeline - - device.pushErrorScope( 'validation' ); -@@ -83213,6 +83259,7 @@ class WebGPUPipelineUtils { - if ( promises === null ) { - - pipelineData.pipeline = device.createRenderPipeline( _renderPipelineDescriptor ); -+ if ( pipelineData._tnDepthProof ) pipelineData._tnDepthProof.pipeline = pipelineData.pipeline; - - _renderPipelineDescriptor.reset(); - -@@ -85208,6 +85255,144 @@ class WebGPUBackend extends Backend { - * - * @param {RenderContext} renderContext - The render context. - */ -+ // Only the renderer-owned presentation scene may speculate. Canonical textures stay public. -+ _isFullTransientViewport( context ) { -+ -+ const v = context.viewportValue; -+ return ! context.scissor && ( ! context.viewport || ( v.x === 0 && v.y === 0 && v.width === context.width && v.height === context.height && v.minDepth === 0 && v.maxDepth === 1 ) ); -+ -+ } -+ -+ _beginTransientDepth( context, descriptor ) { -+ -+ const original = descriptor.depthStencilAttachment; -+ if ( ! this.device.features?.has( 'transient-attachments' ) || ! context._tnOutputOwner || context.textures !== null || ! context.depth || context.stencil || context.camera?.isArrayCamera || this.renderer.xr?.isPresenting || this.renderer.reversedDepthBuffer || context.occlusionQueryCount !== 0 || ! this._isFullTransientViewport( context ) || original?.depthLoadOp !== 'clear' || original.depthClearValue !== 1 || original.depthStoreOp !== 'store' ) return descriptor; -+ -+ const samples = this.utils.getSampleCountRenderContext( context ); -+ if ( samples <= 1 ) return descriptor; -+ const canonical = this.renderer.getCanvasTarget().depthTexture; -+ const cache = this.textureUtils._depthBuffers.get( canonical ); -+ const format = this.get( canonical ).format; -+ if ( cache === undefined || ! format ) return descriptor; -+ if ( cache._tnPrivateDepth === undefined ) { -+ -+ const texture = this.device.createTexture( { label: 'tn-private-output-depth', size: { width: context.width, height: context.height }, format, sampleCount: samples, usage: GPUTextureUsage.RENDER_ATTACHMENT, _tnPassLocalMSAA: true } ); -+ cache._tnPrivateDepth = { texture, used: false }; -+ -+ } -+ const source = cache._tnPrivateDepth; -+ const attachment = { ...original, view: source.texture.createView(), depthLoadOp: 'clear', depthClearValue: 1, depthStoreOp: 'discard' }; -+ if ( format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'clear', stencilClearValue: 0, stencilStoreOp: 'discard' } ); -+ this.get( context )._tnDepth = { original: { ...original }, source, cache, canonical, originalGPU: this.get( canonical ).texture, colorGPU: this.get( this.renderer.getCanvasTarget().colorTexture ).texture, active: true, logical: 1 }; -+ return { ...descriptor, colorAttachments: descriptor.colorAttachments.map( a => ( { ...a } ) ), depthStencilAttachment: attachment }; -+ -+ } -+ -+ _finishTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const state = data._tnDepth; -+ if ( ! state?.active ) return; -+ state.active = false; -+ const attachment = { ...state.original, depthLoadOp: 'clear', depthClearValue: state.logical, depthStoreOp: 'store' }; -+ // A combined texture's original stencil is never changed by this depth-only compatibility clear. -+ if ( attachment.stencilLoadOp !== undefined || this.get( state.canonical ).format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'load', stencilStoreOp: 'store', stencilReadOnly: false } ); -+ data.encoder.beginRenderPass( { colorAttachments: [], depthStencilAttachment: attachment } ).end(); -+ data.descriptor.depthStencilAttachment = { ...state.original, depthLoadOp: 'load' }; -+ -+ } -+ -+ _restoreTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ if ( ! data._tnDepth?.active ) return; -+ data.currentPass.end(); -+ this._finishTransientDepth( context ); -+ this._resumeTransientDepth( context ); -+ -+ } -+ -+ _resumeTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const descriptor = { ...data.descriptor, timestampWrites: undefined, colorAttachments: data.descriptor.colorAttachments.map( a => ( { ...a, loadOp: 'load' } ) ) }; -+ data.descriptor = descriptor; -+ data.currentPass = data.encoder.beginRenderPass( descriptor ); -+ data.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; -+ if ( context.viewport ) this.updateViewport( context ); -+ if ( context.scissor ) this.updateScissor( context ); -+ -+ } -+ -+ _releaseTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const state = data._tnDepth; -+ if ( state && ! state.source.used && state.cache._tnPrivateDepth === state.source ) { -+ -+ state.source.texture.destroy(); -+ delete state.cache._tnPrivateDepth; -+ -+ } -+ delete data._tnDepth; -+ -+ } -+ -+ // Separate encoders must observe the pending canonical contents in queue order. -+ _flushTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ if ( ! data._tnDepth ) return false; -+ this._restoreTransientDepth( context ); -+ data.currentPass.end(); -+ submit( this.device, data.encoder.finish() ); -+ this._releaseTransientDepth( context ); -+ data.currentPass = null; -+ return true; -+ -+ } -+ -+ _validateTransientDepth( renderObject, params, vertexBuffers ) { -+ -+ const { object, context, material, pipeline } = renderObject; -+ const proof = this.get( pipeline )._tnDepthProof; -+ const position = object.geometry.attributes.position; -+ const uploaded = position && this.get( position ); -+ if ( object !== context._tnOutputOwner || ! this._isFullTransientViewport( context ) || ! proof || proof.pipeline !== this.get( pipeline ).pipeline || proof.material !== material || proof.version !== material.version || material.vertexNode !== null || material.positionNode !== null || material.depthNode !== null || material.depthTest !== true || material.depthWrite !== true || material.depthFunc !== 3 || material.alphaToCoverage || material.polygonOffset || material.stencilWrite || params.vertexCount !== 3 || params.firstVertex !== 0 || params.instanceCount !== 1 || renderObject.geometry !== object.geometry || renderObject.getIndex() !== null || object.geometry.index !== null || object.isBatchedMesh || object.isInstancedMesh || ! vertexBuffers.includes( position ) || renderObject.getIndirect() !== null || ! uploaded?._tnFullTriangle || uploaded._tnTriangleVersion !== position.version || uploaded._tnTriangleBuffer !== uploaded.buffer || position.array.length !== 9 || ! [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ].every( ( n, i ) => position.array[ i ] === n ) ) return false; -+ if ( proof.vertex !== this.get( pipeline.vertexProgram )._tnShaderCode || proof.fragment !== this.get( pipeline.fragmentProgram )._tnShaderCode ) return false; -+ if ( /\b(discard|frag_depth|sample_mask|clip_distances)\b/.test( proof.vertex + proof.fragment ) ) return false; -+ // Admit only Three's shipped projection chain. Custom code anywhere in the vertex stage falls back. -+ const code = proof.vertex.replace( /\/\/[^\n]*/g, '' ).replace( /\s+/g, '' ); -+ if ( ( code.match( /fn/g ) || [] ).length !== 1 ) return false; -+ const main = /@vertexfnmain\((.*?)\)->VaryingsStruct\{(.*)\}$/.exec( code ); -+ if ( ! main || ! /^(@location\(\d+\)(position:vec3|uv:vec2),?)+$/.test( main[ 1 ] ) || ! main[ 1 ].includes( '@location(' + proof.positionIndex + ')position:vec3' ) ) return false; -+ let body = main[ 2 ]; -+ // UV passthrough affects colour only; no other extra vertex operation is accepted. -+ body = body.replace( /varyings\.(v_uv|nodeVarying\d+)=uv;/g, '' ); -+ const chain = /^modelViewMatrix=\(render\.cameraViewMatrix\*object\.(nodeUniform\d+)\);positionLocal=position;v_positionView=\(modelViewMatrix\*vec4\(positionLocal,1\.0\)\)\.xyz;(VERTEX_nodeVar\d+)=\(render\.cameraProjectionMatrix\*vec4\(v_positionView,1\.0\)\);VERTEX_v_modelViewProjection=\2;varyings\.builtinClipSpace=VERTEX_v_modelViewProjection;returnvaryings;$/; -+ const match = chain.exec( body ); -+ if ( ! match ) return false; -+ const matrices = new Map(); -+ for ( const group of renderObject.getBindings() ) { -+ -+ const groupData = this.get( group ); -+ if ( ! groupData._tnBindings || ! groupData._tnBindings.every( ( b, i ) => b.binding === group.bindings[ i ] && b.buffer === this.get( b.binding ).buffer ) ) return false; -+ for ( const entry of groupData._tnBindings ) { -+ -+ const binding = entry.binding; -+ const snapshot = this.get( binding )._tnUniformBytes; -+ if ( ! snapshot ) continue; -+ for ( const uniform of entry.uniforms || [] ) if ( uniform.itemSize === 16 ) matrices.set( group.name + '.' + uniform.name, Array.from( snapshot.slice( uniform.offset, uniform.offset + 16 ) ) ); -+ -+ } -+ -+ } -+ const identity = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; -+ const projection = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 ]; -+ return [ [ 'render.cameraViewMatrix', identity ], [ 'object.' + match[ 1 ], identity ], [ 'render.cameraProjectionMatrix', projection ] ].every( ( [ name, expected ] ) => matrices.get( name )?.every( ( value, i ) => value === expected[ i ] ) ); -+ -+ } -+ - beginRender( renderContext ) { - - const renderContextData = this.get( renderContext ); -@@ -85360,10 +85545,15 @@ class WebGPUBackend extends Backend { - - // - -+ descriptor = this._beginTransientDepth( renderContext, descriptor ); -+ - _commandEncoderDescriptor.label = 'renderContext_' + renderContext.id; - const encoder = device.createCommandEncoder( _commandEncoderDescriptor ); - _commandEncoderDescriptor.reset(); - -+ renderContextData.descriptor = descriptor; -+ renderContextData.encoder = encoder; -+ - // Layered render targets: prepare bundle encoders for each camera in the array camera. - - if ( this._isRenderCameraDepthArray( renderContext ) === true ) { -@@ -85657,6 +85847,8 @@ class WebGPUBackend extends Backend { - - } - -+ this._finishTransientDepth( renderContext ); -+ - if ( occlusionQueryCount > 0 ) { - - const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results -@@ -85699,7 +85891,15 @@ class WebGPUBackend extends Backend { - - } - -- submit( this.device, renderContextData.encoder.finish() ); -+ try { -+ -+ submit( this.device, renderContextData.encoder.finish() ); -+ -+ } finally { -+ -+ this._releaseTransientDepth( renderContext ); -+ -+ } - - - // -@@ -85793,6 +85993,8 @@ class WebGPUBackend extends Backend { - */ - updateViewport( renderContext ) { - -+ if ( ! this._isFullTransientViewport( renderContext ) ) this._restoreTransientDepth( renderContext ); -+ - const { currentPass } = this.get( renderContext ); - const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; - -@@ -85807,6 +86009,8 @@ class WebGPUBackend extends Backend { - */ - updateScissor( renderContext ) { - -+ this._restoreTransientDepth( renderContext ); -+ - const { currentPass } = this.get( renderContext ); - const { x, y, width, height } = renderContext.scissorValue; - -@@ -85848,6 +86052,9 @@ class WebGPUBackend extends Backend { - */ - clear( color, depth, stencil, renderTargetContext = null ) { - -+ const active = this.renderer._currentRenderContext; -+ const interrupted = active && ( renderTargetContext === active || ( renderTargetContext === null && active.textures === null ) ) && this._flushTransientDepth( active ); -+ - const device = this.device; - const renderer = this.renderer; - -@@ -85976,6 +86183,12 @@ class WebGPUBackend extends Backend { - currentPass.end(); - - submit( device, encoder.finish() ); -+ if ( interrupted ) { -+ -+ this.get( active ).encoder = device.createCommandEncoder(); -+ this._resumeTransientDepth( active ); -+ -+ } - - } - -@@ -86305,6 +86518,12 @@ class WebGPUBackend extends Backend { - } else { - - passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 ); -+ if ( renderContextData._tnDepth?.active ) { -+ -+ renderContextData._tnDepth.logical = 0; -+ renderContextData._tnDepth.source.used = true; -+ -+ } - - } - -@@ -86340,6 +86559,19 @@ class WebGPUBackend extends Backend { - // vertex buffers - - const vertexBuffers = renderObject.getVertexBuffers(); -+ if ( renderContextData._tnDepth?.active ) { -+ -+ if ( this._validateTransientDepth( renderObject, drawParams, vertexBuffers ) ) { -+ -+ // _draw records the constant only after the actual GPU draw is encoded. -+ -+ } else { -+ -+ this._restoreTransientDepth( context ); -+ -+ } -+ -+ } - - if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) { - -@@ -86671,7 +86903,24 @@ class WebGPUBackend extends Backend { - */ - async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { - -- return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); -+ const active = this.renderer._currentRenderContext; -+ const state = active && this.get( active )._tnDepth; -+ const sourceGPU = state && this.get( texture ).texture; -+ const interrupted = state && ( sourceGPU === state.originalGPU || sourceGPU === state.colorGPU ) && this._flushTransientDepth( active ); -+ try { -+ -+ return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); -+ -+ } finally { -+ -+ if ( interrupted ) { -+ -+ this.get( active ).encoder = this.device.createCommandEncoder(); -+ this._resumeTransientDepth( active ); -+ -+ } -+ -+ } - - } - -@@ -86735,6 +86984,8 @@ class WebGPUBackend extends Backend { - _shaderModuleDescriptor.label = program.stage + ( program.name !== '' ? `_${ program.name }` : '' ); - _shaderModuleDescriptor.code = program.code; - -+ programGPU._tnShaderCode = program.code; -+ - programGPU.module = { - module: this.device.createShaderModule( _shaderModuleDescriptor ), - entryPoint: 'main' -@@ -86882,6 +87133,7 @@ class WebGPUBackend extends Backend { - _bufferDescriptor.reset(); - - uniformBufferData.buffer = bufferGPU; -+ if ( this.device.features?.has( 'transient-attachments' ) && uniformBuffer.uniforms?.some( u => u.itemSize === 16 ) ) uniformBufferData._tnUniformBytes = new Float32Array( byteLength / 4 ); - - } - -@@ -87067,6 +87319,10 @@ class WebGPUBackend extends Backend { - */ - copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { - -+ const active = this.renderer._currentRenderContext; -+ const state = active && this.get( active )._tnDepth; -+ const interrupted = state && [ srcTexture, dstTexture ].some( texture => { const gpu = this.get( texture ).texture; return gpu === state.originalGPU || gpu === state.colorGPU; } ) && this._flushTransientDepth( active ); -+ - let dstX = 0; - let dstY = 0; - let dstZ = 0; -@@ -87148,6 +87404,13 @@ class WebGPUBackend extends Backend { - - submit( this.device, encoder.finish() ); - -+ if ( interrupted ) { -+ -+ this.get( active ).encoder = this.device.createCommandEncoder(); -+ this._resumeTransientDepth( active ); -+ -+ } -+ - if ( dstLevel === 0 && dstTexture.generateMipmaps ) { - - this.textureUtils.generateMipmaps( dstTexture ); -@@ -87165,6 +87428,8 @@ class WebGPUBackend extends Backend { - */ - copyFramebufferToTexture( texture, renderContext, rectangle ) { - -+ this._restoreTransientDepth( renderContext ); -+ - const renderContextData = this.get( renderContext ); - - let sourceGPU = null; -@@ -87738,7 +88003,20 @@ class RenderPipeline { - const currentXR = renderer.xr.enabled; - renderer.xr.enabled = false; - -- this._quadMesh.render( renderer ); -+ const previousOwner = renderer._tnOutputOwner; -+ renderer._tnOutputOwner = currentXR === false ? this._quadMesh : null; -+ try { -+ -+ this._quadMesh.render( renderer ); -+ -+ } finally { -+ -+ renderer._tnOutputOwner = previousOwner; -+ renderer.xr.enabled = currentXR; -+ renderer.toneMapping = toneMapping; -+ renderer.outputColorSpace = outputColorSpace; -+ -+ } - - renderer.xr.enabled = currentXR; - -diff --git a/src/renderers/common/RenderPipeline.js b/src/renderers/common/RenderPipeline.js -index ab3085cfdca456ffb3de6945d7e7f693e4ed33ae..94e54740e13088a9fa40ccfd30000232df7c2eea 100644 ---- a/src/renderers/common/RenderPipeline.js -+++ b/src/renderers/common/RenderPipeline.js -@@ -137,7 +137,20 @@ class RenderPipeline { - const currentXR = renderer.xr.enabled; - renderer.xr.enabled = false; - -- this._quadMesh.render( renderer ); -+ const previousOwner = renderer._tnOutputOwner; -+ renderer._tnOutputOwner = currentXR === false ? this._quadMesh : null; -+ try { -+ -+ this._quadMesh.render( renderer ); -+ -+ } finally { -+ -+ renderer._tnOutputOwner = previousOwner; -+ renderer.xr.enabled = currentXR; -+ renderer.toneMapping = toneMapping; -+ renderer.outputColorSpace = outputColorSpace; -+ -+ } - - renderer.xr.enabled = currentXR; - -diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js -index b1293bfbe2ccf0583711813c625ddd5c5ba78620..769c909b0a63708dea7d22efc5379c39bdf57d92 100644 ---- a/src/renderers/common/Renderer.js -+++ b/src/renderers/common/Renderer.js -@@ -1738,6 +1738,7 @@ class Renderer { - // - - renderContext.camera = camera; -+ renderContext._tnOutputOwner = scene === this._tnOutputOwner ? scene : null; - this.backend.beginRender( renderContext ); - - // process render lists -@@ -1750,13 +1751,22 @@ class Renderer { - opaque: opaqueObjects - } = renderList; - -+ let renderComplete = false; -+ try { -+ - if ( bundles.length > 0 ) this._renderBundles( bundles, sceneRef, lightsNode ); - if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode ); - if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode ); - -- // finish render pass -+ renderComplete = true; -+ -+ } finally { -+ -+ try { - -- this.backend.finishRender( renderContext ); -+ if ( renderComplete || this.backend.get( renderContext )._tnDepth ) this.backend.finishRender( renderContext ); -+ -+ } finally { - - // restore render tree - -@@ -1771,6 +1781,9 @@ class Renderer { - - this._callDepth --; - -+ } -+ } -+ - if ( frameBufferTarget !== null ) { - - this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel ); -diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js -index b86e40b357823cf3919d9fdf3c2554f14bcd9931..94b48d64ed8f2e57f00cb5fd5bd0112da085aeae 100644 ---- a/src/renderers/webgpu/WebGPUBackend.js -+++ b/src/renderers/webgpu/WebGPUBackend.js -@@ -814,6 +814,144 @@ class WebGPUBackend extends Backend { - * - * @param {RenderContext} renderContext - The render context. - */ -+ // Only the renderer-owned presentation scene may speculate. Canonical textures stay public. -+ _isFullTransientViewport( context ) { -+ -+ const v = context.viewportValue; -+ return ! context.scissor && ( ! context.viewport || ( v.x === 0 && v.y === 0 && v.width === context.width && v.height === context.height && v.minDepth === 0 && v.maxDepth === 1 ) ); -+ -+ } -+ -+ _beginTransientDepth( context, descriptor ) { -+ -+ const original = descriptor.depthStencilAttachment; -+ if ( ! this.device.features?.has( 'transient-attachments' ) || ! context._tnOutputOwner || context.textures !== null || ! context.depth || context.stencil || context.camera?.isArrayCamera || this.renderer.xr?.isPresenting || this.renderer.reversedDepthBuffer || context.occlusionQueryCount !== 0 || ! this._isFullTransientViewport( context ) || original?.depthLoadOp !== 'clear' || original.depthClearValue !== 1 || original.depthStoreOp !== 'store' ) return descriptor; -+ -+ const samples = this.utils.getSampleCountRenderContext( context ); -+ if ( samples <= 1 ) return descriptor; -+ const canonical = this.renderer.getCanvasTarget().depthTexture; -+ const cache = this.textureUtils._depthBuffers.get( canonical ); -+ const format = this.get( canonical ).format; -+ if ( cache === undefined || ! format ) return descriptor; -+ if ( cache._tnPrivateDepth === undefined ) { -+ -+ const texture = this.device.createTexture( { label: 'tn-private-output-depth', size: { width: context.width, height: context.height }, format, sampleCount: samples, usage: GPUTextureUsage.RENDER_ATTACHMENT, _tnPassLocalMSAA: true } ); -+ cache._tnPrivateDepth = { texture, used: false }; -+ -+ } -+ const source = cache._tnPrivateDepth; -+ const attachment = { ...original, view: source.texture.createView(), depthLoadOp: 'clear', depthClearValue: 1, depthStoreOp: 'discard' }; -+ if ( format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'clear', stencilClearValue: 0, stencilStoreOp: 'discard' } ); -+ this.get( context )._tnDepth = { original: { ...original }, source, cache, canonical, originalGPU: this.get( canonical ).texture, colorGPU: this.get( this.renderer.getCanvasTarget().colorTexture ).texture, active: true, logical: 1 }; -+ return { ...descriptor, colorAttachments: descriptor.colorAttachments.map( a => ( { ...a } ) ), depthStencilAttachment: attachment }; -+ -+ } -+ -+ _finishTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const state = data._tnDepth; -+ if ( ! state?.active ) return; -+ state.active = false; -+ const attachment = { ...state.original, depthLoadOp: 'clear', depthClearValue: state.logical, depthStoreOp: 'store' }; -+ // A combined texture's original stencil is never changed by this depth-only compatibility clear. -+ if ( attachment.stencilLoadOp !== undefined || this.get( state.canonical ).format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'load', stencilStoreOp: 'store', stencilReadOnly: false } ); -+ data.encoder.beginRenderPass( { colorAttachments: [], depthStencilAttachment: attachment } ).end(); -+ data.descriptor.depthStencilAttachment = { ...state.original, depthLoadOp: 'load' }; -+ -+ } -+ -+ _restoreTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ if ( ! data._tnDepth?.active ) return; -+ data.currentPass.end(); -+ this._finishTransientDepth( context ); -+ this._resumeTransientDepth( context ); -+ -+ } -+ -+ _resumeTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const descriptor = { ...data.descriptor, timestampWrites: undefined, colorAttachments: data.descriptor.colorAttachments.map( a => ( { ...a, loadOp: 'load' } ) ) }; -+ data.descriptor = descriptor; -+ data.currentPass = data.encoder.beginRenderPass( descriptor ); -+ data.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; -+ if ( context.viewport ) this.updateViewport( context ); -+ if ( context.scissor ) this.updateScissor( context ); -+ -+ } -+ -+ _releaseTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const state = data._tnDepth; -+ if ( state && ! state.source.used && state.cache._tnPrivateDepth === state.source ) { -+ -+ state.source.texture.destroy(); -+ delete state.cache._tnPrivateDepth; -+ -+ } -+ delete data._tnDepth; -+ -+ } -+ -+ // Separate encoders must observe the pending canonical contents in queue order. -+ _flushTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ if ( ! data._tnDepth ) return false; -+ this._restoreTransientDepth( context ); -+ data.currentPass.end(); -+ submit( this.device, data.encoder.finish() ); -+ this._releaseTransientDepth( context ); -+ data.currentPass = null; -+ return true; -+ -+ } -+ -+ _validateTransientDepth( renderObject, params, vertexBuffers ) { -+ -+ const { object, context, material, pipeline } = renderObject; -+ const proof = this.get( pipeline )._tnDepthProof; -+ const position = object.geometry.attributes.position; -+ const uploaded = position && this.get( position ); -+ if ( object !== context._tnOutputOwner || ! this._isFullTransientViewport( context ) || ! proof || proof.pipeline !== this.get( pipeline ).pipeline || proof.material !== material || proof.version !== material.version || material.vertexNode !== null || material.positionNode !== null || material.depthNode !== null || material.depthTest !== true || material.depthWrite !== true || material.depthFunc !== 3 || material.alphaToCoverage || material.polygonOffset || material.stencilWrite || params.vertexCount !== 3 || params.firstVertex !== 0 || params.instanceCount !== 1 || renderObject.geometry !== object.geometry || renderObject.getIndex() !== null || object.geometry.index !== null || object.isBatchedMesh || object.isInstancedMesh || ! vertexBuffers.includes( position ) || renderObject.getIndirect() !== null || ! uploaded?._tnFullTriangle || uploaded._tnTriangleVersion !== position.version || uploaded._tnTriangleBuffer !== uploaded.buffer || position.array.length !== 9 || ! [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ].every( ( n, i ) => position.array[ i ] === n ) ) return false; -+ if ( proof.vertex !== this.get( pipeline.vertexProgram )._tnShaderCode || proof.fragment !== this.get( pipeline.fragmentProgram )._tnShaderCode ) return false; -+ if ( /\b(discard|frag_depth|sample_mask|clip_distances)\b/.test( proof.vertex + proof.fragment ) ) return false; -+ // Admit only Three's shipped projection chain. Custom code anywhere in the vertex stage falls back. -+ const code = proof.vertex.replace( /\/\/[^\n]*/g, '' ).replace( /\s+/g, '' ); -+ if ( ( code.match( /fn/g ) || [] ).length !== 1 ) return false; -+ const main = /@vertexfnmain\((.*?)\)->VaryingsStruct\{(.*)\}$/.exec( code ); -+ if ( ! main || ! /^(@location\(\d+\)(position:vec3|uv:vec2),?)+$/.test( main[ 1 ] ) || ! main[ 1 ].includes( '@location(' + proof.positionIndex + ')position:vec3' ) ) return false; -+ let body = main[ 2 ]; -+ // UV passthrough affects colour only; no other extra vertex operation is accepted. -+ body = body.replace( /varyings\.(v_uv|nodeVarying\d+)=uv;/g, '' ); -+ const chain = /^modelViewMatrix=\(render\.cameraViewMatrix\*object\.(nodeUniform\d+)\);positionLocal=position;v_positionView=\(modelViewMatrix\*vec4\(positionLocal,1\.0\)\)\.xyz;(VERTEX_nodeVar\d+)=\(render\.cameraProjectionMatrix\*vec4\(v_positionView,1\.0\)\);VERTEX_v_modelViewProjection=\2;varyings\.builtinClipSpace=VERTEX_v_modelViewProjection;returnvaryings;$/; -+ const match = chain.exec( body ); -+ if ( ! match ) return false; -+ const matrices = new Map(); -+ for ( const group of renderObject.getBindings() ) { -+ -+ const groupData = this.get( group ); -+ if ( ! groupData._tnBindings || ! groupData._tnBindings.every( ( b, i ) => b.binding === group.bindings[ i ] && b.buffer === this.get( b.binding ).buffer ) ) return false; -+ for ( const entry of groupData._tnBindings ) { -+ -+ const binding = entry.binding; -+ const snapshot = this.get( binding )._tnUniformBytes; -+ if ( ! snapshot ) continue; -+ for ( const uniform of entry.uniforms || [] ) if ( uniform.itemSize === 16 ) matrices.set( group.name + '.' + uniform.name, Array.from( snapshot.slice( uniform.offset, uniform.offset + 16 ) ) ); -+ -+ } -+ -+ } -+ const identity = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; -+ const projection = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 ]; -+ return [ [ 'render.cameraViewMatrix', identity ], [ 'object.' + match[ 1 ], identity ], [ 'render.cameraProjectionMatrix', projection ] ].every( ( [ name, expected ] ) => matrices.get( name )?.every( ( value, i ) => value === expected[ i ] ) ); -+ -+ } -+ - beginRender( renderContext ) { - - const renderContextData = this.get( renderContext ); -@@ -966,10 +1104,15 @@ class WebGPUBackend extends Backend { - - // - -+ descriptor = this._beginTransientDepth( renderContext, descriptor ); -+ - _commandEncoderDescriptor.label = 'renderContext_' + renderContext.id; - const encoder = device.createCommandEncoder( _commandEncoderDescriptor ); - _commandEncoderDescriptor.reset(); - -+ renderContextData.descriptor = descriptor; -+ renderContextData.encoder = encoder; -+ - // Layered render targets: prepare bundle encoders for each camera in the array camera. - - if ( this._isRenderCameraDepthArray( renderContext ) === true ) { -@@ -1263,6 +1406,8 @@ class WebGPUBackend extends Backend { - - } - -+ this._finishTransientDepth( renderContext ); -+ - if ( occlusionQueryCount > 0 ) { - - const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results -@@ -1305,7 +1450,15 @@ class WebGPUBackend extends Backend { - - } - -- submit( this.device, renderContextData.encoder.finish() ); -+ try { -+ -+ submit( this.device, renderContextData.encoder.finish() ); -+ -+ } finally { -+ -+ this._releaseTransientDepth( renderContext ); -+ -+ } - - - // -@@ -1399,6 +1552,8 @@ class WebGPUBackend extends Backend { - */ - updateViewport( renderContext ) { - -+ if ( ! this._isFullTransientViewport( renderContext ) ) this._restoreTransientDepth( renderContext ); -+ - const { currentPass } = this.get( renderContext ); - const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; - -@@ -1413,6 +1568,8 @@ class WebGPUBackend extends Backend { - */ - updateScissor( renderContext ) { - -+ this._restoreTransientDepth( renderContext ); -+ - const { currentPass } = this.get( renderContext ); - const { x, y, width, height } = renderContext.scissorValue; - -@@ -1454,6 +1611,9 @@ class WebGPUBackend extends Backend { - */ - clear( color, depth, stencil, renderTargetContext = null ) { - -+ const active = this.renderer._currentRenderContext; -+ const interrupted = active && ( renderTargetContext === active || ( renderTargetContext === null && active.textures === null ) ) && this._flushTransientDepth( active ); -+ - const device = this.device; - const renderer = this.renderer; - -@@ -1582,6 +1742,12 @@ class WebGPUBackend extends Backend { - currentPass.end(); - - submit( device, encoder.finish() ); -+ if ( interrupted ) { -+ -+ this.get( active ).encoder = device.createCommandEncoder(); -+ this._resumeTransientDepth( active ); -+ -+ } - - } - -@@ -1910,6 +2076,12 @@ class WebGPUBackend extends Backend { - } else { - - passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 ); -+ if ( renderContextData._tnDepth?.active ) { -+ -+ renderContextData._tnDepth.logical = 0; -+ renderContextData._tnDepth.source.used = true; -+ -+ } - - } - -@@ -1945,6 +2117,19 @@ class WebGPUBackend extends Backend { - // vertex buffers - - const vertexBuffers = renderObject.getVertexBuffers(); -+ if ( renderContextData._tnDepth?.active ) { -+ -+ if ( this._validateTransientDepth( renderObject, drawParams, vertexBuffers ) ) { -+ -+ // _draw records the constant only after the actual GPU draw is encoded. -+ -+ } else { -+ -+ this._restoreTransientDepth( context ); -+ -+ } -+ -+ } - - if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) { - -@@ -2281,7 +2466,24 @@ class WebGPUBackend extends Backend { - */ - async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { - -- return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); -+ const active = this.renderer._currentRenderContext; -+ const state = active && this.get( active )._tnDepth; -+ const sourceGPU = state && this.get( texture ).texture; -+ const interrupted = state && ( sourceGPU === state.originalGPU || sourceGPU === state.colorGPU ) && this._flushTransientDepth( active ); -+ try { -+ -+ return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); -+ -+ } finally { -+ -+ if ( interrupted ) { -+ -+ this.get( active ).encoder = this.device.createCommandEncoder(); -+ this._resumeTransientDepth( active ); -+ -+ } -+ -+ } - - } - -@@ -2345,6 +2547,8 @@ class WebGPUBackend extends Backend { - _shaderModuleDescriptor.label = program.stage + ( program.name !== '' ? `_${ program.name }` : '' ); - _shaderModuleDescriptor.code = program.code; - -+ programGPU._tnShaderCode = program.code; -+ - programGPU.module = { - module: this.device.createShaderModule( _shaderModuleDescriptor ), - entryPoint: 'main' -@@ -2492,6 +2696,7 @@ class WebGPUBackend extends Backend { - _bufferDescriptor.reset(); - - uniformBufferData.buffer = bufferGPU; -+ if ( this.device.features?.has( 'transient-attachments' ) && uniformBuffer.uniforms?.some( u => u.itemSize === 16 ) ) uniformBufferData._tnUniformBytes = new Float32Array( byteLength / 4 ); - - } - -@@ -2677,6 +2882,10 @@ class WebGPUBackend extends Backend { - */ - copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { - -+ const active = this.renderer._currentRenderContext; -+ const state = active && this.get( active )._tnDepth; -+ const interrupted = state && [ srcTexture, dstTexture ].some( texture => { const gpu = this.get( texture ).texture; return gpu === state.originalGPU || gpu === state.colorGPU; } ) && this._flushTransientDepth( active ); -+ - let dstX = 0; - let dstY = 0; - let dstZ = 0; -@@ -2758,6 +2967,13 @@ class WebGPUBackend extends Backend { - - submit( this.device, encoder.finish() ); - -+ if ( interrupted ) { -+ -+ this.get( active ).encoder = this.device.createCommandEncoder(); -+ this._resumeTransientDepth( active ); -+ -+ } -+ - if ( dstLevel === 0 && dstTexture.generateMipmaps ) { - - this.textureUtils.generateMipmaps( dstTexture ); -@@ -2775,6 +2991,8 @@ class WebGPUBackend extends Backend { - */ - copyFramebufferToTexture( texture, renderContext, rectangle ) { - -+ this._restoreTransientDepth( renderContext ); -+ - const renderContextData = this.get( renderContext ); - - let sourceGPU = null; -diff --git a/src/renderers/webgpu/utils/WebGPUAttributeUtils.js b/src/renderers/webgpu/utils/WebGPUAttributeUtils.js -index 16bd72d0c0f1d693481efc98140a0f1b73905149..ded852d9df7e709c62c9fc3a6109c31737650d97 100644 ---- a/src/renderers/webgpu/utils/WebGPUAttributeUtils.js -+++ b/src/renderers/webgpu/utils/WebGPUAttributeUtils.js -@@ -174,6 +174,9 @@ class WebGPUAttributeUtils { - - buffer.unmap(); - -+ bufferData._tnFullTriangle = backend.device.features?.has( 'transient-attachments' ) && attribute.itemSize === 3 && attribute.count === 3 && attribute.normalized === false && array instanceof Float32Array && array.length === 9 && ! attribute.isInterleavedBufferAttribute && ! attribute.isStorageBufferAttribute && [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ].every( ( value, i ) => array[ i ] === value ); -+ bufferData._tnTriangleVersion = bufferAttribute.version; -+ bufferData._tnTriangleBuffer = buffer; - bufferData.buffer = buffer; - bufferData.version = bufferAttribute.version; - bufferData.uploadCall = backend.renderer.info.calls; -@@ -209,6 +212,8 @@ class WebGPUAttributeUtils { - if ( bufferData.version === bufferAttribute.version && - ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; - -+ bufferData._tnFullTriangle = false; -+ - let array = bufferAttribute.array; - - const itemSize = bufferData._itemSize; -diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js -index c479d0a88794b01bca531e7cbfb780cf93fc4721..7ff28660e2ea3e72eb545c5bbe95bd8d8435a58e 100644 ---- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js -+++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js -@@ -179,6 +179,7 @@ class WebGPUBindingUtils { - - } - -+ bindingsData._tnBindings = this._tnGroups?.get( bindGroupGPU ); - bindingsData.group = bindGroupGPU; - - } -@@ -198,6 +199,7 @@ class WebGPUBindingUtils { - - const updateRanges = binding.updateRanges; - -+ - if ( updateRanges.length === 0 ) { - - device.queue.writeBuffer( -@@ -251,6 +253,15 @@ class WebGPUBindingUtils { - - } - -+ // Keep bytes actually sent to the GPU, including partial writes; never certify mutable CPU values. -+ if ( backend.device.features?.has( 'transient-attachments' ) && binding.uniforms?.some( u => u.itemSize === 16 ) ) { -+ -+ const data = backend.get( binding ); -+ const values = new Float32Array( array.buffer, array.byteOffset, array.byteLength / 4 ); -+ if ( updateRanges.length === 0 ) data._tnUniformBytes = values.slice(); -+ else if ( data._tnUniformBytes ) for ( const range of updateRanges ) data._tnUniformBytes.set( values.subarray( range.start, range.start + range.count ), range.start ); -+ -+ } - } - - /** -@@ -414,6 +425,12 @@ class WebGPUBindingUtils { - } - - const bindGroupGPU = device.createBindGroup( _bindGroupDescriptor ); -+ if ( device.features?.has( 'transient-attachments' ) ) { -+ -+ this._tnGroups ||= new WeakMap(); -+ this._tnGroups.set( bindGroupGPU, bindGroup.bindings.map( binding => ( { binding, buffer: backend.get( binding ).buffer, uniforms: binding.uniforms?.map( u => ( { name: u.name, offset: u.offset, itemSize: u.itemSize } ) ) } ) ) ); -+ -+ } - - _bindGroupDescriptor.reset(); - -diff --git a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js -index 3480fea1e86f1f237cb6ee2ea87b5826d4fc404d..d5872652cc969dbea62c69944044a9eeff1f3ac3 100644 ---- a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js -+++ b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js -@@ -248,6 +248,16 @@ class WebGPUPipelineUtils { - - } - -+ if ( device.features?.has( 'transient-attachments' ) ) { -+ -+ // Capture the actual immutable GPU descriptor and compiled programs, after graph updates. -+ const positionIndex = renderObject.getAttributes().indexOf( geometry.attributes.position ); -+ const positionLayout = vertexBuffers.find( layout => layout.attributes.some( a => a.shaderLocation === positionIndex ) ); -+ const positionSlot = positionLayout?.attributes.find( a => a.shaderLocation === positionIndex ); -+ pipelineData._tnDepthProof = depthStencil.depthWriteEnabled === true && depthStencil.depthCompare === 'less-equal' && ! depthStencil.depthBias && ! depthStencil.depthBiasSlopeScale && ! _renderPipelineDescriptor.multisample.alphaToCoverageEnabled && ( _renderPipelineDescriptor.multisample.mask === undefined || _renderPipelineDescriptor.multisample.mask === 0xffffffff ) && primitiveState.topology === 'triangle-list' && ( primitiveState.cullMode === 'none' || ( primitiveState.cullMode === 'back' && primitiveState.frontFace === 'ccw' ) ) && positionLayout?.arrayStride === 12 && positionLayout.stepMode === 'vertex' && positionSlot?.format === 'float32x3' && positionSlot.offset === 0 ? { material, version: material.version, positionIndex, vertex: backend.get( vertexProgram )._tnShaderCode, fragment: backend.get( fragmentProgram )._tnShaderCode } : null; -+ -+ } -+ - // create pipeline - - device.pushErrorScope( 'validation' ); -@@ -261,6 +271,7 @@ class WebGPUPipelineUtils { - if ( promises === null ) { - - pipelineData.pipeline = device.createRenderPipeline( _renderPipelineDescriptor ); -+ if ( pipelineData._tnDepthProof ) pipelineData._tnDepthProof.pipeline = pipelineData.pipeline; - - _renderPipelineDescriptor.reset(); - -diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js -index 57c1eb62f1e0de063f22e08ec879e31d2a3240b6..f6d510b59151e02c46928689b2be99486c1dfcd8 100644 ---- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js -+++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js -@@ -446,6 +446,7 @@ class WebGPUTextureUtils { - - if ( depthBuffers !== undefined ) { - -+ depthBuffers._tnPrivateDepth?.texture.destroy(); - this._depthBuffers.delete( texture ); - depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); - depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index ee394e98d798be749776cce1fa3e043fb9951769..2a70eb319b4235c958c7b5b1ede148c808d0b4b2 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -85369,11 +85369,11 @@ - - } - const source = cache._tnPrivateDepth; - const attachment = { ...original, view: source.texture.createView(), depthLoadOp: 'clear', depthClearValue: 1, depthStoreOp: 'discard' }; - if ( format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'clear', stencilClearValue: 0, stencilStoreOp: 'discard' } ); -- this.get( context )._tnDepth = { original: { ...original }, source, cache, canonical, originalGPU: this.get( canonical ).texture, colorGPU: this.get( this.renderer.getCanvasTarget().colorTexture ).texture, active: true, logical: 1 }; -+ this.get( context )._tnDepth = { original: { ...original }, source, cache, canonical, active: true, logical: 1 }; - return { ...descriptor, colorAttachments: descriptor.colorAttachments.map( a => ( { ...a } ) ), depthStencilAttachment: attachment }; - - } - - _finishTransientDepth( context ) { -@@ -85421,24 +85421,10 @@ - state.source.texture.destroy(); - delete state.cache._tnPrivateDepth; - - } - delete data._tnDepth; -- -- } -- -- // Separate encoders must observe the pending canonical contents in queue order. -- _flushTransientDepth( context ) { -- -- const data = this.get( context ); -- if ( ! data._tnDepth ) return false; -- this._restoreTransientDepth( context ); -- data.currentPass.end(); -- submit( this.device, data.encoder.finish() ); -- this._releaseTransientDepth( context ); -- data.currentPass = null; -- return true; - - } - - _validateTransientDepth( renderObject, params, vertexBuffers ) { - -@@ -86138,13 +86124,10 @@ - * @param {boolean} stencil - Whether the stencil buffer should be cleared or not. - * @param {?RenderContext} [renderTargetContext=null] - The render context of the current set render target. - */ - clear( color, depth, stencil, renderTargetContext = null ) { - -- const active = this.renderer._currentRenderContext; -- const interrupted = active && ( renderTargetContext === active || ( renderTargetContext === null && active.textures === null ) ) && this._flushTransientDepth( active ); -- - const device = this.device; - const renderer = this.renderer; - - let colorAttachments = []; - let depthStencilAttachment; -@@ -86269,16 +86252,10 @@ - } ); - - currentPass.end(); - - submit( device, encoder.finish() ); -- if ( interrupted ) { -- -- this.get( active ).encoder = device.createCommandEncoder(); -- this._resumeTransientDepth( active ); -- -- } - - } - - // compute - -@@ -86993,28 +86970,11 @@ - * @param {number} faceIndex - The face index. - * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. - */ - async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { - -- const active = this.renderer._currentRenderContext; -- const state = active && this.get( active )._tnDepth; -- const sourceGPU = state && this.get( texture ).texture; -- const interrupted = state && ( sourceGPU === state.originalGPU || sourceGPU === state.colorGPU ) && this._flushTransientDepth( active ); -- try { -- -- return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); -- -- } finally { -- -- if ( interrupted ) { -- -- this.get( active ).encoder = this.device.createCommandEncoder(); -- this._resumeTransientDepth( active ); -- -- } -- -- } -+ return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); - - } - - /** - * Inits a time stamp query for the given render context. -@@ -87409,14 +87369,10 @@ - * @param {number} [srcLevel=0] - The mipmap level to copy. - * @param {number} [dstLevel=0] - The destination mip level to copy to. - */ - copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { - -- const active = this.renderer._currentRenderContext; -- const state = active && this.get( active )._tnDepth; -- const interrupted = state && [ srcTexture, dstTexture ].some( texture => { const gpu = this.get( texture ).texture; return gpu === state.originalGPU || gpu === state.colorGPU; } ) && this._flushTransientDepth( active ); -- - let dstX = 0; - let dstY = 0; - let dstZ = 0; - - let srcX = 0; -@@ -87494,16 +87450,10 @@ - _texelCopyTextureInfoDst.reset(); - _extent3D.reset(); - - submit( this.device, encoder.finish() ); - -- if ( interrupted ) { -- -- this.get( active ).encoder = this.device.createCommandEncoder(); -- this._resumeTransientDepth( active ); -- -- } - - if ( dstLevel === 0 && dstTexture.generateMipmaps ) { - - this.textureUtils.generateMipmaps( dstTexture ); - -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index a0fab1c7c37cfaf270d9ce36dd3d91453ae6a44c..bfa242835dfef49019dd368e3593a62a221dda3d 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -85281,11 +85281,11 @@ - - } - const source = cache._tnPrivateDepth; - const attachment = { ...original, view: source.texture.createView(), depthLoadOp: 'clear', depthClearValue: 1, depthStoreOp: 'discard' }; - if ( format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'clear', stencilClearValue: 0, stencilStoreOp: 'discard' } ); -- this.get( context )._tnDepth = { original: { ...original }, source, cache, canonical, originalGPU: this.get( canonical ).texture, colorGPU: this.get( this.renderer.getCanvasTarget().colorTexture ).texture, active: true, logical: 1 }; -+ this.get( context )._tnDepth = { original: { ...original }, source, cache, canonical, active: true, logical: 1 }; - return { ...descriptor, colorAttachments: descriptor.colorAttachments.map( a => ( { ...a } ) ), depthStencilAttachment: attachment }; - - } - - _finishTransientDepth( context ) { -@@ -85333,24 +85333,10 @@ - state.source.texture.destroy(); - delete state.cache._tnPrivateDepth; - - } - delete data._tnDepth; -- -- } -- -- // Separate encoders must observe the pending canonical contents in queue order. -- _flushTransientDepth( context ) { -- -- const data = this.get( context ); -- if ( ! data._tnDepth ) return false; -- this._restoreTransientDepth( context ); -- data.currentPass.end(); -- submit( this.device, data.encoder.finish() ); -- this._releaseTransientDepth( context ); -- data.currentPass = null; -- return true; - - } - - _validateTransientDepth( renderObject, params, vertexBuffers ) { - -@@ -86050,13 +86036,10 @@ - * @param {boolean} stencil - Whether the stencil buffer should be cleared or not. - * @param {?RenderContext} [renderTargetContext=null] - The render context of the current set render target. - */ - clear( color, depth, stencil, renderTargetContext = null ) { - -- const active = this.renderer._currentRenderContext; -- const interrupted = active && ( renderTargetContext === active || ( renderTargetContext === null && active.textures === null ) ) && this._flushTransientDepth( active ); -- - const device = this.device; - const renderer = this.renderer; - - let colorAttachments = []; - let depthStencilAttachment; -@@ -86181,16 +86164,10 @@ - } ); - - currentPass.end(); - - submit( device, encoder.finish() ); -- if ( interrupted ) { -- -- this.get( active ).encoder = device.createCommandEncoder(); -- this._resumeTransientDepth( active ); -- -- } - - } - - // compute - -@@ -86901,28 +86878,11 @@ - * @param {number} faceIndex - The face index. - * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. - */ - async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { - -- const active = this.renderer._currentRenderContext; -- const state = active && this.get( active )._tnDepth; -- const sourceGPU = state && this.get( texture ).texture; -- const interrupted = state && ( sourceGPU === state.originalGPU || sourceGPU === state.colorGPU ) && this._flushTransientDepth( active ); -- try { -- -- return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); -- -- } finally { -- -- if ( interrupted ) { -- -- this.get( active ).encoder = this.device.createCommandEncoder(); -- this._resumeTransientDepth( active ); -- -- } -- -- } -+ return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); - - } - - /** - * Inits a time stamp query for the given render context. -@@ -87317,14 +87277,10 @@ - * @param {number} [srcLevel=0] - The mipmap level to copy. - * @param {number} [dstLevel=0] - The destination mip level to copy to. - */ - copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { - -- const active = this.renderer._currentRenderContext; -- const state = active && this.get( active )._tnDepth; -- const interrupted = state && [ srcTexture, dstTexture ].some( texture => { const gpu = this.get( texture ).texture; return gpu === state.originalGPU || gpu === state.colorGPU; } ) && this._flushTransientDepth( active ); -- - let dstX = 0; - let dstY = 0; - let dstZ = 0; - - let srcX = 0; -@@ -87402,16 +87358,10 @@ - _texelCopyTextureInfoDst.reset(); - _extent3D.reset(); - - submit( this.device, encoder.finish() ); - -- if ( interrupted ) { -- -- this.get( active ).encoder = this.device.createCommandEncoder(); -- this._resumeTransientDepth( active ); -- -- } - - if ( dstLevel === 0 && dstTexture.generateMipmaps ) { - - this.textureUtils.generateMipmaps( dstTexture ); - -diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js -index 94b48d64ed8f2e57f00cb5fd5bd0112da085aeae..5dbb9bf2ed43fa055c0f7dd2859c146485825817 100644 ---- a/src/renderers/webgpu/WebGPUBackend.js -+++ b/src/renderers/webgpu/WebGPUBackend.js -@@ -841,9 +841,9 @@ - } - const source = cache._tnPrivateDepth; - const attachment = { ...original, view: source.texture.createView(), depthLoadOp: 'clear', depthClearValue: 1, depthStoreOp: 'discard' }; - if ( format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'clear', stencilClearValue: 0, stencilStoreOp: 'discard' } ); -- this.get( context )._tnDepth = { original: { ...original }, source, cache, canonical, originalGPU: this.get( canonical ).texture, colorGPU: this.get( this.renderer.getCanvasTarget().colorTexture ).texture, active: true, logical: 1 }; -+ this.get( context )._tnDepth = { original: { ...original }, source, cache, canonical, active: true, logical: 1 }; - return { ...descriptor, colorAttachments: descriptor.colorAttachments.map( a => ( { ...a } ) ), depthStencilAttachment: attachment }; - - } - -@@ -893,22 +893,8 @@ - delete state.cache._tnPrivateDepth; - - } - delete data._tnDepth; -- -- } -- -- // Separate encoders must observe the pending canonical contents in queue order. -- _flushTransientDepth( context ) { -- -- const data = this.get( context ); -- if ( ! data._tnDepth ) return false; -- this._restoreTransientDepth( context ); -- data.currentPass.end(); -- submit( this.device, data.encoder.finish() ); -- this._releaseTransientDepth( context ); -- data.currentPass = null; -- return true; - - } - - _validateTransientDepth( renderObject, params, vertexBuffers ) { -@@ -1610,11 +1596,8 @@ - * @param {?RenderContext} [renderTargetContext=null] - The render context of the current set render target. - */ - clear( color, depth, stencil, renderTargetContext = null ) { - -- const active = this.renderer._currentRenderContext; -- const interrupted = active && ( renderTargetContext === active || ( renderTargetContext === null && active.textures === null ) ) && this._flushTransientDepth( active ); -- - const device = this.device; - const renderer = this.renderer; - - let colorAttachments = []; -@@ -1741,14 +1724,8 @@ - - currentPass.end(); - - submit( device, encoder.finish() ); -- if ( interrupted ) { -- -- this.get( active ).encoder = device.createCommandEncoder(); -- this._resumeTransientDepth( active ); -- -- } - - } - - // compute -@@ -2465,26 +2442,9 @@ - * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. - */ - async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { - -- const active = this.renderer._currentRenderContext; -- const state = active && this.get( active )._tnDepth; -- const sourceGPU = state && this.get( texture ).texture; -- const interrupted = state && ( sourceGPU === state.originalGPU || sourceGPU === state.colorGPU ) && this._flushTransientDepth( active ); -- try { -- -- return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); -- -- } finally { -- -- if ( interrupted ) { -- -- this.get( active ).encoder = this.device.createCommandEncoder(); -- this._resumeTransientDepth( active ); -- -- } -- -- } -+ return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); - - } - - /** -@@ -2881,12 +2841,8 @@ - * @param {number} [dstLevel=0] - The destination mip level to copy to. - */ - copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { - -- const active = this.renderer._currentRenderContext; -- const state = active && this.get( active )._tnDepth; -- const interrupted = state && [ srcTexture, dstTexture ].some( texture => { const gpu = this.get( texture ).texture; return gpu === state.originalGPU || gpu === state.colorGPU; } ) && this._flushTransientDepth( active ); -- - let dstX = 0; - let dstY = 0; - let dstZ = 0; - -@@ -2966,14 +2922,8 @@ - _extent3D.reset(); - - submit( this.device, encoder.finish() ); - -- if ( interrupted ) { -- -- this.get( active ).encoder = this.device.createCommandEncoder(); -- this._resumeTransientDepth( active ); -- -- } - - if ( dstLevel === 0 && dstTexture.generateMipmaps ) { - - this.textureUtils.generateMipmaps( dstTexture ); -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index 2a70eb319b4235c958c7b5b1ede148c808d0b4b2..7e34d05be672a7fa2e38b19c035f744a8b486446 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -85446,41 +85446,41 @@ - const chain = /^modelViewMatrix=\(render\.cameraViewMatrix\*object\.(nodeUniform\d+)\);positionLocal=position;v_positionView=\(modelViewMatrix\*vec4\(positionLocal,1\.0\)\)\.xyz;(VERTEX_nodeVar\d+)=\(render\.cameraProjectionMatrix\*vec4\(v_positionView,1\.0\)\);VERTEX_v_modelViewProjection=\2;varyings\.builtinClipSpace=VERTEX_v_modelViewProjection;returnvaryings;$/; - const match = chain.exec( body ); - if ( ! match ) return false; - const matrices = new Map(); - for ( const group of renderObject.getBindings() ) { - - const groupData = this.get( group ); - if ( ! groupData._tnBindings || ! groupData._tnBindings.every( ( b, i ) => b.binding === group.bindings[ i ] && b.buffer === this.get( b.binding ).buffer ) ) return false; - for ( const entry of groupData._tnBindings ) { - - const binding = entry.binding; - const snapshot = this.get( binding )._tnUniformBytes; - if ( ! snapshot ) continue; - for ( const uniform of entry.uniforms || [] ) if ( uniform.itemSize === 16 ) matrices.set( group.name + '.' + uniform.name, Array.from( snapshot.slice( uniform.offset, uniform.offset + 16 ) ) ); - - } - - } - const identity = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; - const projection = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 ]; -- return [ [ 'render.cameraViewMatrix', identity ], [ 'object.' + match[ 1 ], identity ], [ 'render.cameraProjectionMatrix', projection ] ].every( ( [ name, expected ] ) => matrices.get( name )?.every( ( value, i ) => value === expected[ i ] ) ); -+ return [ [ 'render.cameraViewMatrix', identity ], [ 'object.' + match[ 1 ], identity ], [ 'render.cameraProjectionMatrix', projection ] ].every( ( [ name, expected ] ) => matrices.get( name )?.length === 16 && matrices.get( name ).every( ( value, i ) => value === expected[ i ] ) ); - - } - - beginRender( renderContext ) { - - const renderContextData = this.get( renderContext ); - - // - - const device = this.device; - const occlusionQueryCount = renderContext.occlusionQueryCount; - - let occlusionQuerySet; - - if ( occlusionQueryCount > 0 ) { - - if ( renderContextData.currentOcclusionQuerySet ) renderContextData.currentOcclusionQuerySet.destroy(); - if ( renderContextData.currentOcclusionQueryBuffer ) renderContextData.currentOcclusionQueryBuffer.destroy(); - - // Get a reference to the array of objects with queries. The renderContextData property -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index bfa242835dfef49019dd368e3593a62a221dda3d..4b7a3baea4fc2a99f9453211d2807131f8a86dd4 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -85358,41 +85358,41 @@ - const chain = /^modelViewMatrix=\(render\.cameraViewMatrix\*object\.(nodeUniform\d+)\);positionLocal=position;v_positionView=\(modelViewMatrix\*vec4\(positionLocal,1\.0\)\)\.xyz;(VERTEX_nodeVar\d+)=\(render\.cameraProjectionMatrix\*vec4\(v_positionView,1\.0\)\);VERTEX_v_modelViewProjection=\2;varyings\.builtinClipSpace=VERTEX_v_modelViewProjection;returnvaryings;$/; - const match = chain.exec( body ); - if ( ! match ) return false; - const matrices = new Map(); - for ( const group of renderObject.getBindings() ) { - - const groupData = this.get( group ); - if ( ! groupData._tnBindings || ! groupData._tnBindings.every( ( b, i ) => b.binding === group.bindings[ i ] && b.buffer === this.get( b.binding ).buffer ) ) return false; - for ( const entry of groupData._tnBindings ) { - - const binding = entry.binding; - const snapshot = this.get( binding )._tnUniformBytes; - if ( ! snapshot ) continue; - for ( const uniform of entry.uniforms || [] ) if ( uniform.itemSize === 16 ) matrices.set( group.name + '.' + uniform.name, Array.from( snapshot.slice( uniform.offset, uniform.offset + 16 ) ) ); - - } - - } - const identity = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; - const projection = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 ]; -- return [ [ 'render.cameraViewMatrix', identity ], [ 'object.' + match[ 1 ], identity ], [ 'render.cameraProjectionMatrix', projection ] ].every( ( [ name, expected ] ) => matrices.get( name )?.every( ( value, i ) => value === expected[ i ] ) ); -+ return [ [ 'render.cameraViewMatrix', identity ], [ 'object.' + match[ 1 ], identity ], [ 'render.cameraProjectionMatrix', projection ] ].every( ( [ name, expected ] ) => matrices.get( name )?.length === 16 && matrices.get( name ).every( ( value, i ) => value === expected[ i ] ) ); - - } - - beginRender( renderContext ) { - - const renderContextData = this.get( renderContext ); - - // - - const device = this.device; - const occlusionQueryCount = renderContext.occlusionQueryCount; - - let occlusionQuerySet; - - if ( occlusionQueryCount > 0 ) { - - if ( renderContextData.currentOcclusionQuerySet ) renderContextData.currentOcclusionQuerySet.destroy(); - if ( renderContextData.currentOcclusionQueryBuffer ) renderContextData.currentOcclusionQueryBuffer.destroy(); - - // Get a reference to the array of objects with queries. The renderContextData property -diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js -index 5dbb9bf2ed43fa055c0f7dd2859c146485825817..0fbcc307d39cbf6d74d9dbc5f199f2b0e4f58b33 100644 ---- a/src/renderers/webgpu/WebGPUBackend.js -+++ b/src/renderers/webgpu/WebGPUBackend.js -@@ -917,41 +917,41 @@ - const chain = /^modelViewMatrix=\(render\.cameraViewMatrix\*object\.(nodeUniform\d+)\);positionLocal=position;v_positionView=\(modelViewMatrix\*vec4\(positionLocal,1\.0\)\)\.xyz;(VERTEX_nodeVar\d+)=\(render\.cameraProjectionMatrix\*vec4\(v_positionView,1\.0\)\);VERTEX_v_modelViewProjection=\2;varyings\.builtinClipSpace=VERTEX_v_modelViewProjection;returnvaryings;$/; - const match = chain.exec( body ); - if ( ! match ) return false; - const matrices = new Map(); - for ( const group of renderObject.getBindings() ) { - - const groupData = this.get( group ); - if ( ! groupData._tnBindings || ! groupData._tnBindings.every( ( b, i ) => b.binding === group.bindings[ i ] && b.buffer === this.get( b.binding ).buffer ) ) return false; - for ( const entry of groupData._tnBindings ) { - - const binding = entry.binding; - const snapshot = this.get( binding )._tnUniformBytes; - if ( ! snapshot ) continue; - for ( const uniform of entry.uniforms || [] ) if ( uniform.itemSize === 16 ) matrices.set( group.name + '.' + uniform.name, Array.from( snapshot.slice( uniform.offset, uniform.offset + 16 ) ) ); - - } - - } - const identity = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; - const projection = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 ]; -- return [ [ 'render.cameraViewMatrix', identity ], [ 'object.' + match[ 1 ], identity ], [ 'render.cameraProjectionMatrix', projection ] ].every( ( [ name, expected ] ) => matrices.get( name )?.every( ( value, i ) => value === expected[ i ] ) ); -+ return [ [ 'render.cameraViewMatrix', identity ], [ 'object.' + match[ 1 ], identity ], [ 'render.cameraProjectionMatrix', projection ] ].every( ( [ name, expected ] ) => matrices.get( name )?.length === 16 && matrices.get( name ).every( ( value, i ) => value === expected[ i ] ) ); - - } - - beginRender( renderContext ) { - - const renderContextData = this.get( renderContext ); - - // - - const device = this.device; - const occlusionQueryCount = renderContext.occlusionQueryCount; - - let occlusionQuerySet; - - if ( occlusionQueryCount > 0 ) { - - if ( renderContextData.currentOcclusionQuerySet ) renderContextData.currentOcclusionQuerySet.destroy(); - if ( renderContextData.currentOcclusionQueryBuffer ) renderContextData.currentOcclusionQueryBuffer.destroy(); - - // Get a reference to the array of objects with queries. The renderContextData property -diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index 7e34d05be672a7fa2e38b19c035f744a8b486446..f9ea33ac39db20e96822fda6b90e2bed02817435 100644 ---- a/build/three.webgpu.js -+++ b/build/three.webgpu.js -@@ -61462,7 +61462,8 @@ class Renderer { - - try { - -- if ( renderComplete || this.backend.get( renderContext )._tnDepth ) this.backend.finishRender( renderContext ); -+ if ( renderComplete ) this.backend.finishRender( renderContext ); -+ else if ( this.backend.get( renderContext )._tnDepth ) this.backend._abortTransientDepth( renderContext ); - - } finally { - -@@ -85412,6 +85413,26 @@ class WebGPUBackend extends Backend { - - } - -+ _abortTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const state = data._tnDepth; -+ try { -+ -+ data.currentPass.end(); -+ data.encoder.finish(); // Close and discard failed work without submitting or restoring depth. -+ -+ } finally { -+ -+ data.descriptor.depthStencilAttachment = { ...state.original }; -+ delete data.currentPass; -+ delete data.encoder; -+ this._releaseTransientDepth( context ); -+ -+ } -+ -+ } -+ - _releaseTransientDepth( context ) { - - const data = this.get( context ); -@@ -85968,6 +85989,7 @@ class WebGPUBackend extends Backend { - try { - - submit( this.device, renderContextData.encoder.finish() ); -+ if ( renderContextData._tnDepth?.logical === 0 ) renderContextData._tnDepth.source.used = true; - - } finally { - -@@ -86585,7 +86607,6 @@ class WebGPUBackend extends Backend { - if ( renderContextData._tnDepth?.active ) { - - renderContextData._tnDepth.logical = 0; -- renderContextData._tnDepth.source.used = true; - - } - -diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index 4b7a3baea4fc2a99f9453211d2807131f8a86dd4..93cedce745291edfbd333fb76d8154b8eff685bd 100644 ---- a/build/three.webgpu.nodes.js -+++ b/build/three.webgpu.nodes.js -@@ -61415,7 +61415,8 @@ class Renderer { - - try { - -- if ( renderComplete || this.backend.get( renderContext )._tnDepth ) this.backend.finishRender( renderContext ); -+ if ( renderComplete ) this.backend.finishRender( renderContext ); -+ else if ( this.backend.get( renderContext )._tnDepth ) this.backend._abortTransientDepth( renderContext ); - - } finally { - -@@ -85324,6 +85325,26 @@ class WebGPUBackend extends Backend { - - } - -+ _abortTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const state = data._tnDepth; -+ try { -+ -+ data.currentPass.end(); -+ data.encoder.finish(); // Close and discard failed work without submitting or restoring depth. -+ -+ } finally { -+ -+ data.descriptor.depthStencilAttachment = { ...state.original }; -+ delete data.currentPass; -+ delete data.encoder; -+ this._releaseTransientDepth( context ); -+ -+ } -+ -+ } -+ - _releaseTransientDepth( context ) { - - const data = this.get( context ); -@@ -85880,6 +85901,7 @@ class WebGPUBackend extends Backend { - try { - - submit( this.device, renderContextData.encoder.finish() ); -+ if ( renderContextData._tnDepth?.logical === 0 ) renderContextData._tnDepth.source.used = true; - - } finally { - -@@ -86498,7 +86520,6 @@ class WebGPUBackend extends Backend { - if ( renderContextData._tnDepth?.active ) { - - renderContextData._tnDepth.logical = 0; -- renderContextData._tnDepth.source.used = true; - - } - -diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js -index 769c909b0a63708dea7d22efc5379c39bdf57d92..bc495cb324ed9923008fa5685aede53aee44b6d8 100644 ---- a/src/renderers/common/Renderer.js -+++ b/src/renderers/common/Renderer.js -@@ -1764,7 +1764,8 @@ class Renderer { - - try { - -- if ( renderComplete || this.backend.get( renderContext )._tnDepth ) this.backend.finishRender( renderContext ); -+ if ( renderComplete ) this.backend.finishRender( renderContext ); -+ else if ( this.backend.get( renderContext )._tnDepth ) this.backend._abortTransientDepth( renderContext ); - - } finally { - -diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js -index 0fbcc307d39cbf6d74d9dbc5f199f2b0e4f58b33..add7f3284d411fd3eb188c2568775595b85204f5 100644 ---- a/src/renderers/webgpu/WebGPUBackend.js -+++ b/src/renderers/webgpu/WebGPUBackend.js -@@ -883,6 +883,26 @@ class WebGPUBackend extends Backend { - - } - -+ _abortTransientDepth( context ) { -+ -+ const data = this.get( context ); -+ const state = data._tnDepth; -+ try { -+ -+ data.currentPass.end(); -+ data.encoder.finish(); // Close and discard failed work without submitting or restoring depth. -+ -+ } finally { -+ -+ data.descriptor.depthStencilAttachment = { ...state.original }; -+ delete data.currentPass; -+ delete data.encoder; -+ this._releaseTransientDepth( context ); -+ -+ } -+ -+ } -+ - _releaseTransientDepth( context ) { - - const data = this.get( context ); -@@ -1439,6 +1459,7 @@ class WebGPUBackend extends Backend { - try { - - submit( this.device, renderContextData.encoder.finish() ); -+ if ( renderContextData._tnDepth?.logical === 0 ) renderContextData._tnDepth.source.used = true; - - } finally { - -@@ -2056,7 +2077,6 @@ class WebGPUBackend extends Backend { - if ( renderContextData._tnDepth?.active ) { - - renderContextData._tnDepth.logical = 0; -- renderContextData._tnDepth.source.used = true; - - } - +{ + "version": 2, + "generator": "scripts/generate-three-patch.mjs", + "pin": { + "commit": "2431a09f46f34c560bc8e44b33be0e567723d5b9", + "version": "0.185.1" + }, + "acceptedLegacyCompatPairs": [ + { + "path": "build/three.webgpu.js", + "oldHash": "148869ee7f18c62570ac1eafb7df5775e5a89581", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "5c5ef3c06885c724c1c2288295e7e68483ee1f6e", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "7aa62adb9e78ba8f0a67709d3141bf93c67419cc", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "bdffb9b4069f7beafa85913b155e92d3e83f12d0", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "efd0e16719cc7bbef1fbc18c241948e25eb85881", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "1245f0da9bb6a0f731b8c75a64490d7c91238700", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "1b3a6e96291af0077f7f247040e657cf4aa0cb33", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "3d47c1151d563f1e0ae2bb4b92edd057fe0df2b3", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "81c24b2e05e5047e1a02410a2056949d39b1a05c", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "b8a7b2fd0a2e95911e7e12fd5674592107f012de", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/nodes/accessors/Instance.js", + "oldHash": "ed00e75ba4fd7b49343cfada26eacdef8940a4f4", + "newHash": "18e2b7943393fd07b7989b1e3a5231a61ca24187" + }, + { + "path": "src/nodes/accessors/Skinning.js", + "oldHash": "aaeea633a8ece291be5faf868e312867ddad01b6", + "newHash": "76303e765bc29853cfd7f139dd3ec76545b70798" + }, + { + "path": "src/renderers/common/Renderer.js", + "oldHash": "89b33efb80ca1dcb9db931071dfda62ffdba3526", + "newHash": "b1293bfbe2ccf0583711813c625ddd5c5ba78620" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "69b8b67fdeec5be24de34d8382beda9020642f77", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUAttributeUtils.js", + "oldHash": "83bbe189915c8450c4e14108e6a3f4ed98c4b016", + "newHash": "16bd72d0c0f1d693481efc98140a0f1b73905149" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUTextureUtils.js", + "oldHash": "ff8ee2dbace2816105aa7152891c04f2333f33e8", + "newHash": "57c1eb62f1e0de063f22e08ec879e31d2a3240b6" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "9166d6f2dd1ac1505fa27b625f1976dff6b8ebc1", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "9e7c7f74be1191b761711bd70ee59cc033e586cb", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/nodes/accessors/TextureNode.js", + "oldHash": "f886ed21397ab39d6fdbc58e5ac57729f1871f8a", + "newHash": "1a138b1a055d0867e7686531930d4d557e1a20df" + }, + { + "path": "src/nodes/accessors/TextureSizeNode.js", + "oldHash": "d4668281657ef8b1acad761e0d86a0716909eee4", + "newHash": "43eccaa01bcb12ea6e02c94232d5ee76bcac0d01" + }, + { + "path": "src/nodes/display/PassNode.js", + "oldHash": "cc827a55cb82a6325296dcc5c55a4246f4f70aed", + "newHash": "3cdf66cfe0760066bc062bf7ce3f3a8000eb2b0d" + }, + { + "path": "src/renderers/common/Renderer.js", + "oldHash": "51590d5a23a93365e5cd9fb0360678750270d5bf", + "newHash": "b1293bfbe2ccf0583711813c625ddd5c5ba78620" + }, + { + "path": "src/renderers/webgpu/nodes/WGSLNodeBuilder.js", + "oldHash": "2acc832a628f43adac49614dad8e593ef058c398", + "newHash": "1cbc9b3e02c9832d69a8aadb3a6003392b443c8d" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUBindingUtils.js", + "oldHash": "a5da7ca09f746c0b2859ef173df663ccdc851ebc", + "newHash": "c479d0a88794b01bca531e7cbfb780cf93fc4721" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "941ec68ef0b8174a633054043edb94d9d3d58f5e", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "57f69c17352b936a9eca118a9801805c95c401b6", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "ef853fd6f7382a8c7ff66cda43471344aeb20770", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "e7956e565579aa6ec550ca001336d5a0d76c652a", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/renderers/common/Renderer.js", + "oldHash": "730ce0ccd0f56d70c4a7abe7c514c5077d544d1e", + "newHash": "b1293bfbe2ccf0583711813c625ddd5c5ba78620" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "927e3a1ad5a4665d77238ffbab4c57a603b86828", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUTextureUtils.js", + "oldHash": "c46f6ab0e06d93f124c9fc62ece8859529461d5e", + "newHash": "57c1eb62f1e0de063f22e08ec879e31d2a3240b6" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "e594543d1e604de1ac9f45c6260e213ad0e2d7b5", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "6581a8b0e69dea0ce5bfdc644f75a40f6aaac963", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "00ff55f538873dc1b342d899073a8f07401a8824", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "25918de19dcb52504e13e7ecd3adbd64e8b64d65", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "9b290e496a0951d1af111052bcfe12c97dad4762", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "f9ea33ac39db20e96822fda6b90e2bed02817435", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "e10b2c19633d2690b5dd852319c935735de06d30", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "93cedce745291edfbd333fb76d8154b8eff685bd", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/nodes/accessors/Instance.js", + "oldHash": "3ff041a260cde0e5fb1880daddd4fc700a1c945e", + "newHash": "18e2b7943393fd07b7989b1e3a5231a61ca24187" + }, + { + "path": "src/renderers/common/RenderObject.js", + "oldHash": "4f8d58950b80cd56bbec22a61cf39a7aae5bd731", + "newHash": "a3dcba2f1fb0df19f3ddb3039a97ae408e2938b3" + }, + { + "path": "src/renderers/common/RenderObjects.js", + "oldHash": "91fba38f3bafa35806bd3c82d1c79671a4a6b00d", + "newHash": "72569d0339314d26eb7a35ff2a8462a803c852a6" + }, + { + "path": "src/renderers/common/RenderPipeline.js", + "oldHash": "94e54740e13088a9fa40ccfd30000232df7c2eea", + "newHash": "ab3085cfdca456ffb3de6945d7e7f693e4ed33ae" + }, + { + "path": "src/renderers/common/Renderer.js", + "oldHash": "bc495cb324ed9923008fa5685aede53aee44b6d8", + "newHash": "b1293bfbe2ccf0583711813c625ddd5c5ba78620" + }, + { + "path": "src/renderers/common/Textures.js", + "oldHash": "be47645f5662a2be8002c23de8ff4092bf561049", + "newHash": "5b305173fa4a2814ec74563d2ae5cdc70e7505e1" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "7ebe3404830ecea2e304baa368f04bfa474ff0e1", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "add7f3284d411fd3eb188c2568775595b85204f5", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUAttributeUtils.js", + "oldHash": "ded852d9df7e709c62c9fc3a6109c31737650d97", + "newHash": "16bd72d0c0f1d693481efc98140a0f1b73905149" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUBindingUtils.js", + "oldHash": "7ff28660e2ea3e72eb545c5bbe95bd8d8435a58e", + "newHash": "c479d0a88794b01bca531e7cbfb780cf93fc4721" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUPipelineUtils.js", + "oldHash": "d5872652cc969dbea62c69944044a9eeff1f3ac3", + "newHash": "3480fea1e86f1f237cb6ee2ea87b5826d4fc404d" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUTextureUtils.js", + "oldHash": "03998e6e9067462c50bd402ffda5b29d24634be8", + "newHash": "57c1eb62f1e0de063f22e08ec879e31d2a3240b6" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUTextureUtils.js", + "oldHash": "f6d510b59151e02c46928689b2be99486c1dfcd8", + "newHash": "57c1eb62f1e0de063f22e08ec879e31d2a3240b6" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "5975e257a43fe139b69c249d8c8c1edfe91e12a0", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "b86e40b357823cf3919d9fdf3c2554f14bcd9931", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "ee394e98d798be749776cce1fa3e043fb9951769", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "a0fab1c7c37cfaf270d9ce36dd3d91453ae6a44c", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "94b48d64ed8f2e57f00cb5fd5bd0112da085aeae", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "2a70eb319b4235c958c7b5b1ede148c808d0b4b2", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "bfa242835dfef49019dd368e3593a62a221dda3d", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "5dbb9bf2ed43fa055c0f7dd2859c146485825817", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "7e34d05be672a7fa2e38b19c035f744a8b486446", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "4b7a3baea4fc2a99f9453211d2807131f8a86dd4", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/renderers/common/Renderer.js", + "oldHash": "769c909b0a63708dea7d22efc5379c39bdf57d92", + "newHash": "b1293bfbe2ccf0583711813c625ddd5c5ba78620" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "0fbcc307d39cbf6d74d9dbc5f199f2b0e4f58b33", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + } + ], + "acceptedLegacyFinalHashes": { + "build/three.cjs": "b0f917241f1f2cd25baa21dec20edda32176ef6a", + "build/three.core.js": "868a634b4294be5f32664912a50813d55d6dd6e7", + "build/three.webgpu.js": "a308a892c59283b3b9a71477b3b02d64db40becd", + "build/three.webgpu.nodes.js": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb", + "src/materials/nodes/manager/NodeMaterialObserver.js": "7e2cb9926878b43aa2b8b3a146e3427711be7210", + "src/nodes/accessors/Batch.js": "2cf4bb6039853d5c922e89041419250b243694ef", + "src/nodes/accessors/Instance.js": "18e2b7943393fd07b7989b1e3a5231a61ca24187", + "src/nodes/accessors/Skinning.js": "76303e765bc29853cfd7f139dd3ec76545b70798", + "src/nodes/accessors/TextureNode.js": "1a138b1a055d0867e7686531930d4d557e1a20df", + "src/nodes/accessors/TextureSizeNode.js": "43eccaa01bcb12ea6e02c94232d5ee76bcac0d01", + "src/nodes/accessors/VelocityNode.js": "c48002abbce20ee8b29908cf314d7b03afe0db03", + "src/nodes/core/NodeBuilder.js": "1990da8fe8f1f29a7b53030756dfd966d8de3cb6", + "src/nodes/display/PassNode.js": "3cdf66cfe0760066bc062bf7ce3f3a8000eb2b0d", + "src/nodes/lighting/PointShadowNode.js": "f9019a72890a30fded9f7ce76982722c2f7d6a3f", + "src/nodes/lighting/ShadowFilterNode.js": "fe8b50c8f76d0b6ce3d2fa1452751b2469a821e4", + "src/objects/BatchedMesh.js": "35cc0ffe83a69ea9fa5fca6dec2d820ca193f88d", + "src/renderers/common/Bindings.js": "a3cec4ed844ab88f870b46714c56c941e0cc7a8e", + "src/renderers/common/Geometries.js": "979da2ffc4d0dcda6fdd72747a7e7125e3f8a3a3", + "src/renderers/common/RenderContexts.js": "7319e25e280452b69c6f0c4c2f8c93c955ff9be3", + "src/renderers/common/RenderObject.js": "a3dcba2f1fb0df19f3ddb3039a97ae408e2938b3", + "src/renderers/common/RenderObjects.js": "72569d0339314d26eb7a35ff2a8462a803c852a6", + "src/renderers/common/Renderer.js": "b1293bfbe2ccf0583711813c625ddd5c5ba78620", + "src/renderers/common/Textures.js": "5b305173fa4a2814ec74563d2ae5cdc70e7505e1", + "src/renderers/common/nodes/NodeManager.js": "033128c396c2cad5c5d1d362f2d261e9adbd6bdc", + "src/renderers/webgpu/WebGPUBackend.js": "5636b40e6708a490e926e5f590675834d23bf326", + "src/renderers/webgpu/nodes/WGSLNodeBuilder.js": "1cbc9b3e02c9832d69a8aadb3a6003392b443c8d", + "src/renderers/webgpu/utils/WebGPUAttributeUtils.js": "16bd72d0c0f1d693481efc98140a0f1b73905149", + "src/renderers/webgpu/utils/WebGPUBindingUtils.js": "c479d0a88794b01bca531e7cbfb780cf93fc4721", + "src/renderers/webgpu/utils/WebGPUPipelineUtils.js": "3480fea1e86f1f237cb6ee2ea87b5826d4fc404d", + "src/renderers/webgpu/utils/WebGPUTextureUtils.js": "57c1eb62f1e0de063f22e08ec879e31d2a3240b6", + "src/renderers/common/RenderPipeline.js": "ab3085cfdca456ffb3de6945d7e7f693e4ed33ae" + }, + "generatedFinalHashes": { + "build/three.cjs": "b0f917241f1f2cd25baa21dec20edda32176ef6a", + "build/three.core.js": "868a634b4294be5f32664912a50813d55d6dd6e7", + "build/three.webgpu.js": "ab93e431586812819669512a8f8e9bf299f2fbc6", + "build/three.webgpu.nodes.js": "e3efde006043ae4dec27f823cf223bf74c71c456", + "src/materials/nodes/manager/NodeMaterialObserver.js": "7e2cb9926878b43aa2b8b3a146e3427711be7210", + "src/nodes/accessors/Batch.js": "2cf4bb6039853d5c922e89041419250b243694ef", + "src/nodes/accessors/Instance.js": "3ff041a260cde0e5fb1880daddd4fc700a1c945e", + "src/nodes/accessors/Skinning.js": "76303e765bc29853cfd7f139dd3ec76545b70798", + "src/nodes/accessors/TextureNode.js": "1a138b1a055d0867e7686531930d4d557e1a20df", + "src/nodes/accessors/TextureSizeNode.js": "43eccaa01bcb12ea6e02c94232d5ee76bcac0d01", + "src/nodes/accessors/VelocityNode.js": "c48002abbce20ee8b29908cf314d7b03afe0db03", + "src/nodes/core/NodeBuilder.js": "1990da8fe8f1f29a7b53030756dfd966d8de3cb6", + "src/nodes/display/PassNode.js": "3cdf66cfe0760066bc062bf7ce3f3a8000eb2b0d", + "src/nodes/lighting/PointShadowNode.js": "f9019a72890a30fded9f7ce76982722c2f7d6a3f", + "src/nodes/lighting/ShadowFilterNode.js": "fe8b50c8f76d0b6ce3d2fa1452751b2469a821e4", + "src/objects/BatchedMesh.js": "35cc0ffe83a69ea9fa5fca6dec2d820ca193f88d", + "src/renderers/common/Bindings.js": "a3cec4ed844ab88f870b46714c56c941e0cc7a8e", + "src/renderers/common/Geometries.js": "979da2ffc4d0dcda6fdd72747a7e7125e3f8a3a3", + "src/renderers/common/RenderContexts.js": "7319e25e280452b69c6f0c4c2f8c93c955ff9be3", + "src/renderers/common/RenderObject.js": "4f8d58950b80cd56bbec22a61cf39a7aae5bd731", + "src/renderers/common/RenderObjects.js": "91fba38f3bafa35806bd3c82d1c79671a4a6b00d", + "src/renderers/common/RenderPipeline.js": "94e54740e13088a9fa40ccfd30000232df7c2eea", + "src/renderers/common/Renderer.js": "f9903c26af499dd5178cacca1d66a260a5606352", + "src/renderers/common/Textures.js": "be47645f5662a2be8002c23de8ff4092bf561049", + "src/renderers/common/nodes/NodeManager.js": "033128c396c2cad5c5d1d362f2d261e9adbd6bdc", + "src/renderers/webgpu/WebGPUBackend.js": "1366ed87b026240d3d2a816e9b7f55cdc9df8ebb", + "src/renderers/webgpu/nodes/WGSLNodeBuilder.js": "1cbc9b3e02c9832d69a8aadb3a6003392b443c8d", + "src/renderers/webgpu/utils/WebGPUAttributeUtils.js": "ded852d9df7e709c62c9fc3a6109c31737650d97", + "src/renderers/webgpu/utils/WebGPUBindingUtils.js": "7ff28660e2ea3e72eb545c5bbe95bd8d8435a58e", + "src/renderers/webgpu/utils/WebGPUPipelineUtils.js": "d5872652cc969dbea62c69944044a9eeff1f3ac3", + "src/renderers/webgpu/utils/WebGPUTextureUtils.js": "036202ae74ee01f73ca25113e8d4e2efebe929bc" + }, + "bundleBrotliBase64": "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" +} diff --git a/packages/core/patches/three@0.185.1.patch b/packages/core/patches/three@0.185.1.patch index bf70361b19..039c94b270 100644 --- a/packages/core/patches/three@0.185.1.patch +++ b/packages/core/patches/three@0.185.1.patch @@ -25,7 +25,7 @@ index 0bb9262cd029f411933d077fd44197a51ec5e8e9..868a634b4294be5f32664912a50813d5 * Constructs a new batched mesh. * diff --git a/build/three.webgpu.js b/build/three.webgpu.js -index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed02817435 100644 +index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..ab93e431586812819669512a8f8e9bf299f2fbc6 100644 --- a/build/three.webgpu.js +++ b/build/three.webgpu.js @@ -141,6 +141,15 @@ class NodeMaterialObserver { @@ -294,27 +294,36 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } else if ( levelSnippet ) { -@@ -18320,7 +18403,8 @@ function createInstanceMatrixNode( builder, instanceMatrix ) { +@@ -18318,13 +18401,8 @@ function createInstanceMatrixNode( builder, instanceMatrix ) { - const uniformBufferSize = matrixCount * 16 * 4; + } else { +- const uniformBufferSize = matrixCount * 16 * 4; +- - if ( uniformBufferSize <= builder.getUniformBufferLimit() ) { +- +- instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', matrixCount ).element( instanceIndex ); +- +- } else { + // threenative: the uniform path re-uploads every draw; the attribute path uploads on version change -+ if ( false ) { ++ { - instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', matrixCount ).element( instanceIndex ); + let interleaved = _matrixBuffers.get( instanceMatrix ); -@@ -18414,7 +18498,8 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { +@@ -18412,9 +18490,10 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { - const uniformBufferSize = Math.max( matrices.count, 1 ) * 16 * 4; + if ( ! isStorageMatrix ) { + +- const uniformBufferSize = Math.max( matrices.count, 1 ) * 16 * 4; ++ Math.max( matrices.count, 1 ) * 16 * 4; - if ( uniformBufferSize > builder.getUniformBufferLimit() ) { + // threenative: the uniform path re-uploads every draw; the attribute path uploads on version change -+ if ( true ) { ++ { interleavedMatrix = _matrixBuffers.get( matrices ); -@@ -18455,7 +18540,7 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { +@@ -18455,7 +18534,7 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { // Synchronization of dynamic buffer updates per frame if ( interleavedMatrix !== null || interleavedColor !== null ) { @@ -323,15 +332,19 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( interleavedMatrix !== null ) { -@@ -18496,11 +18581,28 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { +@@ -18496,11 +18575,32 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { const instancedMesh = builder.object; - OnObjectUpdate( ( { object } ) => { + const updatePrevious = ( object, array ) => { + -+ const { previousInstanceMatrix } = _previousInstanceMatrices.get( object ); ++ const history = _previousInstanceMatrices.get( object ); ++ if ( array !== matrices.array && history.lastSource === array && history.lastVersion === matrices.version ) return; ++ const { previousInstanceMatrix } = history; + previousInstanceMatrix.array.set( array ); ++ history.lastSource = array; ++ history.lastVersion = matrices.version; + previousInstanceMatrix.needsUpdate = true; + const interleaved = _matrixBuffers.get( previousInstanceMatrix ); + if ( interleaved !== undefined ) interleaved.needsUpdate = true; @@ -355,7 +368,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } ); -@@ -18599,6 +18701,7 @@ const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => { +@@ -18599,6 +18699,7 @@ const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => { const indirectId = getIndirectIndex( batchMesh._indirectTexture, int( batchingIdNode ) ); const matricesTexture = batchMesh._matricesTexture; @@ -363,10 +376,10 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst(); const j = float( indirectId ).mul( 4 ).toInt().toConst(); -@@ -18626,6 +18729,19 @@ const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => { +@@ -18625,6 +18726,19 @@ const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => { + const bm = mat3( batchingMatrix ); positionLocal.assign( batchingMatrix.mul( positionLocal ) ); - + if ( builder.needsPreviousData() ) { + + const previousBatchingMatrix = mat4( @@ -380,10 +393,10 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed + + } + + const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) ); - const batchingNormal = bm.mul( transformedNormal ).xyz; -@@ -18745,7 +18861,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve +@@ -18745,7 +18859,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve } @@ -400,7 +413,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -18777,7 +18901,8 @@ const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => { +@@ -18777,7 +18899,8 @@ const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => { if ( skeletonData !== undefined ) { @@ -410,7 +423,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -18844,7 +18969,8 @@ const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = null ], +@@ -18844,7 +18967,8 @@ const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = null ], if ( state !== undefined ) { @@ -420,7 +433,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -29911,6 +30037,18 @@ class RenderObject { +@@ -29911,6 +30035,18 @@ class RenderObject { */ this._sourceMaterial = renderer._currentSourceMaterial; @@ -439,7 +452,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed /** * An event listener which is defined by `RenderObjects`. It performs * clean up tasks when `dispose()` on this render object. -@@ -30098,7 +30236,7 @@ class RenderObject { +@@ -30098,7 +30234,7 @@ class RenderObject { */ getChainArray() { @@ -448,7 +461,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -30283,7 +30421,7 @@ class RenderObject { +@@ -30283,7 +30419,7 @@ class RenderObject { * * @return {string} The geometry cache key. */ @@ -457,7 +470,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const { geometry } = this; -@@ -30311,12 +30449,14 @@ class RenderObject { +@@ -30311,12 +30447,14 @@ class RenderObject { const targets = geometry.morphAttributes[ name ]; cacheKey += 'morph-' + name + ','; @@ -473,7 +486,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -30548,6 +30688,7 @@ class RenderObject { +@@ -30548,6 +30686,7 @@ class RenderObject { } cacheKey = hash$1( cacheKey, this.renderer.contextNode.id, this.renderer.contextNode.version ); @@ -481,7 +494,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed return cacheKey; -@@ -30680,6 +30821,8 @@ class RenderObjects { +@@ -30680,6 +30819,8 @@ class RenderObjects { _chainKeys$3[ 1 ] = material; _chainKeys$3[ 2 ] = renderContext; _chainKeys$3[ 3 ] = lightsNode; @@ -490,7 +503,16 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // -@@ -30707,9 +30850,18 @@ class RenderObjects { +@@ -30701,15 +30842,26 @@ class RenderObjects { + + renderObject.updateClipping( clippingContext ); + +- if ( renderObject.needsGeometryUpdate ) { ++ const geometryChanged = renderObject.needsGeometryUpdate; ++ ++ if ( geometryChanged ) { + + renderObject.setGeometry( object.geometry ); } @@ -498,14 +520,14 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed + // When the renderer is drawing through an override material, that material is shared by + // the whole pass and mutated per object, so its `version` alone answers the wrong + // question. The source material this object actually carries answers the right one. - -- if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) { ++ + const sourceMaterial = this.renderer._currentSourceMaterial; + const sourceTracked = sourceMaterial !== null && sourceMaterial !== undefined; + const sourceReplaced = sourceTracked && renderObject._sourceMaterial !== sourceMaterial; + const sourceChanged = sourceTracked && ( sourceReplaced || renderObject.sourceVersion !== sourceMaterial.version ); -+ -+ if ( renderObject.version !== material.version || sourceChanged || renderObject.needsUpdate ) { + +- if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) { ++ if ( geometryChanged || renderObject.version !== material.version || sourceChanged || renderObject.needsUpdate ) { + + if ( sourceReplaced || renderObject.initialCacheKey !== renderObject.getCacheKey() ) { @@ -622,7 +644,22 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -32918,12 +33140,13 @@ class Bindings extends DataMap { +@@ -32832,10 +33054,14 @@ class Bindings extends DataMap { + + } else if ( binding.isSampledTexture ) { + ++ // Async node compilation can yield while a viewport node changes targets. ++ // Refresh its reference before creating the first GPU binding, not after. ++ binding.update(); + this.textures.updateTexture( binding.texture ); + + } else if ( binding.isSampler ) { + ++ binding.update(); + this.textures.updateSampler( binding ); + + } else if ( binding.isStorageBuffer ) { +@@ -32918,12 +33144,13 @@ class Bindings extends DataMap { * Updates the given array of bindings. * * @param {Array} bindings - The bind groups. @@ -638,7 +675,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -32934,11 +33157,27 @@ class Bindings extends DataMap { +@@ -32934,11 +33161,27 @@ class Bindings extends DataMap { * * @param {BindGroup} bindGroup - The bind group to update. * @param {Array} bindings - The bind groups. @@ -667,7 +704,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed let needsBindingsUpdate = false; let cacheBindings = true; let cacheIndex = 0; -@@ -32948,7 +33187,7 @@ class Bindings extends DataMap { +@@ -32948,7 +33191,7 @@ class Bindings extends DataMap { for ( const binding of bindGroup.bindings ) { @@ -676,7 +713,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // every uniforms group is a uniform buffer. So if no update is required, // we move one with the next binding. Otherwise the next if block will update the group. -@@ -33952,7 +34191,8 @@ class RenderContexts { +@@ -33952,7 +34195,8 @@ class RenderContexts { } @@ -686,35 +723,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed renderState.clearDepthValue = this.renderer.getClearDepth(); renderState.clearStencilValue = this.renderer.getClearStencil(); -@@ -34020,6 +34260,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -34169,7 +34413,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34181,6 +34434,21 @@ class Textures extends DataMap { +@@ -34181,6 +34425,21 @@ class Textures extends DataMap { backend.destroyTexture( texture ); @@ -736,198 +745,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } // -@@ -34318,7 +34586,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34328,7 +34605,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34345,6 +34622,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34377,9 +34665,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -34557,6 +34970,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); -@@ -39725,7 +40140,8 @@ class VelocityNode extends TempNode { +@@ -39725,7 +39984,8 @@ class VelocityNode extends TempNode { const previousModelMatrix = getPreviousMatrix( object ); @@ -937,7 +755,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // -@@ -40270,7 +40686,7 @@ class PassMultipleTextureNode extends PassTextureNode { +@@ -40270,7 +40530,7 @@ class PassMultipleTextureNode extends PassTextureNode { * @param {string} textureName - The output texture name. * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. */ @@ -946,7 +764,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // null is passed to the super call since this class does not // use an external texture for rendering pass data into. Instead -@@ -40291,6 +40707,8 @@ class PassMultipleTextureNode extends PassTextureNode { +@@ -40291,6 +40551,8 @@ class PassMultipleTextureNode extends PassTextureNode { * @type {boolean} */ this.previousTexture = previousTexture; @@ -955,7 +773,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed /** * This flag can be used for type testing. -@@ -40312,17 +40730,33 @@ class PassMultipleTextureNode extends PassTextureNode { +@@ -40312,17 +40574,33 @@ class PassMultipleTextureNode extends PassTextureNode { } @@ -990,7 +808,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed newNode.uvNode = this.uvNode; newNode.levelNode = this.levelNode; newNode.biasNode = this.biasNode; -@@ -40503,6 +40937,8 @@ class PassNode extends TempNode { +@@ -40503,6 +40781,8 @@ class PassNode extends TempNode { * @type {Object} */ this._textureNodes = {}; @@ -999,7 +817,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed /** * A dictionary holding the internal depth nodes. -@@ -40809,6 +41245,7 @@ class PassNode extends TempNode { +@@ -40809,6 +41089,7 @@ class PassNode extends TempNode { this._previousTextures[ name ] = texture; this._textureNodes[ name ].updateTexture(); @@ -1007,7 +825,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed this._previousTextureNodes[ name ].updateTexture(); } -@@ -40821,6 +41258,17 @@ class PassNode extends TempNode { +@@ -40821,6 +41102,17 @@ class PassNode extends TempNode { * @param {string} [name='output'] - The output name to get the texture node for. * @return {TextureNode} The texture node. */ @@ -1025,7 +843,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed getTextureNode( name = 'output' ) { let textureNode = this._textureNodes[ name ]; -@@ -41163,6 +41611,7 @@ class PassNode extends TempNode { +@@ -41163,6 +41455,7 @@ class PassNode extends TempNode { */ dispose() { @@ -1033,7 +851,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed this.renderTarget.dispose(); } -@@ -44431,6 +44880,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ +@@ -44431,6 +44724,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); @@ -1080,7 +898,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed /** * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map * with a binary `[0,1]` result. -@@ -44485,8 +44974,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, +@@ -44485,8 +44818,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, }; @@ -1091,7 +909,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const texelSize = vec2( 1 ).div( mapSize ); const radiusScaled = radius.mul( texelSize.x ); -@@ -44517,7 +45006,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, +@@ -44517,7 +44850,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, */ const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { @@ -1100,7 +918,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const texelSize = vec2( 1 ).div( mapSize ); -@@ -45586,8 +46075,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) +@@ -45586,8 +45919,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) */ const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { @@ -1111,7 +929,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const texelSize = radius.div( mapSize.x ); -@@ -45623,9 +46112,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo +@@ -45623,9 +45956,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo const shadowPositionAbs = shadowPosition.abs().toConst(); const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); @@ -1124,7 +942,15 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const result = float( 1.0 ).toVar(); -@@ -53841,7 +54330,7 @@ class NodeBuilder { +@@ -48407,6 +48740,7 @@ var TSL = /*#__PURE__*/Object.freeze({ + getShIrradianceAt: getShIrradianceAt, + getShadowMaterial: getShadowMaterial, + getShadowRenderObjectFunction: getShadowRenderObjectFunction, ++ getShadowUniform: getShadowUniform, + getTextureIndex: getTextureIndex, + getViewPosition: getViewPosition, + ggxConvolution: ggxConvolution, +@@ -53841,7 +54175,7 @@ class NodeBuilder { const mrt = this.renderer.getMRT(); @@ -1133,7 +959,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -55244,15 +55733,17 @@ class NodeManager extends DataMap { +@@ -55244,15 +55578,17 @@ class NodeManager extends DataMap { * Returns `true` if the given node uniforms group must be updated or not. * * @param {NodeUniformsGroup} nodeUniformsGroup - The node uniforms group. @@ -1154,7 +980,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // check for update -@@ -56056,6 +56547,21 @@ class NodeManager extends DataMap { +@@ -56056,6 +56392,21 @@ class NodeManager extends DataMap { } @@ -1176,7 +1002,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed /** * Triggers the call of `updateBefore()` methods * for all nodes of the given render object. -@@ -56065,9 +56571,14 @@ class NodeManager extends DataMap { +@@ -56065,9 +56416,14 @@ class NodeManager extends DataMap { updateBefore( renderObject ) { const nodeBuilder = renderObject.getNodeBuilderState(); @@ -1191,7 +1017,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // update frame state for each node this.getNodeFrameForRender( renderObject ).updateBeforeNode( node ); -@@ -56085,10 +56596,11 @@ class NodeManager extends DataMap { +@@ -56085,10 +56441,11 @@ class NodeManager extends DataMap { updateAfter( renderObject ) { const nodeBuilder = renderObject.getNodeBuilderState(); @@ -1204,7 +1030,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed this.getNodeFrameForRender( renderObject ).updateAfterNode( node ); -@@ -56125,9 +56637,14 @@ class NodeManager extends DataMap { +@@ -56125,9 +56482,14 @@ class NodeManager extends DataMap { const nodeFrame = this.getNodeFrameForRender( renderObject ); const nodeBuilder = renderObject.getNodeBuilderState(); @@ -1219,7 +1045,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed nodeFrame.updateNode( node ); } -@@ -60919,6 +61436,7 @@ class Renderer { +@@ -60919,6 +61281,7 @@ class Renderer { // renderContext.camera = camera; @@ -1227,7 +1053,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed this.backend.beginRender( renderContext ); // process render lists -@@ -60931,13 +61449,23 @@ class Renderer { +@@ -60931,13 +61294,23 @@ class Renderer { opaque: opaqueObjects } = renderList; @@ -1242,10 +1068,10 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed + renderComplete = true; + + } finally { -+ -+ try { - this.backend.finishRender( renderContext ); ++ try { ++ + if ( renderComplete ) this.backend.finishRender( renderContext ); + else if ( this.backend.get( renderContext )._tnDepth ) this.backend._abortTransientDepth( renderContext ); + @@ -1253,7 +1079,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // restore render tree -@@ -60952,6 +61480,9 @@ class Renderer { +@@ -60952,6 +61325,9 @@ class Renderer { this._callDepth --; @@ -1263,7 +1089,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( frameBufferTarget !== null ) { this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel ); -@@ -61118,6 +61649,18 @@ class Renderer { +@@ -61118,6 +61494,18 @@ class Renderer { } @@ -1282,22 +1108,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed /** * Can be used to transfer buffer data from a storage buffer attribute * from the GPU to the CPU in context of compute shaders. -@@ -62111,6 +62654,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -62731,6 +63282,7 @@ class Renderer { +@@ -62731,6 +63119,7 @@ class Renderer { let materialDisplacementMap; let materialDisplacementScale; let materialDisplacementBias; @@ -1305,7 +1116,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const previousSourceMaterial = this._currentSourceMaterial; -@@ -62756,6 +63308,7 @@ class Renderer { +@@ -62756,6 +63145,7 @@ class Renderer { materialDisplacementMap = overrideMaterial.displacementMap; materialDisplacementScale = overrideMaterial.displacementScale; materialDisplacementBias = overrideMaterial.displacementBias; @@ -1313,7 +1124,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( material.positionNode && material.positionNode.isNode ) { -@@ -62763,7 +63316,15 @@ class Renderer { +@@ -62763,7 +63153,15 @@ class Renderer { } @@ -1330,7 +1141,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed overrideMaterial.alphaMap = material.alphaMap; overrideMaterial.displacementMap = material.displacementMap; overrideMaterial.displacementScale = material.displacementScale; -@@ -62825,6 +63386,7 @@ class Renderer { +@@ -62825,6 +63223,7 @@ class Renderer { scene.overrideMaterial.displacementMap = materialDisplacementMap; scene.overrideMaterial.displacementScale = materialDisplacementScale; scene.overrideMaterial.displacementBias = materialDisplacementBias; @@ -1338,15 +1149,15 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -62886,18 +63448,17 @@ class Renderer { +@@ -62886,18 +63285,17 @@ class Renderer { // - const needsRefresh = this._nodes.needsRefresh( renderObject ); +- +- if ( needsRefresh ) { + this._nodes.needsRefresh( renderObject ); -- if ( needsRefresh ) { -- - this._nodes.updateBefore( renderObject ); - - this._geometries.updateForRender( renderObject ); @@ -1367,7 +1178,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed this._pipelines.updateForRender( renderObject ); -@@ -62907,7 +63468,7 @@ class Renderer { +@@ -62907,7 +63305,7 @@ class Renderer { this.backend.draw( renderObject, this.info ); @@ -1376,7 +1187,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -76397,6 +76958,9 @@ class WebGPUTextureUtils { +@@ -76397,6 +76795,9 @@ class WebGPUTextureUtils { */ this._samplerCache = new Map(); @@ -1386,7 +1197,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } /** -@@ -76613,7 +77177,12 @@ class WebGPUTextureUtils { +@@ -76613,7 +77014,12 @@ class WebGPUTextureUtils { } @@ -1400,7 +1211,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed usage |= GPUTextureUsage.RENDER_ATTACHMENT; -@@ -76686,6 +77255,23 @@ class WebGPUTextureUtils { +@@ -76686,6 +77092,23 @@ class WebGPUTextureUtils { destroyTexture( texture, isDefaultTexture = false ) { const backend = this.backend; @@ -1424,7 +1235,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const textureData = backend.get( texture ); if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -76726,7 +77312,7 @@ class WebGPUTextureUtils { +@@ -76726,7 +77149,7 @@ class WebGPUTextureUtils { const colorTexture = canvasTarget.colorTexture; const colorTextureData = backend.get( colorTexture ); @@ -1433,7 +1244,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed return colorTextureData.texture; -@@ -76751,8 +77337,8 @@ class WebGPUTextureUtils { +@@ -76751,8 +77174,8 @@ class WebGPUTextureUtils { // @@ -1444,7 +1255,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed colorTexture.samples = samples; colorTextureData.texture = colorBuffer; -@@ -76775,64 +77361,59 @@ class WebGPUTextureUtils { +@@ -76775,64 +77198,59 @@ class WebGPUTextureUtils { const canvasTarget = backend.renderer.getCanvasTarget(); const { width, height } = backend.getDrawingBufferSize(); const samples = backend.renderer.currentSamples; @@ -1494,12 +1305,12 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } - if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { + // Output and direct overlay passes alternate effective samples 4 and 1. + // Retain at most those two buffers for this canvas size and depth format. + let buffer = cache.textures.get( sampleCount ); +- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { +- - return depthTextureGPU; - - } @@ -1537,151 +1348,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed /** * Uploads the updated texture data to the GPU. * -@@ -76987,6 +77568,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77060,6 +77778,8 @@ class WebGPUTextureUtils { +@@ -77060,6 +77478,8 @@ class WebGPUTextureUtils { */ dispose() { @@ -1690,7 +1357,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed this._samplerCache.clear(); } -@@ -78986,7 +79706,7 @@ class WGSLNodeBuilder extends NodeBuilder { +@@ -78986,7 +79406,7 @@ class WGSLNodeBuilder extends NodeBuilder { * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @return {string} The WGSL snippet. */ @@ -1699,7 +1366,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( levelSnippet === null ) levelSnippet = '0u'; -@@ -79004,7 +79724,14 @@ class WGSLNodeBuilder extends NodeBuilder { +@@ -79004,7 +79424,14 @@ class WGSLNodeBuilder extends NodeBuilder { } else { @@ -1715,7 +1382,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79545,7 +80272,7 @@ class WGSLNodeBuilder extends NodeBuilder { +@@ -79545,7 +79972,7 @@ class WGSLNodeBuilder extends NodeBuilder { // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) // Also textureGather always need sampler. @@ -1724,7 +1391,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( needsSampler ) { -@@ -79586,7 +80313,8 @@ class WGSLNodeBuilder extends NodeBuilder { +@@ -79586,7 +80013,8 @@ class WGSLNodeBuilder extends NodeBuilder { uniformGPU = buffer; @@ -1734,7 +1401,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } else { -@@ -79974,7 +80702,8 @@ ${ flowData.code } +@@ -79974,7 +80402,8 @@ ${ flowData.code } for ( const { name, property, type } of builtins.values() ) { @@ -1744,7 +1411,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -80230,6 +80959,8 @@ ${ flowData.code } +@@ -80230,6 +80659,8 @@ ${ flowData.code } getVaryings( shaderStage ) { const snippets = []; @@ -1753,7 +1420,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( shaderStage === 'vertex' ) { -@@ -80252,7 +80983,12 @@ ${ flowData.code } +@@ -80252,7 +80683,12 @@ ${ flowData.code } let attributesSnippet = `@location( ${ varyingIndex ++ } )`; @@ -1767,7 +1434,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; -@@ -80329,9 +81065,12 @@ ${ flowData.code } +@@ -80329,9 +80765,12 @@ ${ flowData.code } const textureNode = uniform.node; const texture = textureNode.value; @@ -1783,7 +1450,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( needsSampler ) { -@@ -80353,7 +81092,7 @@ ${ flowData.code } +@@ -80353,7 +80792,7 @@ ${ flowData.code } const { primarySamples } = backend.utils.getTextureSampleData( texture ); @@ -1792,7 +1459,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed multisampled = '_multisampled'; -@@ -81107,7 +81846,19 @@ class WebGPUAttributeUtils { +@@ -81107,7 +81546,19 @@ class WebGPUAttributeUtils { buffer.unmap(); @@ -1812,7 +1479,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -81128,6 +81879,13 @@ class WebGPUAttributeUtils { +@@ -81128,6 +81579,13 @@ class WebGPUAttributeUtils { const bufferData = backend.get( bufferAttribute ); const buffer = backend.get( bufferAttribute ).buffer; @@ -1826,7 +1493,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed let array = bufferAttribute.array; const itemSize = bufferData._itemSize; -@@ -81209,6 +81967,9 @@ class WebGPUAttributeUtils { +@@ -81209,6 +81667,9 @@ class WebGPUAttributeUtils { } @@ -1836,25 +1503,25 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } /** -@@ -81295,9 +82056,15 @@ class WebGPUAttributeUtils { +@@ -81295,9 +81756,15 @@ class WebGPUAttributeUtils { destroyAttribute( attribute ) { const backend = this.backend; - const data = backend.get( this._getBufferAttribute( attribute ) ); + const bufferAttribute = this._getBufferAttribute( attribute ); + const data = backend.get( bufferAttribute ); - -- data.buffer.destroy(); ++ + // An interleaved attribute's GPU buffer lives on its InterleavedBuffer, and several attributes + // share it: destroy it once and forget it there too, as the WebGL backend does, or the next + // createAttribute() finds the destroyed buffer and submits it (threenative patch). + if ( data.buffer !== undefined ) data.buffer.destroy(); -+ + +- data.buffer.destroy(); + if ( bufferAttribute !== attribute ) backend.delete( bufferAttribute ); backend.delete( attribute ); -@@ -81598,18 +82365,16 @@ class WebGPUBindingUtils { +@@ -81598,18 +82065,16 @@ class WebGPUBindingUtils { const bindingsData = backend.get( bindGroup ); @@ -1878,7 +1545,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // try to find an existing layout in the cache -@@ -81682,6 +82447,7 @@ class WebGPUBindingUtils { +@@ -81682,6 +82147,7 @@ class WebGPUBindingUtils { } @@ -1886,7 +1553,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed bindingsData.group = bindGroupGPU; } -@@ -81701,6 +82467,7 @@ class WebGPUBindingUtils { +@@ -81701,6 +82167,7 @@ class WebGPUBindingUtils { const updateRanges = binding.updateRanges; @@ -1894,7 +1561,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( updateRanges.length === 0 ) { device.queue.writeBuffer( -@@ -81754,6 +82521,15 @@ class WebGPUBindingUtils { +@@ -81754,6 +82221,15 @@ class WebGPUBindingUtils { } @@ -1910,7 +1577,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } /** -@@ -81830,7 +82606,20 @@ class WebGPUBindingUtils { +@@ -81830,7 +82306,20 @@ class WebGPUBindingUtils { let resourceGPU; @@ -1932,7 +1599,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); -@@ -81904,6 +82693,12 @@ class WebGPUBindingUtils { +@@ -81904,6 +82393,12 @@ class WebGPUBindingUtils { } const bindGroupGPU = device.createBindGroup( _bindGroupDescriptor ); @@ -1945,7 +1612,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed _bindGroupDescriptor.reset(); -@@ -82001,7 +82796,7 @@ class WebGPUBindingUtils { +@@ -82001,7 +82496,7 @@ class WebGPUBindingUtils { const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); @@ -1954,7 +1621,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed texture.multisampled = true; -@@ -82039,7 +82834,7 @@ class WebGPUBindingUtils { +@@ -82039,7 +82534,7 @@ class WebGPUBindingUtils { } else if ( type === FloatType ) { @@ -1963,7 +1630,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed texture.sampleType = GPUTextureSampleType.Float; -@@ -82131,6 +82926,9 @@ class WebGPUBindingUtils { +@@ -82131,6 +82626,9 @@ class WebGPUBindingUtils { } @@ -1973,7 +1640,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } /** -@@ -82502,6 +83300,16 @@ class WebGPUPipelineUtils { +@@ -82502,6 +83000,16 @@ class WebGPUPipelineUtils { } @@ -1990,7 +1657,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // create pipeline device.pushErrorScope( 'validation' ); -@@ -82515,6 +83323,7 @@ class WebGPUPipelineUtils { +@@ -82515,6 +83023,7 @@ class WebGPUPipelineUtils { if ( promises === null ) { pipelineData.pipeline = device.createRenderPipeline( _renderPipelineDescriptor ); @@ -1998,7 +1665,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed _renderPipelineDescriptor.reset(); -@@ -82553,6 +83362,15 @@ class WebGPUPipelineUtils { +@@ -82553,6 +83062,15 @@ class WebGPUPipelineUtils { _renderPipelineDescriptor.reset(); @@ -2014,7 +1681,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( pipelinePromise !== null ) { try { -@@ -82567,7 +83385,7 @@ class WebGPUPipelineUtils { +@@ -82567,7 +83085,7 @@ class WebGPUPipelineUtils { } @@ -2023,7 +1690,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( errorScope !== null || asyncError !== null ) { -@@ -83814,6 +84632,27 @@ class WebGPUBackend extends Backend { +@@ -83814,6 +84332,27 @@ class WebGPUBackend extends Backend { */ this.compatibilityMode = null; @@ -2051,7 +1718,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed /** * A reference to the device. * -@@ -83926,7 +84765,11 @@ class WebGPUBackend extends Backend { +@@ -83926,7 +84465,11 @@ class WebGPUBackend extends Backend { xrCompatible: renderer.xr.enabled }; @@ -2064,7 +1731,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( adapter === null ) { -@@ -84153,7 +84996,9 @@ class WebGPUBackend extends Backend { +@@ -84153,7 +84696,9 @@ class WebGPUBackend extends Backend { let descriptor = canvasData.descriptor; @@ -2075,7 +1742,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed descriptor = new GPURenderPassDescriptor(); descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84180,6 +85025,9 @@ class WebGPUBackend extends Backend { +@@ -84180,6 +84725,9 @@ class WebGPUBackend extends Backend { canvasData.descriptor = descriptor; canvasData.samples = samples; @@ -2085,7 +1752,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -84496,6 +85344,150 @@ class WebGPUBackend extends Backend { +@@ -84496,6 +85044,150 @@ class WebGPUBackend extends Backend { * * @param {RenderContext} renderContext - The render context. */ @@ -2236,7 +1903,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed beginRender( renderContext ) { const renderContextData = this.get( renderContext ); -@@ -84648,10 +85640,15 @@ class WebGPUBackend extends Backend { +@@ -84648,10 +85340,15 @@ class WebGPUBackend extends Backend { // @@ -2252,7 +1919,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // Layered render targets: prepare bundle encoders for each camera in the array camera. if ( this._isRenderCameraDepthArray( renderContext ) === true ) { -@@ -84945,6 +85942,8 @@ class WebGPUBackend extends Backend { +@@ -84945,6 +85642,8 @@ class WebGPUBackend extends Backend { } @@ -2261,7 +1928,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( occlusionQueryCount > 0 ) { const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results -@@ -84987,7 +85986,16 @@ class WebGPUBackend extends Backend { +@@ -84987,7 +85686,16 @@ class WebGPUBackend extends Backend { } @@ -2279,7 +1946,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // -@@ -85081,6 +86089,8 @@ class WebGPUBackend extends Backend { +@@ -85081,6 +85789,8 @@ class WebGPUBackend extends Backend { */ updateViewport( renderContext ) { @@ -2288,7 +1955,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const { currentPass } = this.get( renderContext ); const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; -@@ -85095,6 +86105,8 @@ class WebGPUBackend extends Backend { +@@ -85095,6 +85805,8 @@ class WebGPUBackend extends Backend { */ updateScissor( renderContext ) { @@ -2297,7 +1964,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const { currentPass } = this.get( renderContext ); const { x, y, width, height } = renderContext.scissorValue; -@@ -85461,13 +86473,12 @@ class WebGPUBackend extends Backend { +@@ -85461,13 +86173,12 @@ class WebGPUBackend extends Backend { for ( let i = 0, l = bindings.length; i < l; i ++ ) { const bindGroup = bindings[ i ]; @@ -2314,7 +1981,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -85593,6 +86604,11 @@ class WebGPUBackend extends Backend { +@@ -85593,6 +86304,11 @@ class WebGPUBackend extends Backend { } else { passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 ); @@ -2326,7 +1993,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -85628,6 +86644,19 @@ class WebGPUBackend extends Backend { +@@ -85628,6 +86344,19 @@ class WebGPUBackend extends Backend { // vertex buffers const vertexBuffers = renderObject.getVertexBuffers(); @@ -2346,7 +2013,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) { -@@ -85766,6 +86795,7 @@ class WebGPUBackend extends Backend { +@@ -85766,6 +86495,7 @@ class WebGPUBackend extends Backend { const utils = this.utils; const sampleCount = utils.getSampleCountRenderContext( renderObject.context ); @@ -2354,7 +2021,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const colorSpace = utils.getCurrentColorSpace( renderObject.context ); const colorFormat = utils.getCurrentColorFormat( renderObject.context ); const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context ); -@@ -85787,7 +86817,8 @@ class WebGPUBackend extends Backend { +@@ -85787,7 +86517,8 @@ class WebGPUBackend extends Backend { data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat || data.primitiveTopology !== primitiveTopology || data.frontFaceCW !== frontFaceCW || @@ -2364,7 +2031,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed ) { data.material = material; data.materialVersion = material.version; -@@ -85807,6 +86838,7 @@ class WebGPUBackend extends Backend { +@@ -85807,6 +86538,7 @@ class WebGPUBackend extends Backend { data.primitiveTopology = primitiveTopology; data.frontFaceCW = frontFaceCW; data.clippingContextCacheKey = renderObject.clippingContextCacheKey; @@ -2372,7 +2039,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed needsUpdate = true; -@@ -85828,6 +86860,7 @@ class WebGPUBackend extends Backend { +@@ -85828,6 +86560,7 @@ class WebGPUBackend extends Backend { const utils = this.utils; const renderContext = renderObject.context; @@ -2380,7 +2047,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed // meshes with negative scale have a different frontFace render pipeline // descriptor value so the following must be honored in the cache key -@@ -85846,9 +86879,10 @@ class WebGPUBackend extends Backend { +@@ -85846,9 +86579,10 @@ class WebGPUBackend extends Backend { material.side, frontFaceCW, utils.getSampleCountRenderContext( renderContext ), @@ -2392,20 +2059,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed renderObject.clippingContextCacheKey ].join(); -@@ -85916,6 +86950,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -@@ -86017,6 +87057,8 @@ class WebGPUBackend extends Backend { +@@ -86017,6 +86751,8 @@ class WebGPUBackend extends Backend { _shaderModuleDescriptor.label = program.stage + ( program.name !== '' ? `_${ program.name }` : '' ); _shaderModuleDescriptor.code = program.code; @@ -2414,7 +2068,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed programGPU.module = { module: this.device.createShaderModule( _shaderModuleDescriptor ), entryPoint: 'main' -@@ -86164,6 +87206,7 @@ class WebGPUBackend extends Backend { +@@ -86164,6 +86900,7 @@ class WebGPUBackend extends Backend { _bufferDescriptor.reset(); uniformBufferData.buffer = bufferGPU; @@ -2422,7 +2076,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed } -@@ -86430,6 +87473,7 @@ class WebGPUBackend extends Backend { +@@ -86430,6 +87167,7 @@ class WebGPUBackend extends Backend { submit( this.device, encoder.finish() ); @@ -2430,7 +2084,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed if ( dstLevel === 0 && dstTexture.generateMipmaps ) { this.textureUtils.generateMipmaps( dstTexture ); -@@ -86447,6 +87491,8 @@ class WebGPUBackend extends Backend { +@@ -86447,6 +87185,8 @@ class WebGPUBackend extends Backend { */ copyFramebufferToTexture( texture, renderContext, rectangle ) { @@ -2439,7 +2093,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed const renderContextData = this.get( renderContext ); let sourceGPU = null; -@@ -87073,7 +88119,20 @@ class RenderPipeline { +@@ -87073,7 +87813,20 @@ class RenderPipeline { const currentXR = renderer.xr.enabled; renderer.xr.enabled = false; @@ -2462,7 +2116,7 @@ index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..f9ea33ac39db20e96822fda6b90e2bed renderer.xr.enabled = currentXR; diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js -index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8eff685bd 100644 +index f707acc24f5711fdae065ce920b08435b0869c1b..e3efde006043ae4dec27f823cf223bf74c71c456 100644 --- a/build/three.webgpu.nodes.js +++ b/build/three.webgpu.nodes.js @@ -141,6 +141,15 @@ class NodeMaterialObserver { @@ -2731,27 +2385,36 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } else if ( levelSnippet ) { -@@ -18320,7 +18403,8 @@ function createInstanceMatrixNode( builder, instanceMatrix ) { +@@ -18318,13 +18401,8 @@ function createInstanceMatrixNode( builder, instanceMatrix ) { - const uniformBufferSize = matrixCount * 16 * 4; + } else { +- const uniformBufferSize = matrixCount * 16 * 4; +- - if ( uniformBufferSize <= builder.getUniformBufferLimit() ) { +- +- instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', matrixCount ).element( instanceIndex ); +- +- } else { + // threenative: the uniform path re-uploads every draw; the attribute path uploads on version change -+ if ( false ) { ++ { - instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', matrixCount ).element( instanceIndex ); + let interleaved = _matrixBuffers.get( instanceMatrix ); -@@ -18414,7 +18498,8 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { +@@ -18412,9 +18490,10 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { - const uniformBufferSize = Math.max( matrices.count, 1 ) * 16 * 4; + if ( ! isStorageMatrix ) { + +- const uniformBufferSize = Math.max( matrices.count, 1 ) * 16 * 4; ++ Math.max( matrices.count, 1 ) * 16 * 4; - if ( uniformBufferSize > builder.getUniformBufferLimit() ) { + // threenative: the uniform path re-uploads every draw; the attribute path uploads on version change -+ if ( true ) { ++ { interleavedMatrix = _matrixBuffers.get( matrices ); -@@ -18455,7 +18540,7 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { +@@ -18455,7 +18534,7 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { // Synchronization of dynamic buffer updates per frame if ( interleavedMatrix !== null || interleavedColor !== null ) { @@ -2760,29 +2423,33 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( interleavedMatrix !== null ) { -@@ -18496,11 +18581,28 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { +@@ -18496,11 +18575,32 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => { const instancedMesh = builder.object; - OnObjectUpdate( ( { object } ) => { + const updatePrevious = ( object, array ) => { + -+ const { previousInstanceMatrix } = _previousInstanceMatrices.get( object ); ++ const history = _previousInstanceMatrices.get( object ); ++ if ( array !== matrices.array && history.lastSource === array && history.lastVersion === matrices.version ) return; ++ const { previousInstanceMatrix } = history; + previousInstanceMatrix.array.set( array ); ++ history.lastSource = array; ++ history.lastVersion = matrices.version; + previousInstanceMatrix.needsUpdate = true; + const interleaved = _matrixBuffers.get( previousInstanceMatrix ); + if ( interleaved !== undefined ) interleaved.needsUpdate = true; + + }; -+ + +- const previousInstanceData = _previousInstanceMatrices.get( object ); + // Scheduled history must reach the GPU in this draw, before the geometry uploads. + OnBeforeObjectUpdate( ( { object } ) => { -- const previousInstanceData = _previousInstanceMatrices.get( object ); +- previousInstanceData.previousInstanceMatrix.array.set( matrices.array ); + const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ]; + if ( scheduledPrevious !== undefined ) updatePrevious( object, scheduledPrevious ); - -- previousInstanceData.previousInstanceMatrix.array.set( matrices.array ); ++ + } ); + + // Without a scheduler, retain Three's draw-time snapshot for the next draw. @@ -2792,24 +2459,189 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } ); -@@ -18745,7 +18847,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve +@@ -18599,6 +18699,7 @@ const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => { + const indirectId = getIndirectIndex( batchMesh._indirectTexture, int( batchingIdNode ) ); - } + const matricesTexture = batchMesh._matricesTexture; ++ const previousMatricesTexture = batchMesh._previousMatricesTexture ?? matricesTexture; -- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); -+ // The previous bones and world matrix must use the same frame's bind inverse. -+ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { + const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst(); + const j = float( indirectId ).mul( 4 ).toInt().toConst(); +@@ -18625,6 +18726,19 @@ const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => { + const bm = mat3( batchingMatrix ); + + positionLocal.assign( batchingMatrix.mul( positionLocal ) ); ++ if ( builder.needsPreviousData() ) { + -+ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; -+ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; ++ const previousBatchingMatrix = mat4( ++ textureLoad( previousMatricesTexture, ivec2( x, y ) ), ++ textureLoad( previousMatricesTexture, ivec2( x.add( 1 ), y ) ), ++ textureLoad( previousMatricesTexture, ivec2( x.add( 2 ), y ) ), ++ textureLoad( previousMatricesTexture, ivec2( x.add( 3 ), y ) ) ++ ); + -+ } ); ++ positionPrevious.assign( previousBatchingMatrix.mul( positionPrevious ).xyz ); ++ ++ } ++ + + const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) ); + +@@ -18745,7 +18859,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve + + } + +- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); ++ // The previous bones and world matrix must use the same frame's bind inverse. ++ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { ++ ++ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; ++ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; ++ ++ } ); + + return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); } -@@ -31165,6 +31275,14 @@ class Geometries extends DataMap { +@@ -18777,7 +18899,8 @@ const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => { + + if ( skeletonData !== undefined ) { + +- skeletonData.previousBoneMatrices.set( skeleton.boneMatrices ); ++ const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousBoneMatrices' ) ]; ++ skeletonData.previousBoneMatrices.set( scheduledPrevious ?? skeleton.boneMatrices ); + + } + +@@ -18844,7 +18967,8 @@ const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = null ], + + if ( state !== undefined ) { + +- state.previousBoneMatrices.set( skeleton.boneMatrices ); ++ const scheduledPrevious = skinnedMesh[ Symbol.for( 'threenative.velocity.previousBoneMatrices' ) ]; ++ state.previousBoneMatrices.set( scheduledPrevious ?? skeleton.boneMatrices ); + + } + +@@ -29911,6 +30035,18 @@ class RenderObject { + */ + this._sourceMaterial = renderer._currentSourceMaterial; + ++ /** ++ * The version of `_sourceMaterial` this render object was last validated against. ++ * ++ * An override material is shared by every object in the pass and is used as a per-object ++ * scratchpad, so its own `version` moves for reasons that have nothing to do with this ++ * render object. The source material's version is the signal that does. ++ * ++ * @type {number} ++ * @default 0 ++ */ ++ this.sourceVersion = ( this._sourceMaterial === null || this._sourceMaterial === undefined ) ? 0 : this._sourceMaterial.version; ++ + /** + * An event listener which is defined by `RenderObjects`. It performs + * clean up tasks when `dispose()` on this render object. +@@ -30098,7 +30234,7 @@ class RenderObject { + */ + getChainArray() { + +- return [ this.object, this.material, this.context, this.lightsNode ]; ++ return [ this.object, this.material, this.context, this.lightsNode, this._sourceMaterial ?? this.material ]; + + } + +@@ -30283,7 +30419,7 @@ class RenderObject { + * + * @return {string} The geometry cache key. + */ +- getGeometryCacheKey() { ++ getGeometryCacheKey( useMorphTargetIds = true ) { + + const { geometry } = this; + +@@ -30311,12 +30447,14 @@ class RenderObject { + const targets = geometry.morphAttributes[ name ]; + + cacheKey += 'morph-' + name + ','; ++ if ( ! useMorphTargetIds ) cacheKey += targets.length + ',' + geometry.morphTargetsRelative + ','; + + for ( let i = 0, l = targets.length; i < l; i ++ ) { + + const attribute = targets[ i ]; + +- cacheKey += attribute.id + ','; ++ cacheKey += useMorphTargetIds ? attribute.id + ',' : ++ attribute.count + ',' + attribute.itemSize + ','; + + } + +@@ -30548,6 +30686,7 @@ class RenderObject { + } + + cacheKey = hash$1( cacheKey, this.renderer.contextNode.id, this.renderer.contextNode.version ); ++ cacheKey = hash$1( cacheKey, this.context.sampleCount ); + + return cacheKey; + +@@ -30680,6 +30819,8 @@ class RenderObjects { + _chainKeys$3[ 1 ] = material; + _chainKeys$3[ 2 ] = renderContext; + _chainKeys$3[ 3 ] = lightsNode; ++ // Material groups share the shadow override, but must retain separate GPU bindings. ++ _chainKeys$3[ 4 ] = this.renderer._currentSourceMaterial ?? material; + + // + +@@ -30701,15 +30842,26 @@ class RenderObjects { + + renderObject.updateClipping( clippingContext ); + +- if ( renderObject.needsGeometryUpdate ) { ++ const geometryChanged = renderObject.needsGeometryUpdate; ++ ++ if ( geometryChanged ) { + + renderObject.setGeometry( object.geometry ); + + } + +- if ( renderObject.version !== material.version || renderObject.needsUpdate ) { ++ // When the renderer is drawing through an override material, that material is shared by ++ // the whole pass and mutated per object, so its `version` alone answers the wrong ++ // question. The source material this object actually carries answers the right one. ++ ++ const sourceMaterial = this.renderer._currentSourceMaterial; ++ const sourceTracked = sourceMaterial !== null && sourceMaterial !== undefined; ++ const sourceReplaced = sourceTracked && renderObject._sourceMaterial !== sourceMaterial; ++ const sourceChanged = sourceTracked && ( sourceReplaced || renderObject.sourceVersion !== sourceMaterial.version ); + +- if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) { ++ if ( geometryChanged || renderObject.version !== material.version || sourceChanged || renderObject.needsUpdate ) { ++ ++ if ( sourceReplaced || renderObject.initialCacheKey !== renderObject.getCacheKey() ) { + + renderObject.dispose(); + +@@ -30719,6 +30871,8 @@ class RenderObjects { + + renderObject.version = material.version; + ++ if ( sourceTracked ) renderObject.sourceVersion = sourceMaterial.version; ++ + } + + } +@@ -30731,6 +30885,7 @@ class RenderObjects { + _chainKeys$3[ 1 ] = null; + _chainKeys$3[ 2 ] = null; + _chainKeys$3[ 3 ] = null; ++ _chainKeys$3[ 4 ] = null; + + // + +@@ -31165,6 +31320,14 @@ class Geometries extends DataMap { */ this._geometryDisposeListeners = new Map(); @@ -2824,7 +2656,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } /** -@@ -31190,10 +31308,69 @@ class Geometries extends DataMap { +@@ -31190,10 +31353,69 @@ class Geometries extends DataMap { if ( this.has( renderObject ) === false ) this.initGeometry( renderObject ); @@ -2894,7 +2726,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 /** * Initializes the geometry of the given render object. * -@@ -32771,7 +32948,7 @@ class Bindings extends DataMap { +@@ -32771,7 +32993,7 @@ class Bindings extends DataMap { */ updateForRender( renderObject ) { @@ -2903,7 +2735,22 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } -@@ -32918,12 +33095,13 @@ class Bindings extends DataMap { +@@ -32832,10 +33054,14 @@ class Bindings extends DataMap { + + } else if ( binding.isSampledTexture ) { + ++ // Async node compilation can yield while a viewport node changes targets. ++ // Refresh its reference before creating the first GPU binding, not after. ++ binding.update(); + this.textures.updateTexture( binding.texture ); + + } else if ( binding.isSampler ) { + ++ binding.update(); + this.textures.updateSampler( binding ); + + } else if ( binding.isStorageBuffer ) { +@@ -32918,12 +33144,13 @@ class Bindings extends DataMap { * Updates the given array of bindings. * * @param {Array} bindings - The bind groups. @@ -2919,7 +2766,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } -@@ -32934,11 +33112,27 @@ class Bindings extends DataMap { +@@ -32934,11 +33161,27 @@ class Bindings extends DataMap { * * @param {BindGroup} bindGroup - The bind group to update. * @param {Array} bindings - The bind groups. @@ -2948,7 +2795,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 let needsBindingsUpdate = false; let cacheBindings = true; let cacheIndex = 0; -@@ -32948,7 +33142,7 @@ class Bindings extends DataMap { +@@ -32948,7 +33191,7 @@ class Bindings extends DataMap { for ( const binding of bindGroup.bindings ) { @@ -2957,35 +2804,17 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 // every uniforms group is a uniform buffer. So if no update is required, // we move one with the next binding. Otherwise the next if block will update the group. -@@ -34020,6 +34214,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } +@@ -33952,7 +34195,8 @@ class RenderContexts { -@@ -34169,7 +34367,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { + } -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; +- if ( renderTarget !== null ) renderState.sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; ++ const sampleCount = renderTarget !== null ? renderTarget.samples : this.renderer.currentSamples; ++ renderState.sampleCount = sampleCount === 0 ? 1 : sampleCount; - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -34181,6 +34388,21 @@ class Textures extends DataMap { + renderState.clearDepthValue = this.renderer.getClearDepth(); + renderState.clearStencilValue = this.renderer.getClearStencil(); +@@ -34181,6 +34425,21 @@ class Textures extends DataMap { backend.destroyTexture( texture ); @@ -3007,198 +2836,17 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } // -@@ -34318,7 +34540,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -34328,7 +34559,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -34345,6 +34576,17 @@ class Textures extends DataMap { +@@ -39725,7 +39984,8 @@ class VelocityNode extends TempNode { - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -34377,9 +34619,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } + const previousModelMatrix = getPreviousMatrix( object ); -@@ -34557,6 +34924,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { +- this.previousModelWorldMatrix.value.copy( previousModelMatrix ); ++ const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousWorldMatrix' ) ]; ++ this.previousModelWorldMatrix.value.copy( scheduledPrevious ?? previousModelMatrix ); -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { + // - const textureData = this.get( texture ); -@@ -40270,7 +40639,7 @@ class PassMultipleTextureNode extends PassTextureNode { +@@ -40270,7 +40530,7 @@ class PassMultipleTextureNode extends PassTextureNode { * @param {string} textureName - The output texture name. * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. */ @@ -3207,7 +2855,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 // null is passed to the super call since this class does not // use an external texture for rendering pass data into. Instead -@@ -40291,6 +40660,8 @@ class PassMultipleTextureNode extends PassTextureNode { +@@ -40291,6 +40551,8 @@ class PassMultipleTextureNode extends PassTextureNode { * @type {boolean} */ this.previousTexture = previousTexture; @@ -3216,7 +2864,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 /** * This flag can be used for type testing. -@@ -40312,17 +40683,33 @@ class PassMultipleTextureNode extends PassTextureNode { +@@ -40312,17 +40574,33 @@ class PassMultipleTextureNode extends PassTextureNode { } @@ -3251,7 +2899,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 newNode.uvNode = this.uvNode; newNode.levelNode = this.levelNode; newNode.biasNode = this.biasNode; -@@ -40503,6 +40890,8 @@ class PassNode extends TempNode { +@@ -40503,6 +40781,8 @@ class PassNode extends TempNode { * @type {Object} */ this._textureNodes = {}; @@ -3260,7 +2908,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 /** * A dictionary holding the internal depth nodes. -@@ -40809,6 +41198,7 @@ class PassNode extends TempNode { +@@ -40809,6 +41089,7 @@ class PassNode extends TempNode { this._previousTextures[ name ] = texture; this._textureNodes[ name ].updateTexture(); @@ -3268,7 +2916,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 this._previousTextureNodes[ name ].updateTexture(); } -@@ -40821,6 +41211,17 @@ class PassNode extends TempNode { +@@ -40821,6 +41102,17 @@ class PassNode extends TempNode { * @param {string} [name='output'] - The output name to get the texture node for. * @return {TextureNode} The texture node. */ @@ -3286,7 +2934,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 getTextureNode( name = 'output' ) { let textureNode = this._textureNodes[ name ]; -@@ -41163,6 +41564,7 @@ class PassNode extends TempNode { +@@ -41163,6 +41455,7 @@ class PassNode extends TempNode { */ dispose() { @@ -3294,7 +2942,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 this.renderTarget.dispose(); } -@@ -44431,6 +44833,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ +@@ -44431,6 +44724,46 @@ var RendererUtils = /*#__PURE__*/Object.freeze({ const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); @@ -3341,7 +2989,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 /** * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map * with a binary `[0,1]` result. -@@ -44485,8 +44927,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, +@@ -44485,8 +44818,8 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, }; @@ -3352,7 +3000,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 const texelSize = vec2( 1 ).div( mapSize ); const radiusScaled = radius.mul( texelSize.x ); -@@ -44517,7 +44959,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, +@@ -44517,7 +44850,7 @@ const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, */ const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { @@ -3361,7 +3009,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 const texelSize = vec2( 1 ).div( mapSize ); -@@ -45586,8 +46028,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) +@@ -45586,8 +45919,8 @@ const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp } ) */ const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { @@ -3372,7 +3020,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 const texelSize = radius.div( mapSize.x ); -@@ -45623,9 +46065,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo +@@ -45623,9 +45956,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo const shadowPositionAbs = shadowPosition.abs().toConst(); const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); @@ -3385,7 +3033,24 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 const result = float( 1.0 ).toVar(); -@@ -55244,15 +55686,17 @@ class NodeManager extends DataMap { +@@ -48407,6 +48740,7 @@ var TSL = /*#__PURE__*/Object.freeze({ + getShIrradianceAt: getShIrradianceAt, + getShadowMaterial: getShadowMaterial, + getShadowRenderObjectFunction: getShadowRenderObjectFunction, ++ getShadowUniform: getShadowUniform, + getTextureIndex: getTextureIndex, + getViewPosition: getViewPosition, + ggxConvolution: ggxConvolution, +@@ -53841,7 +54175,7 @@ class NodeBuilder { + + const mrt = this.renderer.getMRT(); + +- return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( this.object ).useVelocity === true; ++ return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( this.object ).useVelocity === true || this.object.userData?.useVelocity === true; + + } + +@@ -55244,15 +55578,17 @@ class NodeManager extends DataMap { * Returns `true` if the given node uniforms group must be updated or not. * * @param {NodeUniformsGroup} nodeUniformsGroup - The node uniforms group. @@ -3406,7 +3071,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 // check for update -@@ -56056,6 +56500,21 @@ class NodeManager extends DataMap { +@@ -56056,6 +56392,21 @@ class NodeManager extends DataMap { } @@ -3428,7 +3093,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 /** * Triggers the call of `updateBefore()` methods * for all nodes of the given render object. -@@ -56065,9 +56524,14 @@ class NodeManager extends DataMap { +@@ -56065,9 +56416,14 @@ class NodeManager extends DataMap { updateBefore( renderObject ) { const nodeBuilder = renderObject.getNodeBuilderState(); @@ -3443,7 +3108,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 // update frame state for each node this.getNodeFrameForRender( renderObject ).updateBeforeNode( node ); -@@ -56085,10 +56549,11 @@ class NodeManager extends DataMap { +@@ -56085,10 +56441,11 @@ class NodeManager extends DataMap { updateAfter( renderObject ) { const nodeBuilder = renderObject.getNodeBuilderState(); @@ -3456,7 +3121,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 this.getNodeFrameForRender( renderObject ).updateAfterNode( node ); -@@ -56125,9 +56590,14 @@ class NodeManager extends DataMap { +@@ -56125,9 +56482,14 @@ class NodeManager extends DataMap { const nodeFrame = this.getNodeFrameForRender( renderObject ); const nodeBuilder = renderObject.getNodeBuilderState(); @@ -3471,7 +3136,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 nodeFrame.updateNode( node ); } -@@ -60919,6 +61389,7 @@ class Renderer { +@@ -60919,6 +61281,7 @@ class Renderer { // renderContext.camera = camera; @@ -3479,7 +3144,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 this.backend.beginRender( renderContext ); // process render lists -@@ -60931,13 +61402,23 @@ class Renderer { +@@ -60931,13 +61294,23 @@ class Renderer { opaque: opaqueObjects } = renderList; @@ -3494,18 +3159,18 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 + renderComplete = true; + + } finally { -+ + +- this.backend.finishRender( renderContext ); + try { + + if ( renderComplete ) this.backend.finishRender( renderContext ); + else if ( this.backend.get( renderContext )._tnDepth ) this.backend._abortTransientDepth( renderContext ); - -- this.backend.finishRender( renderContext ); ++ + } finally { // restore render tree -@@ -60952,6 +61433,9 @@ class Renderer { +@@ -60952,6 +61325,9 @@ class Renderer { this._callDepth --; @@ -3515,7 +3180,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( frameBufferTarget !== null ) { this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel ); -@@ -61118,6 +61602,18 @@ class Renderer { +@@ -61118,6 +61494,18 @@ class Renderer { } @@ -3534,37 +3199,63 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 /** * Can be used to transfer buffer data from a storage buffer attribute * from the GPU to the CPU in context of compute shaders. -@@ -62111,6 +62607,14 @@ class Renderer { +@@ -62731,6 +63119,7 @@ class Renderer { + let materialDisplacementMap; + let materialDisplacementScale; + let materialDisplacementBias; ++ let materialAlphaTest; - } + const previousSourceMaterial = this._currentSourceMaterial; -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -62886,18 +63390,17 @@ class Renderer { +@@ -62756,6 +63145,7 @@ class Renderer { + materialDisplacementMap = overrideMaterial.displacementMap; + materialDisplacementScale = overrideMaterial.displacementScale; + materialDisplacementBias = overrideMaterial.displacementBias; ++ materialAlphaTest = overrideMaterial.alphaTest; + + if ( material.positionNode && material.positionNode.isNode ) { + +@@ -62763,7 +63153,15 @@ class Renderer { + + } + +- overrideMaterial.alphaTest = material.alphaTest; ++ // `Material.alphaTest`'s setter bumps `version` whenever alpha-test-ness flips, and this ++ // override material is shared by every caster in the pass. A scene that mixes alpha-tested ++ // casters with opaque ones therefore moves that shared version several times per frame, ++ // and every OTHER render object drawn with it then fails the version gate in ++ // `RenderObjects.get` and recomputes a full material cache key that always matches. ++ // Writing the backing field leaves the `alphaTest` getter — and so the cache key — still ++ // reporting this caster's value, while each render object tracks the staleness signal ++ // that belongs to it: its own source material's version. ++ overrideMaterial._alphaTest = material.alphaTest; + overrideMaterial.alphaMap = material.alphaMap; + overrideMaterial.displacementMap = material.displacementMap; + overrideMaterial.displacementScale = material.displacementScale; +@@ -62825,6 +63223,7 @@ class Renderer { + scene.overrideMaterial.displacementMap = materialDisplacementMap; + scene.overrideMaterial.displacementScale = materialDisplacementScale; + scene.overrideMaterial.displacementBias = materialDisplacementBias; ++ scene.overrideMaterial._alphaTest = materialAlphaTest; + + } + +@@ -62886,18 +63285,17 @@ class Renderer { // - const needsRefresh = this._nodes.needsRefresh( renderObject ); - - if ( needsRefresh ) { -- ++ this._nodes.needsRefresh( renderObject ); + - this._nodes.updateBefore( renderObject ); - - this._geometries.updateForRender( renderObject ); - - this._nodes.updateForRender( renderObject ); - this._bindings.updateForRender( renderObject ); -+ this._nodes.needsRefresh( renderObject ); - +- - } + // Every step of a draw runs, settled or not. What a settled static object skips is decided + // inside each of them - its OBJECT node updates, its object uniform group write and its @@ -3578,7 +3269,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 this._pipelines.updateForRender( renderObject ); -@@ -62907,7 +63410,7 @@ class Renderer { +@@ -62907,7 +63305,7 @@ class Renderer { this.backend.draw( renderObject, this.info ); @@ -3587,7 +3278,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } -@@ -76397,6 +76900,9 @@ class WebGPUTextureUtils { +@@ -76397,6 +76795,9 @@ class WebGPUTextureUtils { */ this._samplerCache = new Map(); @@ -3597,7 +3288,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } /** -@@ -76613,7 +77119,12 @@ class WebGPUTextureUtils { +@@ -76613,7 +77014,12 @@ class WebGPUTextureUtils { } @@ -3611,7 +3302,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 usage |= GPUTextureUsage.RENDER_ATTACHMENT; -@@ -76686,6 +77197,23 @@ class WebGPUTextureUtils { +@@ -76686,6 +77092,23 @@ class WebGPUTextureUtils { destroyTexture( texture, isDefaultTexture = false ) { const backend = this.backend; @@ -3635,7 +3326,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 const textureData = backend.get( texture ); if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); -@@ -76726,7 +77254,7 @@ class WebGPUTextureUtils { +@@ -76726,7 +77149,7 @@ class WebGPUTextureUtils { const colorTexture = canvasTarget.colorTexture; const colorTextureData = backend.get( colorTexture ); @@ -3644,7 +3335,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 return colorTextureData.texture; -@@ -76751,8 +77279,8 @@ class WebGPUTextureUtils { +@@ -76751,8 +77174,8 @@ class WebGPUTextureUtils { // @@ -3655,7 +3346,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 colorTexture.samples = samples; colorTextureData.texture = colorBuffer; -@@ -76775,64 +77303,59 @@ class WebGPUTextureUtils { +@@ -76775,64 +77198,59 @@ class WebGPUTextureUtils { const canvasTarget = backend.renderer.getCanvasTarget(); const { width, height } = backend.getDrawingBufferSize(); const samples = backend.renderer.currentSamples; @@ -3670,19 +3361,19 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 - depthTexture.samples === samples && - depthTexture.depth === depth && - depthTexture.stencil === stencil ) { -+ let cache = this._depthBuffers.get( depthTexture ); - +- - return backend.get( depthTexture ).texture; -+ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { - +- - } - - // - - const depthTextureGPU = backend.get( depthTexture ).texture; -- ++ let cache = this._depthBuffers.get( depthTexture ); + - let format, type; -- ++ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { + - if ( stencil ) { + this.destroyTexture( depthTexture ); + cache = undefined; @@ -3705,12 +3396,12 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } - if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { + // Output and direct overlay passes alternate effective samples 4 and 1. + // Retain at most those two buffers for this canvas size and depth format. + let buffer = cache.textures.get( sampleCount ); +- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { +- - return depthTextureGPU; - - } @@ -3748,151 +3439,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 /** * Uploads the updated texture data to the GPU. * -@@ -76987,6 +77510,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -77060,6 +77720,8 @@ class WebGPUTextureUtils { +@@ -77060,6 +77478,8 @@ class WebGPUTextureUtils { */ dispose() { @@ -3901,7 +3448,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 this._samplerCache.clear(); } -@@ -78986,7 +79648,7 @@ class WGSLNodeBuilder extends NodeBuilder { +@@ -78986,7 +79406,7 @@ class WGSLNodeBuilder extends NodeBuilder { * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. * @return {string} The WGSL snippet. */ @@ -3910,7 +3457,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( levelSnippet === null ) levelSnippet = '0u'; -@@ -79004,7 +79666,14 @@ class WGSLNodeBuilder extends NodeBuilder { +@@ -79004,7 +79424,14 @@ class WGSLNodeBuilder extends NodeBuilder { } else { @@ -3926,7 +3473,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { -@@ -79545,7 +80214,7 @@ class WGSLNodeBuilder extends NodeBuilder { +@@ -79545,7 +79972,7 @@ class WGSLNodeBuilder extends NodeBuilder { // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) // Also textureGather always need sampler. @@ -3935,7 +3482,17 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( needsSampler ) { -@@ -79974,7 +80643,8 @@ ${ flowData.code } +@@ -79586,7 +80013,8 @@ class WGSLNodeBuilder extends NodeBuilder { + + uniformGPU = buffer; + +- uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id; ++ // The base builder supplies a unique name within this program. A process-wide ++ // allocation id changes otherwise identical shader source for every buffer. + + } else { + +@@ -79974,7 +80402,8 @@ ${ flowData.code } for ( const { name, property, type } of builtins.values() ) { @@ -3945,7 +3502,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } -@@ -80230,6 +80900,8 @@ ${ flowData.code } +@@ -80230,6 +80659,8 @@ ${ flowData.code } getVaryings( shaderStage ) { const snippets = []; @@ -3954,7 +3511,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( shaderStage === 'vertex' ) { -@@ -80252,7 +80924,12 @@ ${ flowData.code } +@@ -80252,7 +80683,12 @@ ${ flowData.code } let attributesSnippet = `@location( ${ varyingIndex ++ } )`; @@ -3968,7 +3525,23 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; -@@ -80353,7 +81030,7 @@ ${ flowData.code } +@@ -80329,9 +80765,12 @@ ${ flowData.code } + const textureNode = uniform.node; + const texture = textureNode.value; + +- // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) +- // Also textureGather always need sampler. +- const needsSampler = texture.isCubeTexture === true || ( this.isUnfilterable( texture ) === false && textureNode.isStorageTextureNode !== true ) || textureNode.gatherNode !== null; ++ // The bind group layout freezes sampler inclusion once, in getUniformFromNode ++ // (nodeData.uniformGPU). Re-deriving it here from live texture filter state can ++ // disagree with that layout and shift every later binding index. ++ const nodeData = this.getDataFromNode( textureNode, shaderStage, this.globalCache ); ++ const uniformGPU = nodeData.uniformGPU; ++ const needsSampler = Array.isArray( uniformGPU ) && uniformGPU[ 0 ].isSampler === true && uniformGPU[ 0 ].isSampledTexture !== true; + + if ( needsSampler ) { + +@@ -80353,7 +80792,7 @@ ${ flowData.code } const { primarySamples } = backend.utils.getTextureSampleData( texture ); @@ -3977,7 +3550,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 multisampled = '_multisampled'; -@@ -81107,7 +81784,19 @@ class WebGPUAttributeUtils { +@@ -81107,7 +81546,19 @@ class WebGPUAttributeUtils { buffer.unmap(); @@ -3997,7 +3570,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } -@@ -81128,6 +81817,13 @@ class WebGPUAttributeUtils { +@@ -81128,6 +81579,13 @@ class WebGPUAttributeUtils { const bufferData = backend.get( bufferAttribute ); const buffer = backend.get( bufferAttribute ).buffer; @@ -4011,7 +3584,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 let array = bufferAttribute.array; const itemSize = bufferData._itemSize; -@@ -81209,6 +81905,9 @@ class WebGPUAttributeUtils { +@@ -81209,6 +81667,9 @@ class WebGPUAttributeUtils { } @@ -4021,7 +3594,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } /** -@@ -81295,9 +81994,15 @@ class WebGPUAttributeUtils { +@@ -81295,9 +81756,15 @@ class WebGPUAttributeUtils { destroyAttribute( attribute ) { const backend = this.backend; @@ -4039,7 +3612,31 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 backend.delete( attribute ); -@@ -81682,6 +82387,7 @@ class WebGPUBindingUtils { +@@ -81598,18 +82065,16 @@ class WebGPUBindingUtils { + + const bindingsData = backend.get( bindGroup ); + +- // check if the the bind group already has a layout ++ const entries = this._createLayoutEntries( bindGroup ); ++ const bindGroupLayoutKey = hashString( JSON.stringify( entries ) ); + + if ( bindingsData.layout ) { + +- return bindingsData.layout.layoutGPU; +- +- } ++ if ( bindingsData.layoutKey === bindGroupLayoutKey ) return bindingsData.layout.layoutGPU; + +- // if not, assing one ++ this.deleteBindGroupData( bindGroup ); + +- const entries = this._createLayoutEntries( bindGroup ); +- const bindGroupLayoutKey = hashString( JSON.stringify( entries ) ); ++ } + + // try to find an existing layout in the cache + +@@ -81682,6 +82147,7 @@ class WebGPUBindingUtils { } @@ -4047,7 +3644,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 bindingsData.group = bindGroupGPU; } -@@ -81701,6 +82407,7 @@ class WebGPUBindingUtils { +@@ -81701,6 +82167,7 @@ class WebGPUBindingUtils { const updateRanges = binding.updateRanges; @@ -4055,7 +3652,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( updateRanges.length === 0 ) { device.queue.writeBuffer( -@@ -81754,6 +82461,15 @@ class WebGPUBindingUtils { +@@ -81754,6 +82221,15 @@ class WebGPUBindingUtils { } @@ -4071,7 +3668,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } /** -@@ -81830,7 +82546,20 @@ class WebGPUBindingUtils { +@@ -81830,7 +82306,20 @@ class WebGPUBindingUtils { let resourceGPU; @@ -4093,7 +3690,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); -@@ -81904,6 +82633,12 @@ class WebGPUBindingUtils { +@@ -81904,6 +82393,12 @@ class WebGPUBindingUtils { } const bindGroupGPU = device.createBindGroup( _bindGroupDescriptor ); @@ -4106,7 +3703,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 _bindGroupDescriptor.reset(); -@@ -82001,7 +82736,7 @@ class WebGPUBindingUtils { +@@ -82001,7 +82496,7 @@ class WebGPUBindingUtils { const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); @@ -4115,7 +3712,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 texture.multisampled = true; -@@ -82039,7 +82774,7 @@ class WebGPUBindingUtils { +@@ -82039,7 +82534,7 @@ class WebGPUBindingUtils { } else if ( type === FloatType ) { @@ -4124,7 +3721,17 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 texture.sampleType = GPUTextureSampleType.Float; -@@ -82502,6 +83237,16 @@ class WebGPUPipelineUtils { +@@ -82131,6 +82626,9 @@ class WebGPUBindingUtils { + + } + ++ bindingsData.groups = []; ++ bindingsData.versions = []; ++ + } + + /** +@@ -82502,6 +83000,16 @@ class WebGPUPipelineUtils { } @@ -4141,7 +3748,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 // create pipeline device.pushErrorScope( 'validation' ); -@@ -82515,6 +83260,7 @@ class WebGPUPipelineUtils { +@@ -82515,6 +83023,7 @@ class WebGPUPipelineUtils { if ( promises === null ) { pipelineData.pipeline = device.createRenderPipeline( _renderPipelineDescriptor ); @@ -4149,7 +3756,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 _renderPipelineDescriptor.reset(); -@@ -82553,6 +83299,15 @@ class WebGPUPipelineUtils { +@@ -82553,6 +83062,15 @@ class WebGPUPipelineUtils { _renderPipelineDescriptor.reset(); @@ -4165,7 +3772,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( pipelinePromise !== null ) { try { -@@ -82567,7 +83322,7 @@ class WebGPUPipelineUtils { +@@ -82567,7 +83085,7 @@ class WebGPUPipelineUtils { } @@ -4174,7 +3781,48 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( errorScope !== null || asyncError !== null ) { -@@ -84153,7 +84908,9 @@ class WebGPUBackend extends Backend { +@@ -83814,6 +84332,27 @@ class WebGPUBackend extends Backend { + */ + this.compatibilityMode = null; + ++ /** ++ * A reference to the WebGPU instance that creates adapters. ++ * ++ * @type {?GPU} ++ * @default null ++ */ ++ this.gpu = null; ++ ++ /** ++ * A reference to the adapter this device was requested from. ++ * ++ * The adapter owns the wire instance every operation in flight completes on. Chromium ++ * collects an adapter the page stops referencing and the instance goes with it, so every ++ * operation still running completes with a shutdown the page reads as ++ * 'A valid external Instance reference no longer exists.' and presentation stops for. ++ * ++ * @type {?GPUAdapter} ++ * @default null ++ */ ++ this.adapter = null; ++ + /** + * A reference to the device. + * +@@ -83926,7 +84465,11 @@ class WebGPUBackend extends Backend { + xrCompatible: renderer.xr.enabled + }; + +- const adapter = ( typeof navigator !== 'undefined' ) ? await navigator.gpu.requestAdapter( adapterOptions ) : null; ++ this.gpu = ( typeof navigator !== 'undefined' ) ? navigator.gpu : null; ++ ++ const adapter = this.gpu !== null ? await this.gpu.requestAdapter( adapterOptions ) : null; ++ ++ this.adapter = adapter; + + if ( adapter === null ) { + +@@ -84153,7 +84696,9 @@ class WebGPUBackend extends Backend { let descriptor = canvasData.descriptor; @@ -4185,7 +3833,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 descriptor = new GPURenderPassDescriptor(); descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); -@@ -84180,6 +84937,9 @@ class WebGPUBackend extends Backend { +@@ -84180,6 +84725,9 @@ class WebGPUBackend extends Backend { canvasData.descriptor = descriptor; canvasData.samples = samples; @@ -4195,7 +3843,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } -@@ -84496,6 +85256,150 @@ class WebGPUBackend extends Backend { +@@ -84496,6 +85044,150 @@ class WebGPUBackend extends Backend { * * @param {RenderContext} renderContext - The render context. */ @@ -4346,7 +3994,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 beginRender( renderContext ) { const renderContextData = this.get( renderContext ); -@@ -84648,10 +85552,15 @@ class WebGPUBackend extends Backend { +@@ -84648,10 +85340,15 @@ class WebGPUBackend extends Backend { // @@ -4362,7 +4010,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 // Layered render targets: prepare bundle encoders for each camera in the array camera. if ( this._isRenderCameraDepthArray( renderContext ) === true ) { -@@ -84945,6 +85854,8 @@ class WebGPUBackend extends Backend { +@@ -84945,6 +85642,8 @@ class WebGPUBackend extends Backend { } @@ -4371,7 +4019,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( occlusionQueryCount > 0 ) { const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results -@@ -84987,7 +85898,16 @@ class WebGPUBackend extends Backend { +@@ -84987,7 +85686,16 @@ class WebGPUBackend extends Backend { } @@ -4389,7 +4037,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 // -@@ -85081,6 +86001,8 @@ class WebGPUBackend extends Backend { +@@ -85081,6 +85789,8 @@ class WebGPUBackend extends Backend { */ updateViewport( renderContext ) { @@ -4398,16 +4046,33 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 const { currentPass } = this.get( renderContext ); const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; -@@ -85095,6 +86017,8 @@ class WebGPUBackend extends Backend { - */ - updateScissor( renderContext ) { +@@ -85095,6 +85805,8 @@ class WebGPUBackend extends Backend { + */ + updateScissor( renderContext ) { + ++ this._restoreTransientDepth( renderContext ); ++ + const { currentPass } = this.get( renderContext ); + const { x, y, width, height } = renderContext.scissorValue; + +@@ -85461,13 +86173,12 @@ class WebGPUBackend extends Backend { + for ( let i = 0, l = bindings.length; i < l; i ++ ) { + + const bindGroup = bindings[ i ]; ++ const bindingsData = this.get( bindGroup ); + +- if ( currentBindingGroups[ i ] !== bindGroup.id ) { +- +- const bindingsData = this.get( bindGroup ); ++ if ( currentBindingGroups[ i ] !== bindingsData.group ) { + + passEncoderGPU.setBindGroup( i, bindingsData.group ); +- currentBindingGroups[ i ] = bindGroup.id; ++ currentBindingGroups[ i ] = bindingsData.group; -+ this._restoreTransientDepth( renderContext ); -+ - const { currentPass } = this.get( renderContext ); - const { x, y, width, height } = renderContext.scissorValue; + } -@@ -85593,6 +86517,11 @@ class WebGPUBackend extends Backend { +@@ -85593,6 +86304,11 @@ class WebGPUBackend extends Backend { } else { passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 ); @@ -4419,7 +4084,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } -@@ -85628,6 +86557,19 @@ class WebGPUBackend extends Backend { +@@ -85628,6 +86344,19 @@ class WebGPUBackend extends Backend { // vertex buffers const vertexBuffers = renderObject.getVertexBuffers(); @@ -4439,20 +4104,53 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) { -@@ -85916,6 +86858,12 @@ class WebGPUBackend extends Backend { +@@ -85766,6 +86495,7 @@ class WebGPUBackend extends Backend { + const utils = this.utils; - } + const sampleCount = utils.getSampleCountRenderContext( renderObject.context ); ++ const bindGroupLayoutKeys = renderObject.getBindings().map( bindGroup => this.get( bindGroup ).layoutKey ).join( '|' ); + const colorSpace = utils.getCurrentColorSpace( renderObject.context ); + const colorFormat = utils.getCurrentColorFormat( renderObject.context ); + const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context ); +@@ -85787,7 +86517,8 @@ class WebGPUBackend extends Backend { + data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat || + data.primitiveTopology !== primitiveTopology || + data.frontFaceCW !== frontFaceCW || +- data.clippingContextCacheKey !== renderObject.clippingContextCacheKey ++ data.clippingContextCacheKey !== renderObject.clippingContextCacheKey || ++ data.bindGroupLayoutKeys !== bindGroupLayoutKeys + ) { -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -@@ -86017,6 +86965,8 @@ class WebGPUBackend extends Backend { + data.material = material; data.materialVersion = material.version; +@@ -85807,6 +86538,7 @@ class WebGPUBackend extends Backend { + data.primitiveTopology = primitiveTopology; + data.frontFaceCW = frontFaceCW; + data.clippingContextCacheKey = renderObject.clippingContextCacheKey; ++ data.bindGroupLayoutKeys = bindGroupLayoutKeys; + + needsUpdate = true; + +@@ -85828,6 +86560,7 @@ class WebGPUBackend extends Backend { + + const utils = this.utils; + const renderContext = renderObject.context; ++ const bindGroupLayoutKeys = renderObject.getBindings().map( bindGroup => this.get( bindGroup ).layoutKey ).join( '|' ); + + // meshes with negative scale have a different frontFace render pipeline + // descriptor value so the following must be honored in the cache key +@@ -85846,9 +86579,10 @@ class WebGPUBackend extends Backend { + material.side, + frontFaceCW, + utils.getSampleCountRenderContext( renderContext ), ++ bindGroupLayoutKeys, + utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), + utils.getPrimitiveTopology( object, material ), +- renderObject.getGeometryCacheKey(), ++ renderObject.getGeometryCacheKey( false ), + renderObject.clippingContextCacheKey + ].join(); + +@@ -86017,6 +86751,8 @@ class WebGPUBackend extends Backend { _shaderModuleDescriptor.label = program.stage + ( program.name !== '' ? `_${ program.name }` : '' ); _shaderModuleDescriptor.code = program.code; @@ -4461,7 +4159,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 programGPU.module = { module: this.device.createShaderModule( _shaderModuleDescriptor ), entryPoint: 'main' -@@ -86164,6 +87114,7 @@ class WebGPUBackend extends Backend { +@@ -86164,6 +86900,7 @@ class WebGPUBackend extends Backend { _bufferDescriptor.reset(); uniformBufferData.buffer = bufferGPU; @@ -4469,7 +4167,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 } -@@ -86430,6 +87381,7 @@ class WebGPUBackend extends Backend { +@@ -86430,6 +87167,7 @@ class WebGPUBackend extends Backend { submit( this.device, encoder.finish() ); @@ -4477,7 +4175,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 if ( dstLevel === 0 && dstTexture.generateMipmaps ) { this.textureUtils.generateMipmaps( dstTexture ); -@@ -86447,6 +87399,8 @@ class WebGPUBackend extends Backend { +@@ -86447,6 +87185,8 @@ class WebGPUBackend extends Backend { */ copyFramebufferToTexture( texture, renderContext, rectangle ) { @@ -4486,7 +4184,7 @@ index f707acc24f5711fdae065ce920b08435b0869c1b..93cedce745291edfbd333fb76d8154b8 const renderContextData = this.get( renderContext ); let sourceGPU = null; -@@ -87020,7 +87974,20 @@ class RenderPipeline { +@@ -87020,7 +87760,20 @@ class RenderPipeline { const currentXR = renderer.xr.enabled; renderer.xr.enabled = false; @@ -4806,7 +4504,7 @@ index ab62e98dadab2539d7e1d663fd42267bbc8ebcaa..2cf4bb6039853d5c922e89041419250b - - diff --git a/src/nodes/accessors/Instance.js b/src/nodes/accessors/Instance.js -index b89834b9166c75fd946ade24db265bf8c367d244..18e2b7943393fd07b7989b1e3a5231a61ca24187 100644 +index b89834b9166c75fd946ade24db265bf8c367d244..3ff041a260cde0e5fb1880daddd4fc700a1c945e 100644 --- a/src/nodes/accessors/Instance.js +++ b/src/nodes/accessors/Instance.js @@ -1,6 +1,6 @@ @@ -4846,15 +4544,19 @@ index b89834b9166c75fd946ade24db265bf8c367d244..18e2b7943393fd07b7989b1e3a5231a6 if ( interleavedMatrix !== null ) { -@@ -216,11 +218,28 @@ export const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder +@@ -216,11 +218,32 @@ export const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder const instancedMesh = builder.object; - OnObjectUpdate( ( { object } ) => { + const updatePrevious = ( object, array ) => { + -+ const { previousInstanceMatrix } = _previousInstanceMatrices.get( object ); ++ const history = _previousInstanceMatrices.get( object ); ++ if ( array !== matrices.array && history.lastSource === array && history.lastVersion === matrices.version ) return; ++ const { previousInstanceMatrix } = history; + previousInstanceMatrix.array.set( array ); ++ history.lastSource = array; ++ history.lastVersion = matrices.version; + previousInstanceMatrix.needsUpdate = true; + const interleaved = _matrixBuffers.get( previousInstanceMatrix ); + if ( interleaved !== undefined ) interleaved.needsUpdate = true; @@ -5444,7 +5146,7 @@ index c900fa4c9654f8cf3512e96de52a2c1c83e189a6..4f8d58950b80cd56bbec22a61cf39a7a return cacheKey; diff --git a/src/renderers/common/RenderObjects.js b/src/renderers/common/RenderObjects.js -index 676cc2bd4169427ed420f2467b1e6684bd2aeebc..72569d0339314d26eb7a35ff2a8462a803c852a6 100644 +index 676cc2bd4169427ed420f2467b1e6684bd2aeebc..91fba38f3bafa35806bd3c82d1c79671a4a6b00d 100644 --- a/src/renderers/common/RenderObjects.js +++ b/src/renderers/common/RenderObjects.js @@ -97,6 +97,8 @@ class RenderObjects { @@ -5456,7 +5158,16 @@ index 676cc2bd4169427ed420f2467b1e6684bd2aeebc..72569d0339314d26eb7a35ff2a8462a8 // -@@ -124,9 +126,18 @@ class RenderObjects { +@@ -118,15 +120,26 @@ class RenderObjects { + + renderObject.updateClipping( clippingContext ); + +- if ( renderObject.needsGeometryUpdate ) { ++ const geometryChanged = renderObject.needsGeometryUpdate; ++ ++ if ( geometryChanged ) { + + renderObject.setGeometry( object.geometry ); } @@ -5464,20 +5175,20 @@ index 676cc2bd4169427ed420f2467b1e6684bd2aeebc..72569d0339314d26eb7a35ff2a8462a8 + // When the renderer is drawing through an override material, that material is shared by + // the whole pass and mutated per object, so its `version` alone answers the wrong + // question. The source material this object actually carries answers the right one. - -- if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) { ++ + const sourceMaterial = this.renderer._currentSourceMaterial; + const sourceTracked = sourceMaterial !== null && sourceMaterial !== undefined; + const sourceReplaced = sourceTracked && renderObject._sourceMaterial !== sourceMaterial; + const sourceChanged = sourceTracked && ( sourceReplaced || renderObject.sourceVersion !== sourceMaterial.version ); -+ -+ if ( renderObject.version !== material.version || sourceChanged || renderObject.needsUpdate ) { + +- if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) { ++ if ( geometryChanged || renderObject.version !== material.version || sourceChanged || renderObject.needsUpdate ) { + + if ( sourceReplaced || renderObject.initialCacheKey !== renderObject.getCacheKey() ) { renderObject.dispose(); -@@ -136,6 +147,8 @@ class RenderObjects { +@@ -136,6 +149,8 @@ class RenderObjects { renderObject.version = material.version; @@ -5486,7 +5197,7 @@ index 676cc2bd4169427ed420f2467b1e6684bd2aeebc..72569d0339314d26eb7a35ff2a8462a8 } } -@@ -148,6 +161,7 @@ class RenderObjects { +@@ -148,6 +163,7 @@ class RenderObjects { _chainKeys[ 1 ] = null; _chainKeys[ 2 ] = null; _chainKeys[ 3 ] = null; @@ -5521,7 +5232,7 @@ index ab3085cfdca456ffb3de6945d7e7f693e4ed33ae..94e54740e13088a9fa40ccfd30000232 renderer.xr.enabled = currentXR; diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js -index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..bc495cb324ed9923008fa5685aede53aee44b6d8 100644 +index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..f9903c26af499dd5178cacca1d66a260a5606352 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -1738,6 +1738,7 @@ class Renderer { @@ -5545,15 +5256,15 @@ index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..bc495cb324ed9923008fa5685aede53a - // finish render pass + renderComplete = true; - -- this.backend.finishRender( renderContext ); ++ + } finally { + + try { + + if ( renderComplete ) this.backend.finishRender( renderContext ); + else if ( this.backend.get( renderContext )._tnDepth ) this.backend._abortTransientDepth( renderContext ); -+ + +- this.backend.finishRender( renderContext ); + } finally { // restore render tree @@ -5587,22 +5298,7 @@ index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..bc495cb324ed9923008fa5685aede53a /** * Can be used to transfer buffer data from a storage buffer attribute * from the GPU to the CPU in context of compute shaders. -@@ -2930,6 +2956,14 @@ class Renderer { - - } - -+ // Explicit cold compressed-texture gate; ordinary initTexture keeps its synchronous path. -+ async prepareTextureAsync( texture, options = {} ) { -+ -+ if ( this._initialized === false ) throw new Error( 'Renderer must be initialized before texture preparation.' ); -+ await this._textures.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Initializes the given render target. - * -@@ -3550,6 +3584,7 @@ class Renderer { +@@ -3550,6 +3576,7 @@ class Renderer { let materialDisplacementMap; let materialDisplacementScale; let materialDisplacementBias; @@ -5610,7 +5306,7 @@ index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..bc495cb324ed9923008fa5685aede53a const previousSourceMaterial = this._currentSourceMaterial; -@@ -3575,6 +3610,7 @@ class Renderer { +@@ -3575,6 +3602,7 @@ class Renderer { materialDisplacementMap = overrideMaterial.displacementMap; materialDisplacementScale = overrideMaterial.displacementScale; materialDisplacementBias = overrideMaterial.displacementBias; @@ -5618,7 +5314,7 @@ index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..bc495cb324ed9923008fa5685aede53a if ( material.positionNode && material.positionNode.isNode ) { -@@ -3582,7 +3618,15 @@ class Renderer { +@@ -3582,7 +3610,15 @@ class Renderer { } @@ -5635,7 +5331,7 @@ index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..bc495cb324ed9923008fa5685aede53a overrideMaterial.alphaMap = material.alphaMap; overrideMaterial.displacementMap = material.displacementMap; overrideMaterial.displacementScale = material.displacementScale; -@@ -3644,6 +3688,7 @@ class Renderer { +@@ -3644,6 +3680,7 @@ class Renderer { scene.overrideMaterial.displacementMap = materialDisplacementMap; scene.overrideMaterial.displacementScale = materialDisplacementScale; scene.overrideMaterial.displacementBias = materialDisplacementBias; @@ -5643,19 +5339,19 @@ index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..bc495cb324ed9923008fa5685aede53a } -@@ -3705,18 +3750,17 @@ class Renderer { +@@ -3705,18 +3742,17 @@ class Renderer { // - const needsRefresh = this._nodes.needsRefresh( renderObject ); -+ this._nodes.needsRefresh( renderObject ); - +- - if ( needsRefresh ) { - - this._nodes.updateBefore( renderObject ); - - this._geometries.updateForRender( renderObject ); -- ++ this._nodes.needsRefresh( renderObject ); + - this._nodes.updateForRender( renderObject ); - this._bindings.updateForRender( renderObject ); - @@ -5672,7 +5368,7 @@ index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..bc495cb324ed9923008fa5685aede53a this._pipelines.updateForRender( renderObject ); -@@ -3726,7 +3770,7 @@ class Renderer { +@@ -3726,7 +3762,7 @@ class Renderer { this.backend.draw( renderObject, this.info ); @@ -5682,38 +5378,10 @@ index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..bc495cb324ed9923008fa5685aede53a } diff --git a/src/renderers/common/Textures.js b/src/renderers/common/Textures.js -index f42b90eac69ca042c39e7999773d6b8303fc3dd0..5b305173fa4a2814ec74563d2ae5cdc70e7505e1 100644 +index f42b90eac69ca042c39e7999773d6b8303fc3dd0..be47645f5662a2be8002c23de8ff4092bf561049 100644 --- a/src/renderers/common/Textures.js +++ b/src/renderers/common/Textures.js -@@ -54,6 +54,10 @@ class Textures extends DataMap { - * @type {Set} - */ - this._htmlTextures = new Set(); -+ this._pendingUploads = new Map(); -+ this._uploadTail = Promise.resolve(); -+ this._uploadSlice = { started: null }; -+ this._uploadDisposed = false; - - } - -@@ -203,7 +207,16 @@ class Textures extends DataMap { - */ - updateTexture( texture, options = {} ) { - -+ if ( this._pendingUploads.has( texture ) ) throw new Error( 'Compressed texture preparation is pending.' ); -+ if ( this.has( texture ) && this.get( texture ).uploadError ) throw this.get( texture ).uploadError; -+ return this._updateTexture( texture, options ); -+ -+ } -+ -+ _updateTexture( texture, options = {}, upload = null ) { -+ - const textureData = this.get( texture ); -+ let uploadPromise; - if ( textureData.initialized === true && textureData.version === texture.version ) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; -@@ -215,6 +228,21 @@ class Textures extends DataMap { +@@ -215,6 +215,21 @@ class Textures extends DataMap { backend.destroyTexture( texture ); @@ -5735,197 +5403,6 @@ index f42b90eac69ca042c39e7999773d6b8303fc3dd0..5b305173fa4a2814ec74563d2ae5cdc7 } // -@@ -352,7 +380,16 @@ class Textures extends DataMap { - - } - -- if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); -+ if ( texture.source.dataReady === true ) { -+ -+ if ( upload ) { -+ -+ this._initializeTexture( texture, textureData ); -+ uploadPromise = upload( texture, options ); -+ -+ } else backend.updateTexture( texture, options ); -+ -+ } - - const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false; - -@@ -362,7 +399,7 @@ class Textures extends DataMap { - - } - -- if ( texture.onUpdate ) texture.onUpdate( texture ); -+ if ( ! upload && texture.onUpdate ) texture.onUpdate( texture ); - - } - -@@ -379,6 +416,17 @@ class Textures extends DataMap { - - } - -+ this._initializeTexture( texture, textureData ); -+ if ( upload ) return uploadPromise; -+ -+ // -+ -+ textureData.version = texture.version; -+ -+ } -+ -+ _initializeTexture( texture, textureData ) { -+ - // dispose handler - - if ( textureData.initialized !== true ) { -@@ -411,9 +459,134 @@ class Textures extends DataMap { - - } - -- // - -- textureData.version = texture.version; -+ } -+ -+ // A single preparation lane, keyed by Texture identity; Source clones own distinct GPU data. -+ updateTextureAsync( texture, options = {} ) { -+ -+ if ( this._uploadDisposed ) return Promise.reject( new Error( 'Texture manager disposed.' ) ); -+ if ( this._uploadDeviceLost ) return Promise.reject( this._uploadDeviceLost ); -+ if ( ! this._uploadLossSubscribed && this.backend.device?.lost ) { -+ -+ this._uploadLossSubscribed = true; -+ const lost = error => { -+ -+ this._uploadDeviceLost = error instanceof Error ? error : new Error( 'Device lost during texture preparation.' ); -+ for ( const job of this._pendingUploads.values() ) job.controller.abort( this._uploadDeviceLost ); -+ -+ }; -+ this.backend.device.lost.then( lost, lost ); -+ -+ } -+ if ( options.signal?.aborted ) return Promise.reject( options.signal.reason ); -+ const pending = this._pendingUploads.get( texture ); -+ if ( pending ) { -+ -+ if ( pending.version !== texture.version ) return Promise.reject( new Error( 'Texture changed during preparation.' ) ); -+ return this._waitForUpload( pending, options.signal ); -+ -+ } -+ const data = this.get( texture ); -+ if ( data.uploadError ) return Promise.reject( data.uploadError ); -+ if ( ! ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) || texture.isCubeTexture || texture.version <= 0 || texture.source.dataReady !== true || texture.mipmaps.length === 0 || typeof this.backend.updateTextureAsync !== 'function' ) return Promise.reject( new Error( 'Bounded texture preparation requires ready compressed mip data.' ) ); -+ if ( data.initialized === true && data.version === texture.version ) { -+ -+ if ( data.isDefaultTexture !== true && this.backend.has( texture ) && this.backend.get( texture ).texture && this.backend.get( texture ).version === texture.version ) return Promise.resolve(); -+ return Promise.reject( new Error( 'Compressed texture content has not completed an upload.' ) ); -+ -+ } -+ if ( data.version !== undefined && data.isDefaultTexture !== true ) return Promise.reject( new Error( 'Bounded texture preparation requires a cold texture.' ) ); -+ const controller = new AbortController(); -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const image = texture.image; -+ const size = { width: image?.width, height: image?.height, depth: image?.depth }; -+ const layers = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const device = this.backend.device; -+ const job = { texture, data, version: texture.version, controller, users: 0, promise: null }; -+ const checkQueued = () => { -+ -+ if ( controller.signal.aborted ) throw controller.signal.reason; -+ if ( ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || texture.source !== source || source.dataReady !== true || texture.image !== image || image?.complete === false || image?.width !== size.width || image?.height !== size.height || image?.depth !== size.depth || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) || this.backend.device !== device ) throw new Error( 'Compressed texture changed or invalidated before preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== layers.length || layers.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed before preparation.' ); -+ -+ }; -+ let backendData, gpuTexture; -+ const onQueuedDispose = () => controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ texture.addEventListener( 'dispose', onQueuedDispose ); -+ this._pendingUploads.set( texture, job ); -+ const cancelled = new Promise( ( resolve, reject ) => controller.signal.addEventListener( 'abort', () => reject( controller.signal.reason ), { once: true } ) ); -+ const work = this._uploadTail.then( () => { -+ -+ checkQueued(); -+ const result = this._updateTexture( texture, {}, ( value, uploadOptions ) => this.backend.updateTextureAsync( value, { ...uploadOptions, budgetMs: options.budgetMs, maxBytesPerWrite: options.maxBytesPerWrite, signal: controller.signal, uploadSlice: this._uploadSlice } ) ); -+ if ( ! result || typeof result.then !== 'function' || ! this.backend.has( texture ) ) throw new Error( 'Compressed texture upload did not start.' ); -+ backendData = this.backend.get( texture ); -+ gpuTexture = backendData.texture; -+ return result; -+ -+ } ); -+ job.promise = Promise.race( [ work, cancelled ] ).then( () => { -+ -+ if ( controller.signal.aborted || ! this.has( texture ) || this.get( texture ) !== data || texture.version !== job.version || source.dataReady !== true || this.backend.device !== device || ! this.backend.has( texture ) || this.backend.get( texture ) !== backendData || backendData.texture !== gpuTexture || backendData.version !== job.version ) throw new Error( 'Texture preparation invalidated.' ); -+ data.version = job.version; -+ delete data.uploadError; -+ if ( texture.onUpdate ) texture.onUpdate( texture ); -+ -+ } ).catch( error => { -+ -+ if ( this.has( texture ) && this.get( texture ) === data ) data.uploadError = error; -+ throw error; -+ -+ } ).finally( () => { -+ -+ texture.removeEventListener( 'dispose', onQueuedDispose ); -+ if ( this._pendingUploads.get( texture ) === job ) this._pendingUploads.delete( texture ); -+ -+ } ); -+ // Caller cancellation is prompt; the backend lane stays owned until validation drains. -+ this._uploadTail = Promise.allSettled( [ work, job.promise ] ).then( () => {} ); -+ return this._waitForUpload( job, options.signal ); -+ -+ } -+ -+ _waitForUpload( job, signal ) { -+ -+ job.users ++; -+ return new Promise( ( resolve, reject ) => { -+ -+ let settled = false; -+ const finish = ( error ) => { -+ -+ if ( settled ) return; -+ settled = true; -+ signal?.removeEventListener( 'abort', abort ); -+ job.users --; -+ if ( error ) reject( error ); else resolve(); -+ if ( job.users === 0 && ! job.controller.signal.aborted ) job.controller.abort( new Error( 'Texture preparation has no remaining caller.' ) ); -+ -+ }; -+ const abort = () => finish( signal.reason || new Error( 'Texture preparation cancelled.' ) ); -+ signal?.addEventListener( 'abort', abort, { once: true } ); -+ job.promise.then( () => finish(), finish ); -+ -+ } ); -+ -+ } -+ -+ dispose() { -+ -+ this._uploadDisposed = true; -+ for ( const job of this._pendingUploads.values() ) { -+ -+ job.controller.abort( new Error( 'Texture manager disposed.' ) ); -+ if ( this.has( job.texture ) && this.get( job.texture ).initialized === true ) this._destroyTexture( job.texture ); -+ -+ } -+ this._pendingUploads.clear(); -+ super.dispose(); - - } - -@@ -591,6 +764,8 @@ class Textures extends DataMap { - */ - _destroyTexture( texture ) { - -+ this._pendingUploads.get( texture )?.controller.abort( new Error( 'Texture disposed during preparation.' ) ); -+ - if ( this.has( texture ) === true ) { - - const textureData = this.get( texture ); diff --git a/src/renderers/common/nodes/NodeManager.js b/src/renderers/common/nodes/NodeManager.js index f6d4f0b54e848b172c2c7e138a26e42b98c01978..033128c396c2cad5c5d1d362f2d261e9adbd6bdc 100644 --- a/src/renderers/common/nodes/NodeManager.js @@ -6017,7 +5494,7 @@ index f6d4f0b54e848b172c2c7e138a26e42b98c01978..033128c396c2cad5c5d1d362f2d261e9 } diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js -index 06e203693ed9b168a2bddf773cd8ce8dc23196e4..add7f3284d411fd3eb188c2568775595b85204f5 100644 +index 06e203693ed9b168a2bddf773cd8ce8dc23196e4..1366ed87b026240d3d2a816e9b7f55cdc9df8ebb 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -102,6 +102,27 @@ class WebGPUBackend extends Backend { @@ -6389,20 +5866,7 @@ index 06e203693ed9b168a2bddf773cd8ce8dc23196e4..add7f3284d411fd3eb188c2568775595 renderObject.clippingContextCacheKey ].join(); -@@ -2204,6 +2420,12 @@ class WebGPUBackend extends Backend { - - } - -+ updateTextureAsync( texture, options ) { -+ -+ return this.textureUtils.updateTextureAsync( texture, options ); -+ -+ } -+ - /** - * Generates mipmaps for the given texture. - * -@@ -2305,6 +2527,8 @@ class WebGPUBackend extends Backend { +@@ -2305,6 +2521,8 @@ class WebGPUBackend extends Backend { _shaderModuleDescriptor.label = program.stage + ( program.name !== '' ? `_${ program.name }` : '' ); _shaderModuleDescriptor.code = program.code; @@ -6411,7 +5875,7 @@ index 06e203693ed9b168a2bddf773cd8ce8dc23196e4..add7f3284d411fd3eb188c2568775595 programGPU.module = { module: this.device.createShaderModule( _shaderModuleDescriptor ), entryPoint: 'main' -@@ -2452,6 +2676,7 @@ class WebGPUBackend extends Backend { +@@ -2452,6 +2670,7 @@ class WebGPUBackend extends Backend { _bufferDescriptor.reset(); uniformBufferData.buffer = bufferGPU; @@ -6419,7 +5883,7 @@ index 06e203693ed9b168a2bddf773cd8ce8dc23196e4..add7f3284d411fd3eb188c2568775595 } -@@ -2718,6 +2943,7 @@ class WebGPUBackend extends Backend { +@@ -2718,6 +2937,7 @@ class WebGPUBackend extends Backend { submit( this.device, encoder.finish() ); @@ -6427,7 +5891,7 @@ index 06e203693ed9b168a2bddf773cd8ce8dc23196e4..add7f3284d411fd3eb188c2568775595 if ( dstLevel === 0 && dstTexture.generateMipmaps ) { this.textureUtils.generateMipmaps( dstTexture ); -@@ -2735,6 +2961,8 @@ class WebGPUBackend extends Backend { +@@ -2735,6 +2955,8 @@ class WebGPUBackend extends Backend { */ copyFramebufferToTexture( texture, renderContext, rectangle ) { @@ -6794,7 +6258,7 @@ index 764267c51628af3b8340888a1fa62ee9f00f435c..d5872652cc969dbea62c69944044a9ee if ( errorScope !== null || asyncError !== null ) { diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js -index ff8ee2dbace2816105aa7152891c04f2333f33e8..f6d510b59151e02c46928689b2be99486c1dfcd8 100644 +index ff8ee2dbace2816105aa7152891c04f2333f33e8..036202ae74ee01f73ca25113e8d4e2efebe929bc 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -41,6 +41,7 @@ import { @@ -6890,17 +6354,17 @@ index ff8ee2dbace2816105aa7152891c04f2333f33e8..f6d510b59151e02c46928689b2be9948 - depthTexture.stencil === stencil ) { - - return backend.get( depthTexture ).texture; -- ++ let cache = this._depthBuffers.get( depthTexture ); + - } - - // -- -- const depthTextureGPU = backend.get( depthTexture ).texture; -+ let cache = this._depthBuffers.get( depthTexture ); - -- let format, type; + if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { +- const depthTextureGPU = backend.get( depthTexture ).texture; +- +- let format, type; +- - if ( stencil ) { + this.destroyTexture( depthTexture ); + cache = undefined; @@ -6923,15 +6387,15 @@ index ff8ee2dbace2816105aa7152891c04f2333f33e8..f6d510b59151e02c46928689b2be9948 } - if ( depthTextureGPU !== undefined ) { -- -- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { + // Output and direct overlay passes alternate effective samples 4 and 1. + // Retain at most those two buffers for this canvas size and depth format. + let buffer = cache.textures.get( sampleCount ); -- return depthTextureGPU; +- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { + if ( buffer === undefined ) { +- return depthTextureGPU; +- - } - - this.destroyTexture( depthTexture ); @@ -6966,151 +6430,7 @@ index ff8ee2dbace2816105aa7152891c04f2333f33e8..f6d510b59151e02c46928689b2be9948 /** * Uploads the updated texture data to the GPU. * -@@ -733,6 +754,143 @@ class WebGPUTextureUtils { - - } - -+ // Opt-in preparation for cold compressed textures. The caller owns the visibility gate. -+ async updateTextureAsync( texture, options = {} ) { -+ -+ const { budgetMs = 2, maxBytesPerWrite = 65536, signal } = options; -+ if ( ! Number.isFinite( budgetMs ) || budgetMs <= 0 || ! Number.isSafeInteger( maxBytesPerWrite ) ) throw new Error( 'Invalid compressed upload budget.' ); -+ const backend = this.backend; -+ const device = backend.device; -+ if ( ! backend.has( texture ) ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ const textureData = backend.get( texture ); -+ const gpuTexture = textureData.texture; -+ const descriptor = textureData.textureDescriptorGPU; -+ const block = descriptor && this._getBlockData( descriptor.format ); -+ if ( ! block || ! gpuTexture || maxBytesPerWrite < block.byteLength ) throw new Error( 'Compressed texture allocation or block budget is unsupported.' ); -+ const version = texture.version; -+ const source = texture.source; -+ const mipmaps = texture.mipmaps; -+ const captured = mipmaps.map( mip => ( { mip, data: mip.data, width: mip.width, height: mip.height } ) ); -+ const updates = texture.isCompressedArrayTexture ? [ ...texture.layerUpdates ] : []; -+ const layers = updates.length > 0 ? updates : Array.from( { length: descriptor.size.depthOrArrayLayers }, ( _, i ) => i ); -+ const check = () => { -+ -+ if ( signal?.aborted ) throw signal.reason || new Error( 'Compressed texture upload cancelled.' ); -+ if ( backend.device !== device || ! backend.has( texture ) || backend.get( texture ) !== textureData || textureData.texture !== gpuTexture || textureData.textureDescriptorGPU !== descriptor ) throw new Error( 'Compressed texture allocation invalidated.' ); -+ if ( source.dataReady !== true || texture.version !== version || texture.source !== source || texture.mipmaps !== mipmaps || mipmaps.length !== captured.length || captured.some( ( c, i ) => mipmaps[ i ] !== c.mip || c.mip.data !== c.data || c.mip.width !== c.width || c.mip.height !== c.height ) ) throw new Error( 'Compressed texture changed during preparation.' ); -+ if ( texture.isCompressedArrayTexture && ( texture.layerUpdates.size !== updates.length || updates.some( layer => ! texture.layerUpdates.has( layer ) ) ) ) throw new Error( 'Compressed texture layers changed during preparation.' ); -+ -+ }; -+ function* tiles() { -+ -+ for ( let mipLevel = 0; mipLevel < captured.length; mipLevel ++ ) { -+ -+ const mip = captured[ mipLevel ]; -+ const columns = Math.ceil( mip.width / block.width ); -+ const rows = Math.ceil( mip.height / block.height ); -+ const stride = columns * block.byteLength; -+ const imageBytes = stride * rows; -+ const bytes = new Uint8Array( mip.data.buffer, mip.data.byteOffset, mip.data.byteLength ); -+ const tileColumns = Math.min( columns, Math.floor( maxBytesPerWrite / block.byteLength ) ); -+ const tileRows = stride <= maxBytesPerWrite ? Math.max( 1, Math.floor( maxBytesPerWrite / stride ) ) : 1; -+ for ( const layer of layers ) { -+ -+ if ( ! Number.isInteger( layer ) || layer < 0 || layer >= descriptor.size.depthOrArrayLayers || ( layer + 1 ) * imageBytes > bytes.byteLength ) throw new Error( 'Invalid compressed texture layer data.' ); -+ for ( let y = 0; y < rows; y += tileRows ) { -+ -+ for ( let x = 0; x < columns; x += tileColumns ) { -+ -+ const width = Math.min( tileColumns, columns - x ); -+ const height = Math.min( tileRows, rows - y ); -+ const offset = layer * imageBytes + y * stride + x * block.byteLength; -+ const length = ( height - 1 ) * stride + width * block.byteLength; -+ yield { destination: { texture: gpuTexture, mipLevel, origin: { x: x * block.width, y: y * block.height, z: layer } }, data: bytes.subarray( offset, offset + length ), layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, extent: { width: width * block.width, height: height * block.height, depthOrArrayLayers: 1 } }; -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ -+ } -+ const slice = options.uploadSlice || { started: null }; -+ const yieldHost = async () => { -+ -+ await new Promise( resolve => setTimeout( resolve, 0 ) ); -+ slice.started = performance.now(); -+ check(); -+ -+ }; -+ const iterator = tiles(); -+ const validations = []; -+ const failures = []; -+ let next = iterator.next(); -+ let uploadFailed = false; -+ let uploadError; -+ try { -+ -+ while ( ! next.done && failures.length === 0 ) { -+ -+ check(); -+ if ( slice.started === null ) slice.started = performance.now(); -+ if ( performance.now() - slice.started >= budgetMs ) await yieldHost(); -+ if ( failures.length > 0 ) break; -+ const scoped = typeof device.pushErrorScope === 'function' && typeof device.popErrorScope === 'function'; -+ if ( scoped ) { device.pushErrorScope( 'out-of-memory' ); device.pushErrorScope( 'validation' ); } -+ try { -+ -+ do { -+ -+ check(); -+ const tile = next.value; -+ device.queue.writeTexture( tile.destination, tile.data, tile.layout, tile.extent ); -+ next = iterator.next(); -+ -+ } while ( ! next.done && performance.now() - slice.started < budgetMs ); -+ -+ } finally { -+ -+ // Pop synchronously: a loading-screen render must not enter our error scopes. -+ if ( scoped ) validations.push( Promise.allSettled( [ device.popErrorScope(), device.popErrorScope() ] ).then( results => { -+ -+ for ( const result of results ) { -+ -+ if ( result.status === 'rejected' ) failures.push( result.reason ); -+ else if ( result.value !== null ) failures.push( new Error( result.value.message ) ); -+ -+ } -+ -+ } ) ); -+ -+ } -+ // Bound validation latency without changing the host slice or byte budgets. -+ if ( validations.length === 4 ) await validations.shift(); -+ check(); -+ if ( ! next.done && failures.length === 0 ) await yieldHost(); -+ -+ } -+ -+ } catch ( error ) { -+ -+ uploadFailed = true; -+ uploadError = error; -+ -+ } finally { -+ -+ // Observe every issued scope, including after cancellation or a write failure. -+ await Promise.all( validations ); -+ -+ } -+ if ( uploadFailed ) throw uploadError; -+ if ( failures.length > 0 ) throw new AggregateError( failures, 'Compressed texture upload failed: ' + failures.map( error => error?.message || String( error ) ).join( '; ' ) ); -+ check(); -+ textureData.version = version; -+ if ( updates.length > 0 ) texture.clearLayerUpdates(); -+ -+ } -+ - /** - * Returns texture data as a typed array. - * -@@ -806,6 +964,8 @@ class WebGPUTextureUtils { +@@ -806,6 +827,8 @@ class WebGPUTextureUtils { */ dispose() { diff --git a/packages/core/scripts/apply-three-patch.mjs b/packages/core/scripts/apply-three-patch.mjs index be0f9f7896..f3272b9783 100644 --- a/packages/core/scripts/apply-three-patch.mjs +++ b/packages/core/scripts/apply-three-patch.mjs @@ -5,10 +5,15 @@ import { readFile, writeFile } from "node:fs/promises"; import { createRequire } from "node:module"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { brotliDecompressSync } from "node:zlib"; const THREE_VERSION = "0.185.1"; +const PIN_COMMIT = "2431a09f46f34c560bc8e44b33be0e567723d5b9"; const PATCH_NAME = `three@${THREE_VERSION}.patch`; const UPGRADE_PATCH_NAME = `three@${THREE_VERSION}-prd269-upgrade.patch`; +const UPGRADE_JSON_VERSION = 2; +const MAX_BUNDLE_BYTES = 8 * 1024 * 1024; +const HEX40 = /^[a-f0-9]{40}$/u; /** Apply the package-owned Three.js patch to the dependency resolved for the consumer. */ export async function applyThreePatch(options = {}) { @@ -32,10 +37,10 @@ export async function applyThreePatch(options = {}) { const patchPath = path.join(packageRoot, "patches", PATCH_NAME); const patch = parsePatch(await readFile(patchPath, "utf8"), patchPath); const upgradePath = path.join(packageRoot, "patches", UPGRADE_PATCH_NAME); - const upgrades = existsSync(upgradePath) - ? parsePatch(await readFile(upgradePath, "utf8"), upgradePath) - : []; - const plans = await planPatch(threeRoot, patch, upgrades); + const upgrade = existsSync(upgradePath) + ? parseUpgrade(await readFile(upgradePath, "utf8"), upgradePath, patch) + : { legacy: [] }; + const plans = await planPatch(threeRoot, patch, upgrade); // `planPatch` refuses any file it does not fully recognise, so every plan that is not already // `patched` is safe to write — including the files an older release of this patch already // touched, which is the state a project upgrading from the previous `latest` is in. @@ -217,7 +222,190 @@ function patchPathFromHeader(header, patchPath) { return relative; } -async function planPatch(threeRoot, files, upgrades) { +function invalid(message, file, cause) { + return new Error( + `TN_THREE_PATCH_INVALID: ${message} in ${file}${cause === undefined ? "" : `: ${String(cause)}`}.`, + ); +} + +function isSafeRelativePath(value) { + return ( + typeof value === "string" && + value.length > 0 && + !value.startsWith("/") && + !value.includes("\\") && + value.split("/").every((part) => part !== "" && part !== "." && part !== "..") + ); +} + +/** + * The upgrade patch is either the current versioned JSON document or the legacy unified diff an + * older release shipped. The JSON is detected by its opening brace; anything else stays the + * legacy reader's input, byte for byte. + */ +function parseUpgrade(text, upgradePath, baseFiles) { + if (!text.trimStart().startsWith("{")) return { legacy: parsePatch(text, upgradePath) }; + return { json: parseUpgradeJson(text, upgradePath, baseFiles), legacy: [] }; +} + +function parseUpgradeJson(text, upgradePath, baseFiles) { + let raw; + try { + raw = JSON.parse(text); + } catch (error) { + throw invalid("upgrade patch is not valid JSON", upgradePath, error); + } + if (raw === null || typeof raw !== "object" || Array.isArray(raw)) { + throw invalid("upgrade patch JSON is not an object", upgradePath); + } + if (raw.version !== UPGRADE_JSON_VERSION) { + throw invalid(`unsupported upgrade version ${String(raw.version)}`, upgradePath); + } + if ( + raw.pin === null || + typeof raw.pin !== "object" || + raw.pin.version !== THREE_VERSION || + raw.pin.commit !== PIN_COMMIT + ) { + throw invalid("upgrade pin must name the supported three version and commit", upgradePath); + } + + const compatOldHashes = new Map(); + if (!Array.isArray(raw.acceptedLegacyCompatPairs) || raw.acceptedLegacyCompatPairs.length === 0) { + throw invalid("acceptedLegacyCompatPairs must be a non-empty array", upgradePath); + } + const seenPairs = new Set(); + for (const pair of raw.acceptedLegacyCompatPairs) { + if (pair === null || typeof pair !== "object") { + throw invalid("acceptedLegacyCompatPairs has a non-object entry", upgradePath); + } + if (!isSafeRelativePath(pair.path)) + throw invalid(`unsafe compat path '${pair.path}'`, upgradePath); + if (!HEX40.test(pair.oldHash ?? "") || !HEX40.test(pair.newHash ?? "")) { + throw invalid(`invalid 40-hex compat hash for '${pair.path}'`, upgradePath); + } + const key = `${pair.path}\0${pair.oldHash}`; + if (seenPairs.has(key)) throw invalid(`duplicate compat pair '${pair.path}'`, upgradePath); + seenPairs.add(key); + const hashes = compatOldHashes.get(pair.path) ?? new Set(); + hashes.add(pair.oldHash); + compatOldHashes.set(pair.path, hashes); + } + + const acceptedLegacyFinalHashes = validateHashMap( + raw.acceptedLegacyFinalHashes, + "acceptedLegacyFinalHashes", + upgradePath, + ); + const generatedFinalHashes = validateHashMap( + raw.generatedFinalHashes, + "generatedFinalHashes", + upgradePath, + ); + for (const pair of raw.acceptedLegacyCompatPairs) { + if (pair.newHash !== acceptedLegacyFinalHashes[pair.path]) { + throw invalid( + `compat pair '${pair.path}' does not match its accepted final hash`, + upgradePath, + ); + } + } + + if (typeof raw.bundleBrotliBase64 !== "string" || raw.bundleBrotliBase64.length === 0) { + throw invalid("bundleBrotliBase64 must be a non-empty string", upgradePath); + } + if ( + !/^[A-Za-z0-9+/]*={0,2}$/u.test(raw.bundleBrotliBase64) || + raw.bundleBrotliBase64.length % 4 !== 0 + ) { + throw invalid("bundleBrotliBase64 is not valid base64", upgradePath); + } + let bundleBuffer; + try { + bundleBuffer = Buffer.from(raw.bundleBrotliBase64, "base64"); + } catch (error) { + throw invalid("bundleBrotliBase64 is not valid base64", upgradePath, error); + } + let bundleText; + try { + bundleText = brotliDecompressSync(bundleBuffer, { maxOutputLength: MAX_BUNDLE_BYTES }); + } catch (error) { + throw invalid("upgrade bundle is not a bounded Brotli payload", upgradePath, error); + } + let targetTexts; + try { + targetTexts = JSON.parse(bundleText.toString("utf8")); + } catch (error) { + throw invalid("upgrade bundle is not valid JSON", upgradePath, error); + } + if (targetTexts === null || typeof targetTexts !== "object" || Array.isArray(targetTexts)) { + throw invalid("upgrade bundle is not a path-to-text object", upgradePath); + } + + const baseByPath = new Map(baseFiles.map((file) => [file.oldPath, file])); + for (const [rel, base] of baseByPath) { + if (generatedFinalHashes[rel] !== base.newBlob) { + throw invalid(`base patch target '${rel}' has no matching generated hash`, upgradePath); + } + if (acceptedLegacyFinalHashes[rel] === undefined) { + throw invalid(`base patch target '${rel}' has no matching accepted final hash`, upgradePath); + } + } + for (const rel of Object.keys(generatedFinalHashes)) { + if (!baseByPath.has(rel)) { + throw invalid(`generated hash '${rel}' is not in the base patch`, upgradePath); + } + } + for (const rel of Object.keys(acceptedLegacyFinalHashes)) { + if (!baseByPath.has(rel)) { + throw invalid(`accepted final hash '${rel}' is not in the base patch`, upgradePath); + } + } + for (const pair of raw.acceptedLegacyCompatPairs) { + if (!baseByPath.has(pair.path)) { + throw invalid(`compat pair '${pair.path}' is not in the base patch`, upgradePath); + } + } + for (const [rel, contents] of Object.entries(targetTexts)) { + if (!isSafeRelativePath(rel) || typeof contents !== "string") { + throw invalid(`unsafe or invalid upgrade target '${rel}'`, upgradePath); + } + if (!baseByPath.has(rel)) { + throw invalid(`upgrade target '${rel}' is not in the base patch`, upgradePath); + } + if (blobHash(contents) !== generatedFinalHashes[rel]) { + throw invalid(`upgrade target '${rel}' does not match its generated hash`, upgradePath); + } + } + for (const [rel] of baseByPath) { + const needsTarget = + compatOldHashes.has(rel) || generatedFinalHashes[rel] !== acceptedLegacyFinalHashes[rel]; + if (needsTarget && targetTexts[rel] === undefined) { + throw invalid(`upgrade bundle is missing the target for '${rel}'`, upgradePath); + } + if (!needsTarget && targetTexts[rel] !== undefined) { + throw invalid(`upgrade bundle has an unneeded target for '${rel}'`, upgradePath); + } + } + + return { acceptedLegacyFinalHashes, compatOldHashes, generatedFinalHashes, targetTexts }; +} + +function validateHashMap(map, name, upgradePath) { + if (map === null || typeof map !== "object" || Array.isArray(map)) { + throw invalid(`${name} must be a path-to-hash object`, upgradePath); + } + const keys = Object.keys(map); + if (keys.length === 0) throw invalid(`${name} must be non-empty`, upgradePath); + for (const rel of keys) { + if (!isSafeRelativePath(rel)) throw invalid(`unsafe ${name} path '${rel}'`, upgradePath); + if (!HEX40.test(map[rel])) + throw invalid(`invalid 40-hex ${name} hash for '${rel}'`, upgradePath); + } + return map; +} + +async function planPatch(threeRoot, files, upgrade) { const plans = []; for (const file of files) { const target = path.resolve(threeRoot, file.oldPath); @@ -227,7 +415,11 @@ async function planPatch(threeRoot, files, upgrades) { ); } const original = await readFile(target, "utf8"); - const recognised = upgradeKnownFile(original, file.oldPath, upgrades); + if (upgrade.json !== undefined) { + plans.push(planJsonFile(original, file, upgrade.json, target)); + continue; + } + const recognised = upgradeKnownFile(original, file.oldPath, upgrade.legacy); const lines = recognised.replaceAll("\r\n", "\n").split("\n"); const states = classifyFile(lines, file.hunks); const unknown = states.findIndex((state) => state === "unknown"); @@ -241,7 +433,7 @@ async function planPatch(threeRoot, files, upgrades) { : states.some((state) => state === "patched") ? "extended" : "stock"; - assertKnownExtension(status, recognised === original, file.oldPath, upgrades); + assertKnownExtension(status, recognised === original, file.oldPath, upgrade.legacy); if (status === "patched") { plans.push({ contents: recognised, @@ -250,15 +442,67 @@ async function planPatch(threeRoot, files, upgrades) { }); continue; } - plans.push({ - contents: applyFile(lines, file.hunks, states, target).join("\n"), - file: target, - status, - }); + const contents = applyFile(lines, file.hunks, states, target).join("\n"); + if (file.newBlob !== undefined && blobHash(contents) !== file.newBlob) { + throw invalid( + "patched output does not match the base patch index blob", + file.oldPath, + target, + ); + } + plans.push({ contents, file: target, status }); } return plans; } +/** + * Plan one file against the versioned JSON upgrade: an exact current target is idempotent, an + * exact known predecessor (legacy compat hash or the previously shipped final hash) is replaced + * wholesale by the bundle's current target, and an exact stock file takes the base patch hunks. + * Anything else on a registered path is refused before any file is written. + */ +function planJsonFile(original, file, json, target) { + const rel = file.oldPath; + const normalized = original.replaceAll("\r\n", "\n"); + const hash = blobHash(normalized); + if (hash === json.generatedFinalHashes[rel]) { + return { + contents: normalized, + file: target, + status: original === normalized ? "patched" : "extended", + }; + } + const legacy = json.compatOldHashes.get(rel); + if (legacy?.has(hash) === true || hash === json.acceptedLegacyFinalHashes[rel]) { + const contents = json.targetTexts[rel]; + if (contents === undefined) throw partialError(rel, undefined, target); + return { contents, file: target, status: "extended" }; + } + if (hash === file.oldBlob) { + const lines = normalized.split("\n"); + const states = classifyFile(lines, file.hunks); + const unknown = states.findIndex((state) => state === "unknown"); + if (unknown !== -1) throw partialError(rel, unknown, target); + const applied = applyFile(lines, file.hunks, states, target).join("\n"); + if (blobHash(applied) !== file.newBlob) { + throw invalid("patched output does not match the base patch index blob", rel, target); + } + return { + contents: applied, + file: target, + status: applied === original ? "patched" : "extended", + }; + } + throw partialError(rel, undefined, target); +} + +function partialError(rel, hunkNumber, target) { + const where = hunkNumber === undefined ? "" : `hunk ${String(hunkNumber + 1)} of `; + return new Error( + `TN_THREE_PATCH_PARTIAL: ${where}${rel} matches neither a supported predecessor nor the current patch in ${target}; refusing to modify an installation this package does not recognise.`, + ); +} + /** Registered migration files must not fall through to permissive hunk-only upgrades. */ function assertKnownExtension(status, unchanged, file, upgrades) { if ( @@ -318,7 +562,13 @@ function classifyFile(lines, hunks) { for (const hunk of hunks) { const oldLines = hunkLines(hunk, "old"); const newLines = hunkLines(hunk, "new"); - if (locate(lines, newLines, hunk.newStart - 1) !== undefined) states.push("patched"); + // A deletion's remaining context also occurs in stock; its full old text wins. + if ( + !hunk.lines.some((line) => line[0] === "+") && + locate(lines, oldLines, hunk.oldStart - 1 + offset) !== undefined + ) + states.push("stock"); + else if (locate(lines, newLines, hunk.newStart - 1) !== undefined) states.push("patched"); else if (locate(lines, oldLines, hunk.oldStart - 1 + offset) !== undefined) states.push("stock"); else states.push("unknown"); diff --git a/packages/core/scripts/generate-three-patch.mjs b/packages/core/scripts/generate-three-patch.mjs new file mode 100644 index 0000000000..17bdba6a60 --- /dev/null +++ b/packages/core/scripts/generate-three-patch.mjs @@ -0,0 +1,328 @@ +#!/usr/bin/env node +import { execFile } from "node:child_process"; +import { createHash } from "node:crypto"; +import { createReadStream, createWriteStream, existsSync } from "node:fs"; +import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { Readable } from "node:stream"; +import { pipeline } from "node:stream/promises"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import zlib from "node:zlib"; + +const execFileAsync = promisify(execFile); +const PIN_VERSION = "0.185.1"; +const PIN_COMMIT = "2431a09f46f34c560bc8e44b33be0e567723d5b9"; +const ARCHIVE_URL = `https://codeload.github.com/mrdoob/three.js/tar.gz/${PIN_COMMIT}`; +const BASE_PATCH_NAME = `three@${PIN_VERSION}.patch`; +const UPGRADE_PATCH_NAME = `three@${PIN_VERSION}-prd269-upgrade.patch`; +const MAX_BUFFER = 64 * 1024 * 1024; +const MAX_BUNDLE_BYTES = 8 * 1024 * 1024; +const HEX40 = /^[a-f0-9]{40}$/u; +const HEX64 = /^[a-f0-9]{64}$/u; + +const err = (message) => new Error(`TN_GENERATE_PATCH: ${message}`); + +export function blobHash(contents) { + return createHash("sha1") + .update(`blob ${Buffer.byteLength(contents)}\0`) + .update(contents) + .digest("hex"); +} + +async function fileSha256(file) { + const hash = createHash("sha256"); + for await (const chunk of createReadStream(file)) hash.update(chunk); + return hash.digest("hex"); +} + +function isSafeRelative(value, prefixes) { + if (typeof value !== "string" || value.length === 0) return false; + if (value.startsWith("/") || value.includes("\\")) return false; + if (value.split("/").some((part) => part === "" || part === "." || part === "..")) return false; + return prefixes.some((prefix) => value.startsWith(`${prefix}/`)); +} + +function validateCompatPairs(pairs) { + if (!Array.isArray(pairs) || pairs.length === 0) { + throw err("pin.json frozenCompatPairs must be a non-empty array."); + } + const seen = new Set(); + for (const pair of pairs) { + if (pair === null || typeof pair !== "object") + throw err("pin.json has an invalid compat pair."); + if (!isSafeRelative(pair.path, ["src", "build"])) + throw err(`unsafe compat path '${pair.path}'.`); + if (!HEX40.test(pair.oldHash ?? "") || !HEX40.test(pair.newHash ?? "")) { + throw err(`invalid compat hash for ${pair.path}.`); + } + const key = `${pair.path}\0${pair.oldHash}`; + if (seen.has(key)) throw err(`duplicate compat pair ${pair.path} ${pair.oldHash}.`); + seen.add(key); + } +} + +function validateFinalHashes(finals) { + if (finals === null || typeof finals !== "object" || Array.isArray(finals)) { + throw err("pin.json baseFinalHashes must be an object."); + } + const paths = Object.keys(finals); + if (paths.length === 0) throw err("pin.json baseFinalHashes must be non-empty."); + for (const rel of paths) { + if (!isSafeRelative(rel, ["src", "build"])) throw err(`unsafe final path '${rel}'.`); + if (!HEX40.test(finals[rel])) throw err(`invalid final hash for ${rel}.`); + } +} + +export function validatePin(pin) { + if (pin === null || typeof pin !== "object") throw err("pin.json must be an object."); + if (pin.version !== PIN_VERSION || pin.commit !== PIN_COMMIT) { + throw err(`unsupported pin ${pin.version} ${pin.commit}`); + } + for (const key of ["packageJsonSha256", "packageLockSha256", "archiveSha256"]) { + if (!HEX64.test(pin[key] ?? "")) throw err(`pin.json has an invalid ${key}.`); + } + validateCompatPairs(pin.frozenCompatPairs); + validateFinalHashes(pin.baseFinalHashes); + return pin; +} + +/** Every `diff --git`/`---`/`+++` path must be an identical, safe `src/` path. */ +export function parseSourceDeltaPaths(sourceDelta) { + const sections = sourceDelta + .split(/^(?=diff --git )/mu) + .filter((section) => section.trim().length > 0); + if (sections.length === 0) throw err("source delta is empty."); + const paths = []; + for (const section of sections) { + const header = /^diff --git a\/(\S+) b\/(\S+)/mu.exec(section); + if (header === null) throw err("source delta section has no diff header."); + const [, oldPath, newPath] = header; + const oldHeader = /^--- a\/(\S+)/mu.exec(section); + const newHeader = /^\+\+\+ b\/(\S+)/mu.exec(section); + if (oldPath !== newPath) + throw err(`source delta rename '${oldPath}' -> '${newPath}' is not allowed.`); + if (oldHeader?.[1] !== oldPath || newHeader?.[1] !== newPath) { + throw err(`source delta paths disagree for '${oldPath}'.`); + } + if (!isSafeRelative(oldPath, ["src"])) + throw err(`source delta path must be a safe src/ path: '${oldPath}'.`); + paths.push(oldPath); + } + return paths; +} + +export async function generateThreePatch(options = {}) { + const packageRoot = + options.packageRoot ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + const pinPath = path.join(packageRoot, "three-patch", "pin.json"); + const sourceDeltaPath = path.join(packageRoot, "three-patch", "source-delta.patch"); + if (!existsSync(pinPath) || !existsSync(sourceDeltaPath)) { + throw err("missing pin.json or source-delta.patch in three-patch directory."); + } + const pin = validatePin(JSON.parse(await readFile(pinPath, "utf8"))); + const sourceDelta = await readFile(sourceDeltaPath, "utf8"); + const approved = new Set(Object.keys(pin.baseFinalHashes)); + for (const rel of parseSourceDeltaPaths(sourceDelta)) { + if (!approved.has(rel)) throw err(`source delta path '${rel}' is not a pinned target.`); + } + + const scratch = await mkdtemp(path.join(os.tmpdir(), "tn-three-gen-")); + try { + const planner = options.planner ?? planFromUpstream; + const { basePatch, upgradePatch } = await planner({ + archivePath: options.archive, + pin, + scratch, + sourceDelta, + }); + return await handleArtifacts({ + basePatch, + upgradePatch, + packageRoot, + checkOnly: options.check ?? false, + }); + } finally { + await rm(scratch, { force: true, recursive: true }); + } +} + +async function planFromUpstream({ archivePath, pin, scratch, sourceDelta }) { + const archive = archivePath ?? path.join(scratch, "upstream.tar.gz"); + if (archivePath !== undefined && !existsSync(archivePath)) { + throw err(`archive not found at ${archivePath}.`); + } + if (archivePath === undefined) await downloadArchive(ARCHIVE_URL, archive); + if ((await fileSha256(archive)) !== pin.archiveSha256) { + throw err("upstream archive sha256 does not match the pinned hash."); + } + + const prefix = `three.js-${pin.commit}`; + const members = [ + `${prefix}/src`, + `${prefix}/build`, + `${prefix}/utils/build`, + `${prefix}/package.json`, + `${prefix}/package-lock.json`, + ]; + await execFileAsync("tar", ["-xzf", archive, "-C", scratch, "--", ...members], { + maxBuffer: MAX_BUFFER, + }); + const workDir = path.join(scratch, prefix); + if ((await fileSha256(path.join(workDir, "package.json"))) !== pin.packageJsonSha256) { + throw err("upstream package.json sha256 does not match the pin."); + } + if ((await fileSha256(path.join(workDir, "package-lock.json"))) !== pin.packageLockSha256) { + throw err("upstream package-lock.json sha256 does not match the pin."); + } + + const stockDir = path.join(scratch, "stock-snapshot"); + await cp(path.join(workDir, "src"), path.join(stockDir, "src"), { recursive: true }); + await cp(path.join(workDir, "build"), path.join(stockDir, "build"), { recursive: true }); + + const deltaFile = path.join(scratch, "delta.patch"); + await writeFile(deltaFile, sourceDelta, "utf8"); + await execFileAsync("patch", ["-p1", "--batch", "--silent", "-i", deltaFile], { cwd: workDir }); + + await execFileAsync("npm", ["ci", "--ignore-scripts", "--no-audit", "--no-fund"], { + cwd: workDir, + maxBuffer: MAX_BUFFER, + }); + await execFileAsync("npm", ["run", "build"], { cwd: workDir, maxBuffer: MAX_BUFFER }); + + return assemblePatches({ patchedDir: workDir, pin, stockDir }); +} + +async function downloadArchive(url, dest) { + const response = await fetch(url, { signal: AbortSignal.timeout(120_000) }); + if (!response.ok || response.body === null) { + throw err(`could not fetch the upstream archive: HTTP ${response.status}.`); + } + await pipeline(Readable.fromWeb(response.body), createWriteStream(dest)); +} + +/** Emit the base patch and the upgrade bundle for exactly the pinned target paths. */ +export async function assemblePatches({ stockDir, patchedDir, pin }) { + validatePin(pin); + const sections = []; + const targetTexts = {}; + const generatedFinalHashes = {}; + const migrationPaths = new Set(pin.frozenCompatPairs.map((pair) => pair.path)); + for (const rel of Object.keys(pin.baseFinalHashes).sort()) { + const oldFile = path.join(stockDir, rel); + const newFile = path.join(patchedDir, rel); + if (!existsSync(oldFile) || !existsSync(newFile)) throw err(`missing scoped target ${rel}.`); + const oldBytes = await readFile(oldFile); + const newBytes = await readFile(newFile); + generatedFinalHashes[rel] = blobHash(newBytes); + // Carry the current target only where a consumer can hold a different copy of it: an older + // compat state, or a target this build changed from the previously shipped one. An unchanged + // target is recognised by hash and never needs its full text duplicated in the bundle. + if (migrationPaths.has(rel) || generatedFinalHashes[rel] !== pin.baseFinalHashes[rel]) { + targetTexts[rel] = newBytes.toString("utf8"); + } + const diff = await diffScopedFile(oldFile, newFile, rel); + if (diff.length > 0) sections.push(diff); + } + + const targetJson = Buffer.from(JSON.stringify(targetTexts)); + if (targetJson.length > MAX_BUNDLE_BYTES) throw err("upgrade target JSON exceeds the 8MiB cap."); + const bundle = zlib.brotliCompressSync(targetJson, { + params: { + [zlib.constants.BROTLI_PARAM_QUALITY]: 6, + [zlib.constants.BROTLI_PARAM_LGWIN]: 24, + }, + }); + if (bundle.length > MAX_BUNDLE_BYTES) throw err("upgrade brotli bundle exceeds the 8MiB cap."); + + const upgradeJson = { + version: 2, + generator: "scripts/generate-three-patch.mjs", + pin: { commit: pin.commit, version: pin.version }, + acceptedLegacyCompatPairs: pin.frozenCompatPairs, + acceptedLegacyFinalHashes: pin.baseFinalHashes, + generatedFinalHashes, + bundleBrotliBase64: bundle.toString("base64"), + }; + return { + basePatch: sections.join(""), + upgradePatch: `${JSON.stringify(upgradeJson, null, 2)}\n`, + }; +} + +async function diffScopedFile(oldFile, newFile, rel) { + let raw = ""; + try { + const result = await execFileAsync( + "git", + [ + "diff", + "--no-index", + "--no-color", + "--no-ext-diff", + "--unified=3", + "--diff-algorithm=myers", + "--", + oldFile, + newFile, + ], + { maxBuffer: MAX_BUFFER }, + ); + raw = result.stdout; + } catch (error) { + if (typeof error.stdout === "string" && error.stdout.length > 0) raw = error.stdout; + else throw error; + } + if (raw.length === 0) return ""; + const oldBlob = blobHash(await readFile(oldFile)); + const newBlob = blobHash(await readFile(newFile)); + return raw + .split("\n") + .map((line) => { + if (line.startsWith("diff --git ")) return `diff --git a/${rel} b/${rel}`; + if (line.startsWith("index ")) return `index ${oldBlob}..${newBlob} 100644`; + if (line.startsWith("--- ")) return `--- a/${rel}`; + if (line.startsWith("+++ ")) return `+++ b/${rel}`; + return line; + }) + .join("\n"); +} + +async function handleArtifacts({ basePatch, upgradePatch, packageRoot, checkOnly }) { + const baseDest = path.join(packageRoot, "patches", BASE_PATCH_NAME); + const upgradeDest = path.join(packageRoot, "patches", UPGRADE_PATCH_NAME); + if (checkOnly) { + const currentBase = existsSync(baseDest) ? await readFile(baseDest, "utf8") : ""; + const currentUpgrade = existsSync(upgradeDest) ? await readFile(upgradeDest, "utf8") : ""; + if (currentBase !== basePatch || currentUpgrade !== upgradePatch) { + throw err("check failed, generated patch drifts from the committed patch."); + } + return { status: "matched" }; + } + await mkdir(path.dirname(baseDest), { recursive: true }); + await writeFile(baseDest, basePatch, "utf8"); + await writeFile(upgradeDest, upgradePatch, "utf8"); + return { status: "written" }; +} + +function parseCli(argv) { + const options = { archive: undefined, check: false }; + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + if (arg === "--check") options.check = true; + else if (arg === "--archive") { + const value = argv[index + 1]; + if (value === undefined || value.startsWith("--")) throw err("--archive requires a path."); + options.archive = value; + index += 1; + } else throw err(`unknown argument '${arg}'.`); + } + return options; +} + +const entry = process.argv[1]; +if (entry !== undefined && path.resolve(entry) === path.resolve(fileURLToPath(import.meta.url))) { + const result = await generateThreePatch(parseCli(process.argv.slice(2))); + process.stdout.write(`generate-three-patch: ${result.status}\n`); +} diff --git a/packages/core/src/assets.ts b/packages/core/src/assets.ts index f22fbc1471..d3b686490d 100644 --- a/packages/core/src/assets.ts +++ b/packages/core/src/assets.ts @@ -15,6 +15,7 @@ import { } from "three"; import { TN_VIRTUAL_GEOMETRY, VirtualGeometryPlugin } from "./clustered-mesh.js"; import { GEOMETRY_ASSET_KEY } from "./geometry-capture.js"; +import { attachInstancedLod } from "./instanced-batch-lod.js"; import { DiscreteLodPlugin, type IModelLodPolicy, TN_DISCRETE_LOD } from "./model-lod.js"; export interface IAssetLoaderOptions { @@ -1142,7 +1143,7 @@ export function createAssetLoader(options: IAssetLoaderOptions = {}): IAssetLoad // Built here, after both widenings, so the derived levels share the widened base // attributes instead of pinning the quantized ones. if (root !== undefined && discreteLod !== undefined) - discreteLod.attach(root, await discretePolicy(path)); + discreteLod.attach(root, await discretePolicy(path), attachInstancedLod); // Before the game ever sees it: clips z-mirrored against their own bind pose play every // animal backwards, silently. See `reconcileMirroredClips`. const clips = modelClips(value); diff --git a/packages/core/src/compressed-texture-preparation.ts b/packages/core/src/compressed-texture-preparation.ts new file mode 100644 index 0000000000..811c895edc --- /dev/null +++ b/packages/core/src/compressed-texture-preparation.ts @@ -0,0 +1,1008 @@ +import { + ColorManagement, + type CompressedArrayTexture, + type CompressedTexture, + ExternalTexture, + R11_EAC_Format, + RED_GREEN_RGTC2_Format, + RED_RGTC1_Format, + RG11_EAC_Format, + RGBA_ASTC_4x4_Format, + RGBA_ASTC_5x4_Format, + RGBA_ASTC_5x5_Format, + RGBA_ASTC_6x5_Format, + RGBA_ASTC_6x6_Format, + RGBA_ASTC_8x5_Format, + RGBA_ASTC_8x6_Format, + RGBA_ASTC_8x8_Format, + RGBA_ASTC_10x5_Format, + RGBA_ASTC_10x6_Format, + RGBA_ASTC_10x8_Format, + RGBA_ASTC_10x10_Format, + RGBA_ASTC_12x10_Format, + RGBA_ASTC_12x12_Format, + RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format, + RGBA_S3TC_DXT1_Format, + RGBA_S3TC_DXT3_Format, + RGBA_S3TC_DXT5_Format, + RGB_BPTC_SIGNED_Format, + RGB_BPTC_UNSIGNED_Format, + RGB_ETC1_Format, + RGB_ETC2_Format, + RGB_S3TC_DXT1_Format, + SIGNED_R11_EAC_Format, + SIGNED_RED_GREEN_RGTC2_Format, + SIGNED_RED_RGTC1_Format, + SIGNED_RG11_EAC_Format, + SRGBTransfer, + Source, + type Texture, +} from "three"; + +/** + * One compressed GPU format and the block geometry a bounded write must respect. The table is + * pinned to the installed Three release (see `three-patch/pin.json`); the GPU format strings are + * the WebGPU enum values Three maps its public texture constants to. `feature` is the WebGPU + * `device.features` name the format needs, checked before any allocation. + */ +export interface ICompressedFormatEntry { + readonly gpuFormat: string; + readonly feature: string; + readonly blockWidth: number; + readonly blockHeight: number; + readonly blockBytes: number; +} + +type IFormatRow = Omit & { + readonly base: string; + readonly srgb?: true; +}; + +const BC = "texture-compression-bc"; +const ETC2 = "texture-compression-etc2"; +const ASTC = "texture-compression-astc"; + +// Public Three constants -> WebGPU compressed format, required device feature, block size and +// bytes per block. Kept local so the framework never imports Three's internal texture utils. +const FORMAT_ROWS: ReadonlyArray = [ + [ + RGB_S3TC_DXT1_Format, + { + base: "bc1-rgba-unorm", + srgb: true, + feature: BC, + blockWidth: 4, + blockHeight: 4, + blockBytes: 8, + }, + ], + [ + RGBA_S3TC_DXT1_Format, + { + base: "bc1-rgba-unorm", + srgb: true, + feature: BC, + blockWidth: 4, + blockHeight: 4, + blockBytes: 8, + }, + ], + [ + RGBA_S3TC_DXT3_Format, + { + base: "bc2-rgba-unorm", + srgb: true, + feature: BC, + blockWidth: 4, + blockHeight: 4, + blockBytes: 16, + }, + ], + [ + RGBA_S3TC_DXT5_Format, + { + base: "bc3-rgba-unorm", + srgb: true, + feature: BC, + blockWidth: 4, + blockHeight: 4, + blockBytes: 16, + }, + ], + [ + RED_RGTC1_Format, + { base: "bc4-r-unorm", feature: BC, blockWidth: 4, blockHeight: 4, blockBytes: 8 }, + ], + [ + SIGNED_RED_RGTC1_Format, + { base: "bc4-r-snorm", feature: BC, blockWidth: 4, blockHeight: 4, blockBytes: 8 }, + ], + [ + RED_GREEN_RGTC2_Format, + { base: "bc5-rg-unorm", feature: BC, blockWidth: 4, blockHeight: 4, blockBytes: 16 }, + ], + [ + SIGNED_RED_GREEN_RGTC2_Format, + { base: "bc5-rg-snorm", feature: BC, blockWidth: 4, blockHeight: 4, blockBytes: 16 }, + ], + [ + RGBA_BPTC_Format, + { + base: "bc7-rgba-unorm", + srgb: true, + feature: BC, + blockWidth: 4, + blockHeight: 4, + blockBytes: 16, + }, + ], + [ + RGB_BPTC_SIGNED_Format, + { base: "bc6h-rgb-float", feature: BC, blockWidth: 4, blockHeight: 4, blockBytes: 16 }, + ], + [ + RGB_BPTC_UNSIGNED_Format, + { base: "bc6h-rgb-ufloat", feature: BC, blockWidth: 4, blockHeight: 4, blockBytes: 16 }, + ], + [ + RGB_ETC2_Format, + { + base: "etc2-rgb8unorm", + srgb: true, + feature: ETC2, + blockWidth: 4, + blockHeight: 4, + blockBytes: 8, + }, + ], + [ + RGB_ETC1_Format, + { + base: "etc2-rgb8unorm", + srgb: true, + feature: ETC2, + blockWidth: 4, + blockHeight: 4, + blockBytes: 8, + }, + ], + [ + RGBA_ETC2_EAC_Format, + { + base: "etc2-rgba8unorm", + srgb: true, + feature: ETC2, + blockWidth: 4, + blockHeight: 4, + blockBytes: 16, + }, + ], + [ + R11_EAC_Format, + { base: "eac-r11unorm", feature: ETC2, blockWidth: 4, blockHeight: 4, blockBytes: 8 }, + ], + [ + SIGNED_R11_EAC_Format, + { base: "eac-r11snorm", feature: ETC2, blockWidth: 4, blockHeight: 4, blockBytes: 8 }, + ], + [ + RG11_EAC_Format, + { base: "eac-rg11unorm", feature: ETC2, blockWidth: 4, blockHeight: 4, blockBytes: 16 }, + ], + [ + SIGNED_RG11_EAC_Format, + { base: "eac-rg11snorm", feature: ETC2, blockWidth: 4, blockHeight: 4, blockBytes: 16 }, + ], + [ + RGBA_ASTC_4x4_Format, + { + base: "astc-4x4-unorm", + srgb: true, + feature: ASTC, + blockWidth: 4, + blockHeight: 4, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_5x4_Format, + { + base: "astc-5x4-unorm", + srgb: true, + feature: ASTC, + blockWidth: 5, + blockHeight: 4, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_5x5_Format, + { + base: "astc-5x5-unorm", + srgb: true, + feature: ASTC, + blockWidth: 5, + blockHeight: 5, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_6x5_Format, + { + base: "astc-6x5-unorm", + srgb: true, + feature: ASTC, + blockWidth: 6, + blockHeight: 5, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_6x6_Format, + { + base: "astc-6x6-unorm", + srgb: true, + feature: ASTC, + blockWidth: 6, + blockHeight: 6, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_8x5_Format, + { + base: "astc-8x5-unorm", + srgb: true, + feature: ASTC, + blockWidth: 8, + blockHeight: 5, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_8x6_Format, + { + base: "astc-8x6-unorm", + srgb: true, + feature: ASTC, + blockWidth: 8, + blockHeight: 6, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_8x8_Format, + { + base: "astc-8x8-unorm", + srgb: true, + feature: ASTC, + blockWidth: 8, + blockHeight: 8, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_10x5_Format, + { + base: "astc-10x5-unorm", + srgb: true, + feature: ASTC, + blockWidth: 10, + blockHeight: 5, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_10x6_Format, + { + base: "astc-10x6-unorm", + srgb: true, + feature: ASTC, + blockWidth: 10, + blockHeight: 6, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_10x8_Format, + { + base: "astc-10x8-unorm", + srgb: true, + feature: ASTC, + blockWidth: 10, + blockHeight: 8, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_10x10_Format, + { + base: "astc-10x10-unorm", + srgb: true, + feature: ASTC, + blockWidth: 10, + blockHeight: 10, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_12x10_Format, + { + base: "astc-12x10-unorm", + srgb: true, + feature: ASTC, + blockWidth: 12, + blockHeight: 10, + blockBytes: 16, + }, + ], + [ + RGBA_ASTC_12x12_Format, + { + base: "astc-12x12-unorm", + srgb: true, + feature: ASTC, + blockWidth: 12, + blockHeight: 12, + blockBytes: 16, + }, + ], +]; + +const FORMATS = new Map(FORMAT_ROWS); + +/** + * Resolves the GPU format for a compressed texture, or `undefined` when it is unsupported. The + * sRGB suffix follows the texture's color-space transfer, so any transfer that maps to sRGB is + * treated as sRGB rather than only the literal `SRGBColorSpace` string. + */ +export function compressedFormatFor(texture: Texture): ICompressedFormatEntry | undefined { + const row = FORMATS.get(texture.format); + if (row === undefined) return undefined; + const srgb = + row.srgb === true && ColorManagement.getTransfer(texture.colorSpace) === SRGBTransfer; + return { + gpuFormat: srgb ? `${row.base}-srgb` : row.base, + feature: row.feature, + blockWidth: row.blockWidth, + blockHeight: row.blockHeight, + blockBytes: row.blockBytes, + }; +} + +type CompressedLike = Texture & + Pick & { + isCompressedTexture?: boolean; + isCompressedArrayTexture?: boolean; + }; + +/** One bounded GPU write: at most `maxBytesPerWrite` payload bytes, block-aligned. */ +export interface ICompressedUploadTile { + readonly mipLevel: number; + readonly origin: { readonly x: number; readonly y: number; readonly z: number }; + readonly data: Uint8Array; + readonly layout: { + readonly offset: number; + readonly bytesPerRow: number; + readonly rowsPerImage: number; + }; + readonly extent: { + readonly width: number; + readonly height: number; + readonly depthOrArrayLayers: number; + }; +} + +export interface ICompressedUploadPlan { + readonly tiles: ReadonlyArray; + readonly totalBytes: number; + readonly layers: number; + readonly width: number; + readonly height: number; +} + +function compressedLayers(source: CompressedLike): number { + const depth = (source.image as { depth?: number } | null)?.depth; + if (!source.isCompressedArrayTexture) { + // A 2D texture has one layer. Reject any other declared depth instead of dropping layers. + if (depth !== undefined && depth !== 1) { + if (depth > 1) + throw new Error( + "Compressed 2D texture cannot carry image depth above 1. Use CompressedArrayTexture.", + ); + throw new Error("Invalid compressed 2D texture depth. Only undefined or 1 is valid."); + } + return 1; + } + if (!Number.isSafeInteger(depth) || (depth ?? 0) <= 0) + throw new Error("Invalid compressed texture layer count."); + return depth as number; +} + +/** + * Validates a compressed source and plans whole-texture coverage as block-aligned writes that + * never exceed `maxBytesPerWrite` and never exceed one mip tail or one array layer per write. + * Fails closed on a block budget smaller than one block, an invalid dimension, a base mip that + * differs from the image, or data too short for the declared layers. Each mip must match the + * halved base size, and the chain cannot be longer than the base allows. Called before any GPU + * allocation. + */ +export function planCompressedUpload( + source: CompressedLike, + block: ICompressedFormatEntry, + maxBytesPerWrite: number, +): ICompressedUploadPlan { + const mipmaps = source.mipmaps; + if (!Number.isSafeInteger(maxBytesPerWrite) || maxBytesPerWrite < block.blockBytes) + throw new Error("Compressed texture block budget is unsupported."); + if (!Array.isArray(mipmaps) || mipmaps.length === 0) + throw new Error("Bounded texture preparation requires ready compressed mip data."); + const image = source.image as { width?: number; height?: number } | null; + const width = image?.width; + const height = image?.height; + if ( + !Number.isSafeInteger(width) || + !Number.isSafeInteger(height) || + (width ?? 0) <= 0 || + (height ?? 0) <= 0 + ) + throw new Error("Invalid compressed texture dimensions."); + if (mipmaps[0]?.width !== width || mipmaps[0]?.height !== height) + throw new Error("Invalid compressed texture dimensions."); + const baseWidth = width as number; + const baseHeight = height as number; + const maxLevels = Math.floor(Math.log2(Math.max(baseWidth, baseHeight))) + 1; + if (mipmaps.length > maxLevels) throw new Error("Invalid compressed texture mip chain."); + const layers = compressedLayers(source); + const tiles: ICompressedUploadTile[] = []; + let totalBytes = 0; + for (let mipLevel = 0; mipLevel < mipmaps.length; mipLevel++) { + const mip = mipmaps[mipLevel]; + if (mip === undefined) throw new Error("Compressed texture mip map is missing."); + const expectedWidth = Math.max(1, Math.floor(baseWidth / 2 ** mipLevel)); + const expectedHeight = Math.max(1, Math.floor(baseHeight / 2 ** mipLevel)); + if (mip.width !== expectedWidth || mip.height !== expectedHeight) + throw new Error("Invalid compressed texture dimensions."); + const columns = Math.ceil(mip.width / block.blockWidth); + const rows = Math.ceil(mip.height / block.blockHeight); + const stride = columns * block.blockBytes; + const imageBytes = stride * rows; + if (!Number.isSafeInteger(imageBytes)) + throw new Error("Invalid compressed texture layer data."); + const bytes = new Uint8Array(mip.data.buffer, mip.data.byteOffset, mip.data.byteLength); + const tileColumns = Math.min(columns, Math.floor(maxBytesPerWrite / block.blockBytes)); + const tileRows = + stride <= maxBytesPerWrite ? Math.max(1, Math.floor(maxBytesPerWrite / stride)) : 1; + for (let layer = 0; layer < layers; layer++) { + if ((layer + 1) * imageBytes > bytes.byteLength) + throw new Error("Invalid compressed texture layer data."); + for (let y = 0; y < rows; y += tileRows) { + for (let x = 0; x < columns; x += tileColumns) { + const tileWidth = Math.min(tileColumns, columns - x); + const tileHeight = Math.min(tileRows, rows - y); + const offset = layer * imageBytes + y * stride + x * block.blockBytes; + const length = (tileHeight - 1) * stride + tileWidth * block.blockBytes; + tiles.push({ + mipLevel, + origin: { x: x * block.blockWidth, y: y * block.blockHeight, z: layer }, + data: bytes.subarray(offset, offset + length), + layout: { offset: 0, bytesPerRow: stride, rowsPerImage: rows }, + extent: { + width: tileWidth * block.blockWidth, + height: tileHeight * block.blockHeight, + depthOrArrayLayers: 1, + }, + }); + totalBytes += length; + } + } + } + } + return { tiles, totalBytes, layers, width: width as number, height: height as number }; +} + +/** The public slice of a WebGPU device this lane needs; the rest of `GPUDevice` is unused. */ +export interface ICompressedUploadDevice { + readonly queue: { + writeTexture( + destination: { + readonly texture: unknown; + readonly mipLevel: number; + readonly origin: { readonly x: number; readonly y: number; readonly z: number }; + }, + data: Uint8Array, + layout: ICompressedUploadTile["layout"], + extent: ICompressedUploadTile["extent"], + ): void; + }; + readonly features?: { has(name: string): boolean }; + createTexture(descriptor: { + readonly size: { + readonly width: number; + readonly height: number; + readonly depthOrArrayLayers: number; + }; + readonly format: string; + readonly mipLevelCount: number; + readonly dimension?: string; + readonly usage?: number; + }): { destroy(): void }; + readonly lost?: Promise; + pushErrorScope?(filter: "validation" | "out-of-memory"): void; + popErrorScope?(): Promise; +} + +interface ICacheEntry { + device: ICompressedUploadDevice; + snapshot: readonly unknown[]; + prepared: ExternalTexture; + listener: () => void; +} + +function destroyPrepared(prepared: ExternalTexture): void { + (prepared.sourceTexture as { destroy?: () => void } | null)?.destroy?.(); +} + +interface IUploadJob { + readonly controller: AbortController; + readonly version: number; + readonly device: ICompressedUploadDevice; + readonly snapshot: readonly unknown[]; + users: number; + promise: Promise; +} + +/** + * What a prepared texture depends on, in a fixed order: identities for objects and copied values + * for scalars. A mutable object cannot show that an old width changed, so each scalar is copied. + * The binding fields are the ones `Texture.copy` carries, except the `name` and `userData` + * metadata, plus the optional `wrapR` and `compareFunction`. Matrix elements are spread so their + * numbers, not the mutable array, are compared. + */ +function snapshotOf(texture: CompressedLike): unknown[] { + const image = texture.image as { width?: unknown; height?: unknown; depth?: unknown } | null; + const fields: unknown[] = [ + texture.version, + texture.source, + texture.source.version, + texture.source.data, + texture.source.dataReady, + texture.mipmaps, + image?.width, + image?.height, + image?.depth, + texture.mapping, + texture.channel, + texture.format, + texture.type, + texture.internalFormat, + texture.normalized, + texture.colorSpace, + texture.wrapS, + texture.wrapT, + "wrapR" in texture ? texture.wrapR : undefined, + texture.magFilter, + texture.minFilter, + texture.anisotropy, + "compareFunction" in texture ? texture.compareFunction : undefined, + texture.flipY, + texture.premultiplyAlpha, + texture.unpackAlignment, + texture.generateMipmaps, + texture.matrixAutoUpdate, + texture.offset.x, + texture.offset.y, + texture.repeat.x, + texture.repeat.y, + texture.center.x, + texture.center.y, + texture.rotation, + ...texture.matrix.elements, + texture.renderTarget, + texture.isRenderTargetTexture, + texture.isArrayTexture, + ]; + for (const mip of texture.mipmaps) { + const depth = "depth" in mip ? mip.depth : undefined; + fields.push(mip, mip.data, mip.width, mip.height, depth); + } + return fields; +} + +function sameSnapshot(expected: readonly unknown[], actual: readonly unknown[]): boolean { + if (expected.length !== actual.length) return false; + return expected.every((value, index) => Object.is(value, actual[index])); +} + +const TEXTURE_BINDING = 4; +const COPY_DST = 2; + +/** + * One serialized upload lane for cold compressed textures. The lane keys work by `Texture` + * identity, deduplicates concurrent callers of the same texture, keeps a caller's abort from + * cancelling another caller, and owns every `GPUTexture` it allocates until the original is + * disposed, the renderer disposes it, or the device is lost. It never mutates or disposes the + * borrowed original. + */ +export class CompressedTextureUploads { + private readonly jobs = new Map(); + private readonly cache = new Map(); + private readonly owned = new Set(); + private readonly reverse = new Map(); + private tail: Promise = Promise.resolve(); + private disposed = false; + private readonly observedDevices = new WeakSet(); + private deviceLost: Error | undefined; + + constructor( + private readonly getDevice: () => ICompressedUploadDevice | undefined, + private readonly options: { readonly budgetMs: number; readonly maxBytesPerWrite: number } = { + budgetMs: 2, + maxBytesPerWrite: 65_536, + }, + ) {} + + /** + * Resolves a texture this lane owns back to the original it was prepared from. A repeated call + * that hands back a previously published replacement keeps its identity instead of looking like + * an unrelated texture. + */ + resolveOriginal(texture: Texture): Texture { + return this.reverse.get(texture as ExternalTexture) ?? texture; + } + + /** Uploads one compressed texture and resolves the prepared `Texture`, or the original if it is not compressed. */ + upload(texture: Texture, signal?: AbortSignal): Promise { + if (this.disposed) + return Promise.reject(new Error("Renderer disposed during texture preparation.")); + if (this.deviceLost) return Promise.reject(this.deviceLost); + const device = this.getDevice(); + if (device === undefined) + return Promise.reject( + new Error("WebGPU renderer lacks bounded compressed texture preparation."), + ); + this.subscribeLoss(device); + if (signal?.aborted) return Promise.reject(signal.reason); + + const original = this.resolveOriginal(texture); + const job = this.jobs.get(original); + if (job !== undefined) { + if (job.version !== original.version) + return Promise.reject(new Error("Texture changed during preparation.")); + // A pending job serves only the device and source snapshot it captured when it started. + if (job.device !== device) + return Promise.reject(new Error("Texture preparation is pending on another device.")); + if (!sameSnapshot(job.snapshot, snapshotOf(original as CompressedLike))) + return Promise.reject(new Error("Texture changed during preparation.")); + return this.wait(job, signal); + } + + const cached = this.cache.get(original); + if (cached !== undefined && this.matches(cached, device, original as CompressedLike)) + return Promise.resolve(cached.prepared); + + if ( + !( + ("isCompressedTexture" in original && original.isCompressedTexture === true) || + ("isCompressedArrayTexture" in original && original.isCompressedArrayTexture === true) + ) + ) + return Promise.resolve(original); + if ( + original.version <= 0 || + original.mipmaps.length === 0 || + original.source.dataReady !== true + ) + return Promise.reject( + new Error("Bounded texture preparation requires ready compressed mip data."), + ); + const block = compressedFormatFor(original); + if (block === undefined) + return Promise.reject(new Error("Unsupported compressed texture format.")); + if (device.features?.has(block.feature) !== true) + return Promise.reject(new Error(`Compressed texture format requires ${block.feature}.`)); + + const started = this.start(original as CompressedLike, device, block); + return this.wait(started, signal); + } + + private matches( + entry: ICacheEntry, + device: ICompressedUploadDevice, + texture: CompressedLike, + ): boolean { + return entry.device === device && sameSnapshot(entry.snapshot, snapshotOf(texture)); + } + + private start( + texture: CompressedLike, + device: ICompressedUploadDevice, + block: ICompressedFormatEntry, + ): IUploadJob { + const controller = new AbortController(); + const version = texture.version; + // The job reads its snapshot before it queues. A source edit made while the job waits rejects + // its write. + const snapshot = snapshotOf(texture); + // Queued and in-flight work needs its own dispose hook until commit adds the cache listener. + // The job removes this hook when it settles. + const transient = () => this.releaseOriginal(texture); + texture.addEventListener("dispose", transient); + const cancelled = new Promise((_resolve, reject) => + controller.signal.addEventListener("abort", () => reject(controller.signal.reason), { + once: true, + }), + ); + const work = this.tail.then(() => { + if (controller.signal.aborted) throw controller.signal.reason; + return this.write(texture, device, block, controller.signal, snapshot); + }); + const job: IUploadJob = { + controller, + device, + promise: Promise.resolve(undefined as never), + snapshot, + users: 0, + version, + }; + // Owning this job's outcome on `work` (not on the raced promise) means a late write that + // loses the race to cancellation still releases its allocation instead of leaking it. + const settle = work.then((prepared) => { + if ( + controller.signal.aborted || + this.getDevice() !== device || + !sameSnapshot(snapshot, snapshotOf(texture)) + ) { + this.destroyOwned(prepared); + throw new Error("Texture preparation invalidated."); + } + this.commit(texture, device, prepared, snapshot); + return prepared; + }); + job.promise = Promise.race([settle, cancelled]).finally(() => { + texture.removeEventListener("dispose", transient); + if (this.jobs.get(texture) === job) this.jobs.delete(texture); + }); + this.jobs.set(texture, job); + // Keep the lane serialized until the backend work and validation settle. + this.tail = Promise.allSettled([work, job.promise]).then(() => undefined); + return job; + } + + /** + * Registers a prepared replacement and keeps the original's dispose listener alive while any + * cache entry or owned version for it survives. A version refresh publishes a new replacement + * and leaves the previous one valid until its lifetime ends. + */ + private commit( + texture: CompressedLike, + device: ICompressedUploadDevice, + prepared: ExternalTexture, + snapshot: readonly unknown[], + ): void { + const existing = this.cache.get(texture); + if (existing === undefined) { + const listener = () => this.releaseOriginal(texture); + texture.addEventListener("dispose", listener); + this.cache.set(texture, { device, snapshot, prepared, listener }); + } else { + existing.device = device; + existing.prepared = prepared; + existing.snapshot = snapshot; + } + this.owned.add(prepared); + this.reverse.set(prepared, texture); + } + + private releaseOriginal(texture: Texture): void { + const entry = this.cache.get(texture); + if (entry !== undefined) { + texture.removeEventListener("dispose", entry.listener); + this.cache.delete(texture); + } + for (const prepared of [...this.owned]) { + if (this.reverse.get(prepared) === texture) { + this.owned.delete(prepared); + this.reverse.delete(prepared); + destroyPrepared(prepared); + } + } + this.jobs.get(texture)?.controller.abort(new Error("Texture disposed during preparation.")); + } + + private destroyOwned(prepared: ExternalTexture): void { + if (this.owned.delete(prepared)) { + this.reverse.delete(prepared); + destroyPrepared(prepared); + } + } + + private wait(job: IUploadJob, signal?: AbortSignal): Promise { + job.users += 1; + return new Promise((resolve, reject) => { + let settled = false; + const finish = (error: unknown, value?: ExternalTexture) => { + if (settled) return; + settled = true; + signal?.removeEventListener("abort", abort); + job.users -= 1; + if (error !== undefined && error !== null) reject(error); + else resolve(value as ExternalTexture); + if (job.users === 0 && !job.controller.signal.aborted) + job.controller.abort(new Error("Texture preparation has no remaining caller.")); + }; + const abort = () => finish(signal?.reason ?? new Error("Texture preparation cancelled.")); + signal?.addEventListener("abort", abort, { once: true }); + job.promise.then( + (value) => finish(undefined, value), + (error: unknown) => finish(error), + ); + }); + } + + private async write( + texture: CompressedLike, + device: ICompressedUploadDevice, + block: ICompressedFormatEntry, + signal: AbortSignal, + snapshot: readonly unknown[], + ): Promise { + const mipmaps = texture.mipmaps; + const check = () => { + if (signal.aborted) throw signal.reason ?? new Error("Compressed texture upload cancelled."); + // A replaced device invalidates the job the same way a changed source does. + if (this.getDevice() !== device || !sameSnapshot(snapshot, snapshotOf(texture))) + throw new Error("Texture preparation invalidated."); + }; + check(); + // Validate every dimension, layer, mip size and data bound before any GPU allocation. + const plan = planCompressedUpload(texture, block, this.options.maxBytesPerWrite); + check(); + if ( + (plan.width % block.blockWidth !== 0 || plan.height % block.blockHeight !== 0) && + device.features?.has("texture-compression-unaligned") !== true + ) + throw new Error( + "Compressed texture base must be block-aligned without texture-compression-unaligned.", + ); + const gpuTexture = device.createTexture({ + size: { width: plan.width, height: plan.height, depthOrArrayLayers: plan.layers }, + format: block.gpuFormat, + mipLevelCount: Math.max(1, mipmaps.length), + dimension: "2d", + usage: TEXTURE_BINDING | COPY_DST, + }); + const prepared = new ExternalTexture(gpuTexture); + // Own the replacement from creation, before `.copy` and any await, so a throwing copy, an + // invalidation or `dispose` releases the allocation immediately and exactly once; `Set.delete` + // in `destroyOwned` keeps that from double-destroying a committed version. + this.owned.add(prepared); + this.reverse.set(prepared, texture); + try { + // `ExternalTexture.copy` shares the original's `source` and increments its version. + // Copy from a view whose `source` is a new `Source` over the same data. The original keeps + // its own `source` and version. + const isolatedSource = new Source(texture.source.data); + const view = new Proxy(texture, { + get(target, key, receiver) { + return key === "source" ? isolatedSource : Reflect.get(target, key, receiver); + }, + }); + // `ExternalTexture.copy` copies the original's `sourceTexture` (undefined for a cold texture), + // so reassign the public GPU backing the frame binds after the copy. + prepared.copy(view as never); + prepared.sourceTexture = gpuTexture; + prepared.needsUpdate = true; + await this.writeTiles(device, gpuTexture, plan, signal, check); + } catch (error) { + // `.copy` can reset the public backing before it throws; restore it so the owned release + // destroys the freshly allocated GPU texture exactly once. + prepared.sourceTexture = gpuTexture; + this.destroyOwned(prepared); + throw error; + } + return prepared; + } + + private async writeTiles( + device: ICompressedUploadDevice, + gpuTexture: { destroy(): void }, + plan: ICompressedUploadPlan, + signal: AbortSignal, + check: () => void, + ): Promise { + const budgetMs = this.options.budgetMs; + const scoped = + typeof device.pushErrorScope === "function" && typeof device.popErrorScope === "function"; + const failures: unknown[] = []; + const validations: Promise[] = []; + let started: number | null = null; + let index = 0; + try { + while (index < plan.tiles.length && failures.length === 0) { + check(); + if (started === null) started = performance.now(); + if (performance.now() - started >= budgetMs) { + await new Promise((resolve) => setTimeout(resolve, 0)); + started = performance.now(); + check(); + } + if (scoped) { + device.pushErrorScope?.("out-of-memory"); + device.pushErrorScope?.("validation"); + } + try { + do { + check(); + const tile = plan.tiles[index]; + if (tile === undefined) break; + device.queue.writeTexture( + { texture: gpuTexture, mipLevel: tile.mipLevel, origin: tile.origin }, + tile.data, + tile.layout, + tile.extent, + ); + index += 1; + } while (index < plan.tiles.length && performance.now() - started < budgetMs); + } finally { + if (scoped) { + validations.push( + Promise.allSettled([device.popErrorScope?.(), device.popErrorScope?.()]).then( + (results) => { + for (const result of results) { + if (result.status === "rejected") failures.push(result.reason); + else if (result.value !== null && result.value !== undefined) + failures.push(new Error((result.value as { message?: string }).message)); + } + }, + ), + ); + } + } + if (validations.length === 4) await validations.shift(); + check(); + if (index < plan.tiles.length && failures.length === 0) + await new Promise((resolve) => setTimeout(resolve, 0)); + } + } finally { + // A `check` or `writeTexture` throw exits the loop early. Settle every popped scope before + // the lane advances. `allSettled` keeps the original failure as the propagated error. + await Promise.allSettled(validations); + } + if (failures.length > 0) + throw new AggregateError( + failures, + `Compressed texture upload failed: ${failures + .map((error) => (error as Error)?.message ?? String(error)) + .join("; ")}`, + ); + check(); + } + + private subscribeLoss(device: ICompressedUploadDevice): void { + if (this.observedDevices.has(device) || device.lost === undefined) return; + this.observedDevices.add(device); + const lost = (error: unknown) => { + this.deviceLost = + error instanceof Error ? error : new Error("Device lost during texture preparation."); + for (const job of this.jobs.values()) job.controller.abort(this.deviceLost); + for (const prepared of [...this.owned]) this.destroyOwned(prepared); + }; + device.lost.then(lost, lost); + } + + /** Releases every GPU allocation and listener the lane owns. Originals are untouched. */ + dispose(): void { + this.disposed = true; + for (const job of this.jobs.values()) + job.controller.abort(new Error("Renderer disposed during texture preparation.")); + this.jobs.clear(); + for (const [texture, entry] of this.cache) + texture.removeEventListener("dispose", entry.listener); + this.cache.clear(); + for (const prepared of [...this.owned]) this.destroyOwned(prepared); + this.owned.clear(); + this.reverse.clear(); + } +} diff --git a/packages/core/src/frame-budget.ts b/packages/core/src/frame-budget.ts index fb4527da7d..da0a26d1c2 100644 --- a/packages/core/src/frame-budget.ts +++ b/packages/core/src/frame-budget.ts @@ -422,10 +422,12 @@ export interface IFrameBudgetWindow { export interface IFrameBudgetSimulation { readonly tick: () => number; readonly step: number; + /** False selects presented-frame windows; frozen or unspecified clocks use simulation ticks. */ + readonly clockFrozen?: boolean; } export interface IFrameBudgetOptions { - /** Simulation ticks per report window when a clock is supplied, otherwise presented frames. Default 300. */ + /** Simulation ticks per window unless the clock is live or absent, then presented frames. Default 300. */ readonly reportEvery?: number; /** Minimum measured rendered frames per report window. Default 1. */ readonly minFrames?: number; @@ -1299,9 +1301,9 @@ export class FrameBudget { } #maybeReport(): void { - // Callers without a loop clock retain presented-frame window boundaries. + // Live clocks must keep reporting even while simulation is held or has not advanced. const elapsed = - this.#simulation === undefined + this.#simulation === undefined || this.#simulation.clockFrozen === false ? this.#framesInWindow : this.#simulation.tick() - (this.#windowStartTick ?? this.#simulation.tick()); if (elapsed < this.reportEvery || this.#framesInWindow < this.minFrames) return; diff --git a/packages/core/src/game.ts b/packages/core/src/game.ts index 4749176949..b46d6dc4fb 100644 --- a/packages/core/src/game.ts +++ b/packages/core/src/game.ts @@ -20,7 +20,11 @@ import { rendererBackendIdentity, } from "./geometry-capture.js"; import { type ContextMenuPolicy, type InputBindings, InputMap } from "./input.js"; -import { watchDeviceLoss, watchStartupStall } from "./launch-diagnostics.js"; +import { + reportSceneLoadFailure, + watchDeviceLoss, + watchStartupStall, +} from "./launch-diagnostics.js"; import { FixedStepLoop, type IAfterPhysicsPhase, @@ -870,7 +874,13 @@ class GameImpl, TPhysics> const scene = new SceneType(); this.#scene = scene; this.#sceneName = name; - const loaded = scene.load(ctx); + let loaded: ReturnType["load"]>; + try { + loaded = scene.load(ctx); + } catch (error) { + reportSceneLoadFailure(error); + throw error; + } const transition = loaded === undefined ? this.#enterTransitionScene(scene, ctx) @@ -880,7 +890,10 @@ class GameImpl, TPhysics> }; // Handled here as well as by the caller: a start scene that navigates inside `enter()` throws // its transition away, and the gate below still has to learn that no world arrived. - void transition.then(settled, settled); + void transition.then(settled, (error: unknown) => { + settled(); + reportSceneLoadFailure(error); + }); this.#pendingSceneEnter = transition; return transition; } @@ -1460,6 +1473,9 @@ class GameImpl, TPhysics> get step() { return gameLoop.step; }, + get clockFrozen() { + return gameLoop.clockFrozen; + }, }, // The scaler reads the same windows the marker reports, so what it acted on and what // the record shows are the same measurement rather than two sampling paths. @@ -2070,6 +2086,7 @@ class GameImpl, TPhysics> startHeldLoop(); if (loaded !== undefined) await loaded; } catch (error) { + reportSceneLoadFailure(error); this.#teardown(ctx); throw error; } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2b24d8981b..5adaaec810 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -916,7 +916,7 @@ export { Daylight } from "./render/daylight.js"; export type { IDaylightOptions } from "./render/daylight.js"; export type { IVirtualShadowOptions, IVirtualShadowStats } from "./render/virtual-shadow.js"; /** - * Attach hundreds of built objects to the scene in slices, presenting a frame between each. + * Attach hundreds of built objects to the scene in slices with an 8 ms default budget, presenting a frame between each. * @situation add hundreds of built objects to the scene without one multi-second frame * @situation stream a detail tier in behind a loading curtain without the page looking hung * @constraint the objects, and where each one goes, stay the game's; this decides only when each joins the graph diff --git a/packages/core/src/launch-diagnostics.ts b/packages/core/src/launch-diagnostics.ts index 9fdcc7487f..2d115418d0 100644 --- a/packages/core/src/launch-diagnostics.ts +++ b/packages/core/src/launch-diagnostics.ts @@ -1,11 +1,9 @@ /** * Why a launch stopped, said where the player can read it. * - * A launch has exactly two ways to go wrong quietly, and both were measured on a real game: - * progress stops moving (a decode that never returns, an asset that never settles) and the GPU - * device is lost (another process had taken 94% of VRAM). In both the loop keeps iterating, the - * loading layer keeps painting, and the only account of what happened is a line on stdout — which - * a player does not have, and which a bug report therefore never carries. + * A launch can fail when a scene load rejects, progress stops moving, or the GPU device is + * lost. Without a report, the player sees a loading layer while the actionable error stays + * on stdout or in a discarded scene-transition promise. * * So the failure is reported as text, once, through `onLaunchFailure` — and core stops there. Core * cannot draw it: on the native host `document` is a Three.js compatibility stub whose @@ -14,8 +12,8 @@ * asked for when they report a launch that hung); this owns the noticing and the wording. */ -/** What failed. `stalled` is "no progress for a while"; `device-lost` is the GPU going away. */ -export type LaunchFailureKind = "stalled" | "device-lost"; +/** What failed: stalled progress, a rejected scene load, or a lost GPU device. */ +export type LaunchFailureKind = "stalled" | "device-lost" | "load-failed"; export interface ILaunchFailure { readonly kind: LaunchFailureKind; @@ -47,7 +45,12 @@ export function resetLaunchFailures(): void { export function reportLaunchFailure(failure: ILaunchFailure): void { if (reported.has(failure.kind)) return; reported.add(failure.kind); - const marker = failure.kind === "stalled" ? "TN_STARTUP_STALLED" : "TN_DEVICE_LOST"; + const marker = + failure.kind === "stalled" + ? "TN_STARTUP_STALLED" + : failure.kind === "load-failed" + ? "TN_STARTUP_FAILED" + : "TN_DEVICE_LOST"; console.error(`${marker}: ${failure.message}`); for (const listener of listeners) listener(failure); } @@ -143,3 +146,11 @@ export function watchDeviceLoss( }); }); } + +/** Preserve the actual loader error for the shell and playtest console diagnostics. */ +export function reportSceneLoadFailure(error: unknown): void { + reportLaunchFailure({ + kind: "load-failed", + message: error instanceof Error ? error.message : String(error), + }); +} diff --git a/packages/core/src/model-lod.ts b/packages/core/src/model-lod.ts index 43cffaad1f..53fc3788a4 100644 --- a/packages/core/src/model-lod.ts +++ b/packages/core/src/model-lod.ts @@ -4,6 +4,7 @@ import { BufferGeometry, type Camera, Group, + InstancedMesh, Matrix4, type Mesh, Object3D, @@ -11,6 +12,7 @@ import { Vector3, } from "three"; import { pixelsPerUnit } from "./clustered-mesh.js"; +import type { attachInstancedLod } from "./instanced-batch-lod.js"; /** * Engine-owned detail selection for automatic discrete LOD (`TN_discrete_lod`). @@ -228,6 +230,7 @@ interface IParserLike { animations?: { channels?: { target?: { node?: number } }[] }[]; extensions?: Record; meshes?: { name?: string; primitives?: { extensions?: Record }[] }[]; + nodes?: { mesh?: number; extensions?: Record }[]; }; } @@ -895,6 +898,7 @@ function controllerFor(mesh: Mesh): IModelLodController | undefined { export class DiscreteLodPlugin { readonly name = TN_DISCRETE_LOD; #parser: IParserLike | undefined; + #associations: IParserLike["associations"] | undefined; readonly #pending = new Map(); readonly #rungs: { readonly def: IJoinedRungDef; readonly prototype: Object3D }[] = []; /** A temporary holder for far-mesh prototypes, so widening reaches their joined geometry. */ @@ -904,10 +908,33 @@ export class DiscreteLodPlugin { this.#parser = parser; } + async beforeRoot(): Promise { + const parser = this.#parser; + if (parser === undefined) return; + // Each glTF scene prunes parser.associations; retain the complete node map before that. + await Promise.all( + (parser.json.nodes ?? []).map((_, index) => parser.getDependency("node", index)), + ); + this.#associations = parser.associations; + } + async afterRoot(result: { scene?: Object3D }): Promise { const parser = this.#parser; if (parser === undefined) return; - for (const [object, association] of parser.associations) { + const associations = this.#associations ?? parser.associations; + // GLTFLoader's instancing extension replaces the primitive meshes without copying associations. + for (const [object, association] of associations) { + if (association?.primitives !== undefined) continue; + const node = parser.json.nodes?.[association?.nodes ?? -1]; + if (node?.mesh === undefined || node.extensions?.EXT_mesh_gpu_instancing === undefined) + continue; + const parts = object instanceof InstancedMesh ? [object] : (object as Object3D).children; + for (const [primitives, mesh] of parts.entries()) { + if (!(mesh instanceof InstancedMesh)) continue; + associations.set(mesh, { ...association, meshes: node.mesh, primitives }); + } + } + for (const [object, association] of associations) { const mesh = object as Mesh; if (mesh.isMesh !== true) continue; const { meshes, primitives } = association; @@ -970,7 +997,11 @@ export class DiscreteLodPlugin { } /** Builds the derived geometries against the mesh's final (widened) attributes and registers. */ - attach(_root: Object3D, policy: IModelLodPolicy | undefined): number { + attach( + _root: Object3D, + policy: IModelLodPolicy | undefined, + instancedLod?: typeof attachInstancedLod, + ): number { const applied: IAppliedLod = { hysteresis: policy?.hysteresis ?? DISCRETE_LOD_DEFAULT_HYSTERESIS, maxPixelError: policy?.maxPixelError ?? DISCRETE_LOD_DEFAULT_ERROR_PIXELS, @@ -1022,7 +1053,15 @@ export class DiscreteLodPlugin { // a coarser level carries that derived geometry, and must still resolve to this chain. for (const geometry of levels) chains.set(geometry, registered); preserveChainClones(registered); - controllers.set(mesh, new ModelLod(mesh, registered.chain, applied)); + if (mesh instanceof InstancedMesh) { + if (instancedLod === undefined) + throw new Error( + "TN_discrete_lod requires the instanced LOD attachment for InstancedMesh.", + ); + instancedLod(mesh, { autoLod: policy }); + } else { + controllers.set(mesh, new ModelLod(mesh, registered.chain, applied)); + } appliedCount += 1; } this.#registerJoinedRungs(applied); diff --git a/packages/core/src/render-pass-budget.ts b/packages/core/src/render-pass-budget.ts index f64b04532d..eee4111984 100644 --- a/packages/core/src/render-pass-budget.ts +++ b/packages/core/src/render-pass-budget.ts @@ -203,7 +203,7 @@ function readCounters(target: IRenderPassTarget): { draws: number; triangles: nu return { draws, triangles }; } -const installed = new WeakSet(); +const installed = new WeakMap(); /** * Resolved frames kept for GPU attribution. A resolve lags the frame it measures by a few frames, @@ -213,11 +213,16 @@ const installed = new WeakSet(); const GPU_UID_FRAME_LIMIT = 64; /** - * Names a render call by what three already calls it: the outermost call is the main camera, and a - * nested call carries the scene name three or the reflector gave it. + * Three's shadow/reflection names take priority over the renderer's actual world root/camera. + * Standalone raw renders retain the outermost-main convention when no world draw is scoped. */ -function passKind(scene: unknown, depth: number): FramePassKind { - if (depth === 0) return "main"; +function passKind( + scene: unknown, + camera: unknown, + depth: number, + world: { scene: unknown; camera: unknown } | undefined, +): FramePassKind { + if (world === undefined && depth === 0) return "main"; const name = typeof scene === "object" && scene !== null && @@ -226,6 +231,7 @@ function passKind(scene: unknown, depth: number): FramePassKind { : ""; if (name.startsWith("Shadow Map")) return "shadow"; if (/reflect/iu.test(name)) return "reflection"; + if (world !== undefined && scene === world.scene && camera === world.camera) return "main"; return "nested"; } @@ -258,6 +264,7 @@ export class RenderPassBudget { readonly #original: (scene: unknown, camera: unknown) => void; readonly #frame: IRenderPassSample[] = []; readonly #stack: IPassFrame[] = []; + #world: { scene: unknown; camera: unknown } | undefined; /** Timestamp uids each render call allocated, keyed by the Three.js frame they belong to. */ readonly #gpuUids = new Map(); /** The last frame `nextGpuFrame` handed out; every earlier frame has been reported. */ @@ -280,7 +287,7 @@ export class RenderPassBudget { childTriangles: 0, entryDraws: counters.draws, entryTriangles: counters.triangles, - kind: passKind(scene, depth), + kind: passKind(scene, camera, depth, this.#world), }; this.#stack.push(frame); try { @@ -309,7 +316,7 @@ export class RenderPassBudget { }; target.render = instrumented; this.#instrumentDraws(target.info); - installed.add(target); + installed.set(target, this); } /** @@ -349,6 +356,22 @@ export class RenderPassBudget { return new RenderPassBudget(target); } + /** The renderer's authored root/camera, including scene passes nested beneath post processing. */ + static renderWorld(target: object, scene: unknown, camera: unknown, draw: () => void): void { + const budget = installed.get(target); + if (budget === undefined) { + draw(); + return; + } + const previous = budget.#world; + budget.#world = { scene, camera }; + try { + draw(); + } finally { + budget.#world = previous; + } + } + /** * The kind of the innermost render call currently running, or undefined outside one. * diff --git a/packages/core/src/render/world-impostor.ts b/packages/core/src/render/world-impostor.ts index e70c9a751e..8e74dd11cc 100644 --- a/packages/core/src/render/world-impostor.ts +++ b/packages/core/src/render/world-impostor.ts @@ -38,7 +38,7 @@ import { mipAlphaTestNode } from "./foliage-alpha.js"; export const WORLD_IMPOSTOR_MARKER = "TN_WORLD_IMPOSTOR"; /** Views per atlas row and column: a full-sphere octahedral grid, so `N*N` layers. */ -export const IMPOSTOR_VIEW_GRID = 4; +export const IMPOSTOR_VIEW_GRID = 8; /** Layers the atlas holds: one per view. */ export const IMPOSTOR_VIEWS = IMPOSTOR_VIEW_GRID * IMPOSTOR_VIEW_GRID; /** diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts index 4b97750575..fb489f3405 100644 --- a/packages/core/src/renderer.ts +++ b/packages/core/src/renderer.ts @@ -5,6 +5,10 @@ import { RenderPipeline, type StorageBufferAttribute, } from "three/webgpu"; +import { + CompressedTextureUploads, + type ICompressedUploadDevice, +} from "./compressed-texture-preparation.js"; import { ComputeTimingScopes, type IComputeTimingReceipt } from "./compute-timing.js"; import type { IFrameSurfaceState } from "./frame-budget.js"; import { @@ -23,7 +27,7 @@ import { type PipelineCensus, createPipelineCensus, } from "./pipeline-census.js"; -import type { ITimestampQueryPool } from "./render-pass-budget.js"; +import { type ITimestampQueryPool, RenderPassBudget } from "./render-pass-budget.js"; import { AlphaAntialiasing, type IAlphaAntialiasingReport } from "./render/alpha-antialiasing.js"; import { type IRenderChainBudgetWindow, @@ -252,11 +256,18 @@ export interface IRendererLike { * Ordinary textures and the WebGL fallback retain their existing paths and report no work. * `onProgress` receives the completed unique compressed texture count after each preparation * promise resolves; queued textures and writes that have not settled receive no credit. + * + * Pass `replacements` to receive caller-owned `original -> prepared` pairs. The renderer owns + * the prepared GPU textures and publishes the map only after the whole call succeeds; an abort + * or failure leaves the caller's map unchanged. Without it the call keeps the numeric contract + * and prepared compressed replacements stay unpublished, so a material that samples the + * original keeps its borrowed identity. */ prepareTextures?( textures: Iterable, signal?: AbortSignal, onProgress?: (completed: number) => void, + replacements?: Map, ): Promise; /** * Copies one GPU storage attribute back to the CPU, asynchronously. @@ -468,10 +479,6 @@ type RendererInstance = IStorageBufferSource & { domElement: HTMLCanvasElement; init?: () => Promise; compileAsync?: (scene: Object3D, camera: Camera, targetScene?: Object3D) => Promise; - prepareTextureAsync?: ( - texture: Texture, - options: { budgetMs: number; maxBytesPerWrite: number; signal: AbortSignal }, - ) => Promise; compute?: (node: unknown) => void; getArrayBufferAsync?: ( attribute: unknown, @@ -559,7 +566,9 @@ function wrapRenderer( let activeCompiles = 0; let compileCount = 0; let disposed = false; - const texturePreparations = new Set(); + const texturePreparations = new CompressedTextureUploads( + () => (raw.backend as { device?: ICompressedUploadDevice } | undefined)?.device, + ); const storageBuffers = new StorageBufferLeases(() => raw); let computeTimings: ComputeTimingScopes | undefined; let pendingScale: { scale: number; source: "auto" | "auto-pinned" } | undefined; @@ -948,39 +957,47 @@ function wrapRenderer( } return created; }, - prepareTextures: async (textures, signal, onProgress) => { + prepareTextures: async (textures, signal, onProgress, replacements) => { if (disposed) throw new Error("Renderer disposed during texture preparation."); if (kind !== "webgpu") return 0; - const unique = new Set( - [...textures].filter( - (texture) => "isCompressedTexture" in texture && texture.isCompressedTexture === true, - ), - ); - if (unique.size === 0) return 0; - if (typeof raw.prepareTextureAsync !== "function") - throw new Error("WebGPU renderer lacks bounded compressed texture preparation."); - const controller = new AbortController(); - const abort = () => controller.abort(signal?.reason); - if (signal?.aborted) abort(); - else signal?.addEventListener("abort", abort, { once: true }); - texturePreparations.add(controller); + // Snapshot the caller's iterable once. An owned replacement resolves back to its original, + // so a repeated call that hands back a published replacement keeps a stable identity instead + // of looking like an unrelated texture and returning stale work. + const inputs: Texture[] = []; + const originals = new Set(); + for (const texture of textures) { + inputs.push(texture); + const original = texturePreparations.resolveOriginal(texture); + const compressed = + "isCompressedTexture" in original && original.isCompressedTexture === true; + const compressedArray = + "isCompressedArrayTexture" in original && original.isCompressedArrayTexture === true; + if (original !== texture || compressed || compressedArray) originals.add(original); + } + if (originals.size === 0) return 0; + const preparedReplacements = new Map(); let completed = 0; - try { - for (const texture of unique) { - if (controller.signal.aborted) throw controller.signal.reason; - await raw.prepareTextureAsync(texture, { - budgetMs: 2, - maxBytesPerWrite: 65_536, - signal: controller.signal, - }); + for (const original of originals) { + if (signal?.aborted) throw signal.reason; + const prepared = await texturePreparations.upload(original, signal); + if (prepared !== original) { + preparedReplacements.set(original, prepared); completed += 1; onProgress?.(completed); } - return unique.size; - } finally { - texturePreparations.delete(controller); - signal?.removeEventListener("abort", abort); } + if (disposed) throw new Error("Renderer disposed during texture preparation."); + if (signal?.aborted) throw signal.reason; + // Publish only after every texture in this call succeeded; abort or failure left this local. + if (replacements !== undefined) { + for (const [original, prepared] of preparedReplacements) { + replacements.set(original, prepared); + for (const input of inputs) + if (texturePreparations.resolveOriginal(input) === original) + replacements.set(input, prepared); + } + } + return completed; }, scenePassDepth: () => { // Three's implicit depth is in Textures.updateRenderTarget's data, not target.depthTexture. @@ -1084,9 +1101,7 @@ function wrapRenderer( return; } disposed = true; - for (const preparation of texturePreparations) - preparation.abort(new Error("Renderer disposed during texture preparation.")); - texturePreparations.clear(); + texturePreparations.dispose(); gpuObservation?.dispose(); gpuObservation = undefined; pendingScale = undefined; @@ -1110,7 +1125,7 @@ function wrapRenderer( if (!timestampFramesManaged) setTimestampTracking(); renderingFrame += 1; try { - renderFrame(scene, camera); + RenderPassBudget.renderWorld(raw, scene, camera, () => renderFrame(scene, camera)); } finally { renderingFrame -= 1; } diff --git a/packages/core/src/warmup.ts b/packages/core/src/warmup.ts index 19fdc98fb6..5b223b6934 100644 --- a/packages/core/src/warmup.ts +++ b/packages/core/src/warmup.ts @@ -659,16 +659,21 @@ interface IReflectorNodeLike { * * Three's `reflector()` hangs a `_reflectorBaseNode` off a `ReflectorNode` that a material's * `colorNode` (or a node composed around it) references. The walk is bounded and only descends - * real nodes, so a material with no reflector costs one shallow pass. + * real nodes, so a material with no reflector costs one shallow pass. `seen` holds the shallowest + * depth each node was reached at: a TSL graph shares subgraphs, and without it every path to a + * shared node walks it again. */ -function nodeHasReflector(root: unknown, depth: number): boolean { +function nodeHasReflector(root: unknown, depth: number, seen: Map): boolean { if (depth > 64 || typeof root !== "object" || root === null) return false; const node = root as IReflectorNodeLike; if (node._reflectorBaseNode !== undefined) return true; if (node.isNode !== true || typeof node.getChildren !== "function") return false; + const reached = seen.get(node); + if (reached !== undefined && reached <= depth) return false; + seen.set(node, depth); try { for (const child of node.getChildren()) { - if (nodeHasReflector(child, depth + 1)) return true; + if (nodeHasReflector(child, depth + 1, seen)) return true; } } catch { return false; @@ -677,6 +682,7 @@ function nodeHasReflector(root: unknown, depth: number): boolean { } function hasReflector(scene: Object3D): boolean { + const seen = new Map(); const stack: Object3D[] = [scene]; while (stack.length > 0) { const object = stack.pop() as Object3D & { material?: unknown }; @@ -687,7 +693,7 @@ function hasReflector(scene: Object3D): boolean { (material as { isNodeMaterial?: boolean }).isNodeMaterial === true ) { for (const value of Object.values(material as Record)) { - if (nodeHasReflector(value, 0)) return true; + if (nodeHasReflector(value, 0, seen)) return true; } } const children = object.children ?? []; diff --git a/packages/core/src/world-bundle-safety.ts b/packages/core/src/world-bundle-safety.ts new file mode 100644 index 0000000000..600a26ab8e --- /dev/null +++ b/packages/core/src/world-bundle-safety.ts @@ -0,0 +1,295 @@ +import type { Material, Mesh } from "three"; +import { isEngineRenderHook } from "./engine-render-hook.js"; + +/** + * The value `NodeUpdateType.RENDER` has, read as text so this file needs no import for it: a node + * whose `updateBefore` runs on every render is a node whose work happens inside the draw that + * recorded it, which is the whole test. + */ +const PER_RENDER_UPDATE = "render"; + +/** + * Whether a node graph copies or renders the framebuffer, which a bundle encoder cannot be asked for. + * + * Every such node does its work in `updateBefore` and every one of them touches the *current* render + * pass: `ViewportTextureNode` copies the bound framebuffer into a texture, and transmission, an + * `RTTNode`, a `ReflectorNode` or a `PassNode` renders into one. Recorded into a bundle, the current + * pass is a `GPURenderBundleEncoder`, which has no `end()` to close and no framebuffer to read, so + * the call throws `currentPass.end is not a function` from inside the record and takes the whole + * bundle's frame with it. + * + * Structural, over own properties and plain sub-objects, because a material's slots are node graphs + * rather than one node: a `viewportSharedTexture()` under an arithmetic chain is still this. What it + * cannot see is a node hidden inside an `Fn` body, which is how three keeps its own transmission + * sampler — hence `transmission` is asked of the material itself as well. + */ +interface IBundleGraphSnapshot { + readonly node: object; + readonly keys: readonly string[]; + readonly links: readonly unknown[]; + readonly isNode: unknown; + readonly beforeType: unknown; + readonly before: string; + readonly version: unknown; + readonly terminal: boolean; + readonly childReader: unknown; + readonly children: readonly object[] | undefined; +} +interface IBundleGraphCapture { + readonly rows: IBundleGraphSnapshot[]; + links: number; + overflow: boolean; +} +interface IBundleMaterialCheck { + readonly safe: boolean; + readonly revision: number; + readonly version: number; + readonly transparent: boolean; + readonly visible: boolean; + readonly transmission: unknown; + readonly rows: readonly IBundleGraphSnapshot[]; +} + +/** Own-link and installed-Node child snapshots avoid inherited TSL method scans. Deep unversioned + * edits still invalidate: version/root identity alone cannot certify a mutable node graph. A large + * graph uses the uncached safety walk, and conservatively rerecords rather than reusing a verdict. */ +export class BundleSurfaceSafety { + #checks = new WeakMap(); + #revision = 0; + readonly stats = { checks: 0, refreshes: 0, validations: 0, uncached: 0 }; + + clear(): void { + this.#checks = new WeakMap(); + } + + test(material: Material): IBundleMaterialCheck { + this.stats.checks++; + const previous = this.#checks.get(material); + if ( + previous !== undefined && + previous.version === material.version && + previous.transparent === material.transparent && + previous.visible === material.visible && + previous.transmission === Reflect.get(material, "transmission") && + previous.rows.every(graphSnapshotCurrent) + ) { + this.stats.validations++; + return previous; + } + this.stats.refreshes++; + const capture: IBundleGraphCapture = { rows: [], links: 0, overflow: false }; + const checked: IBundleMaterialCheck = { + safe: bundleSafeMaterial(material, capture), + revision: ++this.#revision, + version: material.version, + transparent: material.transparent, + visible: material.visible, + transmission: Reflect.get(material, "transmission"), + rows: capture.rows, + }; + if (capture.overflow) { + this.stats.uncached++; + this.#checks.delete(material); + } else this.#checks.set(material, checked); + return checked; + } +} + +function graphSnapshotCurrent(row: IBundleGraphSnapshot): boolean { + const node = row.node; + const before = typeof Reflect.get(node, "updateBefore"); + if ( + Reflect.get(node, "isNode") !== row.isNode || + Reflect.get(node, "updateBeforeType") !== row.beforeType || + before !== row.before || + Reflect.get(node, "version") !== row.version + ) + return false; + if (row.terminal) return true; // Still a framebuffer reader, whatever its other children do. + // TSL prototypes carry thousands of enumerable chaining methods, which are not child links. + // Own slots stay cheap; Three's actual child reader witnesses inherited container links below. + const keys = bundleGraphKeys(node); + if (keys.length !== row.keys.length) return false; + for (let index = 0; index < keys.length; index++) { + const key = keys[index] as string; + if (row.keys[index] !== key) return false; + const value = Reflect.get(node, key); + if ((value !== null && typeof value === "object" ? value : undefined) !== row.links[index]) + return false; + } + const reader = bundleGraphChildReader(node); + if (reader !== row.childReader) return false; + const children = readBundleGraphChildren(node, reader); + return ( + children !== undefined && + row.children !== undefined && + children.length === row.children.length && + children.every((child, index) => child === row.children?.[index]) + ); +} + +/** Three reads non-enumerable own Node/Material slots, but never inherited TSL chain methods. */ +function bundleGraphKeys(node: object): string[] { + return Reflect.get(node, "isNode") === true || Reflect.get(node, "isMaterial") === true + ? Object.getOwnPropertyNames(node) + : Object.keys(node); +} +function bundleGraphChildReader(node: object): unknown { + return Reflect.get(node, "isNode") === true ? Reflect.get(node, "getChildren") : undefined; +} +function readBundleGraphChildren(node: object, reader: unknown): readonly object[] | undefined { + if (reader === undefined) return []; + if (typeof reader !== "function") return undefined; + try { + const result: unknown = reader.call(node); + if ( + result === null || + typeof result !== "object" || + typeof Reflect.get(result, Symbol.iterator) !== "function" + ) + return undefined; + const children: object[] = []; + for (const child of result as Iterable) { + if ( + children.length >= 8192 || + child === null || + typeof child !== "object" || + Reflect.get(child, "isNode") !== true + ) + return undefined; + children.push(child); + } + return children; + } catch { + return undefined; + } +} +function captureBundleGraph( + node: object, + terminal: boolean, + capture: IBundleGraphCapture, + childReader: unknown, + children: readonly object[] | undefined, +): readonly unknown[] { + const entries = terminal + ? [] + : bundleGraphKeys(node).map((key) => [key, Reflect.get(node, key)] as const); + capture.links += entries.length + (children?.length ?? 0); + if (children === undefined) capture.overflow = true; + if (capture.rows.length >= 1024 || capture.links > 8192) capture.overflow = true; + if (!capture.overflow) + capture.rows.push({ + node, + terminal, + childReader, + children, + keys: entries.map(([key]) => key), + links: entries.map(([, value]) => + value !== null && typeof value === "object" ? value : undefined, + ), + isNode: Reflect.get(node, "isNode"), + beforeType: Reflect.get(node, "updateBeforeType"), + before: typeof Reflect.get(node, "updateBefore"), + version: Reflect.get(node, "version"), + }); + return entries.map(([, value]) => value); +} + +function samplesFramebuffer( + node: unknown, + seen: Set, + capture?: IBundleGraphCapture, +): boolean { + // Pixels and vertex data hold no node, and Object.values would copy every element of them. + if (node === null || typeof node !== "object" || ArrayBuffer.isView(node) || seen.has(node)) + return false; + seen.add(node); + const candidate = node as { + readonly isNode?: boolean; + readonly updateBefore?: unknown; + readonly updateBeforeType?: unknown; + }; + if ( + candidate.isNode === true && + typeof candidate.updateBefore === "function" && + candidate.updateBeforeType === PER_RENDER_UPDATE + ) { + if (capture !== undefined) captureBundleGraph(node, true, capture, undefined, []); + return true; + } + // getChildren owns Three's non-enumerable, dictionary and sparse-array child semantics. Its + // ordered identities are a live witness, including prototype edits with unchanged own keys. + const reader = bundleGraphChildReader(node); + const children = readBundleGraphChildren(node, reader); + const values = + capture === undefined + ? bundleGraphKeys(node).map((key) => Reflect.get(node, key)) + : captureBundleGraph(node, false, capture, reader, children); + // Malformed/oversized child output retains the live source path. The stored witness is capped; + // Three's eager getChildren implementation can still scan an authored array before yielding. + if (children === undefined) return true; + for (const value of [...values, ...children]) { + if (value === null || typeof value !== "object") continue; + if (samplesFramebuffer(value, seen, capture)) return true; + } + return false; +} + +/** + * Whether one mesh's draw can be recorded into a bundle and replayed from it. + * + * A bundle is an encoder, not a render pass: three fixes its render list and its draw order when it + * records it, and every replay runs the encoded commands with no pass of its own to ask anything of. + * So a mesh is bundle-safe only when nothing about its draw needs a live pass — the one question + * {@link samplesFramebuffer} and the checks below both ask, of the node graph and of the object. + * + * - **The framebuffer.** A viewport or screen-space refraction sample, transmission, a render target, + * a reflector: `samplesFramebuffer`, plus `transmission` itself, which three's own lighting model + * reaches through an `Fn` body no structural walk can see. + * - **Per-frame order.** A transparent mesh is sorted against its neighbours every frame; a record + * froze that order when it was made, so a bundled one draws in the order it happened to be in. + * - **Per-object hooks.** `onBeforeRender`/`onAfterRender` are the two places a game reads the camera + * and the frame for a draw, and a replay runs them once at record time and never again. The + * engine's own borrow — the prewarm counting its batch's first submitted draw — is bookkeeping + * about the draw rather than a claim on the object, so it does not refuse; `isEngineRenderHook` is + * what tells them apart, the same answer `projection-plan.ts` gives. + * - **Per-frame vertices.** A skinned or morphed mesh's positions are posed per frame from bones the + * record never reads, so its encoded vertices are its rest pose forever. + * + * A mesh that fails this stays on the per-object path — the path every chunk drew before bundles + * existed — so a refusal costs the draw it was already paying and nothing else. + */ +export function bundleSafe(mesh: Mesh): boolean { + if (ownHook(mesh, "onBeforeRender")) return false; + if (ownHook(mesh, "onAfterRender")) return false; + if ((mesh as Mesh & { readonly isSkinnedMesh?: boolean }).isSkinnedMesh === true) return false; + if (mesh.morphTargetInfluences !== undefined) return false; + return (Array.isArray(mesh.material) ? mesh.material : [mesh.material]).every((material) => + bundleSafeMaterial(material), + ); +} + +/** + * Whether this mesh carries a render hook of its own that is the game's, which a record freezes. + * + * `Object.hasOwn`, not the prototype comparison: the engine installs its own hook as an own property + * on the very batches a prewarm borrowed, and refusing those would keep every batch of a streamed + * world on the per-object path. + */ +export function ownHook(mesh: Mesh, name: "onAfterRender" | "onBeforeRender"): boolean { + return Object.hasOwn(mesh, name) && isEngineRenderHook(mesh[name]) === false; +} + +/** The material half of {@link bundleSafe}: nothing it draws may need a pass it was recorded in. */ +export function bundleSafeMaterial( + material: Material | undefined, + capture?: IBundleGraphCapture, +): boolean { + if (material === undefined) return true; + if (material.transparent === true) return false; + // Read structurally: a `MeshPhysicalMaterial` always has the number and nothing else does, and + // three's transmission sampler is not a node slot, so `transmission` is the only way to see it. + const transmission = Reflect.get(material, "transmission"); + if (typeof transmission === "number" && transmission > 0) return false; + return samplesFramebuffer(material, new Set(), capture) === false; +} diff --git a/packages/core/src/world-cell-batches.ts b/packages/core/src/world-cell-batches.ts new file mode 100644 index 0000000000..79a41a8e3d --- /dev/null +++ b/packages/core/src/world-cell-batches.ts @@ -0,0 +1,1057 @@ +import { + Box3, + type BufferGeometry, + InstancedBufferAttribute, + type InstancedMesh, + type Material, + Sphere, + Vector3, +} from "three"; +import type { InstancedBatch } from "./instanced-batch.js"; +import { currentWriteEpoch, pooledMesh } from "./render/mesh-pool.js"; + +/** + * Records a retired batch's instance buffer is shrunk to; see `SharedBatch.park`. + * + * A released batch holds no records, so what keeping it is buying is the uuid three built a node for, + * and the buffer is the whole of what that retention costs. At the batch's own ring capacity the + * walk's back-traffic costs more than the byte budget holds, so the budget evicts the very keys a + * walk is about to return to and the walk re-mints a mesh for each of them. Parked, a key costs + * `PARKED_SHARED_CAPACITY` records; `rebind` puts the capacity back before anything is written. + */ +const PARKED_SHARED_CAPACITY = 1; + +/** + * One asset part at one distance level, drawn for every resident cell by a single InstancedMesh. + * + * Each cell owns a block of the instance buffer, admitted as the count it needs and freed as one, + * so the draw object count is per asset part and level rather than per cell: a 25-cell ring of a + * 218-asset world was ~5,000 meshes and 20+ ms of CPU a frame in three's per-object render path. + * + * A shared batch packs its live instances at the front of the instance buffer, so `mesh.count` is + * the live total. It used to be `(the highest used segment + 1) * segmentSize`, which drew every + * zero-matrix slot of a partly filled segment and of every freed segment below the top one — tens of + * thousands of slots per pass — and each empty slot still ran the vertex shader over the whole mesh + * in the main pass and in every shadow pass. A block is now sized to its own cell's batch instead of + * to the asset's largest cell, and a freed or shortened block is compacted into the lowest hole, so + * the drawn range holds nothing but live records. The handles are opaque: `allocate` only hands one + * back to `write` and `clear`. + * + * A **main** batch is `clustered`, and that changes the packing: its blocks are laid out grouped by + * the world-grid square they sit in, the same square the caster clusters are keyed by, and + * {@link SharedBatch.cullFrom} narrows `mesh.count` to the squares the main camera's frustum covers. + * Three's whole-mesh frustum test cannot do this on its own — the bounds are the whole ring, so the + * test always passes and every instance behind or beside the camera is vertex-shaded. See + * {@link WorldCells.#cullMainPass} for which camera is allowed to decide. + */ +/** + * A `SharedBatch`'s mesh plus the one field it publishes for a shadow level's size gate: the largest + * instance scale any live record carries. Duck-typed rather than augmented into `three`, the same as + * `isInstancedMesh` — `render/virtual-shadow.ts` reads it back off the mesh. + */ +export interface ICasterScaleMesh extends InstancedMesh { + casterInstanceScale: number; + /** + * Whether the main pass will draw the records behind this caster half this frame; see + * {@link SharedBatch.setMainAdmitted}. Read by `VirtualShadowNode`'s probe beside the other two + * flags this class writes on a mesh. + */ + mainAdmitted?: boolean; +} + +/** One world-grid square's records in a clustered batch: how many of them there are. */ +type ISquareSizes = Map; + +/** + * The one buffer a clustered regroup permutes through. + * + * A regroup is a permutation of `[0, drawn)`, and there is no in-place order for one: putting the + * visible squares at the front necessarily writes over squares that have not been copied out yet. A + * snapshot is the honest way to do it — read every block into here, then write the new layout back — + * and one buffer is enough for the whole world, because a regroup never nests and the largest + * main batch is the only bound on it. Module-level like the upload epoch, because there is one world + * drawing at a time. + */ +let regroupScratch = new Float32Array(0); + +/** A scratch with room for `floats`, kept between regroups so a walk allocates it once. */ +function scratchFor(floats: number): Float32Array { + if (regroupScratch.length < floats) regroupScratch = new Float32Array(floats * 2); + return regroupScratch; +} + +/** Records a square's blocks hold, which is its share of the batch. */ +function sizeOf(square: { blocks: Array<[number, number, number, string]> }): number { + let size = 0; + for (const block of square.blocks) size += block[2]; + return size; +} + +export function sameSquares(next: Set, was: Set | undefined): boolean { + if (was === undefined || was.size !== next.size) return false; + for (const cluster of next) if (!was.has(cluster)) return false; + return true; +} + +export class SharedBatch { + mesh: ICasterScaleMesh; + /** The pass this batch draws in; see `#dressMesh`. Only `main` is clustered. */ + readonly role: "cluster" | "key" | "main" | "wide"; + /** + * Set by `WorldCells` from the asset's own bounds: a wide caster of ground cover goes on the + * finest level's small-caster layer rather than the wide one. Written after construction because + * `#segmentIn` re-dresses a grown mesh with nothing but the batch to hand, and a flag the batch + * carries is the one thing that survives that. + */ + smallCaster = false; + /** + * Set when the GPU scene owns this batch's draw, with the key it was minted under. See + * `WorldCells#dressGpu`. + * + * It is a field rather than a name test because a dressed batch's `mesh.instanceMatrix` is the + * scene's shared compaction buffer, and everything below writes records into that attribute: a + * dressed batch keeps its bookkeeping and skips every byte of it, which is the whole of what the + * `gpu` guards in `write`, `clear`, `#settle`, `#touched` and `#publish` are. + */ + gpu: + | { + readonly asset: string; + readonly level: number; + readonly part: number; + readonly key: string; + } + | undefined; + /** + * The instance count a GPU-dressed mesh is submitted with: its key's region capacity, written by + * `WorldCells#dressGpu`. + * + * A dressed mesh's own records are not what it draws — the dispatch appends survivors into the + * scene's shared buffer and the indirect record decides how many — so `#drawn` is not its count. + * Three skips the indirect draw entirely before the GPU ever reads the record when an + * `InstancedMesh.count` is zero (`RenderObject.getDrawParameters`), so the region's capacity is the + * submission bound: the record's own count draws what the GPU selected, and this only admits the + * submission. `0` until the mesh is dressed. + */ + gpuCount = 0; + /** + * How many of this batch's live records the GPU scene holds a placement for, carried beside the + * buffer and the segments so admission is one comparison per caster half a frame. Incremented by + * `WorldCells#placeSources` and decremented by `#clearSegment`, where the asset's own `#gpuResident` + * count already moves; see {@link WorldCells.#publishCasterAdmission}. + */ + gpuPlaced = 0; + /** + * Whether this batch's mesh is parented under one of the world's main record shards. Set by + * `WorldCells#bundleIn` and cleared by `#bundleOut`. + * + * It is a field rather than `mesh.parent` because a bundled mesh's `visible` is never written + * again: three fixes a bundle's render list when it records it, and the replay never re-reads + * visibility — so the two places that hide an empty batch have to know, and a mesh that has just + * been detached from the group by a retire has still been in the last recorded one. + */ + bundled = false; + /** + * Last checked eligibility of this mesh. CPU draws revalidate bounded material graph snapshots + * and live object hooks each frame, so deep mutations can return the mesh to the per-object path. + */ + bundlable = false; + bundleMaterialRevision = -1; + /** + * The instance count the world's last record of this mesh was cut at. A record freezes the draw's + * instance count, so a CPU-path batch's moving window is one re-record when this moves; a dressed + * batch submits its key's region capacity, which only the dress changes and which re-records there. + */ + bundledCount = -1; + /** + * The same descriptor, recorded whether or not the GPU scene is on, which is the whole of what + * lets a mesh minted before the scene came up be dressed after it: `gpu` says the mesh draws from + * the scene's buffers, `main` says which key it would draw. A prewarm that mints a whole ring on a + * loading screen mints it with the scene off, and the first frame with a renderer turns it on. + */ + main: + | { + readonly asset: string; + readonly level: number; + readonly part: number; + readonly key: string; + } + | undefined; + readonly #clustered: boolean; + readonly #segmentSize: number; + /** Block handle -> the block's first instance record. */ + readonly #start = new Map(); + /** Block handle -> live records in it; the reserved count until `write` reports the real one. */ + readonly #size = new Map(); + /** Block handle -> the world-grid square it sits in; only a clustered batch records it. */ + readonly #blockCluster = new Map(); + /** World-grid square -> its records in the buffer, so the window can be summed. See `#regroup`. */ + readonly #squareSizes: ISquareSizes = new Map(); + /** The squares the last cull kept, how many records of the batch they are, and whether that is current. */ + #visible: Set | undefined; + /** The `visibleEpoch` this batch last laid itself out at; see {@link SharedBatch.cullFrom}. */ + #packedEpoch = -1; + /** Reused across culls, so a repack allocates no set. */ + #scratch: Set = new Set(); + #window = 0; + #narrowed = false; + /** + * A prewarmed batch whose first draw is still owed, so it is visible at count 0 until that draw + * counts. The draw is the whole of the prewarm; see {@link #publish}. + */ + #awaitingPrewarm = false; + /** + * Whether the main pass will draw this batch's records this frame; a main batch is always + * admitted, so this only ever says something about a caster half. See + * {@link WorldCells.#publishCasterAdmission}. + */ + #mainAdmitted = true; + /** Free record ranges `[from, to)`, sorted, disjoint and never adjacent. */ + #free: Array<[number, number]> = []; + #handles = 0; + /** One past the last live record: the range the whole buffer holds. A clustered batch draws `#window`. */ + #drawn = 0; + /** Block handle -> the AABB of the records it holds, and the union of those is the mesh's bounds. */ + readonly #boxes = new Map(); + /** Block handle -> the largest instance scale in it; the union is published on the mesh. */ + readonly #scales = new Map(); + /** Carried out of the last `#boxOf` pass, which already reads every record's basis. */ + #maxScale = 0; + #localGeometry: BufferGeometry | undefined; + readonly #localCenter = new Vector3(); + readonly #localHalf = new Vector3(); + readonly #union = new Box3(); + readonly #point = new Vector3(); + /** This batch's own pending upload span, and the `update` it was opened in. See `#touched`. */ + #pendingLow = 0; + #pendingHigh = 0; + #pendingEpoch = -1; + + constructor( + geometry: BufferGeometry, + material: Material, + segmentSize: number, + segments: number, + name: string, + bounds: Box3, + role: "cluster" | "key" | "main" | "wide", + ) { + this.role = role; + this.#clustered = role === "main"; + this.#segmentSize = Math.max(1, segmentSize); + this.#bounds = bounds; + this.mesh = SharedBatch.#meshFor( + geometry, + material, + this.#segmentSize * segments, + name, + bounds, + ); + this.#free = [[0, this.mesh.instanceMatrix.count]]; + this.#ceiling = this.mesh.instanceMatrix.count; + this.#rebound(); + } + + /** This batch's own record ceiling; see {@link liveCeiling}. */ + #ceiling = 0; + + /** The package extent, the conservative fallback when a part's own box cannot be read. */ + readonly #bounds: Box3; + + /** + * Takes a pooled mesh with room for `capacity`, so a shared batch that outgrows its buffer keeps + * the uuid — and the node three built for it — instead of getting a new one. Every field a pooled + * mesh carries stale is written here, and the two shadow flags are set false because `grow` copies + * them off the mesh it replaces and the world sets them per level. + */ + static #meshFor( + geometry: BufferGeometry, + material: Material, + capacity: number, + name: string, + bounds: Box3, + ): ICasterScaleMesh { + const mesh = pooledMesh(geometry, material, capacity) as ICasterScaleMesh; + mesh.name = name; + mesh.boundingBox = bounds.clone(); + mesh.boundingSphere = bounds.getBoundingSphere(mesh.boundingSphere ?? new Sphere()); + mesh.count = 0; + mesh.visible = false; + // Cullable, because the bounds are now this batch's own. Every camera three draws this mesh + // with — the main one and each virtual-shadow level's — asks first, which is what stops a 48 m + // shadow level from submitting every shared batch in the world. + mesh.frustumCulled = true; + mesh.castShadow = false; + mesh.receiveShadow = false; + (mesh.instanceMatrix.array as Float32Array).fill(0); + mesh.instanceMatrix.needsUpdate = true; + // A pooled mesh arrives carrying the last user's scale; nothing lives here yet. + mesh.casterInstanceScale = 0; + // Nor the last user's bundle membership: a mesh is only bundled while `WorldCells#bundleIn` says + // so, and the shadow node reads this marker to keep its texel gate off a bundled mesh. + mesh.userData.tnBundled = false; + // Nor the last caster's admission: a main batch is always admitted, and this mesh is not one. + mesh.mainAdmitted = true; + return mesh; + } + + /** Point an empty batch at the parts a reload brought; see {@link rebind}. */ + rebind(geometry: BufferGeometry, material: Material): this { + this.mesh.geometry = geometry; + this.mesh.material = material; + this.#localGeometry = undefined; + // A new surface is the one thing a rebind can bring that a mesh swap cannot, so what can be + // recorded has to be asked again: the answer cached for the old material is not this one's. + this.bundlable = false; + if (this.#parked > 1) this.#resize(this.#parked); + this.#rebound(); + return this; + } + + /** The capacity this batch had when it was parked; see {@link park}. */ + #parked = 0; + + /** + * Give up the instance buffer of a batch that holds no records, keeping the mesh. + * + * This is what makes a retained set affordable. Unparked, a walk's back-traffic costs a ring-sized + * buffer per key, so `RETIRED_SHARED_BYTES` could hold only the last few dozen keys of a world with + * hundreds of them, `#sharedFor` then found nothing to hand back, and the walk minted a second + * mesh for every key it returned to. Parked, the same keys cost `PARKED_SHARED_CAPACITY` records + * each and `rebind` gives the buffer back before the first record is written into it. + */ + park(): this { + // The batch's own ceiling, never its mesh's `count`: a dressed batch's `instanceMatrix` is the + // GPU scene's shared compaction buffer, so its count is the whole world's, and parking by it + // turned one key's capacity into the whole buffer — `rebind` then resized every later batch + // against the world and the shared buffer regrew until the device refused it. + this.#parked = this.#ceiling; + // `gpu` too, not only a capacity over one: a dressed batch's `instanceMatrix` is the GPU scene's + // shared compaction buffer whatever its logical ceiling is, so a batch parked at a ceiling of one + // that skipped this resized nothing and left the retired mesh holding the whole world's records. + if (this.gpu !== undefined || this.#parked > PARKED_SHARED_CAPACITY) + this.#resize(PARKED_SHARED_CAPACITY); + return this; + } + + /** + * Swap in an instance buffer of `capacity` records, and with it the free range and the drawn count + * the buffer is the record of. The mesh is off the world and holds no records when this runs, so + * `#start`, `#size` and `#boxes` are already empty and the one free range is the whole buffer. + */ + #resize(capacity: number): void { + const mesh = this.mesh; + mesh.instanceMatrix = new InstancedBufferAttribute(new Float32Array(capacity * 16), 16); + this.#ceiling = capacity; + this.#publish(0); + this.#free = [[0, capacity]]; + this.#drawn = 0; + } + + /** + * The mesh's own extent, the union of the blocks' boxes, written on every add and remove. A union + * over at most one box per resident cell is a few dozen comparisons, against a per-draw saving of + * every batch the camera does not overlap. `#compact` moves records between slots but never between + * blocks, so a block's box survives a compaction untouched. + */ + #rebound(): void { + const box = this.#union; + let live = false; + for (const one of this.#boxes.values()) { + if (live === false) { + box.copy(one); + live = true; + } else box.union(one); + } + if (live === false) box.copy(this.#bounds); + (this.mesh.boundingBox as Box3).copy(box); + this.mesh.boundingSphere = this.mesh.boundingSphere ?? new Sphere(); + box.getBoundingSphere(this.mesh.boundingSphere); + // The size a per-instance shadow gate reads, published next to the mesh rather than asked for + // again per level: the cluster's own sphere is ~24 m of grid square, so gating on it dropped + // nothing, while a fern is 0.3 m and every fern cluster clears every coarse level. + this.mesh.casterInstanceScale = live === false ? 0 : Math.max(...this.#scales.values()); + } + + /** + * The AABB of records `[from, from + count)` of the instance buffer, read from the bytes just + * written rather than from the batch that wrote them, so it is the same numbers the GPU gets. The + * half-extents are `|basis| * half`, which is the box a rotated or stretched instance of the part + * actually covers — a translate-only box would let a scaled trunk through the shadow cull. + */ + #boxOf(from: number, count: number): Box3 { + const array = this.mesh.instanceMatrix.array as Float32Array; + const geometry = this.mesh.geometry; + if (this.#localGeometry !== geometry) { + this.#localGeometry = geometry; + if (geometry?.boundingBox === null || geometry?.boundingBox === undefined) + geometry?.computeBoundingBox?.(); + const local = geometry?.boundingBox; + // No readable local box means no readable instances either; the package extent is the answer + // that cannot be wrong, and it is what these meshes carried before the blocks were sized. + if (local === undefined || local === null || local.isEmpty()) return this.#bounds; + this.#localCenter.copy(local.getCenter(this.#point)); + this.#localHalf.copy(local.getSize(this.#point)).multiplyScalar(0.5); + } + const cx = this.#localCenter.x; + const cy = this.#localCenter.y; + const cz = this.#localCenter.z; + const hx = this.#localHalf.x; + const hy = this.#localHalf.y; + const hz = this.#localHalf.z; + const out = new Box3(); + let minX = Number.POSITIVE_INFINITY; + let minY = Number.POSITIVE_INFINITY; + let minZ = Number.POSITIVE_INFINITY; + let maxX = Number.NEGATIVE_INFINITY; + let maxY = Number.NEGATIVE_INFINITY; + let maxZ = Number.NEGATIVE_INFINITY; + let scale = 0; + for (let index = from; index < from + count; index += 1) { + const at = index * 16; + const m0 = array[at] as number; + const m1 = array[at + 1] as number; + const m2 = array[at + 2] as number; + const m4 = array[at + 4] as number; + const m5 = array[at + 5] as number; + const m6 = array[at + 6] as number; + const m8 = array[at + 8] as number; + const m9 = array[at + 9] as number; + const m10 = array[at + 10] as number; + const px = m0 * cx + m4 * cy + m8 * cz + (array[at + 12] as number); + const py = m1 * cx + m5 * cy + m9 * cz + (array[at + 13] as number); + const pz = m2 * cx + m6 * cy + m10 * cz + (array[at + 14] as number); + const ex = Math.abs(m0) * hx + Math.abs(m4) * hy + Math.abs(m8) * hz; + const ey = Math.abs(m1) * hx + Math.abs(m5) * hy + Math.abs(m9) * hz; + const ez = Math.abs(m2) * hx + Math.abs(m6) * hy + Math.abs(m10) * hz; + if (px - ex < minX) minX = px - ex; + if (py - ey < minY) minY = py - ey; + if (pz - ez < minZ) minZ = pz - ez; + if (px + ex > maxX) maxX = px + ex; + if (py + ey > maxY) maxY = py + ey; + if (pz + ez > maxZ) maxZ = pz + ez; + // The largest column of the instance basis: the scale a fern or a trunk is drawn at. + const basis = Math.max( + Math.hypot(m0, m1, m2), + Math.hypot(m4, m5, m6), + Math.hypot(m8, m9, m10), + ); + if (basis > scale) scale = basis; + } + this.#maxScale = scale; + out.min.set(minX, minY, minZ); + out.max.set(maxX, maxY, maxZ); + return out; + } + + /** A handle for `count` free records, or `undefined` when the buffer has none and must grow. */ + allocate(count: number, cluster?: string): number | undefined { + const at = this.#take(count); + if (at < 0) return undefined; + const handle = this.#handles; + this.#handles += 1; + this.#start.set(handle, at); + this.#size.set(handle, count); + // Only a clustered batch groups, so only it pays for the lookup. The square is the caller's + // cell's, and a cell's placements never move, so a record is only ever grouped when the cell + // holding it is admitted or evicted. + if (this.#clustered && cluster !== undefined) this.#blockCluster.set(handle, cluster); + return handle; + } + + /** + * Doubles the buffer in a new mesh (same data), returning the one it replaces for the pool. + * + * The replacement comes from the pool, and what it replaces is offered back to it, so the pair + * costs one new uuid between them instead of one each. This used to push the old mesh onto an + * unbounded `retired` array that nothing ever read and nothing ever freed. + */ + grow(): InstancedMesh | undefined { + const old = this.mesh; + if (this.gpu !== undefined) { + // A dressed batch's records are the GPU scene's, held in a buffer this batch must not copy, + // zero or regroup: its own share is the logical ceiling the next `#dressGpu` sizes the scene's + // region from, so growth is that number and nothing else. The live mesh is kept and nothing is + // returned: `#segmentIn` dresses, attaches and parks only a real replacement, and the next + // `#dressGpu` grows the region from this ceiling before the draw. + const was = this.#ceiling; + this.#ceiling = was * 2; + this.#hole(was, this.#ceiling); + return undefined; + } + const mesh = SharedBatch.#meshFor( + old.geometry, + old.material as Material, + old.instanceMatrix.count * 2, + old.name, + this.#bounds, + ); + (mesh.instanceMatrix.array as Float32Array).set(old.instanceMatrix.array as Float32Array); + mesh.castShadow = old.castShadow; + mesh.receiveShadow = old.receiveShadow; + this.mesh = mesh; + this.#ceiling = mesh.instanceMatrix.count; + this.#settle(); + // The whole drawn range, because this is a different buffer. `#meshFor` left the new + // attribute's range list empty, and an empty list is how three is told "upload everything" — so + // before the coalescing below, this was the whole buffer by default and the records copied out + // of the old array reached the GPU. A narrow range here would leave the new buffer's first + // records as the zeros it was created with. + this.#touched(0, this.#drawn); + this.#rebound(); + return old; + } + + /** + * Swap in the object this batch draws with, and hand back the one it replaces. + * + * Every reference the batch holds is `this.mesh` read at the moment it is needed, so the swap is + * the field. What does not follow the field is what the batch derives rather than stores — the + * bounds, the instance scale, the count and the visibility published on the object, and the two + * containers a fresh object starts out without them in. Those are republished here from the + * batch's own records, so a replacement draws exactly what the object it replaced drew. + */ + replaceMesh(next: ICasterScaleMesh): ICasterScaleMesh { + const old = this.mesh; + this.mesh = next; + this.mesh.boundingBox = this.mesh.boundingBox ?? new Box3(); + this.mesh.boundingSphere = this.mesh.boundingSphere ?? new Sphere(); + (next as { casterPrewarmOwed?: boolean }).casterPrewarmOwed = this.#awaitingPrewarm; + next.mainAdmitted = this.#mainAdmitted; + this.#rebound(); + this.#publish(this.#drawn); + return old; + } + + write(segment: number, batch: InstancedBatch): void { + const start = this.#start.get(segment); + const reserved = this.#size.get(segment); + if (start === undefined || reserved === undefined) return; + if (this.gpu !== undefined) { + // The records themselves are the GPU scene's, written by its dispatch from the source buffer; + // what the coarse per-cell gate and the counters need is the per-square count and the total. + const cluster = this.#blockCluster.get(segment) ?? ""; + this.#size.set(segment, batch.count); + this.#squareSizes.set(cluster, (this.#squareSizes.get(cluster) ?? 0) + batch.count); + this.#drawn += batch.count; + this.#publish(this.#drawn); + return; + } + const array = this.mesh.instanceMatrix.array as Float32Array; + const written = batch.writeMatrices(array, start); + array.fill(0, (start + written) * 16, (start + reserved) * 16); + if (written < reserved) this.#hole(start + written, start + reserved); + this.#size.set(segment, written); + // The block's extent, read from the records where they are now, because `#compact` is about to + // move them. The box is keyed by handle, so the move cannot falsify it. + if (written > 0) { + this.#boxes.set(segment, this.#boxOf(start, written)); + this.#scales.set(segment, this.#maxScale); + } else { + this.#boxes.delete(segment); + this.#scales.delete(segment); + } + this.#settle(); + this.#touched(start, written); + this.#rebound(); + } + + clear(segment: number): void { + const start = this.#start.get(segment); + if (start === undefined) return; + const size = this.#size.get(segment) ?? 0; + if (this.gpu !== undefined) { + // The mirrored half of `write`: the coarse gate and the live total follow the records that left, + // and no byte of the shared buffer is touched, because none of them were ever in it. + const cluster = this.#blockCluster.get(segment) ?? ""; + this.#squareSizes.set(cluster, Math.max(0, (this.#squareSizes.get(cluster) ?? 0) - size)); + this.#drawn -= size; + } + this.#start.delete(segment); + this.#size.delete(segment); + this.#blockCluster.delete(segment); + if (this.gpu !== undefined) { + // The block's range goes back to the free list, exactly as the CPU path's does below. Without + // this the GPU path's free list only ever shrank: a walk that streamed cells in and out + // exhausted it, `allocate` failed, `grow` doubled the batch's ceiling, and the scene's region + // — and with it the shared buffer — regrew for records that had already left. + this.#hole(start, start + size); + this.#publish(this.#drawn); + return; + } + (this.mesh.instanceMatrix.array as Float32Array).fill(0, start * 16, (start + size) * 16); + this.#hole(start, start + size); + this.#settle(); + // The drawn range, not nothing. `#compact` closed the hole by moving the topmost block down + // into it, so the bytes below `#drawn` changed and an empty range list is read by three as + // "upload the whole buffer" — a ring-sized write to zero a block that is no longer drawn. + this.#touched(0, this.#drawn); + this.#boxes.delete(segment); + this.#scales.delete(segment); + this.#rebound(); + } + + /** The first free range with room for `count` records, taken out of the list; -1 when there is none. */ + #take(count: number): number { + for (let index = 0; index < this.#free.length; index += 1) { + const range = this.#free[index] as [number, number]; + const size = range[1] - range[0]; + if (size < count) continue; + if (size === count) this.#free.splice(index, 1); + else this.#free[index] = [range[0] + count, range[1]]; + return range[0]; + } + return -1; + } + + /** Records `[from, to)` back as free, merged with every range it touches. */ + #hole(from: number, to: number): void { + if (to <= from) return; + const free = this.#free; + free.push([from, to]); + free.sort((a, b) => a[0] - b[0]); + const merged: Array<[number, number]> = []; + for (const range of free) { + const last = merged[merged.length - 1]; + if (last !== undefined && range[0] <= last[1]) { + if (range[1] > last[1]) last[1] = range[1]; + continue; + } + merged.push(range); + } + this.#free = merged; + } + + /** + * Closes every hole below the drawn range, so `mesh.count` is the live total: the topmost block + * moves into the lowest hole, or, when it does not fit, everything above the hole shifts down over + * it. Whatever sits past the topmost block is free again, so one cell's room serves the next cell + * instead of a `grow`, and the free list is left as the single tail range `[drawn, capacity)`. + */ + #compact(): void { + const array = this.mesh.instanceMatrix.array as Float32Array; + for (;;) { + let drawn = 0; + let top = -1; + let topFrom = 0; + for (const [handle, start] of this.#start) { + const end = start + (this.#size.get(handle) ?? 0); + if (end <= drawn) continue; + drawn = end; + top = handle; + topFrom = start; + } + this.#drawn = drawn; + const lowest = this.#free[0]; + if (top < 0 || lowest === undefined || lowest[0] >= drawn) break; + const [holeStart, holeEnd] = lowest; + const size = this.#size.get(top) ?? 0; + if (size <= holeEnd - holeStart) { + array.copyWithin(holeStart * 16, topFrom * 16, drawn * 16); + this.#start.set(top, holeStart); + if (holeEnd - holeStart > size) this.#free[0] = [holeStart + size, holeEnd]; + else this.#free.shift(); + this.#hole(topFrom, drawn); + this.#drawn = holeStart + size; + this.#touched(holeStart, size); + } else { + const shift = holeEnd - holeStart; + array.copyWithin(holeStart * 16, holeEnd * 16, drawn * 16); + this.#free.shift(); + for (const [handle, start] of this.#start) { + if (start >= holeEnd) this.#start.set(handle, start - shift); + } + for (const range of this.#free) { + if (range[0] >= holeEnd) { + range[0] -= shift; + range[1] -= shift; + } + } + this.#drawn = drawn - shift; + this.#touched(holeStart, this.#drawn - holeStart); + } + } + const capacity = this.mesh.instanceMatrix.count; + const free: Array<[number, number]> = []; + for (const [from, to] of this.#free) { + if (from < this.#drawn) free.push([from, to > this.#drawn ? this.#drawn : to]); + } + if (this.#drawn < capacity) free.push([this.#drawn, capacity]); + this.#free = free; + } + + /** + * Close the holes a write, a clear or a grow left, the way this batch's role needs. + * + * A caster batch compacts: one square holds the key's records, so the lowest hole is where the + * topmost block belongs. A main batch regroups instead, because compaction would carry a block + * away from the square it belongs to and the frustum test reads the squares. + */ + #settle(): void { + // A dressed batch's layout is the dispatch's: its `instanceMatrix` is the scene's shared buffer, + // so a compact or regroup here would read that buffer's count as this batch's capacity and + // shuffle the whole world's records. `write` and `clear` bypass this already; `grow` does too. + if (this.gpu !== undefined) return; + if (this.#clustered) { + this.#regroup(); + // Records moved, so `mesh.count` is the live total again and the window has to be re-narrowed + // by the next draw — with no copy, because the regroup just put the squares in place. + this.#narrowed = false; + } else this.#compact(); + } + + /** + * Lay every live block out again, grouped by its world-grid square, `visible`'s squares first. + * + * A regroup is a permutation of `[0, drawn)` and there is no in-place order for one: writing the + * visible squares to the front necessarily lands on squares whose records have not been read out + * yet, in either direction. So every block is copied into {@link regroupScratch} and the new order + * is copied back — two passes over the drawn range, and only when a square entered or left the + * window, which on a settled camera is not once a frame. + * + * Squares keep their relative order within each half, so a residency change regroups in place and + * a cull that found the same set writes nothing. What each square holds is recorded here, because + * that is the only per-key data the window needs: the squares' world volumes belong to the grid, + * not to a key, and are derived once a frame for all of them. See {@link WorldCells.#cullMainPass}. + * + * Only the records that actually moved are handed to the upload, and on a walk that is usually the + * tail or nothing at all: the squares that stayed in the half they were in keep their offsets + * exactly, so a square crossing the boundary between the two halves moves the records from there + * to the boundary and leaves `[0, boundary)` byte-identical. Uploading `[0, drawn)` instead — which + * is what this did — re-sent a whole key's records to the GPU on every frame the window moved, + * which on a walking camera is most frames, and on the largest key in a 2 km walk measured + * **37 draws over 7 ms each**. An identity reorder now costs no bytes at all, and the rest costs + * the span that moved; `#touched` still widens a span over half the drawn range, so the ranges + * stay few and large rather than many and small. + */ + #regroup(visible = this.#visible): void { + const array = this.mesh.instanceMatrix.array as Float32Array; + const squares = this.#squares(); + let total = 0; + for (const square of squares) total += sizeOf(square); + const scratch = scratchFor(total * 16); + // The visible squares first. `filter` is stable, so both halves keep the order the buffer had + // them in and a residency change lays out exactly what the last one did. + const front = squares.filter((one) => visible?.has(one.cluster) ?? true); + const back = squares.filter((one) => !(visible?.has(one.cluster) ?? true)); + let written = 0; + // Where the layout departed from the one the GPU holds: the first record written somewhere other + // than where it was read from, and the last one after it. `-1` until something moves, so an + // identity reorder touches nothing. + let movedFrom = -1; + let movedTo = 0; + this.#squareSizes.clear(); + for (const square of [...front, ...back]) { + const union = new Box3(); + for (const [handle, start, count] of square.blocks) { + scratch.set(array.subarray(start * 16, (start + count) * 16), written * 16); + // Read before it is overwritten: a block written back to its own offset is a record the GPU + // already holds, and is the whole difference between a repack that costs nothing and one + // that re-uploads the key. + if (this.#start.get(handle) !== written) { + if (movedFrom < 0) movedFrom = written; + movedTo = written + count; + } + this.#start.set(handle, written); + const box = this.#boxes.get(handle); + // A fresh `Box3` is already empty, so the first block's box is the square's and every block + // after it unions into it. + if (box !== undefined) union.union(box); + written += count; + } + this.#squareSizes.set(square.cluster, sizeOf(square)); + } + array.set(scratch.subarray(0, written * 16)); + this.#drawn = written; + const capacity = this.mesh.instanceMatrix.count; + this.#free = written < capacity ? [[written, capacity]] : []; + this.#window = this.#windowOf(visible); + // After `#drawn`, because `#touched` measures the span it may widen against the drawn range, and + // before the caller's own `#touched`, which the epoch unions with it rather than replacing it. + if (movedFrom >= 0) this.#touched(movedFrom, movedTo - movedFrom); + } + + /** + * The live blocks, in buffer order and gathered into the squares they belong to. + * + * Buffer order is what "the squares keep their relative order" means, and it is not the order of + * `#start`, which is keyed by handle rather than by position. A square's blocks are only adjacent + * because the last regroup made them so — a cell admitted after that lands anywhere in the free + * tail — so the squares are gathered here and written out by {@link #regroup}, and it is these + * two orders that a residency change has to preserve. + */ + #squares(): Array<{ cluster: string; blocks: Array<[number, number, number, string]> }> { + const blocks: Array<[number, number, number, string]> = []; + for (const [handle, start] of this.#start) { + const size = this.#size.get(handle) ?? 0; + if (size > 0) blocks.push([handle, start, size, this.#blockCluster.get(handle) ?? ""]); + } + blocks.sort((a, b) => a[1] - b[1]); + const squares: Array<{ cluster: string; blocks: typeof blocks }> = []; + const squareOf = new Map(); + for (const block of blocks) { + const cluster = block[3] as string; + const index = squareOf.get(cluster); + if (index === undefined) { + squareOf.set(cluster, squares.length); + squares.push({ cluster, blocks: [block] }); + continue; + } + (squares[index] as { blocks: typeof blocks }).blocks.push(block); + } + return squares; + } + + /** Records of the squares in `visible`, the count a clustered batch's `mesh.count` draws. */ + #windowOf(visible: Set | undefined): number { + if (visible === undefined) return this.#drawn; + let window = 0; + for (const [cluster, size] of this.#squareSizes) if (visible.has(cluster)) window += size; + return window; + } + + /** + * Narrow the draw window to the visibility cells `visible` names, rewriting the instance buffer + * when this batch's own share of that set changed. `settled` when nothing had to be looked at, + * `narrowed` when the count was re-widened, `repacked` when records were copied. + * + * `visible` and `epoch` are the frame's answer, derived once for the whole main pass by + * {@link WorldCells.#cullMainPass}: one frustum, one set of visible visibility cells, and an epoch + * that only moves when that set did. A batch whose records have not moved since it last packed at + * that epoch answers `false` from two integer comparisons — no frustum, no set, no copy — which is + * the whole of a settled frame across the ~230 meshes of a 2 km walk. Before that it was every + * batch rebuilding a frustum from the same camera, allocating a set and re-testing its own per-key + * volumes: ~4 ms a frame of `compute` for an answer that had not changed. + * + * Three's whole-mesh test cannot do this at all: the mesh's bounds are every resident cell's, so on + * a 640 m ring the test always passes and every instance behind or beside the camera is + * vertex-shaded — which on a tree-heavy 2 km map is most of a 65 M-triangle main pass, and the + * whole cost of it is vertex work that half resolution does not touch. + * + * The cells are the main cull's own and the volumes are per placement (see + * {@link WorldCells.#cullMainPass}), which is what this narrowness is spent on: a cell's XZ is + * one world cell rather than a 128 m caster square, and its Y is the height its own placements + * reach rather than the whole map's terrain range. + * + * A draw that finds the same cells it found last time touches nothing at all: no copy, no + * `needsUpdate`, and therefore no attribute version — which is what a settled static draw watches + * to know it still has to scan. + */ + cullFrom(visible: ReadonlySet, epoch: number): "settled" | "narrowed" | "repacked" { + if (!this.#clustered) return "settled"; + // A batch holding no square draws nothing whatever the camera found, so it is published empty + // and takes no set and no scan — a prewarmed batch sits in exactly this state. + if (this.#squareSizes.size === 0) { + this.#publish(0); + return "settled"; + } + // Settled: the frame's set is the one this window was laid out at and no record joined or left + // since. O(1), and no allocation of any kind. + if (this.#narrowed && this.#packedEpoch === epoch) return "settled"; + const next = this.#scratch; + next.clear(); + for (const square of this.#squareSizes.keys()) if (visible.has(square)) next.add(square); + // The same squares as the buffer holds: a residency change or a square elsewhere entering and + // leaving the view. The layout is already right, so the window only has to be re-narrowed — a + // residency change left `mesh.count` at the live total, and `#settle`'s regroup put the squares + // in the window's order with no copy. + if (sameSquares(next, this.#visible)) { + this.#publish(this.#window); + this.#narrowed = true; + this.#packedEpoch = epoch; + return "narrowed"; + } + this.#regroup(next); + // The reorder hands its own upload, over the records that actually moved — a window that moved + // a square across the half boundary and left every record where it was costs nothing here, which + // is the walking camera's common case. See `#regroup`. + // The layout now belongs to the other set, so the two trade places and the next cull reuses the + // one this batch is no longer holding. + const was = this.#visible; + this.#visible = next; + this.#scratch = was ?? new Set(); + this.#scratch.clear(); + // `#publish` is what hides a window the camera narrowed to nothing, and what shows it again when + // the camera turns back. + this.#publish(this.#window); + this.#narrowed = true; + this.#packedEpoch = epoch; + return "repacked"; + } + + /** Live records this batch holds, whether the camera is drawing them or not. */ + get live(): number { + return this.#drawn; + } + + /** + * The record ceiling of this batch's *own* buffer, which is what a key's GPU region is sized from: + * the same ring-sized ceiling the CPU path stops growing at. Held rather than read, because a + * dressed batch's `mesh.instanceMatrix` is the GPU scene's shared buffer and its count is that + * buffer's, not this key's. + */ + get liveCeiling(): number { + return this.#ceiling; + } + + /** Records `mesh.count` draws: every live one, until a cull narrows the window. */ + get drawn(): number { + return this.#clustered ? this.#window : this.#drawn; + } + + /** + * One coalesced upload per mesh, not one per moved, written or zeroed record range. Three's WebGPU + * backend turns each entry of `updateRanges` into its own `GPUQueue.writeBuffer`, so the + * per-range bookkeeping here was the walk's largest CPU cost: thousands of sub-1 KB vertex writes + * per frame, over a second of a short walk spent on the queue. The dirty span is accumulated here + * across every write, compact and clear of one update, so a frame that touches a mesh from + * several directions still issues one writeBuffer. A span over half the drawn range becomes the + * whole drawn range, which is never more bytes than the two halves it replaced. + */ + #touched(from: number, count: number): void { + // A dressed batch's records were never in this attribute, so there is nothing to upload and + // nothing to put back in charge of culling: the dispatch decided what it drew. + if (this.gpu !== undefined) return; + const matrix = this.mesh.instanceMatrix; + // A record is 16 array elements, and that is the unit `updateRanges` is in: three reads them + // as element offsets into `array`, not as bytes. + const drawn = this.#drawn * 16; + const low0 = from * 16; + const high0 = (from + count) * 16; + // The pending span is ours, and it lasts one update. Three's WebGPU path uploads through the + // interleaved buffer it derives from `instanceMatrix.array` and only that buffer's ranges are + // cleared after the write, so the ranges on `instanceMatrix` are never consumed: they were + // unioned in here forever, and after one wide write every later write re-uploaded the whole + // drawn range. The epoch moves once per `update()`, and the render at the end of an update is + // what consumes the span, so a fresh epoch is a fresh span. + let low = low0; + let high = high0; + const epoch = currentWriteEpoch(); + if (this.#pendingEpoch === epoch) { + if (this.#pendingLow < low) low = this.#pendingLow; + if (this.#pendingHigh > high) high = this.#pendingHigh; + } + if (low > 0 && (high - low) * 2 > drawn) { + low = 0; + high = drawn; + } + this.#pendingLow = low; + this.#pendingHigh = high; + this.#pendingEpoch = epoch; + matrix.clearUpdateRanges(); + if (high > low) matrix.addUpdateRange(low, high - low); + matrix.needsUpdate = true; + this.#publish(this.#drawn); + // Records are real, so the bounds are the ones `#rebound` just wrote and the mesh goes back to + // being culled. + this.mesh.frustumCulled = true; + } + + /** + * The one place `mesh.count` is written, so a batch that would draw nothing is not submitted. + * + * In three r185's WebGPU path an `InstancedMesh` with `count === 0` still runs the whole per-draw + * JS chain — nodes, bindings, pipeline, attributes — and only the backend's `getDrawParameters` + * finally answers null. A walk census measured 322 of 871 main batch meshes and 437 of 1,574 + * caster meshes at count 0, so every empty batch was ~17 µs of CPU a frame for nothing drawn. + * `visible` is the one flag three reads before any of that, so a batch with nothing to draw is + * hidden here and shown again by the first write that gives it a record: a placement add, a + * rebind of a retained batch, a residency change, or the main cull's window opening again. + * + * The exception is a batch still owed its prewarm draw, which stays visible *because* it is empty: + * that one draw is what builds the node and the pipeline the loading screen exists to pay for, + * and hiding it would hand the build to the first walk frame that wants the key. It hides as soon + * as the draw is counted — see {@link prewarmDrew} — so this costs one zero-count draw per + * prewarmed batch over the whole load, which is the cost the prewarm is for. + */ + #publish(count: number): void { + // A shadow key is submitted every render of a map that covers it: its own records are the + // dispatch's, so an empty-looking key is one this map draws nothing of, not one to hide. + if (this.role === "key") { + this.mesh.count = this.gpuCount; + this.mesh.visible = true; + return; + } + // A dressed batch's instances are the GPU scene's, and its own `count` says nothing about how + // many the dispatch kept — a zero there makes three skip the indirect draw before the GPU reads + // the record at all. So the mesh is submitted at its region's capacity and the record decides + // what draws; the per-asset coarse gate in `#cullMainPass` runs after this every frame and is + // what shows it. Until then the prewarm draw is the one reason to stay visible *because* it is + // empty: that submission is what builds the node and the pipeline. + if (this.gpu !== undefined) { + this.mesh.count = this.gpuCount; + if (this.bundled === false) this.mesh.visible = this.#awaitingPrewarm; + return; + } + this.mesh.count = count; + this.mesh.visible = count > 0 || this.#awaitingPrewarm; + } + + /** + * The GPU-driven main pass's coarse gate, asked per key and answered per asset: whether any of + * this batch's CPU grid squares is in `visible`, or it still owes the draw that builds its node. + * + * It is deliberately *not* the draw decision. A key's squares are the level the CPU picked from the + * follow point when it built them, and the dispatch re-picks the level from the camera every frame + * — so a key holding no cell can be the one the dispatch draws, and hiding on its own empty answer + * is how a tree disappeared on approach. The caller unions this across every level and part of the + * asset and hands the asset's answer back; see `WorldCells#cullMainPass` and `applyGpuVisible`. + */ + seenIn(visible: ReadonlySet): boolean { + if (this.#awaitingPrewarm) return true; + for (const square of this.#squareSizes.keys()) if (visible.has(square)) return true; + return false; + } + + /** + * Apply the asset's conservative visibility answer to this dressed mesh, and publish the count a + * draw and the census read. A bundled mesh is shown by its recorded bundle and never asked. + */ + applyGpuVisible(shown: boolean): void { + // The region's capacity, not this batch's own records: an empty-looking key — one the CPU built + // at another level — must still submit its indirect draw, and the record inside it draws nothing + // when the dispatch selected none. See `gpuCount`. + this.mesh.count = this.gpuCount; + this.mesh.visible = shown; + } + + /** + * Owe this batch its prewarm draw, and show it: empty, unsubmitted work is not what a prewarm + * wants, one submission is. + * + * The flag on the mesh is what `VirtualShadowNode`'s `#probe` reads: a caster is drawn by a level, + * and a level picks one of the two caster layers, so a level that finds an owed caster renders + * both — otherwise the half it did not pick would leave its prewarm casters unbuilt for the whole + * walk. Engine bookkeeping on the mesh, beside the `casterInstanceScale` the probe already reads. + */ + awaitPrewarmDraw(): void { + this.#awaitingPrewarm = true; + (this.mesh as { casterPrewarmOwed?: boolean }).casterPrewarmOwed = true; + this.#publish(this.drawn); + } + + /** + * Publish this batch's admission for the frame, on the mesh beside the other two flags the shadow + * probe reads off it. A `main` batch has nothing to say — it is the truth the caster halves are + * measured against — and writes its own mesh, which is on layer 0 and no level ever draws. + */ + setMainAdmitted(admitted: boolean): void { + if (this.role === "main" || this.#mainAdmitted === admitted) return; + this.#mainAdmitted = admitted; + this.mesh.mainAdmitted = admitted; + } + + /** + * The owed draw is counted, or it can never come, so the batch follows the count again and hides + * if no placement ever gave it a record. Only `visible` is written: the count three is submitting + * this frame is the right one, and `#publish` would replace it with a window no cull has run for. + */ + prewarmDrew(): void { + this.#awaitingPrewarm = false; + (this.mesh as { casterPrewarmOwed?: boolean }).casterPrewarmOwed = false; + // A bundled mesh is in a render list three fixed when it recorded the bundle, and the replay + // never re-reads visibility — so hiding it here would not save this frame's draw and would lose + // the mesh from every record after the next structural change. See `bundled`. + if (this.bundled === true) return; + this.mesh.visible = this.mesh.count > 0; + } +} diff --git a/packages/core/src/world-cell-chunks.ts b/packages/core/src/world-cell-chunks.ts new file mode 100644 index 0000000000..be7f504103 --- /dev/null +++ b/packages/core/src/world-cell-chunks.ts @@ -0,0 +1,1097 @@ +import { + Box3, + type BufferGeometry, + type Camera, + InstancedMesh, + Material, + Matrix4, + Mesh, + Object3D, +} from "three"; +import { markEngineRenderHook } from "./engine-render-hook.js"; +import { mergeByMaterial } from "./merge-parts.js"; +import { + conservativeViewDepth, + isLodJoinProxy, + lodChainOf, + setModelLodLayers, +} from "./model-lod.js"; +import { displacesVertices } from "./projection-plan.js"; +import type { IRendererLike } from "./renderer.js"; +import { markStatic } from "./static-transform.js"; +import { addInSlices, loadAll } from "./streaming.js"; +import { within, yieldToHost } from "./warmup.js"; +import type { SharedBatch } from "./world-cell-batches.js"; +import type { ICellProxyState, IResidentCell } from "./world-cell-data.js"; +import { + CellProxySelection, + cellProxyGeometrySafe, + cellProxyMaterialMatches, + cellProxyMaterialSafe, + cellProxySourceSafe, + cellProxyWorldScale, + measureCellProxy, + prepareCellProxy, +} from "./world-cell-proxy.js"; +import type { IAssetState } from "./world-cells.js"; +import { + CHUNK_NAME, + VIRTUAL_SHADOW_CASTER_LAYER, + VIRTUAL_SHADOW_WIDE_CASTER_LAYER, + buildChunkShadowProxies, +} from "./world-chunk-shadow.js"; +import type { IWorldCell } from "./world-package.js"; +import type { IAdmissionBudget } from "./world-tiles.js"; + +interface ICellChunksOptions { + readonly loadModel: ((url: string) => Promise) | undefined; + readonly byteBudget: number; + readonly logicalBase: string; + readonly receiveShadow: boolean; + readonly placementReach: Float32Array | undefined; + readonly autoLodMaxPixelError: number; + readonly chunkMergeMaxTriangles: number; + readonly castShadowLevels: number; + readonly bundlesWanted: boolean; +} + +export interface ICellChunksHost { + disposeProxyModel(state: ICellProxyState): void; + disposeLoaded(model: Object3D | undefined): number; + releaseModelPaths(paths: string[]): void; + retainModelPaths(paths: readonly string[], into: string[]): void; + addFailures(count: number): void; + sourceBytes(): number; + load(path: string, wanted: () => boolean): Promise; + cellLive(cell: IResidentCell, generation: number): boolean; + add(object: Object3D): void; + intersectsObject(mesh: Mesh): boolean; + gpuSceneOn(): boolean; + asset(id: string): IAssetState | undefined; + invalidateBundles(key: string): void; + worldSphere( + bounds: Parameters[0], + ): ReturnType; + reportCellFailure( + stage: "asset" | "attach" | "chunk", + key: string, + paths: readonly string[], + error: unknown, + ): void; + parent(): Object3D | null; + prepareCompile( + root: Object3D, + camera: Camera, + renderer: IRendererLike, + ): (() => Promise) | undefined; + compileChunk(renderer: IRendererLike, chunk: Object3D, camera: Camera): Promise; + addChunk(cell: IResidentCell, object: Object3D): void; + publishChunkBounds(cell: IResidentCell, box: Box3 | undefined, proxies: number): void; +} + +/** + * Bytes one merged chunk group may hold: 4 MiB, which is where a single first-draw upload stops + * being a few milliseconds and becomes a frame. + * + * The 228.8 MB of `createAttribute` buffers a browser walk measured — 826 buffers, 33 over 1 MB, + * the largest 23.2 / 18.1 / 17.2 / 16.4 MB — all arrived on the first draw of a merged chunk mesh, + * and that first draw took up to 230 ms. A group is split rather than grown, so each of those + * uploads is bounded and none of them is a stall. + */ +const CHUNK_MERGE_MAX_BYTES = 4 * 1024 * 1024; +/** Position (12) + normal (12) + uv (8) in float32: the three channels a merged group keeps. */ +const CHUNK_MERGE_BYTES_PER_VERTEX = 32; +/** Vertices in 4 MiB of the above. The budget a chunk's material group is split on. */ +const CHUNK_MERGE_MAX_VERTICES = CHUNK_MERGE_MAX_BYTES / CHUNK_MERGE_BYTES_PER_VERTEX; +/** + * Triangles a chunk's merged material group may reach before one of its `InstancedMesh` is left + * instanced instead. A merged group is one draw; past this many triangles the instanced draw is the + * cheaper of the two, so the mesh is kept rather than expanded. It is the same 4 MiB counted in the + * worst case, a de-indexed triangle of three 32-byte vertices, so the cap and the vertex budget above + * are the one size read in two units. See `IWorldCellsLoadOptions.chunkMergeMaxTriangles`. + */ +export const CHUNK_MERGE_MAX_TRIANGLES = Math.floor( + CHUNK_MERGE_MAX_BYTES / (3 * CHUNK_MERGE_BYTES_PER_VERTEX), +); +/** + * How long one chunk's warm-up may take before the chunk is attached uncompiled. The engine's own + * loading-screen bound for a single pipeline is 2,000 ms, and a compile that outlives it is a + * compile that is not coming back — which is a slow frame rather than a chunk that never appears. + */ +const CHUNK_WARM_TIMEOUT_MS = 2000; +/** The engine frame clock, for the warm-up's bound. */ +export const warmClock = (): number => globalThis.performance?.now() ?? Date.now(); + +/** GPU resources already handed to a `dispose`, so no second path can tear the same one down. */ +const tornDown = new WeakSet(); + +/** + * Release one GPU resource: at most once per resource, and never by throwing. + * + * Exactly once is the streaming contract: an asset's geometry is shared by every cell batch drawn + * from it, so several residency paths can reach the same object, and a second teardown is the + * renderer's problem, not the game's. + * + * Never throwing is the other half. Three disposes a geometry by deleting each of its attributes, + * and `WebGPUAttributeUtils.destroyAttribute` reads the GPU buffer off the attribute and calls + * `destroy` on it with no guard — so disposing a geometry whose upload never produced that buffer + * throws `TypeError: Cannot read properties of undefined (reading 'destroy')` from wherever the + * teardown was called. A cell leaving range is the most ordinary thing a streaming world does, so + * that has to land as a counted failure here, not as a dead frame. + * + * @returns `true` when the teardown itself threw, for the caller to count. + */ +export function release(target: { dispose: () => void } | undefined): boolean { + if (target === undefined || tornDown.has(target)) return false; + tornDown.add(target); + try { + target.dispose(); + } catch { + return true; + } + return false; +} + +export function resolveRelative(baseUrl: string, relative: string): string { + if (/^(?:[a-z]+:)?\/\//iu.test(relative) || relative.startsWith("data:")) return relative; + return `${baseUrl}${relative.replace(/^\//u, "")}`; +} + +/** What one chunk's merge did, for `TN_WORLD_CHUNK_MERGE`. */ +interface IChunkMerge { + /** Meshes the chunk held before, and the ones its subtree submits after. */ + readonly meshes: number; + readonly draws: number; + /** + * Bytes the merged groups hold: exactly what the first draw of those meshes uploads, and the + * number this merge exists to keep small. + */ + readonly bytes: number; + /** Instanced meshes baked into a material's group, and the ones left instanced. */ + readonly expanded: number; + readonly keptInstanced: number; + /** Teardowns of the consumed shapes that threw, for the caller to count. */ + readonly failed: number; + /** Shadow-only proxies, per side for merged and retained parts; 0 when nothing casts. */ + readonly shadowDraws: number; + /** + * The opaque meshes a proxy covers. They keep drawing the main pass and keep receiving, and + * they stop casting: the depth pass reads positions, not materials, so it reads the proxy instead. + */ + readonly shadowCovered: readonly Mesh[]; +} + +function meshesOf(root: Object3D): number { + let meshes = 0; + root.traverse((object) => { + if ((object as Mesh).isMesh === true) meshes += 1; + }); + return meshes; +} + +/** + * One chunk's static meshes merged by material, in place, before the chunk is added. + * + * A hand-placed chunk is a bridge or a yard, not a prop: its mesh nodes are packed a few dozen to a + * few hundred per file over a handful of materials, and each one is its own draw — in the main pass + * and again in every shadow level that redraws it. Merging by material is what the cook already did + * for repeated meshes (hence the `InstancedMesh`es in a loaded chunk), so this is the same fix one + * step further out: one draw per material per chunk instead of one per node. + * + * Nothing here decides how anything looks. The merged mesh draws the game's own material and holds + * the geometry exactly as authored, baked into the chunk root's space; what changes is how many + * calls it takes to draw. `mergeByMaterial` does the bake and owns the refusals, so a group it + * cannot merge (a material whose meshes disagree about `uv`) leaves the whole chunk as it was and is + * counted as a failure — a chunk never disappears over a draw-call saving. + * + * Left alone: a skinned mesh (posed per frame), one carrying morph targets (the bake drops them), a + * multi-material mesh (one draw per material already), and anything carrying a baked AutoLOD chain, + * whose own `ModelLod` swaps its geometry and would be swapped for nothing. No chunk node is + * animation-targeted: the chunk path takes `gltf.scene` and never a clip list. + * + * `disposeSources` says who owns the shapes the bake consumed. The merged buffers are the chunk's + * own and are released with it on eviction; a shape nothing draws any more is released here, unless + * the loader cache still holds it — `createAssetLoader` keys models by path and hands the same scene + * back on the next load, so a cached shape is not this world's to tear down. A geometry a kept mesh + * still draws is never released either way. + * + * `castShadow` says the chunk's shadow bill exists at all — the same answer the caller hands the + * chunk's nodes — and only then is it worth one position-only proxy per `side`. See + * `buildChunkShadowProxies`, which draws every side of every chunk with the world's one material for + * that side. + * + * @returns what the merge did, or `undefined` when the bake refused the whole chunk. + */ +function mergeChunk( + chunk: Object3D, + maxTriangles: number, + disposeSources: boolean, + castShadow: boolean, + proxyMaterials: Map, +): IChunkMerge | undefined { + chunk.updateMatrixWorld(true); + const meshes: Mesh[] = []; + const skipped = new Set(); + let instanced = 0; + chunk.traverse((object) => { + const mesh = object as Mesh & { isInstancedMesh?: boolean }; + if (mesh.isMesh !== true) return; + meshes.push(mesh); + // A chain is baked into the geometry the mesh draws, so a merged buffer would carry a `ModelLod` + // that has nothing to swap: the mesh keeps its own geometry and its own level selection. + if (lodChainOf(mesh.geometry) !== undefined) { + skipped.add(mesh); + return; + } + if (Object.keys(mesh.geometry.morphAttributes).length > 0) { + skipped.add(mesh); + return; + } + if (mesh.isInstancedMesh === true) instanced += 1; + }); + const before = meshes.length; + let result: Mesh[]; + try { + result = mergeByMaterial(chunk, { + expandInstancedUnderTriangles: maxTriangles, + label: `world chunk ${chunk.name}`, + maxGroupVertices: CHUNK_MERGE_MAX_VERTICES, + skip: (mesh) => skipped.has(mesh), + }); + } catch { + return undefined; + } + const kept = new Set(skipped); + let keptInstanced = 0; + for (const mesh of result) { + if ((mesh as Mesh & { isInstancedMesh?: boolean }).isInstancedMesh !== true) continue; + kept.add(mesh); + keptInstanced += 1; + } + const held = new Set(); + for (const mesh of kept) held.add(mesh.geometry); + let failed = 0; + // Only what the bake consumed leaves its place; a kept mesh draws the same either way, so it is + // not re-parented and the chunk's authored hierarchy survives around the merged groups. + for (const mesh of meshes) { + if (kept.has(mesh)) continue; + if (disposeSources && !held.has(mesh.geometry) && release(mesh.geometry)) failed += 1; + mesh.removeFromParent(); + } + for (const mesh of result) + if (!(mesh as Mesh & { isInstancedMesh?: boolean }).isInstancedMesh === true) chunk.add(mesh); + // Only what the bake produced is covered: a kept mesh was never merged, so it still holds its own + // per-material draw and its own materials the proxy knows nothing about. + const shadow = castShadow + ? buildChunkShadowProxies( + chunk, + result.filter((mesh) => !kept.has(mesh)), + `world chunk ${chunk.name}`, + proxyMaterials, + ) + : undefined; + // AutoLOD and morphs remain their own draws. Only unmodified static instance silhouettes qualify. + const toRoot = chunk.matrixWorld.clone().invert(); + const retained = castShadow + ? buildChunkShadowProxies( + chunk, + [...kept].filter( + (mesh) => + mesh instanceof InstancedMesh && + mesh.count > 0 && + mesh.layers.isEnabled(0) && + lodChainOf(mesh.geometry) === undefined && + Object.keys(mesh.geometry.morphAttributes).length === 0 && + mesh.morphTexture === null && + mesh.customDepthMaterial === undefined && + mesh.customDistanceMaterial === undefined && + mesh.onBeforeRender === Object3D.prototype.onBeforeRender && + !Array.isArray(mesh.material) && + !displacesVertices(mesh.material) && + mesh.material.visible && + mesh.material.allowOverride && + mesh.material.onBeforeCompile === Material.prototype.onBeforeCompile && + Reflect.get(mesh.material, "isShaderMaterial") !== true && + mesh.material.shadowSide === null && + (mesh.material.clippingPlanes?.length ?? 0) === 0 && + Reflect.get(mesh.material, "vertexNode") == null && + Reflect.get(mesh.material, "depthNode") == null && + Reflect.get(mesh.material, "castShadowNode") == null && + Reflect.get(mesh.material, "wireframe") !== true && + Reflect.get(mesh.geometry, "indirect") == null && + Reflect.get(mesh, "casterInstanceScale") == null && + new Matrix4().multiplyMatrices(toRoot, mesh.matrixWorld).determinant() >= 0, + ), + `world chunk ${chunk.name} retained`, + proxyMaterials, + true, + ) + : undefined; + return { + bytes: mergedBytes(result, kept), + draws: meshesOf(chunk), + expanded: instanced - keptInstanced, + failed, + keptInstanced, + meshes: before, + shadowCovered: [...(shadow?.covered ?? []), ...(retained?.covered ?? [])], + shadowDraws: (shadow?.proxies.length ?? 0) + (retained?.proxies.length ?? 0), + }; +} + +/** + * Bytes the bake's own meshes hold, indices included: the upload their first draw would have paid. + * A kept mesh is not one of them — it was never merged, and it draws the same either way. + */ +function mergedBytes(merged: readonly Mesh[], kept: ReadonlySet): number { + let bytes = 0; + for (const mesh of merged) { + if (kept.has(mesh)) continue; + for (const attribute of Object.values(mesh.geometry.attributes)) + bytes += attribute.array.byteLength; + const index = mesh.geometry.getIndex(); + if (index !== null) bytes += index.array.byteLength; + } + return bytes; +} + +/** Triangles one geometry submits; the count the `TN_WORLD_LOD_CHAIN` marker reports per level. */ +export function levelTriangles(geometry: BufferGeometry): number { + const drawn = geometry.index?.count ?? geometry.getAttribute("position")?.count ?? 0; + return Math.floor(drawn / 3); +} + +export class CellChunks { + #camera: Camera | undefined; + /** + * The renderer and camera of the last frame that ran, kept so a chunk streamed in between frames + * can be compiled before it is attached. `update(renderer, camera)` is the only place either is + * known, and the admission that loads a chunk is not a place that has them. + */ + #renderer: IRendererLike | undefined; + + readonly #hlod: boolean; + readonly #cellProxies = new Set(); + #hlodBytes = 0; + #hlodRefused = 0; + #hlodReason = ""; + #hlodFailures = 0; + /** + * Per-side surfaces shared world-wide, separately for merged and retained proxies. See + * `worldOwned`: a chunk teardown must not release another chunk's shadow surface. + */ + readonly #proxyMaterials = new Map(); + + #proxyObserved(state: ICellProxyState): boolean { + return state.prepared !== undefined && state.observed.size === state.prepared.meshes.length; + } + + releaseCellProxy(state: ICellProxyState): void { + if (!this.#cellProxies.delete(state)) return; + state.prepared?.root.removeFromParent(); + for (const mesh of state.prepared?.meshes ?? []) { + mesh.onBeforeRender = Mesh.prototype.onBeforeRender; + mesh.onAfterRender = Mesh.prototype.onAfterRender; + this.#host.addFailures(release(mesh.geometry) ? 1 : 0); + } + // Cached model ownership uses the same global path holders as chunks and source LODs. + // A compilation never races this release: cancellation retains everything until settlement. + this.#host.disposeProxyModel(state); + this.#host.releaseModelPaths(state.retainedPaths); + this.#hlodBytes -= state.bytes; + state.bytes = 0; + } + + #beginCellProxy(cell: IResidentCell): void { + const proxy = cell.cell.proxy; + if (proxy === undefined) return; + // Reserve resident headroom before IO; decoded source and fixed copies must fit it. + // Temporary pressure retries through the existing admission loop. + const reserve = Math.min( + CHUNK_MERGE_MAX_BYTES * 2, + this.#byteBudget - this.#host.sourceBytes() - this.#hlodBytes, + ); + if (reserve <= 0) return; + const state: ICellProxyState = { + cell, + selector: new CellProxySelection(proxy), + retainedPaths: [], + pending: true, + compiling: false, + cancelled: false, + compiled: false, + active: false, + bytes: 0, + observed: new Set(), + hidden: new Map(), + mainMasks: new Map(), + }; + const source = this.#cellProxySource(state); + // A retained neighboring key or a cheaper source LOD cannot benefit. Reconsider after later + // residency changes without allocating another copy or running a proxy load meanwhile. + if ( + !source.compatible || + proxy.triangles >= source.triangles || + proxy.materialGroups > source.draws + ) + return; + cell.proxy = state; + state.bytes = reserve; + this.#hlodBytes += reserve; + this.#cellProxies.add(state); + const path = resolveRelative(this.#logicalBase, proxy.glb); + if (this.#loadModel === undefined) this.#host.retainModelPaths([path], state.retainedPaths); + void this.#host + .load(path, () => this.#host.cellLive(cell, cell.generation)) + .then( + (model) => { + state.pending = false; + state.model = model; + if ( + !this.#host.cellLive(cell, cell.generation) || + state.cancelled || + model === undefined + ) { + state.cancelled = true; + this.releaseCellProxy(state); + return; + } + try { + if (measureCellProxy(model, proxy, true) > state.bytes) + throw new Error("Cell proxy exceeds its reserved decoded budget."); + } catch (error) { + this.#hlodReason = `${state.cell.key}: ${String(error instanceof Error ? error.message : error).slice(0, 160)}`; + state.cancelled = true; + this.#hlodRefused++; + this.releaseCellProxy(state); + return; + } + // Preparation waits for an admission unit on a rendered update. + }, + () => { + state.pending = false; + state.cancelled = true; + this.#hlodFailures++; + this.releaseCellProxy(state); + }, + ); + } + + #prepareCellProxy(state: ICellProxyState): void { + try { + const prepared = prepareCellProxy( + state.model as Object3D, + state.cell.cell.proxy as NonNullable, + state.bytes, + ); + state.prepared = prepared; + if (prepared.bytes > state.bytes) + throw new Error("Cell proxy exceeds its reserved decoded budget."); + this.#hlodBytes += prepared.bytes - state.bytes; + state.bytes = prepared.bytes; + prepared.root.name = `world-cell-proxy:${state.cell.key}`; + for (const mesh of prepared.meshes) mesh.receiveShadow = this.#receiveShadow; + this.#host.add(prepared.root); + } catch (error) { + this.#hlodReason = `${state.cell.key}: ${String(error instanceof Error ? error.message : error).slice(0, 160)}`; + state.cancelled = true; + this.#hlodRefused++; + this.releaseCellProxy(state); + } + } + + #compileCellProxy(state: ICellProxyState, renderer: IRendererLike, camera: Camera): void { + const prepared = state.prepared; + if (prepared === undefined) return; + const compile = this.#host.prepareCompile(prepared.root, camera, renderer); + if (compile === undefined) return; + state.compiling = true; + // No timeout counts as cancellation. This original promise owns every staged resource until it + // settles, even if the cell/world has already left. Refusal keeps source and releases afterward. + void Promise.resolve() + .then(compile) + .then( + () => { + state.compiling = false; + if (state.cancelled || !this.#host.cellLive(state.cell, state.cell.generation)) { + state.cancelled = true; + this.releaseCellProxy(state); + return; + } + this.#uploadAttributes(prepared.root); + state.compiled = true; + // Three calls render hooks during compile too, and may swallow pipeline errors. Observe a + // matching main-camera backend submission only AFTER this compile settles. Zero-count + // submission proves readiness of that route, not full-triangle execution or visual quality. + for (const mesh of prepared.meshes) { + let before: number | undefined; + const calls = (): number | undefined => { + try { + const info = renderer.info as { render?: { drawCalls?: number; calls?: number } }; + const value = info.render?.drawCalls ?? info.render?.calls; + return Number.isSafeInteger(value) && (value as number) >= 0 ? value : undefined; + } catch { + return undefined; + } + }; + mesh.onBeforeRender = (_raw, _scene, drawnCamera) => { + before = + drawnCamera === this.#camera && renderer.compiling !== true ? calls() : undefined; + }; + markEngineRenderHook(mesh.onBeforeRender); + mesh.onAfterRender = (_raw, _scene, drawnCamera) => { + const after = calls(); + if ( + drawnCamera === this.#camera && + renderer.compiling !== true && + before !== undefined && + after !== undefined && + after > before + ) + state.observed.add(mesh); + before = undefined; + }; + markEngineRenderHook(mesh.onAfterRender); + } + }, + () => { + state.compiling = false; + state.cancelled = true; + this.#hlodFailures++; + this.releaseCellProxy(state); + }, + ) + .catch(() => { + state.compiling = false; + state.cancelled = true; + this.#hlodFailures++; + this.releaseCellProxy(state); + }); + } + + #cellSourceDrawn(mesh: Mesh): boolean { + if ( + this.#camera === undefined || + (mesh instanceof InstancedMesh && mesh.count === 0) || + !this.#camera.layers.test(mesh.layers) || + Array.isArray(mesh.material) || + !mesh.material.visible + ) + return false; + for (let object: Object3D | null = mesh; object !== null; object = object.parent) + if (!object.visible) return false; + if (mesh.frustumCulled && !this.#host.intersectsObject(mesh)) return false; + return true; + } + + #cellProxySource(state: ICellProxyState): { + compatible: boolean; + triangles: number; + draws: number; + meshes: Mesh[]; + } { + const cell = state.cell; + const meshes = [...cell.mainChunkMeshes]; + for (const chunk of cell.chunks) + chunk.traverse((node) => { + if (node instanceof Mesh && isLodJoinProxy(node)) meshes.push(node); + }); + const materials = new Set(); + const staticInstances = new Set(); + for (const [mesh, baseline] of cell.chunkInstances) + if ( + mesh.instanceMatrix === baseline.matrix && + mesh.instanceMatrix.version === baseline.version && + mesh.count === baseline.count + ) + staticInstances.add(mesh); + let triangles = 0; + let draws = 0; + let compatible = + cellLoaded(cell) && (state.cell.cell.proxy?.scope !== "cell" || !this.#host.gpuSceneOn()); + for (const mesh of meshes) { + if ( + !cellProxySourceSafe(mesh, staticInstances) || + Array.isArray(mesh.material) || + !cellProxyGeometrySafe(mesh.geometry) || + !cellProxyMaterialSafe(mesh.material as Material) + ) + compatible = false; + else materials.add(mesh.material as Material); + if (this.#cellSourceDrawn(mesh)) { + triangles += + levelTriangles(mesh.geometry) * (mesh instanceof InstancedMesh ? mesh.count : 1); + draws++; + } + } + if (state.cell.cell.proxy?.scope === "cell") { + const counts = new Map(); + const costs = new Map(); + for (const run of cell.cell.runs) { + const asset = this.#host.asset(run.asset); + if ( + asset === undefined || + !asset.hlodSafe || + asset.impostor !== undefined || + asset.definition.maxDistance !== undefined || + this.#placementReach !== undefined + ) { + compatible = false; + continue; + } + for (const [part, shape] of (asset.levels[0] ?? []).entries()) { + const entries = cell.batches.filter((entry) => entry.run === run && entry.part === part); + if ( + entries.length !== 1 || + entries[0]?.level !== 0 || + entries[0]?.batch.count !== run.count || + !cellProxyMaterialSafe(shape.material) + ) { + compatible = false; + continue; + } + materials.add(shape.material); + } + } + for (const entry of cell.batches) { + if (entry.shared === undefined || entry.segment < 0 || entry.level !== 0) { + compatible = false; + continue; + } + counts.set(entry.shared, (counts.get(entry.shared) ?? 0) + entry.batch.count); + costs.set( + entry.shared, + (costs.get(entry.shared) ?? 0) + levelTriangles(entry.batch.geometry) * entry.batch.count, + ); + } + for (const [shared, count] of counts) { + // Retain the segment, hence near restoration is a flag write with no budget dependency. + // A neighboring holder keeps this key drawn; such a key cannot be retired for this cell. + if (shared.live !== count) compatible = false; + else { + if (this.#cellSourceDrawn(shared.mesh)) { + draws++; + triangles += costs.get(shared) ?? 0; + } + meshes.push(shared.mesh); + } + } + } + for (const mesh of state.prepared?.meshes ?? []) { + if ( + ![...materials].some((material) => + cellProxyMaterialMatches(mesh.material as Material, material), + ) + ) + compatible = false; + } + return { compatible, triangles, draws, meshes }; + } + + restoreCellProxySource(state: ICellProxyState): void { + for (const [mesh, visible] of state.hidden) mesh.visible = visible; + state.hidden.clear(); + for (const [mesh, mask] of state.mainMasks) setModelLodLayers(mesh, mask); + state.mainMasks.clear(); + } + + showCellProxy(state: ICellProxyState, active: boolean, meshes?: readonly Mesh[]): void { + const prepared = state.prepared; + if (prepared === undefined) return; + const changed = state.active !== active; + // AutoLOD owns chunk visibility. Retire only its main pass, retaining the selected source + // geometry as a caster; joined rungs inherit this same pass ownership on activation. + this.restoreCellProxySource(state); + if (active) + for (const mesh of meshes ?? this.#cellProxySource(state).meshes) { + if (mesh.layers.isEnabled(0)) { + if (state.cell.mainChunkMeshes.includes(mesh) || isLodJoinProxy(mesh)) { + state.mainMasks.set(mesh, mesh.layers.mask); + let mask = mesh.layers.mask & ~1; + if (mesh.castShadow) + mask |= (1 << VIRTUAL_SHADOW_CASTER_LAYER) | (1 << VIRTUAL_SHADOW_WIDE_CASTER_LAYER); + setModelLodLayers(mesh, mask); + } else { + state.hidden.set(mesh, mesh.visible); + mesh.visible = false; + } + } + } + prepared.root.visible = active || !this.#proxyObserved(state); + prepared.meshes.forEach((mesh, index) => { + const range = prepared.ranges[index]; + if (range !== undefined) mesh.geometry.setDrawRange(range.start, active ? range.count : 0); + }); + state.active = active; + if (changed) { + this.#host.invalidateBundles(state.cell.key); + } + } + + stepCellProxies( + cells: Iterable, + matrixWorld: Matrix4, + budget: IAdmissionBudget, + renderer?: IRendererLike, + camera?: Camera, + ): void { + if (!this.#hlod) return; + for (const cell of cells) { + if (cell.cell.proxy === undefined) continue; + if (cell.proxy === undefined) { + if (cellLoaded(cell)) budget.admit(() => this.#beginCellProxy(cell)); + continue; + } + const state = cell.proxy; + if (state.cancelled || state.pending) continue; + if (state.model !== undefined && state.prepared === undefined) + budget.admit(() => this.#prepareCellProxy(state)); + if (state.cancelled || state.prepared === undefined) continue; + if (!state.compiled && !state.compiling && renderer !== undefined && camera !== undefined) + budget.admit(() => this.#compileCellProxy(state, renderer, camera)); + this.restoreCellProxySource(state); + const source = this.#cellProxySource(state); + const scale = cellProxyWorldScale(matrixWorld); + const sphere = this.#host.worldSphere(state.prepared.bounds); + sphere.radius = state.prepared.bounds.radius * scale; + const view = + camera === undefined + ? undefined + : conservativeViewDepth( + camera, + sphere.center, + sphere.radius, + Reflect.get(camera, "near") ?? 0.1, + ); + const height = renderer?.domElement.height; + const selected = state.selector.select({ + sourceReady: cellLoaded(cell), + proxyReady: state.compiled && this.#proxyObserved(state), + sourceCompatible: source.compatible && scale > 0, + sourceTriangles: source.triangles, + replaceableSourceDraws: source.draws, + views: + camera !== undefined && + height !== undefined && + Number.isFinite(height) && + height > 0 && + view !== undefined + ? [ + { + camera, + viewportHeight: height, + ...view, + finest: Reflect.get(camera, "isOrthographicCamera") === true || view.degenerate, + }, + ] + : [], + // Collapsing a valid Three transform retains detail; a zero error scale cannot select LOD. + errorScale: scale === 0 ? 1 : scale, + maxPixelError: this.#autoLodMaxPixelError, + }); + this.showCellProxy(state, selected === "proxy", source.meshes); + } + } + + startChunkLoad(cell: IResidentCell): void { + const generation = cell.generation; + const paths: string[] = []; + for (const chunk of cell.cell.chunks ?? []) + paths.push(resolveRelative(this.#logicalBase, chunk)); + // A chunk's source is the same loader cache entry an asset's level would be: held while this + // cell is resident and handed back with the last cell naming the path, through the one + // `#modelPaths` map. A `loadModel` override parses per load and reaches no cache, so it owns + // nothing here either; see `#releaseModelPaths`. + if (this.#loadModel === undefined) this.#host.retainModelPaths(paths, cell.retainedModelPaths); + loadAll(paths, (path) => this.#host.load(path, () => this.#host.cellLive(cell, generation)), { + marker: false, + }).then( + (models) => { + void this.#attachChunks( + cell, + generation, + models.filter((model): model is Object3D => model !== undefined), + ); + }, + (error: unknown) => { + cell.chunkFailed = true; + this.#host.addFailures(1); + this.#host.reportCellFailure("chunk", cell.key, paths, error); + }, + ); + } + + /** + * Hand the chunk's own buffers to the renderer now, and report how many it took. + * + * A compile builds pipelines, not geometry, so the bake's new attributes reached the device on + * whichever frame first drew them — 826 buffers and 228.8 MB on an 8 s walk, 33 of them over a + * megabyte, lining up exactly with the p95 and max frame spikes. The backend's own attribute + * creation is idempotent, so this is a move rather than a second upload, and a renderer with no + * such seam answers 0 and behaves as it did. + */ + #uploadAttributes(chunk: Object3D): number { + const upload = this.#renderer?.uploadAttributes; + if (upload === undefined) return 0; + const geometries = new Set(); + chunk.traverse((object) => { + const mesh = object as Mesh; + if (mesh.isMesh === true) geometries.add(mesh.geometry); + }); + return upload(geometries); + } + + /** + * Hand this chunk's own textures to the device now, and report how many it took. + * + * The same argument as the buffers above, one resource class over: a compile builds pipelines, not + * pixels, so the bake's textures reached the device inside `_renderObjectDirect` on the frame the + * chunk first drew — 87 ms of a 94 ms first draw in one frame of a Machinefall map-walk, and 79 ms + * of 131 ms in another. A texture the world already shares with what is on screen is one early + * return inside three and no work here. + */ + #uploadTextures(chunk: Object3D): number { + const upload = this.#renderer?.uploadTextures; + if (upload === undefined) return 0; + return upload(chunk); + } + + /** + * Compile a merged chunk before anything can draw it, and report whether the compile came back. + * + * A chunk's first draw is the most expensive frame a streaming world has: its own first draw built + * nodes and a render pipeline synchronously, and one chunk cost **248 ms of a 291 ms walk** in the + * census that found it. The engine already owns this work — `warmUpScene` drives the renderer's + * own `compileAsync`, and this is that same call with the world as the target scene, so the chunk's + * materials resolve the same lights, environment and clipping the real frame will resolve them + * against. A pipeline compiled against no lights would be a different pipeline, and the frame would + * still pay for it. + * + * It is bounded by `within`, the warm-up's own bound: a compile that never settles is abandoned and + * the chunk is attached anyway, because a missing chunk is worse than a slow frame. A world that is + * in no scene is skipped outright — nothing projects it, so a compile would build nothing, which is + * the same answer `load()` gets, where the loading screen is about to draw it anyway. + */ + async #warmChunk(chunk: Object3D): Promise { + const renderer = this.#renderer; + const camera = this.#camera; + if (renderer === undefined || camera === undefined || this.#host.parent() === null) + return false; + // The rejected compile is a pipeline this warm-up could not build, not a reason to fail the + // admission: the frame that needs it will try again and fail there, where the error belongs. + return within( + this.#host.compileChunk(renderer, chunk, camera), + CHUNK_WARM_TIMEOUT_MS, + yieldToHost, + warmClock, + ); + } + + async #attachChunks( + cell: IResidentCell, + generation: number, + models: readonly Object3D[], + ): Promise { + const live = (): boolean => this.#host.cellLive(cell, generation); + let attached = 0; + // Merged and shaped, not yet in the world: the compile below runs between the two, and a cell + // that left the ring in the middle takes its prepared chunks with it. + const prepared: { readonly object: Object3D; readonly proxies: number }[] = []; + try { + const report = await addInSlices( + models, + (object) => { + if (!live()) return; + object.name = CHUNK_NAME; + // One draw per material before the chunk is ever added, so the main pass and every shadow + // level that redraws it submit the chunk's materials rather than its nodes. A refusal + // leaves the chunk exactly as authored and is counted: it never costs the chunk itself. + const merge = mergeChunk( + object, + this.#chunkMergeMaxTriangles, + // A `loadModel` override parses per load, so a chunk's shapes are this world's to + // release; `assets.model` caches by path and hands the same scene back, so they are not. + this.#loadModel !== undefined, + this.#castShadowLevels > 0, + this.#proxyMaterials, + ); + // Where the frame budget counts this chunk's main-pass draws (`RenderPassBudget`). Written + // after the bake and over the whole subtree, because that is the only point where all three + // outcomes exist at once: a merged group, an instanced mesh the bake kept, and a chunk it + // refused whole are all one chunk's draws. The proxies the bake built are off layer 0 and + // keep the `proxies` origin they were given. + object.traverse((node) => { + if ((node as Mesh).isMesh !== true || node.layers.isEnabled(0) === false) return; + node.userData.tnDrawSource = "chunks"; + }); + // The buffers the bake just made, uploaded here rather than on the chunk's first draw, + // which is the frame that uploads them today: 228.8 MB over 826 `createAttribute` calls + // in a browser walk, and up to 230 ms inside the frames that first submitted them. + // `addInSlices` is the admission budget, so this is one chunk per update, never a batch. + const uploaded = this.#uploadAttributes(object); + this.#uploadTextures(object); + if (merge === undefined) this.#host.addFailures(1); + else { + this.#host.addFailures(merge.failed); + console.info( + `TN_WORLD_CHUNK_MERGE meshes=${String(merge.meshes)} draws=${String(merge.draws)} ` + + `bytes=${String(merge.bytes)} uploaded=${String(uploaded)} ` + + `instancedExpanded=${String(merge.expanded)} keptInstanced=${String(merge.keptInstanced)} ` + + `shadowDraws=${String(merge.shadowDraws)}`, + ); + } + // Hand-placed chunks are the buildings and set dressing: full shape, so they cast with the + // near scatter and receive like everything else when the game asks for shadows. A merged + // opaque mesh the proxy covers keeps receiving and stops casting: the depth pass reads the + // proxy, so casting from both would pay the bill the proxy was built to remove. The proxy + // itself is off layer 0 and already carries what the level cameras need of it. + if (this.#castShadowLevels > 0 || this.#receiveShadow) { + const covered = new Set(merge?.shadowCovered); + object.traverse((node) => { + if (node.layers.isEnabled(0) === false) return; + node.castShadow = this.#castShadowLevels > 0 && !covered.has(node as Mesh); + node.receiveShadow = this.#receiveShadow; + }); + } + prepared.push({ object, proxies: merge?.shadowDraws ?? 0 }); + }, + { marker: false, while: live }, + ); + if (report.stopped) + for (let i = report.added; i < models.length; i += 1) + this.#host.addFailures(this.#host.disposeLoaded(models[i] as Object3D)); + for (const { object, proxies } of prepared) { + // The compile is the whole point of the wait, and the wait is the only thing between the + // merge and the attach. Everything the world does not own is released when the cell that + // asked for the chunk is gone, so a chunk that arrives after its cell left is torn down here + // rather than compiled and thrown at a world that is not looking. + if (live()) await this.#warmChunk(object); + if (!live()) { + this.#host.addFailures(this.#host.disposeLoaded(object)); + continue; + } + cell.chunks.push(object); + object.traverse((node) => { + if (node instanceof Mesh && node.layers.isEnabled(0)) cell.mainChunkMeshes.push(node); + if (node instanceof InstancedMesh && node.layers.isEnabled(0)) + cell.chunkInstances.set(node, { + matrix: node.instanceMatrix, + version: node.instanceMatrix.version, + count: node.count, + }); + }); + // The chunk's own box, from the merged geometry's boxes rather than its vertices: a chunk + // is loaded once and both consumers of it are only asked to be no wider than the chunk. Read + // before `#addChunk`, which may hand part of the subtree to a cell's record and leave the + // rest behind, so the box is the chunk's whatever ends up where. + const box = + proxies > 0 || this.#bundlesWanted ? new Box3().setFromObject(object, false) : undefined; + this.#host.addChunk(cell, object); + // A loaded chunk is placed once and never rewritten: its transforms, geometry and material + // are the ones the export gave it, so it is the one subtree here with nothing to announce. + markStatic(object); + // The proxies' shadow pipelines are the one thing a compile cannot build — three creates a + // shadow variant on the first pass that draws the caster, and the pass is the levels'. So + // the levels are told a caster arrived, exactly as a prewarmed caster cluster tells them: + // the first level whose window covers the chunk submits the proxy and builds it. With one + // material per `side` world-wide, that is one pipeline for the world's first chunk of each + // side and a node binding for every chunk after it, not a compile per chunk. + this.#host.publishChunkBounds(cell, box, proxies); + attached += 1; + } + } catch (error) { + cell.chunkFailed = true; + this.#host.addFailures(1); + this.#host.reportCellFailure( + "attach", + cell.key, + Array.from(cell.cell.chunks ?? [], (chunk) => resolveRelative(this.#logicalBase, chunk)), + error, + ); + for (let i = attached; i < models.length; i += 1) + this.#host.addFailures(this.#host.disposeLoaded(models[i] as Object3D)); + } + if (this.#host.cellLive(cell, generation) && !cell.chunkFailed) cell.chunksLoaded = true; + } + get renderer(): IRendererLike | undefined { + return this.#renderer; + } + set renderer(value: IRendererLike | undefined) { + this.#renderer = value; + } + get camera(): Camera | undefined { + return this.#camera; + } + set camera(value: Camera | undefined) { + this.#camera = value; + } + readonly #host: ICellChunksHost; + readonly #loadModel: ((url: string) => Promise) | undefined; + readonly #byteBudget: number; + readonly #logicalBase: string; + readonly #receiveShadow: boolean; + readonly #placementReach: Float32Array | undefined; + readonly #autoLodMaxPixelError: number; + readonly #chunkMergeMaxTriangles: number; + readonly #castShadowLevels: number; + readonly #bundlesWanted: boolean; + constructor(hlod: boolean, options: ICellChunksOptions, host: ICellChunksHost) { + this.#hlod = hlod; + this.#host = host; + this.#loadModel = options.loadModel; + this.#byteBudget = options.byteBudget; + this.#logicalBase = options.logicalBase; + this.#receiveShadow = options.receiveShadow; + this.#placementReach = options.placementReach; + this.#autoLodMaxPixelError = options.autoLodMaxPixelError; + this.#chunkMergeMaxTriangles = options.chunkMergeMaxTriangles; + this.#castShadowLevels = options.castShadowLevels; + this.#bundlesWanted = options.bundlesWanted; + } + get bytes(): number { + return this.#hlodBytes; + } + restoreSources(): void { + for (const state of this.#cellProxies) this.restoreCellProxySource(state); + } + releaseMaterials(): void { + for (const material of this.#proxyMaterials.values()) + this.#host.addFailures(release(material) ? 1 : 0); + this.#proxyMaterials.clear(); + } + stats() { + return { + on: this.#hlod, + loaded: [...this.#cellProxies].filter( + (state) => state.prepared !== undefined && !state.cancelled, + ).length, + compiled: [...this.#cellProxies].filter((state) => state.compiled && !state.cancelled).length, + observed: [...this.#cellProxies].filter( + (state) => this.#proxyObserved(state) && !state.cancelled, + ).length, + active: [...this.#cellProxies].filter((state) => state.active).length, + pending: [...this.#cellProxies].filter( + (state) => + state.pending || + state.compiling || + (!state.cancelled && !state.compiled && state.prepared !== undefined), + ).length, + bytes: this.#hlodBytes, + refused: this.#hlodRefused, + reason: this.#hlodReason, + failures: this.#hlodFailures, + }; + } + evictProxy(cell: IResidentCell): void { + if (cell.proxy !== undefined) { + this.showCellProxy(cell.proxy, false); + cell.proxy.cancelled = true; + cell.proxy.prepared?.root.removeFromParent(); + if (!cell.proxy.pending && !cell.proxy.compiling) this.releaseCellProxy(cell.proxy); + } + } +} + +export function cellLoaded(cell: IResidentCell): boolean { + return cell.chunksLoaded && cell.completedRuns.size === cell.cell.runs.length; +} diff --git a/packages/core/src/world-cell-data.ts b/packages/core/src/world-cell-data.ts new file mode 100644 index 0000000000..a09d03de0c --- /dev/null +++ b/packages/core/src/world-cell-data.ts @@ -0,0 +1,911 @@ +import type { + Box3, + BufferGeometry, + Camera, + InstancedMesh, + Material, + Matrix4, + Mesh, + Object3D, +} from "three"; + +import type { IAssetLoader } from "./assets.js"; +import type { InstancedBatch } from "./instanced-batch.js"; +import type { OcclusionMode } from "./render/depth-pyramid.js"; +import type { SharedBatch } from "./world-cell-batches.js"; +import type { CellProxySelection, IPreparedCellProxy } from "./world-cell-proxy.js"; + +import type { IAssetState } from "./world-cells.js"; +import type { IWorldCell, IWorldPackage, IWorldRun } from "./world-package.js"; + +import type { IWorldTilesOptions } from "./world-tiles.js"; + +/** The point a streamed world follows; an `Object3D` satisfies this shape. */ +export interface IWorldCellsFollow { + readonly position: { readonly x: number; readonly y?: number; readonly z: number }; +} + +/** Hard caps on what stays resident. A cap that is reached reports pressure, it never throws. */ +export interface IWorldCellsBudget { + readonly residentCells: number; + readonly instances: number; + /** Placement-record bytes (`32` per instance) the resident cells are allowed to weigh. */ + readonly bytes: number; +} + +/** Terrain options forwarded to the composed `TerrainTiles`; `tileSize` defaults to `cellSize`. */ +export interface IWorldCellsTerrainOptions { + readonly tileSize?: number; + readonly tileResolution?: number; + readonly lodFactors?: readonly number[]; + readonly lodDistances?: readonly number[]; + readonly skirtDepth?: number; + /** + * Tiles kept resident around the follow tile, independent of the cell `ring`: terrain can reach + * to the horizon while props stay near. Defaults to `ring`, so raising only this widens the + * ground without widening the props, and the far tiles take the coarser `lodDistances` levels. + */ + readonly streamRadius?: number; + /** + * Tiles that get a `createCollider` body, by Chebyshev radius from the follow tile. Defaults to + * `ring`. A tile entering or leaving it creates or disposes its collider, so a wide terrain + * radius does not have to mean a physics body for every tile in it. + */ + readonly colliderRadius?: number; +} + +export interface IWorldCellsLoadOptions { + /** + * Logical path of `world.json` (`world/world.json`); every other path in the package resolves + * against it. A leading `/` is accepted and stripped, so an uncompiled `public/world` keeps + * working. + */ + readonly url: string; + /** + * Loader the package's paths resolve through — its manifest, or the authored names when there is + * none. Defaults to a fresh `createAssetLoader()`; inside a game, pass `ctx.assets` so the + * package's compiled output and compressed textures reach the renderer the game booted with. + * + * The world releases the source models it asks this loader for only when it created the loader + * itself: each asset's requested level paths are released through `release("model", path)` once + * the last asset naming a path is gone, so a streamed package does not pin an internally owned + * cache. An explicitly supplied loader is the caller's — the world never releases its cache, so a + * second game or world holding the same model keeps it alive; the caller ends that lifetime with + * its own `release`. The loader is never cleared, and a path this world never asked for is never + * touched either way. + */ + readonly assets?: IAssetLoader; + /** Game-owned terrain surface, handed straight to `TerrainTiles`. */ + readonly surface: IWorldTilesOptions["surface"]; + /** Passed straight to `TerrainTiles` for per-tile colliders. */ + readonly createCollider?: IWorldTilesOptions["createCollider"]; + /** Read once per update; an `Object3D` works. */ + readonly follow: IWorldCellsFollow; + /** Chebyshev radius in cells to keep resident; a cell leaves only beyond `ring + 1`. */ + readonly ring: number; + readonly budgets: IWorldCellsBudget; + /** False streams props/chunks only; the caller owns terrain geometry and collision. */ + readonly terrain?: IWorldCellsTerrainOptions | false; + /** Cooked cell proxies are considered by default; false retains source rendering. Static CPU + * cells only, with strict measured triangle/draw savings and projected-error hysteresis. + * Source collision and shadow representations stay resident; unsupported sources retain detail. */ + readonly hlod?: boolean; + /** Optional immutable per-record 3D draw reach in metres, indexed like placements. + * Nonnegative finite values or Infinity (no extra limit); combined with asset maxDistance. + * Filtered by the existing admission/refilter budget, never by a separate culler. + */ + readonly placementReach?: Float32Array; + /** Inward return margin for authored CPU LODs, in [0, 1). Requires gpuScene:false and impostors:false (the default). + * Omitted, selection uses the existing direct gates. State survives cell eviction/revisit. */ + readonly lodHysteresis?: number; + /** Preserve supplied part/material identity and exact authored LOD membership, including shadow batches. Requires impostors:false (the default). + * Default content sharing and missing-alpha-part coverage remain enabled when omitted. */ + readonly preserveAuthoredParts?: boolean; + /** Already decoded immutable world-v1 data; URL still supplies the base for model paths. */ + readonly data?: { + readonly manifest: IWorldPackage; + readonly placements: ArrayBuffer; + /** Omitted values load the manifest heightmap unless terrain is false. */ + readonly heightmap?: Uint16Array; + }; + /** + * How a scattered part whose own material is `transparent` is drawn. `"cutout"` (the default) + * gives the part a clone of that material with `transparent: false` and an `alphaTest`, so the + * instanced draw needs no per-instance sorting and the depth buffer rejects what is behind it; + * `"blend"` draws the material as authored. One clone per asset part, never per cell. + */ + readonly transparentScatter?: "cutout" | "blend"; + /** + * Shadows for the streamed world, all off by default. `cast` makes scattered batches cast into + * the scene's shadow map. Their `castLevels` finest distance levels (default 1: the near shape) + * each cast their own shape; a placement drawn at a coarser level of a chain casts with the + * coarsest of those, so it keeps its shadow without a caster mesh per level — every level casting + * its own minted ~3x the casters and the churn showed as garbage collection on a walk (PRD-541). + * Authored `lods` past `castLevels` cast nothing. `receive` lets scatter and terrain receive + * shadows. + */ + readonly shadows?: { + readonly cast?: boolean; + readonly castLevels?: number; + readonly receive?: boolean; + /** + * @deprecated Accepted and ignored. Clusters replace it: what a shadow level submits is bounded + * by the clusters its own window covers, not by a hand-tuned radius. It still validates, so a + * game that set it keeps compiling and keeps loading. + */ + readonly castDistance?: number; + /** + * Called in a batch at most once a second after streamed records changed, so the shadow levels + * that read them redraw. A call with no region asks `VirtualShadowNode.invalidateAll`; + * nothing changed means no call. + * + * Admissions and evictions are separate calls, each with the union of the bounds of the records + * that changed — the placement position widened by the asset's own bounds, unioned over the + * records of this update and nothing else, so a hook that forwards it to + * `VirtualShadowNode.invalidateRegion` redraws the levels whose window covers them and leaves + * the rest of the cascade holding its maps. A call with no region is still a blanket one: the + * prewarm passes it while a caster's first draw is owed, and there is no region for that. + */ + readonly invalidate?: (region?: IShadowRegion) => void; + /** + * An asset whose authored bounds are shorter than this casts into the finest shadow level only, + * default 1.5 m. Ground cover below it — ferns, grass, bushes — casts a shadow that is sub-texel + * noise at every range a wide level covers, and costs one draw per key per level there. Raise it + * to draw everything at every range, lower it to cull more. Measured on the authored bounds, so + * a package that scales a fern up four times is judged on the un-scaled fern. + */ + readonly smallCasterMetres?: number; + }; + /** + * Triangles a hand-placed chunk's merged material group may reach before one of its + * `InstancedMesh` is left instanced. Defaults to 43,690, the same 4 MiB + * budget the merged buffers are split on counted in the worst case, a de-indexed triangle: a merged + * group is a single draw, so expanding into it wins until the merged geometry is too big for the + * frame, and above it the instanced draw is the cheaper of the two. A group that crosses the budget + * is split into several meshes in traversal order rather than grown, and an instanced shape over + * 2,048 triangles is never expanded. Everything else in a chunk merges regardless — one mesh per + * material, in the main pass and in every shadow pass that redraws it. + */ + readonly chunkMergeMaxTriangles?: number; + /** + * `(url) => Promise`, overriding `assets.model`; a raw `GLTFLoader` or a game's own + * loader works. The url is the authored one, so a compiled package wants `assets` instead. + */ + readonly loadModel?: (url: string) => Promise; + /** + * Model loads in flight at once, assets and chunks together. Defaults to `loadAll`'s + * `concurrency`, six. + */ + readonly concurrency?: number; + /** + * Cell-asset batches refiltered per `update`, nearest cell first. Defaults to 16. A cell that did + * not get its turn keeps drawing the batches it has until a later update replaces them. + */ + readonly rebuildsPerUpdate?: number; + /** + * Seconds of motion to stream ahead of, default 1.5: the ring centres on where the follow point + * will be (`position + velocity * prefetchSeconds`), so a fast camera finds cells already loaded + * instead of outrunning them. The lead is capped at three quarters of the ring, so the cell the + * follow point is in always stays resident. `0` streams around the follow point itself. + */ + readonly prefetchSeconds?: number; + /** + * New batch meshes created per update, default 2. On WebGPU each new InstancedMesh builds its + * own shader the first frame it draws (~10-20 ms of main thread), so a burst of first-seen assets + * is spread over frames instead of stacked into one; recycled meshes are not counted. + */ + readonly freshMeshesPerUpdate?: number; + /** + * New batch meshes per update while the startup cover hides the world, default 8x + * `freshMeshesPerUpdate`. The first-draw shader cost the default spreads is invisible behind an + * opaque loading screen, and a canopy that needs fresh meshes otherwise trickles in two a frame. + */ + readonly coveredFreshMeshesPerUpdate?: number; + /** + * Side of the world-grid square one shared batch's records are clustered into, in world units. + * Defaults to the package's own `cellSize`, which is the square the placements are already cut + * on. Each `(key, cluster)` is its own caster InstancedMesh with its own bounds on the shadow + * caster layer, so a virtual-shadow level submits only the clusters its window covers. The main + * pass keeps one mesh per `asset:level:part` whatever this is, so it costs main-pass draws + * nothing. A larger square means fewer caster meshes and more of the world in every level render. + */ + readonly clusterSize?: number; + /** + * Milliseconds one `update` may spend admitting streamed content — cell batches, terrain tiles, + * colliders and `maxDistance`/`lods` refilters together. Defaults to 2, and `Infinity` opts out + * of the ceiling entirely. + * + * The ceiling is a budget, not a promise: one unit of work always finishes, so a frame spends at + * most this plus its last unit. Work that does not fit waits for the next `update` rather than + * being dropped, and `stats().admission` reports what is waiting. A frame that admits nothing + * costs nothing here. + */ + readonly admissionBudgetMs?: number; + /** + * The admission budget while the startup cover hides the world and readiness is pending. Defaults + * to 8x `admissionBudgetMs` (16 ms for the 2 ms default), so the spawn a loading screen waits on + * is admitted in fewer frames. Set it equal to `admissionBudgetMs` to keep one budget throughout. + */ + readonly coveredAdmissionBudgetMs?: number; + /** + * Milliseconds source for the admission budget, `performance.now` by default. Injectable so a + * test can prove the ceiling instead of hoping a machine is slow enough to show it. + */ + readonly admissionNow?: () => number; + /** + * The screen-space projection the extra levels of a baked AutoLOD chain are switched at, for an + * asset whose `world.json` entry names no nonempty authored `lods` of its own. An explicit empty + * list also uses the chain when prepared geometry and material still match its static contract. + * + * A chain measures its levels' geometric error in world units and the model runtime turns that + * into pixels per frame; a batched draw cannot, so the same test is solved for distance once, + * here, and every placement beyond it draws the next level. `fovY` is the vertical field of view + * in degrees and `viewportHeight` the drawing-buffer height in raster pixels — a CSS height is the + * wrong number, exactly as it is for the model runtime. + * + * Defaults are 60° over 1080 raster rows: a desktop view, and what the switch distance is a + * function of. A world whose camera this class cannot see should pass its own, or a world with a + * narrow field of view (a telephoto gun sight) or a tall window will switch earlier than its + * pixels ask for. An asset with nonempty authored `lods` ignores all three numbers entirely: the package is + * the authority on its own shape. + * + * `maxPixelError` is the error budget, in pixels, every synthesized level is selected against, and + * it defaults to 4 rather than to the budget the asset's chain was registered with. The + * registered budget is calibrated for a `ModelLod`, which picks a level for *one* mesh from where + * that mesh is on screen; an instanced draw cannot, because every placement in the draw is at its + * own distance and the draw is one call with one geometry. The batched answer has to be solved + * once, for the whole key, which moves every switch distance outward by the ratio of the budgets — + * and a 1 px budget is calibrated for a hero object filling the screen. At 1 px a tree's switches + * land at 400–600 m on a 1080-row view, which on a 2 km map with a 640 m ring is past most of the + * resident world: the levels are all there and none of them is ever selected, which is the same + * full triangle bill the chain was added to remove. Four pixels puts the first switch inside the + * ring, where it can actually do something. + */ + readonly autoLod?: { + readonly fovY?: number; + readonly viewportHeight?: number; + readonly maxPixelError?: number; + }; + /** + * The GPU-driven main pass: one compute dispatch culls and LOD-selects every resident placement + * into a shared matrix buffer, and each main key draws its own region of it through an indirect + * record. The CPU keeps only coarse per-cell visibility and admits the complete resident + * `maxDistance` source set; a walking camera therefore costs no instance repack or cull/LOD + * replacement. An explicit `placementReach` remains an admission boundary because it has no GPU + * slot equivalent. + * + * `true` by default where the backend can run it: it needs compute, storage buffers and + * `drawIndexedIndirect`, and a backend without them falls back to exactly this class's CPU path — + * a lost saving, never a wrong picture. `gpuScene: false`, `TN_GPU_SCENE=0` or `?tnGpuScene=0` + * turns it off, and `stats().gpuScene` and the `TN_WORLD_GPU_SCENE` line say which path a run + * took. + */ + readonly gpuScene?: boolean; + /** + * Hold every dispatch's indirect args against the pure `cullAndSelect` reference and print the keys + * that disagree. `false` by default, and `TN_GPU_SCENE_VALIDATE=1` or `?tnGpuSceneValidate=1` turns + * it on. + * + * A readback is a queue submission and a mapped buffer, so this is a mode for answering "which key + * draws fewer instances than the CPU path" and never a walk to be measured with. + * For a fixed-step diagnostic walk, call {@link validateGpuFrame} after the render and await its + * promise before advancing the next frame; that opt-in fence checks every dispatch and replaces + * the periodic samples for that world. + */ + readonly gpuSceneValidate?: boolean; + /** + * Sample what the GPU actually selects in the main pass — a readback of the indirect args every + * 30 dispatches — and report it as `stats().gpuScene` and on the `TN_WORLD_GPU_SCENE` + * line. Off unless asked: the engine asks when the frame budget is on, and a validation turns it on + * by itself, so `TN_FRAME_BUDGET`'s `mainGpuTriangles` is the GPU-selected count and not the mesh + * capacity `tri=` reports. Set it explicitly when driving the world without a frame budget. + */ + readonly gpuSceneTally?: boolean; + /** + * Run the hierarchical-depth occlusion test in the cull kernel and report what it would cull. + * + * `"measure"` (the default unless `?tnOcclusion=off`, `TN_OCCLUSION=off` or a caller's own value + * says otherwise) builds a max-distance pyramid from the previous frame's scene-pass depth and + * counts the placements the test hides **while drawing all of them**: a measured frame is the frame + * an unmeasured run draws, so the only difference between the two runs is the count and the cost of + * building the pyramid. `"off"` runs no test and allocates nothing. `stats().gpuScene.occlusion` + * reports the counts, the pyramid's own cost, and `refused: no scene depth` when no render chain + * installed a pass to take a depth from. + * + * See {@link occlusionMode} and `render/depth-pyramid.ts`. + */ + readonly occlusion?: OcclusionMode; + /** + * Record every GPU-dressed main batch mesh and every resident cell's hand-placed chunks into + * `BundleGroup`s, and replay the records instead of re-walking three's per-object path for each + * draw. On by default: PRD-494 AC-2 measured the walking `draw` span at a 5.0 ms lower p95 than + * develop over 3 interleaved runs with bundles on and a picture a blind world gate called equal, + * which is the measurement the old opt-in default was decided without — so `bundles: false`, + * `?tnBundles=0` or `TN_BUNDLES=0` is the way back to the per-object path, and `stats().bundle` + * and the `TN_WORLD_BUNDLE` line say which way a run took and what asked for it. + * + * A record is an encoder, not a render pass, so only a draw that needs no live pass is recorded: + * a framebuffer reader, a transmissive impostor, a skinned or morphed mesh and a per-object hook + * all stay on the path they were already on. See `bundleSafe`. + * + * The render list inside a bundle is fixed when it is recorded, so a bundled mesh is never hidden: + * the dispatch draws zero instances for a key the camera cannot see, and an indirect draw of zero + * instances costs the GPU nothing. Toggling `visible` would force a re-record instead, and the + * shadow node's texel gate skips a bundled mesh for the same reason. + * + * A chunk has no dispatch behind it, so the two answers it needs are its own: its meshes are never + * culled by the record, because a mesh a record dropped is missing geometry the moment the camera + * turns towards it, and each cell records into its own group whose `visible` is the main cull's + * answer for that cell — the same coarse answer the scatter batches take, one cell wide. What that + * costs is the price of the option: a cell the cull cannot see is not drawn at all, which takes its + * chunk casters out of the shadow levels with it, and a cell it can only partly see draws the part + * facing away. Both rasterize nothing or nearly so; the missing shadow is the real difference. + */ + readonly bundles?: boolean; + /** + * Raise the LOD distance bias when the main pass is over its share of the frame's GPU budget, and + * decay it back toward 1 when it is comfortably under, default true. + * + * The main pass is triangle-bound: a flyover's near and mid tree levels are millions of triangles + * and the GPU time tracks that count. When `renderer.gpuMainMs()` — the smoothed main-pass GPU + * time — exceeds `mainGpuShare` of a 60 fps frame, every LOD switch is crossed earlier by + * multiplying the camera distance both selection paths compare by one shared multiplier, coarsening + * the same placements the GPU is already struggling to draw. It rises in bounded steps, never in a + * jump, so no level pops. `false` pins the bias at 1 for the whole run, which is byte-identical to + * the selection before this feature existed: `?tnAdaptiveLod=0`, `TN_ADAPTIVE_LOD=0` or + * `__tnAdaptiveLod = 0` also turns it off. + */ + readonly adaptiveLod?: boolean; + /** + * The fraction of a frame's GPU time the main pass may take before the adaptive LOD bias rises, + * `(0, 1]`, default 0.5. + * + * The budget is `mainGpuShare x min(deltaTime, 1/60 s)`: the frame cap keeps the budget honest the + * same way the shadow refresh gate does, because the frame that draws an expensive main pass is + * itself long and reading its own inflated delta would let the cost it judges pass its own test. + * `?tnMainGpuShare=0.1`, `TN_MAIN_GPU_SHARE=0.1` or `__tnMainGpuShare = 0.1` forces adaptation on a + * GPU that would otherwise never exceed the share. + */ + readonly mainGpuShare?: number; + /** + * Bake a whole-asset octahedral impostor for every alpha-cutout foliage asset and append it as the + * asset's terminal, two-triangle LOD. Default false: omission keeps the authored/generated source + * levels, and `?tnImpostors=1` or `TN_IMPOSTORS=1` opts a launch in. It is opt-in until the judged + * Machinefall capture passes (PRD-473 AC-3); `preserveAuthoredParts` and `lodHysteresis` still + * require it to be off. + * + * Set true to bake them, or leave it off to keep the source levels for a visual comparison. + * + * The bake runs inside the render cadence — one atlas view per `process` — so it costs a + * slice of a frame rather than a hitch, and it starts only after the asset's own model has been + * adopted. A seamless switch needs the impostor's atlas to cover the same cutout the source draws, + * so an asset whose levels carry no alpha-cutout part is never baked. `false` drops only the atlas + * terminal: every level the authored package and its baked chain give is kept, and the authored + * middle-level cutout coverage a level would otherwise be missing its foliage at is still applied. + */ + readonly impostors?: boolean; +} + +/** + * What the GPU actually selected in the main pass, from the scene's last landed tally. + * + * `triangles` is the GPU-selected count (`instanceCount x indexCount / 3` summed over the indirect + * records), never the mesh-capacity upper bound `renderer.info.render.triangles` reports, and it + * covers the main pass only: shadow passes are not tallied. `ageFrames` is how many dispatches have + * passed since those bytes were issued, so a caller never mistakes a stale sample for this frame's. + */ +export interface IWorldCellsGpuTally { + readonly instances: number; + readonly triangles: number; + readonly ageFrames: number; +} + +/** Local render coverage, independent of shader prewarm and distant admission. */ +export interface IWorldRegionReadiness { + readonly requiredCells: number; + readonly loadedCells: number; + readonly requiredTerrainTiles: number; + readonly loadedTerrainTiles: number; + readonly failures: number; + readonly cancelled: boolean; + readonly ready: boolean; +} + +/** One `whenSpawnReady` caller: a fixed region snapshot, its deadline and its promise sinks. */ +export interface ISpawnWaiter { + readonly position: { readonly x: number; readonly z: number }; + readonly radius: number; + readonly resolve: (readiness: IWorldRegionReadiness) => void; + readonly reject: (error: Error) => void; + readonly timer: ReturnType; +} + +export interface ICellBatch { + readonly asset: string; + /** + * The run these records came from. One cell can hold two runs of one canonical asset — two names + * for the same model — and each builds its own, so a refilter and a rebuild have to know which of + * the cell's entries belong to the run they are replacing. + */ + readonly run: IWorldRun; + readonly batch: InstancedBatch; + readonly level: number; + readonly part: number; + /** How far the follow point has to move before this batch is refiltered, `undefined` never. */ + readonly threshold: number | undefined; + /** The shared mesh this cell's instances are written into, and its segment there (-1: none). */ + shared: SharedBatch | undefined; + segment: number; + /** + * The GPU scene's source records this entry draws from, shared by every part of its level, and + * handed back by `#clearSegment`. `undefined` while the scene is off or the records are the + * dispatches' own already — a record released twice is a no-op, which is what lets one list be + * held by a whole level's parts. + */ + gpu: number[] | undefined; + /** + * The shadow-caster cluster this cell's records also go into, and its segment there (-1: none). + * The main mesh is one per key and casts nothing; the same records are written into one mesh per + * `(key, cluster)` on the caster layer, which is what a virtual-shadow level culls. + */ + caster: SharedBatch | undefined; + casterSegment: number; + /** + * The wide-caster half, and its segment there (-1: none): the same records again, in one mesh per + * key covering the whole ring, alone on the wide caster layer. A shadow level whose window covers + * the ring would otherwise submit one cluster per square for the same pixels. + */ + wide: SharedBatch | undefined; + wideSegment: number; + /** + * The root-matrix records this entry's level put in the far shadow, when the asset has an + * impostor: the placement transform alone, never `placement * part local`. The whole-asset impostor + * bakes every LOD0 part's own transform into its atlas, so its quad is drawn at the placement root + * and composing a source part's offset again would apply that part's transform twice. `undefined` + * for a non-impostor asset, whose wide half keeps the per-part source records. + */ + wideRoot: InstancedBatch | undefined; + /** + * The world bounds of the records this entry holds, from the filter that built it. The shadow + * invalidation is the union of these over the entries a swap moved in and out, which is the point + * of measuring them per record rather than per cell: a 48 m level's window is smaller than the + * cell box, so the cell box always covered it and always redrew it. + */ + shadowBounds: Box3 | undefined; + lastFilterY: number; + lastFilterX: number; + lastFilterZ: number; +} + +/** The geometry and material one batch draws; a part is one of these plus what it does not need. */ +export type IBatchShape = Pick; + +/** Which GPU-scene key one main batch draws: `asset:level:part`, and the part's place in the model. */ +export interface IKeyDescriptor { + readonly asset: string; + readonly key: string; + readonly level: number; + readonly part: number; +} + +/** One key a loaded asset's levels contributed to the prewarm queue. */ + +/** One asset's placements, waiting to be built, or already holding their own cell's records. */ +export interface IPrewarmEntry { + readonly asset: string; + /** + * Which pass this mesh is: the main pass's key, its caster cluster, its wide, or the shadow twin + * of the main key that a GPU-driven map draws instead of the two caster halves. + */ + readonly role: "cluster" | "key" | "main" | "wide"; + readonly geometry: BufferGeometry; + readonly key: string; + readonly level: number; + readonly material: Material; + /** The part of the level, which is the third half of a key's name. */ + readonly part: number; +} + +/** + * A plain `{ min, max }` region, the shape a shadow level's invalidation reads and the shape a game + * can be handed without importing three. Plain numbers, so the object a caller records its bounds + * in is the object that crosses the boundary. + */ +export interface IShadowRegion { + /** Only added casters; their redraw can retain the existing depth. */ + admission?: boolean; + min: { x: number; y: number; z: number }; + max: { x: number; y: number; z: number }; +} + +/** + * A camera, plus the one flag the exclusion rule reads. Three sets `isOrthographicCamera` on an + * `OrthographicCamera` at runtime and declares it on no base camera type, so it is duck-typed here + * the same way `ICasterScaleMesh` and `isInstancedMesh` are. + */ +export type ICamera = Camera & { readonly isOrthographicCamera?: boolean }; + +/** + * The `@x,z` half of a cluster key, for a cell index. `cellsPerCluster` squares of cells per + * cluster, so the answer is stable for as long as the grid is: placements never move, so a record is + * only ever clustered when the cell holding it is admitted or evicted. + */ +export function clusterOf(cellX: number, cellZ: number, cellsPerCluster: number): string { + return `${String(Math.floor(cellX / cellsPerCluster))},${String(Math.floor(cellZ / cellsPerCluster))}`; +} + +/** + * Whether any of `gates` — ascending — lies in `[low, high]`. One binary search for the first gate at + * or past `low`, against the walk of every gate a scan does per cell: a follow point that has not + * carried anything across a boundary is the common case, and the refilter runs it over every resident + * cell every 2 m of travel. Ends included, so a placement exactly on a gate is one of the reasons to + * rebuild. + */ +export function crossesGate(gates: readonly number[], low: number, high: number): boolean { + let start = 0; + let end = gates.length; + while (start < end) { + const middle = (start + end) >> 1; + if ((gates[middle] as number) < low) start = middle + 1; + else end = middle; + } + return start < gates.length && (gates[start] as number) <= high; +} + +export interface ICellProxyState { + readonly cell: IResidentCell; + readonly selector: CellProxySelection; + readonly retainedPaths: string[]; + pending: boolean; + compiling: boolean; + cancelled: boolean; + compiled: boolean; + active: boolean; + bytes: number; + model?: Object3D; + prepared?: IPreparedCellProxy; + readonly observed: Set; + readonly hidden: Map; + readonly mainMasks: Map; +} + +export interface IResidentCell { + proxy?: ICellProxyState; + readonly mainChunkMeshes: Mesh[]; + readonly chunkInstances: Map< + InstancedMesh, + { + readonly matrix: InstancedMesh["instanceMatrix"]; + readonly version: number; + readonly count: number; + } + >; + readonly completedRuns: Set; + chunksLoaded: boolean; + chunkFailed: boolean; + readonly key: string; + readonly x: number; + readonly z: number; + readonly cell: IWorldCell; + /** + * The union of the distances from every one of this cell's batches' own filter points to the cell's + * rectangle, widened as batches are built and never narrowed; see `#staleIn`. Monotone on purpose: + * too wide a range means a cell the gate test does not reject, and too narrow one would drop a + * rebuild the pass owes. + */ + filterNear: number; + filterFar: number; + readonly generation: number; + readonly instances: number; + readonly bytes: number; + readonly batches: ICellBatch[]; + readonly chunks: Object3D[]; + /** + * The exact `assets.model` cache paths this cell's chunks asked the loader for, held in + * `#modelPaths` and handed back once the last cell naming a path is gone, exactly like an asset's + * levels; see `#releaseModelPaths`. Empty for a cell with no chunks or a `loadModel` override. + */ + readonly retainedModelPaths: string[]; +} + +/** + * One cell's asset run waiting to be built, in units small enough to fit a frame's admission + * budget: a slice of placements, or one mesh built. + * + * A job outlives the `update` that queued it, so every field it needs to resume is on the job and + * not on the call stack — a streamed cell holds its residency slot while it waits, so the work is + * never spent on a cell that has since left. + */ +export interface IBuildJob { + readonly asset: IAssetState; + readonly cell: IResidentCell; + readonly run: IWorldRun; + /** The follow snapshot taken at first admission, held fixed across every placement slice. */ + filterY: number; + filterX: number; + filterZ: number; + /** A refilter's outgoing batches, drawn until this job's own batches are attached. */ + replaced: ICellBatch[]; + readonly replacement: boolean; + /** This job's built batches, attached together when the last one is ready. */ + readonly fresh: ICellBatch[]; + /** The run's placement records, read once and kept across the slices that filter them. */ + records: Float32Array | undefined; + /** Next placement to filter; the filter is complete when it reaches `run.count`. */ + next: number; + batches: InstancedBatch[][] | undefined; + /** + * One root-matrix batch per level the filter reached, filled only when the asset can become a + * whole-asset impostor: the placement transform with no part offset, which is what its far quad + * needs. `undefined` for every other asset, so nothing pays for records the wide half never reads. + */ + roots: (InstancedBatch | undefined)[] | undefined; + /** + * Bypass the refilter's identical-answer fast path. A handoff that changes what a wide segment must + * hold is not an identical answer even when the level and records are, so the rebuild has to swap. + */ + force: boolean; + /** One world-bounds box per `(level, part)` batch, filled by `#addPlacements` and read on swap. */ + boxes: Box3[][] | undefined; + /** How many of the (level, part) meshes have been built. */ + published: number; + /** + * The GPU scene's source records for the placements this filter kept, one per placement beside the + * level it reached — filled by `#addPlacements` only while the scene is on, and read on the swap. + */ + sources: IGpuSource[] | undefined; + /** The records placed per level, so every part of a level shares one list; see `#placeSources`. */ + gpuByLevel: Map | undefined; +} + +/** One placement as the GPU scene's source buffer holds it: the placement's own transform, no part. */ +export interface IGpuSource { + readonly level: number; + readonly matrix: Matrix4; + readonly x: number; + readonly y: number; + readonly z: number; + /** Half the diagonal of the asset's authored bounds at this placement's scale. */ + readonly radius: number; + /** The placement's uniform scale, carried so the dispatch can scale a whole-asset impostor gate. */ + readonly scale: number; +} + +/** One drawable part of one level, read out of the level's own GLB. */ +export interface IAssetPart { + readonly geometry: BufferGeometry; + /** The mesh's transform relative to the model root, composed into every instance matrix. */ + readonly local: Matrix4; + /** The surface the batch draws with: the shared surface for this part's material content. */ + readonly material: Material; + /** The `materialKey` of that surface, released through the shared-surface registry. */ + readonly surface: string; +} + +export function positiveInteger(value: number, name: string): number { + if (!Number.isInteger(value) || value < 1) + throw new Error(`WorldCells ${name} must be a positive integer.`); + return value; +} + +export function nonNegativeInteger(value: number, name: string): number { + if (!Number.isInteger(value) || value < 0) + throw new Error(`WorldCells ${name} must be a non-negative integer.`); + return value; +} + +/** A metre threshold, unlike the counts above: 1.5 m is the whole point of the option. */ +export function positiveMetres(value: number, name: string): number { + if (!Number.isFinite(value) || value <= 0) + throw new Error(`WorldCells ${name} must be a finite number greater than 0.`); + return value; +} + +/** + * The admission budget: a positive number of milliseconds, or `Infinity` for no ceiling at all — + * which is what a game that wants one `update` to admit everything it can asks for. Zero is refused + * rather than obeyed, because a world whose budget is spent before it starts never streams at all, + * and that is never what anyone meant to write. + */ +export function admissionBudgetMs(value: number): number { + if (Number.isNaN(value) || value <= 0) + throw new Error("WorldCells admissionBudgetMs must be a positive number of milliseconds."); + return value; +} + +export function cellKey(x: number, z: number): string { + return `${String(x)}:${String(z)}`; +} + +import type { IWorldGpuSceneReport } from "./world-gpu-scene.js"; + +export interface IWorldCellsStats { + readonly residentCells: number; + /** + * Placement bytes the resident cells hold. Admission adds them. Eviction removes them. + * Far-impostor and HLOD bytes sit outside this figure, in `impostor.far.bytes` and `hlod.bytes`. + * `budgets.bytes` caps the sum of all three. + */ + readonly residentBytes: number; + /** Resident cells whose initial placement runs and chunks have completed admission. */ + readonly loadedCells: number; + readonly residentKeys: readonly string[]; + /** Terrain tiles drawn, out to `terrain.streamRadius`; independent of the props' `ring`. */ + readonly residentTiles: number; + /** Tiles holding a `createCollider` body, inside `terrain.colliderRadius`; 0 with no factory. */ + readonly residentColliders: number; + /** Placement instances the resident cells hold, before any `maxDistance` filter. */ + readonly instances: number; + readonly loadsInFlight: number; + /** Model loads waiting for a free lane; served nearest-cell first, in admission order. */ + readonly loadsQueued: number; + /** + * Shared batches still to be minted, plus the ones minted and not yet drawn; `0` once + * `prewarmed` has resolved. + */ + readonly pendingPrewarm: number; + /** Shared batches minted by the prewarm so far, cumulative. */ + readonly prewarmMinted: number; + readonly evictions: number; + /** Cumulative `maxDistance`/`lods` refilters performed, across every update. */ + readonly rebuilds: number; + /** + * Cumulative refilter *passes* run, whether or not they queued a rebuild. The pass walks every + * resident cell, derives both distance brackets and sorts what it found, so this is what says + * whether a follow point standing inside the 2 m step paid for it; `rebuilds` only says whether it + * was worth anything. See `LEVEL_REFILTER_STEP_METRES`. + */ + readonly refilters: number; + readonly levelRefilters: number; + /** + * Cumulative stale cell-assets the refilter passes found, across every update. The pass walks every + * resident cell and gates it as a whole before it looks at one batch, so a follow point that has + * crossed nothing leaves this still — which is what says the walk cost brackets and comparisons and + * not allocations. + */ + readonly refilterEntries: number; + /** + * Cumulative refilters whose rebuild held the same records and were settled without a write, + * clear or compaction. A rising share of `rebuilds` is a follow point crossing a cell's distance + * bracket rather than any placement crossing a gate. + */ + readonly unchanged: number; + readonly failures: number; + /** + * What the main-pass cull cost, cumulative, and the evidence that a settled camera costs nothing. + * + * `frustums` is one a frame that had a perspective camera — it used to be one per main mesh, ~230 + * of them on a 2 km walk. `windows` is how many of those meshes had to re-derive their window at + * all, and `repacks` how many copied records: both are 0 on a frame where the visible squares did + * not change and no record joined or left, which is what the two integer comparisons in + * `SharedBatch.cullFrom` buy. + */ + readonly mainCull: { + readonly frustums: number; + readonly windows: number; + readonly repacks: number; + readonly visibleEpoch: number; + }; + /** + * What the GPU-driven main pass is doing, and why it is not: `on` is the answer, `reason` is the + * one line `TN_WORLD_GPU_SCENE` printed, and `dispatches` is what the walk actually paid for its + * per-instance work. `mainCull.repacks` and `refilters` stay at `0` while it is on — that is the + * CPU work it replaced, counted by the same counters that report it when it is off. `dressed` over + * `meshes` is the marker line's own pair, and it is the one that says the scene has meshes to draw + * with: an `on` with `0` dressed is a ring the CPU path is still drawing. + */ + readonly gpuScene: { + readonly on: boolean; + readonly reason: string; + readonly dispatches: number; + /** Resident source records: one per placement, shared by every part of the level it reached. */ + readonly instances: number; + readonly keys: number; + readonly dressed: number; + readonly meshes: number; + /** + * Instances and triangles the GPU actually selected in the main pass, and the age of that + * sample in dispatches. Absent until the first tally lands, never zero: the scene's own + * `instances` above is what it holds, and `gpuInstances` is what the GPU drew. + */ + readonly gpuInstances?: number; + readonly gpuTriangles?: number; + readonly gpuTallyAgeFrames?: number; + /** + * What the pyramid occlusion test would cull, when a launch asked it to measure. `share` is the + * would-cull share of the main pass's GPU-selected triangles from the same landed readback, which + * is the number the go/no-go multiplies by `gpuMain`; `reason` names why nothing was measured. + */ + readonly occlusion?: NonNullable; + }; + /** + * Main-pass bundle membership: `children` counts grouped roots — every recorded main mesh under one + * of the main record's shards, plus every chunk of every resident cell under that cell's own group — + * `cpuMeshes` and `gpuMeshes` count shared batch members, and `on` reports actual main/chunk + * membership. These are membership counts, not observed backend draws. `records` counts + * invalidations since load: membership, material or geometry changes and CPU window repacks + * invalidate; GPU indirect count updates do not. + * + * `eligibility` counts CPU material checks, snapshot validations, fresh structural walks and + * oversized or unsupported child output. Snapshots check own links and Three's child readers on + * every check, detecting deep edits without requiring `needsUpdate`; child discovery still allocates. + * + * `reason` is who asked: `default` is a run that set nothing, `option` is a load that named + * `bundles`, and `launch` is `?tnBundles` / `TN_BUNDLES` / `__tnBundles`. It is on the marker line + * too, because a run that turns the default off is exactly the run a reader cannot tell from a + * failing one without it. + */ + readonly bundle: { + readonly on: boolean; + readonly children: number; + readonly cpuMeshes: number; + readonly gpuMeshes: number; + readonly eligibility: { + readonly checks: number; + readonly refreshes: number; + readonly validations: number; + readonly uncached: number; + }; + readonly reason: string; + readonly records: number; + }; + /** + * The runtime impostor path: how many assets appended a terminal level, how many bakes are still + * queued or in flight, the triangles all terminal levels submit together — the number the whole + * feature exists to reduce — and the cache size: the logical colour atlas bytes held (each atlas's + * RGBA8 colour and normal mip chains), not every physical GPU buffer a render target may own, and + * the budget those bytes are enforced against. + */ + readonly impostor: { + readonly assets: number; + readonly pending: number; + readonly atlasBytes: number; + readonly budgetBytes: number; + readonly terminalTriangles: number; + /** + * The whole-map far aggregates: one `InstancedMesh` per atlas cache key, the ORIGINAL placements + * they hold (`instances`), how many of those the near ring has taken back (`nearOwned`), the + * physical instance bytes, and how many times an aggregate's buffer has been written. Missing + * measurements stay absent; a settled camera holds `uploads` still. + */ + readonly far: { + readonly aggregates: number; + readonly instances: number; + readonly live: number; + readonly nearOwned: number; + readonly bytes: number; + readonly uploads: number; + }; + }; + /** Cumulative rejected requests: cells skipped, instances or bytes refused, terrain retries. */ + /** Compilation and zero-count main backend submission are separate observations. These counts + * do not prove GPU execution, image quality, or reduced frame time. bytes includes pending + * reservation and decoded copies/textures, never a claim about physical driver allocation. */ + readonly hlod: { + readonly on: boolean; + readonly loaded: number; + readonly compiled: number; + readonly observed: number; + readonly active: number; + readonly pending: number; + readonly bytes: number; + readonly refused: number; + readonly reason: string; + readonly failures: number; + }; + readonly pressure: { readonly cells: number; readonly instances: number; readonly bytes: number }; + /** + * What the last `update` spent on admitting streamed content, and what it left behind. + * + * A rising `backlog` is the honest signal that admission is not keeping up with the follow point; + * it is the one number that says a player is outrunning the world. + */ + readonly admission: { + /** Milliseconds the last `update` spent admitting; at most the budget plus its last unit. */ + readonly spentMs: number; + /** Cell-asset builds the last `update` had no budget for, still queued for a later one. */ + readonly deferred: number; + /** Units of work those builds still owe — placement slices left plus meshes left to publish. */ + readonly backlog: number; + }; +} diff --git a/packages/core/src/world-cell-impostors.ts b/packages/core/src/world-cell-impostors.ts new file mode 100644 index 0000000000..c713a57e4b --- /dev/null +++ b/packages/core/src/world-cell-impostors.ts @@ -0,0 +1,1367 @@ +import { + type BufferGeometry, + InstancedBufferAttribute, + InstancedMesh, + type Material, + Matrix4, + type Object3D, + Quaternion, + Vector3, +} from "three"; +import { dropPooledFor } from "./render/mesh-pool.js"; +import { + IMPOSTOR_FAR_CULL_ATTRIBUTE, + WorldImpostorSurface, +} from "./render/world-impostor-surface.js"; +import { + type IImpostorPart, + type IImpostorRawRenderer, + IMPOSTOR_FRAME_PIXELS, + IMPOSTOR_VIEWS, + WORLD_IMPOSTOR_MARKER, + type WorldImpostorAtlas, + WorldImpostorBaker, + impostorBounds, +} from "./render/world-impostor.js"; +import type { IRendererLike } from "./renderer.js"; +import { levelTriangles } from "./world-cell-chunks.js"; +import { cellKey } from "./world-cell-data.js"; +import type { + IAssetPart, + IResidentCell, + IWorldCellsBudget, + IWorldCellsStats, +} from "./world-cell-data.js"; +import type { IAssetState } from "./world-cells.js"; +import { VIRTUAL_SHADOW_WIDE_CASTER_LAYER } from "./world-chunk-shadow.js"; +import { + type IWorldAsset, + type IWorldCell, + type IWorldPackage, + type IWorldRun, + cellPlacements, +} from "./world-package.js"; + +/** + * GPU bytes of completed impostor atlases one world admits, 128 MiB. A 128 px two-attachment RGBA8 + * atlas with mips is 2,796,160 B, so 48 fit and the 49th is refused before its atlas is allocated, + * keeping its source LODs. An atlas a live asset still draws from is never evicted, so the active + * record count is a hard admission cap rather than a target the cache trims toward — eviction only + * ever drops an inactive record to make room for a new one. + */ +const DEFAULT_IMPOSTOR_ATLAS_BUDGET_BYTES = 128 * 1024 * 1024; + +/** + * The declared byte cost of one atlas at the bake's fixed frame, the same numbers + * `WorldImpostorAtlas.bytes` reports: both RGBA8 attachments' mip chains, the logical colour atlas + * cost rather than every physical GPU buffer a render target holds. Computed here so the cache + * budget is enforced BEFORE `baker.begin` allocates the atlas, never after. + */ +const IMPOSTOR_ATLAS_BYTES = ((): number => { + const levels = Math.floor(Math.log2(Math.max(1, IMPOSTOR_FRAME_PIXELS))) + 1; + let bytes = 0; + for (let level = 0; level < levels; level += 1) { + const edge = Math.max(1, IMPOSTOR_FRAME_PIXELS >> level); + bytes += edge * edge * IMPOSTOR_VIEWS * 4; + } + return 2 * bytes; +})(); + +/** + * The far-residency diagnostic marker: one bounded line per whole-map aggregate built, naming the one + * `InstancedMesh` per atlas cache key, its original placements, the live/near-owned split, its + * physical instance bytes and how many times its instance buffer has been written. + */ +const WORLD_IMPOSTOR_FAR_MARKER = "TN_WORLD_IMPOSTOR_FAR"; + +/** + * Bytes one far instance costs: its sixteen-float32 matrix plus the one-float per-instance cull the + * shared far surface reads. Both are physical allocations the budget bounds; the cull is uploaded + * once because the placement never moves. + */ +const FAR_INSTANCE_BYTES = 16 * 4 + 4; + +/** + * One completed whole-asset atlas, cached by its content key and its source cutout contract. + * + * Bounded, not a generic cache: the atlas is the one GPU resource a later far reference may want to + * hold without keeping the full source GLB, and the CPU `center`/`radius` are the two scalars the + * surface needs to project it. `users` counts the live assets drawing it; a record with no user is + * inactive and evictable once the byte budget is over, and one with a user is never evicted. + */ +interface IImpostorRecord { + readonly key: string; + readonly atlas: WorldImpostorAtlas; + readonly center: Vector3; + readonly radius: number; + readonly bytes: number; + users: number; +} + +/** The terminal LOD one asset appended once its bake landed; owned and released with the asset. */ +export interface IImpostorState { + readonly part: IAssetPart; + readonly surface: WorldImpostorSurface; + readonly record: IImpostorRecord; +} + +/** + * One `(cell, run)`'s slice of a whole-map far aggregate. + * + * The records are the run's ORIGINAL placement roots, composed once when the aggregate is built and + * never rewritten while the camera moves. `live` is false while the near ring owns the run, which is + * the atomic handoff: a run is drawn by exactly one of the two, never both and never neither. + */ +interface IFarSegment { + readonly cell: IWorldCell; + readonly run: IWorldRun; + /** First instance record in the aggregate's buffer, and how many the run holds. */ + readonly start: number; + readonly count: number; + /** This run's own view-distance gate, `Infinity` when its asset authored no `maxDistance`. */ + readonly cull: number; + live: boolean; +} + +/** + * One whole-map far aggregate per exact atlas cache key: a single `InstancedMesh` holding every + * ORIGINAL placement root of every canonical asset that bakes to that key, independent of the near + * full-geometry ring and of the near source GLBs. + * + * The surface is the aggregate's own, built from the record's atlas and the first source material + * that baked to it, so disposing a near asset's surface cannot invalidate the far mesh. The atlas is + * borrowed: `record.users` is incremented once for the aggregate and held until the world disposes, + * which is what stops `#evictImpostors` dropping an atlas the far mesh still draws. + */ +interface IFarAggregate { + readonly key: string; + readonly surface: WorldImpostorSurface; + readonly record: IImpostorRecord; + mesh: InstancedMesh; + capacity: number; + /** Every original placement root the aggregate holds, live and near-owned. */ + readonly segments: Map; + /** How many times the instance buffer has been written, for the settled-frame check. */ + uploads: number; +} + +/** + * The metadata a refused far cohort is kept as, without its source GLB. + * + * A hard budget refusal releases the asset's geometry and materials the way any release does; the + * atlas is pinned by one borrowed `users` and the aggregate's own surface is built once here, so a + * later capacity release retries from this alone — no full model is held alive for a cohort that may + * never fit. See `#deferFar`, `#retryFar`. + */ +interface IFarCohort { + readonly id: string; + readonly key: string; + readonly record: IImpostorRecord; + /** The alpha-foliage source material to twin; absent once a refused cohort has its surface built. */ + readonly source?: Material; + readonly alphaTest: number; + readonly cull: number; + readonly runs: ReadonlyArray<{ cell: IWorldCell; run: IWorldRun }>; +} + +/** + * A refused far cohort kept without its source GLB: metadata plus the surface already built from the + * source at refusal. No material, geometry or texture of the model survives here — only the surface, + * which cleared its UV maps and borrows the pinned atlas. + */ +interface IFarRetryEntry { + readonly id: string; + readonly key: string; + readonly record: IImpostorRecord; + readonly alphaTest: number; + readonly cull: number; + readonly runs: ReadonlyArray<{ cell: IWorldCell; run: IWorldRun }>; + readonly surface: WorldImpostorSurface; +} + +/** + * The raw renderer seam the impostor bake needs, or `undefined` when this backend does not expose it. + * + * `IRendererLike.raw` is `unknown` on purpose: it is whatever the running renderer is, and a backend + * without layered render targets has no bake seam at all. The shape is checked rather than cast, so a + * stub whose raw is not a renderer leaves the bake un-run instead of throwing inside a frame. + * + * A WebGL renderer is the reason the three-method shape alone is not enough: it carries `render`, + * `setRenderTarget` and `initRenderTarget` and would pass, then fail mid-frame on the layered MRT + * capture it cannot run. So the backend must declare itself WebGPU (`kind` and the renderer's own + * `isWebGPURenderer`), which a WebGL fallback and an unsupported native backend do not, and the full + * getter/setter seam the baker calls must be present before the cast — the ones the baker reaches + * with `?.` still have to exist on a real backend, and a raw object missing them would be half-run, + * not skipped cleanly. + */ +const REQUIRED_IMPOSTOR_METHODS = [ + "getActiveCubeFace", + "getActiveMipmapLevel", + "getClearAlpha", + "getClearColor", + "getMRT", + "getRenderTarget", + "initRenderTarget", + "render", + "setClearAlpha", + "setClearColor", + "setMRT", + "setRenderTarget", +] as const; + +function impostorRawRenderer( + renderer: IRendererLike | undefined, +): IImpostorRawRenderer | undefined { + if (renderer === undefined || renderer.kind !== "webgpu") return undefined; + const raw = renderer.raw as + | (Partial & { isWebGPURenderer?: boolean }) + | null; + if (raw === null || raw === undefined) return undefined; + if (raw.isWebGPURenderer !== true) return undefined; + // A `WebGPURenderer` can wrap the WebGL fallback backend and still report `kind: 'webgpu'` and + // `isWebGPURenderer === true`; that backend cannot run a layered MRT capture, so the method shape + // alone would accept it and fail mid-frame. The backend it actually wrapped is the honest answer. + if ((raw as { backend?: { isWebGLBackend?: boolean } }).backend?.isWebGLBackend === true) + return undefined; + for (const method of REQUIRED_IMPOSTOR_METHODS) + if (typeof raw[method] !== "function") return undefined; + return raw as IImpostorRawRenderer; +} + +/** The bake's view of one asset level: each part's geometry, transform and surface, by reference. */ +function impostorParts(parts: readonly IAssetPart[]): IImpostorPart[] { + const staged: IImpostorPart[] = []; + for (const part of parts) + staged.push({ geometry: part.geometry, local: part.local, material: part.material }); + return staged; +} + +/** + * The alpha threshold a cutout gets when the part's own material names none. ponytail: a fixed + * 0.5, because the cutout point is authored data a GLB carries per texture and nothing reads it + * here. A tree whose needles visibly dither at 0.5 wants a lower one — give + * `transparentScatter` a third mode carrying a threshold when that is worth a knob. + */ +export const DEFAULT_CUTOUT_ALPHA = 0.5; + +/** The one test that separates an alpha-cutout foliage part from an opaque one: MASK or BLEND. */ +export function isAlphaFoliage(material: Material): boolean { + return material.transparent || material.alphaTest > 0; +} +export interface ICellImpostorsHost { + released(): boolean; + renderer(): IRendererLike | undefined; + asset(id: string): IAssetState | undefined; + hasAsset(id: string): boolean; + assets(): Iterable; + nearWorkPending(): boolean; + newAsset(id: string, definition: IWorldAsset): IAssetState; + startAssetLoad(asset: IAssetState): void; + release(id: string): void; + noteGate(gate: number): void; + retargetWideCasters(asset: IAssetState): void; + terminalLevelReady(asset: IAssetState): void; + residentCell(key: string): IResidentCell | undefined; + canonical(id: string): string; + shadowRecordsMoved(): void; + residency(): { readonly instances: number; readonly bytes: number; readonly hlodBytes: number }; + add(object: Object3D): void; + remove(object: Object3D): void; + addFailure(): void; + markReleasedPart(part: IAssetPart): void; +} +export interface ICellImpostorsSettings { + readonly enabled: boolean; + readonly receiveShadow: boolean; + readonly candidates: readonly string[]; + readonly cells: readonly IWorldCell[]; + readonly manifest: IWorldPackage; + readonly placements: ArrayBuffer; + readonly transparentScatter: "cutout" | "blend"; + readonly autoLodPixelsPerUnit: number; + readonly castShadowLevels: number; + readonly budgets: IWorldCellsBudget; +} +export class CellImpostors { + readonly #host: ICellImpostorsHost; + + readonly #cells: readonly IWorldCell[]; + + readonly #manifest: IWorldPackage; + + readonly #placements: ArrayBuffer; + + readonly #transparentScatter: "cutout" | "blend"; + + /** + * Pixels per world unit at unit depth, the scale a baked chain's level errors are divided by to + * reach a switch distance. See `IWorldCellsLoadOptions.autoLod` and {@link chainLevels}. + */ + readonly #autoLodPixelsPerUnit: number; + + /** How many of an asset's finest levels cast shadows; 0 when the game asked for none. */ + readonly #castShadowLevels: number; + + readonly #budgets: IWorldCellsBudget; + + readonly #position = new Vector3(); + + readonly #rotation = new Quaternion(); + + readonly #scale = new Vector3(); + + readonly #matrix = new Matrix4(); + + /** Whether alpha-foliage assets append a baked whole-asset terminal level; see the option. */ + readonly #impostors: boolean; + readonly #receiveShadow: boolean; + + /** The baker one view per render-cadence update; see `#stepImpostor`. */ + readonly #baker = new WorldImpostorBaker(); + + /** Assets whose bake is queued, in adoption order; `#impostorQueued` is the same set by id. */ + readonly #impostorQueue: IAssetState[] = []; + + readonly #impostorQueued = new Set(); + + /** The asset whose bake holds the baker right now, if any. */ + #impostorBaking: IAssetState | undefined; + + /** + * Completed atlases by content key. An atlas a live asset draws from is active and never evicted; + * one with no user is kept for a later reference until the byte budget needs the room. + */ + readonly #impostorRecords = new Map(); + + #impostorRecordBytes = 0; + + /** The declared bytes of the one bake in flight, held against the budget before `begin`. */ + #impostorReservedBytes = 0; + + readonly #impostorBudgetBytes: number; + + /** Assets whose `TN_WORLD_IMPOSTOR` line has printed, so the marker is one per asset. */ + readonly #impostorReported = new Set(); + + /** + * Assets whose pre-bake wide segments are still waiting for a block in the one whole-asset mesh, + * because the frame that handed the bake over had spent its fresh-mesh allowance. Retried on the + * next update rather than left with no far shadow. + */ + readonly #wideRetry = new Set(); + + /** + * The whole-map far aggregates, one per exact atlas cache key, and the `run -> aggregate` index a + * near-ready handoff and an eviction read to toggle a run's far segment without asking the asset. + * + * `#farInstances` and `#farBytes` are the physical allocations those meshes hold, charged against + * the world's own instance and byte budgets beside the near ring's records. + */ + readonly #far = new Map(); + + readonly #farSegments = new Map(); + + #farInstances = 0; + + #farBytes = 0; + + /** Atlas keys whose `TN_WORLD_IMPOSTOR_FAR` line has printed, so the marker is one per aggregate. */ + readonly #farReported = new Set(); + + /** + * Cohorts whose far aggregate a full budget refused, as metadata plus a built surface and never + * the source GLB. Retried when a near cell releases its allocation — an event, not a per-frame + * scan — so a walk away from the ring makes room and the refused species appears without holding + * a full model alive; see {@link IFarRetryEntry}, `#retryFar` and `#deferFar`. + */ + readonly #farRetry = new Map(); + + /** + * Canonical asset ids placed anywhere in the map, adopted on idle frames so a species that never + * enters the near ring still bakes its atlas and appears on the far mesh. Enumerated once from the + * static placements; see `#pumpFarAcquisition`. + */ + readonly #farCandidateIds: readonly string[]; + + /** The rotating cursor over `#farCandidateIds`, so the map is walked a bounded step per idle frame. */ + #farCursor = 0; + + /** Canonical ids whose far handling has settled: baked and built, or classified with no atlas. */ + readonly #farSeen = new Set(); + + /** Canonical ids acquired for the far half alone, held until their aggregate lands or is refused. */ + readonly #farTemp = new Set(); + + /** Every candidate is settled, so the idle pump can stop scanning the map; see `#pumpFarAcquisition`. */ + #farExhausted = false; + constructor(settings: ICellImpostorsSettings, host: ICellImpostorsHost) { + this.#host = host; + this.#impostors = settings.enabled; + this.#receiveShadow = settings.receiveShadow; + this.#farCandidateIds = settings.candidates; + this.#impostorBudgetBytes = DEFAULT_IMPOSTOR_ATLAS_BUDGET_BYTES; + this.#cells = settings.cells; + this.#manifest = settings.manifest; + this.#placements = settings.placements; + this.#transparentScatter = settings.transparentScatter; + this.#autoLodPixelsPerUnit = settings.autoLodPixelsPerUnit; + this.#castShadowLevels = settings.castShadowLevels; + this.#budgets = settings.budgets; + } + get farBytes(): number { + return this.#farBytes; + } + get farInstances(): number { + return this.#farInstances; + } + hasFarTemp(id: string): boolean { + return this.#farTemp.has(id); + } + hasQueued(id: string): boolean { + return this.#impostorQueued.has(id); + } + deferWide(id: string): void { + this.#wideRetry.add(id); + } + + /** + * Whether this asset can append a whole-asset impostor, so its builds keep the placement-root + * records the far quad draws from. The same test `#maybeQueueImpostor` makes, minus the cache: an + * asset with no alpha-cutout LOD0 part never bakes, so it never needs the extra records. + */ + rootsNeeded(asset: IAssetState): boolean { + if (this.#impostors === false) return false; + const parts = asset.levels[0]; + return parts !== undefined && this.#impostorSource(parts) !== undefined; + } + + /** + * The first LOD0 part that is alpha foliage, and the cutoff its own material authored — or + * `undefined` when no part is. A BLEND part carries `alphaTest 0`, and the scatter path draws it + * through the same cutout a MASK part gets, so the contract records that default rather than a + * zero that would bake a solid impostor. + */ + #impostorSource( + parts: readonly IAssetPart[], + ): { readonly source: Material; readonly alphaTest: number } | undefined { + for (const part of parts) { + const material = part.material; + if (!isAlphaFoliage(material)) continue; + return { + source: material, + alphaTest: material.alphaTest > 0 ? material.alphaTest : DEFAULT_CUTOUT_ALPHA, + }; + } + return undefined; + } + + /** + * The content key one impostor atlas is cached under: the loader's resolved cooked url of LOD0, + * plus every LOD0 source part's surface/content key and its exact cutout contract. Two package + * entries that fetch the same cooked model and draw the same parts with the same cutoffs are one + * bake, which is the canonical-asset idea applied to the atlas rather than to the levels — the + * package's own `lods`/`maxDistance` metadata may have stopped the two entries aliasing, but the + * atlas is built from LOD0 alone, so it must be shared anyway. + * + * The cutoff is the exact number the bake uses, never a rounded one: `0.42` and `0.420001` are + * different silhouettes and must not collide in the cache. + */ + #impostorKey(asset: IAssetState): string { + const parts = asset.levels[0] ?? []; + const contract: string[] = []; + for (const part of parts) { + const cutoff = isAlphaFoliage(part.material) + ? part.material.alphaTest > 0 + ? part.material.alphaTest + : DEFAULT_CUTOUT_ALPHA + : 0; + contract.push(`${part.surface}@${String(cutoff)}`); + } + return `${asset.resolvedGlb}|${this.#transparentScatter}|${contract.join(",")}`; + } + + /** Mark a cached atlas touched, so the least recently used inactive record is the one evicted. */ + #touchImpostor(key: string, record: IImpostorRecord): void { + this.#impostorRecords.delete(key); + this.#impostorRecords.set(key, record); + } + + /** + * Queue one adopted asset's whole-asset impostor, or append it at once from a cached atlas. + * + * Called after `#widenWithChain` and before the prewarm, so the terminal level is known to the same + * prewarm and rebuild machinery every adoption uses. An asset with no alpha-cutout LOD0 part is + * never baked: a solid impostor is not the thing it replaces. + */ + maybeQueueImpostor(asset: IAssetState): void { + if (this.#impostors === false || asset.impostor !== undefined) return; + const parts = asset.levels[0]; + if (parts === undefined) return; + const source = this.#impostorSource(parts); + if (source === undefined) return; + const key = this.#impostorKey(asset); + const cached = this.#impostorRecords.get(key); + if (cached !== undefined) { + this.#touchImpostor(key, cached); + this.#finishImpostor(asset, cached); + return; + } + if (this.#impostorQueued.has(asset.id)) return; + this.#impostorQueued.add(asset.id); + this.#impostorQueue.push(asset); + } + + /** + * One view of the bake per render-cadence update, then the terminal level when it completes. + * + * The renderer is asked for its raw seam and the bake waits for one that can run it: a world that + * has no renderer yet — or a backend whose raw seam is not the layered-target API — simply does + * not advance, and nothing downstream of the bake is owed until it does. No landmark is already + * waited on, so a bake that never runs never deadlocks a loading screen. + */ + stepImpostor(renderer: IRendererLike | undefined): void { + if (this.#impostors === false || this.#host.released()) return; + // A renderer that exists but carries no layered bake seam is a real answer, not a delay. Waiting + // on it would leave every queued asset pending and its atlas bytes reserved for the world's whole + // life, so the bake is cancelled, the reservation and queue dropped, and each asset reported: + // the source LODs stay, which is the documented fallback. A world with no renderer yet keeps + // waiting — that is the loading-screen case the wait exists for. + const current = renderer ?? this.#host.renderer(); + if (current !== undefined && impostorRawRenderer(current) === undefined) { + this.#dropUnsupportedImpostors(); + return; + } + if (this.#impostorBaking === undefined) { + while (this.#impostorQueue.length > 0) { + const asset = this.#impostorQueue.shift() as IAssetState; + this.#impostorQueued.delete(asset.id); + if (this.#host.asset(asset.id) !== asset || asset.disposed || asset.impostor !== undefined) + continue; + const parts = asset.levels[0]; + if (parts === undefined || this.#impostorSource(parts) === undefined) continue; + // Re-check the completed cache when DEQUEUED, not only when queued: two entries whose bake + // was queued before either finished share one atlas instead of baking it twice. + const key = this.#impostorKey(asset); + const cached = this.#impostorRecords.get(key); + if (cached !== undefined) { + this.#touchImpostor(key, cached); + this.#finishImpostor(asset, cached); + continue; + } + // Enforce the declared colour+mip budget BEFORE the atlas is allocated, and never at the + // cost of an atlas a live asset draws from: inactive LRU records make room first. + if ( + this.#impostorRecordBytes + this.#impostorReservedBytes + IMPOSTOR_ATLAS_BYTES > + this.#impostorBudgetBytes + ) + this.#evictImpostors(IMPOSTOR_ATLAS_BYTES); + if (this.#impostorRecordBytes + IMPOSTOR_ATLAS_BYTES > this.#impostorBudgetBytes) { + // Out of room: keep the source LODs and say why, rather than overrun the budget with one + // more atlas or evict one still in use. + this.#reportImpostor(asset, "budget"); + // A far-only species fetched for the far half alone has no near run to fall back on, and + // nothing here retries an atlas-budget refusal, so keeping its source GLB alive buys + // nothing. Release it; the near path re-acquires it normally if the follow point reaches it. + if (this.#farTemp.has(asset.id)) this.releaseFarTemp(asset.id); + continue; + } + const staged: IImpostorPart[] = impostorParts(parts); + try { + this.#baker.begin(asset.id, staged, { pixels: IMPOSTOR_FRAME_PIXELS }); + } catch { + this.#host.addFailure(); + this.#reportImpostor(asset, "begin-failed"); + continue; + } + this.#impostorReservedBytes = IMPOSTOR_ATLAS_BYTES; + this.#impostorBaking = asset; + break; + } + } + const baking = this.#impostorBaking; + if (baking === undefined) return; + const raw = impostorRawRenderer(renderer); + if (raw === undefined) return; + let atlas: WorldImpostorAtlas | undefined; + try { + atlas = this.#baker.step(raw); + } catch { + this.#host.addFailure(); + this.#impostorBaking = undefined; + this.#impostorReservedBytes = 0; + this.#reportImpostor(baking, "bake-failed"); + return; + } + if (atlas === undefined) return; + this.#impostorBaking = undefined; + this.#adoptImpostorAtlas(baking, atlas); + } + + /** + * Drop every queued and in-flight bake because the renderer cannot run the layered capture, and + * report each asset once. The reservation is released with the abort, so a later supported renderer + * (a device swap, a backend that only comes up after a frame) can still bake: the queue is empty, + * but a fresh adoption re-queues, and an asset already refused here is left with its source LODs. + */ + #dropUnsupportedImpostors(): void { + if (this.#impostorQueue.length === 0 && this.#impostorBaking === undefined) return; + const dropped: IAssetState[] = []; + if (this.#impostorBaking !== undefined) { + dropped.push(this.#impostorBaking); + this.#reportImpostor(this.#impostorBaking, "unsupported"); + this.#baker.abort(); + this.#impostorBaking = undefined; + this.#impostorReservedBytes = 0; + } + const queued = this.#impostorQueue.splice(0, this.#impostorQueue.length); + this.#impostorQueued.clear(); + dropped.push(...queued); + for (const asset of queued) this.#reportImpostor(asset, "unsupported"); + // A far-only species cannot bake on a backend that has none, so it must not keep holding the + // source it fetched for a bake that will never land. Its atlas cache key stays for a later backend. + for (const asset of dropped) if (this.#farTemp.has(asset.id)) this.releaseFarTemp(asset.id); + } + + /** Store a finished atlas and hand the asset its terminal level. */ #adoptImpostorAtlas( + asset: IAssetState, + atlas: WorldImpostorAtlas, + ): void { + this.#impostorReservedBytes = 0; + const parts = asset.levels[0]; + const source = parts === undefined ? undefined : this.#impostorSource(parts); + if (parts === undefined || source === undefined || asset.impostor !== undefined) { + atlas.dispose(); + return; + } + const key = this.#impostorKey(asset); + const existing = this.#impostorRecords.get(key); + if (existing !== undefined) { + atlas.dispose(); + this.#touchImpostor(key, existing); + this.#finishImpostor(asset, existing); + return; + } + const staged: IImpostorPart[] = impostorParts(parts); + const bounds = impostorBounds(staged); + const record: IImpostorRecord = { + atlas, + bytes: atlas.bytes, + center: bounds.center, + key, + radius: bounds.radius, + users: 0, + }; + this.#impostorRecords.set(key, record); + this.#impostorRecordBytes += record.bytes; + // Acquire the active user BEFORE any eviction, so the fresh record cannot be the eviction + // victim and `#finishImpostor` never builds a surface that borrows a disposed atlas. Admission + // already reserved this record's bytes, so the trim below only drops older inactive records. + this.#finishImpostor(asset, record); + this.#evictImpostors(); + } + + /** + * Append the whole-asset terminal level: one part, identity local, two triangles, and a switch at + * the projection the 128 px atlas sphere reaches — never a fixed metre, and strictly beyond the + * last authored or chain gate so `levelAt` cannot pick it before the level that gate named. + * + * The surface's own geometry and material are the part's, so the level is a normal level to every + * consumer downstream: the prewarm mints its keys, the swap rebuilds the resident runs, and the + * caster halves read it through `#shapeFor` like any other. The atlas is borrowed and released by + * the record; see `#releaseImpostor`. + */ + #finishImpostor(asset: IAssetState, record: IImpostorRecord): void { + if (asset.impostor !== undefined || asset.disposed) return; + if (this.#host.asset(asset.id) !== asset) return; + const parts = asset.levels[0]; + if (parts === undefined) return; + const source = this.#impostorSource(parts); + if (source === undefined) return; + let surface: WorldImpostorSurface; + try { + surface = new WorldImpostorSurface({ + alphaTest: source.alphaTest, + atlas: record.atlas, + center: record.center, + radius: record.radius, + source: source.source, + }); + } catch { + this.#host.addFailure(); + this.#reportImpostor(asset, "surface-failed"); + return; + } + // The terminal gate is stored as the BASE-sphere projected distance — the distance at which the + // asset's own LOD0 sphere, at scale 1, covers the 128 px atlas — and the placement's own scale is + // applied per placement at selection (`levelAtGates`). A placement scaled 8-12x therefore reaches + // the atlas 8-12x as far instead of switching at the unit-scale distance. There is no fixed + // margin: `levelAtGates` floors the terminal one representable Float32 step past the last source + // gate, so the source level keeps a window while the ordering survives the f32 store. + const switchAt = (2 * record.radius * this.#autoLodPixelsPerUnit) / IMPOSTOR_FRAME_PIXELS; + if (!Number.isFinite(switchAt) || switchAt <= 0) { + surface.dispose(); + return; + } + const part: IAssetPart = { + geometry: surface.geometry, + local: new Matrix4(), + material: surface.material, + surface: "", + }; + asset.levels = [...asset.levels, [part]]; + asset.distances = [...asset.distances, switchAt]; + asset.gates = [...asset.gates, switchAt]; + this.#host.noteGate(switchAt); + asset.threshold = asset.gates.length === 0 ? undefined : Math.min(...asset.gates); + asset.impostor = { part, record, surface }; + record.users += 1; + // The whole-map far aggregate for this atlas key, before the rebuild loop below: its segments + // exist when the swaps land, and any run already near-ready is handed over immediately. A budget + // refusal keeps only the far cohort's metadata and surface in `#farRetry`; see `#buildFar`. + this.#buildFar(asset, record); + this.#reportImpostor(asset, "ready"); + // A far-only asset has no near batches or casters to hand over; once its aggregate is up its + // temporary source has done its job and goes, while the atlas the aggregate borrows stays. A + // refusal releases the source too — the deferred cohort in `#farRetry` holds only metadata and a + // surface, never the GLB — so a far species that cannot fit is not kept alive wholesale. + if (this.#farTemp.has(asset.id)) { + this.releaseFarTemp(asset.id); + return; + } + // The far casters of the levels that were already built: part 0's key is re-pointed at the one + // whole-asset shape, and the per-part keys the terminal level has no counterpart for are dropped, + // so the far shadow draws one quad per placement rather than a duplicate per bark/needle part. + this.#host.retargetWideCasters(asset); + // The terminal level's keys and every resident run of this asset, through the same prewarm and + // swap the first build took: no active job array is mutated halfway. + this.#host.terminalLevelReady(asset); + } + + /** + * The whole-map far aggregate for one exact atlas cache key: every ORIGINAL placement root of every + * canonical asset that bakes to that key, in one `InstancedMesh`, independent of the near ring and + * of the near source GLBs. + * + * One aggregate per EXACT atlas key, never one per `maxDistance`: a shared atlas can back assets + * with different authored cutoffs, and each placement's own value rides the instance buffer (see + * {@link IFarSegment.cull}) rather than splitting the key into a second aggregate — the exact key + * is what the atlas cache already bounds, and a split would build a second surface for one atlas. + * The first asset to bake a key builds the aggregate's own surface and instance buffer; a later + * canonical asset that shares the key appends its own runs to the same mesh, growing the buffer at + * most once per asset. A key already carrying every one of this asset's runs is left untouched, so + * re-adopting an asset from the cached atlas after its near source was released rebuilds nothing. + * Never `part.local`: the atlas bakes each part's own transform, so the far quad is the placement + * root alone. + */ + #buildFar(asset: IAssetState, record: IImpostorRecord): boolean { + const parts = asset.levels[0]; + const source = parts === undefined ? undefined : this.#impostorSource(parts); + if (parts === undefined || source === undefined) return false; + // Every run this asset owns anywhere in the map: the far half covers the whole map, not the ring. + const runs = this.#farRuns(asset.id); + if (runs.length === 0) return true; + return this.#buildFarCohort({ + alphaTest: source.alphaTest, + cull: cullDistance(asset.definition.maxDistance) ?? Number.POSITIVE_INFINITY, + id: asset.id, + key: record.key, + record, + runs, + source: source.source, + }); + } + + /** Every run of one canonical asset anywhere in the map, the whole set a far aggregate holds. */ + #farRuns(id: string): Array<{ cell: IWorldCell; run: IWorldRun }> { + const runs: Array<{ cell: IWorldCell; run: IWorldRun }> = []; + for (const held of this.#cells) + for (const run of held.runs) { + if (this.#host.canonical(run.asset) !== id || run.count <= 0) continue; + runs.push({ cell: held, run }); + } + return runs; + } + + /** + * Build or grow one far cohort's aggregate, or defer it against the hard budget. + * + * The budget check counts the growth transient, not the net new count: a grow allocates the new + * larger buffer while the old one is still alive, so the peak is the new buffer's size — the old + * buffer is already charged in `#farBytes`. See {@link #farBudgetRefuses}. A refusal is remembered + * as metadata plus a built surface, never the source GLB; see {@link #deferFar}. + */ + #buildFarCohort(cohort: IFarCohort): boolean { + const key = cohort.key; + const aggregate = this.#far.get(key); + const missing = + aggregate === undefined + ? cohort.runs + : cohort.runs.filter((entry) => !(aggregate as IFarAggregate).segments.has(entry.run)); + if (missing.length === 0) { + this.#dropFarRetry(cohort.id); + return true; + } + const extra = missing.reduce((total, entry) => total + entry.run.count, 0); + const peak = aggregate === undefined ? extra : (aggregate as IFarAggregate).capacity + extra; + if (this.#farBudgetRefuses(peak)) { + // Hard finite budget: say why rather than overrun it, and keep only the metadata the retry + // needs. Never the full source GLB; see `#deferFar`. + this.#reportFarBudget(key, cohort.id, extra); + this.#deferFar(cohort); + return false; + } + let built = aggregate as IFarAggregate | undefined; + const deferred = this.#farRetry.get(cohort.id); + // The surface actually drawn by the aggregate after this call. A new aggregate adopts the + // deferred surface; a grow reuses the aggregate's own and leaves the deferred one unused. + let usedSurface: WorldImpostorSurface | undefined; + if (built === undefined) { + usedSurface = deferred?.surface ?? this.#makeFarSurface(cohort); + if (usedSurface === undefined) return false; + built = { + capacity: extra, + key, + mesh: this.#newFarMesh(usedSurface, extra, cohort.id), + record: cohort.record, + segments: new Map(), + surface: usedSurface, + uploads: 0, + }; + cohort.record.users += 1; + this.#far.set(key, built); + this.#farInstances += extra; + this.#farBytes += extra * FAR_INSTANCE_BYTES; + } else { + this.#growFar(built, extra); + } + this.#dropFarRetry(cohort.id, usedSurface); + let at = this.#farAllocated(built); + for (const { cell: held, run } of missing) { + const segment: IFarSegment = { + cell: held, + count: run.count, + cull: cohort.cull, + live: true, + run, + start: at, + }; + at += run.count; + built.segments.set(run, segment); + this.#farSegments.set(run, built); + this.#writeFarSegment(built, segment); + } + this.#syncFarSegments(built); + // The far half just gained records, so the shadow levels drawing the ground they stand on are + // stale; the same blanket tell a near swap makes. See `#shadowRecordsMoved`. + this.#host.shadowRecordsMoved(); + if (!this.#farReported.has(key)) { + this.#farReported.add(key); + const split = this.#farSplit(built); + console.info( + `${WORLD_IMPOSTOR_FAR_MARKER} key=${key} asset=${cohort.id} ` + + `instances=${String(built.capacity)} live=${String(split.live)} ` + + `nearOwned=${String(split.nearOwned)} bytes=${String(built.capacity * FAR_INSTANCE_BYTES)} ` + + `uploads=${String(built.uploads)}`, + ); + } + return true; + } + + /** The aggregate's own far surface: the record's atlas, the cohort's material twin, per-instance cull. */ + #makeFarSurface(cohort: IFarCohort): WorldImpostorSurface | undefined { + if (cohort.source === undefined) return undefined; + try { + return new WorldImpostorSurface({ + alphaTest: cohort.alphaTest, + atlas: cohort.record.atlas, + center: cohort.record.center, + cull: true, + radius: cohort.record.radius, + source: cohort.source, + }); + } catch { + this.#host.addFailure(); + return undefined; + } + } + + /** + * Keep a refused far cohort as metadata alone: the atlas pinned by one borrowed user, the runs, and + * a built surface. The asset's own geometry and materials are released by the caller, so a refused + * far species never holds a full GLB alive; `#retryFar` rebuilds from this when a near release frees + * room. Idempotent: a repeat refusal keeps the surface already built. + */ + #deferFar(cohort: IFarCohort): void { + if (this.#farRetry.has(cohort.id)) return; + const surface = this.#makeFarSurface(cohort); + if (surface === undefined) return; + cohort.record.users += 1; + this.#farRetry.set(cohort.id, { + alphaTest: cohort.alphaTest, + cull: cohort.cull, + id: cohort.id, + key: cohort.key, + record: cohort.record, + runs: cohort.runs, + surface, + }); + } + + /** Drop a deferred cohort, releasing its surface and the atlas user it pinned. */ + #dropFarRetry(id: string, consumedSurface?: WorldImpostorSurface): void { + const entry = this.#farRetry.get(id); + if (entry === undefined) return; + this.#farRetry.delete(id); + if (consumedSurface !== entry.surface) entry.surface.dispose(); + entry.record.users = Math.max(0, entry.record.users - 1); + this.#evictImpostors(); + } + + /** Retry the far aggregates a full budget refused, when a near cell freed its allocation. */ + retryFar(): void { + if (this.#farRetry.size === 0) return; + for (const entry of [...this.#farRetry.values()]) this.#buildFarCohort(entry); + } + + /** + * Adopt one far-only species per idle frame: a canonical asset the map places but no resident run + * has asked for yet. Its LOD0 is loaded through the same limiter, classified by its actual + * materials, baked on the same queue, and its source released once the aggregate lands. Gated on an + * empty near queue so loading the unseen never delays the near ring or holds its overlay open. + */ + pumpFarAcquisition(): void { + if (this.#impostors === false || this.#host.released() || this.#farExhausted) return; + if (this.#farCandidateIds.length === 0) return; + // Near work first: a queued build, an in-flight near load, a bake in flight or waiting. Only a + // settled frame spends itself on the unseen far half. + if ( + this.#host.nearWorkPending() || + this.#impostorQueue.length > 0 || + this.#impostorBaking !== undefined + ) + return; + // The bake needs a renderer that can run it; acquiring before one exists would hold source GLBs + // for a bake with no way to land. A renderer that is present but unsupported is the same answer. + const renderer = this.#host.renderer(); + if (renderer === undefined || impostorRawRenderer(renderer) === undefined) return; + const count = this.#farCandidateIds.length; + for (let step = 0; step < count; step += 1) { + const at = (this.#farCursor + step) % count; + const id = this.#farCandidateIds[at] as string; + if (this.#farSeen.has(id) || this.#host.hasAsset(id) || this.#farRetry.has(id)) continue; + this.#farCursor = (at + 1) % count; + const definition = this.#manifest.assets[id]; + if (definition === undefined) { + this.#farSeen.add(id); + continue; + } + const asset = this.#host.newAsset(id, definition); + asset.refcount += 1; + this.#farTemp.add(id); + this.#host.startAssetLoad(asset); + return; + } + // A full pass with nothing startable means every species is either baked, held for a retry, or + // classified as having no atlas. Static placements add none later, so the scan stops here. + this.#farExhausted = true; + } + + /** + * Drop a far-only asset's temporary reference once its atlas is held by an aggregate or has been + * deferred. The deferred cohort keeps its own pinned atlas user, so it is not dropped here. + */ + releaseFarTemp(id: string): void { + this.#farTemp.delete(id); + this.#farSeen.add(id); + this.#host.release(id); + } + + /** + * Whether an incoming far allocation would overrun the world's instance or byte budget. + * + * `count` is the peak, not the net: a grow allocates the new larger buffer while the old one is + * still alive, so the caller passes the new buffer's full size — the old buffer is already inside + * `#farInstances`/`#farBytes`. A net-new count would understate the transient a resize holds. + */ + #farBudgetRefuses(count: number): boolean { + return ( + this.#host.residency().instances + this.#farInstances + count > this.#budgets.instances || + this.#host.residency().bytes + + this.#farBytes + + this.#host.residency().hlodBytes + + count * FAR_INSTANCE_BYTES > + this.#budgets.bytes + ); + } + + /** One bounded line for a far aggregate the world's own budgets refused, once per atlas key. */ + #reportFarBudget(key: string, assetId: string, count: number): void { + if (this.#farReported.has(key)) return; + this.#farReported.add(key); + console.info( + `${WORLD_IMPOSTOR_FAR_MARKER} key=${key} asset=${assetId} ` + + `instances=${String(count)} reason=budget`, + ); + } + + /** + * One far mesh: the whole-asset quad, every original root, drawn regardless of how far the camera + * resolves it. `frustumCulled = false` is what keeps the draw: the projected-size gate exempts it + * before it ever reads bounds, so no `alwaysRender` marker is needed when a game configured no + * shadows to exempt it under `renderer.minimumProjectedPixels`. The per-placement cull rides the + * geometry's instance attribute, one static value per placement; see + * {@link IMPOSTOR_FAR_CULL_ATTRIBUTE}. + */ + #newFarMesh(surface: WorldImpostorSurface, capacity: number, assetId: string): InstancedMesh { + const mesh = new InstancedMesh(surface.geometry, surface.material, capacity); + mesh.count = capacity; + mesh.frustumCulled = false; + // Three's default usage uploads by version; grow/write/zero announce every matrix change through + // needsUpdate. DynamicDrawUsage would upload the same whole-map buffer on every settled draw. + mesh.name = `tn-far:${assetId}`; + // Drawn on layer 0, so this is a main-pass draw the frame budget counts under the impostor + // aggregate's own source rather than under `other` (`RenderPassBudget`). + mesh.userData.tnDrawSource = "proxies"; + if (surface.cull) this.#attachFarCull(surface.geometry, capacity); + // Far trees cast like near ones, into the coarse wide caster layer. The same mesh draws the main + // pass (layer 0) and the virtual-shadow levels (wide layer), so one representation covers both + // and a run hands its far segment over to the near ring exactly once; see `#disableFar`. The + // per-asset maxDistance gate is a main-pass shader gate, so the shadow half still reaches the map. + if (this.#castShadowLevels > 0) { + mesh.layers.enable(VIRTUAL_SHADOW_WIDE_CASTER_LAYER); + mesh.castShadow = true; + } + mesh.receiveShadow = this.#receiveShadow; + this.#host.add(mesh); + return mesh; + } + + /** Install a fresh per-instance cull buffer on the aggregate's shared geometry, all gates open. */ + #attachFarCull(geometry: BufferGeometry, capacity: number): void { + geometry.setAttribute( + IMPOSTOR_FAR_CULL_ATTRIBUTE, + new InstancedBufferAttribute(new Float32Array(capacity).fill(Number.POSITIVE_INFINITY), 1), + ); + } + + /** Append `extra` instance slots to an aggregate, one mesh swap, charged to the budgets. */ + #growFar(aggregate: IFarAggregate, extra: number): void { + const old = aggregate.mesh; + const capacity = aggregate.capacity + extra; + // Capture the cull values before the replacement mesh attaches a fresh buffer to the SHARED + // geometry; the old attribute object is replaced in place there. + const oldCull = aggregate.surface.cull + ? ( + aggregate.surface.geometry.getAttribute(IMPOSTOR_FAR_CULL_ATTRIBUTE)?.array as + | Float32Array + | undefined + )?.slice(0, aggregate.capacity) + : undefined; + const next = this.#newFarMesh(aggregate.surface, capacity, old.name.replace(/^tn-far:/u, "")); + (next.instanceMatrix.array as Float32Array).set( + (old.instanceMatrix.array as Float32Array).subarray(0, aggregate.capacity * 16), + ); + next.instanceMatrix.needsUpdate = true; + if (oldCull !== undefined) { + const attribute = next.geometry.getAttribute(IMPOSTOR_FAR_CULL_ATTRIBUTE) as + | InstancedBufferAttribute + | undefined; + if (attribute !== undefined) (attribute.array as Float32Array).set(oldCull, 0); + } + this.#host.remove(old); + old.dispose(); + aggregate.mesh = next; + aggregate.capacity = capacity; + aggregate.uploads += 1; + this.#farInstances += extra; + this.#farBytes += extra * FAR_INSTANCE_BYTES; + } + + /** Slots allocated in an aggregate: the sum of its segment counts, the offset the next one takes. */ + #farAllocated(aggregate: IFarAggregate): number { + let at = 0; + for (const segment of aggregate.segments.values()) at += segment.count; + return at; + } + + /** Compose one run's ORIGINAL placement roots into its segment, exactly as `#addPlacements` does. */ + #writeFarSegment(aggregate: IFarAggregate, segment: IFarSegment): void { + const records = cellPlacements(this.#placements, segment.run); + const array = aggregate.mesh.instanceMatrix.array as Float32Array; + const base = segment.start * 16; + for (let index = 0; index < segment.count; index += 1) { + const at = index * PLACEMENT_RECORD_FLOATS; + this.#position.set( + records[at] as number, + records[at + 1] as number, + records[at + 2] as number, + ); + this.#rotation.set( + records[at + 3] as number, + records[at + 4] as number, + records[at + 5] as number, + records[at + 6] as number, + ); + this.#scale.setScalar(records[at + 7] as number); + this.#matrix.compose(this.#position, this.#rotation, this.#scale); + this.#matrix.toArray(array, base + index * 16); + } + aggregate.mesh.instanceMatrix.needsUpdate = true; + // The per-placement gate is static, so it is written with the matrices and re-uploaded only when + // a handoff rewrites this segment. A whole-map surface reads it so every canonical asset keeps + // its own authored cutoff over the shared atlas. See {@link IFarSegment.cull}. + const cull = aggregate.mesh.geometry.getAttribute(IMPOSTOR_FAR_CULL_ATTRIBUTE) as + | InstancedBufferAttribute + | undefined; + if (cull !== undefined) { + (cull.array as Float32Array).fill(segment.cull, segment.start, segment.start + segment.count); + cull.needsUpdate = true; + } + aggregate.uploads += 1; + this.#farBoundsStale(aggregate); + } + + /** + * Zero one segment's records: the run is drawn by the near ring now, not the far mesh. Each record + * keeps a homogeneous `w` of 1 while its xyz/scale terms collapse to 0, so the instance projects + * to the origin and draws nothing. An all-zero record would leave `w = 0`, and Three's + * `Box3.applyMatrix4` divides by it, so `computeBoundingBox` over the shared buffer would return + * NaN and every caster in the aggregate would be dropped by the shadow levels. + */ + #zeroFarSegment(aggregate: IFarAggregate, segment: IFarSegment): void { + const array = aggregate.mesh.instanceMatrix.array as Float32Array; + const from = segment.start * 16; + const to = (segment.start + segment.count) * 16; + array.fill(0, from, to); + for (let at = from; at < to; at += 16) array[at + 15] = 1; + aggregate.mesh.instanceMatrix.needsUpdate = true; + aggregate.uploads += 1; + this.#farBoundsStale(aggregate); + } + + /** + * A far matrix write changes the mesh's instance extent, so drop the cached pair and let Three + * rebuild it on the next read; this is the native `null` convention `computeBoundingBox` honors. + */ + #farBoundsStale(aggregate: IFarAggregate): void { + aggregate.mesh.boundingBox = null; + aggregate.mesh.boundingSphere = null; + } + + /** The near ring has attached this run's full representation; the far mesh stops drawing it. */ + disableFar(run: IWorldRun): void { + const aggregate = this.#farSegments.get(run); + if (aggregate === undefined) return; + const segment = aggregate.segments.get(run); + if (segment === undefined || segment.live === false) return; + this.#zeroFarSegment(aggregate, segment); + segment.live = false; + } + + /** This run is leaving the ring; the far mesh draws it again before the near batches are cleared. */ + restoreFar(run: IWorldRun): void { + const aggregate = this.#farSegments.get(run); + if (aggregate === undefined) return; + const segment = aggregate.segments.get(run); + if (segment === undefined || segment.live === true) return; + this.#writeFarSegment(aggregate, segment); + segment.live = true; + } + + /** + * Hand over every run of an aggregate a resident cell already draws. A run is near-ready exactly + * when the cell holds one of its batches; the whole job is attached in one swap, so presence is the + * answer. This is the build-time half of the atomic handoff; `#swap` is the per-frame half. + */ + #syncFarSegments(aggregate: IFarAggregate): void { + for (const segment of aggregate.segments.values()) { + if (segment.live === false) continue; + const resident = this.#host.residentCell(cellKey(segment.cell.x, segment.cell.z)); + if (resident === undefined) continue; + if (!resident.batches.some((entry) => entry.run === segment.run)) continue; + this.#zeroFarSegment(aggregate, segment); + segment.live = false; + } + } + + /** An aggregate's live and near-owned instance counts. */ + #farSplit(aggregate: IFarAggregate): { live: number; nearOwned: number } { + let live = 0; + let nearOwned = 0; + for (const segment of aggregate.segments.values()) { + if (segment.live) live += segment.count; + else nearOwned += segment.count; + } + return { live, nearOwned }; + } + + /** The far block {@link stats} reports: aggregates, physical instances/bytes and their split. */ + #farStats(): IWorldCellsStats["impostor"]["far"] { + let instances = 0; + let live = 0; + let nearOwned = 0; + let uploads = 0; + for (const aggregate of this.#far.values()) { + instances += aggregate.capacity; + uploads += aggregate.uploads; + const split = this.#farSplit(aggregate); + live += split.live; + nearOwned += split.nearOwned; + } + return { + aggregates: this.#far.size, + bytes: this.#farBytes, + instances, + live, + nearOwned, + uploads, + }; + } + + /** Dispose every far aggregate: its own mesh and surface, and its borrowed atlas user. */ + #drainFar(): void { + for (const aggregate of this.#far.values()) { + aggregate.mesh.removeFromParent(); + aggregate.mesh.dispose(); + aggregate.surface.dispose(); + aggregate.record.users = Math.max(0, aggregate.record.users - 1); + } + this.#far.clear(); + this.#farSegments.clear(); + this.#farInstances = 0; + this.#farBytes = 0; + } + + /** Retry the handoffs a frame's fresh-mesh allowance deferred; one bounded pass per update. */ + retryWide(): void { + if (this.#wideRetry.size === 0) return; + const ids = [...this.#wideRetry]; + this.#wideRetry.clear(); + for (const id of ids) { + const asset = this.#host.asset(id); + if (asset === undefined || asset.impostor === undefined) continue; + this.#host.retargetWideCasters(asset); + } + } + + /** The impostor block {@link stats} reports: assets landed, bakes pending, atlas bytes, terminal tris. */ + impostorStats(): IWorldCellsStats["impostor"] { + let assets = 0; + let terminalTriangles = 0; + for (const asset of this.#host.assets()) { + if (asset.impostor === undefined) continue; + assets += 1; + terminalTriangles += levelTriangles(asset.impostor.part.geometry); + } + return { + assets, + atlasBytes: this.#impostorRecordBytes, + budgetBytes: this.#impostorBudgetBytes, + far: this.#farStats(), + pending: this.#impostorQueue.length + (this.#impostorBaking === undefined ? 0 : 1), + terminalTriangles, + }; + } + + /** Drop queued/baking work for an asset whose source geometry is about to be released. */ + cancelImpostor(asset: IAssetState): void { + this.#impostorQueued.delete(asset.id); + const at = this.#impostorQueue.indexOf(asset); + if (at >= 0) this.#impostorQueue.splice(at, 1); + if (this.#impostorBaking === asset) { + // Before the borrowed source geometry is released: the baker stages it, and a view drawn after + // the teardown reads a disposed buffer. + this.#baker.abort(); + this.#impostorBaking = undefined; + this.#impostorReservedBytes = 0; + } + } + + /** Release an asset's terminal level and give its atlas back to the cache; see `#release`. */ + releaseImpostor(asset: IAssetState): void { + const impostor = asset.impostor; + if (impostor === undefined) return; + asset.impostor = undefined; + // Marked first, so `#releaseParts` skips the part whose geometry and material the surface owns. + this.#host.markReleasedPart(impostor.part); + dropPooledFor(impostor.part.geometry, impostor.part.material); + impostor.surface.dispose(); + impostor.record.users = Math.max(0, impostor.record.users - 1); + this.#evictImpostors(); + } + + /** + * Drop the oldest inactive atlases until the cache is back under budget; active ones are kept. + * + * `reserve` is the declared bytes of a bake about to start: the caller asks whether evicting + * inactive records can make room for it. A record with a live user is never dropped, so a full + * cache of active atlases simply leaves the reservation unmet and the caller refuses the bake. + */ + #evictImpostors(reserve = 0): void { + if (this.#impostorRecordBytes + reserve <= this.#impostorBudgetBytes) return; + for (const [key, record] of [...this.#impostorRecords]) { + if (this.#impostorRecordBytes + reserve <= this.#impostorBudgetBytes) break; + if (record.users > 0) continue; + this.#impostorRecords.delete(key); + this.#impostorRecordBytes -= record.bytes; + record.atlas.dispose(); + } + } + + /** Dispose every cached atlas and any in-flight bake; the world's teardown path calls it once. */ + drainImpostors(): void { + // Far-only sources no cell run owns would otherwise never reach `#evict`; release them here so + // their geometry goes the way every near asset's does. + for (const id of [...this.#farTemp]) this.#host.release(id); + this.#farTemp.clear(); + // Deferred far cohorts own a surface and pin an atlas user; release both before the records are + // disposed below, so no surface is left pointing at a disposed atlas. + for (const entry of this.#farRetry.values()) { + entry.surface.dispose(); + entry.record.users = Math.max(0, entry.record.users - 1); + } + this.#farRetry.clear(); + this.#farSeen.clear(); + // The far aggregates own surfaces over these atlases and hold a user on their records; they go + // first, so no far surface is left pointing at an atlas disposed below it. + this.#drainFar(); + this.#impostorQueue.length = 0; + this.#impostorQueued.clear(); + this.#impostorBaking = undefined; + this.#impostorReservedBytes = 0; + this.#baker.dispose(); + for (const record of this.#impostorRecords.values()) record.atlas.dispose(); + this.#impostorRecords.clear(); + this.#impostorRecordBytes = 0; + } + + /** + * `TN_WORLD_IMPOSTOR`, one bounded line per asset: its terminal level, the metres it takes over at, + * the views the atlas holds, its GPU bytes, and — when the world can name it — why it did not land. + */ + #reportImpostor(asset: IAssetState, reason: string): void { + if (this.#impostorReported.has(asset.id)) return; + this.#impostorReported.add(asset.id); + const impostor = asset.impostor; + const atlas = impostor?.record.atlas ?? this.#baker.pending?.atlas; + console.info( + `${WORLD_IMPOSTOR_MARKER} asset=${asset.id} level=${String(asset.levels.length - 1)} ` + + `switch=${(asset.distances.at(-1) ?? 0).toFixed(1)} ` + + `views=${String(atlas === undefined ? 0 : IMPOSTOR_VIEWS)} ` + + `bytes=${String(atlas?.bytes ?? 0)} reason=${reason}`, + ); + } +} + +export const PLACEMENT_RECORD_BYTES = 32; + +export const PLACEMENT_RECORD_FLOATS = 8; + +/** + * The distance a `maxDistance` prop actually culls at, one eighth short of itself: the slack + * `#addPlacements` leaves, so a follow point that has not moved a whole eighth of the cull distance + * cannot have culled a placement it had not already culled. + */ +export function cullDistance(maxDistance: number | undefined): number | undefined { + return maxDistance === undefined ? undefined : maxDistance - maxDistance / 8; +} diff --git a/packages/core/src/world-cell-models.ts b/packages/core/src/world-cell-models.ts new file mode 100644 index 0000000000..b48ff18462 --- /dev/null +++ b/packages/core/src/world-cell-models.ts @@ -0,0 +1,1029 @@ +import { type BufferGeometry, type Material, Matrix4, Mesh, Object3D, SkinnedMesh } from "three"; +import { type IAssetLoader, createAssetLoader } from "./assets.js"; +import { type ILodChain, lodChainOf } from "./model-lod.js"; +import { displacesVertices } from "./projection-plan.js"; +import { cutoutSurface } from "./render/foliage-alpha.js"; +import { materialKey } from "./render/material-key.js"; +import { dropPooledFor } from "./render/mesh-pool.js"; +import { levelTriangles, release, resolveRelative } from "./world-cell-chunks.js"; +import type { IAssetPart, IResidentCell, IWorldCellsLoadOptions } from "./world-cell-data.js"; +import { positiveInteger } from "./world-cell-data.js"; +import { + DEFAULT_CUTOUT_ALPHA, + type IImpostorState, + cullDistance, + isAlphaFoliage, +} from "./world-cell-impostors.js"; +import { cellProxySourceSafe } from "./world-cell-proxy.js"; +import { worldOwned } from "./world-chunk-shadow.js"; +import { ModelLoadLimiter } from "./world-load-limiter.js"; +import type { IWorldAsset, IWorldPackage, IWorldRun } from "./world-package.js"; + +/** A surface every part with the same material content draws with, and who still uses it. */ +interface ISharedSurface { + readonly material: Material; + /** The canonical source and any cutout made from it, torn down with the last user. */ + readonly owned: readonly Material[]; + users: number; +} + +export interface IAssetState { + readonly id: string; + readonly definition: IWorldAsset; + /** + * The distance at which each level takes over, index-aligned with `glbs`; level 0 never does. + * + * Mutable because a chain is only known once the asset's own model has loaded: an asset with no + * authored `lods` is widened with the levels that model's baked chain carries, and every consumer + * of this reads it at build time — after `#adoptAsset` — so nothing has to be rebuilt. + */ + distances: readonly number[]; + /** The package-relative GLB per level, index-aligned with `distances`. */ + readonly glbs: readonly string[]; + /** Every distance the level or cull answer changes at, `threshold` being the nearest. */ + gates: readonly number[]; + /** The nearest distance this asset's batching can be crossed at, or `undefined` when it cannot. */ + threshold: number | undefined; + refcount: number; + hlodSafe: boolean; + pending: boolean; + failed: boolean; + disposed: boolean; + /** + * The exact `assets.model` cache paths this state asked the loader for, held in `#modelPaths` + * until the last state naming each one is released. Empty when `loadModel` bypasses the cache; + * filled once per state, so a load retried after an empty answer does not count twice. + */ + retainedPaths: string[]; + /** + * One part list per entry in `glbs`, so a level that failed to load reuses the level below it and + * the batch builder never has to know a level is missing. + */ + levels: readonly (readonly IAssetPart[])[]; + /** + * Parts taken out of DRAW but still owned here until teardown. Authored levels now keep their own + * geometry, so this stays empty; it is drained once with the asset on release. + */ + spilled: readonly IAssetPart[]; + /** + * The loader's resolved cooked url for LOD0, recorded at load. It is the atlas cache key's model + * half, so two package entries that fetch the same cooked bytes share one bake even when their + * `lods`/`maxDistance` metadata stopped them aliasing. See `#impostorKey`. + */ + resolvedGlb: string; + /** The whole-asset impostor level this asset appended, once its bake landed. See `#finishImpostor`. */ + impostor?: IImpostorState; +} + +export async function loadModelWith(assets: IAssetLoader, path: string): Promise { + const gltf = await assets.model<{ scene?: Object3D }>(path); + const scene = gltf?.scene; + if (!(scene instanceof Object3D)) + throw new Error(`World asset '${path}' loaded without an Object3D scene.`); + return scene; +} + +/** + * Mip compensation for scattered foliage. Golus's 0.25 suits dense cards; a needle atlas at ~9 % + * coverage averages to ~0.09 alpha by mip 8, where a tree 150 m out samples, so 0.25 still drops + * every needle. 0.75 keeps the cutoff under that average through mip 8. + * ponytail: one tuned constant; coverage-preserving mips at cook time are the upgrade path. + */ +const SCATTER_MIP_ALPHA_SCALE = 0.75; + +/** + * The surface a scattered part draws with, and the cutout this class owns when it made one. + * + * An `InstancedMesh` cannot sort its instances, so a `transparent` material on scattered foliage + * draws in submission order — wrong against itself, and overdraw on top of it. A cutout keeps the + * part's own alpha shape, keeps the depth buffer, and lets the draw reject what is behind it. The + * GLB's material is never mutated: the cutout is per asset part, so every cell batch of the asset + * shares one and a game that still wants blending asks for it. + * + * The cutout itself is `cutoutSurface`, because a cutout that ignores the mip chain deletes itself + * at distance: the needle card's mips average to a tenth, and every needle is discarded. + */ +function scatterMaterial( + material: Material, + transparentScatter: "cutout" | "blend", +): { readonly material: Material; readonly owned: readonly Material[] } { + if (transparentScatter === "blend" || !material.transparent) + return { material, owned: [material] }; + const cutout = cutoutSurface( + material, + material.alphaTest > 0 ? material.alphaTest : DEFAULT_CUTOUT_ALPHA, + SCATTER_MIP_ALPHA_SCALE, + ); + // The authored material is owned too: the batch draws the cutout, and the GLB's own surface is + // still this asset's to hand back. + worldOwned.add(cutout); + return { material: cutout, owned: [material, cutout] }; +} + +/** + * Every drawable part of a loaded model: one per `Mesh`, each with its shape, its surface, and + * where it sits relative to the model root. + * + * A GLB authored as a tree — bark `OPAQUE`, needles `BLEND` — is one node carrying one mesh with + * two primitives, which three loads as a `Group` of one child `Mesh` per primitive. Taking only the + * first of those drew 62k bare trunks, so every one of them is a part and each keeps its own + * material. + * + * `SkinnedMesh` is skipped deliberately: its vertices are posed per frame from a skeleton, and one + * `InstancedMesh` of the geometry would draw the rest pose once per placement. `Points` and + * `LineSegments` are not `Mesh`es and are skipped with them. + */ +function renderableParts( + model: Object3D, + surfaceFor: (material: Material) => { readonly key: string; readonly material: Material }, +): readonly IAssetPart[] { + model.updateMatrixWorld(true); + const rootInverse = new Matrix4().copy(model.matrixWorld).invert(); + const local = new Matrix4(); + const parts: IAssetPart[] = []; + model.traverse((object: Object3D) => { + if (object instanceof SkinnedMesh || !(object instanceof Mesh)) return; + const material = Array.isArray(object.material) ? object.material[0] : object.material; + if (object.geometry === undefined || material === undefined) return; + const surface = surfaceFor(material); + parts.push({ + geometry: object.geometry, + local: local.multiplyMatrices(rootInverse, object.matrixWorld).clone(), + material: surface.material, + surface: surface.key, + }); + }); + return parts; +} + +function disposeModel(model: Object3D): number { + let failed = 0; + model.traverse((object: Object3D) => { + if (!(object instanceof Mesh)) return; + if (release(object.geometry)) failed += 1; + const materials: Material[] = Array.isArray(object.material) + ? object.material + : [object.material]; + // A world-owned surface is one chunk's proxy material, and the next chunk's teardown would + // otherwise release a material this world's remaining proxies are still drawing with. It is + // released with the world that owns it; see `WorldCells#dispose`. + for (const material of materials) + if (!worldOwned.has(material) && release(material)) failed += 1; + }); + return failed; +} + +/** + * The levels one asset is drawn at, and the distances its batching can be crossed at. + * + * Level 0 is the asset's own `glb` and never switches; `lods[i - 1].distance` is where an instance + * that far out draws `lods[i]` instead. A `lods` entry at or beyond the cull distance is dropped: + * an instance that far out is culled, so its shape can never be drawn. `gates` is every distance + * the level or cull answer changes at — `threshold` is the nearest of them, so a follow point that + * has moved an eighth of it may have moved an instance either way. + */ +function assetLevels(definition: IWorldAsset): { + readonly distances: readonly number[]; + readonly gates: readonly number[]; + readonly glbs: readonly string[]; + readonly threshold: number | undefined; +} { + const maxDistance = definition.maxDistance; + const distances: number[] = [0]; + const glbs: string[] = [definition.glb]; + const gates: number[] = []; + for (const lod of definition.lods ?? []) { + // A level at or beyond the cull distance is never drawn: an instance that far out is gone by + // the time the level would take over, so its GLB is never asked for. + if (maxDistance !== undefined && lod.distance >= maxDistance) continue; + distances.push(lod.distance); + glbs.push(lod.glb); + gates.push(lod.distance); + } + const cull = cullDistance(maxDistance); + if (cull !== undefined) gates.push(cull); + return { + distances, + gates, + glbs, + threshold: gates.length === 0 ? undefined : Math.min(...gates), + }; +} + +/** + * The batched shape of one definition, as the loader will actually fetch it: the url the manifest + * gives its `glb`, the url and switch distance of every `lods` entry in order, its `maxDistance` and + * its bounds. Two definitions that agree on all of it are the same draw — same geometry, same + * materials, same levels, same gates — so one model serves both and their placements batch together. + * + * `undefined` when the loader cannot resolve a path at all: a path the manifest does not list makes + * `resolve` throw, and the load that asks for it will say so. Such a definition never aliases, + * which is the same behaviour it has today. + */ +async function assetSignature( + assets: IAssetLoader, + definition: IWorldAsset, + logicalBase: string, +): Promise { + const url = async (glb: string): Promise => { + const candidates = await assets.resolve(resolveRelative(logicalBase, glb)); + return candidates[0] ?? glb; + }; + try { + const parts = [await url(definition.glb)]; + for (const lod of definition.lods ?? []) + parts.push(`${await url(lod.glb)}@${String(lod.distance)}`); + parts.push( + definition.maxDistance === undefined ? "" : String(definition.maxDistance), + definition.bounds.min.join(","), + definition.bounds.max.join(","), + ); + return parts.join("|"); + } catch { + return undefined; + } +} + +/** + * `alias id -> canonical id` for every definition that is another name for the same cooked model, + * which is what a 374-asset package with 273 duplicates is: one model exported under many object + * names, cooked once and fetched once. + * + * The canonical is the lexicographically smallest id of the group, so it does not depend on the + * order the exporter happened to write them in. An empty map — every definition its own model, or a + * package whose paths resolve to nothing — leaves the whole runtime exactly as it is. + */ +export async function assetAliases( + assets: IAssetLoader, + manifest: IWorldPackage, + logicalBase: string, +): Promise> { + const canonicalBySignature = new Map(); + const aliases = new Map(); + // Sorted, so the first id to claim a signature is the smallest one in its group. + for (const id of Object.keys(manifest.assets).sort()) { + const definition = manifest.assets[id]; + if (definition === undefined) continue; + const signature = await assetSignature(assets, definition, logicalBase); + if (signature === undefined) continue; + const canonical = canonicalBySignature.get(signature); + if (canonical === undefined) canonicalBySignature.set(signature, id); + else aliases.set(id, canonical); + } + return aliases; +} + +/** What a baked chain gives a batched draw: the extra levels, where they take over, what they cost. */ +interface IChainLevels { + /** Index-aligned with `distances`; index 0 is the parts the asset already had. */ + readonly levels: (readonly IAssetPart[])[]; + /** `distances[0]` is 0 and never switches; the rest are the synthesized switch distances. */ + readonly distances: number[]; + /** Triangles every level submits, all its parts together, index-aligned with `levels`. */ + readonly triangles: number[]; +} + +/** + * The extra levels an asset's baked AutoLOD chain gives a batched draw, or `undefined` when none of + * its parts carries a chain. + * + * A `ModelLod` swaps one mesh's geometry, so a world that batches thousands of placements of one + * model into a handful of instanced draws never reaches that selection: every placement drew + * LOD0, which on a 2 km map is the whole triangle bill at every distance. The chain already holds + * the reduced shapes, so this derives the levels a batch can hold and the distance each one takes + * over at — the same screen-space test `ModelLod` runs, solved for depth: + * `error * pixelsPerUnit / maxPixelError`, with each part's own registered budget. + * + * Level `i` is `chain.levels[min(i, length - 1)]` per part, so a part without a chain — and one + * whose chain ran out below `i` — keeps the shape above it. The switch distances come from the + * parts that descend the furthest, the **max** across those at each level, so the deepest chain's + * whole ladder survives. A part with a shallower chain clamps to its last level and does not set a + * switch: it cannot be drawn finer there anyway, so its error must not push the switch past the + * deepest chain's own last error and collapse the ladder to one far step. + */ +function chainDistances( + chains: readonly (ILodChain | undefined)[], + pixelsPerUnit: number, + maxPixelError: number, + maxDistance: number | undefined, +): number[] { + let deepest = 1; + for (const chain of chains) + if (chain !== undefined) deepest = Math.max(deepest, chain.levels.length); + const distances = [0]; + for (let level = 1; level < deepest; level += 1) { + let error = 0; + for (const chain of chains) { + // Only the parts that descend the furthest set the schedule. A part whose chain ran out has + // no finer shape to offer past its last level, so letting its — often terminal, and so huge — + // error gate this switch cannot buy that part any detail back. It would only deny every + // sibling the coarser level and collapse the chain to a single far switch, which is how a + // bark primitive's 2-level chain truncated a pine's 4-level needles chain to two levels. + if (chain === undefined || chain.levels.length !== deepest) continue; + error = Math.max(error, chain.errors[level] ?? 0); + } + const at = (error * pixelsPerUnit) / maxPixelError; + // A chain's errors only ascend, so a switch that does not move outward — or that lands at or + // past the cull — ends the list: every level above it is further out still, and none of them + // can be selected or drawn. That keeps the thresholds strictly increasing and inside + // `maxDistance`, exactly as `assetLevels` keeps authored ones. + if (at <= (distances[level - 1] as number)) break; + if (maxDistance !== undefined && at >= maxDistance) break; + distances.push(at); + } + return distances; +} + +/** One level's parts, and what they submit: the chain's shape where it has one, the one above where not. */ +function chainLevelParts( + parts: readonly IAssetPart[], + chains: readonly (ILodChain | undefined)[], + level: number, +): { readonly parts: IAssetPart[]; readonly triangles: number } { + const at: IAssetPart[] = []; + let triangles = 0; + for (const [index, part] of parts.entries()) { + const chain = chains[index]; + // A part with no chain keeps its own shape, in its own slot: the batch builder and + // `#addPlacements` index every level's parts by the same number. + const geometry = + chain === undefined + ? part.geometry + : (chain.levels[Math.min(level, chain.levels.length - 1)] as BufferGeometry); + at.push(geometry === part.geometry ? part : { ...part, geometry }); + triangles += levelTriangles(geometry); + } + return { parts: at, triangles }; +} + +/** + * The extra levels an asset's baked AutoLOD chain gives a batched draw, or `undefined` when none of + * its parts carries a chain. + * + * A `ModelLod` swaps one mesh's geometry, so a world that batches thousands of placements of one + * model into a handful of instanced draws never reaches that selection: every placement drew + * LOD0, which on a 2 km map is the whole triangle bill at every distance. The chain already holds + * the reduced shapes, so this derives the levels a batch can hold and the distance each one takes + * over at — the same screen-space test `ModelLod` runs, solved for depth: + * `error * pixelsPerUnit / maxPixelError`, over the world's own `autoLod.maxPixelError` rather than + * the budget the loader registered the chain with. See + * {@link IWorldCellsLoadOptions.autoLod} for why a batch cannot use the registered one. + * + * Level `i` is `chain.levels[min(i, length - 1)]` per part, so a part without a chain — and one + * whose chain ran out below `i` — keeps the shape above it. The switch distances come from the + * parts that descend the furthest, the **max** across those at each level, so the deepest chain's + * whole ladder survives. A part with a shallower chain clamps to its last level and does not set a + * switch: it cannot be drawn finer there anyway, so its error must not push the switch past the + * deepest chain's own last error and collapse the ladder to one far step. + */ +/** Prepared sources may have replaced attributes or cut a material section after cloning. */ +function preparedChainSafe(part: IAssetPart): boolean { + const base = part.geometry; + const chain = lodChainOf(base); + if (chain === undefined) return true; + if ( + base.drawRange.start !== 0 || + base.drawRange.count !== Number.POSITIVE_INFINITY || + base.groups.length > 0 || + part.material.transparent || + part.material.alphaTest > 0 || + displacesVertices(part.material) || + Reflect.get(part.material, "vertexNode") != null + ) + return false; + const names = Object.keys(base.attributes); + return chain.levels.every( + (level) => + Object.keys(level.attributes).length === names.length && + names.every((name) => level.getAttribute(name) === base.getAttribute(name)), + ); +} + +function chainLevels( + parts: readonly IAssetPart[], + pixelsPerUnit: number, + maxPixelError: number, + maxDistance: number | undefined, +): IChainLevels | undefined { + const chains: (ILodChain | undefined)[] = []; + for (const part of parts) chains.push(lodChainOf(part.geometry)); + const distances = chainDistances(chains, pixelsPerUnit, maxPixelError, maxDistance); + if (distances.length === 1) return undefined; + const levels: (readonly IAssetPart[])[] = [parts]; + // Level 0 leads, so the marker opens with what the asset submits today. + const triangles: number[] = [parts.reduce((sum, part) => sum + levelTriangles(part.geometry), 0)]; + for (let level = 1; level < distances.length; level += 1) { + const at = chainLevelParts(parts, chains, level); + levels.push(at.parts); + triangles.push(at.triangles); + } + return { distances, levels, triangles }; +} + +/** + * The DRAW levels an authored asset gets: every level keeps the geometry, transform and surface it + * authored, and the root LOD0's alpha-cutout parts are appended to a level that authored fewer alpha + * slots than the root carries, so no middle level loses leaves its reduced card cannot honestly + * represent. + * + * An authored reduced foliage card has no honest twin — the automatic reducer's error metric cannot + * see the holes a cutout silhouette is made of — but a level that authored such a card keeps it: + * only the slots a level carries none of fall back to the root, in the root's own per-role order, so + * a middle with one reduced card and a root with two gets its own card plus the root's second. The + * fallback parts are appended after the level's authored parts, never reordered, so each level keeps + * its own part indices and counts. Nothing is spilled: a fallback shares the root part by reference, + * so one resource is still one teardown. + */ +function coverAuthoredLevels( + levels: readonly (readonly IAssetPart[])[], +): readonly (readonly IAssetPart[])[] { + const rootAlpha = (levels[0] ?? []).filter((part) => isAlphaFoliage(part.material)); + if (rootAlpha.length === 0 || levels.length < 2) return levels; + const covered: (readonly IAssetPart[])[] = [levels[0] as readonly IAssetPart[]]; + for (const parts of levels.slice(1)) { + let authoredAlpha = 0; + for (const part of parts) if (isAlphaFoliage(part.material)) authoredAlpha += 1; + if (authoredAlpha >= rootAlpha.length) { + covered.push(parts); + continue; + } + const mapped: IAssetPart[] = [...parts]; + for (let slot = authoredAlpha; slot < rootAlpha.length; slot += 1) + mapped.push(rootAlpha[slot] as IAssetPart); + covered.push(mapped); + } + return covered; +} + +/** + * Model loads in flight for a streamed world. `loadAll`'s six suits a scene's one-off load; a + * world crossing new asset types as it moves spends most of each load waiting on the network and + * the transcoder workers, so more in flight keeps the ring ahead of a fast camera. + */ +const WORLD_LOAD_CONCURRENCY = 12; + +/** + * Assets reported one by one before the `TN_WORLD_LOD_CHAIN` marker collapses into a single line. + * A 2 km package carries hundreds of chained assets, and a console line each is a wall of text that + * buries the one that matters. + */ +const CHAIN_LOD_MARKER_LIMIT = 20; +export interface ICellModelsHost { + canonical(id: string): string; + noteGate(gate: number): void; + queueBuild(asset: IAssetState, cell: IResidentCell, run: IWorldRun, force?: boolean): void; + released(): boolean; + failures(): number; + setFailures(count: number): void; + reportCellFailure( + stage: "asset" | "attach" | "chunk", + key: string, + paths: readonly string[], + error: unknown, + ): void; + drainShared(assetId: string): number; + residentCells(): Iterable; + queuePrewarm(asset: IAssetState): void; + hasFarTemp(id: string): boolean; + releaseFarTemp(id: string): void; + maybeQueueImpostor(asset: IAssetState): void; + hasQueuedImpostor(id: string): boolean; + cancelImpostor(asset: IAssetState): void; + releaseImpostor(asset: IAssetState): void; +} +export interface ICellModelsSettings { + readonly lodHysteresis: number; + readonly manifest: IWorldPackage; + readonly baseUrl: string; + readonly logicalBase: string; + readonly preserveAuthoredParts: boolean; + readonly autoLodPixelsPerUnit: number; + readonly autoLodMaxPixelError: number; + readonly transparentScatter: "cutout" | "blend"; +} + +export class CellModels { + readonly #host: ICellModelsHost; + + /** Parts already handed back, so a level that fell back to the one above releases it once. */ + static readonly #releasedParts = new WeakSet(); + + readonly #loader: IAssetLoader; + + /** + * Whether {@link #loader} was created here rather than supplied by the game. Only an internally + * owned loader may have its `model` cache released when this world's last cell stops naming a + * path: an explicitly supplied loader is the caller's, another world may hold the same cached + * model, and its own `release` is the caller's to call. See `#releaseModelPaths`. + */ + readonly #ownsLoader: boolean; + + readonly #loadModel: ((url: string) => Promise) | undefined; + + readonly #limiter: ModelLoadLimiter; + + readonly #assets = new Map(); + + /** + * Exact `assets.model` cache paths this world holds, and how many of its asset states ask for + * each. Two distinct canonical assets can name the same path when their `lods`/`maxDistance` + * differ — the package's own aliasing leaves them separate — so the loader entry is released only + * when the last state naming the path goes. Keyed on the logical path requested, which is what the + * loader caches by, not the cooked url it resolves to. Empty under a `loadModel` override, which + * reaches no cache. + */ + readonly #modelPaths = new Map(); + + /** Surfaces shared by material content across every asset part; see `materialKey`. */ + readonly #surfaces = new Map(); + + /** Assets widened by their own chain so far, and whether the marker has already collapsed. */ + #chainLodAssets = 0; + + #chainLodSummarised = false; + + readonly #lodHysteresis: number; + + readonly #manifest: IWorldPackage; + + readonly #baseUrl: string; + + readonly #logicalBase: string; + + readonly #preserveAuthoredParts: boolean; + + /** + * Pixels per world unit at unit depth, the scale a baked chain's level errors are divided by to + * reach a switch distance. See `IWorldCellsLoadOptions.autoLod` and {@link chainLevels}. + */ + readonly #autoLodPixelsPerUnit: number; + + /** The screen-space error budget a synthesized level is switched at; see `autoLod`. */ + readonly #autoLodMaxPixelError: number; + + readonly #transparentScatter: "cutout" | "blend"; + constructor( + init: Pick & { + readonly ownsAssets?: boolean; + }, + settings: ICellModelsSettings, + host: ICellModelsHost, + ) { + this.#host = host; + this.#lodHysteresis = settings.lodHysteresis; + this.#manifest = settings.manifest; + this.#baseUrl = settings.baseUrl; + this.#logicalBase = settings.logicalBase; + this.#preserveAuthoredParts = settings.preserveAuthoredParts; + this.#autoLodPixelsPerUnit = settings.autoLodPixelsPerUnit; + this.#autoLodMaxPixelError = settings.autoLodMaxPixelError; + this.#transparentScatter = settings.transparentScatter; + this.#loader = init.assets ?? createAssetLoader(); + this.#ownsLoader = init.ownsAssets ?? init.assets === undefined; + this.#loadModel = init.loadModel; + this.#limiter = new ModelLoadLimiter( + positiveInteger(init.concurrency ?? WORLD_LOAD_CONCURRENCY, "concurrency"), + ); + } + get loadModel(): ((url: string) => Promise) | undefined { + return this.#loadModel; + } + get loadsInFlight(): number { + return this.#limiter.inFlight; + } + get loadsQueued(): number { + return this.#limiter.queued; + } + load(path: string, wanted: () => boolean): Promise { + return this.#limiter.load(() => this.model(path), wanted); + } + asset(id: string): IAssetState | undefined { + return this.#assets.get(id); + } + hasAsset(id: string): boolean { + return this.#assets.has(id); + } + assets(): Iterable { + return this.#assets.values(); + } + entries(): Iterable<[string, IAssetState]> { + return this.#assets.entries(); + } + pathUsers(path: string): number { + return this.#modelPaths.get(path) ?? 0; + } + markReleasedPart(part: IAssetPart): void { + CellModels.#releasedParts.add(part); + } + + /** One fresh asset state for a definition, registered and its gates noted. */ + newAsset(id: string, definition: IWorldAsset): IAssetState { + const asset: IAssetState = { + ...assetLevels(definition), + definition, + disposed: false, + id, + levels: [], + pending: false, + failed: false, + refcount: 0, + hlodSafe: false, + retainedPaths: [], + resolvedGlb: "", + spilled: [], + }; + if (this.#lodHysteresis > 0 && definition.lods !== undefined) { + asset.gates = [ + ...asset.gates, + ...asset.distances.slice(1).map((gate) => gate * (1 - this.#lodHysteresis)), + ]; + asset.threshold = Math.min(...asset.gates); + } + this.#assets.set(id, asset); + for (const gate of asset.gates) this.#host.noteGate(gate); + return asset; + } + + acquire(run: IWorldRun, cell: IResidentCell): void { + const id = this.#host.canonical(run.asset); + let asset = this.#assets.get(id); + if (asset === undefined) { + const definition = this.#manifest.assets[id]; + if (definition === undefined) return; + asset = this.newAsset(id, definition); + } + asset.refcount += 1; + if (asset.levels.length > 0) { + this.#host.queueBuild(asset, cell, run); + return; + } + if (!asset.pending) this.startAssetLoad(asset); + } + + /** + * One model, from the loader the package was given. `loadModel` keeps the override and takes the + * authored url, so a game's own GLB loader is unchanged; without it the logical path goes to + * `assets.model`, which is what makes a compiled package and its KTX2 textures work. + */ + model(path: string): Promise { + return this.#loadModel === undefined + ? loadModelWith(this.#loader, path) + : this.#loadModel(resolveRelative(this.#baseUrl, path)); + } + + /** + * Every level of one asset, through the same limiter and the same resolution as `lod0`, so a + * package's compiled output and compressed textures reach the renderer the game booted with. One + * level refusing is not the asset refusing: it is counted and `#adoptAsset` falls back to the + * level below it, so the far placements still draw. + */ + startAssetLoad(asset: IAssetState): void { + asset.pending = true; + // The source models this state asks `assets.model` for are this world's to hand back; the + // loader caches by logical path and would otherwise pin every texture for the world's life. + // Counted once per state, so a load retried after an empty answer does not double-count. + // `loadModel` overrides the cache entirely, so it retains nothing. + if (this.#loadModel === undefined && asset.retainedPaths.length === 0) { + this.retainModelPaths( + Array.from(asset.glbs, (glb) => resolveRelative(this.#logicalBase, glb)), + asset.retainedPaths, + ); + } + const wanted = (): boolean => + !this.#host.released() && this.#assets.get(asset.id) === asset && !asset.disposed; + // `Promise.all` hands back the levels in `glbs` order, and the limiter is what bounds them: it + // already caps every load in flight across the resident cells, assets and chunks together. + const loads: Array> = []; + for (const glb of asset.glbs) { + const path = resolveRelative(this.#logicalBase, glb); + loads.push( + this.#limiter + .load(() => this.model(path), wanted) + .catch((error: unknown) => { + asset.failed = true; + this.#host.setFailures(this.#host.failures() + 1); + this.#host.reportCellFailure("asset", asset.id, [path], error); + return undefined; + }), + ); + } + // The cooked url LOD0 was actually fetched from, resolved the same way `assetSignature` does, + // so the atlas key names the bytes rather than the package entry. It is needed before adoption, + // so the two promises are awaited together; a path the loader cannot resolve falls back to the + // logical url, exactly as `assetSignature`'s caller does. + const lod0 = resolveRelative(this.#logicalBase, asset.glbs[0] as string); + const resolved = this.#loader + .resolve(lod0) + .then((candidates) => candidates[0] ?? lod0) + .catch(() => lod0); + void Promise.all([Promise.all(loads), resolved]).then(([models, url]) => { + // The state stays, refcount and all, whatever the loads answered: cells still resident are + // holding it, and the next acquire retries. Dropping it here would let a later cell refcount + // from zero and hand an eviction of an old cell the geometry a still-resident cell draws. + asset.resolvedGlb = url; + asset.pending = false; + this.#adoptAsset(asset, models); + }); + } + + /** One loaded model's drawable parts, or `undefined` when it carries nothing to draw. */ + #partsOf(model: Object3D | undefined): readonly IAssetPart[] | undefined { + if (model === undefined) return undefined; + const parts = renderableParts(model, (material) => this.#surfaceFor(material)); + if (parts.length === 0) { + // Same as a refused load: counted, released, and the next acquire retries. + this.#host.setFailures(this.#host.failures() + 1); + this.#host.setFailures(this.#host.failures() + this.disposeLoaded(model)); + return undefined; + } + return parts; + } + + /** + * One part list per loaded model: level 0 is the asset's own shape, and every level below it + * falls back to the one above when it did not load. `undefined` is the asset refusing to exist — + * no `lod0` to fall back to — and the levels that did load are released rather than held by an + * asset that draws nothing, cutout clones included. The refcounted state stays either way, so the + * next acquire retries. + */ + #adoptLevels( + models: readonly (Object3D | undefined)[], + ): readonly (readonly IAssetPart[])[] | undefined { + const levels: (readonly IAssetPart[])[] = []; + for (const [index, model] of models.entries()) { + const level = this.#partsOf(model) ?? levels[index - 1]; + if (level === undefined) { + for (const model of models.slice(index)) + this.#host.setFailures(this.#host.failures() + this.disposeLoaded(model)); + for (const parts of levels) + this.#host.setFailures(this.#host.failures() + this.#releaseParts(parts)); + return undefined; + } + levels.push(level); + } + return levels; + } + + /** + * An asset's levels are in, so every resident cell holding it needs its batches — queued, not + * built. This is the path that used to be worst: adopting one asset rebuilt every resident cell's + * batches for it in a single frame, and a 218-asset package pays it over and over as the ring + * moves. The queue turns it into the same per-frame allowance every other admission path gets. + */ + #adoptAsset(asset: IAssetState, models: readonly (Object3D | undefined)[]): void { + if (this.#host.released() || this.#assets.get(asset.id) !== asset || asset.disposed) { + for (const model of models) + this.#host.setFailures(this.#host.failures() + this.disposeLoaded(model)); + return; + } + asset.hlodSafe = models[0] !== undefined && cellProxySourceSafe(models[0]); + const adopted = this.#adoptLevels(models); + if (adopted === undefined) { + // A far-only asset that would not load has nothing to bake; drop the temporary reference so + // the source it did fetch is not held for the world's whole life. + if (this.#host.hasFarTemp(asset.id)) this.#host.releaseFarTemp(asset.id); + return; + } + // Before the prewarm and the queued builds, both of which read the levels, the distances and + // the gates: an asset whose chain widened it is batched at those levels from its first build. + asset.levels = this.#widenWithChain(asset, adopted); + // A far-only asset is drawn by no near run, so it takes no prewarm and queues no cell build: + // its whole job is the atlas behind the aggregate, and its source is released once that lands. + if (this.#host.hasFarTemp(asset.id)) { + this.#host.maybeQueueImpostor(asset); + // Nothing to bake (no alpha-cutout LOD0) and nothing queued: release the source now. + if (asset.impostor === undefined && !this.#host.hasQueuedImpostor(asset.id)) + this.#host.releaseFarTemp(asset.id); + return; + } + // After the chain, so the impostor's switch sits beyond every synthesized gate; before the + // prewarm, so a cached atlas appends its level in this same admission. + this.#host.maybeQueueImpostor(asset); + // Every level and part of the asset now, before any of them is asked for. A level with no + // placement in it is not drawn and would otherwise mint its mesh — and pay its node build — on + // the frame a walk first moves far enough to switch into it. + this.#host.queuePrewarm(asset); + for (const cell of [...this.#host.residentCells()]) { + for (const run of cell.cell.runs) { + if (this.#host.canonical(run.asset) === asset.id) this.#host.queueBuild(asset, cell, run); + } + } + } + + /** + * An asset's loaded levels, widened with the ones its model's baked AutoLOD chain carries. + * + * An asset with authored `lods` keeps its own switch distances and its own middle geometry: each + * level draws the shape the pipeline authored for it, with the root LOD0's alpha cutouts appended + * to any level that authored fewer of them so no middle loses its leaves; see + * `coverAuthoredLevels`. A chain the loader also happens to have registered is a fallback for a + * package that named no `lods`, not a second opinion over one that did. Everything downstream is + * unchanged either way — a level is a level, and its switch is a gate like any other — so the + * batch keys, the prewarm, the caster clusters and the retire/rebind pool all see the derived + * levels as ordinary ones. + */ + #widenWithChain( + asset: IAssetState, + levels: readonly (readonly IAssetPart[])[], + ): readonly (readonly IAssetPart[])[] { + if ((asset.definition.lods?.length ?? 0) > 0) + return this.#preserveAuthoredParts ? levels : coverAuthoredLevels(levels); + // An empty list names no authored rung. Prepared static parts can use their existing chain, + // while sectioned/deforming or replaced-attribute copies keep their supplied source shape. + if (asset.definition.lods !== undefined && !(levels[0] ?? []).every(preparedChainSafe)) + return levels; + const chained = chainLevels( + levels[0] as readonly IAssetPart[], + this.#autoLodPixelsPerUnit, + this.#autoLodMaxPixelError, + asset.definition.maxDistance, + ); + if (chained === undefined) return levels; + asset.distances = chained.distances; + asset.gates = [...chained.distances.slice(1), ...asset.gates]; + for (const gate of asset.gates) this.#host.noteGate(gate); + asset.threshold = asset.gates.length === 0 ? undefined : Math.min(...asset.gates); + this.#reportChainLod(asset.id, chained); + return chained.levels; + } + + /** + * `TN_WORLD_LOD_CHAIN`, one line per asset that was widened: how many levels it is now drawn at, + * where each takes over in metres to one decimal, and what each submits in triangles. Level 0 + * leads, so the first triangle count is what the asset submits today and the last is what it + * submits past the last switch: the reduction is the whole point of the line. + */ + #reportChainLod(id: string, chained: IChainLevels): void { + this.#chainLodAssets += 1; + if (this.#chainLodAssets > CHAIN_LOD_MARKER_LIMIT) { + if (this.#chainLodSummarised) return; + this.#chainLodSummarised = true; + console.info( + `TN_WORLD_LOD_CHAIN more than ${String(CHAIN_LOD_MARKER_LIMIT)} assets carry a chain; ` + + `the rest are one line: assets=${String(this.#chainLodAssets)}`, + ); + return; + } + const listed: string[] = []; + for (const distance of chained.distances) listed.push(distance.toFixed(1)); + console.info( + `TN_WORLD_LOD_CHAIN ${id}: levels=${String(chained.levels.length)} ` + + `distances=${listed.join(",")} tris=${chained.triangles.join(",")}`, + ); + } + + /** + * Release every part's shape and every surface it owns — its own material and, for a scattered + * transparent part, the cutout clone made for it. + * + * At most once per resource, the same contract `release` gives the single-shape case: two parts + * of one GLB routinely share a material, a level that fell back shares the level below's whole + * part list, and every cell batch of the asset drew the same one. A clone is a resource this + * class made, so it is released here and nowhere else. + * + * @returns how many teardowns threw, for the caller to count. + */ + /** + * Tear down a model the loader handed this world, respecting who owns the loader. + * + * A model from the loader this world created is the world's to dispose. A model from an + * explicitly supplied loader is the caller's — another world or game may hold the same cached + * scene — so this world drops its batches but leaves the shapes and surfaces alone; the caller's + * own `release` ends that lifetime. See `#releaseModelPaths`. + */ + disposeLoaded(model: Object3D | undefined): number { + if (model === undefined) return 0; + return this.#ownsLoader ? disposeModel(model) : 0; + } + + /** + * Release an adopted source shape or authored surface, unless a caller-supplied loader owns it. + * The caller's cache holds another world's copy too, so only a world-owned loader disposes what + * it adopted; a cutout this world built is always its own. See `#disposeLoaded`. + */ + releaseSource(target: { dispose: () => void } | undefined): boolean { + return this.#ownsLoader && release(target); + } + + #releaseParts(parts: readonly IAssetPart[]): number { + let failed = 0; + for (const part of parts) { + if (CellModels.#releasedParts.has(part)) continue; + CellModels.#releasedParts.add(part); + // A parked mesh holds the part's own geometry and material, so once those are released the + // parked mesh can never be handed out again — it would give the next cell that asks for one a + // buffer waiting on a disposed resource. Dropped here, so it does not sit in the pool counting + // against the meshes a walk can park. + dropPooledFor(part.geometry, part.material); + if (this.releaseSource(part.geometry)) failed += 1; + failed += this.#releaseSurface(part.surface); + } + return failed; + } + + /** + * The shared surface for a material's content: the first material seen for a key becomes the + * canonical one (converted to a cutout when it is transparent scatter), and every later material + * with the same content is released on arrival and draws with it instead. + */ + #surfaceFor(source: Material): { readonly key: string; readonly material: Material } { + const key = this.#preserveAuthoredParts ? source.uuid : materialKey(source); + const shared = this.#surfaces.get(key); + if (shared !== undefined) { + shared.users += 1; + if (!shared.owned.includes(source) && this.releaseSource(source)) + this.#host.setFailures(this.#host.failures() + 1); + return { key, material: shared.material }; + } + const drawn = scatterMaterial(source, this.#transparentScatter); + this.#surfaces.set(key, { material: drawn.material, owned: drawn.owned, users: 1 }); + return { key, material: drawn.material }; + } + + /** One user gone; the last one tears the shared surface down. Returns teardowns that threw. */ + #releaseSurface(key: string): number { + const shared = this.#surfaces.get(key); + if (shared === undefined) return 0; + shared.users -= 1; + if (shared.users > 0) return 0; + this.#surfaces.delete(key); + let failed = 0; + // A caller-supplied loader's authored surfaces belong to the caller's cache, like its shapes. + // The cutout clones this world built are still the world's to release — `worldOwned` is the + // marker. See `#releaseSource`. + for (const material of shared.owned) + if ((this.#ownsLoader || worldOwned.has(material)) && release(material)) failed += 1; + return failed; + } + + release(id: string): void { + const asset = this.#assets.get(id); + if (asset === undefined) return; + asset.refcount -= 1; + if (asset.refcount > 0) return; + this.#assets.delete(id); + asset.disposed = true; + // Before anything is released: a pending bake stages this asset's borrowed geometry, and a view + // drawn after the teardown reads a disposed buffer. Then the terminal level's own surface, whose + // geometry and material `#releaseParts` must skip; see `#releaseImpostor`. + this.#host.cancelImpostor(asset); + this.#host.releaseImpostor(asset); + // The last cell drawing this asset is gone, so this is the one teardown its levels get. It is + // also the one a lost device makes expensive, which is why every part goes through `release`: + // at most once per resource, and a level that fell back shares the level below's parts, so one + // shape is torn down once however many levels point at it. One refusal is counted per asset, + // the way one refused `lod0` load was. + const levels = asset.levels; + const spilled = asset.spilled; + asset.levels = []; + asset.spilled = []; + let failed = 0; + failed += this.#host.drainShared(id); + for (const parts of levels) failed += this.#releaseParts(parts); + // The middle levels' own reduced foliage cards, taken out of DRAW but still owned here. The + // released-parts set makes the ones a draw level also holds single-teardown, so a shared surface + // is never dropped early. + failed += this.#releaseParts(spilled); + if (failed > 0) this.#host.setFailures(this.#host.failures() + 1); + this.releaseModelPaths(asset.retainedPaths); + } + + /** + * Note the exact `assets.model` cache paths an owner (an asset's levels, a cell's chunks) asks the + * loader for, in the one world-wide `#modelPaths` map, so the last owner to let a path go can hand + * it back. Both owners share the map, so an asset and a chunk that name the same bytes keep the + * entry alive until neither holds it. + */ + retainModelPaths(paths: readonly string[], into: string[]): void { + for (const path of paths) { + this.#modelPaths.set(path, (this.#modelPaths.get(path) ?? 0) + 1); + into.push(path); + } + } + + /** + * Hand back the exact `assets.model` cache paths this owner requested, so the loader drops the + * source scenes and their textures instead of pinning them for the world's whole life. + * + * A path another owner still names is not released: two definitions that fetch the same bytes but + * carry different `lods`/`maxDistance` are distinct assets, and the one leaving the ring must not + * tear out a cached source the other still draws with. Keyed on the requested logical path, so a + * path this world never asked for is never touched. + * + * Only the loader this world created is released. An explicitly supplied `assets` loader is the + * caller's: another game or world may hold the same cached model, so this world never drops a + * cache entry it does not own — the caller's own `release` is where that lifetime ends. + */ + releaseModelPaths(paths: string[]): void { + for (const path of paths) { + const holders = (this.#modelPaths.get(path) ?? 1) - 1; + if (holders > 0) { + this.#modelPaths.set(path, holders); + continue; + } + this.#modelPaths.delete(path); + if (this.#ownsLoader) this.#loader.release("model", path); + } + paths.length = 0; + } +} diff --git a/packages/core/src/world-cell-prewarm.ts b/packages/core/src/world-cell-prewarm.ts new file mode 100644 index 0000000000..d18e484ab7 --- /dev/null +++ b/packages/core/src/world-cell-prewarm.ts @@ -0,0 +1,445 @@ +import type { Box3, InstancedMesh, Object3D } from "three"; +import { markEngineRenderHook } from "./engine-render-hook.js"; +import { SharedBatch } from "./world-cell-batches.js"; +import type { IBatchShape, IPrewarmEntry } from "./world-cell-data.js"; +import type { IAssetState } from "./world-cells.js"; + +export interface ICellPrewarmHost { + released(): boolean; + parent(): Object3D | null; + invalidateShadows(): void; + backlog(): number; + keysForShadow(): boolean; + hasShared(key: string): boolean; + hasRetired(key: string): boolean; + runMax(asset: string): number | undefined; + residentCells(): number; + extentBounds(): Box3; + smallCaster(asset: string): boolean; + dressMesh(shared: SharedBatch, receiveShadow: boolean): void; + receiveShadow(): boolean; + adoptGpu(shared: SharedBatch, asset: string, key: string, level: number, part: number): void; + setShared(key: string, shared: SharedBatch): void; + attach(shared: SharedBatch): void; + casts(level: number): boolean; + wideOwed(asset: string, part: number): boolean; + followCluster(): string; + wideShape( + asset: IAssetState | undefined, + level: number, + part: number, + fallback: IBatchShape, + ): IBatchShape; +} + +/** + * Prewarmed batches minted per update, and the updates a minted batch must survive before it counts + * as drawn. Three keys its render-object/node cache by `object.uuid`, so every InstancedMesh it has + * never drawn costs a NodeBuilder build the first frame it is projected (~9 ms in the main pass, and + * again in the shadow pass for a level that casts): hundreds of them land in a few seconds of + * walking, which is the walk-time frame cost this pays earlier instead. + * + * The rate is a rate of *rendered* updates, not of `update` calls. Minting a batch is cheap (an + * InstancedMesh and a zeroed buffer) and its build is paid by the next render, so an unbounded queue + * would hand the whole map's seconds of builds to one frame. 8 per update is ~150 ms of build on the + * frame that pays it, which the loading screen covers. + */ +const PREWARM_PER_UPDATE = 8; +/** + * Two updates is the honest floor for "drawn at least once in both the main and the shadow + * context": the mesh is added during one update and the next render projects it. Tracking it per + * context would mean hooking the renderer, which this class does not own. + */ +const PREWARM_WARM_UPDATES = 2; +/** + * Updates a mesh may still be owed a prewarm draw after the gate settled before its borrow is handed + * back anyway. An empty prewarmed batch draws, so this is the ceiling on the ones that never will — + * a projection that collapsed the world, a camera that never reached the mesh — and not a diagnosis + * of any of them. Without it those meshes would hold their hook for the whole walk. + */ +const PREWARM_RELEASE_UPDATES = 8; +/** The whole ring, as one caster cluster: `@*` is every square's records in one mesh. */ +export const WIDE_CLUSTER = "*"; + +export class CellPrewarm { + /** + * Every `asset:level:part` of a loaded asset is minted empty, before any placement wants it, so + * the node build lands in the loading phase instead of the frame a cell row first draws the key. + * `#prewarmQueue` is the keys still to mint, `#prewarmKeys` the ones queued (a retried load must + * not queue the same key twice), and `#prewarmPending` the ones minted but not yet + * `PREWARM_WARM_UPDATES` updates old. `prewarmed` is the game's gate for "safe to show me this + * world"; `stats().pendingPrewarm` is the same thing as a number. + */ + readonly #prewarmQueue: IPrewarmEntry[] = []; + readonly #prewarmKeys = new Set(); + #prewarmPending = 0; + /** Every mint so far, so a loading bar can read minted / (minted + pending). */ + #prewarmMinted = 0; + /** + * Minted meshes whose first draw has been submitted, and the meshes still owed one. A mint is + * only half the work: the node is built when the mesh is first submitted, and that is the frame + * the loading bar used to sit still for. `pendingPrewarm` reports this pair, so the bar counts the + * draw half instead of jumping from minting straight to done. The gate is a promise about batches + * (`#prewarmPending`), never about these: a companion no shadow level covers is owed a draw + * forever, and the gate must not wait on it. + */ + #prewarmOwed = 0; + #prewarmDrawn = 0; + #prewarmEpoch = 0; + #prewarmWait = 0; + /** + * Awaited meshes that only a shadow level can draw, and the share of them that have had one. + * + * A caster's node is built in the shadow context, so a walk-time first shadow draw of a streamed + * key is a `NodeBuilder.build` inside a shadow pass — a third of the shadow lane's CPU on the + * 2 km walk. Counting them separately is what `TN_WORLD_PREWARM`'s `shadowPrewarmed` reports and + * what the gate waits on; a count of "every batch drew once" cannot tell the two apart. + */ + #prewarmCasterMinted = 0; + #prewarmCasterOwed = 0; + #prewarmCasterDrawn = 0; + /** + * Meshes still carrying a prewarm borrow, with the hook each one has to be handed back, the batch + * holding it visible, and the post-settle updates it has waited; see + * {@link #releaseImpossibleBorrows}. + */ + readonly #awaited = new Map< + InstancedMesh, + { + batch: SharedBatch; + borrow: unknown; + own: unknown; + hadOwn: boolean; + waited: number; + epoch: number; + } + >(); + #prewarmSettled = false; + #prewarmAsked = false; + #prewarmResolve: () => void = () => { + // replaced in the field initialiser below, once `this` exists + }; + readonly #prewarmed: Promise = new Promise((resolve) => { + this.#prewarmResolve = resolve; + }); + + /** + * Mint this update's prewarm allowance, then settle the readiness signal. + * + * A batch minted while the world is not in a scene is projected by nothing and builds nothing, so + * the ready count only advances on an update that had somewhere to draw: a `load()` loop filling + * the ring mints the whole ring and still leaves the gate shut, which is the honest answer. + */ + drainPrewarm(): void { + if (this.#host.released()) return; + // The loading rate drops to the same 2-per-update a fresh mesh costs once the loading screen is + // gone. 8 per update is ~150 ms of node builds on the frame that pays it, and the loading + // screen is the only thing that was covering it: a walk that streams a new asset in afterwards + // used to hand that frame's builds to a frame that is trying to hold its rate. + const perUpdate = this.#prewarmSettled ? 2 : PREWARM_PER_UPDATE; + let minted = 0; + while (minted < perUpdate && this.#prewarmQueue.length > 0) { + const entry = this.#prewarmQueue.shift() as IPrewarmEntry; + this.#prewarmKeys.delete(entry.key); + this.#mintPrewarm(entry); + minted += 1; + } + // A minted batch is only *owed* a draw by `#awaitDraw`; nothing made anything draw it. A main + // batch is drawn by the next main render, but a caster is drawn by a shadow level, and the + // levels render when their window moves or something invalidates them — on a loading screen the + // follow point stands still, so every caster minted after the first frame sat in the graph with + // a node no level had ever asked for and the walk's first window move built all of them in one + // shadow pass. Invalidating while the gate is open, minting or not, is what makes the prewarm + // prewarm: the build is paid by the loading screen, which is the only frame rate hiding it. + if (minted > 0) this.#prewarmWait = 0; + // A caster's draw is a shadow level's, and the levels render at most one per frame, so the gate + // keeps asking them to while one is owed — bounded, because a borrow nothing draws is handed + // back by `#releaseImpossibleBorrows` below and a world that never draws one settles anyway. + const waitingOnCasters = this.#host.parent() !== null && this.#prewarmCasterOwed > 0; + if (minted > 0 || waitingOnCasters) this.#host.invalidateShadows(); + else if (this.#host.parent() !== null) this.#prewarmWait += 1; + this.#releaseImpossibleBorrows(); + // Nothing to settle until an asset has asked; once one has, an ask whose keys were all already + // built minted nothing and is owed nothing, so it settles like a drained one. + if (this.#prewarmQueue.length > 0 || (this.#prewarmPending <= 0 && !this.#prewarmAsked)) return; + if (this.#prewarmWait < PREWARM_WARM_UPDATES) return; + // A caster still owed a draw is a build the walk would otherwise pay inside a shadow pass, so the + // gate holds for it until its borrow is handed back, which is the bound. + if (waitingOnCasters) return; + // A build still owed draws nothing until it lands. When the scene comes up over a ring built + // without it, that is the whole ring re-seeded: the gate settled over 1,378 owed builds and a + // third of Machinefall's forest arrived in the first 4 s of play (PRD-478). The loading screen + // covers them instead. + if (this.#host.backlog() > 0) return; + this.#prewarmPending = 0; + // The gate is settled, so nothing is owed a draw. A companion no shadow level covers leaves its + // pair non-zero, and `pendingPrewarm` is documented `0` from here on. + this.#prewarmOwed = 0; + this.#prewarmDrawn = 0; + // Retained first-draw hooks belong to the counters just cleared, not later streamed work. + this.#prewarmEpoch += 1; + this.#prewarmWait = 0; + this.#settlePrewarm(); + } + + /** + * The empty batch for one prewarmed key, built exactly as `#batchFor` builds the real one so a + * placement that later wants it draws into this mesh and keeps the node three built for it. Both + * halves of the split are queued: the main key, and the follow point's own caster cluster, which + * is the one the first shadow level render asks for. + */ + #mintPrewarm(entry: IPrewarmEntry): void { + // A world whose shadow levels draw its keys mints no caster half: the two are the same + // placements by two routes, and a level's map can only draw one of them, so a prewarmed half is + // a build behind the gate that pays for nothing and an owed draw the gate waits on until it + // gives up. Decided at the mint rather than at the queue, which is filled before the first + // renderer hands the GPU scene over. The shadow keys are prewarmed where they are minted + // instead; see `#shadowKeyFor`. + if (entry.role !== "main" && this.#host.keysForShadow()) return; + // A key the retained set is holding needs no prewarmed batch: `#batchFor` rebinds that mesh + // when the walk comes back, and rebinding is the whole point of keeping it. The prewarm did not + // ask, so it minted a second InstancedMesh for a key that already had one — a uuid three built a + // node for twice, in the main pass and in every shadow pass, for the whole of a walk back. + if ( + this.#host.released() || + this.#host.hasShared(entry.key) || + this.#host.hasRetired(entry.key) + ) + return; + const shared = new SharedBatch( + entry.geometry, + entry.material, + this.#host.runMax(entry.asset) ?? 1, + this.#host.residentCells() + 1, + entry.key, + this.#host.extentBounds(), + entry.role, + ); + shared.smallCaster = this.#host.smallCaster(entry.asset); + this.#host.dressMesh(shared, entry.role === "main" && this.#host.receiveShadow()); + // Dressed here, before the walk that fills it: the prewarm is what mints every key of an asset + // before its first placement is swapped, so this is where the whole gate table comes from. + this.#host.adoptGpu(shared, entry.asset, entry.key, entry.level, entry.part); + this.#host.setShared(entry.key, shared); + this.#host.attach(shared); + this.#prewarmPending += 1; + this.#prewarmMinted += 1; + // Every role is owed a draw by `#awaitDraw` and is shown while empty until that draw is counted: + // the node, the bindings and the pipeline are built by a submission, and one zero-count + // submission per prewarmed batch is the price of having the walk never pay for it. A caster's + // submission is a shadow level's, not the main pass's — which is why `#drainPrewarm` keeps the + // levels rendering while one is owed, and why `VirtualShadowNode`'s `#probe` renders both caster + // layers when it finds one. A caster that is not awaited here is built on the first shadow draw + // of the walk, inside a shadow pass, which is the cost this prewarm exists to move. + this.awaitDraw(shared); + } + + /** Count this batch's first submitted draw, once, then hand `onBeforeRender` back to whoever had it. */ + awaitDraw(shared: SharedBatch): void { + const mesh = shared.mesh; + const caster = shared.role !== "main"; + this.#prewarmOwed += 1; + if (caster) { + this.#prewarmCasterMinted += 1; + this.#prewarmCasterOwed += 1; + } + shared.awaitPrewarmDraw(); + // three's `Object3D` carries an inherited no-op, so reading `onBeforeRender` off a fresh mesh + // hands back a function that was never an own property. Writing it back as one leaves every + // batch that has drawn since looking permanently hooked, and the projection's `hasRenderHook` + // reads own properties — so the borrow has to remember whether there was one to give back. + const hadOwn = Object.hasOwn(mesh, "onBeforeRender"); + this.#borrowDraw(shared, mesh, hadOwn, hadOwn ? mesh.onBeforeRender : undefined); + } + + /** + * The borrow that counts `mesh`'s first submitted draw and then gives the mesh its own hook back, + * keyed by the mesh object — so an object that is replaced has to be handed the borrow of the one + * it replaces; see `#carryPrewarm`. + */ + #borrowDraw( + shared: SharedBatch, + mesh: InstancedMesh, + hadOwn: boolean, + own: unknown, + epoch = this.#prewarmEpoch, + ): void { + const caster = shared.role !== "main"; + const borrow = own as ((...args: unknown[]) => void) | undefined; + const counted = (...args: unknown[]): void => { + if (epoch === this.#prewarmEpoch) this.#prewarmDrawn += 1; + if (caster) { + this.#prewarmCasterOwed -= 1; + this.#prewarmCasterDrawn += 1; + } + this.#handBack(mesh, hadOwn, own); + this.#awaited.delete(mesh); + // The node is built and the pipeline is compiled: the batch owes nothing more, so an empty one + // goes back to hiding. + shared.prewarmDrew(); + borrow?.(...args); + }; + // Engine bookkeeping, not a claim on the mesh: the scene-render projection's `hasRenderHook` + // ignores a marked borrow, so a streamed world's prewarmed batches stay eligible for the + // collapse they exist to be folded into. Unmarked, 202 of them permanently declined it. + markEngineRenderHook(counted); + this.#awaited.set(mesh, { batch: shared, borrow: counted, own, hadOwn, waited: 0, epoch }); + mesh.onBeforeRender = counted as typeof mesh.onBeforeRender; + } + + /** + * Hand back the borrow of a mesh whose prewarm draw can never come, so no hook outlives the + * prewarm it served. + * + * A mesh that is merely empty now draws — `visible` at count 0 is what builds its node and its + * pipeline, and the borrow is counted off that one submission — so emptiness is no longer a + * reason to release anything. What is left is a mesh nothing can draw, and which mesh that is + * depends on the role: a main batch is drawn by the main pass, so a world projected by nothing + * (`parent === null`, which is a `load()` loop's prewarm and settles the moment the world joins a + * scene) still owes it one, while a *caster* is drawn by a shadow level, and a world nothing + * projects has no level to draw it — its visible-at-count-0 exception is handed straight back + * rather than held for the whole load. Otherwise a level is a frame's schedule away and the + * borrow waits `PREWARM_RELEASE_UPDATES` updates for it. That is a ceiling rather than a + * diagnosis — a projection that collapsed the world, a camera that never reached the mesh, a game + * that passed no `shadows.invalidate` for the levels to be told by — and it is here so those + * meshes neither hold a hook nor sit visible at count 0 for the whole walk. + */ + #releaseImpossibleBorrows(): void { + const projected = this.#host.parent() !== null; + for (const [mesh, entry] of [...this.#awaited]) { + entry.waited += 1; + const caster = entry.batch.role !== "main"; + if (projected === true && entry.waited < PREWARM_RELEASE_UPDATES) continue; + if (projected === false && caster === false) continue; + if (mesh.onBeforeRender === entry.borrow) this.#handBack(mesh, entry.hadOwn, entry.own); + this.#awaited.delete(mesh); + if (caster) this.#prewarmCasterOwed -= 1; + entry.batch.prewarmDrew(); + } + } + + /** Give a borrowed mesh back the hook it had, or none at all when it never had one of its own. */ + #handBack(mesh: InstancedMesh, hadOwn: boolean, own: unknown): void { + if (hadOwn) mesh.onBeforeRender = own as typeof mesh.onBeforeRender; + // Deleted rather than assigned `undefined`: three calls `object.onBeforeRender(...)` + // unconditionally, and an own `undefined` shadows the prototype's no-op and throws. + // quality-allow: the hook must go back to the prototype, which only `delete` can restore. + // biome-ignore lint/performance/noDelete: restoring the prototype lookup is the point. + else delete (mesh as Partial).onBeforeRender; + } + + /** + * Every level and every part of one asset, queued to be minted empty: the main key, and the one + * caster cluster under the follow point. One square because a cluster per cell of the map would + * be a prewarm of the whole package, and the squares a walk reaches first are minted by + * residency, two an update. + */ + queuePrewarm(asset: IAssetState): void { + this.#prewarmAsked = true; + const wideKey = `${asset.id}:*@${WIDE_CLUSTER}`; + for (const [level, parts] of asset.levels.entries()) { + for (const [part, entry] of parts.entries()) { + const key = `${asset.id}:${String(level)}:${String(part)}`; + for (const role of ["main", "cluster", "wide"] as const) { + // Only a level that casts has a caster half at all; see `#casts`. + if (role !== "main" && !this.#host.casts(level)) continue; + // One whole-asset representation covers the far shadow; see `#wideOwed`. + if (role === "wide" && !this.#host.wideOwed(asset.id, part)) continue; + // A baked impostor's far half is one mesh per asset, not one per level; prewarm the mesh the + // handoff will keep rather than a per-level key it would retire one frame later. + const name = + role === "main" + ? key + : role === "wide" && asset.impostor !== undefined + ? wideKey + : `${key}@${role === "cluster" ? this.#host.followCluster() : WIDE_CLUSTER}`; + // A retained key is already paid for; see `#mintPrewarm`. + if ( + this.#host.hasShared(name) || + this.#host.hasRetired(name) || + this.#prewarmKeys.has(name) + ) + continue; + // The wide half is prewarmed with the same shape the swap draws; see `#wideShape`. + const shape = role === "wide" ? this.#host.wideShape(asset, level, part, entry) : entry; + this.#prewarmKeys.add(name); + this.#prewarmQueue.push({ + asset: asset.id, + role, + geometry: shape.geometry, + key: name, + level, + material: shape.material, + part, + }); + } + } + } + } + + #settlePrewarm(): void { + if (this.#prewarmSettled) return; + this.#prewarmSettled = true; + // The one line a walk's log needs to say the prewarm paid what it exists to pay: + // `shadowPrewarmed` is the casters whose shadow-context node was built behind the gate, and + // `castersUnbuilt` the ones it could not reach — a non-zero there is the walk-time first shadow + // build the prewarm was supposed to move, named rather than inferred. + console.info( + `TN_WORLD_PREWARM minted=${String(this.#prewarmMinted)} ` + + `shadowPrewarmed=${String(this.#prewarmCasterDrawn)} ` + + `castersUnbuilt=${String(Math.max(0, this.#prewarmCasterMinted - this.#prewarmCasterDrawn))}`, + ); + this.#prewarmResolve(); + } + + /** + * Hand a replaced mesh's prewarm borrow to the object that replaced it. + * + * The borrow is keyed by the mesh object and counts *that* object's first submitted draw, so an + * object that goes away before its draw would take the owed draw with it: the batch would sit + * visible at count 0 for the rest of the load, and the owed counters would never balance. Nothing + * new is prewarmed here — the batch still owes exactly the one draw it owed. + */ + carryPrewarm(old: InstancedMesh, next: InstancedMesh): void { + const entry = this.#awaited.get(old); + if (entry === undefined) return; + this.#awaited.delete(old); + this.#borrowDraw(entry.batch, next, entry.hadOwn, entry.own, entry.epoch); + } + constructor(readonlyHost: ICellPrewarmHost) { + this.#host = readonlyHost; + } + readonly #host: ICellPrewarmHost; + get prewarmed(): Promise { + return this.#prewarmed; + } + get queued(): number { + return this.#prewarmQueue.length; + } + get pending(): number { + return this.#prewarmPending; + } + get owed(): number { + return this.#prewarmOwed; + } + get drawn(): number { + return this.#prewarmDrawn; + } + get minted(): number { + return this.#prewarmMinted; + } + get settled(): boolean { + return this.#prewarmSettled; + } + releaseHooks(): void { + for (const [mesh, entry] of [...this.#awaited]) { + if (mesh.onBeforeRender === entry.borrow) this.#handBack(mesh, entry.hadOwn, entry.own); + } + this.#awaited.clear(); + } + settleDisposal(): void { + this.#prewarmQueue.length = 0; + this.#prewarmKeys.clear(); + this.#settlePrewarm(); + } +} diff --git a/packages/core/src/world-cell-render.ts b/packages/core/src/world-cell-render.ts new file mode 100644 index 0000000000..49faf13b8f --- /dev/null +++ b/packages/core/src/world-cell-render.ts @@ -0,0 +1,2552 @@ +import type { IAssetState } from "./world-cell-models.js"; + +import type { + IAssetPart, + IBatchShape, + IBuildJob, + ICamera, + ICellBatch, + IKeyDescriptor, + IResidentCell, + IShadowRegion, + IWorldCellsBudget, + IWorldCellsFollow, + IWorldCellsStats, +} from "./world-cell-data.js"; +import { PLACEMENT_RECORD_FLOATS, cullDistance, isAlphaFoliage } from "./world-cell-impostors.js"; + +import { clusterOf } from "./world-cell-data.js"; + +import { + Box3, + BufferGeometry, + type Camera, + Frustum, + InstancedMesh, + type Material, + Matrix4, + type Mesh, + type Object3D, + Quaternion, + Vector3, +} from "three"; +import { BundleGroup } from "three/webgpu"; + +import { lodChainOf, setLodBias } from "./model-lod.js"; +import type { OcclusionMode } from "./render/depth-pyramid.js"; + +import { parkMesh } from "./render/mesh-pool.js"; +import type { IRendererLike } from "./renderer.js"; +import { isStatic, markStatic } from "./static-transform.js"; + +import { BundleSurfaceSafety, bundleSafe, ownHook } from "./world-bundle-safety.js"; +import { type ICasterScaleMesh, SharedBatch, sameSquares } from "./world-cell-batches.js"; +import { levelTriangles, release } from "./world-cell-chunks.js"; +import { WIDE_CLUSTER } from "./world-cell-prewarm.js"; +import { + CHUNK_NAME, + VIRTUAL_SHADOW_CASTER_LAYER, + VIRTUAL_SHADOW_WIDE_CASTER_LAYER, +} from "./world-chunk-shadow.js"; +import { + DRAW_ARGS_BYTES, + type IAssetSlot, + type ILiveAsset, + type IMeshDraw, + type IShadowLevel, + WorldGpuScene, + gpuSceneValidationRequested, + liveKeyInstances, +} from "./world-gpu-scene.js"; + +import { + type IWorldAsset, + type IWorldCell, + type IWorldPackage, + type IWorldRun, + cellPlacements, +} from "./world-package.js"; + +/** + * Instance-matrix bytes a world keeps in released shared batches, oldest dropped first. A batch is + * sized to its asset's largest cell times the resident cells, so on a 2 km package one runs 80 kB + * median and 0.9 MB worst, and every key in the world at once is 26 MB. The budget holds the walk's + * own back-traffic — a batch leaves when the last cell holding its asset leaves, and is wanted + * again a few hundred metres back — and lets the rest go, so a session that crosses the whole map + * does not carry the whole map. + */ +const RETIRED_SHARED_BYTES = 8_000_000; + +/** + * How far past the frustum a world-grid square is kept inside the main pass's draw window, in metres. + * + * The window is decided once per draw, from the camera that draw uses, and a square that has just + * left the frustum is dropped from it — so a square at the frustum's edge would be drawn in one + * frame and not the next, and a tree on that edge pops in at the edge of the screen. Growing each + * square's box by this margin keeps a band of it in the window while it is still off screen, so a + * tree crosses into view already there. + * + * An eighth of the default 64 m square is deliberately small. It has to exceed what a camera + * travels between two draws — 8 m a frame is 480 m/s at 60 fps, so no playable camera reaches it — + * and nothing more, because every metre of it is a metre of geometry the vertex stage still shades. + */ +const MAIN_CULL_PAD_METRES = 8; + +/** + * Side of the main cull's own visibility cell, in metres, when it divides the package's `cellSize`. + * + * This is the finest grid the main pass can cull on, and it is not a free parameter: a shared + * batch's block is one *world* cell's records and placements never move, so a world cell cannot be + * spread across two cells of a finer grid without giving every world cell its own block — one more + * main mesh per key, which is the cost this whole cull exists to avoid. So a package whose + * `cellSize` is not a whole multiple of 32 m falls back to its own `cellSize`, which is the square + * its placements are already cut on, and the rule below is the whole of it. + * + * A visibility cell is a sub-square of the shadow caster's world-grid square, and it is deliberately + * finer than that square: `clusterSize` is sized for how many meshes a shadow level submits, while + * this is sized for how little geometry the vertex stage is handed. It is also the one volume the + * cull tests, so everything in it — a 64 m cell's XZ and the Y its own placements and asset bounds + * reach — is what a draw of that cell costs. See {@link WorldCells.#cullMainPass}. + */ +export const MAIN_CULL_SQUARE_METRES = 32; + +/** + * How often `TN_WORLD_MAIN_CULL` is printed. Five seconds is a line a playtest capture can hold and + * a walk long enough to see the window move; every frame is a wall of text over the marker it would + * report on. + */ +const MAIN_CULL_MARKER_MS = 5e3; + +/** + * The wide casters only the finest level renders: a fern's or a grass tuft's, whose shadow is + * sub-texel noise past a few metres and whose per-key draw a 192 m or 640 m window was paying + * hundreds of times for nothing. Duplicated from `virtual-shadow.ts` like the two layers above, and + * chosen from the asset's own authored bounds rather than from what a game says, so a package + * nobody has annotated still gets the cull. `shadows.smallCasterMetres` is the override. + */ +const VIRTUAL_SHADOW_SMALL_CASTER_LAYER = 26; + +/** + * The layer a GPU-driven shadow map draws: one mesh per `asset:level:part`, over the shadow twin of + * the dispatch's own output, so a level submits a handful of indirect draws rather than one per + * world-grid square. Every level camera carries this bit, so it replaces both halves above rather + * than joining the choice between them. Duplicated from `virtual-shadow.ts` like the three above. + */ +const VIRTUAL_SHADOW_KEY_LAYER = 25; + +/** + * The name half a shadow key is minted under: `asset:level:part@gpu`, beside the main key it twins + * rather than in place of it, so both meshes exist — one for the main pass's compaction buffer and + * one for the shadow twin's, which is the whole difference between them. + */ +const GPU_KEY_SUFFIX = "@gpu"; + +/** The two scratch objects a cull builds its frustum in, and whether two square sets are the same. */ +const _cullFrustum = new Frustum(); + +const _cullProjScreen = new Matrix4(); + +/** The eye the live-state reference measures every placement's XZ distance from. */ +const _gpuEye = new Vector3(); + +/** + * How many groups the main record is split into. A record walks every draw in its group, so a mint + * on a streaming walk costs the draws of every shard it touches: Machinefall's ~200 recorded main + * draws re-recorded as one group put ~290 traversed draws in a frame. Eight shards still read 145, + * because one asset mints several keys (levels, parts) at once and they land in two or three shards; + * thirty-two keep a frame that touches several to a few dozen draws. A replay is one + * `executeBundles` per group, so a shard costs far less than the draws it stops re-walking. + */ +const MAIN_BUNDLE_SHARDS = 32; + +/** The shard a key lands in, from its name (FNV-1a), so a key keeps its group for its whole life. */ +export function mainShard(key: string): number { + let hash = 0x811c9dc5; + for (let index = 0; index < key.length; index += 1) { + hash ^= key.charCodeAt(index); + hash = Math.imul(hash, 0x01000193); + } + return (hash >>> 0) % MAIN_BUNDLE_SHARDS; +} + +/** Every mesh a prepared chunk draws through the main pass, in traversal order. */ +function chunkDraws(chunk: Object3D): Mesh[] { + const draws: Mesh[] = []; + chunk.traverse((node) => { + const mesh = node as Mesh; + if (mesh.isMesh === true && node.layers.isEnabled(0)) draws.push(mesh); + }); + return draws; +} + +/** + * A geometry that draws `source`'s shape through an indirect record: its own object, so the record is + * this mesh's, over the same attributes and index, so no vertex buffer is copied. + */ +function indirectView( + source: BufferGeometry, + args: Parameters[0], + offset: number, +): BufferGeometry { + const view = new BufferGeometry(); + for (const [name, attribute] of Object.entries(source.attributes)) + view.setAttribute(name, attribute); + view.setIndex(source.index); + view.morphAttributes = source.morphAttributes; + view.morphTargetsRelative = source.morphTargetsRelative; + for (const group of source.groups) view.addGroup(group.start, group.count, group.materialIndex); + view.boundingBox = source.boundingBox; + view.boundingSphere = source.boundingSphere; + view.setIndirect(args, offset); + return view; +} + +/** The materials `redressMaterial` gave a mesh, disposed when the mesh is or when it is re-dressed. */ +const redressed = new WeakMap(); + +/** + * A new object drawing `geometry`, carrying everything `old` carried. + * + * Three builds an `InstancedMesh`'s instancing node when the object is first compiled, from the + * `instanceMatrix` it holds then, and keeps that node for the object's whole life — so a mesh the + * prewarm already compiled is not pointed at the GPU scene's storage buffer by writing a field onto + * it. Measured on machinefall: 3,724 indirect draws submitted in 2 s, over indirect records and + * drawn matrices that were correct at draw time, and a dressed forest that drew nothing at all, + * until every dressed mesh was a fresh object. A fresh object costs a uuid and a node build, which + * is what the prewarm paid for a mesh that was about to be replaced anyway; see `#dressGpu`. + */ +function freshMeshFor(old: InstancedMesh, geometry: BufferGeometry, count: number): InstancedMesh { + const next = new InstancedMesh(geometry, old.material, count); + next.name = old.name; + next.layers.mask = old.layers.mask; + next.castShadow = old.castShadow; + next.receiveShadow = old.receiveShadow; + next.renderOrder = old.renderOrder; + next.userData = old.userData; + next.matrix.copy(old.matrix); + next.matrixAutoUpdate = old.matrixAutoUpdate; + next.matrixWorld.copy(old.matrixWorld); + next.matrixWorldAutoUpdate = old.matrixWorldAutoUpdate; + // A dressed batch is frozen, and the freeze is keyed by the object: a replacement that did not + // re-arm it would compose its own world matrix every walk forever. + if (isStatic(old)) markStatic(next); + return next; +} + +/** + * Give a mesh a material of its own, so the node three builds for it is its own rather than a shared + * one's, and dispose those materials with the mesh. The clones share every texture; only the + * material objects are new. See `#dressGpu`. + */ +function redressMaterial(mesh: InstancedMesh): void { + for (const material of redressed.get(mesh) ?? []) material.dispose(); + const clones: Material[] = []; + for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) + clones.push(material.clone()); + mesh.material = Array.isArray(mesh.material) ? clones : (clones[0] as Material); + if (!redressed.has(mesh)) + mesh.addEventListener("dispose", () => { + for (const material of redressed.get(mesh) ?? []) material.dispose(); + redressed.delete(mesh); + }); + redressed.set(mesh, clones); +} +export interface ICellRenderHost { + asset(id: string): IAssetState | undefined; + assets(): Iterable; + canonical(id: string): string; + renderer(): IRendererLike | undefined; + camera(): Camera | undefined; + now(): number; + stats(): IWorldCellsStats; + residentCells(): IterableIterator; + runMax(asset: string): number | undefined; + widenFilterRange(cell: IResidentCell, x: number, z: number): void; + addUnchanged(): void; + disableFar(run: IWorldRun): void; + deferWide(id: string): void; + queuedBuilds(): number; + covered(): boolean; + prewarmSettled(): boolean; + awaitDraw(shared: SharedBatch): void; + carryPrewarm(old: ICasterScaleMesh, next: ICasterScaleMesh): void; + add(object: Object3D): void; + isParent(object: Object3D): boolean; + isBundleHome(shared: SharedBatch): boolean; +} +interface ICellRenderSettings { + readonly mainCullToldAt: number; + readonly follow: IWorldCellsFollow; + readonly budgets: IWorldCellsBudget; + readonly cellSize: number; + readonly minX: number; + readonly minZ: number; + readonly manifest: IWorldPackage; + readonly placements: ArrayBuffer; + readonly coveredFreshMeshesPerUpdate: number; + readonly freshMeshesPerUpdate: number; + readonly receiveShadow: boolean; + readonly castShadowLevels: number; + readonly extentBounds: Box3; + readonly preserveAuthoredParts: boolean; + readonly cellsPerCluster: number; + readonly cellsPerCullCell: number; + readonly smallCasterMetres: number; + readonly bundlesWanted: boolean; + readonly bundleReason: string; + readonly invalidateShadows: ((region?: IShadowRegion) => void) | undefined; +} + +interface IPublishedGpuGates extends IAssetSlot { + readonly locals: readonly (readonly Float32Array[])[]; +} + +export class CellRender { + readonly #budgets: IWorldCellsBudget; + + readonly #cellSize: number; + + readonly #manifest: IWorldPackage; + + readonly #minX: number; + + readonly #minZ: number; + + readonly #placements: ArrayBuffer; + + readonly #freshMeshesPerUpdate: number; + + readonly #coveredFreshMeshesPerUpdate: number; + + /** Fresh meshes created this update, and whether a build waited for next frame's allowance. */ + #freshThisUpdate = 0; + + #meshStalled = false; + + /** One shared mesh per `asset:level:part`, holding every resident cell's segment; see SharedBatch. */ + readonly #shared = new Map(); + + readonly #bundleSafety = new BundleSurfaceSafety(); + + /** + * `asset:level:part` -> a released batch kept for the walk back over the same ground, oldest + * first. `#release` used to throw the mesh away with the asset, so a cell that left the ring and + * came back drew into a brand new one — a uuid three had never built a node for, in the main pass + * and in every shadow pass, which is the walk-time frame cost the standing frame rate never pays. + * The batch is empty by then (`#evict` clears a cell's own block before the refcount reaches + * zero), so it is handed back whole; only its parts are gone, and `rebind` replaces them. + */ + readonly #retired = new Map(); + + #retiredBytes = 0; + + readonly #extentBounds: Box3; + + /** How many of an asset's finest levels cast shadows; 0 when the game asked for none. */ + readonly #castShadowLevels: number; + + readonly #receiveShadow: boolean; + + /** Asset bounds height under which a wide caster goes on the finest level's layer only. */ + readonly #smallCasterMetres: number; + + /** The six planes of `#camera`'s frustum, as the live-state reference reads them; reused per check. */ + readonly #gpuPlanes = new Float32Array(24); + + readonly #cellsPerCluster: number; + + /** + * Streamed records changed since the shadow levels were last told, and when they were last told. + * One invalidation batch a second, and only for a game that passed the hook: the companions + * used to make the hook necessary, and now the levels cull clusters themselves. + */ + // `true` so the first `update` runs a residency pass: `#residencyStale` reads it, and a fresh + // world has admitted nothing. + #shadowMoved = true; + + #shadowToldAt = Number.NEGATIVE_INFINITY; + + /** + * Separate unions of evicted and admitted records since the levels were last told. An unknown + * region sets `#shadowFull`, so a narrower later change cannot discard the blanket ask. + */ + #shadowRegion: IShadowRegion | undefined; + #shadowAdmissionRegion: IShadowRegion | undefined; + #shadowFull = false; + + /** When `TN_WORLD_MAIN_CULL` was last printed; see {@link #reportMainCull}. */ + #mainCullToldAt = 0; + + /** Window repacks since the last marker; see {@link #reportMainCull}. */ + #mainCullRepacks = 0; + + /** Frusta the main cull has built, one a frame; see `IWorldCellsStats.mainCull`. */ + #mainCullFrustums = 0; + + /** Windows main batches have had to re-derive, cumulative; see `IWorldCellsStats.mainCull`. */ + #mainCullWindows = 0; + + /** Window repacks, cumulative; see `IWorldCellsStats.mainCull`. */ + #mainCullPacks = 0; + + /** + * One AABB per resident visibility cell, and how many resident world cells hold it. Shared by + * every key, so the main cull is O(visibility cells) a frame rather than O(keys × cells); see + * {@link #cullMainPass}. + */ + readonly #cullCells = new Map(); + + /** World cells per main-cull visibility cell; see `MAIN_CULL_SQUARE_METRES`. */ + readonly #cellsPerCullCell: number; + + /** The visibility cells the last cull kept and the frame's set, trading places; see `#cullMainPass`. */ + #visibleSquares: Set = new Set(); + + #cullScratch: Set = new Set(); + + /** Bumped only when `#visibleSquares` changed, so a settled camera repacks no batch at all. */ + #visibleEpoch = 0; + + /** The game's hook to refresh the shadow levels after streamed records changed. */ + readonly #invalidateShadows: ((region?: IShadowRegion) => void) | undefined; + + readonly #position = new Vector3(); + + readonly #rotation = new Quaternion(); + + readonly #scale = new Vector3(); + + readonly #matrix = new Matrix4(); + + /** Reused transformed authored bounds for admission and conservative visibility volumes. */ + readonly #recordBounds = new Box3(); + + /** + * The GPU-driven main pass, and whether the world asked for it. Off is byte-for-byte this class's + * own CPU path: nothing below reads `on` except the four places the option changes what a frame + * does. See the `gpuScene` load option and `#cullMainPass`. + */ + readonly #gpuScene = new WorldGpuScene(); + readonly #publishedGpuGates = new Map(); + readonly #gpuLevelKeys = new Map>(); + + /** Every main key this world has minted a shadow twin for, which is what a level's dispatch asks for. */ + readonly #shadowKeyNames = new Set(); + + /** Resident placements per canonical asset, which is the capacity a key of that asset needs. */ + readonly #gpuResident = new Map(); + + /** + * The `BundleGroup`s every recorded main batch mesh is parented under, one per shard of + * {@link MAIN_BUNDLE_SHARDS}, and the number of times one has been re-recorded. A shard is minted by + * the first mesh that lands in it, so a world that never dresses a mesh has no group to pay for. + * + * A re-record is a structural change and nothing else: see `IWorldCellsStats.bundle` and + * {@link #bumpBundle}. Streaming, culling and LOD are GPU-side answers and never move it. A record + * walks every draw its group holds, so a mint pays for its own shard and not the whole main pass. + */ + readonly #bundles = new Map(); + + #bundleRecords = 0; + + /** + * Shards re-recorded while a compile was in flight, so they are re-recorded again once it settles. + * Three encodes a draw only when its pipeline is ready, so a record cut during a `compileAsync` + * silently leaves a not-yet-ready draw out, and the replay never re-checks it. Bumped from + * {@link #bumpBundle} while {@link #compileBusy}, flushed at the top of the update that sees the + * compile settle. + */ + #compileSeen = 0; + + #compileBusy = false; + + readonly #recordedWhileBusy = new Set(); + + /** + * One `BundleGroup` per resident cell that has chunks attached, keyed by cell key, each with the + * main-cull cluster whose answer decides whether it is in the frame. Minted by the first chunk a + * cell attaches, so a world with no chunks pays nothing; see the `bundles` option. + * + * Per cell and not one group for the whole world: a record is fixed when it is made, so a group's + * own answer to "is this in view" cannot change without a re-record — which is the whole cost this + * is removing. A cell is the granularity the coarse cull already answers at, so `visible` on the + * group is a write rather than a record. See `#cullMainPass`. + */ + readonly #chunkBundles = new Map< + string, + { readonly cluster: string; readonly group: BundleGroup } + >(); + + /** How many chunk roots those groups hold, so `stats().bundle.children` costs nothing to read. */ + #chunkBundleChildren = 0; + + /** `bundles` as the load asked for it, before the query string and the environment. */ + readonly #bundlesWanted: boolean; + + /** + * Who decided that: the load that named `bundles`, the launch that asked for `off` or `on`, or + * nothing at all — which is what the marker reports so a run on the default is never mistaken for + * one a flag turned off. See {@link bundlesAsked}. + */ + readonly #bundleReason: string; + + readonly #preserveAuthoredParts: boolean; + + /** The resident ring has been handed to the scene once; see `#seedGpuSources`. */ + #gpuSeeded = false; + + /** + * Narrow every main batch's draw window to the world-grid squares `camera`'s frustum covers, once + * per frame, for the whole set at once. + * + * This used to be each main mesh's own `onBeforeRender`, which three never calls for an invisible + * mesh — so the moment a batch that draws nothing was hidden, the one thing that could show it + * again when the camera turned back was gone with it, and the world lost its trees for good. The + * decision therefore moved to the frame's own driver: the render-cadence compute dispatch, which + * the engine already runs once per rendered frame — on the same readiness gate residency itself + * sits behind, so a frame that skips this skipped the world's records too — and which now hands the + * render camera to the world, so it reaches every main mesh whether it is drawn or not. + * + * The camera rules are the ones the hook had, and they are the whole of what decides a window: + * + * - **An orthographic camera never repacks.** A virtual-shadow level's camera is a + * `DirectionalLightShadow`'s own, which three makes orthographic, so a level's window — one shadow + * slice of the ring, deliberately not the main view's — can never overwrite the main pass's. A + * game whose own camera is orthographic keeps the old behaviour: everything resident is drawn, and + * nothing pops. That is a lost saving, never a wrong picture. + * - **The render camera is the one that decides.** The engine dispatches this with the camera it + * is about to draw the main pass with, so the second-perspective-camera race the hook had to + * arbitrate per mesh cannot arise: a mirror or a minimap is drawn with its own camera and is + * served the main view's window, which is the honest trade for a world with one main view. + * - **A set that did not change touches nothing.** The frustum, the visible cells and the epoch + * are the frame's, derived once for the whole pass; a batch whose records have not moved since it + * packed at that epoch compares two integers and returns. A camera at rest costs one frustum and + * one test per resident visibility cell a frame, and nothing at all per mesh. + */ + cullMainPass(camera: Camera | undefined): void { + if (camera === undefined || (camera as ICamera).isOrthographicCamera === true) return; + this.#mainCullFrustums += 1; + const frustum = _cullFrustum.setFromProjectionMatrix( + _cullProjScreen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse), + ); + // The visible visibility cells, for the whole pass, into a set that is reused and compared + // rather than allocated: two sets trading places, so a settled camera allocates nothing at all. + const next = this.#cullScratch; + next.clear(); + for (const [cell, entry] of this.#cullCells) { + // An occupied cell whose placements have not been decoded yet holds nothing to draw, and its + // Y range is still empty — a volume that cannot be tested against ±Infinity is not one this + // frame's answer may rest on. + if (entry.box.isEmpty() === false && frustum.intersectsBox(entry.box)) next.add(cell); + } + if (!sameSquares(next, this.#visibleSquares)) { + this.#visibleEpoch += 1; + const was = this.#visibleSquares; + this.#visibleSquares = next; + this.#cullScratch = was; + this.#cullScratch.clear(); + } + // A chunk's record is frozen, so its cell's answer is the only thing that can take it out of the + // frame without re-recording it: a write per resident cell that has chunks, at the same + // granularity every other coarse gate in this pass uses. See the `bundles` option. + for (const entry of this.#chunkBundles.values()) { + const visible = this.#visibleSquares.has(entry.cluster); + if (entry.group.visible !== visible) entry.group.visible = visible; + } + const gpu = this.#gpuScene; + const dressed: Array<{ asset: string; shared: SharedBatch }> = []; + const version = gpu.version; + for (const shared of [...this.#shared.values()]) { + if (shared.role !== "main") continue; + // A dressed batch draws from the GPU scene's own buffers, so the frame's per-instance answer is + // the dispatch's and this mesh's own window would be a second, stale copy of it. + if (gpu.on && shared.gpu !== undefined) { + this.dressGpu(shared, shared.gpu); + // A bundled mesh is in a render list three fixed when it recorded the bundle, and the + // dispatch has already culled per instance: there is nothing coarse left to hide, and a + // `visible` written here is one the replay never reads. See `#bundleIn`. + if (shared.bundled === false) dressed.push({ asset: shared.gpu.asset, shared }); + continue; + } + // A batch the GPU scene does not own, and every one of a world without it: its own window is + // the CPU path's answer, and it is a per-frame answer a frozen render list never re-reads. So + // the record answers the gate instead of the gate writing `visible` into it — a batch the gate + // shows joins, one it hides leaves, and a window that moved is one more record, because the + // record holds the layout the move made. The same trade a cell's chunk record makes, at the + // granularity this gate already uses. See `#bundleIn`. + const outcome = shared.cullFrom(this.#visibleSquares, this.#visibleEpoch); + this.recordCpuMain(shared, outcome); + if (outcome === "settled") continue; + this.#mainCullWindows += 1; + if (outcome === "repacked") { + this.#mainCullRepacks += 1; + this.#mainCullPacks += 1; + } + } + // A regrow late in the loop replaces the scene's `drawn` — and, a non-grouped key, its `args` — + // after an earlier mesh in the same loop was dressed against the generation before it, so that + // mesh would read a buffer the dispatch no longer writes for one frame. One more pass over the + // same settled set, only when the scene changed under the first, rebinds every mesh to the final + // buffers. Registration is idempotent and every key is already minted and sized, so this pass + // mints nothing and cannot grow the scene again. + if (gpu.on && gpu.version !== version) { + for (const shared of [...this.#shared.values()]) + if (shared.role === "main" && shared.gpu !== undefined) this.dressGpu(shared, shared.gpu); + // And every shadow key, for the same reason: minting one grew the twin buffers every other + // key was dressed against, so a sibling dressed earlier in the loop above is holding the + // generation the dispatch replaced. The main key's own dress brings its twin with it; this is + // what closes the seam for the keys whose main key was dressed before the grow. + for (const shared of [...this.#shared.values()]) + if (shared.role === "key") + this.dressShadowKey(shared, shared.mesh.name.slice(0, -GPU_KEY_SUFFIX.length)); + } + // After every key has been dressed, so the asset's answer is the union across its levels and + // parts. + this.applyGpuMain(dressed); + // After the coarse gate, and only for the main camera: an orthographic one — every shadow + // level's own — returned at the top, so a level's render never dispatches over the main pass's + // compaction. + if (gpu.on) gpu.dispatch(this.#host.renderer() as IRendererLike, camera); + } + + /** + * The coarse gate for the GPU-dressed main pass, computed per asset and reused across every level + * and part of it. + * + * A key's own CPU squares are the level the follow point picked when it built them, and the + * dispatch re-picks the level from the camera every frame — so a key the CPU never gave a cell to + * can be exactly the one the dispatch draws, and a per-key answer hid it: a tree walked into at a + * level it was not built at disappeared. One conservative answer per asset — any key holds a + * visible cell, or still owes its prewarm draw — is what shows all of them, and the indirect + * record decides what each one actually draws. + */ + applyGpuMain(dressed: readonly { asset: string; shared: SharedBatch }[]): void { + const shown = new Map(); + for (const { asset, shared } of dressed) { + if (shown.get(asset) === true || shared.seenIn(this.#visibleSquares)) shown.set(asset, true); + else if (shown.has(asset) === false) shown.set(asset, false); + } + for (const { asset, shared } of dressed) shared.applyGpuVisible(shown.get(asset) === true); + } + + /** Borrowed scratch box: consume its transformed volume before the next bounds query. */ + #boundsAt(bounds: IWorldAsset["bounds"], matrix: Matrix4): Box3 { + this.#recordBounds.min.fromArray(bounds.min); + this.#recordBounds.max.fromArray(bounds.max); + return this.#recordBounds.applyMatrix4(matrix); + } + + /** + * The one volume per resident visibility cell, shared by every key: its own XZ footprint, and in Y + * the range its resident placements actually reach once their assets' own bounds are added to them, + * padded by `MAIN_CULL_PAD_METRES`. + * + * It is derived here, on admission, and never a frame — a volume walked every frame to answer a + * question about placements that never move is a frame's work for an admission's answer. + * + * It is deliberately not per key. Every key drew the same cell set, so deriving it per key meant + * ~230 frusta and ~230 sets a frame to learn the same answer; one volume per cell is what makes the + * pass O(cells) instead of O(keys × cells). + * + * The Y is what makes it a volume rather than a slab. It used to be the whole terrain height range + * widened by the tallest asset bound in the world — ~100 m of Y on a map with a 10 m relief, which + * made every cell's bounding sphere a ~90 m radius that a view beside the world still cleared, and + * handed the vertex stage the whole ring. This is per placement instead: a sub-square of flat + * ground with ferns on it is the height of its ferns. + */ + + cullResident(cell: IWorldCell): void { + const per = this.#cellsPerCullCell; + const side = per * this.#cellSize; + const first = Math.floor(cell.x / per); + const firstZ = Math.floor(cell.z / per); + const name = clusterOf(cell.x, cell.z, per); + let entry = this.#cullCells.get(name); + if (entry === undefined) { + // XZ from the grid, padded, and an empty Y: this cell's own placements are unioned in below, + // and a cell holding none of them yet is never visible rather than visible by its footprint. + const box = new Box3( + new Vector3( + this.#minX + first * side - MAIN_CULL_PAD_METRES, + Number.POSITIVE_INFINITY, + this.#minZ + firstZ * side - MAIN_CULL_PAD_METRES, + ), + new Vector3( + this.#minX + (first + 1) * side + MAIN_CULL_PAD_METRES, + Number.NEGATIVE_INFINITY, + this.#minZ + (firstZ + 1) * side + MAIN_CULL_PAD_METRES, + ), + ); + entry = { box, cells: 0 }; + this.#cullCells.set(name, entry); + } + entry.cells += 1; + const box = entry.box; + for (const run of cell.runs) { + const bounds = this.#manifest.assets[this.#host.canonical(run.asset)]?.bounds; + if (bounds === undefined || run.count === 0) continue; + const records = cellPlacements(this.#placements, run); + for (let index = 0; index < run.count; index += 1) { + const at = index * PLACEMENT_RECORD_FLOATS; + // The placement, widened by its own asset's bounds at the scale it is drawn at, and by the + // pad — a union, so the pad is counted once however many placements there are. + const scale = records[at + 7] as number; + this.#position.fromArray(records, at); + this.#rotation.fromArray(records, at + 3); + this.#scale.setScalar(scale); + this.#matrix.compose(this.#position, this.#rotation, this.#scale); + box.union( + this.#boundsAt(bounds, this.#matrix).expandByScalar( + MAIN_CULL_PAD_METRES * Math.max(1, Math.abs(scale)), + ), + ); + } + } + } + + /** + * Drop the visibility cell an evicted cell sat in once nothing resident is left in it. Records are + * cleared before this runs, so a batch can never be holding a cell the cull no longer knows: an + * unknown cell is not in the visible set, and that would be a tree nobody draws. + * + * The box goes with it, so a cell that comes back derives its volume again — which is what keeps a + * released asset's height out of a cell that no longer holds it. + */ + cullEvicted(cell: IResidentCell): void { + const name = clusterOf(cell.x, cell.z, this.#cellsPerCullCell); + const entry = this.#cullCells.get(name); + if (entry === undefined) return; + entry.cells -= 1; + if (entry.cells <= 0) this.#cullCells.delete(name); + } + + /** + * The render-cadence dispatch. The engine hands the frame's render camera over, and this world's + * main windows follow it; see {@link #cullMainPass} for why the decision is not the meshes' own. + */ + /** New batch meshes this update may create: scaled while the startup cover hides the world. */ + freshAllowance(): number { + return this.#host.covered() ? this.#coveredFreshMeshesPerUpdate : this.#freshMeshesPerUpdate; + } + + /** + * Hand the cell its finished batches and take back the ones they replace, in one step. + * + * This is the no-hole rule. Attaching and detaching in the same synchronous block means no frame + * ever shows the replacement beside the batch it replaces, and a refilter that waits three frames + * for budget shows the level the cell already had for those three frames rather than nothing. + */ + swap(job: IBuildJob): boolean { + const { cell } = job; + // A refilter that rebuilt byte-for-byte the records the cell already draws must write, clear and + // move nothing. `#staleIn` queues on the *cell's* distance bracket, which is wider than any one + // placement's, so a rebuild whose level or cull answer did not move still comes back identical. + // A matched pair is settled here instead: the outgoing entry keeps its block and its place in + // the cell, only its filter point moves, so the next gate is measured from where the follow + // point is now rather than from where it was when the answer was already right. Everything + // else falls through to the swap below untouched. + for (let index = job.fresh.length - 1; index >= 0 && job.force === false; index -= 1) { + const fresh = job.fresh[index] as ICellBatch; + const at = job.replaced.findIndex( + (old) => + old.level === fresh.level && old.part === fresh.part && old.batch.equals(fresh.batch), + ); + if (at < 0) continue; + const same = job.replaced.splice(at, 1)[0] as ICellBatch; + this.#host.addUnchanged(); + same.lastFilterY = fresh.lastFilterY; + same.lastFilterX = fresh.lastFilterX; + same.lastFilterZ = fresh.lastFilterZ; + this.#host.widenFilterRange(cell, fresh.lastFilterX, fresh.lastFilterZ); + job.fresh.splice(index, 1); + // The batch that stays is the one that was already drawing, so its source records are its own + // — and a cell the ring built before the scene came up has none. This is the only path that + // hands them over for a rebuild whose records came out identical, which is every one of them: + // the records are the same, so the fast path is always the one taken. + if (this.#gpuScene.on && same.gpu === undefined) same.gpu = this.placeSources(job, same); + } + // Every segment first, so a frame out of fresh meshes leaves the old batches drawing whole. + for (const entry of job.fresh) { + if (entry.batch.count === 0) continue; + // Both halves for every fresh entry, whether or not a stalled frame already claimed one: + // skipping an entry that has its main block meant the retry never asked for its cluster, so + // those records reached the main pass and no shadow map. + if (entry.segment < 0) { + const shared = this.sharedFor(job.asset.id, entry); + const segment = + shared === undefined + ? undefined + : this.segmentIn(shared, entry.batch.count, this.cullCellOf(cell)); + if (shared === undefined || segment === undefined) { + this.#meshStalled = true; + return false; + } + entry.shared = shared; + entry.segment = segment; + } + // The caster cluster, when this level casts. A frame that runs out of fresh meshes between + // the main mesh and its cluster leaves the main one drawing whole and takes the cluster next + // frame, which is the same no-hole rule as above: never half a replacement. + if (!this.claimCaster(job.asset.id, entry, cell)) { + this.#meshStalled = true; + return false; + } + // The wide half, by the same no-hole rule: a key with a cluster and no wide caster draws into + // a level that may already be reading the wide layer with nothing in it. + if (!this.claimWide(job.asset.id, entry)) { + this.#meshStalled = true; + return false; + } + } + for (const entry of job.replaced) { + const at = cell.batches.indexOf(entry); + if (at >= 0) cell.batches.splice(at, 1); + this.clearSegment(entry); + } + for (const entry of job.fresh) { + cell.batches.push(entry); + if (entry.shared !== undefined) entry.shared.write(entry.segment, entry.batch); + if (this.#gpuScene.on) entry.gpu = this.placeSources(job, entry); + if (entry.caster !== undefined) entry.caster.write(entry.casterSegment, entry.batch); + // The wide half draws the whole-asset impostor at the placement root when this entry kept one, + // and the source part shape otherwise; see `ICellBatch.wideRoot`. + if (entry.wide !== undefined) + entry.wide.write(entry.wideSegment, entry.wideRoot ?? entry.batch); + } + // A batch the walk just wrote to, cleared or compacted holds new records, so the shadow levels + // that drew the old ones are stale. One flag, told at most once a second. + this.publishGpuGates(job.asset); + if (job.fresh.some((entry) => entry.batch.count > 0)) + this.shadowRecordsMoved(this.changedBounds(job.fresh), true); + if (job.replaced.some((entry) => entry.batch.count > 0)) + this.shadowRecordsMoved(this.changedBounds(job.replaced)); + // This run's near representation is attached, so the far mesh stops drawing its original roots in + // the same synchronous block: a cell is never drawn by both and never by neither. A no-op until + // the asset's atlas lands; see `#syncFarSegments` for the build-time half and `#restoreFar`. + this.#host.disableFar(job.run); + return true; + } + + /** + * The world bounds of these records. A missing bound keeps the tell a blanket one. + */ + changedBounds(batches: readonly ICellBatch[]): Box3 | undefined { + let union: Box3 | undefined; + for (const entry of batches) { + if (entry.batch.count === 0) continue; + const box = entry.shadowBounds; + if ( + box === undefined || + box.isEmpty() || + ![...box.min.toArray(), ...box.max.toArray()].every(Number.isFinite) + ) + return undefined; + if (union === undefined) union = box.clone(); + else union.union(box); + } + return union; + } + + /** + * Claim this cell's block in its caster cluster, when the level casts and it does not have one. + * `false` means the frame's fresh allowance is spent and the swap waits, so the main mesh and its + * cluster are never half attached. + */ + claimCaster(assetId: string, entry: ICellBatch, cell: IResidentCell): boolean { + const level = this.castLevelOf(entry); + if (entry.casterSegment >= 0 || level === undefined) return true; + // Nothing to claim, and nothing to wait for: a world whose levels draw its keys mints no caster + // half at all (`#casterFor`), so reading that as a spent allowance refused every swap in the + // world — the main mesh was never attached, no cell published its placements, and the main pass + // drew nothing while its meshes sat there prewarmed and empty. + if (this.keysForShadow()) return true; + const caster = this.casterFor(assetId, entry, cell, level); + const at = caster === undefined ? undefined : this.segmentIn(caster, entry.batch.count); + if (caster === undefined || at === undefined) return false; + entry.caster = caster; + entry.casterSegment = at; + return true; + } + + /** + * Claim this cell's block in its key's wide caster mesh, when the level casts and it does not + * have one. `false` means the frame's fresh allowance is spent and the swap waits, so the + * clusters and the wide half are never half attached. + */ + claimWide(assetId: string, entry: ICellBatch): boolean { + const level = this.castLevelOf(entry); + if (entry.wideSegment >= 0 || level === undefined) return true; + // A part the asset's one whole-asset representation already covers: the entry keeps its near + // main and cluster, and the far shadow reads part 0's one quad; see `#wideOwed`. + if (!this.wideOwed(assetId, entry.part)) return true; + // As `#claimCaster`: the key twin is this level's wide half, and there is no mesh to wait on. + if (this.keysForShadow()) return true; + const wide = this.wideFor(assetId, entry, level); + // The impostor's far half draws the placement root, not `placement * part local`, so its block + // is sized to the root records the build kept beside the part's own; see `ICellBatch.wideRoot`. + const count = entry.wideRoot?.count ?? entry.batch.count; + const at = wide === undefined ? undefined : this.segmentIn(wide, count); + if (wide === undefined || at === undefined) return false; + entry.wide = wide; + entry.wideSegment = at; + return true; + } + + clearSegment(entry: ICellBatch): void { + if (entry.shared !== undefined && entry.segment >= 0) entry.shared.clear(entry.segment); + // The source records go back with the block that held them, on every path a cell's records leave: + // a refilter's replacement, an eviction, and a queued build dropped with its cell. Releasing one + // twice is a no-op, which is what lets a level's parts share a single list. + if (entry.gpu !== undefined) { + const placed = entry.gpu.length; + for (const at of entry.gpu) this.#gpuScene.release(at); + const left = (this.#gpuResident.get(entry.asset) ?? 0) - placed; + if (left > 0) this.#gpuResident.set(entry.asset, left); + else this.#gpuResident.delete(entry.asset); + // Before the halves go below, which is what clears the handles this reads. + this.countPlaced(entry, -placed); + } + entry.gpu = undefined; + if (entry.caster !== undefined && entry.casterSegment >= 0) + entry.caster.clear(entry.casterSegment); + if (entry.wide !== undefined && entry.wideSegment >= 0) entry.wide.clear(entry.wideSegment); + entry.shared = undefined; + entry.segment = -1; + entry.caster = undefined; + entry.casterSegment = -1; + entry.wide = undefined; + entry.wideSegment = -1; + } + + /** + * One cell's records changed, so the shadow levels that cover it redraw — at most once a second, + * and only for a game that passed the hook. The levels cull clusters themselves now, so nothing + * has to be copied for them; the flag only says "what you last drew is out of date". + * + * `bounds` is the records that changed and is kept until the tell, unioned with whatever else + * changed in the same second. A change with no bounds is a blanket one: it has to be, because + * there is nothing narrower to say. + */ + shadowRecordsMoved(bounds?: Box3, admission = false): void { + this.#shadowMoved = true; + if ( + bounds === undefined || + bounds.isEmpty() || + ![bounds.min.x, bounds.min.y, bounds.min.z, bounds.max.x, bounds.max.y, bounds.max.z].every( + Number.isFinite, + ) + ) { + this.#shadowFull = true; + return; + } + const current = admission ? this.#shadowAdmissionRegion : this.#shadowRegion; + if (current === undefined) { + const region: IShadowRegion = { + ...(admission ? { admission: true } : {}), + max: { x: bounds.max.x, y: bounds.max.y, z: bounds.max.z }, + min: { x: bounds.min.x, y: bounds.min.y, z: bounds.min.z }, + }; + if (admission) this.#shadowAdmissionRegion = region; + else this.#shadowRegion = region; + return; + } + current.min.x = Math.min(current.min.x, bounds.min.x); + current.min.y = Math.min(current.min.y, bounds.min.y); + current.min.z = Math.min(current.min.z, bounds.min.z); + current.max.x = Math.max(current.max.x, bounds.max.x); + current.max.y = Math.max(current.max.y, bounds.max.y); + current.max.z = Math.max(current.max.z, bounds.max.z); + } + + /** + * Tell the shadow levels in up to two calls, if anything changed and a second has passed. + * + * The companions used to make this the expensive call it still is; the levels redraw from + * scratch, so the cadence is the only lever and once a second is the one that keeps a walk's + * frames off the shadow lane without leaving the ground stale behind a player. The region is + * what the changed records' own bounds add: a hook that forwards it redraws the levels whose + * window covers them, which for a streaming world is usually the finest one alone. + */ + tellShadows(): void { + if (!this.#shadowMoved) return; + // No hook means there is no level to tell, so the flag is discharged here. Leaving it set made + // `#residencyStale` true forever, and every update ran a full residency pass on a world that had + // nothing left to do. + if (this.#invalidateShadows === undefined) { + this.#shadowMoved = false; + this.#shadowRegion = undefined; + this.#shadowAdmissionRegion = undefined; + this.#shadowFull = false; + return; + } + const now = this.#host.now(); + if (now - this.#shadowToldAt < 1e3) return; + this.#shadowToldAt = now; + this.#shadowMoved = false; + const region = this.#shadowRegion; + const admission = this.#shadowAdmissionRegion; + const full = this.#shadowFull; + this.#shadowRegion = undefined; + this.#shadowAdmissionRegion = undefined; + this.#shadowFull = false; + if (full || (region === undefined && admission === undefined)) this.#invalidateShadows(); + else { + if (region !== undefined) this.#invalidateShadows(region); + if (admission !== undefined) this.#invalidateShadows(admission); + } + } + + resetShadows(): void { + this.#shadowMoved = false; + this.#shadowRegion = undefined; + this.#shadowAdmissionRegion = undefined; + this.#shadowFull = false; + } + + /** + * Publish, on every caster half, whether the main pass will draw the records behind it this frame. + * + * A caster whose main counterpart is not drawable casts the shadow of nothing. With the GPU scene + * on, a dressed main mesh draws from the dispatch's own record, and a batch the scene holds no + * placement for submits that record and draws none of it — while the caster halves are plain CPU + * batches whose `#clustered` is false for every role but `main` (see the constructor), so they keep + * `#drawn`, the whole resident ring, and a shadow level submits all of it. `#seedGpuSources` is + * what puts the placements in, by queueing a rebuild of the ring behind the admission budget, so + * the hole lasts as long as that takes to drain and the picture shows canopy on empty ground for + * every frame of it. + * + * Per caster half, which is the granularity the ring is placed at and the only one that cannot draw + * a shadow for a source the dispatch has not been given: a `@x,z` cluster covers the cells of one + * world-grid square and a `@*` wide mesh every cell of its asset, so each compares its own placed + * count (`SharedBatch#gpuPlaced`, kept by `#placeSources` and `#clearSegment`) with its own live + * records. Per asset was one count too coarse and it showed: near cells of an asset are placed first, + * so a per-asset rule admitted every far cluster of that asset and its unplaced canopies drew + * shadows on empty ground — one capture in four showed dozens of them at the start pose. + * + * The direction is fail-closed, and the only price is a shadow that arrives with its trees: a half + * whose sources are still being placed casts nothing this frame, and casts once they are all there. + * + * Not the camera's frustum, which is the other narrowing and the wrong one: an off-screen caster + * the main pass has no reason to draw is the reason a shadow map exists. + */ + publishCasterAdmission(): void { + // With the scene off there is no dress to wait for: a main batch draws from its own records and + // its own cull window, so every caster half is admitted whatever the camera is looking at. One + // comparison per half, no name work and no allocation: `live` is the records the main pass would + // have to be drawing for this half to cast at all. + const gpu = this.#gpuScene.on; + for (const shared of this.#shared.values()) { + if (shared.role === "main") continue; + shared.setMainAdmitted(gpu === false || shared.gpuPlaced >= shared.live); + } + } + + /** + * `TN_WORLD_MAIN_CULL`, every `MAIN_CULL_MARKER_MS`: what the main pass holds, what it is drawing, + * how many of its meshes are submitted at all, and how many times the window moved to get there. + * + * `instances` is every live record the main batches hold — the whole resident ring, which is what + * three's whole-mesh test used to hand the vertex stage — and `drawn` is what the cull left in the + * windows. The gap between them is the saving, `repacks` says what it cost to get: a settled camera + * repacks nothing, so a walk that repacks dozens of times a second is a window that is chasing the + * camera, not one that is thrashing. `hidden` and `castersHidden` are the batches with nothing to + * draw that are now not submitted at all: in three r185's WebGPU path a zero-count `InstancedMesh` + * still runs the whole per-draw JS chain, so an empty batch was CPU a frame for no draw — the number + * is the part of the census that no longer costs anything. + * + * `dressed=N/M` is the GPU scene's own claim, repeated every five seconds: a scene that is on over + * meshes nothing is dressed into compacts a set the CPU path is still drawing, and `keys` alone + * cannot say so — a ring built before the scene came up has no keys at all and looks healthy. + */ + reportMainCull(): void { + const now = this.#host.now(); + if (now - this.#mainCullToldAt < MAIN_CULL_MARKER_MS) return; + this.#mainCullToldAt = now; + const repacks = this.#mainCullRepacks; + this.#mainCullRepacks = 0; + let instances = 0; + let drawn = 0; + let hidden = 0; + let castersHidden = 0; + let clusterWithheld = 0; + let clusterRecords = 0; + let clusterMissing = 0; + let wideWithheld = 0; + let wideRecords = 0; + let wideMissing = 0; + // What the two caster layers hold between them, per layer: the resident triangle bill a shadow + // level that picked that layer submits. Two numbers that must not be equal is the whole claim — + // the wide half draws the asset's coarsest level, so it is the one that falls. + let clusterTriangles = 0; + let wideTriangles = 0; + let dressed = 0; + let mainMeshes = 0; + for (const shared of this.#shared.values()) { + if (shared.role !== "main") { + if (shared.mesh.visible === false) castersHidden += 1; + if (shared.mesh.mainAdmitted === false && shared.live > 0) { + if (shared.role === "cluster") { + clusterWithheld += 1; + clusterRecords += shared.live; + clusterMissing += Math.max(0, shared.live - shared.gpuPlaced); + } else if (shared.role === "wide") { + wideWithheld += 1; + wideRecords += shared.live; + wideMissing += Math.max(0, shared.live - shared.gpuPlaced); + } + } + // Zero records add zero, so an empty batch needs no test of its own here. + const triangles = levelTriangles(shared.mesh.geometry) * shared.live; + if (shared.role === "cluster") clusterTriangles += triangles; + else wideTriangles += triangles; + continue; + } + mainMeshes += 1; + if (shared.gpu !== undefined) dressed += 1; + instances += shared.live; + drawn += shared.drawn; + if (shared.mesh.visible === false) hidden += 1; + } + const stats = this.#host.stats(); + console.info( + `TN_WORLD_MAIN_CULL instances=${String(instances)} drawn=${String(drawn)} ` + + `hidden=${String(hidden)} castersHidden=${String(castersHidden)} repacks=${String(repacks)} ` + + `refilters=${String(stats.refilters)} levelRefilters=${String(stats.levelRefilters)} ` + + `clusterTris=${String(clusterTriangles)} wideTris=${String(wideTriangles)} ` + + `dressed=${String(dressed)}/${String(mainMeshes)}`, + ); + console.info( + `TN_WORLD_SHADOW_ADMISSION clusterWithheld=${String(clusterWithheld)} ` + + `clusterRecords=${String(clusterRecords)} clusterMissing=${String(clusterMissing)} ` + + `wideWithheld=${String(wideWithheld)} wideRecords=${String(wideRecords)} ` + + `wideMissing=${String(wideMissing)} seeded=${String(this.#gpuSeeded)} ` + + `queued=${String(this.#host.queuedBuilds())}`, + ); + const hlod = stats.hlod; + let proxyDraws = 0; + let replacedSources = 0; + for (const cell of this.#host.residentCells()) { + const state = cell.proxy; + if (state === undefined || !state.active) continue; + proxyDraws += state.prepared?.meshes.length ?? 0; + replacedSources += state.hidden.size + state.mainMasks.size; + } + const why = hlod.reason === "" ? "" : ` reason=${JSON.stringify(hlod.reason)}`; + console.info( + `TN_WORLD_HLOD on=${String(hlod.on)} loaded=${String(hlod.loaded)} ` + + `compiled=${String(hlod.compiled)} observed=${String(hlod.observed)} ` + + `active=${String(hlod.active)} pending=${String(hlod.pending)} ` + + `draws=${String(proxyDraws)} replaced=${String(replacedSources)} ` + + `bytes=${String(hlod.bytes)} refused=${String(hlod.refused)} failures=${String(hlod.failures)}${why}`, + ); + // The bundles on their own line, because the pair reads against a different question: how many + // objects are recorded, and how many times the recording was thrown away and redone. A walk + // streaming and culling holds `records` at the keys and chunks that came and went; a walk that + // repacks it every frame is drawing a moving set, which is the bug a bundle cannot have. `reason` + // is on it because the default is now on: a reader has to be able to tell a run that turned it + // off from a run where recording never happened. + const bundles = stats.bundle; + console.info( + `TN_WORLD_BUNDLE ${bundles.on ? "on" : "off"} reason=${bundles.reason} ` + + `children=${String(bundles.children)} cpuMeshes=${String(bundles.cpuMeshes)} gpuMeshes=${String(bundles.gpuMeshes)} records=${String(bundles.records)}`, + ); + } + + /** The main pass's one mesh for an asset part at a level, covering every resident cell. */ + sharedFor(assetId: string, entry: ICellBatch): SharedBatch | undefined { + return this.batchFor(assetId, entry, this.keyOf(entry), "main", this.#receiveShadow); + } + + /** + * The shadow-caster cluster for one asset part at one level, or `undefined` when the level does + * not cast or the frame's fresh allowance is spent. + * + * One per `(key, cluster)`, on `VIRTUAL_SHADOW_CASTER_LAYER` and off the main camera, so a level + * culls the clusters its own window covers while the main pass keeps drawing one mesh per key. + */ + casterFor( + assetId: string, + entry: ICellBatch, + cell: IResidentCell, + level = entry.level, + ): SharedBatch | undefined { + // A world whose shadow levels draw its keys mints no cluster: the two are the same placements by + // two routes, and a level's map can only draw one of them. + if (!this.casts(level) || this.keysForShadow()) return undefined; + return this.batchFor( + assetId, + entry, + `${this.keyOf(entry, level)}@${this.clusterOf(cell)}`, + "cluster", + false, + level, + ); + } + + /** + * The wide caster for one asset part at one level, or `undefined` when the level does not cast or + * the frame's fresh allowance is spent. + * + * One per key, holding every cell's records, on the wide caster layer: a level whose window + * covers the whole resident ring pays one draw for the key instead of one per square, and the fine + * levels that cull clusters never see this layer. + */ + wideFor(assetId: string, entry: ICellBatch, level = entry.level): SharedBatch | undefined { + // As `#casterFor`: the key twin replaces both halves, so neither is minted. + if (!this.casts(level) || this.keysForShadow()) return undefined; + // A whole-asset impostor is one shape for every part and level, so its far shadow is ONE mesh per + // asset holding one root record per placement, not one mesh per level whose records a source part + // offset would then double. Every other asset keeps the per-key mesh its per-part shapes need. + const impostor = this.#host.asset(assetId)?.impostor !== undefined; + const key = impostor + ? `${assetId}:*@${WIDE_CLUSTER}` + : `${this.keyOf(entry, level)}@${WIDE_CLUSTER}`; + return this.batchFor(assetId, entry, key, "wide", false, level); + } + + /** `asset:level:part`, the main pass's one mesh per key. */ + keyOf(entry: ICellBatch, level = entry.level): string { + return `${entry.asset}:${String(level)}:${String(entry.part)}`; + } + + /** + * The level a cell batch casts with: its own when that level casts, otherwise the coarsest + * casting level, when that level holds the same parts — a chain's derived levels do, authored + * `lods` need not — so a tree past `castLevels` keeps its shadow in the last shape that has a + * caster rather than leaving a hole in the map (PRD-541). `undefined`: it casts nothing. + */ + castLevelOf(entry: ICellBatch): number | undefined { + if (this.casts(entry.level)) return entry.level; + if (this.#castShadowLevels === 0) return undefined; + const asset = this.#host.asset(entry.asset); + if (asset === undefined || asset.definition.lods !== undefined) return undefined; + const level = Math.min(entry.level, this.#castShadowLevels) - 1; + return asset.levels[level]?.[entry.part] === undefined ? undefined : level; + } + + /** + * Whether a shadow level draws this world's keys instead of its two caster halves: the GPU scene + * is up *and* a provider registered on it, which `?tnShadowGpuKeys=1` is what asks for. Both, or + * neither — a provider with the scene off is a world whose shadow maps have nothing to draw, so + * the world keeps the caster path until both are true. + */ + keysForShadow(): boolean { + return this.#gpuScene.on && this.#gpuScene.shadowKeys; + } + + /** + * The shadow twin of one main key: a second mesh on the key layer, over the shadow twin of the + * dispatch's output, so a level's map submits one indirect draw for this key instead of one per + * world-grid square the two caster halves split it into. + * + * Its records are never written — the twin buffers are the dispatch's, and this mesh's count is + * its region's capacity — so it holds no instance buffer of its own and is minted at one slot. + * Minted for the levels that cast, which is what the caster halves did and what the CPU path + * already meant: a placement the dispatch routes to a level with no mesh casts nothing here, + * exactly as it casts nothing from a caster half nobody minted. + */ + shadowKeyFor(key: IKeyDescriptor, part: IAssetPart): void { + // Registered here rather than at construction, because it is a question about keys that exist: + // a level's dispatch asks the provider for the keys a map draws, and the answer is this world's. + // Without a provider the scene registers nothing, which is what keeps the flag from minting + // anything at all; see `shadowGpuKeysRequested`. + this.#gpuScene.shadowKeysFrom(() => this.#shadowKeyNames); + if (this.keysForShadow() === false || this.casts(key.level) === false) return; + const name = `${key.key}${GPU_KEY_SUFFIX}`; + this.#shadowKeyNames.add(key.key); + let shared = this.#shared.get(name); + if (shared === undefined) { + // A released twin keeps its uuid and the node three built for it, for the same reason a + // caster half's does: rebinding is cheaper than a fresh mesh, and a fresh mesh is a fresh + // node build inside a shadow pass. + const released = this.#retired.get(name); + if (released !== undefined) { + this.#retired.delete(name); + this.#retiredBytes -= this.heldBytes(released); + shared = released; + } else { + // A fresh mesh is charged against the frame's allowance like any other; the walk that runs + // out of it comes back for this key rather than paying its build inside a shadow render. + if (this.#freshThisUpdate >= this.freshAllowance()) return; + this.#freshThisUpdate += 1; + shared = new SharedBatch( + part.geometry, + part.material, + 1, + 1, + name, + this.#extentBounds, + "key", + ); + } + shared.rebind(part.geometry, part.material); + shared.mesh.name = name; + this.dressMesh(shared, false); + this.#shared.set(name, shared); + this.attach(shared); + // Owed a draw, and shown because it is empty: a key is submitted by a shadow level, so the + // build behind the gate is the prewarm's whole job. Only while the gate is still open — a key + // streamed in afterwards owes nothing, exactly as a streamed caster half does not. + if (this.#host.prewarmSettled() === false) this.#host.awaitDraw(shared); + } + this.dressShadowKey(shared, key.key); + } + + /** + * Point a shadow key's mesh at the twins, with the same shape and the same reasons as + * {@link #dressGpu}: the mesh's `instanceMatrix` becomes the scene's twin compaction buffer, its + * geometry a view carrying this key's twin record over the same attributes, and its whole-mesh + * frustum test off, because the dispatch tested every instance and the mesh's own bounds are the + * resident ring. Idempotent, so a settled walk reads one comparison — and so a regrown twin, which + * is a new attribute and a new pipeline like the main pass's, is re-dressed rather than left + * pointing at the buffer the dispatch replaced. + */ + dressShadowKey(shared: SharedBatch, key: string): void { + const scene = this.#gpuScene; + // The twin record has to exist before a mesh can be dressed against it: a record minted inside + // the render it would draw in is one render late for every key. Idempotent, and a no-op without + // a provider. + scene.shadowKey(key); + const region = scene.shadowRegionOf(key); + const drawn = scene.shadowDrawn; + const args = scene.shadowArgs; + const geometry = shared.mesh.geometry; + if ( + drawn !== undefined && + shared.mesh.instanceMatrix === drawn && + shared.mesh.frustumCulled === false && + shared.gpuCount === region?.capacity && + geometry.indirect === args && + geometry.indirectOffset === (region?.argsIndex ?? -1) * DRAW_ARGS_BYTES + ) + return; + if (region === undefined || drawn === undefined || args === undefined) return; + // The submission bound: three skips an `InstancedMesh` at count 0 before the GPU reads the + // record, so a key with no instances of its own is submitted at its region's capacity and the + // record decides. See `gpuCount`. + shared.gpuCount = region.capacity; + const mesh = shared.mesh; + const view = indirectView(geometry, args, region.argsIndex * DRAW_ARGS_BYTES); + // A fresh object for the same reason the main pass needs one: three binds the `instanceMatrix` + // an object held when its node was built and keeps that binding for the object's whole life. + if (mesh.instanceMatrix !== drawn) { + const next = freshMeshFor(mesh, view, mesh.count) as ICasterScaleMesh; + redressMaterial(next); + this.#host.carryPrewarm(mesh, next); + this.seat(shared.replaceMesh(next), next); + next.instanceMatrix = drawn; + next.frustumCulled = false; + } else { + mesh.geometry = view; + mesh.instanceMatrix = drawn; + mesh.frustumCulled = false; + } + // Shown and submitted whatever the records say: a key's own count is its region's capacity, and + // the twin record inside is what decides how many instances this map draws of it. See + // `SharedBatch#publish`. Never bundled — a bundle's record is fixed when it is recorded and only + // the main pass's draws are replayed from it, so a shadow key in the group would be drawn by the + // main camera's replay from the wrong buffer. See `#attach`. + mesh.count = shared.gpuCount; + mesh.visible = true; + } + + /** + * The geometry and material one caster half draws, and the one place the coarse shape is chosen. + * + * Only the wide half is coarse. A shadow level picks its granularity by bill — one mesh per square + * on `VIRTUAL_SHADOW_CASTER_LAYER`, one mesh per key on the wide layer — so the wide half is what + * the levels whose window holds the resident ring submit, and their texels are metres across: the + * shape such a map resolves cannot tell level 0 from the asset's coarsest level, so it draws the + * coarsest one per part and the triangles it saves are triangles no fragment ever sampled. A + * cluster is the fine half, culled down to the squares one level's own window covers, so it keeps + * the level the placement selected. An asset with one level is its own coarsest, and every half + * draws what it draws today. + */ + /** + * The shape the coarse wide half draws for one `(level, part)`, or `fallback` when the level has no + * honest coarse counterpart. Shared by {@link #shapeFor} and the prewarm, so the mesh minted behind + * the gate carries the same geometry the swap would later ask for — a prewarm built from the + * coarsest level's part 0 and then handed back unchanged is the wrong shape for good. + */ + wideShape( + asset: IAssetState | undefined, + level: number, + part: number, + fallback: IBatchShape, + ): IBatchShape { + if (this.#preserveAuthoredParts) return fallback; + const terminal = asset?.levels.at(-1); + // The whole-asset impostor is the one case where a part index has no meaning: it is a single + // representation of every LOD0 part, so every part routes through it. `terminal?.[0]` is only + // that shape. On the source-only path the terminal must have a part at this index with the same + // role — an alpha slot drawn with an opaque part's shape (or vice versa) is a different object, + // and a terminal with fewer parts must not fall back to part 0's, which duplicates it. + if (asset?.impostor !== undefined) { + const coarse = terminal?.[0]; + if (coarse !== undefined) return coarse; + } + const coarse = terminal?.[part]; + const source = asset?.levels[level]?.[part]; + if ( + coarse !== undefined && + source !== undefined && + isAlphaFoliage(source.material) === isAlphaFoliage(coarse.material) + ) + return coarse; + return fallback; + } + + shapeFor( + entry: ICellBatch, + role: "cluster" | "key" | "main" | "wide", + level = entry.level, + ): IBatchShape { + // A batch cast at a finer level than its own (`#castLevelOf`) draws that level's part. + const own = + level === entry.level + ? { geometry: entry.batch.geometry, material: entry.batch.material } + : (this.#host.asset(entry.asset)?.levels[level]?.[entry.part] as IAssetPart); + if (role === "wide") + return this.wideShape(this.#host.asset(entry.asset), level, entry.part, { + geometry: own.geometry, + material: own.material, + }); + return { geometry: own.geometry, material: own.material }; + } + + /** + * Whether this entry still owes a wide caster. A terminal whole-asset impostor is one part, and one + * wide mesh per level already holds every placement: only part 0 opens it, so a walk pays one quad + * per placement on the far shadow instead of one per bark/needle part. + */ + wideOwed(assetId: string, part: number): boolean { + if (part === 0) return true; + return this.#host.asset(assetId)?.impostor === undefined; + } + + /** The `@x,z` half of a caster cluster key, for a resident cell. */ + clusterOf(cell: IResidentCell): string { + return clusterOf(cell.x, cell.z, this.#cellsPerCluster); + } + + /** + * The `@x,z` half of the main cull's own visibility-cell key, for a resident cell. This is what a + * main batch's blocks are grouped by and what `#cullMainPass` tests, so it is the granularity the + * main pass is culled at; the caster clusters above are the shadow levels' and are left alone. + */ + cullCellOf(cell: IResidentCell): string { + return clusterOf(cell.x, cell.z, this.#cellsPerCullCell); + } + + /** + * The shared batch for one key, created (a new mesh: counted against the frame's fresh allowance) + * the first time any cell draws it. `undefined` means the allowance is spent and the caller waits + * a frame. A caster batch carries the shadow flags and the caster layer; a main batch casts + * nothing, because its records reach the shadow maps through the caster cluster. + * + * The buffer starts with a block per cell that can hold this key, plus the one a refilter holds + * alongside the blocks it replaces — the cluster's own cells for a caster cluster, the whole ring + * for the main pass's one mesh per key — not the eight it used to start with. Sizing a main batch + * by its cluster's cells left it with two blocks for a ring of dozens, so a walk filled the + * buffer, `grow` minted a fresh mesh — a uuid three had never built a node for, in every shadow + * pass too — and put the old one in an array nothing ever read. Those two sizes are ceilings, so + * this is what stops growing. Cost: a main buffer is ring-sized for assets that never fill it; + * give `residentCells` a smaller value if that outweighs a re-mint. + */ + batchFor( + assetId: string, + entry: ICellBatch, + key: string, + role: "cluster" | "key" | "main" | "wide", + receiveShadow: boolean, + level = entry.level, + ): SharedBatch | undefined { + const existing = this.#shared.get(key); + if (existing !== undefined) return existing; + const shape = this.shapeFor(entry, role, level); + const smallCaster = this.smallCaster(assetId); + // The batch released when this asset's last cell left the ring is the one to draw into again, + // so the mesh keeps the uuid and the node three built for it. It costs no fresh allowance, + // because it is not a fresh mesh. + const released = this.#retired.get(key); + if (released !== undefined) { + this.#retired.delete(key); + this.#retiredBytes -= this.heldBytes(released); + released.rebind(shape.geometry, shape.material); + released.mesh.name = key; + released.smallCaster = smallCaster; + this.dressMesh(released, receiveShadow); + // A rebind swaps the geometry and may swap the buffer, so the key is dressed again: the indirect + // record and the shared matrix buffer belong to the mesh, not to the batch. + this.adoptGpu(released, entry.asset, key, level, entry.part); + this.#shared.set(key, released); + this.attach(released); + return released; + } + if (this.#freshThisUpdate >= this.freshAllowance()) return undefined; + this.#freshThisUpdate += 1; + const shared = new SharedBatch( + shape.geometry, + shape.material, + this.#host.runMax(assetId) ?? entry.batch.count, + // A caster cluster's block count is bounded by the cluster's own cells; a wide caster and the + // main pass's one mesh per key are both bounded by the whole ring, so either way the one a + // refilter holds beside the block it replaces. + role === "cluster" + ? this.#cellsPerCluster * this.#cellsPerCluster + 1 + : this.#budgets.residentCells + 1, + key, + this.#extentBounds, + role, + ); + shared.smallCaster = smallCaster; + this.dressMesh(shared, receiveShadow); + this.adoptGpu(shared, entry.asset, key, level, entry.part); + this.#shared.set(key, shared); + this.attach(shared); + return shared; + } + + /** + * The shadow half of the split, and the two granularities of it. A caster mesh lives alone on + * `VIRTUAL_SHADOW_CASTER_LAYER` — one per world-grid square, which a level whose window is a + * fraction of the ring culls down to the squares it covers — and a wide mesh alone on + * `VIRTUAL_SHADOW_WIDE_CASTER_LAYER`, one per key, which a level whose window covers the ring + * submits once. The level cameras draw one of the two layers and never both, so a key costs the + * main pass one draw, a fine level a few and a wide level one. Neither receives: nothing but a + * shadow level ever sees either. A main mesh keeps layer 0 and casts nothing, because its records + * reach the shadow maps only through a caster half. + */ + dressMesh(shared: SharedBatch, receiveShadow: boolean): void { + const mesh = shared.mesh; + const role = shared.role; + // Every role's records are written into the instance buffer, never moved in the world, and the + // mesh is created and released per role — so this is the one place that covers a fresh mesh, a + // rebound one and the three roles. Static here is only safe because every write announces + // itself: `instanceMatrix.needsUpdate` bumps the attribute's version, which is what a settled + // draw watches to know it still has to scan. + markStatic(mesh); + if (role !== "main") { + mesh.layers.set( + role === "cluster" + ? VIRTUAL_SHADOW_CASTER_LAYER + : role === "key" + ? VIRTUAL_SHADOW_KEY_LAYER + : shared.smallCaster + ? VIRTUAL_SHADOW_SMALL_CASTER_LAYER + : VIRTUAL_SHADOW_WIDE_CASTER_LAYER, + ); + mesh.castShadow = true; + mesh.receiveShadow = false; + return; + } + mesh.layers.set(0); + mesh.castShadow = false; + mesh.receiveShadow = receiveShadow; + // The origin a draw of this mesh is counted under until the GPU scene dresses it + // (`RenderPassBudget`). `#dressGpu` runs straight after this and overwrites it with `gpuScene` + // when the key really is dressed, so what survives here is a main batch the CPU path draws per + // object — a backend with no compute, `gpuScene: false`, or a dress that gave up. A caster half + // returns above and keeps no origin: it lives off layer 0, so the main pass never counts it. + mesh.userData.tnDrawSource = "instanced"; + // Nothing to hang off a main mesh: the window is narrowed by `#cullMainPass` for the whole set, + // because a batch that drew nothing is hidden and three never reaches an invisible mesh's own + // hooks — a mesh that could only re-narrow itself in its `onBeforeRender` would stay hidden for + // as long as the camera looked away. That leaves `onBeforeRender` to the prewarm borrow alone. + } + + /** + * Parent one dressed main mesh under the world's single `BundleGroup`, so a settled frame replays + * its draw instead of re-walking three's per-object path for it. + * + * A mesh is shown once, here, and never hidden again: the bundle's render list is fixed when it is + * recorded, the dispatch already decided which instances each key draws, and an indirect draw of + * zero instances is close to free on the GPU — where a `visible` write would force the whole group + * to be recorded again. The prewarm draw this batch may still owe comes out of that same record. + * + * CPU joins and changed material revisions invalidate the record here, including a repaired mesh + * joining on a settled frame. GPU dressing invalidates its completed record at the caller. + * + * A mesh whose draw needs a live pass is refused rather than recorded — a transmissive impostor or + * a GPU batch reaching the framebuffer, the same answer a chunk's draws get. It keeps the per-object + * path and the coarse gate that hides it, which is the path it was already on. See `bundleSafe`. + */ + bundleIn(shared: SharedBatch, reuseSafety = false): void { + if (this.#bundlesWanted === false) return; + // Shared batches normally carry one material. A mutable array keeps the existing per-object path + // rather than compressing several material identities/revisions into an incomplete signature. + const arrayMaterial = Array.isArray(shared.mesh.material); + const checked = + reuseSafety && !arrayMaterial + ? this.#bundleSafety.test(shared.mesh.material as Material) + : undefined; + shared.bundlable = + lodChainOf(shared.mesh.geometry) === undefined && + (reuseSafety + ? checked !== undefined && + !ownHook(shared.mesh, "onBeforeRender") && + !ownHook(shared.mesh, "onAfterRender") && + Reflect.get(shared.mesh, "isSkinnedMesh") !== true && + shared.mesh.morphTargetInfluences === undefined && + checked.safe + : bundleSafe(shared.mesh)); + if (shared.bundlable === false) { + if (shared.bundled) this.bundleOut(shared); + // A previously culled key may already be detached. Refusal still owes its live source draw. + if (!this.#host.isParent(shared.mesh)) this.#host.add(shared.mesh); + return; + } + const group = this.shardOf(shared); + if (checked !== undefined) { + if (shared.bundled && shared.bundleMaterialRevision !== checked.revision) + this.bumpBundle(group); + shared.bundleMaterialRevision = checked.revision; + } + if (shared.mesh.parent !== group) { + group.add(shared.mesh); + if (reuseSafety) this.bumpBundle(group); + } + shared.bundled = true; + shared.bundledCount = shared.mesh.count; + // A recorded draw was cut from this camera's frustum, so the mesh's own whole-mesh test would drop + // it the moment the camera turned away from a window the record still holds. Off, like a chunk's. + shared.mesh.frustumCulled = false; + shared.mesh.visible = true; + // Read by `VirtualShadowNode#probe`, which must not hide a bundled mesh: the render list a bundle + // recorded is fixed, and `visible = false` would take the mesh out of every record after the next + // re-record. See `bundled`. + shared.mesh.userData.tnBundled = true; + } + + /** + * Take a mesh back out of the group, because the key it was minted under is leaving the world. + * + * A retire is a structural change — the recorded bundle still holds this render object, and + * replaying it would draw a key nothing owns — so it is one of the two events that re-records. + */ + bundleOut(shared: SharedBatch): void { + if (shared.bundled === false) return; + shared.bundled = false; + shared.bundledCount = -1; + shared.mesh.userData.tnBundled = false; + // Back on the per-object path, so the mesh's own whole-mesh test is its gate again. + shared.mesh.frustumCulled = true; + const group = this.#bundles.get(mainShard(shared.mesh.name)); + if (group === undefined) return; + if (shared.mesh.parent === group) group.remove(shared.mesh); + this.bumpBundle(group); + } + + /** One re-record, counted: `BundleGroup.needsUpdate` is a version bump, and nothing else. */ + bumpBundle(group: BundleGroup): void { + this.#bundleRecords += 1; + group.needsUpdate = true; + if (this.#compileBusy) this.#recordedWhileBusy.add(group); + } + + /** + * Put one CPU-path main batch into the world's record, or take it out, by the coarse gate's answer. + * + * A main batch the GPU scene dresses is recorded from `#dressGpu`, because its submission bound is + * its key's region capacity — a number only the dress writes, so the entry cannot go stale. A batch + * the scene does not own submits its own CPU window instead, and that window is per frame: the gate + * narrows it, a residency change repacks it, and a batch it narrows to nothing must draw nothing. + * A record fixes its render list, so all three are the record's business rather than a `visible` + * write the replay ignores. Hence this: the gate's answer is a join or a leave, a window that moved + * or a count that changed is one more record, and `frustumCulled` is off because the record's own + * list was cut by the same frustum when it was made. + * + * The trade is the one a cell's chunk record already makes and the visual A/B already judged: a + * batch whose window is half the view draws the half that faced the camera when the record was cut. + */ + recordCpuMain(shared: SharedBatch, outcome: "settled" | "narrowed" | "repacked"): void { + if (this.#bundlesWanted === false) return; + // `#publish` has just written this frame's answer onto the mesh, which is the only place it is. + if (shared.mesh.visible === false) { + this.bundleOut(shared); + return; + } + this.bundleIn(shared, true); + if (shared.bundled === false) return; + // The count a record freezes, and the layout behind it: a repack moves records inside the window + // without moving its length, so the epoch answer and the count are both read here. + if (outcome === "settled" && shared.mesh.count === shared.bundledCount) return; + shared.bundledCount = shared.mesh.count; + this.bumpBundle(this.shardOf(shared)); + } + + /** + * Attach one prepared chunk: to its cell's own record, or to the world when `bundles` is off. + * + * The group is minted with the first chunk the cell attaches, and a chunk added to it is a + * structural change — the recorded list does not hold this render object and the replay would draw + * the cell's chunks without it — so this is also the one re-record. See the `bundles` option for + * why the group is per cell. + * + * Only the draws a bundle can replay are recorded, and they carry the `tnBundled` marker and the + * frozen cull with them; the rest keep the per-object path. See `bundleSafe`. + */ + addChunk(cell: IResidentCell, chunk: Object3D): void { + if (this.#bundlesWanted === false) { + this.#host.add(chunk); + return; + } + // What the record can hold, and whether anything had to be left behind. A chunk whose every draw + // is recorded goes in whole, which is one entry per chunk; a chunk holding a draw a bundle cannot + // replay stays in the world and hands its safe draws to the record on their own, so one glass + // window does not put the whole building back on the per-object path. See `bundleSafe`. + const draws = chunkDraws(chunk); + const recorded = draws.filter( + (mesh) => lodChainOf(mesh.geometry) === undefined && bundleSafe(mesh), + ); + if (recorded.length === 0) { + this.#host.add(chunk); + return; + } + let entry = this.#chunkBundles.get(cell.key); + if (entry === undefined) { + const group = new BundleGroup(); + group.name = `${CHUNK_NAME}-bundles:${cell.key}`; + this.#host.add(group); + entry = { cluster: clusterOf(cell.x, cell.z, this.#cellsPerCullCell), group }; + this.#chunkBundles.set(cell.key, entry); + } + const whole = recorded.length === draws.length; + if (whole) entry.group.add(chunk); + else { + // The chunk keeps its own hierarchy around whatever it could not record, and each recorded + // draw is attached rather than added so it keeps the world transform it was merged at. The root + // it came out of travels with it, because that is what releases the draw again. + this.#host.add(chunk); + for (const mesh of recorded) { + mesh.userData.tnChunkRoot = chunk; + entry.group.attach(mesh); + } + } + // A recorded draw is frozen where it stands: the group is a child of the world at identity, so + // its matrix will never move, and the freeze is what lets three's settled-object path skip the + // per-object work for the draw a frame is not re-recording anyway. See `markStatic`. + for (const mesh of recorded) { + mesh.userData.tnBundled = true; + mesh.frustumCulled = false; + if (whole === false) markStatic(mesh); + } + this.#chunkBundleChildren += whole ? 1 : recorded.length; + this.bumpBundle(entry.group); + } + + /** + * Take a cell's chunk record out of the world, because the cell that owned every chunk in it has + * left: a record nothing owns must not be replayed, and the cell coming back mints its own. + * + * A chunk the bake split left its recorded draws in the group rather than under their own root, so + * each goes back to the root it came out of first: the caller's own disposal walk finds them there, + * and a draw released by neither is a buffer nothing ever asks for again. + */ + dropChunkBundles(cell: IResidentCell): void { + const entry = this.#chunkBundles.get(cell.key); + if (entry === undefined) return; + this.#chunkBundles.delete(cell.key); + const roots = new Set(cell.chunks); + // Counted before anything leaves the group, because that is what it is holding. + this.#chunkBundleChildren -= entry.group.children.length; + for (const child of entry.group.children) { + const root = child.userData.tnChunkRoot as Object3D | undefined; + if (roots.has(child) === false && root !== undefined) { + child.userData.tnChunkRoot = undefined; + root.attach(child); + } + } + entry.group.removeFromParent(); + this.bumpBundle(entry.group); + } + + /** The main-record shard a batch's key lands in, minted as a child of the world on first use. */ + shardOf(shared: SharedBatch): BundleGroup { + const shard = mainShard(shared.mesh.name); + let group = this.#bundles.get(shard); + if (group === undefined) { + group = new BundleGroup(); + group.name = "world-main-bundles"; + this.#host.add(group); + this.#bundles.set(shard, group); + } + return group; + } + + /** The recorded main batches, over every shard. */ + mainBundleChildren(): number { + let children = 0; + for (const group of this.#bundles.values()) children += group.children.length; + return children; + } + + /** + * Where a freshly bound or grown main batch's mesh goes: the bundle group when it has been dressed + * into one, the world otherwise. Three re-parents on `add`, so a bundled mesh has to be asked for + * or it silently leaves the group it was recorded in. + */ + attach(shared: SharedBatch): void { + // A grow replaces the mesh object, so the marker `#bundleIn` set has to follow the batch's own + // flag onto it. See `bundled`. + shared.mesh.userData.tnBundled = shared.bundled; + if (shared.bundled === true) this.shardOf(shared).add(shared.mesh); + else this.#host.add(shared.mesh); + // A record holds the object it was cut from, so a mesh that arrived by a rebind or a grow is not + // the one the last record named: one re-record, and the record counts this mesh from here. + if (shared.bundled === true) { + shared.mesh.frustumCulled = false; + shared.bundledCount = shared.mesh.count; + this.bumpBundle(this.shardOf(shared)); + } + } + + /** + * Name one main key to the GPU scene, and dress its mesh against the scene's live buffers. + * + * A caster or wide key is named `@x,z` or `@*` and belongs to the shadow passes, which the GPU scene + * does not touch, so those are left exactly as they were. + * + * The key is recorded whether or not the scene is on, because a prewarm mints a whole ring before + * the first frame that can answer whether this backend can run the scene, and a key recorded only + * when the dress happened is a key the scene is never told about. `#seedGpuKeys` comes back for + * those; see there. + */ + adoptGpu(shared: SharedBatch, assetId: string, key: string, level: number, part: number): void { + if (shared.role !== "main") return; + const named = { asset: this.#host.canonical(assetId), key, level, part }; + shared.main = named; + if (this.#gpuScene.on === false) return; + shared.gpu = named; + this.dressGpu(shared, named); + } + + /** + * Point one main key's mesh at the GPU scene's buffers, and hand the asset's gate table over. + * + * The mesh's `instanceMatrix` becomes the shared compacted matrix buffer, its geometry a clone that + * shares the part's own attributes and carries this key's indirect record, and its whole-mesh + * frustum test is off — the dispatch already tested every instance against the camera's six + * planes, and the mesh's own bounds are the whole resident ring. Idempotent: a settled frame's + * buffers are the ones it was dressed against, and a regrow, a rebind or a `grow` replaces them, + * which is the only thing that brings a dressed mesh back through here. + * + * A key whose placements outgrew its region is regrown here rather than silently dropping instances + * against the capacity guard, which is what a fixed ceiling did to any key a walk filled past. + * + * The buffers are read after `scene.key` rather than before it, because minting the first key is + * what allocates them: a dress that gave up on their absence was a dress that never happened, and + * the only frame that would have retried it was one whose fast path had already found the mesh + * dressed. A regrow replaces them, so the attribute handed to the mesh is the one a draw will read. + */ + dressGpu(shared: SharedBatch, key: IKeyDescriptor): void { + const scene = this.#gpuScene; + const asset = this.#host.asset(key.asset); + const part = asset?.levels[key.level]?.[key.part]; + if (asset === undefined || part === undefined) return; + // The asset's own resident placements, not one level's share of them: a placement's level is + // decided per frame from where the camera is, so every level's region has to hold all of them. + const capacity = Math.max(shared.liveCeiling, this.#gpuResident.get(key.asset) ?? 0); + const settled = scene.drawn; + const held = scene.regionOf(key.key); + // The args buffer and the drawn buffer are allocated and grown independently: minting a key + // grows `args` and leaves `drawn` the object it was, so the mesh's `instanceMatrix` and its + // indirect record can point at two different generations. The fast path used to watch only + // `drawn`, and a mesh left holding an old args record was one the dispatch no longer wrote — + // a forest that drew nothing while the readback said the counts were correct. + const geometry = shared.mesh.geometry; + const liveArgs = scene.args; + const bound = + liveArgs !== undefined && + geometry.indirect === liveArgs && + geometry.indirectOffset === (held?.argsIndex ?? -1) * DRAW_ARGS_BYTES; + if ( + settled !== undefined && + shared.mesh.instanceMatrix === settled && + shared.mesh.frustumCulled === false && + (held?.capacity ?? 0) >= capacity && + bound && + // Where the mesh hangs is part of what this pass owns: a batch that was retired and handed back + // by a rebind is a child of the world again, and the bundle that last recorded it no longer + // draws it. Without this the walk would re-mint a key into a bundle that is missing it. + this.#host.isBundleHome(shared) + ) { + // A sibling may have grown the level's capacity without moving this mesh, so the submission + // bound follows the live region rather than the dress that wrote it. See `gpuCount`. + shared.gpuCount = held?.capacity ?? shared.gpuCount; + this.shadowKeyFor(key, part); + return; + } + const region = scene.key(key.key, new Float32Array(part.local.elements), capacity, { + group: `${key.asset}:${String(key.level)}`, + part: key.part, + // The whole run at once, so a part minted later lands in the slot `firstKey + part` names + // rather than at the end of the key space, where another level's key is in between. + parts: (asset.levels[key.level] as readonly IAssetPart[]).length, + }); + if (region === undefined) return; + const levels = this.#gpuLevelKeys.get(key.asset) ?? []; + levels[key.level] = { + firstKey: region - key.part, + parts: asset.levels[key.level]?.length ?? 0, + }; + this.#gpuLevelKeys.set(key.asset, levels); + const drawn = scene.drawn; + const args = scene.args; + const args2 = scene.regionOf(key.key); + if (drawn === undefined || args === undefined || args2 === undefined) return; + // Before the mesh is dressed, so the republish a replacement does takes the dressed branch and + // submits at this bound rather than at a zero the CPU never drew. See `gpuCount`. + shared.gpuCount = args2.capacity; + // Before the mesh is dressed, so the republish a replacement does takes the dressed branch: a + // batch that draws from the scene's buffers is shown by the per-asset gate in `#cullMainPass`, + // not by its own count. + shared.gpu = key; + const mesh = shared.mesh; + // Its own geometry object, so the indirect record is this mesh's, over the SAME attributes: the + // original is the shape the caster halves and a rebind still draw with. `clone()` would copy + // every vertex buffer of every key a second time. + const view = indirectView(mesh.geometry, args, args2.argsIndex * DRAW_ARGS_BYTES); + // A mesh three has already compiled cannot be re-dressed by writing fields onto it: its node + // binds the `instanceMatrix` the object held at that compile, and three keeps that binding for + // the object's whole life. A regrow is the same case — a new `drawn` attribute is a new binding + // for the same reason — so both take the same road: a fresh object carrying everything the old + // one had, and the old one let go. Measured on machinefall, where the prewarm had compiled every + // main batch on the CPU path: 3,724 indirect draws submitted in 2 s over records that were + // correct at draw time, and a dressed forest that drew nothing, until the meshes were fresh + // objects; a material of its own (below) never changed that on its own. + if (mesh.instanceMatrix !== drawn) { + const next = freshMeshFor(mesh, view, mesh.count) as ICasterScaleMesh; + redressMaterial(next); + this.#host.carryPrewarm(mesh, next); + this.seat(shared.replaceMesh(next), next); + next.instanceMatrix = drawn; + next.frustumCulled = false; + } else { + mesh.geometry = view; + mesh.instanceMatrix = drawn; + mesh.frustumCulled = false; + } + // What the record has to say for the draw to name a triangle: the view's own index count, which + // is the shape this key draws. The scene is told rather than left to guess, so its validation can + // hold the record against it. + scene.indexCount(key.key, view.index?.count ?? 0); + // The asset's gate table is built from the keys minted so far, so a level whose key is not minted + // yet carries no parts and the dispatch draws that level nowhere. Re-registering is a no-op while + // the table holds, and a rewrite when a key joined it. + scene.slot(key.asset, this.gatesOf(key.asset, asset)); + // The origin this key's main-pass draws are counted under, read off the object at the draw + // (`RenderPassBudget`). Written after the mesh swap above, so a re-dressed fresh object carries + // it, and before `#bundleIn`, which supersedes it with `tnBundled` for a world with bundles on. + shared.mesh.userData.tnDrawSource = "gpuScene"; + // The bundle the last settled frame replayed is now wrong: this pass minted or regrown a key, + // swapped the geometry, or took a fresh mesh object onto the scene's new buffers. Reaching here + // is one of the three, which is why this is the only place a re-record happens — the fast path + // above is every other frame of the walk. + this.bundleIn(shared); + if (shared.bundled === true) this.bumpBundle(this.shardOf(shared)); + // The shadow twin of this key, minted and dressed on the same pass that named the main one: a + // level that draws keys finds its mesh in the same frame the main pass found its buffer, and a + // regrown twin is re-dressed here rather than left on the buffer the dispatch replaced. + this.shadowKeyFor(key, part); + } + + /** + * The replacement takes the old object's place, and the old one is let go: `InstancedMesh.dispose` + * frees nothing the replacement shares — the vertex buffers and the part's own material belong to + * the refcount path that made them. + * + * Through `add` and `removeFromParent` rather than by writing `parent` and `children`, because + * three fires `childadded`/`childremoved` on the way and the clustered-mesh tracker answers the + * world's arrivals and departures by those alone. The old slot is restored afterwards, so the + * parent's traversal order — and so every draw's order within a frame — is what it was. + */ + seat(old: InstancedMesh, next: InstancedMesh): void { + const parent = old.parent; + if (parent === null) this.#host.add(next); + else { + const at = parent.children.indexOf(old); + old.removeFromParent(); + parent.add(next); + const here = parent.children.indexOf(next); + if (at >= 0 && here >= 0) { + parent.children.splice(here, 1); + parent.children.splice(at, 0, next); + } + } + old.dispose(); + } + + /** + * The asset's own gates, as the kernel reads them: the CPU path's switch distances and cull + * distance, and the scene key each level's parts draw into. A level with no minted key has no parts, + * so the dispatch draws nothing at that level rather than reading a key that is not there. + */ + publishGpuGates(asset: IAssetState): void { + const gates = this.gatesOf(asset.id, asset); + this.#publishedGpuGates.set(asset.id, { + cull: gates.cull, + distances: [...gates.distances], + impostor: gates.impostor, + levels: gates.levels.map((level) => ({ ...level })), + locals: asset.levels.map((parts) => + parts.map((part) => Float32Array.from(part.local.elements)), + ), + }); + } + + gatesOf(id: string, asset: IAssetState): IAssetSlot { + const gates: { firstKey: number; parts: number }[] = []; + // The level's own contiguous run, which is what the kernel's `firstKey + part` addresses. The + // owner records it when dressing the key; reading it back from the scene would make the mesh + // check agree with a corrupted GPU gate table instead of checking that table independently. + const keyed = this.#gpuLevelKeys.get(id); + for (const [level] of asset.levels.entries()) + gates.push(keyed?.[level] ?? { firstKey: 0, parts: 0 }); + return { + cull: cullDistance(asset.definition.maxDistance), + distances: asset.distances, + // The dispatch scales the whole-asset impostor's terminal gate by each placement's own scale; + // the same flag the live-state reference reads, so the two cannot cross it differently. + impostor: asset.impostor !== undefined, + levels: gates, + }; + } + + /** + * Dress every main batch the world already holds, the mirror of {@link #seedGpuSources} for the + * keys rather than the sources: the keys a prewarm minted on the loading screen were minted with + * the scene off, and a key is only named at mint time, so a ring built that way came up with an + * empty region table and every batch still drawing from its own buffer. The dispatch was drawing + * 21,094 placements into no region at all, and the validation marker said so — `compared=0` — for + * a scene that reported itself on. + * + * The pool is seeded with the rest: a retained key's mesh is handed back by a rebind rather than + * re-minted, and rebinding replaces the instance buffer, so a parked batch is undressed by the + * park and dressing it here is what leaves the walk with a key that is already in the table. From + * here every main batch carries `gpu`, which is what puts it on the dispatch's per-instance answer + * and off the CPU repack and the refilter. + */ + seedGpuKeys(): void { + for (const shared of [...this.#shared.values()]) this.seedGpuKey(shared); + for (const shared of this.#retired.values()) this.seedGpuKey(shared); + } + + /** Dress one undressed main batch, or leave a batch the scene does not touch. */ + seedGpuKey(shared: SharedBatch): void { + // `main` is the key; `gpu` is the mesh pointing at the scene's buffers. A dressed mesh is left + // alone, and `#dressGpu` says so itself — it is idempotent against the buffers it was given. + if (shared.main === undefined || shared.gpu !== undefined) return; + this.dressGpu(shared, shared.main); + } + + /** + * The main meshes the scene owns and the main meshes the world holds, live and pooled, which is + * the `dressed=N/M` the marker carries. A scene reporting itself on over a ring it never dressed + * read `dressed=0/21094` on nothing, so the number is the owner's: the scene cannot see the meshes. + */ + gpuCensus(): { readonly dressed: number; readonly meshes: number } { + let dressed = 0; + let meshes = 0; + for (const batch of [...this.#shared.values(), ...this.#retired.values()]) { + if (batch.role !== "main") continue; + meshes += 1; + if (batch.gpu !== undefined) dressed += 1; + } + return { dressed, meshes }; + } + + /** + * Every dressed main mesh's own draw, and what this class's **live** state says it must be holding. + * + * The scene's own validation mirrors its own kernel, so a key, a record, a gate table and a mesh + * that do not correspond are one both sides reproduce and a check that reports `ok` over a forest + * drawn with another tree's geometry. These are the three numbers that are not the scene's: the + * mesh's own name, the record its own geometry points at (`indirectOffset`, which a dress wrote), + * and the instances the owner's own state selects for that name — `asset.distances` as + * {@link levelAt} reads it right now, `cullDistance(definition.maxDistance)`, the camera's own six + * planes over the scene's own placements, and each part's own offset composed in exactly as the + * dispatch composes it. + * + * Not the per-cell records this path's own refilter left behind: the GPU scene bypasses that + * refilter, so the moment it does, the records a mesh is checked against are a snapshot of a + * level selection that no longer applies. A far placement is then compared against a set that + * counted it at its near level, and the check reads `ok` over the picture it cannot see. + */ + gpuDraws(): readonly IMeshDraw[] { + const camera = this.#host.camera(); + if (camera === undefined) return []; + const lives = this.liveGates(); + if (lives.size === 0) return []; + // The dispatch's own camera, its own planes: this is asked for inside the dispatch that is + // running, so the eye and the six planes are the ones the kernel is reading now. + const planes = this.#gpuPlanes; + _cullFrustum.setFromProjectionMatrix( + _cullProjScreen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse), + ); + for (const [index, plane] of _cullFrustum.planes.entries()) { + const at = index * 4; + planes[at] = plane.normal.x; + planes[at + 1] = plane.normal.y; + planes[at + 2] = plane.normal.z; + planes[at + 3] = plane.constant; + } + _gpuEye.setFromMatrixPosition(camera.matrixWorld); + const expected = liveKeyInstances( + this.#gpuScene.placements, + (slot) => { + const id = this.#gpuScene.slotAsset(slot); + return id === undefined ? undefined : lives.get(id); + }, + { planes, x: _gpuEye.x, y: _gpuEye.y, z: _gpuEye.z }, + ); + const draws: IMeshDraw[] = []; + const args = this.#gpuScene.args; + for (const shared of this.#shared.values()) { + if (shared.role !== "main" || shared.gpu === undefined) continue; + const geometry = shared.mesh.geometry; + // `indirectOffset` is typed as an offset or a list of them; a scene's own record is one + // number, and a list is not a record this check can name, so it is not compared. + const offset = geometry.indirectOffset; + if (geometry.indirect === undefined || typeof offset !== "number") continue; + const region = this.#gpuScene.regionOf(shared.mesh.name); + draws.push({ + // The actual binding, asked of the mesh and the scene at this turn, not the record number: + // an offset can name the right record while the mesh reads an older args buffer with the + // same layout, and the field is what lets the check see a stale binding at all. + bound: + args !== undefined && + geometry.indirect === args && + offset === (region?.argsIndex ?? -1) * DRAW_ARGS_BYTES, + instances: expected.get(shared.mesh.name) ?? new Float32Array(0), + name: shared.mesh.name, + record: offset / DRAW_ARGS_BYTES, + }); + } + return draws; + } + + /** + * Every adopted asset's own gates as they stand right now, keyed by id. + * + * The part offsets are copied out of each part's `Matrix4` once per check, because the composition + * reads them as `Float32Array`s and a part's matrix is what the dispatch already composed with. + */ + liveGates(): Map { + const out = new Map(); + const slots = this.#gpuScene.gates(); + for (const [slot] of slots.entries()) { + const id = this.#gpuScene.slotAsset(slot); + const asset = id === undefined ? undefined : this.#host.asset(id); + const published = id === undefined ? undefined : this.#publishedGpuGates.get(id); + if (asset === undefined || published === undefined) continue; + out.set(asset.id, { + cull: published.cull, + distances: published.distances, + id: asset.id, + impostor: published.impostor, + locals: published.locals, + }); + } + return out; + } + + /** + * The source records one swapped-in entry draws from: one per placement, not per part, because the + * part's own offset is the key's and the dispatch composes it. Every part of the level shares the + * list, which is why releasing one is idempotent and a whole level's records are handed back by the + * first of its entries that leaves. + */ + placeSources(job: IBuildJob, entry: ICellBatch): number[] | undefined { + const sources = job.sources; + if (sources === undefined) return undefined; + const asset = this.#host.asset(entry.asset); + if (asset === undefined) return undefined; + const slot = this.#gpuScene.slot(entry.asset, this.gatesOf(entry.asset, asset)); + if (slot < 0) return undefined; + const byLevel = job.gpuByLevel ?? new Map(); + job.gpuByLevel = byLevel; + const held = byLevel.get(entry.level); + // A sibling part of this level shares the list already placed, and counts its own halves for + // itself: `#gpuResident` is the asset's, so this half's records are counted here. + if (held !== undefined) { + this.countPlaced(entry, held.length); + return held; + } + const records: number[] = []; + for (const source of sources) + if (source.level === entry.level) { + const at = this.#gpuScene.place( + slot, + source.matrix, + source.x, + source.y, + source.z, + source.radius, + source.scale, + ); + if (at >= 0) records.push(at); + } + byLevel.set(entry.level, records); + // What a key of this asset has to be able to hold, on any level: see `#dressGpu`. + this.#gpuResident.set(entry.asset, (this.#gpuResident.get(entry.asset) ?? 0) + records.length); + this.countPlaced(entry, records.length); + return records; + } + + /** + * The placed sources one cell's block contributed, carried onto the two caster halves behind it — + * `gpuPlaced`, the count `#publishCasterAdmission` measures a half against its own live records. + * + * One place for both halves and both directions, because `#gpuResident` counts the asset once and a + * half counts only what its own block contributed: every part of a level shares one list of placed + * sources and each half needs its own answer. Negative is the release path's way back down. + */ + countPlaced(entry: ICellBatch, placed: number): void { + if (entry.caster !== undefined) entry.caster.gpuPlaced += placed; + if (entry.wide !== undefined) entry.wide.gpuPlaced += placed; + } + + /** The world-grid square the follow point is in, as the `@x,z` half of a cluster key. */ + followCluster(): string { + return clusterOf( + Math.floor((this.#follow.position.x - this.#minX) / this.#cellSize), + Math.floor((this.#follow.position.z - this.#minZ) / this.#cellSize), + this.#cellsPerCluster, + ); + } + + /** What a released batch costs to keep: its instance buffer, which is all of it. */ + heldBytes(shared: SharedBatch): number { + return (shared.mesh.instanceMatrix.array as Float32Array).byteLength; + } + + /** + * Keeps a released batch for the walk back, or drops it. + * + * Empty is the precondition, and `#evict` guarantees it: it clears a cell's own block before the + * refcount falls, and the refcount is what calls this. A batch that still holds records means the + * buffer state cannot be trusted, so it goes the same way as one over the byte budget. + */ + retire(key: string, shared: SharedBatch): void { + // Live records, not the draw window: a main batch whose squares are all behind the camera still + // holds its records, and that is not a batch to dispose. + if (shared.live > 0) { + shared.mesh.dispose(); + return; + } + // Parked before the budget is measured, or the retained set is charged for buffers nothing keeps + // and the byte budget evicts entries as they arrive — the walk then finds nothing to hand back + // and mints a second mesh for every key it returns to. + shared.park(); + const bytes = this.heldBytes(shared); + this.#retired.delete(key); + this.#retired.set(key, shared); + this.#retiredBytes += bytes; + for (const [oldest, held] of this.#retired) { + if (this.#retiredBytes <= RETIRED_SHARED_BYTES) break; + this.#retired.delete(oldest); + this.#retiredBytes -= this.heldBytes(held); + held.mesh.dispose(); + } + } + + /** + * Whether this level's own batches cast. Only the caster clusters ask; a main mesh never casts, so + * the shadow map holds exactly the cluster meshes a level's window covers. + */ + casts(level: number): boolean { + return this.#castShadowLevels > level; + } + + /** + * An asset too small to resolve in any window but the finest one's, so its wide caster goes on + * that level's layer alone. Measured on the authored bounds the package carries, which is the one + * number a world has for every asset it never loaded a tree for. + */ + smallCaster(assetId: string): boolean { + const bounds = this.#manifest.assets[assetId]?.bounds; + if (bounds === undefined) return false; + return bounds.max[1] - bounds.min[1] < this.#smallCasterMetres; + } + + /** + * A block in `shared` for one cell's `count` records, growing the buffer when it is full. `cluster` + * is the world-grid square the cell sits in, and only a main batch keeps it: its draw window is + * narrowed per square, and a square is a grouping, not a position. + */ + segmentIn(shared: SharedBatch, count: number, cluster?: string): number | undefined { + const segment = shared.allocate(count, cluster); + if (segment !== undefined) return segment; + // A dressed batch's grow is logical: it raises the ceiling the GPU scene sizes this key's region + // from and mints no mesh, so the fresh-mesh allowance — which bounds new pooled meshes and their + // node builds — does not gate it. A CPU grow is a real replacement mesh and still respects it. + if (shared.gpu === undefined && this.#freshThisUpdate >= this.freshAllowance()) + return undefined; + const old = shared.grow(); + // Only a real replacement is a new object to dress, attach and hand to the pool; a logical GPU + // grow keeps the live mesh, which `#dressGpu` re-points at the grown region before the draw. + if (old !== undefined) { + this.#freshThisUpdate += 1; + // The replacement is a different object, so the layer and the cull hook `grow` copied neither of + // have to be dressed onto it again — a grown caster that kept layer 0 would be drawn by the main + // camera and lost to the shadow level, and a grown main batch would stop narrowing at all. + this.dressMesh(shared, shared.role === "main" && this.#receiveShadow); + this.attach(shared); + // The mesh this grow replaced goes back to the pool: a cached shadow level can still replay a + // draw of it until its window next moves, and the pool keeps the buffer and the uuid rather + // than letting one array of retired meshes grow without bound. + parkMesh(old); + } + return shared.allocate(count, cluster); + } + + /** + * Move this asset's already-built wide casters into the one whole-asset mesh, with root records. + * + * Before the bake landed the far shadow held one wide mesh per (level, part 0), each carrying + * `placement * part 0 local` for its own level's placements. The whole-asset representation is one + * root draw per placement, so every one of those segments is dropped and re-claimed from the root + * records the builds kept (`ICellBatch.wideRoot`) in the single mesh `#wideFor` now names. When a + * block cannot be taken in this update's fresh-mesh allowance the asset is retried next frame; the + * old per-level meshes, empty by then, are retired so the far shadow stops submitting them. + */ + retargetWideCasters(asset: IAssetState): void { + const impostor = asset.impostor; + if (impostor === undefined) return; + const globalKey = `${asset.id}:*@${WIDE_CLUSTER}`; + for (const cell of this.#host.residentCells()) + for (const entry of cell.batches) { + if (entry.asset !== asset.id) continue; + if (entry.wide !== undefined && entry.wide.mesh.name === globalKey) continue; + // The old segment's shape and its doubled part offset are both wrong now; hand it back. Its + // placed count goes with the records, so the mesh left behind does not read as fully placed + // and the global one does not read as unplaced: see `SharedBatch#gpuPlaced`. + const placed = entry.gpu?.length ?? 0; + if (entry.wide !== undefined && entry.wideSegment >= 0) { + entry.wide.gpuPlaced -= placed; + entry.wide.clear(entry.wideSegment); + } + entry.wide = undefined; + entry.wideSegment = -1; + if (!this.casts(entry.level) || !this.wideOwed(asset.id, entry.part)) continue; + const source = entry.wideRoot; + if (source === undefined) continue; + const wide = this.batchFor(asset.id, entry, globalKey, "wide", false); + const at = wide === undefined ? undefined : this.segmentIn(wide, source.count); + if (wide === undefined || at === undefined) { + this.#host.deferWide(asset.id); + continue; + } + entry.wide = wide; + entry.wideSegment = at; + wide.gpuPlaced += placed; + wide.write(at, source); + } + this.retireWideKeys(asset.id, globalKey); + } + + /** Give the asset's pre-bake per-key wide meshes back, once the handoff has emptied them. */ + retireWideKeys(assetId: string, keep: string): void { + const prefix = `${assetId}:`; + for (const [key, shared] of [...this.#shared]) { + if (key === keep || shared.role !== "wide" || !key.startsWith(prefix)) continue; + if (shared.live > 0) continue; + shared.mesh.removeFromParent(); + this.bundleOut(shared); + this.#shared.delete(key); + this.retire(key, shared); + } + } + + /** Releases the shared meshes of one asset (or of every asset), keeping them for the walk back. */ + drainShared(assetId?: string): number { + const failed = 0; + for (const [key, shared] of this.#shared) { + if (assetId !== undefined && !key.startsWith(`${assetId}:`)) continue; + shared.mesh.removeFromParent(); + // The mesh is out of the world, so it is out of the bundle the world recorded. A key that comes + // back is minted or rebound into the group again, and each of those is one re-record. + this.bundleOut(shared); + this.#shared.delete(key); + // Retired, not dropped, so the cell that comes back draws into the same mesh. + this.retire(key, shared); + } + return failed; + } + readonly #follow: IWorldCellsFollow; + readonly #host: ICellRenderHost; + constructor(settings: ICellRenderSettings, host: ICellRenderHost) { + this.#host = host; + this.#follow = settings.follow; + this.#budgets = settings.budgets; + this.#cellSize = settings.cellSize; + this.#minX = settings.minX; + this.#minZ = settings.minZ; + this.#manifest = settings.manifest; + this.#placements = settings.placements; + this.#coveredFreshMeshesPerUpdate = settings.coveredFreshMeshesPerUpdate; + this.#freshMeshesPerUpdate = settings.freshMeshesPerUpdate; + this.#receiveShadow = settings.receiveShadow; + this.#castShadowLevels = settings.castShadowLevels; + this.#extentBounds = settings.extentBounds; + this.#preserveAuthoredParts = settings.preserveAuthoredParts; + this.#cellsPerCluster = settings.cellsPerCluster; + this.#cellsPerCullCell = settings.cellsPerCullCell; + this.#smallCasterMetres = settings.smallCasterMetres; + this.#bundlesWanted = settings.bundlesWanted; + this.#bundleReason = settings.bundleReason; + this.#invalidateShadows = settings.invalidateShadows; + this.#mainCullToldAt = settings.mainCullToldAt; + } + + get gpuOn(): boolean { + return this.#gpuScene.on; + } + get shadowMoved(): boolean { + return this.#shadowMoved; + } + get meshStalled(): boolean { + return this.#meshStalled; + } + get gpuSeeded(): boolean { + return this.#gpuSeeded; + } + set gpuSeeded(value: boolean) { + this.#gpuSeeded = value; + } + get bundlesWanted(): boolean { + return this.#bundlesWanted; + } + bundleGroup(shard: number): BundleGroup | undefined { + return this.#bundles.get(shard); + } + hasShared(key: string): boolean { + return this.#shared.has(key); + } + hasRetired(key: string): boolean { + return this.#retired.has(key); + } + setShared(key: string, shared: SharedBatch): void { + this.#shared.set(key, shared); + } + intersectsObject(mesh: Object3D): boolean { + return _cullFrustum.intersectsObject(mesh); + } + dispatchShadow(renderer: IRendererLike, level: IShadowLevel): void { + this.#gpuScene.dispatchShadow(renderer, level); + } + setLodBias(bias: number): void { + this.#gpuScene.setLodBias(bias); + } + report(): ReturnType { + return this.#gpuScene.report(); + } + resetFresh(): void { + this.#freshThisUpdate = 0; + this.#meshStalled = false; + } + clearCompileRecords(): void { + this.#recordedWhileBusy.clear(); + } + validateGpuFrame(renderer: IRendererLike, frame: number) { + return this.#gpuScene.validateCurrentDispatch(renderer, frame); + } + + releaseGpu(): void { + this.#gpuScene.dispose(); + this.#publishedGpuGates.clear(); + this.#gpuLevelKeys.clear(); + this.#bundleSafety.clear(); + this.#gpuResident.clear(); + } + releaseRetired(): void { + for (const shared of this.#retired.values()) shared.mesh.dispose(); + this.#retired.clear(); + this.#retiredBytes = 0; + } + invalidateBundles(key: string): void { + for (const group of this.#bundles.values()) this.bumpBundle(group); + const chunks = this.#chunkBundles.get(key)?.group; + if (chunks !== undefined) this.bumpBundle(chunks); + } + publishChunkBounds(cell: IResidentCell, box: Box3 | undefined, proxies: number): void { + if (box !== undefined) { + if (proxies > 0) this.shadowRecordsMoved(box, true); + // A bundled chunk is drawn or not by its cell's record, and that record is answered from + // the cell's cull volume — which is built from the cell's placements. A chunk is a cell's + // other half, so a cell holding chunks and no scatter would have an empty volume and never + // be drawn at all. Widened here rather than shrunk on eviction: the volume outlives one cell + // whenever a neighbour still holds the cluster, and a stale box only ever shows a cell that + // is there. + if (this.#bundlesWanted) + this.#cullCells.get(clusterOf(cell.x, cell.z, this.#cellsPerCullCell))?.box.union(box); + } + } + bundleStats(): IWorldCellsStats["bundle"] { + return { + children: this.mainBundleChildren() + this.#chunkBundleChildren, + on: this.#bundlesWanted && (this.mainBundleChildren() > 0 || this.#chunkBundleChildren > 0), + cpuMeshes: [...this.#shared.values()].filter( + (shared) => shared.bundled && shared.gpu === undefined, + ).length, + gpuMeshes: [...this.#shared.values()].filter( + (shared) => shared.bundled && shared.gpu !== undefined, + ).length, + eligibility: { ...this.#bundleSafety.stats }, + reason: this.#bundleReason, + records: this.#bundleRecords, + }; + } + mainCullStats(): IWorldCellsStats["mainCull"] { + return { + frustums: this.#mainCullFrustums, + repacks: this.#mainCullPacks, + visibleEpoch: this.#visibleEpoch, + windows: this.#mainCullWindows, + }; + } + + flushCompiles(renderer: IRendererLike | undefined): void { + // A bundle recorded while a compile was in flight left out every draw whose pipeline was not + // ready, and the replay never re-checks; re-record those shards now that it has settled. Set + // `#compileBusy` first, so this flush's own bumps are not added back to the set. + const compiles = renderer?.compileCount ?? 0; + const busy = renderer?.compiling === true || compiles !== this.#compileSeen; + this.#compileSeen = compiles; + this.#compileBusy = busy; + if (!busy && this.#recordedWhileBusy.size > 0) { + const recorded = [...this.#recordedWhileBusy]; + this.#recordedWhileBusy.clear(); + for (const group of recorded) if (group.parent !== null) this.bumpBundle(group); + } + } + enableGpu( + renderer: IRendererLike | undefined, + wanted: boolean, + validate: boolean | undefined, + tally: boolean, + occlusion: OcclusionMode, + ): void { + // The first frame that hands over a renderer is the only one that can answer whether this + // backend can run the GPU scene, and it prints its answer once: `enable` reports, then returns + // early for the rest of the world's life. + if (renderer !== undefined) + this.#gpuScene.enable( + renderer, + wanted, + validate ?? gpuSceneValidationRequested(), + tally, + occlusion, + ); + // The one check the scene cannot make for itself, and the only one that reads this class's own + // numbers rather than the scene's tables: a dressed main mesh's own indirect record, against the + // instances this path composes for the key that mesh is named. Registered before the keys are + // seeded, because the first check is a whole ring's worth of meshes and every one of them is + // already dressed by the time it runs. + this.#gpuScene.drawsFrom(() => this.gpuDraws()); + } + announceGpu(): void { + this.#gpuScene.announce(this.#host.renderer() as IRendererLike, this.gpuCensus()); + } +} diff --git a/packages/core/src/world-cells.ts b/packages/core/src/world-cells.ts index 469049e59b..e28a7444a8 100644 --- a/packages/core/src/world-cells.ts +++ b/packages/core/src/world-cells.ts @@ -1,97 +1,83 @@ +import { CellModels, type IAssetState, assetAliases } from "./world-cell-models.js"; +import { CellRender, MAIN_CULL_SQUARE_METRES, mainShard } from "./world-cell-render.js"; +export type { IAssetState } from "./world-cell-models.js"; + +import type { + IAssetPart, + IBuildJob, + IResidentCell, + IShadowRegion, + ISpawnWaiter, + IWorldCellsBudget, + IWorldCellsFollow, + IWorldCellsGpuTally, + IWorldCellsLoadOptions, + IWorldCellsStats, + IWorldRegionReadiness, +} from "./world-cell-data.js"; import { - Box3, - BufferAttribute, - BufferGeometry, - type Camera, - Frustum, - Group, - InstancedBufferAttribute, - InstancedMesh, - Material, - Matrix4, - Mesh, - Object3D, - Quaternion, - SkinnedMesh, - Sphere, - Vector3, -} from "three"; -import { BundleGroup } from "three/webgpu"; + CellImpostors, + PLACEMENT_RECORD_BYTES, + PLACEMENT_RECORD_FLOATS, + cullDistance, +} from "./world-cell-impostors.js"; + +import { + admissionBudgetMs, + cellKey, + crossesGate, + nonNegativeInteger, + positiveInteger, + positiveMetres, +} from "./world-cell-data.js"; + +export type { + IWorldCellsFollow, + IWorldCellsBudget, + IWorldCellsTerrainOptions, + IWorldCellsLoadOptions, + IWorldCellsGpuTally, + IWorldRegionReadiness, + IShadowRegion, + IWorldCellsStats, +} from "./world-cell-data.js"; + +import { Box3, type Camera, Group, Matrix4, type Object3D, Quaternion, Vector3 } from "three"; + import { type IAssetLoader, createAssetLoader } from "./assets.js"; import type { IComputeDriven } from "./compute-driven.js"; -import { isEngineRenderHook, markEngineRenderHook } from "./engine-render-hook.js"; import { INSTANCED_LOD_MAX_PIXEL_ERROR } from "./instanced-batch-lod.js"; import { InstancedBatch } from "./instanced-batch.js"; -import { mergeByMaterial } from "./merge-parts.js"; -import { - type ILodChain, - biasedLodDistance, - conservativeViewDepth, - isLodJoinProxy, - lodChainOf, - setLodBias, - setModelLodLayers, - worldSphere, -} from "./model-lod.js"; -import { displacesVertices } from "./projection-plan.js"; +import { biasedLodDistance, setLodBias, worldSphere } from "./model-lod.js"; import { type OcclusionMode, occlusionMode } from "./render/depth-pyramid.js"; -import { cutoutSurface } from "./render/foliage-alpha.js"; -import { materialKey } from "./render/material-key.js"; -import { - advanceWriteEpoch, - currentWriteEpoch, - drainMeshPool, - dropPooledFor, - parkMesh, - pooledMesh, -} from "./render/mesh-pool.js"; -import { - IMPOSTOR_FAR_CULL_ATTRIBUTE, - WorldImpostorSurface, -} from "./render/world-impostor-surface.js"; -import { - type IImpostorPart, - type IImpostorRawRenderer, - IMPOSTOR_FRAME_PIXELS, - IMPOSTOR_VIEWS, - WORLD_IMPOSTOR_MARKER, - type WorldImpostorAtlas, - WorldImpostorBaker, - impostorBounds, -} from "./render/world-impostor.js"; + +import { advanceWriteEpoch, drainMeshPool } from "./render/mesh-pool.js"; import type { IRendererLike } from "./renderer.js"; -import { isStatic, markStatic } from "./static-transform.js"; -import { addInSlices, loadAll } from "./streaming.js"; + +import { addInSlices } from "./streaming.js"; import { DEFAULT_TARGET_FPS } from "./target-fps.js"; -import { within, yieldToHost } from "./warmup.js"; +import { yieldToHost } from "./warmup.js"; + +import { SharedBatch } from "./world-cell-batches.js"; import { - CellProxySelection, - type IPreparedCellProxy, - cellProxyGeometrySafe, - cellProxyMaterialMatches, - cellProxyMaterialSafe, - cellProxySourceSafe, - cellProxyWorldScale, - measureCellProxy, - prepareCellProxy, -} from "./world-cell-proxy.js"; + CHUNK_MERGE_MAX_TRIANGLES, + CellChunks, + cellLoaded, + release, + resolveRelative, +} from "./world-cell-chunks.js"; +import { CellPrewarm } from "./world-cell-prewarm.js"; + import { - DRAW_ARGS_BYTES, - type IAssetSlot, - type ILiveAsset, - type IMeshDraw, type IShadowLevel, type IWorldGpuFrameValidation, - type IWorldGpuSceneReport, - WorldGpuScene, bundlesAsked, gpuSceneRequested, - gpuSceneValidationRequested, levelAtGates, - liveKeyInstances, shadowGpuKeysRequested, } from "./world-gpu-scene.js"; import { heightSamplerFromHeightmap, loadWorldHeightmap } from "./world-heightmap.js"; +import { AdmissionBudget } from "./world-load-limiter.js"; import { type IWorldAsset, type IWorldCell, @@ -100,49 +86,11 @@ import { cellPlacements, validateWorldPackage, } from "./world-package.js"; -import { - type IAdmissionBudget, - type IWorldTilesOptions, - TerrainTileBudgetError, - TerrainTiles, -} from "./world-tiles.js"; +import { type IAdmissionBudget, TerrainTileBudgetError, TerrainTiles } from "./world-tiles.js"; + +export { bundleSafeMaterial } from "./world-bundle-safety.js"; -const PLACEMENT_RECORD_BYTES = 32; -const PLACEMENT_RECORD_FLOATS = 8; const DEFAULT_TILE_RESOLUTION = 129; -const CHUNK_NAME = "world-chunk"; -/** - * Bytes one merged chunk group may hold: 4 MiB, which is where a single first-draw upload stops - * being a few milliseconds and becomes a frame. - * - * The 228.8 MB of `createAttribute` buffers a browser walk measured — 826 buffers, 33 over 1 MB, - * the largest 23.2 / 18.1 / 17.2 / 16.4 MB — all arrived on the first draw of a merged chunk mesh, - * and that first draw took up to 230 ms. A group is split rather than grown, so each of those - * uploads is bounded and none of them is a stall. - */ -const CHUNK_MERGE_MAX_BYTES = 4 * 1024 * 1024; -/** Position (12) + normal (12) + uv (8) in float32: the three channels a merged group keeps. */ -const CHUNK_MERGE_BYTES_PER_VERTEX = 32; -/** Vertices in 4 MiB of the above. The budget a chunk's material group is split on. */ -const CHUNK_MERGE_MAX_VERTICES = CHUNK_MERGE_MAX_BYTES / CHUNK_MERGE_BYTES_PER_VERTEX; -/** - * Triangles a chunk's merged material group may reach before one of its `InstancedMesh` is left - * instanced instead. A merged group is one draw; past this many triangles the instanced draw is the - * cheaper of the two, so the mesh is kept rather than expanded. It is the same 4 MiB counted in the - * worst case, a de-indexed triangle of three 32-byte vertices, so the cap and the vertex budget above - * are the one size read in two units. See `IWorldCellsLoadOptions.chunkMergeMaxTriangles`. - */ -const CHUNK_MERGE_MAX_TRIANGLES = Math.floor( - CHUNK_MERGE_MAX_BYTES / (3 * CHUNK_MERGE_BYTES_PER_VERTEX), -); -/** - * How long one chunk's warm-up may take before the chunk is attached uncompiled. The engine's own - * loading-screen bound for a single pipeline is 2,000 ms, and a compile that outlives it is a - * compile that is not coming back — which is a slow frame rather than a chunk that never appears. - */ -const CHUNK_WARM_TIMEOUT_MS = 2000; -/** The engine frame clock, for the warm-up's bound. */ -const warmClock = (): number => globalThis.performance?.now() ?? Date.now(); /** Milliseconds one `update` may spend admitting streamed content, unless the game says otherwise. */ const DEFAULT_ADMISSION_BUDGET_MS = 2; /** @@ -171,51 +119,6 @@ const SETTLED_FOLLOW_METRES = 0.5; * toward. Raise it when a game's gates are far apart, lower it when placements sit inside one. */ const LEVEL_REFILTER_STEP_METRES = 2; -/** - * Instance-matrix bytes a world keeps in released shared batches, oldest dropped first. A batch is - * sized to its asset's largest cell times the resident cells, so on a 2 km package one runs 80 kB - * median and 0.9 MB worst, and every key in the world at once is 26 MB. The budget holds the walk's - * own back-traffic — a batch leaves when the last cell holding its asset leaves, and is wanted - * again a few hundred metres back — and lets the rest go, so a session that crosses the whole map - * does not carry the whole map. - */ -const RETIRED_SHARED_BYTES = 8_000_000; -/** - * Records a retired batch's instance buffer is shrunk to; see `SharedBatch.park`. - * - * A released batch holds no records, so what keeping it is buying is the uuid three built a node for, - * and the buffer is the whole of what that retention costs. At the batch's own ring capacity the - * walk's back-traffic costs more than the byte budget holds, so the budget evicts the very keys a - * walk is about to return to and the walk re-mints a mesh for each of them. Parked, a key costs - * `PARKED_SHARED_CAPACITY` records; `rebind` puts the capacity back before anything is written. - */ -const PARKED_SHARED_CAPACITY = 1; -/** - * Prewarmed batches minted per update, and the updates a minted batch must survive before it counts - * as drawn. Three keys its render-object/node cache by `object.uuid`, so every InstancedMesh it has - * never drawn costs a NodeBuilder build the first frame it is projected (~9 ms in the main pass, and - * again in the shadow pass for a level that casts): hundreds of them land in a few seconds of - * walking, which is the walk-time frame cost this pays earlier instead. - * - * The rate is a rate of *rendered* updates, not of `update` calls. Minting a batch is cheap (an - * InstancedMesh and a zeroed buffer) and its build is paid by the next render, so an unbounded queue - * would hand the whole map's seconds of builds to one frame. 8 per update is ~150 ms of build on the - * frame that pays it, which the loading screen covers. - */ -const PREWARM_PER_UPDATE = 8; -/** - * Two updates is the honest floor for "drawn at least once in both the main and the shadow - * context": the mesh is added during one update and the next render projects it. Tracking it per - * context would mean hooking the renderer, which this class does not own. - */ -const PREWARM_WARM_UPDATES = 2; -/** - * Updates a mesh may still be owed a prewarm draw after the gate settled before its borrow is handed - * back anyway. An empty prewarmed batch draws, so this is the ceiling on the ones that never will — - * a projection that collapsed the world, a camera that never reached the mesh — and not a diagnosis - * of any of them. Without it those meshes would hold their hook for the whole walk. - */ -const PREWARM_RELEASE_UPDATES = 8; /** * The projection a baked chain's levels are switched at when the world names none: 60° of vertical * field of view over 1080 raster rows, which is the desktop view a switch distance is a function of. @@ -228,124 +131,8 @@ const DEFAULT_AUTO_LOD_VIEWPORT_HEIGHT = 1080; * names none. See `IWorldCellsLoadOptions.autoLod`. */ const DEFAULT_AUTO_LOD_MAX_PIXEL_ERROR = INSTANCED_LOD_MAX_PIXEL_ERROR; -/** - * GPU bytes of completed impostor atlases one world admits, 128 MiB. A 128 px two-attachment RGBA8 - * atlas with mips is 2,796,160 B, so 48 fit and the 49th is refused before its atlas is allocated, - * keeping its source LODs. An atlas a live asset still draws from is never evicted, so the active - * record count is a hard admission cap rather than a target the cache trims toward — eviction only - * ever drops an inactive record to make room for a new one. - */ -const DEFAULT_IMPOSTOR_ATLAS_BUDGET_BYTES = 128 * 1024 * 1024; -/** - * The declared byte cost of one atlas at the bake's fixed frame, the same numbers - * `WorldImpostorAtlas.bytes` reports: both RGBA8 attachments' mip chains, the logical colour atlas - * cost rather than every physical GPU buffer a render target holds. Computed here so the cache - * budget is enforced BEFORE `baker.begin` allocates the atlas, never after. - */ -const IMPOSTOR_ATLAS_BYTES = ((): number => { - const levels = Math.floor(Math.log2(Math.max(1, IMPOSTOR_FRAME_PIXELS))) + 1; - let bytes = 0; - for (let level = 0; level < levels; level += 1) { - const edge = Math.max(1, IMPOSTOR_FRAME_PIXELS >> level); - bytes += edge * edge * IMPOSTOR_VIEWS * 4; - } - return 2 * bytes; -})(); -/** - * The far-residency diagnostic marker: one bounded line per whole-map aggregate built, naming the one - * `InstancedMesh` per atlas cache key, its original placements, the live/near-owned split, its - * physical instance bytes and how many times its instance buffer has been written. - */ -const WORLD_IMPOSTOR_FAR_MARKER = "TN_WORLD_IMPOSTOR_FAR"; -/** - * Bytes one far instance costs: its sixteen-float32 matrix plus the one-float per-instance cull the - * shared far surface reads. Both are physical allocations the budget bounds; the cull is uploaded - * once because the placement never moves. - */ -const FAR_INSTANCE_BYTES = 16 * 4 + 4; -/** - * How far past the frustum a world-grid square is kept inside the main pass's draw window, in metres. - * - * The window is decided once per draw, from the camera that draw uses, and a square that has just - * left the frustum is dropped from it — so a square at the frustum's edge would be drawn in one - * frame and not the next, and a tree on that edge pops in at the edge of the screen. Growing each - * square's box by this margin keeps a band of it in the window while it is still off screen, so a - * tree crosses into view already there. - * - * An eighth of the default 64 m square is deliberately small. It has to exceed what a camera - * travels between two draws — 8 m a frame is 480 m/s at 60 fps, so no playable camera reaches it — - * and nothing more, because every metre of it is a metre of geometry the vertex stage still shades. - */ -const MAIN_CULL_PAD_METRES = 8; -/** - * Side of the main cull's own visibility cell, in metres, when it divides the package's `cellSize`. - * - * This is the finest grid the main pass can cull on, and it is not a free parameter: a shared - * batch's block is one *world* cell's records and placements never move, so a world cell cannot be - * spread across two cells of a finer grid without giving every world cell its own block — one more - * main mesh per key, which is the cost this whole cull exists to avoid. So a package whose - * `cellSize` is not a whole multiple of 32 m falls back to its own `cellSize`, which is the square - * its placements are already cut on, and the rule below is the whole of it. - * - * A visibility cell is a sub-square of the shadow caster's world-grid square, and it is deliberately - * finer than that square: `clusterSize` is sized for how many meshes a shadow level submits, while - * this is sized for how little geometry the vertex stage is handed. It is also the one volume the - * cull tests, so everything in it — a 64 m cell's XZ and the Y its own placements and asset bounds - * reach — is what a draw of that cell costs. See {@link WorldCells.#cullMainPass}. - */ -const MAIN_CULL_SQUARE_METRES = 32; -/** - * How often `TN_WORLD_MAIN_CULL` is printed. Five seconds is a line a playtest capture can hold and - * a walk long enough to see the window move; every frame is a wall of text over the marker it would - * report on. - */ -const MAIN_CULL_MARKER_MS = 5e3; -/** - * Assets reported one by one before the `TN_WORLD_LOD_CHAIN` marker collapses into a single line. - * A 2 km package carries hundreds of chained assets, and a console line each is a wall of text that - * buries the one that matters. - */ -const CHAIN_LOD_MARKER_LIMIT = 20; -/** - * The layer the level shadow cameras render and the main camera does not; see - * `VirtualShadowNode`'s `VIRTUAL_SHADOW_CASTER_LAYER`. Duplicated from `index.ts` rather than - * imported, because the world chunk is separate from the main one and this is the one value the two - * halves of the cascade have to agree on. - */ -const VIRTUAL_SHADOW_CASTER_LAYER = 28; -/** - * The layer the wide shadow levels render and the fine ones do not: one caster mesh per - * `asset:level:part` holding every cell's records, for a level whose window covers the whole - * resident ring, where a cluster per square is a draw per square for the same pixels. Duplicated - * from `index.ts` like the layer above, and a bit of its own so a level camera renders one caster - * granularity or the other and never both. - */ -const VIRTUAL_SHADOW_WIDE_CASTER_LAYER = 27; -/** - * The wide casters only the finest level renders: a fern's or a grass tuft's, whose shadow is - * sub-texel noise past a few metres and whose per-key draw a 192 m or 640 m window was paying - * hundreds of times for nothing. Duplicated from `virtual-shadow.ts` like the two layers above, and - * chosen from the asset's own authored bounds rather than from what a game says, so a package - * nobody has annotated still gets the cull. `shadows.smallCasterMetres` is the override. - */ -const VIRTUAL_SHADOW_SMALL_CASTER_LAYER = 26; -/** - * The layer a GPU-driven shadow map draws: one mesh per `asset:level:part`, over the shadow twin of - * the dispatch's own output, so a level submits a handful of indirect draws rather than one per - * world-grid square. Every level camera carries this bit, so it replaces both halves above rather - * than joining the choice between them. Duplicated from `virtual-shadow.ts` like the three above. - */ -const VIRTUAL_SHADOW_KEY_LAYER = 25; -/** - * The name half a shadow key is minted under: `asset:level:part@gpu`, beside the main key it twins - * rather than in place of it, so both meshes exist — one for the main pass's compaction buffer and - * one for the shadow twin's, which is the whole difference between them. - */ -const GPU_KEY_SUFFIX = "@gpu"; /** An asset shorter than this casts into the finest level only; see the layer above. */ const SMALL_CASTER_METRES = 1.5; -/** The whole ring, as one caster cluster: `@*` is every square's records in one mesh. */ -const WIDE_CLUSTER = "*"; /** * Placements filtered per unit of admission work. * @@ -356,9024 +143,2158 @@ const WIDE_CLUSTER = "*"; */ const ADMISSION_SLICE_PLACEMENTS = 256; -/** The point a streamed world follows; an `Object3D` satisfies this shape. */ -export interface IWorldCellsFollow { - readonly position: { readonly x: number; readonly y?: number; readonly z: number }; -} - -/** Hard caps on what stays resident. A cap that is reached reports pressure, it never throws. */ -export interface IWorldCellsBudget { - readonly residentCells: number; - readonly instances: number; - /** Placement-record bytes (`32` per instance) the resident cells are allowed to weigh. */ - readonly bytes: number; -} - -/** Terrain options forwarded to the composed `TerrainTiles`; `tileSize` defaults to `cellSize`. */ -export interface IWorldCellsTerrainOptions { - readonly tileSize?: number; - readonly tileResolution?: number; - readonly lodFactors?: readonly number[]; - readonly lodDistances?: readonly number[]; - readonly skirtDepth?: number; - /** - * Tiles kept resident around the follow tile, independent of the cell `ring`: terrain can reach - * to the horizon while props stay near. Defaults to `ring`, so raising only this widens the - * ground without widening the props, and the far tiles take the coarser `lodDistances` levels. - */ - readonly streamRadius?: number; - /** - * Tiles that get a `createCollider` body, by Chebyshev radius from the follow tile. Defaults to - * `ring`. A tile entering or leaving it creates or disposes its collider, so a wide terrain - * radius does not have to mean a physics body for every tile in it. - */ - readonly colliderRadius?: number; -} - -export interface IWorldCellsLoadOptions { - /** - * Logical path of `world.json` (`world/world.json`); every other path in the package resolves - * against it. A leading `/` is accepted and stripped, so an uncompiled `public/world` keeps - * working. - */ - readonly url: string; - /** - * Loader the package's paths resolve through — its manifest, or the authored names when there is - * none. Defaults to a fresh `createAssetLoader()`; inside a game, pass `ctx.assets` so the - * package's compiled output and compressed textures reach the renderer the game booted with. - * - * The world releases the source models it asks this loader for only when it created the loader - * itself: each asset's requested level paths are released through `release("model", path)` once - * the last asset naming a path is gone, so a streamed package does not pin an internally owned - * cache. An explicitly supplied loader is the caller's — the world never releases its cache, so a - * second game or world holding the same model keeps it alive; the caller ends that lifetime with - * its own `release`. The loader is never cleared, and a path this world never asked for is never - * touched either way. - */ - readonly assets?: IAssetLoader; - /** Game-owned terrain surface, handed straight to `TerrainTiles`. */ - readonly surface: IWorldTilesOptions["surface"]; - /** Passed straight to `TerrainTiles` for per-tile colliders. */ - readonly createCollider?: IWorldTilesOptions["createCollider"]; - /** Read once per update; an `Object3D` works. */ - readonly follow: IWorldCellsFollow; - /** Chebyshev radius in cells to keep resident; a cell leaves only beyond `ring + 1`. */ - readonly ring: number; - readonly budgets: IWorldCellsBudget; - /** False streams props/chunks only; the caller owns terrain geometry and collision. */ - readonly terrain?: IWorldCellsTerrainOptions | false; - /** Cooked cell proxies are considered by default; false retains source rendering. Static CPU - * cells only, with strict measured triangle/draw savings and projected-error hysteresis. - * Source collision and shadow representations stay resident; unsupported sources retain detail. */ - readonly hlod?: boolean; - /** Optional immutable per-record 3D draw reach in metres, indexed like placements. - * Nonnegative finite values or Infinity (no extra limit); combined with asset maxDistance. - * Filtered by the existing admission/refilter budget, never by a separate culler. - */ - readonly placementReach?: Float32Array; - /** Inward return margin for authored CPU LODs, in [0, 1). Requires gpuScene:false and impostors:false; omission keeps source levels. - * Omitted, selection uses the existing direct gates. State survives cell eviction/revisit. */ - readonly lodHysteresis?: number; - /** Preserve supplied part/material identity and exact authored LOD membership, including shadow batches. Requires impostors:false; omission keeps source levels. - * Default content sharing and missing-alpha-part coverage remain enabled when omitted. */ - readonly preserveAuthoredParts?: boolean; - /** Already decoded immutable world-v1 data; URL still supplies the base for model paths. */ - readonly data?: { - readonly manifest: IWorldPackage; - readonly placements: ArrayBuffer; - /** Omitted values load the manifest heightmap unless terrain is false. */ - readonly heightmap?: Uint16Array; - }; - /** - * How a scattered part whose own material is `transparent` is drawn. `"cutout"` (the default) - * gives the part a clone of that material with `transparent: false` and an `alphaTest`, so the - * instanced draw needs no per-instance sorting and the depth buffer rejects what is behind it; - * `"blend"` draws the material as authored. One clone per asset part, never per cell. - */ - readonly transparentScatter?: "cutout" | "blend"; - /** - * Shadows for the streamed world, all off by default. `cast` makes scattered batches cast into - * the scene's shadow map. Their `castLevels` finest distance levels (default 1: the near shape) - * each cast their own shape; a placement drawn at a coarser level of a chain casts with the - * coarsest of those, so it keeps its shadow without a caster mesh per level — every level casting - * its own minted ~3x the casters and the churn showed as garbage collection on a walk (PRD-541). - * Authored `lods` past `castLevels` cast nothing. `receive` lets scatter and terrain receive - * shadows. - */ - readonly shadows?: { - readonly cast?: boolean; - readonly castLevels?: number; - readonly receive?: boolean; - /** - * @deprecated Accepted and ignored. Clusters replace it: what a shadow level submits is bounded - * by the clusters its own window covers, not by a hand-tuned radius. It still validates, so a - * game that set it keeps compiling and keeps loading. - */ - readonly castDistance?: number; - /** - * Called in a batch at most once a second after streamed records changed, so the shadow levels - * that read them redraw. A call with no region asks `VirtualShadowNode.invalidateAll`; - * nothing changed means no call. - * - * Admissions and evictions are separate calls, each with the union of the bounds of the records - * that changed — the placement position widened by the asset's own bounds, unioned over the - * records of this update and nothing else, so a hook that forwards it to - * `VirtualShadowNode.invalidateRegion` redraws the levels whose window covers them and leaves - * the rest of the cascade holding its maps. A call with no region is still a blanket one: the - * prewarm passes it while a caster's first draw is owed, and there is no region for that. - */ - readonly invalidate?: (region?: IShadowRegion) => void; - /** - * An asset whose authored bounds are shorter than this casts into the finest shadow level only, - * default 1.5 m. Ground cover below it — ferns, grass, bushes — casts a shadow that is sub-texel - * noise at every range a wide level covers, and costs one draw per key per level there. Raise it - * to draw everything at every range, lower it to cull more. Measured on the authored bounds, so - * a package that scales a fern up four times is judged on the un-scaled fern. - */ - readonly smallCasterMetres?: number; - }; - /** - * Triangles a hand-placed chunk's merged material group may reach before one of its - * `InstancedMesh` is left instanced. Defaults to 43,690, the same 4 MiB - * budget the merged buffers are split on counted in the worst case, a de-indexed triangle: a merged - * group is a single draw, so expanding into it wins until the merged geometry is too big for the - * frame, and above it the instanced draw is the cheaper of the two. A group that crosses the budget - * is split into several meshes in traversal order rather than grown, and an instanced shape over - * 2,048 triangles is never expanded. Everything else in a chunk merges regardless — one mesh per - * material, in the main pass and in every shadow pass that redraws it. - */ - readonly chunkMergeMaxTriangles?: number; - /** - * `(url) => Promise`, overriding `assets.model`; a raw `GLTFLoader` or a game's own - * loader works. The url is the authored one, so a compiled package wants `assets` instead. - */ - readonly loadModel?: (url: string) => Promise; - /** - * Model loads in flight at once, assets and chunks together. Defaults to `loadAll`'s - * `concurrency`, six. - */ - readonly concurrency?: number; - /** - * Cell-asset batches refiltered per `update`, nearest cell first. Defaults to 16. A cell that did - * not get its turn keeps drawing the batches it has until a later update replaces them. - */ - readonly rebuildsPerUpdate?: number; - /** - * Seconds of motion to stream ahead of, default 1.5: the ring centres on where the follow point - * will be (`position + velocity * prefetchSeconds`), so a fast camera finds cells already loaded - * instead of outrunning them. The lead is capped at three quarters of the ring, so the cell the - * follow point is in always stays resident. `0` streams around the follow point itself. - */ - readonly prefetchSeconds?: number; - /** - * New batch meshes created per update, default 2. On WebGPU each new InstancedMesh builds its - * own shader the first frame it draws (~10-20 ms of main thread), so a burst of first-seen assets - * is spread over frames instead of stacked into one; recycled meshes are not counted. - */ - readonly freshMeshesPerUpdate?: number; - /** - * New batch meshes per update while the startup cover hides the world, default 8x - * `freshMeshesPerUpdate`. The first-draw shader cost the default spreads is invisible behind an - * opaque loading screen, and a canopy that needs fresh meshes otherwise trickles in two a frame. - */ - readonly coveredFreshMeshesPerUpdate?: number; - /** - * Side of the world-grid square one shared batch's records are clustered into, in world units. - * Defaults to the package's own `cellSize`, which is the square the placements are already cut - * on. Each `(key, cluster)` is its own caster InstancedMesh with its own bounds on the shadow - * caster layer, so a virtual-shadow level submits only the clusters its window covers. The main - * pass keeps one mesh per `asset:level:part` whatever this is, so it costs main-pass draws - * nothing. A larger square means fewer caster meshes and more of the world in every level render. - */ - readonly clusterSize?: number; - /** - * Milliseconds one `update` may spend admitting streamed content — cell batches, terrain tiles, - * colliders and `maxDistance`/`lods` refilters together. Defaults to 2, and `Infinity` opts out - * of the ceiling entirely. - * - * The ceiling is a budget, not a promise: one unit of work always finishes, so a frame spends at - * most this plus its last unit. Work that does not fit waits for the next `update` rather than - * being dropped, and `stats().admission` reports what is waiting. A frame that admits nothing - * costs nothing here. - */ - readonly admissionBudgetMs?: number; - /** - * The admission budget while the startup cover hides the world and readiness is pending. Defaults - * to 8x `admissionBudgetMs` (16 ms for the 2 ms default), so the spawn a loading screen waits on - * is admitted in fewer frames. Set it equal to `admissionBudgetMs` to keep one budget throughout. - */ - readonly coveredAdmissionBudgetMs?: number; - /** - * Milliseconds source for the admission budget, `performance.now` by default. Injectable so a - * test can prove the ceiling instead of hoping a machine is slow enough to show it. - */ - readonly admissionNow?: () => number; - /** - * The screen-space projection the extra levels of a baked AutoLOD chain are switched at, for an - * asset whose `world.json` entry names no nonempty authored `lods` of its own. An explicit empty - * list also uses the chain when prepared geometry and material still match its static contract. - * - * A chain measures its levels' geometric error in world units and the model runtime turns that - * into pixels per frame; a batched draw cannot, so the same test is solved for distance once, - * here, and every placement beyond it draws the next level. `fovY` is the vertical field of view - * in degrees and `viewportHeight` the drawing-buffer height in raster pixels — a CSS height is the - * wrong number, exactly as it is for the model runtime. - * - * Defaults are 60° over 1080 raster rows: a desktop view, and what the switch distance is a - * function of. A world whose camera this class cannot see should pass its own, or a world with a - * narrow field of view (a telephoto gun sight) or a tall window will switch earlier than its - * pixels ask for. An asset with nonempty authored `lods` ignores all three numbers entirely: the package is - * the authority on its own shape. - * - * `maxPixelError` is the error budget, in pixels, every synthesized level is selected against, and - * it defaults to 4 rather than to the budget the asset's chain was registered with. The - * registered budget is calibrated for a `ModelLod`, which picks a level for *one* mesh from where - * that mesh is on screen; an instanced draw cannot, because every placement in the draw is at its - * own distance and the draw is one call with one geometry. The batched answer has to be solved - * once, for the whole key, which moves every switch distance outward by the ratio of the budgets — - * and a 1 px budget is calibrated for a hero object filling the screen. At 1 px a tree's switches - * land at 400–600 m on a 1080-row view, which on a 2 km map with a 640 m ring is past most of the - * resident world: the levels are all there and none of them is ever selected, which is the same - * full triangle bill the chain was added to remove. Four pixels puts the first switch inside the - * ring, where it can actually do something. - */ - readonly autoLod?: { - readonly fovY?: number; - readonly viewportHeight?: number; - readonly maxPixelError?: number; - }; - /** - * The GPU-driven main pass: one compute dispatch culls and LOD-selects every resident placement - * into a shared matrix buffer, and each main key draws its own region of it through an indirect - * record. The CPU keeps only coarse per-cell visibility and admits the complete resident - * `maxDistance` source set; a walking camera therefore costs no instance repack or cull/LOD - * replacement. An explicit `placementReach` remains an admission boundary because it has no GPU - * slot equivalent. - * - * `true` by default where the backend can run it: it needs compute, storage buffers and - * `drawIndexedIndirect`, and a backend without them falls back to exactly this class's CPU path — - * a lost saving, never a wrong picture. `gpuScene: false`, `TN_GPU_SCENE=0` or `?tnGpuScene=0` - * turns it off, and `stats().gpuScene` and the `TN_WORLD_GPU_SCENE` line say which path a run - * took. - */ - readonly gpuScene?: boolean; - /** - * Hold every dispatch's indirect args against the pure `cullAndSelect` reference and print the keys - * that disagree. `false` by default, and `TN_GPU_SCENE_VALIDATE=1` or `?tnGpuSceneValidate=1` turns - * it on. - * - * A readback is a queue submission and a mapped buffer, so this is a mode for answering "which key - * draws fewer instances than the CPU path" and never a walk to be measured with. - * For a fixed-step diagnostic walk, call {@link validateGpuFrame} after the render and await its - * promise before advancing the next frame; that opt-in fence checks every dispatch and replaces - * the periodic samples for that world. - */ - readonly gpuSceneValidate?: boolean; - /** - * Sample what the GPU actually selects in the main pass — a readback of the indirect args every - * 30 dispatches — and report it as `stats().gpuScene` and on the `TN_WORLD_GPU_SCENE` - * line. Off unless asked: the engine asks when the frame budget is on, and a validation turns it on - * by itself, so `TN_FRAME_BUDGET`'s `mainGpuTriangles` is the GPU-selected count and not the mesh - * capacity `tri=` reports. Set it explicitly when driving the world without a frame budget. - */ - readonly gpuSceneTally?: boolean; - /** - * Run the hierarchical-depth occlusion test in the cull kernel and report what it would cull. - * - * `"measure"` (the default unless `?tnOcclusion=off`, `TN_OCCLUSION=off` or a caller's own value - * says otherwise) builds a max-distance pyramid from the previous frame's scene-pass depth and - * counts the placements the test hides **while drawing all of them**: a measured frame is the frame - * an unmeasured run draws, so the only difference between the two runs is the count and the cost of - * building the pyramid. `"off"` runs no test and allocates nothing. `stats().gpuScene.occlusion` - * reports the counts, the pyramid's own cost, and `refused: no scene depth` when no render chain - * installed a pass to take a depth from. - * - * See {@link occlusionMode} and `render/depth-pyramid.ts`. - */ - readonly occlusion?: OcclusionMode; - /** - * Record every GPU-dressed main batch mesh and every resident cell's hand-placed chunks into - * `BundleGroup`s, and replay the records instead of re-walking three's per-object path for each - * draw. On by default: PRD-494 AC-2 measured the walking `draw` span at a 5.0 ms lower p95 than - * develop over 3 interleaved runs with bundles on and a picture a blind world gate called equal, - * which is the measurement the old opt-in default was decided without — so `bundles: false`, - * `?tnBundles=0` or `TN_BUNDLES=0` is the way back to the per-object path, and `stats().bundle` - * and the `TN_WORLD_BUNDLE` line say which way a run took and what asked for it. - * - * A record is an encoder, not a render pass, so only a draw that needs no live pass is recorded: - * a framebuffer reader, a transmissive impostor, a skinned or morphed mesh and a per-object hook - * all stay on the path they were already on. See `bundleSafe`. - * - * The render list inside a bundle is fixed when it is recorded, so a bundled mesh is never hidden: - * the dispatch draws zero instances for a key the camera cannot see, and an indirect draw of zero - * instances costs the GPU nothing. Toggling `visible` would force a re-record instead, and the - * shadow node's texel gate skips a bundled mesh for the same reason. - * - * A chunk has no dispatch behind it, so the two answers it needs are its own: its meshes are never - * culled by the record, because a mesh a record dropped is missing geometry the moment the camera - * turns towards it, and each cell records into its own group whose `visible` is the main cull's - * answer for that cell — the same coarse answer the scatter batches take, one cell wide. What that - * costs is the price of the option: a cell the cull cannot see is not drawn at all, which takes its - * chunk casters out of the shadow levels with it, and a cell it can only partly see draws the part - * facing away. Both rasterize nothing or nearly so; the missing shadow is the real difference. - */ - readonly bundles?: boolean; - /** - * Raise the LOD distance bias when the main pass is over its share of the frame's GPU budget, and - * decay it back toward 1 when it is comfortably under, default true. - * - * The main pass is triangle-bound: a flyover's near and mid tree levels are millions of triangles - * and the GPU time tracks that count. When `renderer.gpuMainMs()` — the smoothed main-pass GPU - * time — exceeds `mainGpuShare` of a 60 fps frame, every LOD switch is crossed earlier by - * multiplying the camera distance both selection paths compare by one shared multiplier, coarsening - * the same placements the GPU is already struggling to draw. It rises in bounded steps, never in a - * jump, so no level pops. `false` pins the bias at 1 for the whole run, which is byte-identical to - * the selection before this feature existed: `?tnAdaptiveLod=0`, `TN_ADAPTIVE_LOD=0` or - * `__tnAdaptiveLod = 0` also turns it off. - */ - readonly adaptiveLod?: boolean; +interface IWorldCellsInit extends IWorldCellsLoadOptions { + readonly reachRanges: Map< + IWorldCell, + Map + >; + readonly manifest: IWorldPackage; + readonly placements: ArrayBuffer; + readonly heightmap: Uint16Array; + readonly baseUrl: string; + /** `baseUrl` as the loader keys it: no leading slash, which is what a manifest lists. */ + readonly logicalBase: string; /** - * The fraction of a frame's GPU time the main pass may take before the adaptive LOD bias rises, - * `(0, 1]`, default 0.5. - * - * The budget is `mainGpuShare x min(deltaTime, 1/60 s)`: the frame cap keeps the budget honest the - * same way the shadow refresh gate does, because the frame that draws an expensive main pass is - * itself long and reading its own inflated delta would let the cost it judges pass its own test. - * `?tnMainGpuShare=0.1`, `TN_MAIN_GPU_SHARE=0.1` or `__tnMainGpuShare = 0.1` forces adaptation on a - * GPU that would otherwise never exceed the share. + * Asset ids the package named twice for one model, each pointing at the id that is kept. Built + * once in `load`, where the loader can be asked what each path really fetches; empty when nothing + * is a duplicate. See {@link assetAliases}. */ - readonly mainGpuShare?: number; + readonly aliases: ReadonlyMap; /** - * Bake a whole-asset octahedral impostor for every alpha-cutout foliage asset and append it as the - * asset's terminal, two-triangle LOD. Default false: omission keeps the authored/generated source - * levels, and `?tnImpostors=1` or `TN_IMPOSTORS=1` opts a launch in. It is opt-in until the judged - * Machinefall capture passes (PRD-473 AC-3); `preserveAuthoredParts` and `lodHysteresis` still - * require it to be off. - * - * Set true to bake them, or leave it off to keep the source levels for a visual comparison. - * - * The bake runs inside the render cadence — one of its sixteen views per `process` — so it costs a - * slice of a frame rather than a hitch, and it starts only after the asset's own model has been - * adopted. A seamless switch needs the impostor's atlas to cover the same cutout the source draws, - * so an asset whose levels carry no alpha-cutout part is never baked. `false` drops only the atlas - * terminal: every level the authored package and its baked chain give is kept, and the authored - * middle-level cutout coverage a level would otherwise be missing its foliage at is still applied. + * Whether `load` fabricated the loader it hands in as `assets` instead of the game supplying one. + * `load` always passes a loader to the constructor, so `assets === undefined` cannot tell an owned + * loader from a caller's; this is the bit that can. Left out, ownership is `assets === undefined`. */ - readonly impostors?: boolean; + readonly ownsAssets?: boolean; } /** - * What the GPU actually selected in the main pass, from the scene's last landed tally. - * - * `triangles` is the GPU-selected count (`instanceCount x indexCount / 3` summed over the indirect - * records), never the mesh-capacity upper bound `renderer.info.render.triangles` reports, and it - * covers the main pass only: shadow passes are not tallied. `ageFrames` is how many dispatches have - * passed since those bytes were issued, so a caller never mistakes a stale sample for this frame's. + * Load a package file from the first candidate the loader says might serve it, the way the + * loader's own `loadFirst` walks them: the authored name, then the project's `assets/` source. One + * candidate is what a manifest gives, and a delete-test project has no manifest, so taking only + * the first is a 404 for a package that is present on disk. When none answer, one error names every + * url tried and its status — the failure is two places looked at, not one missing file. */ -export interface IWorldCellsGpuTally { - readonly instances: number; - readonly triangles: number; - readonly ageFrames: number; -} - -export interface IWorldCellsStats { - readonly residentCells: number; - /** Resident cells whose initial placement runs and chunks have completed admission. */ - readonly loadedCells: number; - readonly residentKeys: readonly string[]; - /** Terrain tiles drawn, out to `terrain.streamRadius`; independent of the props' `ring`. */ - readonly residentTiles: number; - /** Tiles holding a `createCollider` body, inside `terrain.colliderRadius`; 0 with no factory. */ - readonly residentColliders: number; - /** Placement instances the resident cells hold, before any `maxDistance` filter. */ - readonly instances: number; - readonly loadsInFlight: number; - /** Model loads waiting for a free lane; served nearest-cell first, in admission order. */ - readonly loadsQueued: number; - /** - * Shared batches still to be minted, plus the ones minted and not yet drawn; `0` once - * `prewarmed` has resolved. - */ - readonly pendingPrewarm: number; - /** Shared batches minted by the prewarm so far, cumulative. */ - readonly prewarmMinted: number; - readonly evictions: number; - /** Cumulative `maxDistance`/`lods` refilters performed, across every update. */ - readonly rebuilds: number; - /** - * Cumulative refilter *passes* run, whether or not they queued a rebuild. The pass walks every - * resident cell, derives both distance brackets and sorts what it found, so this is what says - * whether a follow point standing inside the 2 m step paid for it; `rebuilds` only says whether it - * was worth anything. See `LEVEL_REFILTER_STEP_METRES`. - */ - readonly refilters: number; - /** - * Cumulative replacement builds that entered CPU placement-level selection. Initial admission is - * excluded; GPU `maxDistance`/LOD movement is not a replacement because its complete resident - * source set stays available to dispatch selection. Explicit `placementReach` replacements are - * included because reach remains a CPU admission boundary. - */ - readonly levelRefilters: number; - /** - * Cumulative stale cell-assets the refilter passes found, across every update. The pass walks every - * resident cell and gates it as a whole before it looks at one batch, so a follow point that has - * crossed nothing leaves this still — which is what says the walk cost brackets and comparisons and - * not allocations. - */ - readonly refilterEntries: number; - /** - * Cumulative refilters whose rebuild held the same records and were settled without a write, - * clear or compaction. A rising share of `rebuilds` is a follow point crossing a cell's distance - * bracket rather than any placement crossing a gate. - */ - readonly unchanged: number; - readonly failures: number; - /** - * What the main-pass cull cost, cumulative, and the evidence that a settled camera costs nothing. - * - * `frustums` is one a frame that had a perspective camera — it used to be one per main mesh, ~230 - * of them on a 2 km walk. `windows` is how many of those meshes had to re-derive their window at - * all, and `repacks` how many copied records: both are 0 on a frame where the visible squares did - * not change and no record joined or left, which is what the two integer comparisons in - * `SharedBatch.cullFrom` buy. - */ - readonly mainCull: { - readonly frustums: number; - readonly windows: number; - readonly repacks: number; - readonly visibleEpoch: number; - }; - /** - * What the GPU-driven main pass is doing, and why it is not: `on` is the answer, `reason` is the - * one line `TN_WORLD_GPU_SCENE` printed, and `dispatches` is what the walk actually paid for its - * per-instance work. `mainCull.repacks` stays at `0` while it is on, and `levelRefilters` reports - * replacement builds that still execute CPU placement-level selection. GPU `maxDistance`/LOD - * movement is not a replacement because the source set stays resident for dispatch selection. - * The aggregate `refilters` counter can still rise for its retained scan, and explicit - * `placementReach` replacements remain visible. `dressed` over `meshes` is the marker line's own - * pair, and it is the one that says the scene has meshes to draw with: an `on` with `0` dressed is - * a ring the CPU path is still drawing. - */ - readonly gpuScene: { - readonly on: boolean; - readonly reason: string; - readonly dispatches: number; - /** Resident source records: one per placement, shared by every part of the level it reached. */ - readonly instances: number; - readonly keys: number; - readonly dressed: number; - readonly meshes: number; - /** - * Instances and triangles the GPU actually selected in the main pass, and the age of that - * sample in dispatches. Absent until the first tally lands, never zero: the scene's own - * `instances` above is what it holds, and `gpuInstances` is what the GPU drew. - */ - readonly gpuInstances?: number; - readonly gpuTriangles?: number; - readonly gpuTallyAgeFrames?: number; - /** - * What the pyramid occlusion test would cull, when a launch asked it to measure. `share` is the - * would-cull share of the main pass's GPU-selected triangles from the same landed readback, which - * is the number the go/no-go multiplies by `gpuMain`; `reason` names why nothing was measured. - */ - readonly occlusion?: NonNullable; - }; - /** - * Main-pass bundle membership: `children` counts grouped roots — every recorded main mesh under one - * of the main record's shards, plus every chunk of every resident cell under that cell's own group — - * `cpuMeshes` and `gpuMeshes` count shared batch members, and `on` reports actual main/chunk - * membership. These are membership counts, not observed backend draws. `records` counts - * invalidations since load: membership, material or geometry changes and CPU window repacks - * invalidate; GPU indirect count updates do not. - * - * `eligibility` counts CPU material checks, snapshot validations, fresh structural walks and - * oversized or unsupported child output. Snapshots check own links and Three's child readers on - * every check, detecting deep edits without requiring `needsUpdate`; child discovery still allocates. - * - * `reason` is who asked: `default` is a run that set nothing, `option` is a load that named - * `bundles`, and `launch` is `?tnBundles` / `TN_BUNDLES` / `__tnBundles`. It is on the marker line - * too, because a run that turns the default off is exactly the run a reader cannot tell from a - * failing one without it. - */ - readonly bundle: { - readonly on: boolean; - readonly children: number; - readonly cpuMeshes: number; - readonly gpuMeshes: number; - readonly eligibility: { - readonly checks: number; - readonly refreshes: number; - readonly validations: number; - readonly uncached: number; - }; - readonly reason: string; - readonly records: number; - }; - /** - * The runtime impostor path: how many assets appended a terminal level, how many bakes are still - * queued or in flight, the triangles all terminal levels submit together — the number the whole - * feature exists to reduce — and the cache size: the logical colour atlas bytes held (each atlas's - * RGBA8 colour and normal mip chains), not every physical GPU buffer a render target may own, and - * the budget those bytes are enforced against. - */ - readonly impostor: { - readonly assets: number; - readonly pending: number; - readonly atlasBytes: number; - readonly budgetBytes: number; - readonly terminalTriangles: number; - /** - * The whole-map far aggregates: one `InstancedMesh` per atlas cache key, the ORIGINAL placements - * they hold (`instances`), how many of those the near ring has taken back (`nearOwned`), the - * physical instance bytes, and how many times an aggregate's buffer has been written. Missing - * measurements stay absent; a settled camera holds `uploads` still. - */ - readonly far: { - readonly aggregates: number; - readonly instances: number; - readonly live: number; - readonly nearOwned: number; - readonly bytes: number; - readonly uploads: number; - }; - }; - /** Cumulative rejected requests: cells skipped, instances or bytes refused, terrain retries. */ - /** Compilation and zero-count main backend submission are separate observations. These counts - * do not prove GPU execution, image quality, or reduced frame time. bytes includes pending - * reservation and decoded copies/textures, never a claim about physical driver allocation. */ - readonly hlod: { - readonly on: boolean; - readonly loaded: number; - readonly compiled: number; - readonly observed: number; - readonly active: number; - readonly pending: number; - readonly bytes: number; - readonly refused: number; - /** Why the latest proxy was refused, or empty when none was; a count alone names no cause. */ - readonly reason: string; - readonly failures: number; - }; - readonly pressure: { readonly cells: number; readonly instances: number; readonly bytes: number }; - /** - * What the last `update` spent on admitting streamed content, and what it left behind. - * - * A rising `backlog` is the honest signal that admission is not keeping up with the follow point; - * it is the one number that says a player is outrunning the world. - */ - readonly admission: { - /** Milliseconds the last `update` spent admitting; at most the budget plus its last unit. */ - readonly spentMs: number; - /** Cell-asset builds the last `update` had no budget for, still queued for a later one. */ - readonly deferred: number; - /** Units of work those builds still owe — placement slices left plus meshes left to publish. */ - readonly backlog: number; - }; -} - -/** Local render coverage, independent of shader prewarm and distant admission. */ -export interface IWorldRegionReadiness { - readonly requiredCells: number; - readonly loadedCells: number; - readonly requiredTerrainTiles: number; - readonly loadedTerrainTiles: number; - readonly failures: number; - readonly cancelled: boolean; - readonly ready: boolean; +async function loadLogical( + assets: IAssetLoader, + path: string, + load: (url: string) => Promise, +): Promise { + const candidates = await assets.resolve(path); + const failures: string[] = []; + for (const url of candidates) { + try { + return await load(url); + } catch (error) { + failures.push(`${url} (${error instanceof Error ? error.message : String(error)})`); + } + } + throw new Error( + `World file '${path}' could not be loaded from ${String(candidates.length)} candidate url(s): ${failures.join("; ")}`, + ); } -interface ICellBatch { - readonly asset: string; - /** - * The run these records came from. One cell can hold two runs of one canonical asset — two names - * for the same model — and each builds its own, so a refilter and a rebuild have to know which of - * the cell's entries belong to the run they are replacing. - */ - readonly run: IWorldRun; - readonly batch: InstancedBatch; - readonly level: number; - readonly part: number; - /** How far the follow point has to move before this batch is refiltered, `undefined` never. */ - readonly threshold: number | undefined; - /** The shared mesh this cell's instances are written into, and its segment there (-1: none). */ - shared: SharedBatch | undefined; - segment: number; - /** - * The GPU scene's source records this entry draws from, shared by every part of its level, and - * handed back by `#clearSegment`. `undefined` while the scene is off or the records are the - * dispatches' own already — a record released twice is a no-op, which is what lets one list be - * held by a whole level's parts. - */ - gpu: number[] | undefined; - /** - * The shadow-caster cluster this cell's records also go into, and its segment there (-1: none). - * The main mesh is one per key and casts nothing; the same records are written into one mesh per - * `(key, cluster)` on the caster layer, which is what a virtual-shadow level culls. - */ - caster: SharedBatch | undefined; - casterSegment: number; - /** - * The wide-caster half, and its segment there (-1: none): the same records again, in one mesh per - * key covering the whole ring, alone on the wide caster layer. A shadow level whose window covers - * the ring would otherwise submit one cluster per square for the same pixels. - */ - wide: SharedBatch | undefined; - wideSegment: number; - /** - * The root-matrix records this entry's level put in the far shadow, when the asset has an - * impostor: the placement transform alone, never `placement * part local`. The whole-asset impostor - * bakes every LOD0 part's own transform into its atlas, so its quad is drawn at the placement root - * and composing a source part's offset again would apply that part's transform twice. `undefined` - * for a non-impostor asset, whose wide half keeps the per-part source records. - */ - wideRoot: InstancedBatch | undefined; - /** - * The world bounds of the records this entry holds, from the filter that built it. The shadow - * invalidation is the union of these over the entries a swap moved in and out, which is the point - * of measuring them per record rather than per cell: a 48 m level's window is smaller than the - * cell box, so the cell box always covered it and always redrew it. - */ - shadowBounds: Box3 | undefined; - lastFilterY: number; - lastFilterX: number; - lastFilterZ: number; +/** A `fetch` that refuses anything but an ok response, so candidates are walked on status. */ +async function fetchOk(url: string): Promise { + const response = await fetch(url); + if (!response.ok) throw new Error(`status ${String(response.status)}`); + return response; } -/** The geometry and material one batch draws; a part is one of these plus what it does not need. */ -type IBatchShape = Pick; - -/** Which GPU-scene key one main batch draws: `asset:level:part`, and the part's place in the model. */ -interface IKeyDescriptor { - readonly asset: string; - readonly key: string; - readonly level: number; - readonly part: number; +/** + * One empty batch per (level, part), so a placement is filtered and drawn together and an emptied + * level or a part nobody placed costs one missing mesh rather than the whole cell's run. + */ +function newBatches(asset: IAssetState): InstancedBatch[][] { + const batches: InstancedBatch[][] = []; + for (const parts of asset.levels) { + const levelBatches: InstancedBatch[] = []; + for (const part of parts) + levelBatches.push(new InstancedBatch({ geometry: part.geometry, material: part.material })); + batches.push(levelBatches); + } + return batches; } -/** One key a loaded asset's levels contributed to the prewarm queue. */ - -/** One asset's placements, waiting to be built, or already holding their own cell's records. */ -interface IPrewarmEntry { - readonly asset: string; - /** - * Which pass this mesh is: the main pass's key, its caster cluster, its wide, or the shadow twin - * of the main key that a GPU-driven map draws instead of the two caster halves. - */ - readonly role: "cluster" | "key" | "main" | "wide"; - readonly geometry: BufferGeometry; - readonly key: string; - readonly level: number; - readonly material: Material; - /** The part of the level, which is the third half of a key's name. */ - readonly part: number; +/** How many meshes a build will publish: one per (level, part). */ +function publishCount(batches: readonly InstancedBatch[][]): number { + let total = 0; + for (const levelBatches of batches) total += levelBatches.length; + return total; } /** - * A plain `{ min, max }` region, the shape a shadow level's invalidation reads and the shape a game - * can be handed without importing three. Plain numbers, so the object a caller records its bounds - * in is the object that crosses the boundary. + * One empty bounds box per `(level, part)`, beside the batches `#addPlacements` measures. Written + * as a loop because this file is held to a rule that the array method's name is a material's + * texture property. */ -export interface IShadowRegion { - /** Only added casters; their redraw can retain the existing depth. */ - admission?: boolean; - min: { x: number; y: number; z: number }; - max: { x: number; y: number; z: number }; +function newBoxes(batches: readonly InstancedBatch[][]): Box3[][] { + const boxes: Box3[][] = []; + for (const levelBatches of batches) { + const level: Box3[] = []; + for (const _batch of levelBatches) level.push(new Box3()); + boxes.push(level); + } + return boxes; } /** - * One asset part at one distance level, drawn for every resident cell by a single InstancedMesh. - * - * Each cell owns a block of the instance buffer, admitted as the count it needs and freed as one, - * so the draw object count is per asset part and level rather than per cell: a 25-cell ring of a - * 218-asset world was ~5,000 meshes and 20+ ms of CPU a frame in three's per-object render path. - * - * A shared batch packs its live instances at the front of the instance buffer, so `mesh.count` is - * the live total. It used to be `(the highest used segment + 1) * segmentSize`, which drew every - * zero-matrix slot of a partly filled segment and of every freed segment below the top one — tens of - * thousands of slots per pass — and each empty slot still ran the vertex shader over the whole mesh - * in the main pass and in every shadow pass. A block is now sized to its own cell's batch instead of - * to the asset's largest cell, and a freed or shortened block is compacted into the lowest hole, so - * the drawn range holds nothing but live records. The handles are opaque: `allocate` only hands one - * back to `write` and `clear`. - * - * A **main** batch is `clustered`, and that changes the packing: its blocks are laid out grouped by - * the world-grid square they sit in, the same square the caster clusters are keyed by, and - * {@link SharedBatch.cullFrom} narrows `mesh.count` to the squares the main camera's frustum covers. - * Three's whole-mesh frustum test cannot do this on its own — the bounds are the whole ring, so the - * test always passes and every instance behind or beside the camera is vertex-shaded. See - * {@link WorldCells.#cullMainPass} for which camera is allowed to decide. - */ -/** - * A `SharedBatch`'s mesh plus the one field it publishes for a shadow level's size gate: the largest - * instance scale any live record carries. Duck-typed rather than augmented into `three`, the same as - * `isInstancedMesh` — `render/virtual-shadow.ts` reads it back off the mesh. + * Pixels one world unit covers at unit depth, from the vertical field of view and the raster height. + * The scale `ModelLod` divides an error by; see `IWorldCellsLoadOptions.autoLod`. */ -interface ICasterScaleMesh extends InstancedMesh { - casterInstanceScale: number; - /** - * Whether the main pass will draw the records behind this caster half this frame; see - * {@link SharedBatch.setMainAdmitted}. Read by `VirtualShadowNode`'s probe beside the other two - * flags this class writes on a mesh. - */ - mainAdmitted?: boolean; +function autoLodScale(autoLod: IWorldCellsLoadOptions["autoLod"]): number { + const fovY = autoLod?.fovY ?? DEFAULT_AUTO_LOD_FOV_Y; + const viewportHeight = autoLod?.viewportHeight ?? DEFAULT_AUTO_LOD_VIEWPORT_HEIGHT; + if (!Number.isFinite(fovY) || fovY <= 0 || fovY >= 180) + throw new Error("WorldCells autoLod.fovY must be a finite angle in (0, 180) degrees."); + if (!Number.isFinite(viewportHeight) || !(viewportHeight > 0)) + throw new Error( + "WorldCells autoLod.viewportHeight must be a positive number of raster pixels.", + ); + return viewportHeight / (2 * Math.tan((fovY * Math.PI) / 360)); +} + +/** The screen-space error budget a synthesized level is switched at, in pixels. */ +function autoLodPixelError(autoLod: IWorldCellsLoadOptions["autoLod"]): number { + const maxPixelError = autoLod?.maxPixelError ?? DEFAULT_AUTO_LOD_MAX_PIXEL_ERROR; + if (!Number.isFinite(maxPixelError) || maxPixelError <= 0) + throw new Error("WorldCells autoLod.maxPixelError must be a positive number of pixels."); + return maxPixelError; } -/** One world-grid square's records in a clustered batch: how many of them there are. */ -type ISquareSizes = Map; +/** The adaptive LOD bias floor. A bias is only ever allowed to coarsen selection, never refine it. */ +const LOD_BIAS_MIN = 1; +/** The adaptive LOD bias ceiling, so a runaway maps to a coarser but still bounded world. */ +const LOD_BIAS_MAX = 2.5; +/** How much the bias rises per over-budget step. */ +const LOD_BIAS_RISE = 1.08; +/** How much the bias decays per comfortably-under-budget step. */ +const LOD_BIAS_DECAY = 0.95; +/** Seconds between rise or decay steps; a change is never larger than one step. */ +const LOD_BIAS_STEP_SECONDS = 0.5; +/** Below this share of the affordable budget the bias decays; between it and the budget it holds. */ +const LOD_BIAS_DECAY_SHARE = 0.6; +/** Default fraction of a 60 fps frame the main pass may take before the bias rises. */ +const DEFAULT_MAIN_GPU_SHARE = 0.5; +/** Seconds between two `TN_LOD_BIAS` lines. */ +const LOD_BIAS_MARKER_SECONDS = 1; +/** The line the adaptive loop prints when it moves the bias. */ +export const LOD_BIAS_MARKER = "TN_LOD_BIAS"; /** - * The one buffer a clustered regroup permutes through. - * - * A regroup is a permutation of `[0, drawn)`, and there is no in-place order for one: putting the - * visible squares at the front necessarily writes over squares that have not been copied out yet. A - * snapshot is the honest way to do it — read every block into here, then write the new layout back — - * and one buffer is enough for the whole world, because a regroup never nests and the largest - * main batch is the only bound on it. Module-level like the upload epoch, because there is one world - * drawing at a time. + * One launch override, read once: the environment, then the query string, then the global a test + * sets. The same three ways a debug flag is asked, but the answer is kept as text so a number can + * be parsed by the caller rather than rounded here. */ -let regroupScratch = new Float32Array(0); - -/** A scratch with room for `floats`, kept between regroups so a walk allocates it once. */ -function scratchFor(floats: number): Float32Array { - if (regroupScratch.length < floats) regroupScratch = new Float32Array(floats * 2); - return regroupScratch; +function launchValue(parameter: string, flag: string, globalName: string): string | undefined { + const host = globalThis as { process?: { env?: Record } } & Record< + string, + unknown + >; + const env = host.process?.env?.[flag]; + if (typeof env === "string" && env !== "") return env; + const query = globalThis.location?.search; + if (typeof query === "string") { + const value = new URLSearchParams(query).get(parameter); + if (value !== null) return value; + } + const global = host[globalName]; + if (typeof global === "number") return String(global); + return typeof global === "string" ? global : undefined; } -/** The two scratch objects a cull builds its frustum in, and whether two square sets are the same. */ -const _cullFrustum = new Frustum(); -const _cullProjScreen = new Matrix4(); -/** The eye the live-state reference measures every placement's XZ distance from. */ -const _gpuEye = new Vector3(); +/** + * Whether a launch opts into the whole-asset impostors: off unless `?tnImpostors=1`, `TN_IMPOSTORS=1` + * or `__tnImpostors = 1` says so. Opt-in because the judged Machinefall capture of the default + * (PRD-473 AC-3, 2026-10-14) read REGRESSION — ground shadows and canopy density differ — and its + * pinned frame windows showed no frame-time gain, so a game gets the source levels it had until the + * proof passes. `impostors: true` still asks explicitly. + */ +function impostorsRequested(): boolean { + const raw = launchValue("tnImpostors", "TN_IMPOSTORS", "__tnImpostors"); + return raw === "1" || raw === "true"; +} -/** Records a square's blocks hold, which is its share of the batch. */ -function sizeOf(square: { blocks: Array<[number, number, number, string]> }): number { - let size = 0; - for (const block of square.blocks) size += block[2]; - return size; +/** Whether the adaptive LOD bias runs: on unless a launch pins it off. */ +function adaptiveLodRequested(): boolean { + const raw = launchValue("tnAdaptiveLod", "TN_ADAPTIVE_LOD", "__tnAdaptiveLod"); + return raw === undefined ? true : raw !== "0" && raw !== "false"; } -function sameSquares(next: Set, was: Set | undefined): boolean { - if (was === undefined || was.size !== next.size) return false; - for (const cluster of next) if (!was.has(cluster)) return false; - return true; +/** The main-pass GPU share: the option, then the launch override, then 0.5. */ +function mainGpuShare(option: number | undefined): number { + const raw = launchValue("tnMainGpuShare", "TN_MAIN_GPU_SHARE", "__tnMainGpuShare"); + const launched = raw === undefined ? undefined : Number(raw); + const share = option ?? launched ?? DEFAULT_MAIN_GPU_SHARE; + if (!Number.isFinite(share) || share <= 0 || share > 1) + throw new Error("WorldCells mainGpuShare must be a number in (0, 1]."); + return share; } -class SharedBatch { - mesh: ICasterScaleMesh; - /** The pass this batch draws in; see `#dressMesh`. Only `main` is clustered. */ - readonly role: "cluster" | "key" | "main" | "wide"; +/** + * Stream a Blender-authored world package by cell and keep it resident around a followed point. + * + * The class composes `TerrainTiles` for the package's heightmap, builds one `InstancedBatch` per + * resident cell asset run, distance level and mesh part, and loads hand-placed chunk GLBs through + * `loadAll` + `addInSlices`. Ring residency, CPU per-asset `maxDistance` filtering, GPU dispatch + * culling, the per-asset `lods` levels, hard budgets and generation-tokened cancellation all live + * here; every geometry, material and surface still comes from the package's GLBs and the game. + * + * An asset is drawn per part, not per model: a GLB with several primitives is one `InstancedBatch` + * each, and a scattered part whose own material is transparent draws as an alpha cutout unless the + * game asks for blending, because an `InstancedMesh` cannot sort its instances. + * + * An asset with no nonempty authored `lods` can draw its own model's baked AutoLOD chain at distances + * measured over the `autoLod` viewport. An explicit empty list enrolls only unchanged static parts; + * section ranges, custom attributes, alpha foliage and vertex deformation retain source geometry. + * + * @situation stream a large Blender-authored world by cell instead of one huge GLB + * @situation keep scattered props and hand-placed chunks resident around a moving player + * @situation honour per-asset draw distances and hard streaming budgets without a mid-frame throw + * @constraint surface is the game's; this class creates no material, colour or geometry + * @constraint budgets are hard caps that report pressure instead of over-committing + * @constraint `whenSpawnReady(position, radius, timeoutMs)` snapshots a fixed region and settles from existing updates or disposal; it does not drive streaming, and `prewarmed` remains the separate draw gate + * @constraint model loads are bounded by `concurrency` (default 12) across every resident cell, not per cell + * @constraint refilters are bounded by `rebuildsPerUpdate` (default 16) per update, nearest cell first + * @constraint admission is bounded by `admissionBudgetMs` (default 2) per update across every path, plus at most one unit each for terrain and props; while props are queued terrain takes at most half, so neither starves the other, and a deferred cell keeps drawing what it has + * @constraint SkinnedMesh parts are skipped; an instanced copy would draw one rest pose + * @constraint a baked chain's switch distances are measured against `autoLod` (default 4 px of error over 60° and 1080 raster rows), because an instanced draw cannot select a level per instance; an asset with nonempty authored `lods` never consults it; explicit empty lists enroll only unchanged static geometry/material parts + * @constraint eligible static sources can use cooked HLOD (chunk-only alongside GPU scatter, whole-cell on CPU) after compilation and an observed main-context backend submission; projected-error hysteresis requires actual source triangle reduction and no extra material draws, unsupported or shared source stays resident, and collision/shadow paths retain source representations; stats().hlod counts lifecycle and charged resources, not GPU timing or physical allocation + * @override `hlod: false` keeps source rendering; ring, budgets, terrain tile size/resolution, terrain stream and collider radius, `transparentScatter`, `clusterSize` and `shadows.invalidate`, load `concurrency`, `rebuildsPerUpdate`, `admissionBudgetMs` and the package's per-asset maxDistance + * @constraint `prewarmed` resolves once every prewarmed shared batch has been drawn; a game with a loading screen waits on it, and `stats().pendingPrewarm` is the same gate as a number + * @constraint every `asset:level:part` is one InstancedMesh for the main pass, plus one caster InstancedMesh per world-grid square of `clusterSize` on the shadow caster layer, so the main pass draws one mesh per key and a shadow level submits only the squares it covers + * @constraint two definitions the asset loader resolves to one model — the same cooked `glb`, the same `lods` at the same distances, the same `maxDistance` and bounds — are one asset under the lexicographically smallest id: one model load, one set of `asset:level:part` keys, one prewarm and one refcount, released when the last cell holding any member of the group leaves the ring; `TN_WORLD_ASSET_ALIAS` reports how many of the package's assets are really distinct + * @constraint the main pass mesh draws only the squares the render camera's frustum covers — on by default, narrowed once per frame for every main batch by the engine's render-cadence dispatch, never for an orthographic camera — a batch with nothing to draw is hidden rather than submitted at `count 0`, and `TN_WORLD_MAIN_CULL` reports both every five seconds + * @constraint a loaded chunk is merged by material before it is added, so it submits one draw per material rather than one per node; a skinned, multi-material or morph-target mesh, one carrying a baked AutoLOD chain, and an instanced mesh past `chunkMergeMaxTriangles` (default 43,690 triangles) or with a shape over 2,048 triangles, all keep their own geometry; a material group crossing 131,072 vertices (4 MiB of position + normal + uv) is split into several meshes in traversal order instead of one giant upload, indexed parts keep their index, and `TN_WORLD_CHUNK_MERGE` reports what the merge did and the bytes it left + * @constraint `shadows.castDistance` is accepted and ignored (clusters replaced it); `shadows.invalidate` is called at most once a second after streamed records changed + * @example + * const world = await WorldCells.load({ url: "/world/world.json", surface, follow, ring: 1, budgets: { residentCells: 25, instances: 20000, bytes: 8000000 } }); + * scene.add(world); + * world.update(); + */ +export class WorldCells extends Group implements IComputeDriven { + readonly processCadence = "render" as const; + readonly processDuringStartup = true; + readonly #budgets: IWorldCellsBudget; + readonly #cells: readonly IWorldCell[]; + readonly #cellSize: number; + readonly #follow: IWorldCellsFollow; + readonly #manifest: IWorldPackage; + readonly #minX: number; + readonly #minZ: number; + readonly #placements: ArrayBuffer; + readonly #rebuildsPerUpdate: number; + readonly #prefetchSeconds: number; + readonly #freshMeshesPerUpdate: number; + readonly #coveredFreshMeshesPerUpdate: number; /** - * Set by `WorldCells` from the asset's own bounds: a wide caster of ground cover goes on the - * finest level's small-caster layer rather than the wide one. Written after construction because - * `#segmentIn` re-dresses a grown mesh with nothing but the batch to hand, and a flag the batch - * carries is the one thing that survives that. + * Pixels per world unit at unit depth, the scale a baked chain's level errors are divided by to + * reach a switch distance. See `IWorldCellsLoadOptions.autoLod` and {@link chainLevels}. */ - smallCaster = false; + readonly #autoLodPixelsPerUnit: number; + /** The screen-space error budget a synthesized level is switched at; see `autoLod`. */ + readonly #autoLodMaxPixelError: number; + /** Smoothed follow velocity (m/s) and the sample it was last updated from. */ + #velocityX = 0; + #velocityZ = 0; + #lastSample: { readonly x: number; readonly z: number; readonly t: number } | undefined; + readonly #ring: number; + readonly #terrain: TerrainTiles | undefined; + readonly #hasColliders: boolean; + readonly #lodHysteresis: number; + readonly #placementLevels: Uint32Array | undefined; + readonly #placementReach: Float32Array | undefined; + readonly #reachRanges: Map< + IWorldCell, + Map + >; + readonly #transparentScatter: "cutout" | "blend"; + readonly #baseUrl: string; + readonly #logicalBase: string; /** - * Set when the GPU scene owns this batch's draw, with the key it was minted under. See - * `WorldCells#dressGpu`. - * - * It is a field rather than a name test because a dressed batch's `mesh.instanceMatrix` is the - * scene's shared compaction buffer, and everything below writes records into that attribute: a - * dressed batch keeps its bookkeeping and skips every byte of it, which is the whole of what the - * `gpu` guards in `write`, `clear`, `#settle`, `#touched` and `#publish` are. + * Asset ids the package named twice for one model, each pointing at the id kept. Every lookup of + * an id by a run's `asset` goes through {@link #canonical}, so a duplicate's placements are built, + * keyed, prewarmed, refcounted and released as the one asset they really are. Empty when the + * package has no duplicates, and then the lookup is the identity. */ - gpu: - | { - readonly asset: string; - readonly level: number; - readonly part: number; - readonly key: string; - } - | undefined; + readonly #aliases: ReadonlyMap; + readonly #resident = new Map(); /** - * The instance count a GPU-dressed mesh is submitted with: its key's region capacity, written by - * `WorldCells#dressGpu`. - * - * A dressed mesh's own records are not what it draws — the dispatch appends survivors into the - * scene's shared buffer and the indirect record decides how many — so `#drawn` is not its count. - * Three skips the indirect draw entirely before the GPU ever reads the record when an - * `InstancedMesh.count` is zero (`RenderObject.getDrawParameters`), so the region's capacity is the - * submission bound: the record's own count draws what the GPU selected, and this only admits the - * submission. `0` until the mesh is dressed. + * Games waiting on `whenSpawnReady`, settled by the update that follows admissions or by dispose. + * One set, no listener registry and no polling: the update that already walks residency observes + * these, so a world nobody is waiting on scans nothing. See {@link whenSpawnReady}. */ - gpuCount = 0; + readonly #spawnWaiters = new Set(); + readonly #extentBounds: Box3; + /** The largest run of each asset in any cell: the segment size every cell of it fits. */ + readonly #runMax = new Map(); + /** How many of an asset's finest levels cast shadows; 0 when the game asked for none. */ + readonly #castShadowLevels: number; + readonly #receiveShadow: boolean; + /** Asset bounds height under which a wide caster goes on the finest level's layer only. */ + readonly #smallCasterMetres: number; + /** The triangle cap a chunk's instanced meshes are expanded under; see the load option. */ + readonly #chunkMergeMaxTriangles: number; /** - * How many of this batch's live records the GPU scene holds a placement for, carried beside the - * buffer and the segments so admission is one comparison per caster half a frame. Incremented by - * `WorldCells#placeSources` and decremented by `#clearSegment`, where the asset's own `#gpuResident` - * count already moves; see {@link WorldCells.#publishCasterAdmission}. + * Side of a world-grid square, and how many cells fit in one. One `SharedBatch` per + * `asset:level:part` per square, so the mesh's bounds are that square's records and every camera + * culls at square granularity. A cell's placements never move, so a record's square is the one + * its own cell sits in and nothing is ever re-clustered. */ - gpuPlaced = 0; + readonly #clusterSize: number; + readonly #cellsPerCluster: number; + /** World cells per main-cull visibility cell; see `MAIN_CULL_SQUARE_METRES`. */ + readonly #cellsPerCullCell: number; + /** The follow point the last full residency pass ran for; see `#residencyStale`. */ + readonly #residencyPoint = new Vector3(Number.NaN, 0, Number.NaN); + /** The follow point's cell at the last residency pass; a move of more than one is a jump. */ + #residencyCell: { readonly x: number; readonly z: number } | undefined; + /** The follow point the last refilter pass ran for; see `#refilterStale`. */ + readonly #refilterPoint = new Vector3(Number.NaN, 0, Number.NaN); + /** Bumped by every admission and eviction, so a residency change owes a refilter that update. */ + #residencyEpoch = 0; + /** The residency epoch the last refilter pass saw; see `#refilterStale`. */ + #refilterEpoch = -1; + /** The last pass found more stale cell-assets than `rebuildsPerUpdate` could queue. */ + #refilterOwed = false; + /** The game's hook to refresh the shadow levels after streamed records changed. */ + readonly #invalidateShadows: ((region?: IShadowRegion) => void) | undefined; + /** Cell-asset builds waiting for budget, in admission order: nearest cell first. */ + #jobs: IBuildJob[] = []; + /** The (cell, run) pairs already queued, so a refilter cannot queue itself twice. */ + readonly #queued = new Set(); + readonly #position = new Vector3(); + readonly #rotation = new Quaternion(); + readonly #scale = new Vector3(); + readonly #matrix = new Matrix4(); + readonly #recordBounds = new Box3(); + readonly #instance = new Matrix4(); + /** The authored bounds' centre, placed: the source sphere's centre. See `#addPlacements`. */ + readonly #sourceCentre = new Vector3(); + readonly #pressure = { cells: 0, instances: 0, bytes: 0 }; + readonly #budgetMs: number; + readonly #coveredBudgetMs: number; + /** Set for the length of one `process` call that runs behind the startup cover. */ + #covered = false; + readonly #now: () => number; + #admission = { spentMs: 0, deferred: 0, backlog: 0 }; + #instances = 0; + #bytes = 0; + #evictions = 0; + #failures = 0; + #rebuilds = 0; + /** Refilter passes run, whether or not they queued anything; see `IWorldCellsStats.refilters`. */ + #refilters = 0; + /** Replacement builds that entered #addPlacements; initial admission is not CPU refilter work. */ + #levelRefilters = 0; + /** Refilters whose rebuild came back identical and were settled without a write. */ + #unchanged = 0; + /** Every distinct gate distance the world's assets have, ascending; see `#noteGate`. */ + readonly #gates: number[] = []; + /** The reused hit list and the reused per-cell seen set; see `#staleIn`. */ + readonly #stale: Array<{ + cell: IResidentCell; + distance: number; + id: string; + }> = []; + readonly #staleSeen = new Set(); + /** Stale cell-assets the passes found, cumulative; see `IWorldCellsStats.refilterEntries`. */ + #staleEntries = 0; + #generation = 0; + #released = false; /** - * Whether this batch's mesh is parented under one of the world's main record shards. Set by - * `WorldCells#bundleIn` and cleared by `#bundleOut`. + * How the shadow cascade reaches this world's GPU keys: a level render hands its own four numbers + * here and gets its set selected into the shadow twins, which the shadow key meshes on + * `VIRTUAL_SHADOW_KEY_LAYER` then draw. Published on this object — the world's own root, which is + * what `VirtualShadowNode` walks — and duck-typed there, because the world chunk and the shadow + * node are separate and neither names the other's package types. * - * It is a field rather than `mesh.parent` because a bundled mesh's `visible` is never written - * again: three fixes a bundle's render list when it records it, and the replay never re-reads - * visibility — so the two places that hide an empty batch have to know, and a mesh that has just - * been detached from the group by a retire has still been in the last recorded one. - */ - bundled = false; - /** - * Last checked eligibility of this mesh. CPU draws revalidate bounded material graph snapshots - * and live object hooks each frame, so deep mutations can return the mesh to the per-object path. - */ - bundlable = false; - bundleMaterialRevision = -1; - /** - * The instance count the world's last record of this mesh was cut at. A record freezes the draw's - * instance count, so a CPU-path batch's moving window is one re-record when this moves; a dressed - * batch submits its key's region capacity, which only the dress changes and which re-records there. - */ - bundledCount = -1; - /** - * The same descriptor, recorded whether or not the GPU scene is on, which is the whole of what - * lets a mesh minted before the scene came up be dressed after it: `gpu` says the mesh draws from - * the scene's buffers, `main` says which key it would draw. A prewarm that mints a whole ring on a - * loading screen mints it with the scene off, and the first frame with a renderer turns it on. - */ - main: - | { - readonly asset: string; - readonly level: number; - readonly part: number; - readonly key: string; - } - | undefined; - readonly #clustered: boolean; - readonly #segmentSize: number; - /** Block handle -> the block's first instance record. */ - readonly #start = new Map(); - /** Block handle -> live records in it; the reserved count until `write` reports the real one. */ - readonly #size = new Map(); - /** Block handle -> the world-grid square it sits in; only a clustered batch records it. */ - readonly #blockCluster = new Map(); - /** World-grid square -> its records in the buffer, so the window can be summed. See `#regroup`. */ - readonly #squareSizes: ISquareSizes = new Map(); - /** The squares the last cull kept, how many records of the batch they are, and whether that is current. */ - #visible: Set | undefined; - /** The `visibleEpoch` this batch last laid itself out at; see {@link SharedBatch.cullFrom}. */ - #packedEpoch = -1; - /** Reused across culls, so a repack allocates no set. */ - #scratch: Set = new Set(); - #window = 0; - #narrowed = false; - /** - * A prewarmed batch whose first draw is still owed, so it is visible at count 0 until that draw - * counts. The draw is the whole of the prewarm; see {@link #publish}. - */ - #awaitingPrewarm = false; - /** - * Whether the main pass will draw this batch's records this frame; a main batch is always - * admitted, so this only ever says something about a caster half. See - * {@link WorldCells.#publishCasterAdmission}. - */ - #mainAdmitted = true; - /** Free record ranges `[from, to)`, sorted, disjoint and never adjacent. */ - #free: Array<[number, number]> = []; - #handles = 0; - /** One past the last live record: the range the whole buffer holds. A clustered batch draws `#window`. */ - #drawn = 0; - /** Block handle -> the AABB of the records it holds, and the union of those is the mesh's bounds. */ - readonly #boxes = new Map(); - /** Block handle -> the largest instance scale in it; the union is published on the mesh. */ - readonly #scales = new Map(); - /** Carried out of the last `#boxOf` pass, which already reads every record's basis. */ - #maxScale = 0; - #localGeometry: BufferGeometry | undefined; - readonly #localCenter = new Vector3(); - readonly #localHalf = new Vector3(); - readonly #union = new Box3(); - readonly #point = new Vector3(); - /** This batch's own pending upload span, and the `update` it was opened in. See `#touched`. */ - #pendingLow = 0; - #pendingHigh = 0; - #pendingEpoch = -1; - - constructor( - geometry: BufferGeometry, - material: Material, - segmentSize: number, - segments: number, - name: string, - bounds: Box3, - role: "cluster" | "key" | "main" | "wide", - ) { - this.role = role; - this.#clustered = role === "main"; - this.#segmentSize = Math.max(1, segmentSize); - this.#bounds = bounds; - this.mesh = SharedBatch.#meshFor( - geometry, - material, - this.#segmentSize * segments, - name, - bounds, - ); - this.#free = [[0, this.mesh.instanceMatrix.count]]; - this.#ceiling = this.mesh.instanceMatrix.count; - this.#rebound(); - } - - /** This batch's own record ceiling; see {@link liveCeiling}. */ - #ceiling = 0; - - /** The package extent, the conservative fallback when a part's own box cannot be read. */ - readonly #bounds: Box3; - - /** - * Takes a pooled mesh with room for `capacity`, so a shared batch that outgrows its buffer keeps - * the uuid — and the node three built for it — instead of getting a new one. Every field a pooled - * mesh carries stale is written here, and the two shadow flags are set false because `grow` copies - * them off the mesh it replaces and the world sets them per level. - */ - static #meshFor( - geometry: BufferGeometry, - material: Material, - capacity: number, - name: string, - bounds: Box3, - ): ICasterScaleMesh { - const mesh = pooledMesh(geometry, material, capacity) as ICasterScaleMesh; - mesh.name = name; - mesh.boundingBox = bounds.clone(); - mesh.boundingSphere = bounds.getBoundingSphere(mesh.boundingSphere ?? new Sphere()); - mesh.count = 0; - mesh.visible = false; - // Cullable, because the bounds are now this batch's own. Every camera three draws this mesh - // with — the main one and each virtual-shadow level's — asks first, which is what stops a 48 m - // shadow level from submitting every shared batch in the world. - mesh.frustumCulled = true; - mesh.castShadow = false; - mesh.receiveShadow = false; - (mesh.instanceMatrix.array as Float32Array).fill(0); - mesh.instanceMatrix.needsUpdate = true; - // A pooled mesh arrives carrying the last user's scale; nothing lives here yet. - mesh.casterInstanceScale = 0; - // Nor the last user's bundle membership: a mesh is only bundled while `WorldCells#bundleIn` says - // so, and the shadow node reads this marker to keep its texel gate off a bundled mesh. - mesh.userData.tnBundled = false; - // Nor the last caster's admission: a main batch is always admitted, and this mesh is not one. - mesh.mainAdmitted = true; - return mesh; - } - - /** Point an empty batch at the parts a reload brought; see {@link rebind}. */ - rebind(geometry: BufferGeometry, material: Material): this { - this.mesh.geometry = geometry; - this.mesh.material = material; - this.#localGeometry = undefined; - // A new surface is the one thing a rebind can bring that a mesh swap cannot, so what can be - // recorded has to be asked again: the answer cached for the old material is not this one's. - this.bundlable = false; - if (this.#parked > 1) this.#resize(this.#parked); - this.#rebound(); - return this; - } - - /** The capacity this batch had when it was parked; see {@link park}. */ - #parked = 0; - - /** - * Give up the instance buffer of a batch that holds no records, keeping the mesh. - * - * This is what makes a retained set affordable. Unparked, a walk's back-traffic costs a ring-sized - * buffer per key, so `RETIRED_SHARED_BYTES` could hold only the last few dozen keys of a world with - * hundreds of them, `#sharedFor` then found nothing to hand back, and the walk minted a second - * mesh for every key it returned to. Parked, the same keys cost `PARKED_SHARED_CAPACITY` records - * each and `rebind` gives the buffer back before the first record is written into it. + * Published only when `?tnShadowGpuKeys=1` asked for it, which is what makes the node's own + * keyed path and this world's twin meshes one switch: with nothing published a level reads no + * keys, mints no twin and submits nothing, and every picture is the one it drew before. */ - park(): this { - // The batch's own ceiling, never its mesh's `count`: a dressed batch's `instanceMatrix` is the - // GPU scene's shared compaction buffer, so its count is the whole world's, and parking by it - // turned one key's capacity into the whole buffer — `rebind` then resized every later batch - // against the world and the shared buffer regrew until the device refused it. - this.#parked = this.#ceiling; - // `gpu` too, not only a capacity over one: a dressed batch's `instanceMatrix` is the GPU scene's - // shared compaction buffer whatever its logical ceiling is, so a batch parked at a ceiling of one - // that skipped this resized nothing and left the retired mesh holding the whole world's records. - if (this.gpu !== undefined || this.#parked > PARKED_SHARED_CAPACITY) - this.#resize(PARKED_SHARED_CAPACITY); - return this; - } - + readonly tnShadowGpuKeys: ((renderer: IRendererLike, level: IShadowLevel) => void) | undefined = + shadowGpuKeysRequested() + ? (renderer: IRendererLike, level: IShadowLevel): void => { + this.#render.dispatchShadow(renderer, level); + } + : undefined; + readonly #gpuWanted: boolean; + /** `gpuSceneValidate` as the load asked for it, before the query string and the environment. */ + readonly #gpuValidate: boolean | undefined; /** - * Swap in an instance buffer of `capacity` records, and with it the free range and the drawn count - * the buffer is the record of. The mesh is off the world and holds no records when this runs, so - * `#start`, `#size` and `#boxes` are already empty and the one free range is the whole buffer. + * Whether the GPU scene samples what it actually draws, so `TN_FRAME_BUDGET` and + * `stats().gpuScene` can report a GPU-selected count. A validation turns it on too; the engine + * also turns it on when the frame budget is, so a game pays nothing when neither is. */ - #resize(capacity: number): void { - const mesh = this.mesh; - mesh.instanceMatrix = new InstancedBufferAttribute(new Float32Array(capacity * 16), 16); - this.#ceiling = capacity; - this.#publish(0); - this.#free = [[0, capacity]]; - this.#drawn = 0; - } - + #gpuTally = false; + readonly #occlusion: OcclusionMode; + /** `bundles` as the load asked for it, before the query string and the environment. */ + readonly #bundlesWanted: boolean; /** - * The mesh's own extent, the union of the blocks' boxes, written on every add and remove. A union - * over at most one box per resident cell is a few dozen comparisons, against a per-draw saving of - * every batch the camera does not overlap. `#compact` moves records between slots but never between - * blocks, so a block's box survives a compaction untouched. + * Who decided that: the load that named `bundles`, the launch that asked for `off` or `on`, or + * nothing at all — which is what the marker reports so a run on the default is never mistaken for + * one a flag turned off. See {@link bundlesAsked}. */ - #rebound(): void { - const box = this.#union; - let live = false; - for (const one of this.#boxes.values()) { - if (live === false) { - box.copy(one); - live = true; - } else box.union(one); - } - if (live === false) box.copy(this.#bounds); - (this.mesh.boundingBox as Box3).copy(box); - this.mesh.boundingSphere = this.mesh.boundingSphere ?? new Sphere(); - box.getBoundingSphere(this.mesh.boundingSphere); - // The size a per-instance shadow gate reads, published next to the mesh rather than asked for - // again per level: the cluster's own sphere is ~24 m of grid square, so gating on it dropped - // nothing, while a fern is 0.3 m and every fern cluster clears every coarse level. - this.mesh.casterInstanceScale = live === false ? 0 : Math.max(...this.#scales.values()); - } + readonly #bundleReason: string; + /** `adaptiveLod` resolved against the launch override; see the option. */ + readonly #adaptiveLod: boolean; + readonly #preserveAuthoredParts: boolean; + /** The main pass's allowed GPU share of a frame; see the option. */ + readonly #mainGpuShare: number; + /** The distance multiplier both selection paths are using right now. */ + #lodBias = LOD_BIAS_MIN; + /** The clock the step cadence and the first marker line are measured from. */ + readonly #lodBiasStartedAt: number; + /** The last time a rise or decay step ran. */ + #lodBiasStepAt: number; + /** The last time the `TN_LOD_BIAS` line printed. */ + #lodBiasToldAt: number; - /** - * The AABB of records `[from, from + count)` of the instance buffer, read from the bytes just - * written rather than from the batch that wrote them, so it is the same numbers the GPU gets. The - * half-extents are `|basis| * half`, which is the box a rotated or stretched instance of the part - * actually covers — a translate-only box would let a scaled trunk through the shadow cull. - */ - #boxOf(from: number, count: number): Box3 { - const array = this.mesh.instanceMatrix.array as Float32Array; - const geometry = this.mesh.geometry; - if (this.#localGeometry !== geometry) { - this.#localGeometry = geometry; - if (geometry?.boundingBox === null || geometry?.boundingBox === undefined) - geometry?.computeBoundingBox?.(); - const local = geometry?.boundingBox; - // No readable local box means no readable instances either; the package extent is the answer - // that cannot be wrong, and it is what these meshes carried before the blocks were sized. - if (local === undefined || local === null || local.isEmpty()) return this.#bounds; - this.#localCenter.copy(local.getCenter(this.#point)); - this.#localHalf.copy(local.getSize(this.#point)).multiplyScalar(0.5); - } - const cx = this.#localCenter.x; - const cy = this.#localCenter.y; - const cz = this.#localCenter.z; - const hx = this.#localHalf.x; - const hy = this.#localHalf.y; - const hz = this.#localHalf.z; - const out = new Box3(); - let minX = Number.POSITIVE_INFINITY; - let minY = Number.POSITIVE_INFINITY; - let minZ = Number.POSITIVE_INFINITY; - let maxX = Number.NEGATIVE_INFINITY; - let maxY = Number.NEGATIVE_INFINITY; - let maxZ = Number.NEGATIVE_INFINITY; - let scale = 0; - for (let index = from; index < from + count; index += 1) { - const at = index * 16; - const m0 = array[at] as number; - const m1 = array[at + 1] as number; - const m2 = array[at + 2] as number; - const m4 = array[at + 4] as number; - const m5 = array[at + 5] as number; - const m6 = array[at + 6] as number; - const m8 = array[at + 8] as number; - const m9 = array[at + 9] as number; - const m10 = array[at + 10] as number; - const px = m0 * cx + m4 * cy + m8 * cz + (array[at + 12] as number); - const py = m1 * cx + m5 * cy + m9 * cz + (array[at + 13] as number); - const pz = m2 * cx + m6 * cy + m10 * cz + (array[at + 14] as number); - const ex = Math.abs(m0) * hx + Math.abs(m4) * hy + Math.abs(m8) * hz; - const ey = Math.abs(m1) * hx + Math.abs(m5) * hy + Math.abs(m9) * hz; - const ez = Math.abs(m2) * hx + Math.abs(m6) * hy + Math.abs(m10) * hz; - if (px - ex < minX) minX = px - ex; - if (py - ey < minY) minY = py - ey; - if (pz - ez < minZ) minZ = pz - ez; - if (px + ex > maxX) maxX = px + ex; - if (py + ey > maxY) maxY = py + ey; - if (pz + ez > maxZ) maxZ = pz + ez; - // The largest column of the instance basis: the scale a fern or a trunk is drawn at. - const basis = Math.max( - Math.hypot(m0, m1, m2), - Math.hypot(m4, m5, m6), - Math.hypot(m8, m9, m10), - ); - if (basis > scale) scale = basis; - } - this.#maxScale = scale; - out.min.set(minX, minY, minZ); - out.max.set(maxX, maxY, maxZ); - return out; - } + readonly #render: CellRender; + readonly #prewarm: CellPrewarm; + readonly #chunks: CellChunks; + readonly #impostorState: CellImpostors; + readonly #models: CellModels; - /** A handle for `count` free records, or `undefined` when the buffer has none and must grow. */ - allocate(count: number, cluster?: string): number | undefined { - const at = this.#take(count); - if (at < 0) return undefined; - const handle = this.#handles; - this.#handles += 1; - this.#start.set(handle, at); - this.#size.set(handle, count); - // Only a clustered batch groups, so only it pays for the lookup. The square is the caller's - // cell's, and a cell's placements never move, so a record is only ever grouped when the cell - // holding it is admitted or evicted. - if (this.#clustered && cluster !== undefined) this.#blockCluster.set(handle, cluster); - return handle; - } + private constructor(init: IWorldCellsInit) { + super(); + this.#prewarm = new CellPrewarm({ + released: () => this.#released, + parent: () => this.parent, + invalidateShadows: () => this.#invalidateShadows?.(), + backlog: () => this.#backlog(), + keysForShadow: () => this.#render.keysForShadow(), + hasShared: (key) => this.#render.hasShared(key), + hasRetired: (key) => this.#render.hasRetired(key), + runMax: (asset) => this.#runMax.get(asset), + residentCells: () => this.#budgets.residentCells, + extentBounds: () => this.#extentBounds, + smallCaster: (asset) => this.#render.smallCaster(asset), + dressMesh: (shared, receiveShadow) => this.#render.dressMesh(shared, receiveShadow), + receiveShadow: () => this.#receiveShadow, + adoptGpu: (shared, asset, key, level, part) => + this.#render.adoptGpu(shared, asset, key, level, part), + setShared: (key, shared) => this.#render.setShared(key, shared), + attach: (shared) => this.#render.attach(shared), + casts: (level) => this.#render.casts(level), + wideOwed: (asset, part) => this.#render.wideOwed(asset, part), + followCluster: () => this.#render.followCluster(), + wideShape: (asset, level, part, fallback) => + this.#render.wideShape(asset, level, part, fallback), + }); + this.#budgets = { + bytes: positiveInteger(init.budgets.bytes, "budgets.bytes"), + instances: positiveInteger(init.budgets.instances, "budgets.instances"), + residentCells: positiveInteger(init.budgets.residentCells, "budgets.residentCells"), + }; + this.#ring = nonNegativeInteger(init.ring, "ring"); + this.#budgetMs = admissionBudgetMs(init.admissionBudgetMs ?? DEFAULT_ADMISSION_BUDGET_MS); + this.#coveredBudgetMs = + init.coveredAdmissionBudgetMs === undefined + ? this.#budgetMs * COVERED_BUDGET_SCALE + : admissionBudgetMs(init.coveredAdmissionBudgetMs); + this.#now = init.admissionNow ?? ((): number => globalThis.performance?.now() ?? Date.now()); + this.#follow = init.follow; + this.#manifest = init.manifest; + // Before the first `#canonical` read, which is the run loop below. + this.#aliases = init.aliases; + this.#cells = init.manifest.cells; + const candidates = new Set(); + for (const cell of this.#cells) + for (const run of cell.runs) { + // By the canonical id, so one key's buffer is sized for the largest run of *any* member of + // the group and a duplicate's placements never overflow the block their canonical minted. + const id = this.#canonical(run.asset); + this.#runMax.set(id, Math.max(this.#runMax.get(id) ?? 0, run.count)); + // Every species the map places, however far from the follow point it starts: the far half + // has to be able to bake it without the near ring ever bringing it in. + if (run.count > 0) candidates.add(id); + } - /** - * Doubles the buffer in a new mesh (same data), returning the one it replaces for the pool. - * - * The replacement comes from the pool, and what it replaces is offered back to it, so the pair - * costs one new uuid between them instead of one each. This used to push the old mesh onto an - * unbounded `retired` array that nothing ever read and nothing ever freed. - */ - grow(): InstancedMesh | undefined { - const old = this.mesh; - if (this.gpu !== undefined) { - // A dressed batch's records are the GPU scene's, held in a buffer this batch must not copy, - // zero or regroup: its own share is the logical ceiling the next `#dressGpu` sizes the scene's - // region from, so growth is that number and nothing else. The live mesh is kept and nothing is - // returned: `#segmentIn` dresses, attaches and parks only a real replacement, and the next - // `#dressGpu` grows the region from this ceiling before the draw. - const was = this.#ceiling; - this.#ceiling = was * 2; - this.#hole(was, this.#ceiling); - return undefined; - } - const mesh = SharedBatch.#meshFor( - old.geometry, - old.material as Material, - old.instanceMatrix.count * 2, - old.name, - this.#bounds, + this.#cellSize = init.manifest.cellSize; + this.#minX = init.manifest.extent.minX; + const { extent, terrain } = init.manifest; + // Scatter stands on the terrain: its height range, with headroom for tall trees and props. + this.#extentBounds = new Box3( + new Vector3(extent.minX, terrain.heightMin - 16, extent.minZ), + new Vector3(extent.minX + extent.sizeX, terrain.heightMax + 64, extent.minZ + extent.sizeZ), ); - (mesh.instanceMatrix.array as Float32Array).set(old.instanceMatrix.array as Float32Array); - mesh.castShadow = old.castShadow; - mesh.receiveShadow = old.receiveShadow; - this.mesh = mesh; - this.#ceiling = mesh.instanceMatrix.count; - this.#settle(); - // The whole drawn range, because this is a different buffer. `#meshFor` left the new - // attribute's range list empty, and an empty list is how three is told "upload everything" — so - // before the coalescing below, this was the whole buffer by default and the records copied out - // of the old array reached the GPU. A narrow range here would leave the new buffer's first - // records as the zeros it was created with. - this.#touched(0, this.#drawn); - this.#rebound(); - return old; - } - - /** - * Swap in the object this batch draws with, and hand back the one it replaces. - * - * Every reference the batch holds is `this.mesh` read at the moment it is needed, so the swap is - * the field. What does not follow the field is what the batch derives rather than stores — the - * bounds, the instance scale, the count and the visibility published on the object, and the two - * containers a fresh object starts out without them in. Those are republished here from the - * batch's own records, so a replacement draws exactly what the object it replaced drew. - */ - replaceMesh(next: ICasterScaleMesh): ICasterScaleMesh { - const old = this.mesh; - this.mesh = next; - this.mesh.boundingBox = this.mesh.boundingBox ?? new Box3(); - this.mesh.boundingSphere = this.mesh.boundingSphere ?? new Sphere(); - (next as { casterPrewarmOwed?: boolean }).casterPrewarmOwed = this.#awaitingPrewarm; - next.mainAdmitted = this.#mainAdmitted; - this.#rebound(); - this.#publish(this.#drawn); - return old; - } - - write(segment: number, batch: InstancedBatch): void { - const start = this.#start.get(segment); - const reserved = this.#size.get(segment); - if (start === undefined || reserved === undefined) return; - if (this.gpu !== undefined) { - // The records themselves are the GPU scene's, written by its dispatch from the source buffer; - // what the coarse per-cell gate and the counters need is the per-square count and the total. - const cluster = this.#blockCluster.get(segment) ?? ""; - this.#size.set(segment, batch.count); - this.#squareSizes.set(cluster, (this.#squareSizes.get(cluster) ?? 0) + batch.count); - this.#drawn += batch.count; - this.#publish(this.#drawn); - return; - } - const array = this.mesh.instanceMatrix.array as Float32Array; - const written = batch.writeMatrices(array, start); - array.fill(0, (start + written) * 16, (start + reserved) * 16); - if (written < reserved) this.#hole(start + written, start + reserved); - this.#size.set(segment, written); - // The block's extent, read from the records where they are now, because `#compact` is about to - // move them. The box is keyed by handle, so the move cannot falsify it. - if (written > 0) { - this.#boxes.set(segment, this.#boxOf(start, written)); - this.#scales.set(segment, this.#maxScale); - } else { - this.#boxes.delete(segment); - this.#scales.delete(segment); - } - this.#settle(); - this.#touched(start, written); - this.#rebound(); - } - - clear(segment: number): void { - const start = this.#start.get(segment); - if (start === undefined) return; - const size = this.#size.get(segment) ?? 0; - if (this.gpu !== undefined) { - // The mirrored half of `write`: the coarse gate and the live total follow the records that left, - // and no byte of the shared buffer is touched, because none of them were ever in it. - const cluster = this.#blockCluster.get(segment) ?? ""; - this.#squareSizes.set(cluster, Math.max(0, (this.#squareSizes.get(cluster) ?? 0) - size)); - this.#drawn -= size; - } - this.#start.delete(segment); - this.#size.delete(segment); - this.#blockCluster.delete(segment); - if (this.gpu !== undefined) { - // The block's range goes back to the free list, exactly as the CPU path's does below. Without - // this the GPU path's free list only ever shrank: a walk that streamed cells in and out - // exhausted it, `allocate` failed, `grow` doubled the batch's ceiling, and the scene's region - // — and with it the shared buffer — regrew for records that had already left. - this.#hole(start, start + size); - this.#publish(this.#drawn); - return; - } - (this.mesh.instanceMatrix.array as Float32Array).fill(0, start * 16, (start + size) * 16); - this.#hole(start, start + size); - this.#settle(); - // The drawn range, not nothing. `#compact` closed the hole by moving the topmost block down - // into it, so the bytes below `#drawn` changed and an empty range list is read by three as - // "upload the whole buffer" — a ring-sized write to zero a block that is no longer drawn. - this.#touched(0, this.#drawn); - this.#boxes.delete(segment); - this.#scales.delete(segment); - this.#rebound(); - } + this.#minZ = init.manifest.extent.minZ; + this.#placements = init.placements; + this.#lodHysteresis = init.lodHysteresis ?? 0; + this.#placementLevels = + this.#lodHysteresis > 0 + ? new Uint32Array(init.placements.byteLength / PLACEMENT_RECORD_BYTES) + : undefined; + this.#placementReach = init.placementReach; + this.#reachRanges = init.reachRanges; + this.#rebuildsPerUpdate = positiveInteger(init.rebuildsPerUpdate ?? 16, "rebuildsPerUpdate"); + this.#autoLodPixelsPerUnit = autoLodScale(init.autoLod); + this.#autoLodMaxPixelError = autoLodPixelError(init.autoLod); + // The marker is periodic, and a world's first five seconds are its own: the clock starts at the + // world, not before it, so loading does not print a line. + const mainCullToldAt = this.#now(); + this.#prefetchSeconds = init.prefetchSeconds ?? 1.5; + this.#freshMeshesPerUpdate = positiveInteger( + init.freshMeshesPerUpdate ?? 2, + "freshMeshesPerUpdate", + ); + this.#coveredFreshMeshesPerUpdate = positiveInteger( + init.coveredFreshMeshesPerUpdate ?? + Math.min(Number.MAX_SAFE_INTEGER, this.#freshMeshesPerUpdate * COVERED_BUDGET_SCALE), + "coveredFreshMeshesPerUpdate", + ); + if (!(this.#prefetchSeconds >= 0) || !Number.isFinite(this.#prefetchSeconds)) + throw new Error("WorldCells prefetchSeconds must be a finite number >= 0."); + this.#baseUrl = init.baseUrl; + this.#logicalBase = init.logicalBase; + this.#models = new CellModels( + init, + { + lodHysteresis: this.#lodHysteresis, + manifest: this.#manifest, + baseUrl: this.#baseUrl, + logicalBase: this.#logicalBase, + preserveAuthoredParts: init.preserveAuthoredParts ?? false, + autoLodPixelsPerUnit: this.#autoLodPixelsPerUnit, + autoLodMaxPixelError: this.#autoLodMaxPixelError, + transparentScatter: init.transparentScatter ?? "cutout", + }, + { + canonical: (id) => this.#canonical(id), + noteGate: (gate) => this.#noteGate(gate), + queueBuild: (asset, cell, run, force) => this.#queueBuild(asset, cell, run, force), + released: () => this.#released, + failures: () => this.#failures, + setFailures: (count) => { + this.#failures = count; + }, + reportCellFailure: (stage, key, paths, error) => + this.#reportCellFailure(stage, key, paths, error), + drainShared: (assetId) => this.#render.drainShared(assetId), + residentCells: () => this.#resident.values(), + queuePrewarm: (asset) => this.#prewarm.queuePrewarm(asset), + hasFarTemp: (id) => this.#impostorState.hasFarTemp(id), + releaseFarTemp: (id) => this.#impostorState.releaseFarTemp(id), + maybeQueueImpostor: (asset) => this.#impostorState.maybeQueueImpostor(asset), + hasQueuedImpostor: (id) => this.#impostorState.hasQueued(id), + cancelImpostor: (asset) => this.#impostorState.cancelImpostor(asset), + releaseImpostor: (asset) => this.#impostorState.releaseImpostor(asset), + }, + ); + this.#transparentScatter = init.transparentScatter ?? "cutout"; + this.#gpuWanted = init.gpuScene ?? gpuSceneRequested(); + const asked = bundlesAsked(); + this.#bundlesWanted = init.bundles ?? asked !== "off"; + this.#bundleReason = + init.bundles === undefined ? (asked === "default" ? "default" : "launch") : "option"; - /** The first free range with room for `count` records, taken out of the list; -1 when there is none. */ - #take(count: number): number { - for (let index = 0; index < this.#free.length; index += 1) { - const range = this.#free[index] as [number, number]; - const size = range[1] - range[0]; - if (size < count) continue; - if (size === count) this.#free.splice(index, 1); - else this.#free[index] = [range[0] + count, range[1]]; - return range[0]; - } - return -1; - } + this.#adaptiveLod = init.adaptiveLod ?? adaptiveLodRequested(); + this.#preserveAuthoredParts = init.preserveAuthoredParts ?? false; + this.#mainGpuShare = mainGpuShare(init.mainGpuShare); + // The warm-up and the step cadence are the world's own clock, so loading does not count as a + // second of adaptation and a test can drive the loop with an injected `admissionNow`. + this.#lodBiasStartedAt = this.#now(); + this.#lodBiasStepAt = this.#lodBiasStartedAt; + this.#lodBiasToldAt = this.#lodBiasStartedAt - LOD_BIAS_MARKER_SECONDS * 1000; + this.#gpuValidate = init.gpuSceneValidate; + this.#occlusion = init.occlusion ?? occlusionMode(); + this.#gpuTally = init.gpuSceneTally === true; + this.#castShadowLevels = + init.shadows?.cast === true + ? positiveInteger(init.shadows.castLevels ?? 1, "shadows.castLevels") + : 0; + this.#receiveShadow = init.shadows?.receive === true; + this.#smallCasterMetres = positiveMetres( + init.shadows?.smallCasterMetres ?? SMALL_CASTER_METRES, + "shadows.smallCasterMetres", + ); + this.#chunkMergeMaxTriangles = positiveInteger( + init.chunkMergeMaxTriangles ?? CHUNK_MERGE_MAX_TRIANGLES, + "chunkMergeMaxTriangles", + ); + // Validated and dropped. Clusters bound what a shadow level submits now, so a game that set + // this keeps compiling and keeps loading; see the option's TSDoc. + if (init.shadows?.castDistance !== undefined) + positiveInteger(init.shadows.castDistance, "shadows.castDistance"); + this.#clusterSize = + init.clusterSize === undefined + ? this.#cellSize + : positiveInteger(init.clusterSize, "clusterSize"); + // Rounded, so a cluster is a whole number of cells and a cell belongs to exactly one of them. + this.#cellsPerCluster = Math.max(1, Math.round(this.#clusterSize / this.#cellSize)); + // The main cull's own grid, which is a whole number of world cells or nothing: see + // `MAIN_CULL_SQUARE_METRES`. A `cellSize` that is not a whole multiple of 32 m — and every + // `cellSize` larger than it — falls back to one cell, which is the cull this shipped before it + // had a grid of its own. + this.#cellsPerCullCell = + MAIN_CULL_SQUARE_METRES % this.#cellSize === 0 ? MAIN_CULL_SQUARE_METRES / this.#cellSize : 1; + this.#invalidateShadows = init.shadows?.invalidate; + if (this.#transparentScatter !== "cutout" && this.#transparentScatter !== "blend") + throw new Error("WorldCells transparentScatter must be 'cutout' or 'blend'."); + this.#hasColliders = init.terrain !== false && init.createCollider !== undefined; - /** Records `[from, to)` back as free, merged with every range it touches. */ - #hole(from: number, to: number): void { - if (to <= from) return; - const free = this.#free; - free.push([from, to]); - free.sort((a, b) => a[0] - b[0]); - const merged: Array<[number, number]> = []; - for (const range of free) { - const last = merged[merged.length - 1]; - if (last !== undefined && range[0] <= last[1]) { - if (range[1] > last[1]) last[1] = range[1]; - continue; - } - merged.push(range); - } - this.#free = merged; - } + this.#render = new CellRender( + { + mainCullToldAt, + follow: this.#follow, + budgets: this.#budgets, + cellSize: this.#cellSize, + minX: this.#minX, + minZ: this.#minZ, + manifest: this.#manifest, + placements: this.#placements, + coveredFreshMeshesPerUpdate: this.#coveredFreshMeshesPerUpdate, + freshMeshesPerUpdate: this.#freshMeshesPerUpdate, + receiveShadow: this.#receiveShadow, + castShadowLevels: this.#castShadowLevels, + extentBounds: this.#extentBounds, + preserveAuthoredParts: this.#preserveAuthoredParts, + cellsPerCluster: this.#cellsPerCluster, + cellsPerCullCell: this.#cellsPerCullCell, + smallCasterMetres: this.#smallCasterMetres, + bundlesWanted: this.#bundlesWanted, + bundleReason: this.#bundleReason, + invalidateShadows: this.#invalidateShadows, + }, + { + asset: (id) => this.#models.asset(id), + assets: () => this.#models.assets(), + canonical: (id) => this.#canonical(id), + renderer: () => this.#chunks.renderer, + camera: () => this.#chunks.camera, + now: () => this.#now(), + stats: () => this.stats(), + residentCells: () => this.#resident.values(), + runMax: (asset) => this.#runMax.get(asset), + widenFilterRange: (cell, x, z) => this.#widenFilterRange(cell, x, z), + addUnchanged: () => { + this.#unchanged += 1; + }, + disableFar: (run) => this.#impostorState.disableFar(run), + deferWide: (id) => this.#impostorState.deferWide(id), + queuedBuilds: () => this.#jobs.length, + covered: () => this.#covered, + prewarmSettled: () => this.#prewarm.settled, + awaitDraw: (shared) => this.#prewarm.awaitDraw(shared), + carryPrewarm: (old, next) => this.#prewarm.carryPrewarm(old, next), + add: (object) => { + this.add(object); + }, + isParent: (object) => object.parent === this, + isBundleHome: (shared) => shared.mesh.parent === this.#bundleHome(shared), + }, + ); - /** - * Closes every hole below the drawn range, so `mesh.count` is the live total: the topmost block - * moves into the lowest hole, or, when it does not fit, everything above the hole shifts down over - * it. Whatever sits past the topmost block is free again, so one cell's room serves the next cell - * instead of a `grow`, and the free list is left as the single tail range `[drawn, capacity)`. - */ - #compact(): void { - const array = this.mesh.instanceMatrix.array as Float32Array; - for (;;) { - let drawn = 0; - let top = -1; - let topFrom = 0; - for (const [handle, start] of this.#start) { - const end = start + (this.#size.get(handle) ?? 0); - if (end <= drawn) continue; - drawn = end; - top = handle; - topFrom = start; - } - this.#drawn = drawn; - const lowest = this.#free[0]; - if (top < 0 || lowest === undefined || lowest[0] >= drawn) break; - const [holeStart, holeEnd] = lowest; - const size = this.#size.get(top) ?? 0; - if (size <= holeEnd - holeStart) { - array.copyWithin(holeStart * 16, topFrom * 16, drawn * 16); - this.#start.set(top, holeStart); - if (holeEnd - holeStart > size) this.#free[0] = [holeStart + size, holeEnd]; - else this.#free.shift(); - this.#hole(topFrom, drawn); - this.#drawn = holeStart + size; - this.#touched(holeStart, size); - } else { - const shift = holeEnd - holeStart; - array.copyWithin(holeStart * 16, holeEnd * 16, drawn * 16); - this.#free.shift(); - for (const [handle, start] of this.#start) { - if (start >= holeEnd) this.#start.set(handle, start - shift); - } - for (const range of this.#free) { - if (range[0] >= holeEnd) { - range[0] -= shift; - range[1] -= shift; + this.#chunks = new CellChunks( + init.hlod !== false, + { + loadModel: this.#models.loadModel, + byteBudget: this.#budgets.bytes, + logicalBase: this.#logicalBase, + receiveShadow: this.#receiveShadow, + placementReach: this.#placementReach, + autoLodMaxPixelError: this.#autoLodMaxPixelError, + chunkMergeMaxTriangles: this.#chunkMergeMaxTriangles, + castShadowLevels: this.#castShadowLevels, + bundlesWanted: this.#bundlesWanted, + }, + { + disposeProxyModel: (state) => { + if ( + this.#models.loadModel !== undefined || + state.retainedPaths.every((path) => this.#models.pathUsers(path) <= 1) + ) + this.#failures += this.#models.disposeLoaded(state.model); + }, + disposeLoaded: (model) => this.#models.disposeLoaded(model), + releaseModelPaths: (paths) => this.#models.releaseModelPaths(paths), + retainModelPaths: (paths, into) => this.#models.retainModelPaths(paths, into), + addFailures: (count) => { + this.#failures += count; + }, + sourceBytes: () => this.#bytes + this.#impostorState.farBytes, + load: (path, wanted) => this.#models.load(path, wanted), + cellLive: (cell, generation) => this.#cellLive(cell, generation), + add: (object) => this.add(object), + intersectsObject: (mesh) => this.#render.intersectsObject(mesh), + gpuSceneOn: () => this.#render.gpuOn, + asset: (id) => this.#models.asset(this.#canonical(id)), + invalidateBundles: (key) => this.#render.invalidateBundles(key), + worldSphere: (bounds) => worldSphere(bounds, this), + reportCellFailure: (stage, key, paths, error) => + this.#reportCellFailure(stage, key, paths, error), + parent: () => this.parent, + prepareCompile: (root, camera, renderer) => { + let scene: Object3D = this; + while (scene.parent !== null) scene = scene.parent; + if (Reflect.get(scene, "isScene") !== true) return; + return () => renderer.compileAsync(root, camera, scene); + }, + compileChunk: (renderer, chunk, camera) => renderer.compileAsync(chunk, camera, this), + addChunk: (cell, object) => this.#render.addChunk(cell, object), + publishChunkBounds: (cell, box, proxies) => + this.#render.publishChunkBounds(cell, box, proxies), + }, + ); + this.#impostorState = new CellImpostors( + { + enabled: + init.impostors ?? + (impostorsRequested() && + !(init.preserveAuthoredParts === true || init.lodHysteresis !== undefined)), + receiveShadow: this.#receiveShadow, + candidates: [...candidates], + cells: this.#cells, + manifest: this.#manifest, + placements: this.#placements, + transparentScatter: this.#transparentScatter, + autoLodPixelsPerUnit: this.#autoLodPixelsPerUnit, + castShadowLevels: this.#castShadowLevels, + budgets: this.#budgets, + }, + { + released: () => this.#released, + renderer: () => this.#chunks.renderer, + asset: (id) => this.#models.asset(id), + hasAsset: (id) => this.#models.hasAsset(id), + assets: () => this.#models.assets(), + nearWorkPending: () => + this.#jobs.length > 0 || + this.#models.loadsInFlight > 0 || + this.#models.loadsQueued > 0 || + this.#prewarm.queued > 0, + newAsset: (id, definition) => this.#models.newAsset(id, definition), + startAssetLoad: (asset) => this.#models.startAssetLoad(asset), + release: (id) => this.#models.release(id), + noteGate: (gate) => this.#noteGate(gate), + retargetWideCasters: (asset) => this.#render.retargetWideCasters(asset), + terminalLevelReady: (asset) => { + if (this.#render.gpuOn) { + const level = asset.levels.length - 1; + const part = asset.levels[level]?.[0]; + const key = `${asset.id}:${String(level)}:0`; + if ( + part !== undefined && + !this.#render.hasShared(key) && + !this.#render.hasRetired(key) + ) { + const shared = new SharedBatch( + part.geometry, + part.material, + this.#runMax.get(asset.id) ?? 1, + this.#resident.size + 1, + key, + this.#extentBounds, + "main", + ); + shared.smallCaster = this.#render.smallCaster(asset.id); + this.#render.dressMesh(shared, this.#receiveShadow); + this.#render.adoptGpu(shared, asset.id, key, level, 0); + this.#render.setShared(key, shared); + this.#render.attach(shared); + this.#prewarm.awaitDraw(shared); + } + this.#render.publishGpuGates(asset); + this.#prewarm.queuePrewarm(asset); + return; } - } - this.#drawn = drawn - shift; - this.#touched(holeStart, this.#drawn - holeStart); - } - } - const capacity = this.mesh.instanceMatrix.count; - const free: Array<[number, number]> = []; - for (const [from, to] of this.#free) { - if (from < this.#drawn) free.push([from, to > this.#drawn ? this.#drawn : to]); - } - if (this.#drawn < capacity) free.push([this.#drawn, capacity]); - this.#free = free; - } - - /** - * Close the holes a write, a clear or a grow left, the way this batch's role needs. - * - * A caster batch compacts: one square holds the key's records, so the lowest hole is where the - * topmost block belongs. A main batch regroups instead, because compaction would carry a block - * away from the square it belongs to and the frustum test reads the squares. - */ - #settle(): void { - // A dressed batch's layout is the dispatch's: its `instanceMatrix` is the scene's shared buffer, - // so a compact or regroup here would read that buffer's count as this batch's capacity and - // shuffle the whole world's records. `write` and `clear` bypass this already; `grow` does too. - if (this.gpu !== undefined) return; - if (this.#clustered) { - this.#regroup(); - // Records moved, so `mesh.count` is the live total again and the window has to be re-narrowed - // by the next draw — with no copy, because the regroup just put the squares in place. - this.#narrowed = false; - } else this.#compact(); - } - - /** - * Lay every live block out again, grouped by its world-grid square, `visible`'s squares first. - * - * A regroup is a permutation of `[0, drawn)` and there is no in-place order for one: writing the - * visible squares to the front necessarily lands on squares whose records have not been read out - * yet, in either direction. So every block is copied into {@link regroupScratch} and the new order - * is copied back — two passes over the drawn range, and only when a square entered or left the - * window, which on a settled camera is not once a frame. - * - * Squares keep their relative order within each half, so a residency change regroups in place and - * a cull that found the same set writes nothing. What each square holds is recorded here, because - * that is the only per-key data the window needs: the squares' world volumes belong to the grid, - * not to a key, and are derived once a frame for all of them. See {@link WorldCells.#cullMainPass}. - * - * Only the records that actually moved are handed to the upload, and on a walk that is usually the - * tail or nothing at all: the squares that stayed in the half they were in keep their offsets - * exactly, so a square crossing the boundary between the two halves moves the records from there - * to the boundary and leaves `[0, boundary)` byte-identical. Uploading `[0, drawn)` instead — which - * is what this did — re-sent a whole key's records to the GPU on every frame the window moved, - * which on a walking camera is most frames, and on the largest key in a 2 km walk measured - * **37 draws over 7 ms each**. An identity reorder now costs no bytes at all, and the rest costs - * the span that moved; `#touched` still widens a span over half the drawn range, so the ranges - * stay few and large rather than many and small. - */ - #regroup(visible = this.#visible): void { - const array = this.mesh.instanceMatrix.array as Float32Array; - const squares = this.#squares(); - let total = 0; - for (const square of squares) total += sizeOf(square); - const scratch = scratchFor(total * 16); - // The visible squares first. `filter` is stable, so both halves keep the order the buffer had - // them in and a residency change lays out exactly what the last one did. - const front = squares.filter((one) => visible?.has(one.cluster) ?? true); - const back = squares.filter((one) => !(visible?.has(one.cluster) ?? true)); - let written = 0; - // Where the layout departed from the one the GPU holds: the first record written somewhere other - // than where it was read from, and the last one after it. `-1` until something moves, so an - // identity reorder touches nothing. - let movedFrom = -1; - let movedTo = 0; - this.#squareSizes.clear(); - for (const square of [...front, ...back]) { - const union = new Box3(); - for (const [handle, start, count] of square.blocks) { - scratch.set(array.subarray(start * 16, (start + count) * 16), written * 16); - // Read before it is overwritten: a block written back to its own offset is a record the GPU - // already holds, and is the whole difference between a repack that costs nothing and one - // that re-uploads the key. - if (this.#start.get(handle) !== written) { - if (movedFrom < 0) movedFrom = written; - movedTo = written + count; - } - this.#start.set(handle, written); - const box = this.#boxes.get(handle); - // A fresh `Box3` is already empty, so the first block's box is the square's and every block - // after it unions into it. - if (box !== undefined) union.union(box); - written += count; - } - this.#squareSizes.set(square.cluster, sizeOf(square)); - } - array.set(scratch.subarray(0, written * 16)); - this.#drawn = written; - const capacity = this.mesh.instanceMatrix.count; - this.#free = written < capacity ? [[written, capacity]] : []; - this.#window = this.#windowOf(visible); - // After `#drawn`, because `#touched` measures the span it may widen against the drawn range, and - // before the caller's own `#touched`, which the epoch unions with it rather than replacing it. - if (movedFrom >= 0) this.#touched(movedFrom, movedTo - movedFrom); - } - - /** - * The live blocks, in buffer order and gathered into the squares they belong to. - * - * Buffer order is what "the squares keep their relative order" means, and it is not the order of - * `#start`, which is keyed by handle rather than by position. A square's blocks are only adjacent - * because the last regroup made them so — a cell admitted after that lands anywhere in the free - * tail — so the squares are gathered here and written out by {@link #regroup}, and it is these - * two orders that a residency change has to preserve. - */ - #squares(): Array<{ cluster: string; blocks: Array<[number, number, number, string]> }> { - const blocks: Array<[number, number, number, string]> = []; - for (const [handle, start] of this.#start) { - const size = this.#size.get(handle) ?? 0; - if (size > 0) blocks.push([handle, start, size, this.#blockCluster.get(handle) ?? ""]); - } - blocks.sort((a, b) => a[1] - b[1]); - const squares: Array<{ cluster: string; blocks: typeof blocks }> = []; - const squareOf = new Map(); - for (const block of blocks) { - const cluster = block[3] as string; - const index = squareOf.get(cluster); - if (index === undefined) { - squareOf.set(cluster, squares.length); - squares.push({ cluster, blocks: [block] }); - continue; - } - (squares[index] as { blocks: typeof blocks }).blocks.push(block); - } - return squares; - } - - /** Records of the squares in `visible`, the count a clustered batch's `mesh.count` draws. */ - #windowOf(visible: Set | undefined): number { - if (visible === undefined) return this.#drawn; - let window = 0; - for (const [cluster, size] of this.#squareSizes) if (visible.has(cluster)) window += size; - return window; - } - - /** - * Narrow the draw window to the visibility cells `visible` names, rewriting the instance buffer - * when this batch's own share of that set changed. `settled` when nothing had to be looked at, - * `narrowed` when the count was re-widened, `repacked` when records were copied. - * - * `visible` and `epoch` are the frame's answer, derived once for the whole main pass by - * {@link WorldCells.#cullMainPass}: one frustum, one set of visible visibility cells, and an epoch - * that only moves when that set did. A batch whose records have not moved since it last packed at - * that epoch answers `false` from two integer comparisons — no frustum, no set, no copy — which is - * the whole of a settled frame across the ~230 meshes of a 2 km walk. Before that it was every - * batch rebuilding a frustum from the same camera, allocating a set and re-testing its own per-key - * volumes: ~4 ms a frame of `compute` for an answer that had not changed. - * - * Three's whole-mesh test cannot do this at all: the mesh's bounds are every resident cell's, so on - * a 640 m ring the test always passes and every instance behind or beside the camera is - * vertex-shaded — which on a tree-heavy 2 km map is most of a 65 M-triangle main pass, and the - * whole cost of it is vertex work that half resolution does not touch. - * - * The cells are the main cull's own and the volumes are per placement (see - * {@link WorldCells.#cullMainPass}), which is what this narrowness is spent on: a cell's XZ is - * one world cell rather than a 128 m caster square, and its Y is the height its own placements - * reach rather than the whole map's terrain range. - * - * A draw that finds the same cells it found last time touches nothing at all: no copy, no - * `needsUpdate`, and therefore no attribute version — which is what a settled static draw watches - * to know it still has to scan. - */ - cullFrom(visible: ReadonlySet, epoch: number): "settled" | "narrowed" | "repacked" { - if (!this.#clustered) return "settled"; - // A batch holding no square draws nothing whatever the camera found, so it is published empty - // and takes no set and no scan — a prewarmed batch sits in exactly this state. - if (this.#squareSizes.size === 0) { - this.#publish(0); - return "settled"; - } - // Settled: the frame's set is the one this window was laid out at and no record joined or left - // since. O(1), and no allocation of any kind. - if (this.#narrowed && this.#packedEpoch === epoch) return "settled"; - const next = this.#scratch; - next.clear(); - for (const square of this.#squareSizes.keys()) if (visible.has(square)) next.add(square); - // The same squares as the buffer holds: a residency change or a square elsewhere entering and - // leaving the view. The layout is already right, so the window only has to be re-narrowed — a - // residency change left `mesh.count` at the live total, and `#settle`'s regroup put the squares - // in the window's order with no copy. - if (sameSquares(next, this.#visible)) { - this.#publish(this.#window); - this.#narrowed = true; - this.#packedEpoch = epoch; - return "narrowed"; - } - this.#regroup(next); - // The reorder hands its own upload, over the records that actually moved — a window that moved - // a square across the half boundary and left every record where it was costs nothing here, which - // is the walking camera's common case. See `#regroup`. - // The layout now belongs to the other set, so the two trade places and the next cull reuses the - // one this batch is no longer holding. - const was = this.#visible; - this.#visible = next; - this.#scratch = was ?? new Set(); - this.#scratch.clear(); - // `#publish` is what hides a window the camera narrowed to nothing, and what shows it again when - // the camera turns back. - this.#publish(this.#window); - this.#narrowed = true; - this.#packedEpoch = epoch; - return "repacked"; - } - - /** Live records this batch holds, whether the camera is drawing them or not. */ - get live(): number { - return this.#drawn; - } - - /** - * The record ceiling of this batch's *own* buffer, which is what a key's GPU region is sized from: - * the same ring-sized ceiling the CPU path stops growing at. Held rather than read, because a - * dressed batch's `mesh.instanceMatrix` is the GPU scene's shared buffer and its count is that - * buffer's, not this key's. - */ - get liveCeiling(): number { - return this.#ceiling; - } - - /** Records `mesh.count` draws: every live one, until a cull narrows the window. */ - get drawn(): number { - return this.#clustered ? this.#window : this.#drawn; - } - - /** - * One coalesced upload per mesh, not one per moved, written or zeroed record range. Three's WebGPU - * backend turns each entry of `updateRanges` into its own `GPUQueue.writeBuffer`, so the - * per-range bookkeeping here was the walk's largest CPU cost: thousands of sub-1 KB vertex writes - * per frame, over a second of a short walk spent on the queue. The dirty span is accumulated here - * across every write, compact and clear of one update, so a frame that touches a mesh from - * several directions still issues one writeBuffer. A span over half the drawn range becomes the - * whole drawn range, which is never more bytes than the two halves it replaced. - */ - #touched(from: number, count: number): void { - // A dressed batch's records were never in this attribute, so there is nothing to upload and - // nothing to put back in charge of culling: the dispatch decided what it drew. - if (this.gpu !== undefined) return; - const matrix = this.mesh.instanceMatrix; - // A record is 16 array elements, and that is the unit `updateRanges` is in: three reads them - // as element offsets into `array`, not as bytes. - const drawn = this.#drawn * 16; - const low0 = from * 16; - const high0 = (from + count) * 16; - // The pending span is ours, and it lasts one update. Three's WebGPU path uploads through the - // interleaved buffer it derives from `instanceMatrix.array` and only that buffer's ranges are - // cleared after the write, so the ranges on `instanceMatrix` are never consumed: they were - // unioned in here forever, and after one wide write every later write re-uploaded the whole - // drawn range. The epoch moves once per `update()`, and the render at the end of an update is - // what consumes the span, so a fresh epoch is a fresh span. - let low = low0; - let high = high0; - const epoch = currentWriteEpoch(); - if (this.#pendingEpoch === epoch) { - if (this.#pendingLow < low) low = this.#pendingLow; - if (this.#pendingHigh > high) high = this.#pendingHigh; - } - if (low > 0 && (high - low) * 2 > drawn) { - low = 0; - high = drawn; - } - this.#pendingLow = low; - this.#pendingHigh = high; - this.#pendingEpoch = epoch; - matrix.clearUpdateRanges(); - if (high > low) matrix.addUpdateRange(low, high - low); - matrix.needsUpdate = true; - this.#publish(this.#drawn); - // Records are real, so the bounds are the ones `#rebound` just wrote and the mesh goes back to - // being culled. - this.mesh.frustumCulled = true; - } - - /** - * The one place `mesh.count` is written, so a batch that would draw nothing is not submitted. - * - * In three r185's WebGPU path an `InstancedMesh` with `count === 0` still runs the whole per-draw - * JS chain — nodes, bindings, pipeline, attributes — and only the backend's `getDrawParameters` - * finally answers null. A walk census measured 322 of 871 main batch meshes and 437 of 1,574 - * caster meshes at count 0, so every empty batch was ~17 µs of CPU a frame for nothing drawn. - * `visible` is the one flag three reads before any of that, so a batch with nothing to draw is - * hidden here and shown again by the first write that gives it a record: a placement add, a - * rebind of a retained batch, a residency change, or the main cull's window opening again. - * - * The exception is a batch still owed its prewarm draw, which stays visible *because* it is empty: - * that one draw is what builds the node and the pipeline the loading screen exists to pay for, - * and hiding it would hand the build to the first walk frame that wants the key. It hides as soon - * as the draw is counted — see {@link prewarmDrew} — so this costs one zero-count draw per - * prewarmed batch over the whole load, which is the cost the prewarm is for. - */ - #publish(count: number): void { - // A shadow key is submitted every render of a map that covers it: its own records are the - // dispatch's, so an empty-looking key is one this map draws nothing of, not one to hide. - if (this.role === "key") { - this.mesh.count = this.gpuCount; - this.mesh.visible = true; - return; - } - // A dressed batch's instances are the GPU scene's, and its own `count` says nothing about how - // many the dispatch kept — a zero there makes three skip the indirect draw before the GPU reads - // the record at all. So the mesh is submitted at its region's capacity and the record decides - // what draws; the per-asset coarse gate in `#cullMainPass` runs after this every frame and is - // what shows it. Until then the prewarm draw is the one reason to stay visible *because* it is - // empty: that submission is what builds the node and the pipeline. - if (this.gpu !== undefined) { - this.mesh.count = this.gpuCount; - if (this.bundled === false) this.mesh.visible = this.#awaitingPrewarm; - return; - } - this.mesh.count = count; - this.mesh.visible = count > 0 || this.#awaitingPrewarm; - } - - /** - * The GPU-driven main pass's coarse gate, asked per key and answered per asset: whether any of - * this batch's CPU grid squares is in `visible`, or it still owes the draw that builds its node. - * - * It is deliberately *not* the draw decision. A key's squares are the level the CPU picked from the - * follow point when it built them, and the dispatch re-picks the level from the camera every frame - * — so a key holding no cell can be the one the dispatch draws, and hiding on its own empty answer - * is how a tree disappeared on approach. The caller unions this across every level and part of the - * asset and hands the asset's answer back; see `WorldCells#cullMainPass` and `applyGpuVisible`. - */ - seenIn(visible: ReadonlySet): boolean { - if (this.#awaitingPrewarm) return true; - for (const square of this.#squareSizes.keys()) if (visible.has(square)) return true; - return false; - } - - /** - * Apply the asset's conservative visibility answer to this dressed mesh, and publish the count a - * draw and the census read. A bundled mesh is shown by its recorded bundle and never asked. - */ - applyGpuVisible(shown: boolean): void { - // The region's capacity, not this batch's own records: an empty-looking key — one the CPU built - // at another level — must still submit its indirect draw, and the record inside it draws nothing - // when the dispatch selected none. See `gpuCount`. - this.mesh.count = this.gpuCount; - this.mesh.visible = shown; - } - - /** - * Owe this batch its prewarm draw, and show it: empty, unsubmitted work is not what a prewarm - * wants, one submission is. - * - * The flag on the mesh is what `VirtualShadowNode`'s `#probe` reads: a caster is drawn by a level, - * and a level picks one of the two caster layers, so a level that finds an owed caster renders - * both — otherwise the half it did not pick would leave its prewarm casters unbuilt for the whole - * walk. Engine bookkeeping on the mesh, beside the `casterInstanceScale` the probe already reads. - */ - awaitPrewarmDraw(): void { - this.#awaitingPrewarm = true; - (this.mesh as { casterPrewarmOwed?: boolean }).casterPrewarmOwed = true; - this.#publish(this.drawn); - } - - /** - * Publish this batch's admission for the frame, on the mesh beside the other two flags the shadow - * probe reads off it. A `main` batch has nothing to say — it is the truth the caster halves are - * measured against — and writes its own mesh, which is on layer 0 and no level ever draws. - */ - setMainAdmitted(admitted: boolean): void { - if (this.role === "main" || this.#mainAdmitted === admitted) return; - this.#mainAdmitted = admitted; - this.mesh.mainAdmitted = admitted; - } - - /** - * The owed draw is counted, or it can never come, so the batch follows the count again and hides - * if no placement ever gave it a record. Only `visible` is written: the count three is submitting - * this frame is the right one, and `#publish` would replace it with a window no cull has run for. - */ - prewarmDrew(): void { - this.#awaitingPrewarm = false; - (this.mesh as { casterPrewarmOwed?: boolean }).casterPrewarmOwed = false; - // A bundled mesh is in a render list three fixed when it recorded the bundle, and the replay - // never re-reads visibility — so hiding it here would not save this frame's draw and would lose - // the mesh from every record after the next structural change. See `bundled`. - if (this.bundled === true) return; - this.mesh.visible = this.mesh.count > 0; - } -} - -/** - * A camera, plus the one flag the exclusion rule reads. Three sets `isOrthographicCamera` on an - * `OrthographicCamera` at runtime and declares it on no base camera type, so it is duck-typed here - * the same way `ICasterScaleMesh` and `isInstancedMesh` are. - */ -type ICamera = Camera & { readonly isOrthographicCamera?: boolean }; - -/** - * The `@x,z` half of a cluster key, for a cell index. `cellsPerCluster` squares of cells per - * cluster, so the answer is stable for as long as the grid is: placements never move, so a record is - * only ever clustered when the cell holding it is admitted or evicted. - */ -function clusterOf(cellX: number, cellZ: number, cellsPerCluster: number): string { - return `${String(Math.floor(cellX / cellsPerCluster))},${String(Math.floor(cellZ / cellsPerCluster))}`; -} - -/** - * Whether any of `gates` — ascending — lies in `[low, high]`. One binary search for the first gate at - * or past `low`, against the walk of every gate a scan does per cell: a follow point that has not - * carried anything across a boundary is the common case, and the refilter runs it over every resident - * cell every 2 m of travel. Ends included, so a placement exactly on a gate is one of the reasons to - * rebuild. - */ -function crossesGate(gates: readonly number[], low: number, high: number): boolean { - let start = 0; - let end = gates.length; - while (start < end) { - const middle = (start + end) >> 1; - if ((gates[middle] as number) < low) start = middle + 1; - else end = middle; - } - return start < gates.length && (gates[start] as number) <= high; -} - -interface ICellProxyState { - readonly cell: IResidentCell; - readonly selector: CellProxySelection; - readonly retainedPaths: string[]; - pending: boolean; - compiling: boolean; - cancelled: boolean; - compiled: boolean; - active: boolean; - bytes: number; - model?: Object3D; - prepared?: IPreparedCellProxy; - readonly observed: Set; - readonly hidden: Map; - readonly mainMasks: Map; -} - -interface IResidentCell { - proxy?: ICellProxyState; - readonly mainChunkMeshes: Mesh[]; - /** Authored static instances at attachment, before a caller can mutate the resident chunk. */ - readonly chunkInstances: Map< - InstancedMesh, - { - readonly matrix: InstancedMesh["instanceMatrix"]; - readonly version: number; - readonly count: number; - } - >; - readonly completedRuns: Set; - chunksLoaded: boolean; - chunkFailed: boolean; - readonly key: string; - readonly x: number; - readonly z: number; - readonly cell: IWorldCell; - /** - * The union of the distances from every one of this cell's batches' own filter points to the cell's - * rectangle, widened as batches are built and never narrowed; see `#staleIn`. Monotone on purpose: - * too wide a range means a cell the gate test does not reject, and too narrow one would drop a - * rebuild the pass owes. - */ - filterNear: number; - filterFar: number; - readonly generation: number; - readonly instances: number; - readonly bytes: number; - readonly batches: ICellBatch[]; - readonly chunks: Object3D[]; - /** - * The exact `assets.model` cache paths this cell's chunks asked the loader for, held in - * `#modelPaths` and handed back once the last cell naming a path is gone, exactly like an asset's - * levels; see `#releaseModelPaths`. Empty for a cell with no chunks or a `loadModel` override. - */ - readonly retainedModelPaths: string[]; -} - -/** - * One cell's asset run waiting to be built, in units small enough to fit a frame's admission - * budget: a slice of placements, or one mesh built. - * - * A job outlives the `update` that queued it, so every field it needs to resume is on the job and - * not on the call stack — a streamed cell holds its residency slot while it waits, so the work is - * never spent on a cell that has since left. - */ -interface IBuildJob { - readonly asset: IAssetState; - readonly cell: IResidentCell; - readonly run: IWorldRun; - /** The follow snapshot taken at first admission, held fixed across every placement slice. */ - filterY: number; - filterX: number; - filterZ: number; - /** A refilter's outgoing batches, drawn until this job's own batches are attached. */ - replaced: ICellBatch[]; - /** Whether this job replaces an existing or pending run; initial admission leaves it false. */ - readonly replacement: boolean; - /** This job's built batches, attached together when the last one is ready. */ - readonly fresh: ICellBatch[]; - /** The run's placement records, read once and kept across the slices that filter them. */ - records: Float32Array | undefined; - /** Next placement to filter; the filter is complete when it reaches `run.count`. */ - next: number; - batches: InstancedBatch[][] | undefined; - /** - * One root-matrix batch per level the filter reached, filled only when the asset can become a - * whole-asset impostor: the placement transform with no part offset, which is what its far quad - * needs. `undefined` for every other asset, so nothing pays for records the wide half never reads. - */ - roots: (InstancedBatch | undefined)[] | undefined; - /** - * Bypass the refilter's identical-answer fast path. A handoff that changes what a wide segment must - * hold is not an identical answer even when the level and records are, so the rebuild has to swap. - */ - force: boolean; - /** One world-bounds box per `(level, part)` batch, filled by `#addPlacements` and read on swap. */ - boxes: Box3[][] | undefined; - /** How many of the (level, part) meshes have been built. */ - published: number; - /** - * The GPU scene's source records for the placements this filter kept, one per placement beside the - * level it reached — filled by `#addPlacements` only while the scene is on, and read on the swap. - */ - sources: IGpuSource[] | undefined; - /** The records placed per level, so every part of a level shares one list; see `#placeSources`. */ - gpuByLevel: Map | undefined; -} - -/** One placement as the GPU scene's source buffer holds it: the placement's own transform, no part. */ -interface IGpuSource { - readonly level: number; - readonly matrix: Matrix4; - readonly x: number; - readonly y: number; - readonly z: number; - /** Half the diagonal of the asset's authored bounds at this placement's scale. */ - readonly radius: number; - /** The placement's uniform scale, carried so the dispatch can scale a whole-asset impostor gate. */ - readonly scale: number; -} - -/** One drawable part of one level, read out of the level's own GLB. */ -interface IAssetPart { - readonly geometry: BufferGeometry; - /** The mesh's transform relative to the model root, composed into every instance matrix. */ - readonly local: Matrix4; - /** The surface the batch draws with: the shared surface for this part's material content. */ - readonly material: Material; - /** The `materialKey` of that surface, released through the shared-surface registry. */ - readonly surface: string; -} - -/** - * One completed whole-asset atlas, cached by its content key and its source cutout contract. - * - * Bounded, not a generic cache: the atlas is the one GPU resource a later far reference may want to - * hold without keeping the full source GLB, and the CPU `center`/`radius` are the two scalars the - * surface needs to project it. `users` counts the live assets drawing it; a record with no user is - * inactive and evictable once the byte budget is over, and one with a user is never evicted. - */ -interface IImpostorRecord { - readonly key: string; - readonly atlas: WorldImpostorAtlas; - readonly center: Vector3; - readonly radius: number; - readonly bytes: number; - users: number; -} - -/** The terminal LOD one asset appended once its bake landed; owned and released with the asset. */ -interface IImpostorState { - readonly part: IAssetPart; - readonly surface: WorldImpostorSurface; - readonly record: IImpostorRecord; -} - -/** - * One `(cell, run)`'s slice of a whole-map far aggregate. - * - * The records are the run's ORIGINAL placement roots, composed once when the aggregate is built and - * never rewritten while the camera moves. `live` is false while the near ring owns the run, which is - * the atomic handoff: a run is drawn by exactly one of the two, never both and never neither. - */ -interface IFarSegment { - readonly cell: IWorldCell; - readonly run: IWorldRun; - /** First instance record in the aggregate's buffer, and how many the run holds. */ - readonly start: number; - readonly count: number; - /** This run's own view-distance gate, `Infinity` when its asset authored no `maxDistance`. */ - readonly cull: number; - live: boolean; -} - -/** - * One whole-map far aggregate per exact atlas cache key: a single `InstancedMesh` holding every - * ORIGINAL placement root of every canonical asset that bakes to that key, independent of the near - * full-geometry ring and of the near source GLBs. - * - * The surface is the aggregate's own, built from the record's atlas and the first source material - * that baked to it, so disposing a near asset's surface cannot invalidate the far mesh. The atlas is - * borrowed: `record.users` is incremented once for the aggregate and held until the world disposes, - * which is what stops `#evictImpostors` dropping an atlas the far mesh still draws. - */ -interface IFarAggregate { - readonly key: string; - readonly surface: WorldImpostorSurface; - readonly record: IImpostorRecord; - mesh: InstancedMesh; - capacity: number; - /** Every original placement root the aggregate holds, live and near-owned. */ - readonly segments: Map; - /** How many times the instance buffer has been written, for the settled-frame check. */ - uploads: number; -} - -/** - * The metadata a refused far cohort is kept as, without its source GLB. - * - * A hard budget refusal releases the asset's geometry and materials the way any release does; the - * atlas is pinned by one borrowed `users` and the aggregate's own surface is built once here, so a - * later capacity release retries from this alone — no full model is held alive for a cohort that may - * never fit. See `#deferFar`, `#retryFar`. - */ -interface IFarCohort { - readonly id: string; - readonly key: string; - readonly record: IImpostorRecord; - /** The alpha-foliage source material to twin; absent once a refused cohort has its surface built. */ - readonly source?: Material; - readonly alphaTest: number; - readonly cull: number; - readonly runs: ReadonlyArray<{ cell: IWorldCell; run: IWorldRun }>; -} - -/** - * A refused far cohort kept without its source GLB: metadata plus the surface already built from the - * source at refusal. No material, geometry or texture of the model survives here — only the surface, - * which cleared its UV maps and borrows the pinned atlas. - */ -interface IFarRetryEntry { - readonly id: string; - readonly key: string; - readonly record: IImpostorRecord; - readonly alphaTest: number; - readonly cull: number; - readonly runs: ReadonlyArray<{ cell: IWorldCell; run: IWorldRun }>; - readonly surface: WorldImpostorSurface; -} - -/** A surface every part with the same material content draws with, and who still uses it. */ -interface ISharedSurface { - readonly material: Material; - /** The canonical source and any cutout made from it, torn down with the last user. */ - readonly owned: readonly Material[]; - users: number; -} - -/** Parts already handed back, so a level that fell back to the one above releases it once. */ -const releasedParts = new WeakSet(); - -interface IAssetState { - readonly id: string; - readonly definition: IWorldAsset; - /** - * The distance at which each level takes over, index-aligned with `glbs`; level 0 never does. - * - * Mutable because a chain is only known once the asset's own model has loaded: an asset with no - * authored `lods` is widened with the levels that model's baked chain carries, and every consumer - * of this reads it at build time — after `#adoptAsset` — so nothing has to be rebuilt. - */ - distances: readonly number[]; - /** The package-relative GLB per level, index-aligned with `distances`. */ - readonly glbs: readonly string[]; - /** Every distance the level or cull answer changes at, `threshold` being the nearest. */ - gates: readonly number[]; - /** The nearest distance this asset's batching can be crossed at, or `undefined` when it cannot. */ - threshold: number | undefined; - refcount: number; - hlodSafe: boolean; - pending: boolean; - failed: boolean; - disposed: boolean; - /** - * The exact `assets.model` cache paths this state asked the loader for, held in `#modelPaths` - * until the last state naming each one is released. Empty when `loadModel` bypasses the cache; - * filled once per state, so a load retried after an empty answer does not count twice. - */ - retainedPaths: string[]; - /** - * One part list per entry in `glbs`, so a level that failed to load reuses the level below it and - * the batch builder never has to know a level is missing. - */ - levels: readonly (readonly IAssetPart[])[]; - /** - * Parts taken out of DRAW but still owned here until teardown. Authored levels now keep their own - * geometry, so this stays empty; it is drained once with the asset on release. - */ - spilled: readonly IAssetPart[]; - /** - * The loader's resolved cooked url for LOD0, recorded at load. It is the atlas cache key's model - * half, so two package entries that fetch the same cooked bytes share one bake even when their - * `lods`/`maxDistance` metadata stopped them aliasing. See `#impostorKey`. - */ - resolvedGlb: string; - /** The whole-asset impostor level this asset appended, once its bake landed. See `#finishImpostor`. */ - impostor?: IImpostorState; -} - -/** Immutable CPU representation published beside one source swap for the independent mesh check. */ -interface IPublishedGpuGates extends IAssetSlot { - readonly locals: readonly (readonly Float32Array[])[]; -} - -interface IWorldCellsInit extends IWorldCellsLoadOptions { - readonly reachRanges: Map< - IWorldCell, - Map - >; - readonly manifest: IWorldPackage; - readonly placements: ArrayBuffer; - readonly heightmap: Uint16Array; - readonly baseUrl: string; - /** `baseUrl` as the loader keys it: no leading slash, which is what a manifest lists. */ - readonly logicalBase: string; - /** - * Asset ids the package named twice for one model, each pointing at the id that is kept. Built - * once in `load`, where the loader can be asked what each path really fetches; empty when nothing - * is a duplicate. See {@link assetAliases}. - */ - readonly aliases: ReadonlyMap; - /** - * Whether `load` fabricated the loader it hands in as `assets` instead of the game supplying one. - * `load` always passes a loader to the constructor, so `assets === undefined` cannot tell an owned - * loader from a caller's; this is the bit that can. Left out, ownership is `assets === undefined`. - */ - readonly ownsAssets?: boolean; -} - -/** GPU resources already handed to a `dispose`, so no second path can tear the same one down. */ -const tornDown = new WeakSet(); - -/** - * Release one GPU resource: at most once per resource, and never by throwing. - * - * Exactly once is the streaming contract: an asset's geometry is shared by every cell batch drawn - * from it, so several residency paths can reach the same object, and a second teardown is the - * renderer's problem, not the game's. - * - * Never throwing is the other half. Three disposes a geometry by deleting each of its attributes, - * and `WebGPUAttributeUtils.destroyAttribute` reads the GPU buffer off the attribute and calls - * `destroy` on it with no guard — so disposing a geometry whose upload never produced that buffer - * throws `TypeError: Cannot read properties of undefined (reading 'destroy')` from wherever the - * teardown was called. A cell leaving range is the most ordinary thing a streaming world does, so - * that has to land as a counted failure here, not as a dead frame. - * - * @returns `true` when the teardown itself threw, for the caller to count. - */ -function release(target: { dispose: () => void } | undefined): boolean { - if (target === undefined || tornDown.has(target)) return false; - tornDown.add(target); - try { - target.dispose(); - } catch { - return true; - } - return false; -} - -async function loadModelWith(assets: IAssetLoader, path: string): Promise { - const gltf = await assets.model<{ scene?: Object3D }>(path); - const scene = gltf?.scene; - if (!(scene instanceof Object3D)) - throw new Error(`World asset '${path}' loaded without an Object3D scene.`); - return scene; -} - -function resolveRelative(baseUrl: string, relative: string): string { - if (/^(?:[a-z]+:)?\/\//iu.test(relative) || relative.startsWith("data:")) return relative; - return `${baseUrl}${relative.replace(/^\//u, "")}`; -} - -/** - * The raw renderer seam the impostor bake needs, or `undefined` when this backend does not expose it. - * - * `IRendererLike.raw` is `unknown` on purpose: it is whatever the running renderer is, and a backend - * without layered render targets has no bake seam at all. The shape is checked rather than cast, so a - * stub whose raw is not a renderer leaves the bake un-run instead of throwing inside a frame. - * - * A WebGL renderer is the reason the three-method shape alone is not enough: it carries `render`, - * `setRenderTarget` and `initRenderTarget` and would pass, then fail mid-frame on the layered MRT - * capture it cannot run. So the backend must declare itself WebGPU (`kind` and the renderer's own - * `isWebGPURenderer`), which a WebGL fallback and an unsupported native backend do not, and the full - * getter/setter seam the baker calls must be present before the cast — the ones the baker reaches - * with `?.` still have to exist on a real backend, and a raw object missing them would be half-run, - * not skipped cleanly. - */ -const REQUIRED_IMPOSTOR_METHODS = [ - "getActiveCubeFace", - "getActiveMipmapLevel", - "getClearAlpha", - "getClearColor", - "getMRT", - "getRenderTarget", - "initRenderTarget", - "render", - "setClearAlpha", - "setClearColor", - "setMRT", - "setRenderTarget", -] as const; - -function impostorRawRenderer( - renderer: IRendererLike | undefined, -): IImpostorRawRenderer | undefined { - if (renderer === undefined || renderer.kind !== "webgpu") return undefined; - const raw = renderer.raw as - | (Partial & { isWebGPURenderer?: boolean }) - | null; - if (raw === null || raw === undefined) return undefined; - if (raw.isWebGPURenderer !== true) return undefined; - // A `WebGPURenderer` can wrap the WebGL fallback backend and still report `kind: 'webgpu'` and - // `isWebGPURenderer === true`; that backend cannot run a layered MRT capture, so the method shape - // alone would accept it and fail mid-frame. The backend it actually wrapped is the honest answer. - if ((raw as { backend?: { isWebGLBackend?: boolean } }).backend?.isWebGLBackend === true) - return undefined; - for (const method of REQUIRED_IMPOSTOR_METHODS) - if (typeof raw[method] !== "function") return undefined; - return raw as IImpostorRawRenderer; -} - -/** - * Load a package file from the first candidate the loader says might serve it, the way the - * loader's own `loadFirst` walks them: the authored name, then the project's `assets/` source. One - * candidate is what a manifest gives, and a delete-test project has no manifest, so taking only - * the first is a 404 for a package that is present on disk. When none answer, one error names every - * url tried and its status — the failure is two places looked at, not one missing file. - */ -async function loadLogical( - assets: IAssetLoader, - path: string, - load: (url: string) => Promise, -): Promise { - const candidates = await assets.resolve(path); - const failures: string[] = []; - for (const url of candidates) { - try { - return await load(url); - } catch (error) { - failures.push(`${url} (${error instanceof Error ? error.message : String(error)})`); - } - } - throw new Error( - `World file '${path}' could not be loaded from ${String(candidates.length)} candidate url(s): ${failures.join("; ")}`, - ); -} - -/** A `fetch` that refuses anything but an ok response, so candidates are walked on status. */ -async function fetchOk(url: string): Promise { - const response = await fetch(url); - if (!response.ok) throw new Error(`status ${String(response.status)}`); - return response; -} - -/** - * The alpha threshold a cutout gets when the part's own material names none. ponytail: a fixed - * 0.5, because the cutout point is authored data a GLB carries per texture and nothing reads it - * here. A tree whose needles visibly dither at 0.5 wants a lower one — give - * `transparentScatter` a third mode carrying a threshold when that is worth a knob. - */ -const DEFAULT_CUTOUT_ALPHA = 0.5; - -/** - * Mip compensation for scattered foliage. Golus's 0.25 suits dense cards; a needle atlas at ~9 % - * coverage averages to ~0.09 alpha by mip 8, where a tree 150 m out samples, so 0.25 still drops - * every needle. 0.75 keeps the cutoff under that average through mip 8. - * ponytail: one tuned constant; coverage-preserving mips at cook time are the upgrade path. - */ -const SCATTER_MIP_ALPHA_SCALE = 0.75; - -/** - * The surface a scattered part draws with, and the cutout this class owns when it made one. - * - * An `InstancedMesh` cannot sort its instances, so a `transparent` material on scattered foliage - * draws in submission order — wrong against itself, and overdraw on top of it. A cutout keeps the - * part's own alpha shape, keeps the depth buffer, and lets the draw reject what is behind it. The - * GLB's material is never mutated: the cutout is per asset part, so every cell batch of the asset - * shares one and a game that still wants blending asks for it. - * - * The cutout itself is `cutoutSurface`, because a cutout that ignores the mip chain deletes itself - * at distance: the needle card's mips average to a tenth, and every needle is discarded. - */ -function scatterMaterial( - material: Material, - transparentScatter: "cutout" | "blend", -): { readonly material: Material; readonly owned: readonly Material[] } { - if (transparentScatter === "blend" || !material.transparent) - return { material, owned: [material] }; - const cutout = cutoutSurface( - material, - material.alphaTest > 0 ? material.alphaTest : DEFAULT_CUTOUT_ALPHA, - SCATTER_MIP_ALPHA_SCALE, - ); - // The authored material is owned too: the batch draws the cutout, and the GLB's own surface is - // still this asset's to hand back. - worldOwned.add(cutout); - return { material: cutout, owned: [material, cutout] }; -} - -/** - * Every drawable part of a loaded model: one per `Mesh`, each with its shape, its surface, and - * where it sits relative to the model root. - * - * A GLB authored as a tree — bark `OPAQUE`, needles `BLEND` — is one node carrying one mesh with - * two primitives, which three loads as a `Group` of one child `Mesh` per primitive. Taking only the - * first of those drew 62k bare trunks, so every one of them is a part and each keeps its own - * material. - * - * `SkinnedMesh` is skipped deliberately: its vertices are posed per frame from a skeleton, and one - * `InstancedMesh` of the geometry would draw the rest pose once per placement. `Points` and - * `LineSegments` are not `Mesh`es and are skipped with them. - */ -function renderableParts( - model: Object3D, - surfaceFor: (material: Material) => { readonly key: string; readonly material: Material }, -): readonly IAssetPart[] { - model.updateMatrixWorld(true); - const rootInverse = new Matrix4().copy(model.matrixWorld).invert(); - const local = new Matrix4(); - const parts: IAssetPart[] = []; - model.traverse((object: Object3D) => { - if (object instanceof SkinnedMesh || !(object instanceof Mesh)) return; - const material = Array.isArray(object.material) ? object.material[0] : object.material; - if (object.geometry === undefined || material === undefined) return; - const surface = surfaceFor(material); - parts.push({ - geometry: object.geometry, - local: local.multiplyMatrices(rootInverse, object.matrixWorld).clone(), - material: surface.material, - surface: surface.key, - }); - }); - return parts; -} - -function disposeModel(model: Object3D): number { - let failed = 0; - model.traverse((object: Object3D) => { - if (!(object instanceof Mesh)) return; - if (release(object.geometry)) failed += 1; - const materials: Material[] = Array.isArray(object.material) - ? object.material - : [object.material]; - // A world-owned surface is one chunk's proxy material, and the next chunk's teardown would - // otherwise release a material this world's remaining proxies are still drawing with. It is - // released with the world that owns it; see `WorldCells#dispose`. - for (const material of materials) - if (!worldOwned.has(material) && release(material)) failed += 1; - }); - return failed; -} - -/** What one chunk's merge did, for `TN_WORLD_CHUNK_MERGE`. */ -interface IChunkMerge { - /** Meshes the chunk held before, and the ones its subtree submits after. */ - readonly meshes: number; - readonly draws: number; - /** - * Bytes the merged groups hold: exactly what the first draw of those meshes uploads, and the - * number this merge exists to keep small. - */ - readonly bytes: number; - /** Instanced meshes baked into a material's group, and the ones left instanced. */ - readonly expanded: number; - readonly keptInstanced: number; - /** Teardowns of the consumed shapes that threw, for the caller to count. */ - readonly failed: number; - /** Shadow-only proxies, per side for merged and retained parts; 0 when nothing casts. */ - readonly shadowDraws: number; - /** - * The opaque meshes a proxy covers. They keep drawing the main pass and keep receiving, and - * they stop casting: the depth pass reads positions, not materials, so it reads the proxy instead. - */ - readonly shadowCovered: readonly Mesh[]; -} - -function meshesOf(root: Object3D): number { - let meshes = 0; - root.traverse((object) => { - if ((object as Mesh).isMesh === true) meshes += 1; - }); - return meshes; -} - -/** - * The one surface each `side` of a chunk shadow proxy draws with, world-wide. - * - * A depth pass cannot tell one material from another: it reads `side`, `alphaTest` and position, - * and a proxy covers only opaque, non-alphaTest meshes, so every covered material with the same - * `side` produces the same shadow pipeline. Borrowing one per covered group built a new one for - * every chunk that loaded — 23 shadow draws over 125 ms and a 18 ms worst case on the walk this - * measured, each one compiling a pipeline the one before it had already proven was identical. The - * first covered material seen for a side is the world's, and every later proxy of that side borrows - * it, so a world pays at most one shadow pipeline per side over its whole life. - * - * The materials are the world's own, so `disposeModel` must not release them with the chunk that - * happened to contribute them. - */ -const worldOwned = new WeakSet(); - -/** - * A depth pass cannot tell one material from another, so a chunk's shadow bill is its material - * count, not its triangle count: ~100 shadow draws a level render on the map, each a per-material - * mesh walked through the light's frustum. Positions are all it needs, and a position-only geometry - * grouped by `side` — the one thing a depth material does read — collapses that bill to one draw per - * side. - * - * Every proxy sits on both caster halves: covered originals no longer cast, so choosing - * key-wide scatter must retain the buildings, towers and fences represented by the proxy. - * Either way it is counted by the same `#probe` window test as the scatter caster - * clusters, so a level whose window does not reach the chunk drops it for free. Nothing here decides - * how anything looks: the covered meshes keep the game's own materials and keep drawing the main pass - * with them, and the proxy carries the world's material for its `side` by reference — the group it - * was grouped *by* is the material's own `side`, so the depth pass reads exactly what the covered - * mesh's own depth material would have read, and this file still constructs no material. - * - * Alpha-tested and transparent meshes keep casting themselves. Retained static instances use - * separate per-side proxies, so their per-part gates do not change the existing merged proxies. - */ -function buildChunkShadowProxies( - chunk: Object3D, - merged: readonly Mesh[], - label: string, - proxyMaterials: Map, - selectParts = false, -): { readonly covered: Mesh[]; readonly proxies: Mesh[] } { - const sides = new Map(); - for (const mesh of merged) { - const material = mesh.material; - if (Array.isArray(material) || material.transparent === true || material.alphaTest > 0) - continue; - const side = material.side; - const group = sides.get(side); - if (group === undefined) sides.set(side, { material, meshes: [mesh] }); - else group.meshes.push(mesh); - } - const covered: Mesh[] = []; - const proxies: Mesh[] = []; - for (const [side, group] of sides) { - // Three's sides occupy 0..2; retained sources have their own compatible material pool. - const key = side + (selectParts ? 3 : 0); - const held = proxyMaterials.get(key); - const material = held ?? group.material; - if (held === undefined) { - proxyMaterials.set(key, material); - worldOwned.add(material); - } - const proxy = new Mesh(shadowProxyGeometry(group.meshes, label, chunk), material); - if (selectParts) selectChunkShadowParts(proxy, group.meshes); - proxy.name = `${CHUNK_NAME}-shadow`; - // The origin a draw of this mesh is counted under (`RenderPassBudget`). A proxy on the caster - // layers never reaches the main pass, so this only says anything where the world's own cull - // left one on layer 0 — and it is written here rather than inferred, so the tag cannot rot. - proxy.userData.tnDrawSource = "proxies"; - proxy.layers.set(VIRTUAL_SHADOW_CASTER_LAYER); - proxy.layers.enable(VIRTUAL_SHADOW_WIDE_CASTER_LAYER); - proxy.castShadow = true; - proxy.receiveShadow = false; - chunk.add(proxy); - markStatic(proxy); - proxies.push(proxy); - for (const mesh of group.meshes) { - mesh.castShadow = false; - covered.push(mesh); - } - } - return { covered, proxies }; -} - -/** - * One position-only geometry holding every covered mesh's vertices, indices preserved. - * - * Read through the attribute's own accessors, because a cooked model's positions can be quantized - * and interleaved (`KHR_mesh_quantization`) and the raw array behind them is not what three would - * have drawn. One pass at load time; the result is a depth buffer's whole input. - */ -/** - * One mesh's vertices, placed into `positions` from `vertexAt`. - * - * A merged mesh's positions came out of `mergeByMaterial` as one plain float array, so that is three - * reads, three multiplies and three writes per vertex. The arithmetic is three's, term for term and - * including `applyMatrix4`'s homogeneous divide, so a proxy holds the vertices it held before. A - * retained source can still be quantized and interleaved (`KHR_mesh_quantization`), and it takes the - * accessor loop, which is what that arithmetic resolves to. - */ -function placeVertices( - position: BufferAttribute, - matrix: Matrix4, - positions: Float32Array, - vertexAt: number, -): void { - if ( - position.array instanceof Float32Array && - !position.normalized && - !("isInterleavedBufferAttribute" in position) - ) { - const values = position.array as Float32Array; - const e = matrix.elements; - for (let index = 0; index < position.count; index += 1) { - const x = values[index * 3] as number; - const y = values[index * 3 + 1] as number; - const z = values[index * 3 + 2] as number; - const w = 1 / (e[3] * x + e[7] * y + e[11] * z + e[15]); - const out = (vertexAt + index) * 3; - positions[out] = (e[0] * x + e[4] * y + e[8] * z + e[12]) * w; - positions[out + 1] = (e[1] * x + e[5] * y + e[9] * z + e[13]) * w; - positions[out + 2] = (e[2] * x + e[6] * y + e[10] * z + e[14]) * w; - } - return; - } - const vertex = new Vector3(); - for (let index = 0; index < position.count; index += 1) { - vertex.fromBufferAttribute(position, index).applyMatrix4(matrix); - vertex.toArray(positions, (vertexAt + index) * 3); - } -} - -function shadowProxyGeometry( - meshes: readonly Mesh[], - label: string, - chunk: Object3D, -): BufferGeometry { - chunk.updateMatrixWorld(true); - const toRoot = chunk.matrixWorld.clone().invert(); - const place = new Matrix4(); - const instance = new Matrix4(); - const matrix = new Matrix4(); - let vertices = 0; - let drawn = 0; - for (const mesh of meshes) { - const position = mesh.geometry.getAttribute("position"); - if (position === undefined) - throw new Error(`Chunk shadow proxy (${label}): a merged mesh carries no position.`); - const copies = mesh instanceof InstancedMesh ? mesh.count : 1; - const total = mesh.geometry.getIndex()?.count ?? position.count; - const start = Math.min(total, Math.max(0, mesh.geometry.drawRange.start)); - vertices += position.count * copies; - drawn += Math.max(0, Math.min(total, start + mesh.geometry.drawRange.count) - start) * copies; - } - const positions = new Float32Array(vertices * 3); - const indices = new Uint32Array(drawn); - let vertexAt = 0; - let indexAt = 0; - const geometry = new BufferGeometry(); - for (const mesh of meshes) { - const position = mesh.geometry.getAttribute("position"); - if (position === undefined) - throw new Error(`Chunk shadow proxy (${label}): a merged mesh carries no position.`); - const source = mesh.geometry.getIndex(); - const total = source?.count ?? position.count; - const start = Math.min(total, Math.max(0, mesh.geometry.drawRange.start)); - const end = Math.min(total, start + mesh.geometry.drawRange.count); - const groupAt = indexAt; - place.multiplyMatrices(toRoot, mesh.matrixWorld); - for (let copy = 0; copy < (mesh instanceof InstancedMesh ? mesh.count : 1); copy += 1) { - if (mesh instanceof InstancedMesh) mesh.getMatrixAt(copy, instance); - else instance.identity(); - matrix.multiplyMatrices(place, instance); - placeVertices(position as BufferAttribute, matrix, positions, vertexAt); - for (let index = start; index < end; index += 1) - indices[indexAt++] = (source?.getX(index) ?? index) + vertexAt; - vertexAt += position.count; - } - geometry.addGroup(groupAt, indexAt - groupAt); - } - geometry.setAttribute("position", new BufferAttribute(positions, 3)); - geometry.setIndex(new BufferAttribute(indices, 1)); - geometry.computeBoundingSphere(); - return geometry; -} - -/** Keep retained sources' visibility, frustum and texel decisions on each level's single draw. */ -function selectChunkShadowParts(proxy: Mesh, sources: readonly Mesh[]): void { - const caster = proxy as Mesh & { chunkShadowProxy?: boolean; casterMinDiameter?: number }; - caster.chunkShadowProxy = true; - // The source frustums below decide the exact inputs; the union must not veto that decision. - proxy.frustumCulled = false; - const geometry = proxy.geometry; - const complete = ((geometry.getIndex() as BufferAttribute).array as Uint32Array).slice(); - const levels = new Map(); - const disposeLevels = (event: { target: unknown }) => { - const geometries = new Set([geometry]); - for (const level of levels.values()) geometries.add(level.geometry); - for (const held of geometries) held.removeEventListener("dispose", disposeLevels); - for (const held of geometries) if (held !== event.target) held.dispose(); - levels.clear(); - }; - geometry.addEventListener("dispose", disposeLevels); - const frustum = new Frustum(); - const projection = new Matrix4(); - const sphere = new Sphere(); - proxy.onBeforeRender = function (_renderer, _scene, camera) { - let level = levels.get(camera); - if (level === undefined) { - if (levels.size === 3) { - // ponytail: three resident camera buffers; extra cameras reuse the oldest slot. - const oldest = levels.keys().next().value as Camera; - level = levels.get(oldest) as { geometry: BufferGeometry; selected: Uint8Array }; - levels.delete(oldest); - level.selected.fill(255); - } else { - const perLevel = levels.size === 0 ? geometry : new BufferGeometry(); - if (perLevel !== geometry) { - perLevel.setAttribute("position", geometry.getAttribute("position")); - perLevel.setIndex(new BufferAttribute(new Uint32Array(complete.length), 1)); - perLevel.boundingSphere = geometry.boundingSphere; - perLevel.addEventListener("dispose", disposeLevels); - } - level = { geometry: perLevel, selected: new Uint8Array(sources.length).fill(255) }; - } - levels.set(camera, level); - } - // Three fetches the render object after this hook and detects geometry identity changes, - // even when different cameras share a render object. Exact projection stand-ins use `this`. - this.geometry = level.geometry; - const index = level.geometry.getIndex() as BufferAttribute; - const selected = level.selected; - frustum.setFromProjectionMatrix( - projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse), - camera.coordinateSystem, - camera.reversedDepth, - ); - let changed = false; - let count = 0; - for (let part = 0; part < sources.length; part += 1) { - const source = sources[part] as InstancedMesh; - if (source.boundingSphere === null) source.computeBoundingSphere(); - sphere.copy(source.boundingSphere as Sphere).applyMatrix4(source.matrixWorld); - let visible = true; - for (let node: Object3D | null = source; node !== null; node = node.parent) - if (!node.visible) visible = false; - const draws = - visible && - sphere.radius * 2 >= (caster.casterMinDiameter ?? 0) && - (!source.frustumCulled || frustum.intersectsObject(source)); - const next = draws ? 1 : 0; - if (selected[part] !== next) changed = true; - selected[part] = next; - if (draws) count += geometry.groups[part]?.count ?? 0; - } - if (changed) { - let at = 0; - for (let part = 0; part < sources.length; part += 1) { - const range = geometry.groups[part]; - if (selected[part] !== 1 || range === undefined) continue; - (index.array as Uint32Array).set( - complete.subarray(range.start, range.start + range.count), - at, - ); - at += range.count; - } - index.needsUpdate = true; - } - level.geometry.setDrawRange(0, count); - }; -} - -/** - * One chunk's static meshes merged by material, in place, before the chunk is added. - * - * A hand-placed chunk is a bridge or a yard, not a prop: its mesh nodes are packed a few dozen to a - * few hundred per file over a handful of materials, and each one is its own draw — in the main pass - * and again in every shadow level that redraws it. Merging by material is what the cook already did - * for repeated meshes (hence the `InstancedMesh`es in a loaded chunk), so this is the same fix one - * step further out: one draw per material per chunk instead of one per node. - * - * Nothing here decides how anything looks. The merged mesh draws the game's own material and holds - * the geometry exactly as authored, baked into the chunk root's space; what changes is how many - * calls it takes to draw. `mergeByMaterial` does the bake and owns the refusals, so a group it - * cannot merge (a material whose meshes disagree about `uv`) leaves the whole chunk as it was and is - * counted as a failure — a chunk never disappears over a draw-call saving. - * - * Left alone: a skinned mesh (posed per frame), one carrying morph targets (the bake drops them), a - * multi-material mesh (one draw per material already), and anything carrying a baked AutoLOD chain, - * whose own `ModelLod` swaps its geometry and would be swapped for nothing. No chunk node is - * animation-targeted: the chunk path takes `gltf.scene` and never a clip list. - * - * `disposeSources` says who owns the shapes the bake consumed. The merged buffers are the chunk's - * own and are released with it on eviction; a shape nothing draws any more is released here, unless - * the loader cache still holds it — `createAssetLoader` keys models by path and hands the same scene - * back on the next load, so a cached shape is not this world's to tear down. A geometry a kept mesh - * still draws is never released either way. - * - * `castShadow` says the chunk's shadow bill exists at all — the same answer the caller hands the - * chunk's nodes — and only then is it worth one position-only proxy per `side`. See - * `buildChunkShadowProxies`, which draws every side of every chunk with the world's one material for - * that side. - * - * @returns what the merge did, or `undefined` when the bake refused the whole chunk. - */ -function mergeChunk( - chunk: Object3D, - maxTriangles: number, - disposeSources: boolean, - castShadow: boolean, - proxyMaterials: Map, -): IChunkMerge | undefined { - chunk.updateMatrixWorld(true); - const meshes: Mesh[] = []; - const skipped = new Set(); - let instanced = 0; - chunk.traverse((object) => { - const mesh = object as Mesh & { isInstancedMesh?: boolean }; - if (mesh.isMesh !== true) return; - meshes.push(mesh); - // A chain is baked into the geometry the mesh draws, so a merged buffer would carry a `ModelLod` - // that has nothing to swap: the mesh keeps its own geometry and its own level selection. - if (lodChainOf(mesh.geometry) !== undefined) { - skipped.add(mesh); - return; - } - if (Object.keys(mesh.geometry.morphAttributes).length > 0) { - skipped.add(mesh); - return; - } - if (mesh.isInstancedMesh === true) instanced += 1; - }); - const before = meshes.length; - let result: Mesh[]; - try { - result = mergeByMaterial(chunk, { - expandInstancedUnderTriangles: maxTriangles, - label: `world chunk ${chunk.name}`, - maxGroupVertices: CHUNK_MERGE_MAX_VERTICES, - skip: (mesh) => skipped.has(mesh), - }); - } catch { - return undefined; - } - const kept = new Set(skipped); - let keptInstanced = 0; - for (const mesh of result) { - if ((mesh as Mesh & { isInstancedMesh?: boolean }).isInstancedMesh !== true) continue; - kept.add(mesh); - keptInstanced += 1; - } - const held = new Set(); - for (const mesh of kept) held.add(mesh.geometry); - let failed = 0; - // Only what the bake consumed leaves its place; a kept mesh draws the same either way, so it is - // not re-parented and the chunk's authored hierarchy survives around the merged groups. - for (const mesh of meshes) { - if (kept.has(mesh)) continue; - if (disposeSources && !held.has(mesh.geometry) && release(mesh.geometry)) failed += 1; - mesh.removeFromParent(); - } - for (const mesh of result) - if (!(mesh as Mesh & { isInstancedMesh?: boolean }).isInstancedMesh === true) chunk.add(mesh); - // Only what the bake produced is covered: a kept mesh was never merged, so it still holds its own - // per-material draw and its own materials the proxy knows nothing about. - const shadow = castShadow - ? buildChunkShadowProxies( - chunk, - result.filter((mesh) => !kept.has(mesh)), - `world chunk ${chunk.name}`, - proxyMaterials, - ) - : undefined; - // AutoLOD and morphs remain their own draws. Only unmodified static instance silhouettes qualify. - const toRoot = chunk.matrixWorld.clone().invert(); - const retained = castShadow - ? buildChunkShadowProxies( - chunk, - [...kept].filter( - (mesh) => - mesh instanceof InstancedMesh && - mesh.count > 0 && - mesh.layers.isEnabled(0) && - lodChainOf(mesh.geometry) === undefined && - Object.keys(mesh.geometry.morphAttributes).length === 0 && - mesh.morphTexture === null && - mesh.customDepthMaterial === undefined && - mesh.customDistanceMaterial === undefined && - mesh.onBeforeRender === Object3D.prototype.onBeforeRender && - !Array.isArray(mesh.material) && - !displacesVertices(mesh.material) && - mesh.material.visible && - mesh.material.allowOverride && - mesh.material.onBeforeCompile === Material.prototype.onBeforeCompile && - Reflect.get(mesh.material, "isShaderMaterial") !== true && - mesh.material.shadowSide === null && - (mesh.material.clippingPlanes?.length ?? 0) === 0 && - Reflect.get(mesh.material, "vertexNode") == null && - Reflect.get(mesh.material, "depthNode") == null && - Reflect.get(mesh.material, "castShadowNode") == null && - Reflect.get(mesh.material, "wireframe") !== true && - Reflect.get(mesh.geometry, "indirect") == null && - Reflect.get(mesh, "casterInstanceScale") == null && - new Matrix4().multiplyMatrices(toRoot, mesh.matrixWorld).determinant() >= 0, - ), - `world chunk ${chunk.name} retained`, - proxyMaterials, - true, - ) - : undefined; - return { - bytes: mergedBytes(result, kept), - draws: meshesOf(chunk), - expanded: instanced - keptInstanced, - failed, - keptInstanced, - meshes: before, - shadowCovered: [...(shadow?.covered ?? []), ...(retained?.covered ?? [])], - shadowDraws: (shadow?.proxies.length ?? 0) + (retained?.proxies.length ?? 0), - }; -} - -/** - * Bytes the bake's own meshes hold, indices included: the upload their first draw would have paid. - * A kept mesh is not one of them — it was never merged, and it draws the same either way. - */ -function mergedBytes(merged: readonly Mesh[], kept: ReadonlySet): number { - let bytes = 0; - for (const mesh of merged) { - if (kept.has(mesh)) continue; - for (const attribute of Object.values(mesh.geometry.attributes)) - bytes += attribute.array.byteLength; - const index = mesh.geometry.getIndex(); - if (index !== null) bytes += index.array.byteLength; - } - return bytes; -} - -/** - * How many groups the main record is split into. A record walks every draw in its group, so a mint - * on a streaming walk costs the draws of every shard it touches: Machinefall's ~200 recorded main - * draws re-recorded as one group put ~290 traversed draws in a frame. Eight shards still read 145, - * because one asset mints several keys (levels, parts) at once and they land in two or three shards; - * thirty-two keep a frame that touches several to a few dozen draws. A replay is one - * `executeBundles` per group, so a shard costs far less than the draws it stops re-walking. - */ -const MAIN_BUNDLE_SHARDS = 32; - -/** The shard a key lands in, from its name (FNV-1a), so a key keeps its group for its whole life. */ -function mainShard(key: string): number { - let hash = 0x811c9dc5; - for (let index = 0; index < key.length; index += 1) { - hash ^= key.charCodeAt(index); - hash = Math.imul(hash, 0x01000193); - } - return (hash >>> 0) % MAIN_BUNDLE_SHARDS; -} - -/** - * The value `NodeUpdateType.RENDER` has, read as text so this file needs no import for it: a node - * whose `updateBefore` runs on every render is a node whose work happens inside the draw that - * recorded it, which is the whole test. - */ -const PER_RENDER_UPDATE = "render"; - -/** - * Whether a node graph copies or renders the framebuffer, which a bundle encoder cannot be asked for. - * - * Every such node does its work in `updateBefore` and every one of them touches the *current* render - * pass: `ViewportTextureNode` copies the bound framebuffer into a texture, and transmission, an - * `RTTNode`, a `ReflectorNode` or a `PassNode` renders into one. Recorded into a bundle, the current - * pass is a `GPURenderBundleEncoder`, which has no `end()` to close and no framebuffer to read, so - * the call throws `currentPass.end is not a function` from inside the record and takes the whole - * bundle's frame with it. - * - * Structural, over own properties and plain sub-objects, because a material's slots are node graphs - * rather than one node: a `viewportSharedTexture()` under an arithmetic chain is still this. What it - * cannot see is a node hidden inside an `Fn` body, which is how three keeps its own transmission - * sampler — hence `transmission` is asked of the material itself as well. - */ -interface IBundleGraphSnapshot { - readonly node: object; - readonly keys: readonly string[]; - readonly links: readonly unknown[]; - readonly isNode: unknown; - readonly beforeType: unknown; - readonly before: string; - readonly version: unknown; - readonly terminal: boolean; - readonly childReader: unknown; - readonly children: readonly object[] | undefined; -} -interface IBundleGraphCapture { - readonly rows: IBundleGraphSnapshot[]; - links: number; - overflow: boolean; -} -interface IBundleMaterialCheck { - readonly safe: boolean; - readonly revision: number; - readonly version: number; - readonly transparent: boolean; - readonly visible: boolean; - readonly transmission: unknown; - readonly rows: readonly IBundleGraphSnapshot[]; -} - -/** Own-link and installed-Node child snapshots avoid inherited TSL method scans. Deep unversioned - * edits still invalidate: version/root identity alone cannot certify a mutable node graph. A large - * graph uses the uncached safety walk, and conservatively rerecords rather than reusing a verdict. */ -class BundleSurfaceSafety { - #checks = new WeakMap(); - #revision = 0; - readonly stats = { checks: 0, refreshes: 0, validations: 0, uncached: 0 }; - - clear(): void { - this.#checks = new WeakMap(); - } - - test(material: Material): IBundleMaterialCheck { - this.stats.checks++; - const previous = this.#checks.get(material); - if ( - previous !== undefined && - previous.version === material.version && - previous.transparent === material.transparent && - previous.visible === material.visible && - previous.transmission === Reflect.get(material, "transmission") && - previous.rows.every(graphSnapshotCurrent) - ) { - this.stats.validations++; - return previous; - } - this.stats.refreshes++; - const capture: IBundleGraphCapture = { rows: [], links: 0, overflow: false }; - const checked: IBundleMaterialCheck = { - safe: bundleSafeMaterial(material, capture), - revision: ++this.#revision, - version: material.version, - transparent: material.transparent, - visible: material.visible, - transmission: Reflect.get(material, "transmission"), - rows: capture.rows, - }; - if (capture.overflow) { - this.stats.uncached++; - this.#checks.delete(material); - } else this.#checks.set(material, checked); - return checked; - } -} - -function graphSnapshotCurrent(row: IBundleGraphSnapshot): boolean { - const node = row.node; - const before = typeof Reflect.get(node, "updateBefore"); - if ( - Reflect.get(node, "isNode") !== row.isNode || - Reflect.get(node, "updateBeforeType") !== row.beforeType || - before !== row.before || - Reflect.get(node, "version") !== row.version - ) - return false; - if (row.terminal) return true; // Still a framebuffer reader, whatever its other children do. - // TSL prototypes carry thousands of enumerable chaining methods, which are not child links. - // Own slots stay cheap; Three's actual child reader witnesses inherited container links below. - const keys = bundleGraphKeys(node); - if (keys.length !== row.keys.length) return false; - for (let index = 0; index < keys.length; index++) { - const key = keys[index] as string; - if (row.keys[index] !== key) return false; - const value = Reflect.get(node, key); - if ((value !== null && typeof value === "object" ? value : undefined) !== row.links[index]) - return false; - } - const reader = bundleGraphChildReader(node); - if (reader !== row.childReader) return false; - const children = readBundleGraphChildren(node, reader); - return ( - children !== undefined && - row.children !== undefined && - children.length === row.children.length && - children.every((child, index) => child === row.children?.[index]) - ); -} - -/** Three reads non-enumerable own Node/Material slots, but never inherited TSL chain methods. */ -function bundleGraphKeys(node: object): string[] { - return Reflect.get(node, "isNode") === true || Reflect.get(node, "isMaterial") === true - ? Object.getOwnPropertyNames(node) - : Object.keys(node); -} -function bundleGraphChildReader(node: object): unknown { - return Reflect.get(node, "isNode") === true ? Reflect.get(node, "getChildren") : undefined; -} -function readBundleGraphChildren(node: object, reader: unknown): readonly object[] | undefined { - if (reader === undefined) return []; - if (typeof reader !== "function") return undefined; - try { - const result: unknown = reader.call(node); - if ( - result === null || - typeof result !== "object" || - typeof Reflect.get(result, Symbol.iterator) !== "function" - ) - return undefined; - const children: object[] = []; - for (const child of result as Iterable) { - if ( - children.length >= 8192 || - child === null || - typeof child !== "object" || - Reflect.get(child, "isNode") !== true - ) - return undefined; - children.push(child); - } - return children; - } catch { - return undefined; - } -} -function captureBundleGraph( - node: object, - terminal: boolean, - capture: IBundleGraphCapture, - childReader: unknown, - children: readonly object[] | undefined, -): readonly unknown[] { - const entries = terminal - ? [] - : bundleGraphKeys(node).map((key) => [key, Reflect.get(node, key)] as const); - capture.links += entries.length + (children?.length ?? 0); - if (children === undefined) capture.overflow = true; - if (capture.rows.length >= 1024 || capture.links > 8192) capture.overflow = true; - if (!capture.overflow) - capture.rows.push({ - node, - terminal, - childReader, - children, - keys: entries.map(([key]) => key), - links: entries.map(([, value]) => - value !== null && typeof value === "object" ? value : undefined, - ), - isNode: Reflect.get(node, "isNode"), - beforeType: Reflect.get(node, "updateBeforeType"), - before: typeof Reflect.get(node, "updateBefore"), - version: Reflect.get(node, "version"), - }); - return entries.map(([, value]) => value); -} - -function samplesFramebuffer( - node: unknown, - seen: Set, - capture?: IBundleGraphCapture, -): boolean { - // Pixels and vertex data hold no node, and Object.values would copy every element of them. - if (node === null || typeof node !== "object" || ArrayBuffer.isView(node) || seen.has(node)) - return false; - seen.add(node); - const candidate = node as { - readonly isNode?: boolean; - readonly updateBefore?: unknown; - readonly updateBeforeType?: unknown; - }; - if ( - candidate.isNode === true && - typeof candidate.updateBefore === "function" && - candidate.updateBeforeType === PER_RENDER_UPDATE - ) { - if (capture !== undefined) captureBundleGraph(node, true, capture, undefined, []); - return true; - } - // getChildren owns Three's non-enumerable, dictionary and sparse-array child semantics. Its - // ordered identities are a live witness, including prototype edits with unchanged own keys. - const reader = bundleGraphChildReader(node); - const children = readBundleGraphChildren(node, reader); - const values = - capture === undefined - ? bundleGraphKeys(node).map((key) => Reflect.get(node, key)) - : captureBundleGraph(node, false, capture, reader, children); - // Malformed/oversized child output retains the live source path. The stored witness is capped; - // Three's eager getChildren implementation can still scan an authored array before yielding. - if (children === undefined) return true; - for (const value of [...values, ...children]) { - if (value === null || typeof value !== "object") continue; - if (samplesFramebuffer(value, seen, capture)) return true; - } - return false; -} - -/** - * Whether one mesh's draw can be recorded into a bundle and replayed from it. - * - * A bundle is an encoder, not a render pass: three fixes its render list and its draw order when it - * records it, and every replay runs the encoded commands with no pass of its own to ask anything of. - * So a mesh is bundle-safe only when nothing about its draw needs a live pass — the one question - * {@link samplesFramebuffer} and the checks below both ask, of the node graph and of the object. - * - * - **The framebuffer.** A viewport or screen-space refraction sample, transmission, a render target, - * a reflector: `samplesFramebuffer`, plus `transmission` itself, which three's own lighting model - * reaches through an `Fn` body no structural walk can see. - * - **Per-frame order.** A transparent mesh is sorted against its neighbours every frame; a record - * froze that order when it was made, so a bundled one draws in the order it happened to be in. - * - **Per-object hooks.** `onBeforeRender`/`onAfterRender` are the two places a game reads the camera - * and the frame for a draw, and a replay runs them once at record time and never again. The - * engine's own borrow — the prewarm counting its batch's first submitted draw — is bookkeeping - * about the draw rather than a claim on the object, so it does not refuse; `isEngineRenderHook` is - * what tells them apart, the same answer `projection-plan.ts` gives. - * - **Per-frame vertices.** A skinned or morphed mesh's positions are posed per frame from bones the - * record never reads, so its encoded vertices are its rest pose forever. - * - * A mesh that fails this stays on the per-object path — the path every chunk drew before bundles - * existed — so a refusal costs the draw it was already paying and nothing else. - */ -function bundleSafe(mesh: Mesh): boolean { - // AutoLOD changes geometry and joined membership after recording; replay cannot freeze either. - if (lodChainOf(mesh.geometry) !== undefined) return false; - if (ownHook(mesh, "onBeforeRender")) return false; - if (ownHook(mesh, "onAfterRender")) return false; - if ((mesh as Mesh & { readonly isSkinnedMesh?: boolean }).isSkinnedMesh === true) return false; - if (mesh.morphTargetInfluences !== undefined) return false; - return (Array.isArray(mesh.material) ? mesh.material : [mesh.material]).every((material) => - bundleSafeMaterial(material), - ); -} - -/** - * Whether this mesh carries a render hook of its own that is the game's, which a record freezes. - * - * `Object.hasOwn`, not the prototype comparison: the engine installs its own hook as an own property - * on the very batches a prewarm borrowed, and refusing those would keep every batch of a streamed - * world on the per-object path. - */ -function ownHook(mesh: Mesh, name: "onAfterRender" | "onBeforeRender"): boolean { - return Object.hasOwn(mesh, name) && isEngineRenderHook(mesh[name]) === false; -} - -/** The material half of {@link bundleSafe}: nothing it draws may need a pass it was recorded in. */ -export function bundleSafeMaterial( - material: Material | undefined, - capture?: IBundleGraphCapture, -): boolean { - if (material === undefined) return true; - if (material.transparent === true) return false; - // Read structurally: a `MeshPhysicalMaterial` always has the number and nothing else does, and - // three's transmission sampler is not a node slot, so `transmission` is the only way to see it. - const transmission = Reflect.get(material, "transmission"); - if (typeof transmission === "number" && transmission > 0) return false; - return samplesFramebuffer(material, new Set(), capture) === false; -} - -/** Every mesh a prepared chunk draws through the main pass, in traversal order. */ -function chunkDraws(chunk: Object3D): Mesh[] { - const draws: Mesh[] = []; - chunk.traverse((node) => { - const mesh = node as Mesh; - if (mesh.isMesh === true && node.layers.isEnabled(0)) draws.push(mesh); - }); - return draws; -} - -/** - * One empty batch per (level, part), so a placement is filtered and drawn together and an emptied - * level or a part nobody placed costs one missing mesh rather than the whole cell's run. - */ -function newBatches(asset: IAssetState): InstancedBatch[][] { - const batches: InstancedBatch[][] = []; - for (const parts of asset.levels) { - const levelBatches: InstancedBatch[] = []; - for (const part of parts) - levelBatches.push(new InstancedBatch({ geometry: part.geometry, material: part.material })); - batches.push(levelBatches); - } - return batches; -} - -/** How many meshes a build will publish: one per (level, part). */ -function publishCount(batches: readonly InstancedBatch[][]): number { - let total = 0; - for (const levelBatches of batches) total += levelBatches.length; - return total; -} - -/** - * One empty bounds box per `(level, part)`, beside the batches `#addPlacements` measures. Written - * as a loop because this file is held to a rule that the array method's name is a material's - * texture property. - */ -function newBoxes(batches: readonly InstancedBatch[][]): Box3[][] { - const boxes: Box3[][] = []; - for (const levelBatches of batches) { - const level: Box3[] = []; - for (const _batch of levelBatches) level.push(new Box3()); - boxes.push(level); - } - return boxes; -} - -/** - * The distance a `maxDistance` prop actually culls at, one eighth short of itself: the slack - * `#addPlacements` leaves, so a follow point that has not moved a whole eighth of the cull distance - * cannot have culled a placement it had not already culled. - */ -function cullDistance(maxDistance: number | undefined): number | undefined { - return maxDistance === undefined ? undefined : maxDistance - maxDistance / 8; -} - -/** - * The levels one asset is drawn at, and the distances its batching can be crossed at. - * - * Level 0 is the asset's own `glb` and never switches; `lods[i - 1].distance` is where an instance - * that far out draws `lods[i]` instead. A `lods` entry at or beyond the cull distance is dropped: - * an instance that far out is culled, so its shape can never be drawn. `gates` is every distance - * the level or cull answer changes at — `threshold` is the nearest of them, so a follow point that - * has moved an eighth of it may have moved an instance either way. - */ -function assetLevels(definition: IWorldAsset): { - readonly distances: readonly number[]; - readonly gates: readonly number[]; - readonly glbs: readonly string[]; - readonly threshold: number | undefined; -} { - const maxDistance = definition.maxDistance; - const distances: number[] = [0]; - const glbs: string[] = [definition.glb]; - const gates: number[] = []; - for (const lod of definition.lods ?? []) { - // A level at or beyond the cull distance is never drawn: an instance that far out is gone by - // the time the level would take over, so its GLB is never asked for. - if (maxDistance !== undefined && lod.distance >= maxDistance) continue; - distances.push(lod.distance); - glbs.push(lod.glb); - gates.push(lod.distance); - } - const cull = cullDistance(maxDistance); - if (cull !== undefined) gates.push(cull); - return { - distances, - gates, - glbs, - threshold: gates.length === 0 ? undefined : Math.min(...gates), - }; -} - -/** - * The batched shape of one definition, as the loader will actually fetch it: the url the manifest - * gives its `glb`, the url and switch distance of every `lods` entry in order, its `maxDistance` and - * its bounds. Two definitions that agree on all of it are the same draw — same geometry, same - * materials, same levels, same gates — so one model serves both and their placements batch together. - * - * `undefined` when the loader cannot resolve a path at all: a path the manifest does not list makes - * `resolve` throw, and the load that asks for it will say so. Such a definition never aliases, - * which is the same behaviour it has today. - */ -async function assetSignature( - assets: IAssetLoader, - definition: IWorldAsset, - logicalBase: string, -): Promise { - const url = async (glb: string): Promise => { - const candidates = await assets.resolve(resolveRelative(logicalBase, glb)); - return candidates[0] ?? glb; - }; - try { - const parts = [await url(definition.glb)]; - for (const lod of definition.lods ?? []) - parts.push(`${await url(lod.glb)}@${String(lod.distance)}`); - parts.push( - definition.maxDistance === undefined ? "" : String(definition.maxDistance), - definition.bounds.min.join(","), - definition.bounds.max.join(","), - ); - return parts.join("|"); - } catch { - return undefined; - } -} - -/** - * `alias id -> canonical id` for every definition that is another name for the same cooked model, - * which is what a 374-asset package with 273 duplicates is: one model exported under many object - * names, cooked once and fetched once. - * - * The canonical is the lexicographically smallest id of the group, so it does not depend on the - * order the exporter happened to write them in. An empty map — every definition its own model, or a - * package whose paths resolve to nothing — leaves the whole runtime exactly as it is. - */ -async function assetAliases( - assets: IAssetLoader, - manifest: IWorldPackage, - logicalBase: string, -): Promise> { - const canonicalBySignature = new Map(); - const aliases = new Map(); - // Sorted, so the first id to claim a signature is the smallest one in its group. - for (const id of Object.keys(manifest.assets).sort()) { - const definition = manifest.assets[id]; - if (definition === undefined) continue; - const signature = await assetSignature(assets, definition, logicalBase); - if (signature === undefined) continue; - const canonical = canonicalBySignature.get(signature); - if (canonical === undefined) canonicalBySignature.set(signature, id); - else aliases.set(id, canonical); - } - return aliases; -} - -/** - * A geometry that draws `source`'s shape through an indirect record: its own object, so the record is - * this mesh's, over the same attributes and index, so no vertex buffer is copied. - */ -function indirectView( - source: BufferGeometry, - args: Parameters[0], - offset: number, -): BufferGeometry { - const view = new BufferGeometry(); - for (const [name, attribute] of Object.entries(source.attributes)) - view.setAttribute(name, attribute); - view.setIndex(source.index); - view.morphAttributes = source.morphAttributes; - view.morphTargetsRelative = source.morphTargetsRelative; - for (const group of source.groups) view.addGroup(group.start, group.count, group.materialIndex); - view.boundingBox = source.boundingBox; - view.boundingSphere = source.boundingSphere; - view.setIndirect(args, offset); - return view; -} - -/** The materials `redressMaterial` gave a mesh, disposed when the mesh is or when it is re-dressed. */ -const redressed = new WeakMap(); - -/** - * A new object drawing `geometry`, carrying everything `old` carried. - * - * Three builds an `InstancedMesh`'s instancing node when the object is first compiled, from the - * `instanceMatrix` it holds then, and keeps that node for the object's whole life — so a mesh the - * prewarm already compiled is not pointed at the GPU scene's storage buffer by writing a field onto - * it. Measured on machinefall: 3,724 indirect draws submitted in 2 s, over indirect records and - * drawn matrices that were correct at draw time, and a dressed forest that drew nothing at all, - * until every dressed mesh was a fresh object. A fresh object costs a uuid and a node build, which - * is what the prewarm paid for a mesh that was about to be replaced anyway; see `#dressGpu`. - */ -function freshMeshFor(old: InstancedMesh, geometry: BufferGeometry, count: number): InstancedMesh { - const next = new InstancedMesh(geometry, old.material, count); - next.name = old.name; - next.layers.mask = old.layers.mask; - next.castShadow = old.castShadow; - next.receiveShadow = old.receiveShadow; - next.renderOrder = old.renderOrder; - next.userData = old.userData; - next.matrix.copy(old.matrix); - next.matrixAutoUpdate = old.matrixAutoUpdate; - next.matrixWorld.copy(old.matrixWorld); - next.matrixWorldAutoUpdate = old.matrixWorldAutoUpdate; - // A dressed batch is frozen, and the freeze is keyed by the object: a replacement that did not - // re-arm it would compose its own world matrix every walk forever. - if (isStatic(old)) markStatic(next); - return next; -} - -/** - * Give a mesh a material of its own, so the node three builds for it is its own rather than a shared - * one's, and dispose those materials with the mesh. The clones share every texture; only the - * material objects are new. See `#dressGpu`. - */ -function redressMaterial(mesh: InstancedMesh): void { - for (const material of redressed.get(mesh) ?? []) material.dispose(); - const clones: Material[] = []; - for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) - clones.push(material.clone()); - mesh.material = Array.isArray(mesh.material) ? clones : (clones[0] as Material); - if (!redressed.has(mesh)) - mesh.addEventListener("dispose", () => { - for (const material of redressed.get(mesh) ?? []) material.dispose(); - redressed.delete(mesh); - }); - redressed.set(mesh, clones); -} - -/** Triangles one geometry submits; the count the `TN_WORLD_LOD_CHAIN` marker reports per level. */ -function levelTriangles(geometry: BufferGeometry): number { - const drawn = geometry.index?.count ?? geometry.getAttribute("position")?.count ?? 0; - return Math.floor(drawn / 3); -} - -/** - * Pixels one world unit covers at unit depth, from the vertical field of view and the raster height. - * The scale `ModelLod` divides an error by; see `IWorldCellsLoadOptions.autoLod`. - */ -function autoLodScale(autoLod: IWorldCellsLoadOptions["autoLod"]): number { - const fovY = autoLod?.fovY ?? DEFAULT_AUTO_LOD_FOV_Y; - const viewportHeight = autoLod?.viewportHeight ?? DEFAULT_AUTO_LOD_VIEWPORT_HEIGHT; - if (!Number.isFinite(fovY) || fovY <= 0 || fovY >= 180) - throw new Error("WorldCells autoLod.fovY must be a finite angle in (0, 180) degrees."); - if (!Number.isFinite(viewportHeight) || !(viewportHeight > 0)) - throw new Error( - "WorldCells autoLod.viewportHeight must be a positive number of raster pixels.", - ); - return viewportHeight / (2 * Math.tan((fovY * Math.PI) / 360)); -} - -/** The screen-space error budget a synthesized level is switched at, in pixels. */ -function autoLodPixelError(autoLod: IWorldCellsLoadOptions["autoLod"]): number { - const maxPixelError = autoLod?.maxPixelError ?? DEFAULT_AUTO_LOD_MAX_PIXEL_ERROR; - if (!Number.isFinite(maxPixelError) || maxPixelError <= 0) - throw new Error("WorldCells autoLod.maxPixelError must be a positive number of pixels."); - return maxPixelError; -} - -/** The adaptive LOD bias floor. A bias is only ever allowed to coarsen selection, never refine it. */ -const LOD_BIAS_MIN = 1; -/** The adaptive LOD bias ceiling, so a runaway maps to a coarser but still bounded world. */ -const LOD_BIAS_MAX = 2.5; -/** How much the bias rises per over-budget step. */ -const LOD_BIAS_RISE = 1.08; -/** How much the bias decays per comfortably-under-budget step. */ -const LOD_BIAS_DECAY = 0.95; -/** Seconds between rise or decay steps; a change is never larger than one step. */ -const LOD_BIAS_STEP_SECONDS = 0.5; -/** Below this share of the affordable budget the bias decays; between it and the budget it holds. */ -const LOD_BIAS_DECAY_SHARE = 0.6; -/** Default fraction of a 60 fps frame the main pass may take before the bias rises. */ -const DEFAULT_MAIN_GPU_SHARE = 0.5; -/** Seconds between two `TN_LOD_BIAS` lines. */ -const LOD_BIAS_MARKER_SECONDS = 1; -/** The line the adaptive loop prints when it moves the bias. */ -export const LOD_BIAS_MARKER = "TN_LOD_BIAS"; - -/** - * One launch override, read once: the environment, then the query string, then the global a test - * sets. The same three ways a debug flag is asked, but the answer is kept as text so a number can - * be parsed by the caller rather than rounded here. - */ -function launchValue(parameter: string, flag: string, globalName: string): string | undefined { - const host = globalThis as { process?: { env?: Record } } & Record< - string, - unknown - >; - const env = host.process?.env?.[flag]; - if (typeof env === "string" && env !== "") return env; - const query = globalThis.location?.search; - if (typeof query === "string") { - const value = new URLSearchParams(query).get(parameter); - if (value !== null) return value; - } - const global = host[globalName]; - if (typeof global === "number") return String(global); - return typeof global === "string" ? global : undefined; -} - -/** - * Whether a launch opts into the whole-asset impostors: off unless `?tnImpostors=1`, `TN_IMPOSTORS=1` - * or `__tnImpostors = 1` says so. Opt-in because the judged Machinefall capture of the default - * (PRD-473 AC-3, 2026-10-14) read REGRESSION — ground shadows and canopy density differ — and its - * pinned frame windows showed no frame-time gain, so a game gets the source levels it had until the - * proof passes. `impostors: true` still asks explicitly. - */ -function impostorsRequested(): boolean { - const raw = launchValue("tnImpostors", "TN_IMPOSTORS", "__tnImpostors"); - return raw === "1" || raw === "true"; -} - -/** Whether the adaptive LOD bias runs: on unless a launch pins it off. */ -function adaptiveLodRequested(): boolean { - const raw = launchValue("tnAdaptiveLod", "TN_ADAPTIVE_LOD", "__tnAdaptiveLod"); - return raw === undefined ? true : raw !== "0" && raw !== "false"; -} - -/** The main-pass GPU share: the option, then the launch override, then 0.5. */ -function mainGpuShare(option: number | undefined): number { - const raw = launchValue("tnMainGpuShare", "TN_MAIN_GPU_SHARE", "__tnMainGpuShare"); - const launched = raw === undefined ? undefined : Number(raw); - const share = option ?? launched ?? DEFAULT_MAIN_GPU_SHARE; - if (!Number.isFinite(share) || share <= 0 || share > 1) - throw new Error("WorldCells mainGpuShare must be a number in (0, 1]."); - return share; -} - -/** What a baked chain gives a batched draw: the extra levels, where they take over, what they cost. */ -interface IChainLevels { - /** Index-aligned with `distances`; index 0 is the parts the asset already had. */ - readonly levels: (readonly IAssetPart[])[]; - /** `distances[0]` is 0 and never switches; the rest are the synthesized switch distances. */ - readonly distances: number[]; - /** Triangles every level submits, all its parts together, index-aligned with `levels`. */ - readonly triangles: number[]; -} - -/** - * The extra levels an asset's baked AutoLOD chain gives a batched draw, or `undefined` when none of - * its parts carries a chain. - * - * A `ModelLod` swaps one mesh's geometry, so a world that batches thousands of placements of one - * model into a handful of instanced draws never reaches that selection: every placement drew - * LOD0, which on a 2 km map is the whole triangle bill at every distance. The chain already holds - * the reduced shapes, so this derives the levels a batch can hold and the distance each one takes - * over at — the same screen-space test `ModelLod` runs, solved for depth: - * `error * pixelsPerUnit / maxPixelError`, with each part's own registered budget. - * - * Level `i` is `chain.levels[min(i, length - 1)]` per part, so a part without a chain — and one - * whose chain ran out below `i` — keeps the shape above it. The switch distances come from the - * parts that descend the furthest, the **max** across those at each level, so the deepest chain's - * whole ladder survives. A part with a shallower chain clamps to its last level and does not set a - * switch: it cannot be drawn finer there anyway, so its error must not push the switch past the - * deepest chain's own last error and collapse the ladder to one far step. - */ -function chainDistances( - chains: readonly (ILodChain | undefined)[], - pixelsPerUnit: number, - maxPixelError: number, - maxDistance: number | undefined, -): number[] { - let deepest = 1; - for (const chain of chains) - if (chain !== undefined) deepest = Math.max(deepest, chain.levels.length); - const distances = [0]; - for (let level = 1; level < deepest; level += 1) { - let error = 0; - for (const chain of chains) { - // Only the parts that descend the furthest set the schedule. A part whose chain ran out has - // no finer shape to offer past its last level, so letting its — often terminal, and so huge — - // error gate this switch cannot buy that part any detail back. It would only deny every - // sibling the coarser level and collapse the chain to a single far switch, which is how a - // bark primitive's 2-level chain truncated a pine's 4-level needles chain to two levels. - if (chain === undefined || chain.levels.length !== deepest) continue; - error = Math.max(error, chain.errors[level] ?? 0); - } - const at = (error * pixelsPerUnit) / maxPixelError; - // A chain's errors only ascend, so a switch that does not move outward — or that lands at or - // past the cull — ends the list: every level above it is further out still, and none of them - // can be selected or drawn. That keeps the thresholds strictly increasing and inside - // `maxDistance`, exactly as `assetLevels` keeps authored ones. - if (at <= (distances[level - 1] as number)) break; - if (maxDistance !== undefined && at >= maxDistance) break; - distances.push(at); - } - return distances; -} - -/** The one test that separates an alpha-cutout foliage part from an opaque one: MASK or BLEND. */ -function isAlphaFoliage(material: Material): boolean { - return material.transparent || material.alphaTest > 0; -} - -/** One level's parts, and what they submit: the chain's shape where it has one, the one above where not. */ -function chainLevelParts( - parts: readonly IAssetPart[], - chains: readonly (ILodChain | undefined)[], - level: number, -): { readonly parts: IAssetPart[]; readonly triangles: number } { - const at: IAssetPart[] = []; - let triangles = 0; - for (const [index, part] of parts.entries()) { - const chain = chains[index]; - // A part with no chain keeps its own shape, in its own slot: the batch builder and - // `#addPlacements` index every level's parts by the same number. - const geometry = - chain === undefined - ? part.geometry - : (chain.levels[Math.min(level, chain.levels.length - 1)] as BufferGeometry); - at.push(geometry === part.geometry ? part : { ...part, geometry }); - triangles += levelTriangles(geometry); - } - return { parts: at, triangles }; -} - -/** - * The extra levels an asset's baked AutoLOD chain gives a batched draw, or `undefined` when none of - * its parts carries a chain. - * - * A `ModelLod` swaps one mesh's geometry, so a world that batches thousands of placements of one - * model into a handful of instanced draws never reaches that selection: every placement drew - * LOD0, which on a 2 km map is the whole triangle bill at every distance. The chain already holds - * the reduced shapes, so this derives the levels a batch can hold and the distance each one takes - * over at — the same screen-space test `ModelLod` runs, solved for depth: - * `error * pixelsPerUnit / maxPixelError`, over the world's own `autoLod.maxPixelError` rather than - * the budget the loader registered the chain with. See - * {@link IWorldCellsLoadOptions.autoLod} for why a batch cannot use the registered one. - * - * Level `i` is `chain.levels[min(i, length - 1)]` per part, so a part without a chain — and one - * whose chain ran out below `i` — keeps the shape above it. The switch distances come from the - * parts that descend the furthest, the **max** across those at each level, so the deepest chain's - * whole ladder survives. A part with a shallower chain clamps to its last level and does not set a - * switch: it cannot be drawn finer there anyway, so its error must not push the switch past the - * deepest chain's own last error and collapse the ladder to one far step. - */ -/** Prepared sources may have replaced attributes or cut a material section after cloning. */ -function preparedChainSafe(part: IAssetPart): boolean { - const base = part.geometry; - const chain = lodChainOf(base); - if (chain === undefined) return true; - if ( - base.drawRange.start !== 0 || - base.drawRange.count !== Number.POSITIVE_INFINITY || - base.groups.length > 0 || - part.material.transparent || - part.material.alphaTest > 0 || - displacesVertices(part.material) || - Reflect.get(part.material, "vertexNode") != null - ) - return false; - const names = Object.keys(base.attributes); - return chain.levels.every( - (level) => - Object.keys(level.attributes).length === names.length && - names.every((name) => level.getAttribute(name) === base.getAttribute(name)), - ); -} - -function chainLevels( - parts: readonly IAssetPart[], - pixelsPerUnit: number, - maxPixelError: number, - maxDistance: number | undefined, -): IChainLevels | undefined { - const chains: (ILodChain | undefined)[] = []; - for (const part of parts) chains.push(lodChainOf(part.geometry)); - const distances = chainDistances(chains, pixelsPerUnit, maxPixelError, maxDistance); - if (distances.length === 1) return undefined; - const levels: (readonly IAssetPart[])[] = [parts]; - // Level 0 leads, so the marker opens with what the asset submits today. - const triangles: number[] = [parts.reduce((sum, part) => sum + levelTriangles(part.geometry), 0)]; - for (let level = 1; level < distances.length; level += 1) { - const at = chainLevelParts(parts, chains, level); - levels.push(at.parts); - triangles.push(at.triangles); - } - return { distances, levels, triangles }; -} - -/** - * The DRAW levels an authored asset gets: every level keeps the geometry, transform and surface it - * authored, and the root LOD0's alpha-cutout parts are appended to a level that authored fewer alpha - * slots than the root carries, so no middle level loses leaves its reduced card cannot honestly - * represent. - * - * An authored reduced foliage card has no honest twin — the automatic reducer's error metric cannot - * see the holes a cutout silhouette is made of — but a level that authored such a card keeps it: - * only the slots a level carries none of fall back to the root, in the root's own per-role order, so - * a middle with one reduced card and a root with two gets its own card plus the root's second. The - * fallback parts are appended after the level's authored parts, never reordered, so each level keeps - * its own part indices and counts. Nothing is spilled: a fallback shares the root part by reference, - * so one resource is still one teardown. - */ -function coverAuthoredLevels( - levels: readonly (readonly IAssetPart[])[], -): readonly (readonly IAssetPart[])[] { - const rootAlpha = (levels[0] ?? []).filter((part) => isAlphaFoliage(part.material)); - if (rootAlpha.length === 0 || levels.length < 2) return levels; - const covered: (readonly IAssetPart[])[] = [levels[0] as readonly IAssetPart[]]; - for (const parts of levels.slice(1)) { - let authoredAlpha = 0; - for (const part of parts) if (isAlphaFoliage(part.material)) authoredAlpha += 1; - if (authoredAlpha >= rootAlpha.length) { - covered.push(parts); - continue; - } - const mapped: IAssetPart[] = [...parts]; - for (let slot = authoredAlpha; slot < rootAlpha.length; slot += 1) - mapped.push(rootAlpha[slot] as IAssetPart); - covered.push(mapped); - } - return covered; -} - -/** The bake's view of one asset level: each part's geometry, transform and surface, by reference. */ -function impostorParts(parts: readonly IAssetPart[]): IImpostorPart[] { - const staged: IImpostorPart[] = []; - for (const part of parts) - staged.push({ geometry: part.geometry, local: part.local, material: part.material }); - return staged; -} - -/** - * Model loads in flight for a streamed world. `loadAll`'s six suits a scene's one-off load; a - * world crossing new asset types as it moves spends most of each load waiting on the network and - * the transcoder workers, so more in flight keeps the ring ahead of a fast camera. - */ -const WORLD_LOAD_CONCURRENCY = 12; - -function positiveInteger(value: number, name: string): number { - if (!Number.isInteger(value) || value < 1) - throw new Error(`WorldCells ${name} must be a positive integer.`); - return value; -} - -function nonNegativeInteger(value: number, name: string): number { - if (!Number.isInteger(value) || value < 0) - throw new Error(`WorldCells ${name} must be a non-negative integer.`); - return value; -} - -/** A metre threshold, unlike the counts above: 1.5 m is the whole point of the option. */ -function positiveMetres(value: number, name: string): number { - if (!Number.isFinite(value) || value <= 0) - throw new Error(`WorldCells ${name} must be a finite number greater than 0.`); - return value; -} - -/** - * The admission budget: a positive number of milliseconds, or `Infinity` for no ceiling at all — - * which is what a game that wants one `update` to admit everything it can asks for. Zero is refused - * rather than obeyed, because a world whose budget is spent before it starts never streams at all, - * and that is never what anyone meant to write. - */ -function admissionBudgetMs(value: number): number { - if (Number.isNaN(value) || value <= 0) - throw new Error("WorldCells admissionBudgetMs must be a positive number of milliseconds."); - return value; -} - -function cellKey(x: number, z: number): string { - return `${String(x)}:${String(z)}`; -} - -interface IQueuedModelLoad { - readonly run: () => Promise; - readonly wanted: () => boolean; - readonly resolve: (model: Object3D | undefined) => void; - readonly reject: (error: unknown) => void; -} - -/** - * One bounded lane for every model load a `WorldCells` starts. Assets and chunks share it, so the - * number of `loadModel` calls in flight at once cannot grow with the number of admitted cells. - * Queued loads keep admission order, which is nearest-cell first, and one whose cell left (or - * whose asset was released) before its turn resolves `undefined` without touching the loader. - */ -class ModelLoadLimiter { - readonly #concurrency: number; - readonly #queue: IQueuedModelLoad[] = []; - #inFlight = 0; - - constructor(concurrency: number) { - this.#concurrency = concurrency; - } - - get inFlight(): number { - return this.#inFlight; - } - - get queued(): number { - return this.#queue.length; - } - - load(run: () => Promise, wanted: () => boolean): Promise { - return new Promise((resolve, reject) => { - this.#queue.push({ reject, resolve, run, wanted }); - this.#pump(); - }); - } - - #pump(): void { - while (this.#inFlight < this.#concurrency && this.#queue.length > 0) { - const job = this.#queue.shift() as IQueuedModelLoad; - if (!job.wanted()) { - job.resolve(undefined); - continue; - } - this.#inFlight += 1; - try { - job.run().then( - (model) => { - this.#inFlight -= 1; - job.resolve(model); - this.#pump(); - }, - (error: unknown) => { - this.#inFlight -= 1; - job.reject(error); - this.#pump(); - }, - ); - } catch (error) { - // A loader that throws instead of rejecting must not strand its lane. - this.#inFlight -= 1; - job.reject(error); - this.#pump(); - } - } - } -} - -/** - * One frame's admission allowance, shared by every path that puts streamed content on screen. - * - * A streaming world has no frame-time ceiling anywhere else: a cell row arriving builds a batch per - * asset run over every placement in it, a terrain tile builds every LOD level, and the game's - * `createCollider` runs for every resident tile — all in the one frame that discovers them. That is - * the 100-330 ms hitch a 60 m/s camera feels every time the ring moves, and it is admission cost, - * not streaming: the loads never fail. - * - * The budget is opened once per `update` and drawn down by the units it admits. A unit always - * finishes, so a frame spends at most the limit plus the unit that crossed it, and the rest of the - * backlog waits for the next frame rather than being dropped. - */ -class AdmissionBudget implements IAdmissionBudget { - readonly #limitMs: number; - readonly #now: () => number; - #spentMs = 0; - - constructor(limitMs: number, now: () => number) { - this.#limitMs = limitMs; - this.#now = now; - } - - get spentMs(): number { - return this.#spentMs; - } - - admit(work: () => void): boolean { - if (this.#spentMs >= this.#limitMs) return false; - const startedAt = this.#now(); - work(); - this.#spentMs += this.#now() - startedAt; - return true; - } -} - -/** - * Stream a Blender-authored world package by cell and keep it resident around a followed point. - * - * The class composes `TerrainTiles` for the package's heightmap, builds one `InstancedBatch` per - * resident cell asset run, distance level and mesh part, and loads hand-placed chunk GLBs through - * `loadAll` + `addInSlices`. Ring residency, CPU per-asset `maxDistance` filtering, GPU dispatch - * culling, the per-asset `lods` levels, hard budgets and generation-tokened cancellation all live - * here; every geometry, material and surface still comes from the package's GLBs and the game. - * - * An asset is drawn per part, not per model: a GLB with several primitives is one `InstancedBatch` - * each, and a scattered part whose own material is transparent draws as an alpha cutout unless the - * game asks for blending, because an `InstancedMesh` cannot sort its instances. - * - * An asset with no nonempty authored `lods` can draw its own model's baked AutoLOD chain at distances - * measured over the `autoLod` viewport. An explicit empty list enrolls only unchanged static parts; - * section ranges, custom attributes, alpha foliage and vertex deformation retain source geometry. - * - * @situation stream a large Blender-authored world by cell instead of one huge GLB - * @situation keep scattered props and hand-placed chunks resident around a moving player - * @situation honour per-asset draw distances and hard streaming budgets without a mid-frame throw - * @constraint surface is the game's; this class creates no material, colour or geometry - * @constraint budgets are hard caps that report pressure instead of over-committing - * @constraint model loads are bounded by `concurrency` (default 12) across every resident cell, not per cell - * @constraint refilters are bounded by `rebuildsPerUpdate` (default 16) per update, nearest cell first - * @constraint admission is bounded by `admissionBudgetMs` (default 2) per update across every path, plus at most one unit each for terrain and props; while props are queued terrain takes at most half, so neither starves the other, and a deferred cell keeps drawing what it has - * @constraint SkinnedMesh parts are skipped; an instanced copy would draw one rest pose - * @constraint a baked chain's switch distances are measured against `autoLod` (default 4 px of error over 60° and 1080 raster rows), because an instanced draw cannot select a level per instance; an asset with nonempty authored `lods` never consults it; explicit empty lists enroll only unchanged static geometry/material parts - * @constraint eligible static sources can use cooked HLOD (chunk-only alongside GPU scatter, whole-cell on CPU) after compilation and an observed main-context backend submission; projected-error hysteresis requires actual source triangle reduction and no extra material draws, unsupported or shared source stays resident, and collision/shadow paths retain source representations; stats().hlod counts lifecycle and charged resources, not GPU timing or physical allocation - * @override `hlod: false` keeps source rendering; ring, budgets, terrain tile size/resolution, terrain stream and collider radius, `transparentScatter`, `clusterSize` and `shadows.invalidate`, load `concurrency`, `rebuildsPerUpdate`, `admissionBudgetMs` and the package's per-asset maxDistance - * @constraint `prewarmed` resolves once every prewarmed shared batch has been drawn; a game with a loading screen waits on it, and `stats().pendingPrewarm` is the same gate as a number - * @constraint every `asset:level:part` is one InstancedMesh for the main pass, plus one caster InstancedMesh per world-grid square of `clusterSize` on the shadow caster layer, so the main pass draws one mesh per key and a shadow level submits only the squares it covers - * @constraint two definitions the asset loader resolves to one model — the same cooked `glb`, the same `lods` at the same distances, the same `maxDistance` and bounds — are one asset under the lexicographically smallest id: one model load, one set of `asset:level:part` keys, one prewarm and one refcount, released when the last cell holding any member of the group leaves the ring; `TN_WORLD_ASSET_ALIAS` reports how many of the package's assets are really distinct - * @constraint the main pass mesh draws only the squares the render camera's frustum covers — on by default, narrowed once per frame for every main batch by the engine's render-cadence dispatch, never for an orthographic camera — a batch with nothing to draw is hidden rather than submitted at `count 0`, and `TN_WORLD_MAIN_CULL` reports both every five seconds - * @constraint a loaded chunk is merged by material before it is added, so it submits one draw per material rather than one per node; a skinned, multi-material or morph-target mesh, one carrying a baked AutoLOD chain, and an instanced mesh past `chunkMergeMaxTriangles` (default 43,690 triangles) or with a shape over 2,048 triangles, all keep their own geometry; a material group crossing 131,072 vertices (4 MiB of position + normal + uv) is split into several meshes in traversal order instead of one giant upload, indexed parts keep their index, and `TN_WORLD_CHUNK_MERGE` reports what the merge did and the bytes it left - * @constraint `shadows.castDistance` is accepted and ignored (clusters replaced it); `shadows.invalidate` is called at most once a second after streamed records changed - * @example - * const world = await WorldCells.load({ url: "/world/world.json", surface, follow, ring: 1, budgets: { residentCells: 25, instances: 20000, bytes: 8000000 } }); - * scene.add(world); - * world.update(); - */ -export class WorldCells extends Group implements IComputeDriven { - readonly processCadence = "render" as const; - readonly processDuringStartup = true; - readonly #budgets: IWorldCellsBudget; - readonly #cells: readonly IWorldCell[]; - readonly #cellSize: number; - readonly #follow: IWorldCellsFollow; - readonly #loader: IAssetLoader; - /** - * Whether {@link #loader} was created here rather than supplied by the game. Only an internally - * owned loader may have its `model` cache released when this world's last cell stops naming a - * path: an explicitly supplied loader is the caller's, another world may hold the same cached - * model, and its own `release` is the caller's to call. See `#releaseModelPaths`. - */ - readonly #ownsLoader: boolean; - readonly #loadModel: ((url: string) => Promise) | undefined; - readonly #limiter: ModelLoadLimiter; - readonly #manifest: IWorldPackage; - readonly #minX: number; - readonly #minZ: number; - readonly #placements: ArrayBuffer; - readonly #rebuildsPerUpdate: number; - readonly #prefetchSeconds: number; - readonly #freshMeshesPerUpdate: number; - readonly #coveredFreshMeshesPerUpdate: number; - /** - * Pixels per world unit at unit depth, the scale a baked chain's level errors are divided by to - * reach a switch distance. See `IWorldCellsLoadOptions.autoLod` and {@link chainLevels}. - */ - readonly #autoLodPixelsPerUnit: number; - /** The screen-space error budget a synthesized level is switched at; see `autoLod`. */ - readonly #autoLodMaxPixelError: number; - /** Assets widened by their own chain so far, and whether the marker has already collapsed. */ - #chainLodAssets = 0; - #chainLodSummarised = false; - /** Fresh meshes created this update, and whether a build waited for next frame's allowance. */ - #freshThisUpdate = 0; - #meshStalled = false; - /** Smoothed follow velocity (m/s) and the sample it was last updated from. */ - #velocityX = 0; - #velocityZ = 0; - #lastSample: { readonly x: number; readonly z: number; readonly t: number } | undefined; - readonly #ring: number; - readonly #terrain: TerrainTiles | undefined; - readonly #hasColliders: boolean; - readonly #lodHysteresis: number; - readonly #placementLevels: Uint32Array | undefined; - readonly #placementReach: Float32Array | undefined; - readonly #reachRanges: Map< - IWorldCell, - Map - >; - readonly #transparentScatter: "cutout" | "blend"; - readonly #baseUrl: string; - readonly #logicalBase: string; - /** - * Asset ids the package named twice for one model, each pointing at the id kept. Every lookup of - * an id by a run's `asset` goes through {@link #canonical}, so a duplicate's placements are built, - * keyed, prewarmed, refcounted and released as the one asset they really are. Empty when the - * package has no duplicates, and then the lookup is the identity. - */ - readonly #aliases: ReadonlyMap; - readonly #assets = new Map(); - /** - * Exact `assets.model` cache paths this world holds, and how many of its asset states ask for - * each. Two distinct canonical assets can name the same path when their `lods`/`maxDistance` - * differ — the package's own aliasing leaves them separate — so the loader entry is released only - * when the last state naming the path goes. Keyed on the logical path requested, which is what the - * loader caches by, not the cooked url it resolves to. Empty under a `loadModel` override, which - * reaches no cache. - */ - readonly #modelPaths = new Map(); - readonly #resident = new Map(); - readonly #hlod: boolean; - readonly #cellProxies = new Set(); - #hlodBytes = 0; - #hlodRefused = 0; - #hlodReason = ""; - #hlodFailures = 0; - /** Surfaces shared by material content across every asset part; see `materialKey`. */ - readonly #surfaces = new Map(); - /** One shared mesh per `asset:level:part`, holding every resident cell's segment; see SharedBatch. */ - readonly #shared = new Map(); - readonly #bundleSafety = new BundleSurfaceSafety(); - /** - * `asset:level:part` -> a released batch kept for the walk back over the same ground, oldest - * first. `#release` used to throw the mesh away with the asset, so a cell that left the ring and - * came back drew into a brand new one — a uuid three had never built a node for, in the main pass - * and in every shadow pass, which is the walk-time frame cost the standing frame rate never pays. - * The batch is empty by then (`#evict` clears a cell's own block before the refcount reaches - * zero), so it is handed back whole; only its parts are gone, and `rebind` replaces them. - */ - readonly #retired = new Map(); - #retiredBytes = 0; - /** - * Every `asset:level:part` of a loaded asset is minted empty, before any placement wants it, so - * the node build lands in the loading phase instead of the frame a cell row first draws the key. - * `#prewarmQueue` is the keys still to mint, `#prewarmKeys` the ones queued (a retried load must - * not queue the same key twice), and `#prewarmPending` the ones minted but not yet - * `PREWARM_WARM_UPDATES` updates old. `prewarmed` is the game's gate for "safe to show me this - * world"; `stats().pendingPrewarm` is the same thing as a number. - */ - readonly #prewarmQueue: IPrewarmEntry[] = []; - readonly #prewarmKeys = new Set(); - #prewarmPending = 0; - /** Every mint so far, so a loading bar can read minted / (minted + pending). */ - #prewarmMinted = 0; - /** - * Minted meshes whose first draw has been submitted, and the meshes still owed one. A mint is - * only half the work: the node is built when the mesh is first submitted, and that is the frame - * the loading bar used to sit still for. `pendingPrewarm` reports this pair, so the bar counts the - * draw half instead of jumping from minting straight to done. The gate is a promise about batches - * (`#prewarmPending`), never about these: a companion no shadow level covers is owed a draw - * forever, and the gate must not wait on it. - */ - #prewarmOwed = 0; - #prewarmDrawn = 0; - #prewarmEpoch = 0; - #prewarmWait = 0; - /** - * Awaited meshes that only a shadow level can draw, and the share of them that have had one. - * - * A caster's node is built in the shadow context, so a walk-time first shadow draw of a streamed - * key is a `NodeBuilder.build` inside a shadow pass — a third of the shadow lane's CPU on the - * 2 km walk. Counting them separately is what `TN_WORLD_PREWARM`'s `shadowPrewarmed` reports and - * what the gate waits on; a count of "every batch drew once" cannot tell the two apart. - */ - #prewarmCasterMinted = 0; - #prewarmCasterOwed = 0; - #prewarmCasterDrawn = 0; - /** - * Meshes still carrying a prewarm borrow, with the hook each one has to be handed back, the batch - * holding it visible, and the post-settle updates it has waited; see - * {@link #releaseImpossibleBorrows}. - */ - readonly #awaited = new Map< - InstancedMesh, - { - batch: SharedBatch; - borrow: unknown; - own: unknown; - hadOwn: boolean; - waited: number; - epoch: number; - } - >(); - #prewarmSettled = false; - #prewarmResolve: () => void = () => { - // replaced in the field initialiser below, once `this` exists - }; - readonly #prewarmed: Promise = new Promise((resolve) => { - this.#prewarmResolve = resolve; - }); - readonly #extentBounds: Box3; - /** The largest run of each asset in any cell: the segment size every cell of it fits. */ - readonly #runMax = new Map(); - /** How many of an asset's finest levels cast shadows; 0 when the game asked for none. */ - readonly #castShadowLevels: number; - readonly #receiveShadow: boolean; - /** Asset bounds height under which a wide caster goes on the finest level's layer only. */ - readonly #smallCasterMetres: number; - /** The triangle cap a chunk's instanced meshes are expanded under; see the load option. */ - readonly #chunkMergeMaxTriangles: number; - /** - * Per-side surfaces shared world-wide, separately for merged and retained proxies. See - * `worldOwned`: a chunk teardown must not release another chunk's shadow surface. - */ - readonly #proxyMaterials = new Map(); - /** - * The renderer and camera of the last frame that ran, kept so a chunk streamed in between frames - * can be compiled before it is attached. `update(renderer, camera)` is the only place either is - * known, and the admission that loads a chunk is not a place that has them. - */ - #renderer: IRendererLike | undefined; - #camera: Camera | undefined; - /** The six planes of `#camera`'s frustum, as the live-state reference reads them; reused per check. */ - readonly #gpuPlanes = new Float32Array(24); - /** - * Side of a world-grid square, and how many cells fit in one. One `SharedBatch` per - * `asset:level:part` per square, so the mesh's bounds are that square's records and every camera - * culls at square granularity. A cell's placements never move, so a record's square is the one - * its own cell sits in and nothing is ever re-clustered. - */ - readonly #clusterSize: number; - readonly #cellsPerCluster: number; - /** - * Streamed records changed since the shadow levels were last told, and when they were last told. - * One invalidation batch a second, and only for a game that passed the hook: the companions - * used to make the hook necessary, and now the levels cull clusters themselves. - */ - // `true` so the first `update` runs a residency pass: `#residencyStale` reads it, and a fresh - // world has admitted nothing. - #shadowMoved = true; - #shadowToldAt = Number.NEGATIVE_INFINITY; - /** - * Separate unions of evicted and admitted records since the levels were last told. An unknown - * region sets `#shadowFull`, so a narrower later change cannot discard the blanket ask. - */ - #shadowRegion: IShadowRegion | undefined; - #shadowAdmissionRegion: IShadowRegion | undefined; - #shadowFull = false; - /** When `TN_WORLD_MAIN_CULL` was last printed; see {@link #reportMainCull}. */ - #mainCullToldAt = 0; - /** Window repacks since the last marker; see {@link #reportMainCull}. */ - #mainCullRepacks = 0; - /** Frusta the main cull has built, one a frame; see `IWorldCellsStats.mainCull`. */ - #mainCullFrustums = 0; - /** Windows main batches have had to re-derive, cumulative; see `IWorldCellsStats.mainCull`. */ - #mainCullWindows = 0; - /** Window repacks, cumulative; see `IWorldCellsStats.mainCull`. */ - #mainCullPacks = 0; - /** - * One AABB per resident visibility cell, and how many resident world cells hold it. Shared by - * every key, so the main cull is O(visibility cells) a frame rather than O(keys × cells); see - * {@link #cullMainPass}. - */ - readonly #cullCells = new Map(); - /** World cells per main-cull visibility cell; see `MAIN_CULL_SQUARE_METRES`. */ - readonly #cellsPerCullCell: number; - /** The visibility cells the last cull kept and the frame's set, trading places; see `#cullMainPass`. */ - #visibleSquares: Set = new Set(); - #cullScratch: Set = new Set(); - /** Bumped only when `#visibleSquares` changed, so a settled camera repacks no batch at all. */ - #visibleEpoch = 0; - /** The follow point the last full residency pass ran for; see `#residencyStale`. */ - readonly #residencyPoint = new Vector3(Number.NaN, 0, Number.NaN); - /** The follow point's cell at the last residency pass; a move of more than one is a jump. */ - #residencyCell: { readonly x: number; readonly z: number } | undefined; - /** The follow point the last refilter pass ran for; see `#refilterStale`. */ - readonly #refilterPoint = new Vector3(Number.NaN, 0, Number.NaN); - /** Bumped by every admission and eviction, so a residency change owes a refilter that update. */ - #residencyEpoch = 0; - /** The residency epoch the last refilter pass saw; see `#refilterStale`. */ - #refilterEpoch = -1; - /** The last pass found more stale cell-assets than `rebuildsPerUpdate` could queue. */ - #refilterOwed = false; - /** The game's hook to refresh the shadow levels after streamed records changed. */ - readonly #invalidateShadows: ((region?: IShadowRegion) => void) | undefined; - /** Cell-asset builds waiting for budget, in admission order: nearest cell first. */ - #jobs: IBuildJob[] = []; - /** The (cell, run) pairs already queued, so a refilter cannot queue itself twice. */ - readonly #queued = new Set(); - readonly #position = new Vector3(); - readonly #rotation = new Quaternion(); - readonly #scale = new Vector3(); - readonly #matrix = new Matrix4(); - readonly #instance = new Matrix4(); - /** The authored bounds' centre, placed: the source sphere's centre. See `#addPlacements`. */ - readonly #sourceCentre = new Vector3(); - /** Reused transformed authored bounds for admission and conservative visibility volumes. */ - readonly #recordBounds = new Box3(); - readonly #pressure = { cells: 0, instances: 0, bytes: 0 }; - readonly #budgetMs: number; - readonly #coveredBudgetMs: number; - /** Set for the length of one `process` call that runs behind the startup cover. */ - #covered = false; - readonly #now: () => number; - #admission = { spentMs: 0, deferred: 0, backlog: 0 }; - #instances = 0; - #bytes = 0; - #evictions = 0; - #failures = 0; - #rebuilds = 0; - /** Refilter passes run, whether or not they queued anything; see `IWorldCellsStats.refilters`. */ - #refilters = 0; - /** Replacement builds that entered #addPlacements; initial admission is not CPU refilter work. */ - #levelRefilters = 0; - /** Refilters whose rebuild came back identical and were settled without a write. */ - #unchanged = 0; - /** Every distinct gate distance the world's assets have, ascending; see `#noteGate`. */ - readonly #gates: number[] = []; - /** The reused hit list and the reused per-cell seen set; see `#staleIn`. */ - readonly #stale: Array<{ - cell: IResidentCell; - distance: number; - id: string; - }> = []; - readonly #staleSeen = new Set(); - /** Stale cell-assets the passes found, cumulative; see `IWorldCellsStats.refilterEntries`. */ - #staleEntries = 0; - #generation = 0; - #released = false; - /** - * The GPU-driven main pass, and whether the world asked for it. Off is byte-for-byte this class's - * own CPU path: nothing below reads `on` except the four places the option changes what a frame - * does. See the `gpuScene` load option and `#cullMainPass`. - */ - readonly #gpuScene = new WorldGpuScene(); - /** - * How the shadow cascade reaches this world's GPU keys: a level render hands its own four numbers - * here and gets its set selected into the shadow twins, which the shadow key meshes on - * `VIRTUAL_SHADOW_KEY_LAYER` then draw. Published on this object — the world's own root, which is - * what `VirtualShadowNode` walks — and duck-typed there, because the world chunk and the shadow - * node are separate and neither names the other's package types. - * - * Published only when `?tnShadowGpuKeys=1` asked for it, which is what makes the node's own - * keyed path and this world's twin meshes one switch: with nothing published a level reads no - * keys, mints no twin and submits nothing, and every picture is the one it drew before. - */ - readonly tnShadowGpuKeys: ((renderer: IRendererLike, level: IShadowLevel) => void) | undefined = - shadowGpuKeysRequested() - ? (renderer: IRendererLike, level: IShadowLevel): void => { - this.#gpuScene.dispatchShadow(renderer, level); - } - : undefined; - /** Every main key this world has minted a shadow twin for, which is what a level's dispatch asks for. */ - readonly #shadowKeyNames = new Set(); - readonly #gpuWanted: boolean; - /** `gpuSceneValidate` as the load asked for it, before the query string and the environment. */ - readonly #gpuValidate: boolean | undefined; - /** - * Whether the GPU scene samples what it actually draws, so `TN_FRAME_BUDGET` and - * `stats().gpuScene` can report a GPU-selected count. A validation turns it on too; the engine - * also turns it on when the frame budget is, so a game pays nothing when neither is. - */ - #gpuTally = false; - readonly #occlusion: OcclusionMode; - /** Resident placements per canonical asset, which is the capacity a key of that asset needs. */ - readonly #gpuResident = new Map(); - /** CPU-owned gate metadata for the source representation last published by a completed swap. */ - readonly #publishedGpuGates = new Map(); - /** CPU-owned key runs recorded when the owner dresses a source part. */ - readonly #gpuLevelKeys = new Map>(); - /** - * The `BundleGroup`s every recorded main batch mesh is parented under, one per shard of - * {@link MAIN_BUNDLE_SHARDS}, and the number of times one has been re-recorded. A shard is minted by - * the first mesh that lands in it, so a world that never dresses a mesh has no group to pay for. - * - * A re-record is a structural change and nothing else: see `IWorldCellsStats.bundle` and - * {@link #bumpBundle}. Streaming, culling and LOD are GPU-side answers and never move it. A record - * walks every draw its group holds, so a mint pays for its own shard and not the whole main pass. - */ - readonly #bundles = new Map(); - #bundleRecords = 0; - /** - * Shards re-recorded while a compile was in flight, so they are re-recorded again once it settles. - * Three encodes a draw only when its pipeline is ready, so a record cut during a `compileAsync` - * silently leaves a not-yet-ready draw out, and the replay never re-checks it. Bumped from - * {@link #bumpBundle} while {@link #compileBusy}, flushed at the top of the update that sees the - * compile settle. - */ - #compileSeen = 0; - #compileBusy = false; - readonly #recordedWhileBusy = new Set(); - /** - * One `BundleGroup` per resident cell that has chunks attached, keyed by cell key, each with the - * main-cull cluster whose answer decides whether it is in the frame. Minted by the first chunk a - * cell attaches, so a world with no chunks pays nothing; see the `bundles` option. - * - * Per cell and not one group for the whole world: a record is fixed when it is made, so a group's - * own answer to "is this in view" cannot change without a re-record — which is the whole cost this - * is removing. A cell is the granularity the coarse cull already answers at, so `visible` on the - * group is a write rather than a record. See `#cullMainPass`. - */ - readonly #chunkBundles = new Map< - string, - { readonly cluster: string; readonly group: BundleGroup } - >(); - /** How many chunk roots those groups hold, so `stats().bundle.children` costs nothing to read. */ - #chunkBundleChildren = 0; - /** `bundles` as the load asked for it, before the query string and the environment. */ - readonly #bundlesWanted: boolean; - /** - * Who decided that: the load that named `bundles`, the launch that asked for `off` or `on`, or - * nothing at all — which is what the marker reports so a run on the default is never mistaken for - * one a flag turned off. See {@link bundlesAsked}. - */ - readonly #bundleReason: string; - /** `adaptiveLod` resolved against the launch override; see the option. */ - readonly #adaptiveLod: boolean; - readonly #preserveAuthoredParts: boolean; - /** The main pass's allowed GPU share of a frame; see the option. */ - readonly #mainGpuShare: number; - /** The distance multiplier both selection paths are using right now. */ - #lodBias = LOD_BIAS_MIN; - /** The clock the step cadence and the first marker line are measured from. */ - readonly #lodBiasStartedAt: number; - /** The last time a rise or decay step ran. */ - #lodBiasStepAt: number; - /** The last time the `TN_LOD_BIAS` line printed. */ - #lodBiasToldAt: number; - /** The resident ring has been handed to the scene once; see `#seedGpuSources`. */ - #gpuSeeded = false; - /** Whether alpha-foliage assets append a baked whole-asset terminal level; see the option. */ - readonly #impostors: boolean; - /** The baker one view per render-cadence update; see `#stepImpostor`. */ - readonly #baker = new WorldImpostorBaker(); - /** Assets whose bake is queued, in adoption order; `#impostorQueued` is the same set by id. */ - readonly #impostorQueue: IAssetState[] = []; - readonly #impostorQueued = new Set(); - /** The asset whose bake holds the baker right now, if any. */ - #impostorBaking: IAssetState | undefined; - /** - * Completed atlases by content key. An atlas a live asset draws from is active and never evicted; - * one with no user is kept for a later reference until the byte budget needs the room. - */ - readonly #impostorRecords = new Map(); - #impostorRecordBytes = 0; - /** The declared bytes of the one bake in flight, held against the budget before `begin`. */ - #impostorReservedBytes = 0; - readonly #impostorBudgetBytes: number; - /** Assets whose `TN_WORLD_IMPOSTOR` line has printed, so the marker is one per asset. */ - readonly #impostorReported = new Set(); - /** - * Assets whose pre-bake wide segments are still waiting for a block in the one whole-asset mesh, - * because the frame that handed the bake over had spent its fresh-mesh allowance. Retried on the - * next update rather than left with no far shadow. - */ - readonly #wideRetry = new Set(); - /** - * The whole-map far aggregates, one per exact atlas cache key, and the `run -> aggregate` index a - * near-ready handoff and an eviction read to toggle a run's far segment without asking the asset. - * - * `#farInstances` and `#farBytes` are the physical allocations those meshes hold, charged against - * the world's own instance and byte budgets beside the near ring's records. - */ - readonly #far = new Map(); - readonly #farSegments = new Map(); - #farInstances = 0; - #farBytes = 0; - /** Atlas keys whose `TN_WORLD_IMPOSTOR_FAR` line has printed, so the marker is one per aggregate. */ - readonly #farReported = new Set(); - /** - * Cohorts whose far aggregate a full budget refused, as metadata plus a built surface and never - * the source GLB. Retried when a near cell releases its allocation — an event, not a per-frame - * scan — so a walk away from the ring makes room and the refused species appears without holding - * a full model alive; see {@link IFarRetryEntry}, `#retryFar` and `#deferFar`. - */ - readonly #farRetry = new Map(); - /** - * Canonical asset ids placed anywhere in the map, adopted on idle frames so a species that never - * enters the near ring still bakes its atlas and appears on the far mesh. Enumerated once from the - * static placements; see `#pumpFarAcquisition`. - */ - readonly #farCandidateIds: readonly string[]; - /** The rotating cursor over `#farCandidateIds`, so the map is walked a bounded step per idle frame. */ - #farCursor = 0; - /** Canonical ids whose far handling has settled: baked and built, or classified with no atlas. */ - readonly #farSeen = new Set(); - /** Canonical ids acquired for the far half alone, held until their aggregate lands or is refused. */ - readonly #farTemp = new Set(); - /** Every candidate is settled, so the idle pump can stop scanning the map; see `#pumpFarAcquisition`. */ - #farExhausted = false; - - private constructor(init: IWorldCellsInit) { - super(); - this.#budgets = { - bytes: positiveInteger(init.budgets.bytes, "budgets.bytes"), - instances: positiveInteger(init.budgets.instances, "budgets.instances"), - residentCells: positiveInteger(init.budgets.residentCells, "budgets.residentCells"), - }; - this.#ring = nonNegativeInteger(init.ring, "ring"); - this.#hlod = init.hlod !== false; - this.#budgetMs = admissionBudgetMs(init.admissionBudgetMs ?? DEFAULT_ADMISSION_BUDGET_MS); - this.#coveredBudgetMs = - init.coveredAdmissionBudgetMs === undefined - ? this.#budgetMs * COVERED_BUDGET_SCALE - : admissionBudgetMs(init.coveredAdmissionBudgetMs); - this.#now = init.admissionNow ?? ((): number => globalThis.performance?.now() ?? Date.now()); - this.#follow = init.follow; - this.#manifest = init.manifest; - // Before the first `#canonical` read, which is the run loop below. - this.#aliases = init.aliases; - this.#cells = init.manifest.cells; - const candidates = new Set(); - for (const cell of this.#cells) - for (const run of cell.runs) { - // By the canonical id, so one key's buffer is sized for the largest run of *any* member of - // the group and a duplicate's placements never overflow the block their canonical minted. - const id = this.#canonical(run.asset); - this.#runMax.set(id, Math.max(this.#runMax.get(id) ?? 0, run.count)); - // Every species the map places, however far from the follow point it starts: the far half - // has to be able to bake it without the near ring ever bringing it in. - if (run.count > 0) candidates.add(id); - } - this.#farCandidateIds = [...candidates]; - this.#cellSize = init.manifest.cellSize; - this.#minX = init.manifest.extent.minX; - const { extent, terrain } = init.manifest; - // Scatter stands on the terrain: its height range, with headroom for tall trees and props. - this.#extentBounds = new Box3( - new Vector3(extent.minX, terrain.heightMin - 16, extent.minZ), - new Vector3(extent.minX + extent.sizeX, terrain.heightMax + 64, extent.minZ + extent.sizeZ), - ); - this.#minZ = init.manifest.extent.minZ; - this.#placements = init.placements; - this.#lodHysteresis = init.lodHysteresis ?? 0; - this.#placementLevels = - this.#lodHysteresis > 0 - ? new Uint32Array(init.placements.byteLength / PLACEMENT_RECORD_BYTES) - : undefined; - this.#placementReach = init.placementReach; - this.#reachRanges = init.reachRanges; - this.#rebuildsPerUpdate = positiveInteger(init.rebuildsPerUpdate ?? 16, "rebuildsPerUpdate"); - this.#autoLodPixelsPerUnit = autoLodScale(init.autoLod); - this.#autoLodMaxPixelError = autoLodPixelError(init.autoLod); - // The marker is periodic, and a world's first five seconds are its own: the clock starts at the - // world, not before it, so loading does not print a line. - this.#mainCullToldAt = this.#now(); - this.#prefetchSeconds = init.prefetchSeconds ?? 1.5; - this.#freshMeshesPerUpdate = positiveInteger( - init.freshMeshesPerUpdate ?? 2, - "freshMeshesPerUpdate", - ); - this.#coveredFreshMeshesPerUpdate = positiveInteger( - init.coveredFreshMeshesPerUpdate ?? - Math.min(Number.MAX_SAFE_INTEGER, this.#freshMeshesPerUpdate * COVERED_BUDGET_SCALE), - "coveredFreshMeshesPerUpdate", - ); - if (!(this.#prefetchSeconds >= 0) || !Number.isFinite(this.#prefetchSeconds)) - throw new Error("WorldCells prefetchSeconds must be a finite number >= 0."); - this.#baseUrl = init.baseUrl; - this.#logicalBase = init.logicalBase; - this.#loader = init.assets ?? createAssetLoader(); - this.#ownsLoader = init.ownsAssets ?? init.assets === undefined; - this.#loadModel = init.loadModel; - this.#limiter = new ModelLoadLimiter( - positiveInteger(init.concurrency ?? WORLD_LOAD_CONCURRENCY, "concurrency"), - ); - this.#transparentScatter = init.transparentScatter ?? "cutout"; - this.#gpuWanted = init.gpuScene ?? gpuSceneRequested(); - const asked = bundlesAsked(); - this.#bundlesWanted = init.bundles ?? asked !== "off"; - this.#bundleReason = - init.bundles === undefined ? (asked === "default" ? "default" : "launch") : "option"; - this.#impostors = - init.impostors ?? - (impostorsRequested() && - !(init.preserveAuthoredParts === true || init.lodHysteresis !== undefined)); - this.#impostorBudgetBytes = DEFAULT_IMPOSTOR_ATLAS_BUDGET_BYTES; - this.#adaptiveLod = init.adaptiveLod ?? adaptiveLodRequested(); - this.#preserveAuthoredParts = init.preserveAuthoredParts ?? false; - this.#mainGpuShare = mainGpuShare(init.mainGpuShare); - // The warm-up and the step cadence are the world's own clock, so loading does not count as a - // second of adaptation and a test can drive the loop with an injected `admissionNow`. - this.#lodBiasStartedAt = this.#now(); - this.#lodBiasStepAt = this.#lodBiasStartedAt; - this.#lodBiasToldAt = this.#lodBiasStartedAt - LOD_BIAS_MARKER_SECONDS * 1000; - this.#gpuValidate = init.gpuSceneValidate; - this.#occlusion = init.occlusion ?? occlusionMode(); - this.#gpuTally = init.gpuSceneTally === true; - this.#castShadowLevels = - init.shadows?.cast === true - ? positiveInteger(init.shadows.castLevels ?? 1, "shadows.castLevels") - : 0; - this.#receiveShadow = init.shadows?.receive === true; - this.#smallCasterMetres = positiveMetres( - init.shadows?.smallCasterMetres ?? SMALL_CASTER_METRES, - "shadows.smallCasterMetres", - ); - this.#chunkMergeMaxTriangles = positiveInteger( - init.chunkMergeMaxTriangles ?? CHUNK_MERGE_MAX_TRIANGLES, - "chunkMergeMaxTriangles", - ); - // Validated and dropped. Clusters bound what a shadow level submits now, so a game that set - // this keeps compiling and keeps loading; see the option's TSDoc. - if (init.shadows?.castDistance !== undefined) - positiveInteger(init.shadows.castDistance, "shadows.castDistance"); - this.#clusterSize = - init.clusterSize === undefined - ? this.#cellSize - : positiveInteger(init.clusterSize, "clusterSize"); - // Rounded, so a cluster is a whole number of cells and a cell belongs to exactly one of them. - this.#cellsPerCluster = Math.max(1, Math.round(this.#clusterSize / this.#cellSize)); - // The main cull's own grid, which is a whole number of world cells or nothing: see - // `MAIN_CULL_SQUARE_METRES`. A `cellSize` that is not a whole multiple of 32 m — and every - // `cellSize` larger than it — falls back to one cell, which is the cull this shipped before it - // had a grid of its own. - this.#cellsPerCullCell = - MAIN_CULL_SQUARE_METRES % this.#cellSize === 0 ? MAIN_CULL_SQUARE_METRES / this.#cellSize : 1; - this.#invalidateShadows = init.shadows?.invalidate; - if (this.#transparentScatter !== "cutout" && this.#transparentScatter !== "blend") - throw new Error("WorldCells transparentScatter must be 'cutout' or 'blend'."); - this.#hasColliders = init.terrain !== false && init.createCollider !== undefined; - if (init.terrain === false) return; - // Terrain can reach further than the props do, so its radius is its own option; the collider - // radius stays on the ring, which is how far a player can actually walk into this world. - const streamRadius = nonNegativeInteger( - init.terrain?.streamRadius ?? this.#ring, - "terrain.streamRadius", - ); - const colliderRadius = nonNegativeInteger( - init.terrain?.colliderRadius ?? this.#ring, - "terrain.colliderRadius", - ); - const tileCount = (2 * streamRadius + 1) ** 2; - this.#terrain = new TerrainTiles({ - ...(init.createCollider === undefined - ? {} - : { colliderRadius, createCollider: init.createCollider }), - receiveShadow: init.shadows?.receive === true, - ...(init.terrain?.lodDistances === undefined - ? {} - : { lodDistances: init.terrain.lodDistances }), - ...(init.terrain?.lodFactors === undefined ? {} : { lodFactors: init.terrain.lodFactors }), - ...(init.terrain?.skirtDepth === undefined ? {} : { skirtDepth: init.terrain.skirtDepth }), - // Sized from the terrain stream radius: the wanted square fits the tile budget. ponytail: no - // terrain byte cap (a tile's bytes are deterministic from the radius, and the cells own the - // memory budget); give `TerrainTiles` its own byte budget when terrain is not radius-bounded. - // A tile that still cannot fit is caught in `update` and reported as pressure. - residentByteBudget: Number.MAX_SAFE_INTEGER, - residentTileBudget: tileCount, - sampleHeight: heightSamplerFromHeightmap( - init.manifest.terrain, - init.manifest.extent, - init.heightmap, - ), - streamRadius, - surface: init.surface, - tileResolution: init.terrain?.tileResolution ?? DEFAULT_TILE_RESOLUTION, - tileSize: init.terrain?.tileSize ?? this.#cellSize, - }); - this.add(this.#terrain); - } - - static async load(options: IWorldCellsLoadOptions): Promise { - if (options.preserveAuthoredParts && options.impostors) - throw new Error("WorldCells preserveAuthoredParts requires impostors:false."); - if ( - options.lodHysteresis !== undefined && - (!Number.isFinite(options.lodHysteresis) || - options.lodHysteresis < 0 || - options.lodHysteresis >= 1 || - options.gpuScene !== false || - options.impostors === true) - ) - throw new Error( - "WorldCells lodHysteresis must be in [0, 1) with gpuScene:false and impostors:false.", - ); - const assets = options.assets ?? createAssetLoader(); - const baseUrl = options.url.slice(0, options.url.lastIndexOf("/") + 1); - // Logical paths, which is what the loader keys its manifest by: the authored name with no - // leading slash, and the directory the manifest itself sits in as their base. - const manifestPath = options.url.replace(/^\//u, ""); - const logicalBase = manifestPath.slice(0, manifestPath.lastIndexOf("/") + 1); - const manifest = - options.data?.manifest ?? - (await loadLogical( - assets, - manifestPath, - async (url) => (await (await fetchOk(url)).json()) as IWorldPackage, - )); - const placements = - options.data?.placements ?? - (await loadLogical(assets, resolveRelative(logicalBase, manifest.placements), async (url) => - (await fetchOk(url)).arrayBuffer(), - )); - const heightmap = - options.terrain === false - ? new Uint16Array(0) - : (options.data?.heightmap ?? - (await loadLogical( - assets, - resolveRelative(logicalBase, manifest.terrain.heightmap), - loadWorldHeightmap, - ))); - const validation = validateWorldPackage(manifest, { - ...(options.terrain === false ? {} : { heightmapByteLength: heightmap.length * 2 }), - placementsByteLength: placements.byteLength, - }); - if (!validation.ok) { - const codes = new Set(); - const details: string[] = []; - for (const entry of validation.errors) { - codes.add(entry.code); - details.push(`${entry.path} ${entry.message}`); - } - const error = new Error( - `World package '${options.url}' failed validation: ${[...codes].join(", ")}. ${details.join(" ")}`, - ); - error.name = "WorldPackageValidationError"; - throw error; - } - const reach = options.placementReach; - if (reach !== undefined) { - if ( - !(reach instanceof Float32Array) || - reach.length !== placements.byteLength / PLACEMENT_RECORD_BYTES - ) - throw new Error("WorldCells placementReach length must match the placement record count."); - } - // One line, at the one moment the whole package is known: how many assets it names and how many - // of them are one model under several names. `canonical` equal to `assets` is a package with no - // duplicates, which is most of them. - const aliases = await assetAliases(assets, manifest, logicalBase); - const reachRanges = new Map< - IWorldCell, - Map - >(); - if (reach !== undefined) { - // Cap each load-time unit, then yield when the measured slice budget is spent. - // One host turn per cheap chunk delays registration behind hundreds of loading frames. - function* validationChunks() { - for (let start = 0; start < (reach as Float32Array).length; start += 512) yield start; - } - await addInSlices( - validationChunks(), - (start) => { - for (let index = start; index < Math.min(start + 512, reach.length); index++) { - const value = reach[index] as number; - if (!(value >= 0) || (value !== Number.POSITIVE_INFINITY && !Number.isFinite(value))) - throw new Error( - "WorldCells placementReach values must be nonnegative metres or Infinity.", - ); - } - }, - { marker: false }, - ); - // Start indexing in a fresh host turn instead of combining two separate slice budgets. - await yieldToHost(); - const records = new Float32Array(placements); - function* chunks() { - for (const cell of manifest.cells) - for (const run of cell.runs) - for (let start = run.offset; start < run.offset + run.count; start += 512) - yield { cell, run, start, end: Math.min(start + 512, run.offset + run.count) }; - } - await addInSlices( - chunks(), - ({ cell, run, start, end }) => { - const id = aliases.get(run.asset) ?? run.asset; - const ranges = - reachRanges.get(cell) ?? - new Map(); - const range = ranges.get(id) ?? { - min: Number.POSITIVE_INFINITY, - max: 0, - minY: Number.POSITIVE_INFINITY, - maxY: Number.NEGATIVE_INFINITY, - }; - for (let index = start; index < end; index++) { - const value = reach[index] as number; - if (value !== Number.POSITIVE_INFINITY) { - range.min = Math.min(range.min, value); - range.max = Math.max(range.max, value); - } - const y = records[index * PLACEMENT_RECORD_FLOATS + 1] as number; - range.minY = Math.min(range.minY, y); - range.maxY = Math.max(range.maxY, y); - } - ranges.set(id, range); - reachRanges.set(cell, ranges); - }, - { marker: false }, - ); - } - const named = Object.keys(manifest.assets).length; - console.info( - `TN_WORLD_ASSET_ALIAS assets=${String(named)} canonical=${String(named - aliases.size)}`, - ); - return new WorldCells({ - ...options, - aliases, - reachRanges, - assets, - baseUrl, - heightmap, - logicalBase, - manifest, - // The loader made here when the game supplied none is this world's to release; one the game - // passed in is the game's, and `#releaseModelPaths` never touches its cache. - ownsAssets: options.assets === undefined, - placements, - }); - } - - get released(): boolean { - return this.#released; - } - - get warmupNodes(): readonly unknown[] { - return this.#terrain?.warmupNodes ?? []; - } - - /** - * Resolves once every minted batch has been drawn at least once, main and shadow context. - * - * The loading gate a game with a loading screen waits on, so the shader builds the prewarm exists - * to move happen behind it instead of in the first seconds of play. It counts updates the world - * was in a scene for, because a batch added while the world is not in a scene is projected by - * nothing and builds nothing. `dispose` settles it too: a world torn down mid-load must not leave - * a game awaiting it forever. - */ - get prewarmed(): Promise { - return this.#prewarmed; - } - - /** - * Fence one just-rendered GPU dispatch for a diagnostic walk, resolving after both readbacks land. - * Call after {@link update} and the render, then await it before advancing the measured frame. - */ - validateGpuFrame(frame: number): Promise { - if (this.#released) - return Promise.reject(new Error("World GPU frame validation cannot run after dispose.")); - if (this.#renderer === undefined) - return Promise.reject(new Error("World GPU frame validation needs a renderer.")); - return this.#gpuScene.validateCurrentDispatch(this.#renderer, frame); - } - - /** - * Per-frame residency step; call it wherever `TerrainTiles.process` is called. - * - * Reads the follow target, keeps the in-ring cells, evicts cells beyond the hysteresis ring, and - * spends one admission budget on everything the ring newly wants: cell-asset batches, terrain - * tiles, colliders and the `maxDistance`/`lods` refilters the follow point has moved far enough to - * have changed. Whatever does not fit waits for the next call — nothing is dropped, and - * `stats().admission` is the honest report of what is waiting. A terrain budget throw is caught - * and counted, and so is a teardown that throws while releasing what left; `failures` carries - * both. Every other error, the game's included, escapes. - * - * The residency pass itself is skipped while the follow point has moved less than half a metre - * since the last one and nothing is pending (a load, a cell build, a deferred terrain admission, a - * prewarm mint or a shadow level not yet told), because the pass would re-derive the set the - * world already holds. The drain, the prewarm, the invalidation and the main cull below it run - * every time. - * - * `camera` is the frame's render camera, which the engine's render-cadence dispatch hands over - * because it is the one this world draws with. A game that drives this world itself can pass it, - * and a game that does not keeps every resident record drawn. - */ - update(renderer?: IRendererLike, camera?: Camera): void { - if (this.#released) return; - // Kept for the chunk warm-up below: a chunk is loaded asynchronously and compiled when it - // arrives, which can be a frame or a loading screen after the frame that gave us these. - if (renderer !== undefined) this.#renderer = renderer; - if (camera !== undefined) this.#camera = camera; - // A bundle recorded while a compile was in flight left out every draw whose pipeline was not - // ready, and the replay never re-checks; re-record those shards now that it has settled. Set - // `#compileBusy` first, so this flush's own bumps are not added back to the set. - const compiles = renderer?.compileCount ?? 0; - const busy = renderer?.compiling === true || compiles !== this.#compileSeen; - this.#compileSeen = compiles; - this.#compileBusy = busy; - if (!busy && this.#recordedWhileBusy.size > 0) { - const recorded = [...this.#recordedWhileBusy]; - this.#recordedWhileBusy.clear(); - for (const group of recorded) if (group.parent !== null) this.#bumpBundle(group); - } - // The adaptive LOD bias, before the dispatch below reads the gates it scales. - this.#adaptLodBias(renderer); - // The first frame that hands over a renderer is the only one that can answer whether this - // backend can run the GPU scene, and it prints its answer once: `enable` reports, then returns - // early for the rest of the world's life. - if (renderer !== undefined) - this.#gpuScene.enable( - renderer, - this.#gpuWanted, - this.#gpuValidate ?? gpuSceneValidationRequested(), - this.#gpuTally, - this.#occlusion, - ); - // The one check the scene cannot make for itself, and the only one that reads this class's own - // numbers rather than the scene's tables: a dressed main mesh's own indirect record, against the - // instances this path composes for the key that mesh is named. Registered before the keys are - // seeded, because the first check is a whole ring's worth of meshes and every one of them is - // already dressed by the time it runs. - this.#gpuScene.drawsFrom(() => this.#gpuDraws()); - // The scene coming up under a ring that was already built: every placement swapped in before it - // was on has no source record, and the dispatch draws nothing it is not given. One rebuild of the - // resident ring puts them in, through the same build every first-seen asset takes. - if (this.#gpuScene.on && this.#gpuSeeded === false) { - this.#gpuSeeded = true; - this.#seedGpuSources(); - this.#seedGpuKeys(); - // After the keys, because that is the only point at which the marker can say how many of the - // world's main meshes are dressed: printed at enable it would read the ring as it was a frame - // earlier, which is the state the run that found this was in. - this.#gpuScene.announce(this.#renderer as IRendererLike, this.#gpuCensus()); - } - // A new upload epoch, so no batch's pending span outlives the render that consumed it. See - // `SharedBatch#touched`. - advanceWriteEpoch(); - const x = this.#follow.position.x; - const z = this.#follow.position.z; - // Terrain admits first, and a tile is one unit of several milliseconds, so after a jump (a - // teleport, a review camera) the 17 x 17 terrain ring spent the whole allowance every frame for - // seconds and the prop queue got nothing: the forest never built where the camera landed. While - // props are queued the terrain is held to half, and the props get whatever it left. - const budgetMs = this.#covered ? this.#coveredBudgetMs : this.#budgetMs; - const terrainMs = this.#jobs.length > 0 ? budgetMs / 2 : budgetMs; - const budget = new AdmissionBudget(terrainMs, this.#now); - this.#freshThisUpdate = 0; - this.#meshStalled = false; - // The residency pass is a function of the follow point, so a follow point that has not moved - // and a world with nothing pending has the same answer as the last update: hundreds of cells - // walked and re-sorted, every resident cell refiltered, every terrain tile re-levelled, all of - // it to reach the set it already holds. Skipped while both hold. A move, a load, a build, a - // deferred admission or a shadow level not yet told runs the pass exactly as before. - const terrainView = - this.#terrain !== undefined && - this.#camera !== undefined && - this.#renderer?.domElement?.height !== undefined - ? { - camera: this.#camera, - viewportHeight: this.#renderer.domElement.height, - maxPixelError: this.#autoLodMaxPixelError, - hysteresis: this.#lodHysteresis, - } - : undefined; - if (this.#residencyStale(x, z)) { - this.#residencyPoint.set(x, this.#followY(), z); - try { - this.#terrain?.follow({ x, z }, budget, terrainView); - this.#terrain?.process(renderer, camera, undefined, terrainView); - } catch (error) { - if (!(error instanceof TerrainTileBudgetError)) throw error; - this.#pressure.bytes += 1; - } - const ahead = this.#ahead(x, z); - this.#updateResidency( - { - x: Math.floor((ahead.x - this.#minX) / this.#cellSize), - z: Math.floor((ahead.z - this.#minZ) / this.#cellSize), - }, - ahead.x, - ahead.z, - ); - // The refilter is a scan of every resident cell, so it runs on its own cadence rather than - // with the residency pass above; see `#refilterStale` and `LEVEL_REFILTER_STEP_METRES`. - // GPU source admission keeps every resident maxDistance candidate, so the dispatch's slot - // cull and level gates handle a moving camera without a CPU replacement. `#staleIn` still - // watches placementReach, whose per-record admission boundary has no GPU slot equivalent. - if (this.#refilterStale(x, z)) { - this.#refilterPoint.set(x, this.#followY(), z); - this.#refilterEpoch = this.#residencyEpoch; - this.#updateMaxDistance(x, z); - } - } else if ( - // A blend in flight is not a settled ring, and the blend is advanced by `process` alone — - // `follow` only re-levels tiles. A follow point standing still used to skip both, so a - // three-frame tile morph froze on its first frame until the player moved again. - // With a render view, settled tiles also recheck cached errors against the current projection. - terrainView !== undefined || - (this.#terrain?.blendingTiles ?? 0) > 0 - ) { - this.#terrain?.process(renderer, camera, undefined, terrainView); - } - const props = new AdmissionBudget(budgetMs - Math.min(budget.spentMs, terrainMs), this.#now); - this.#drain(props); - // The prewarm runs outside the admission budget on purpose — it is not residency, it is the - // shader builds the residency is about to need, and the allowance that spreads those is - // `PREWARM_PER_UPDATE`. - this.#drainPrewarm(); - // One bake view per render update, after the prewarm so its own atlas allocation never delays a - // node build the walk is waiting on, and before the levels are told so a level the bake just - // appended is in this frame's window. - this.#stepImpostor(renderer); - // After the bake handoff, so a wide block it deferred to this frame's exhausted allowance is - // claimed now; a no-op on every frame with nothing waiting. - this.#retryWide(); - // After the near work, so a species no resident run has asked for still bakes and reaches the - // far mesh without the camera ever traversing its cell. Bounded to one asset per idle frame. - this.#pumpFarAcquisition(); - // After the drain, so the levels are told about this update's records and not the last one's. - this.#tellShadows(); - // After the drain too, so a record admitted this update is in the window the camera draws and - // not one frame behind it. - for (const state of this.#cellProxies) this.#restoreCellProxySource(state); - this.#cullMainPass(camera); - // With it, because it reads the drain's own answer: a caster whose main counterpart is not - // drawable this frame must not cast before the levels render this frame's maps. - this.#publishCasterAdmission(); - // After main publication/culling: an exclusive source key stays fully resident, so returning - // near detail never waits for admission or an instance allocation. Proxy loads/staging alone - // consume admission units; the synchronous visibility commit always protects the current view. - this.#stepCellProxies(props, renderer, camera); - this.#reportMainCull(); - this.#admission = { - backlog: this.#backlog(), - deferred: this.#jobs.length, - spentMs: budget.spentMs + props.spentMs, - }; - } - - /** - * Narrow every main batch's draw window to the world-grid squares `camera`'s frustum covers, once - * per frame, for the whole set at once. - * - * This used to be each main mesh's own `onBeforeRender`, which three never calls for an invisible - * mesh — so the moment a batch that draws nothing was hidden, the one thing that could show it - * again when the camera turned back was gone with it, and the world lost its trees for good. The - * decision therefore moved to the frame's own driver: the render-cadence compute dispatch, which - * the engine already runs once per rendered frame — on the same readiness gate residency itself - * sits behind, so a frame that skips this skipped the world's records too — and which now hands the - * render camera to the world, so it reaches every main mesh whether it is drawn or not. - * - * The camera rules are the ones the hook had, and they are the whole of what decides a window: - * - * - **An orthographic camera never repacks.** A virtual-shadow level's camera is a - * `DirectionalLightShadow`'s own, which three makes orthographic, so a level's window — one shadow - * slice of the ring, deliberately not the main view's — can never overwrite the main pass's. A - * game whose own camera is orthographic keeps the old behaviour: everything resident is drawn, and - * nothing pops. That is a lost saving, never a wrong picture. - * - **The render camera is the one that decides.** The engine dispatches this with the camera it - * is about to draw the main pass with, so the second-perspective-camera race the hook had to - * arbitrate per mesh cannot arise: a mirror or a minimap is drawn with its own camera and is - * served the main view's window, which is the honest trade for a world with one main view. - * - **A set that did not change touches nothing.** The frustum, the visible cells and the epoch - * are the frame's, derived once for the whole pass; a batch whose records have not moved since it - * packed at that epoch compares two integers and returns. A camera at rest costs one frustum and - * one test per resident visibility cell a frame, and nothing at all per mesh. - */ - #cullMainPass(camera: Camera | undefined): void { - if (camera === undefined || (camera as ICamera).isOrthographicCamera === true) return; - this.#mainCullFrustums += 1; - const frustum = _cullFrustum.setFromProjectionMatrix( - _cullProjScreen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse), - ); - // The visible visibility cells, for the whole pass, into a set that is reused and compared - // rather than allocated: two sets trading places, so a settled camera allocates nothing at all. - const next = this.#cullScratch; - next.clear(); - for (const [cell, entry] of this.#cullCells) { - // An occupied cell whose placements have not been decoded yet holds nothing to draw, and its - // Y range is still empty — a volume that cannot be tested against ±Infinity is not one this - // frame's answer may rest on. - if (entry.box.isEmpty() === false && frustum.intersectsBox(entry.box)) next.add(cell); - } - if (!sameSquares(next, this.#visibleSquares)) { - this.#visibleEpoch += 1; - const was = this.#visibleSquares; - this.#visibleSquares = next; - this.#cullScratch = was; - this.#cullScratch.clear(); - } - // A chunk's record is frozen, so its cell's answer is the only thing that can take it out of the - // frame without re-recording it: a write per resident cell that has chunks, at the same - // granularity every other coarse gate in this pass uses. See the `bundles` option. - for (const entry of this.#chunkBundles.values()) { - const visible = this.#visibleSquares.has(entry.cluster); - if (entry.group.visible !== visible) entry.group.visible = visible; - } - const gpu = this.#gpuScene; - const dressed: Array<{ asset: string; shared: SharedBatch }> = []; - const version = gpu.version; - for (const shared of [...this.#shared.values()]) { - if (shared.role !== "main") continue; - // A dressed batch draws from the GPU scene's own buffers, so the frame's per-instance answer is - // the dispatch's and this mesh's own window would be a second, stale copy of it. - if (gpu.on && shared.gpu !== undefined) { - this.#dressGpu(shared, shared.gpu); - // A bundled mesh is in a render list three fixed when it recorded the bundle, and the - // dispatch has already culled per instance: there is nothing coarse left to hide, and a - // `visible` written here is one the replay never reads. See `#bundleIn`. - if (shared.bundled === false) dressed.push({ asset: shared.gpu.asset, shared }); - continue; - } - // A batch the GPU scene does not own, and every one of a world without it: its own window is - // the CPU path's answer, and it is a per-frame answer a frozen render list never re-reads. So - // the record answers the gate instead of the gate writing `visible` into it — a batch the gate - // shows joins, one it hides leaves, and a window that moved is one more record, because the - // record holds the layout the move made. The same trade a cell's chunk record makes, at the - // granularity this gate already uses. See `#bundleIn`. - const outcome = shared.cullFrom(this.#visibleSquares, this.#visibleEpoch); - this.#recordCpuMain(shared, outcome); - if (outcome === "settled") continue; - this.#mainCullWindows += 1; - if (outcome === "repacked") { - this.#mainCullRepacks += 1; - this.#mainCullPacks += 1; - } - } - // A regrow late in the loop replaces the scene's `drawn` — and, a non-grouped key, its `args` — - // after an earlier mesh in the same loop was dressed against the generation before it, so that - // mesh would read a buffer the dispatch no longer writes for one frame. One more pass over the - // same settled set, only when the scene changed under the first, rebinds every mesh to the final - // buffers. Registration is idempotent and every key is already minted and sized, so this pass - // mints nothing and cannot grow the scene again. - if (gpu.on && gpu.version !== version) { - for (const shared of [...this.#shared.values()]) - if (shared.role === "main" && shared.gpu !== undefined) this.#dressGpu(shared, shared.gpu); - // And every shadow key, for the same reason: minting one grew the twin buffers every other - // key was dressed against, so a sibling dressed earlier in the loop above is holding the - // generation the dispatch replaced. The main key's own dress brings its twin with it; this is - // what closes the seam for the keys whose main key was dressed before the grow. - for (const shared of [...this.#shared.values()]) - if (shared.role === "key") - this.#dressShadowKey(shared, shared.mesh.name.slice(0, -GPU_KEY_SUFFIX.length)); - } - // After every key has been dressed, so the asset's answer is the union across its levels and - // parts. - this.#applyGpuMain(dressed); - // After the coarse gate, and only for the main camera: an orthographic one — every shadow - // level's own — returned at the top, so a level's render never dispatches over the main pass's - // compaction. - if (gpu.on) gpu.dispatch(this.#renderer as IRendererLike, camera); - } - - /** - * The coarse gate for the GPU-dressed main pass, computed per asset and reused across every level - * and part of it. - * - * A key's own CPU squares are the level the follow point picked when it built them, and the - * dispatch re-picks the level from the camera every frame — so a key the CPU never gave a cell to - * can be exactly the one the dispatch draws, and a per-key answer hid it: a tree walked into at a - * level it was not built at disappeared. One conservative answer per asset — any key holds a - * visible cell, or still owes its prewarm draw — is what shows all of them, and the indirect - * record decides what each one actually draws. - */ - #applyGpuMain(dressed: readonly { asset: string; shared: SharedBatch }[]): void { - const shown = new Map(); - for (const { asset, shared } of dressed) { - if (shown.get(asset) === true || shared.seenIn(this.#visibleSquares)) shown.set(asset, true); - else if (shown.has(asset) === false) shown.set(asset, false); - } - for (const { asset, shared } of dressed) shared.applyGpuVisible(shown.get(asset) === true); - } - - /** Borrowed scratch box: consume its transformed volume before the next bounds query. */ - #boundsAt(bounds: IWorldAsset["bounds"], matrix: Matrix4): Box3 { - this.#recordBounds.min.fromArray(bounds.min); - this.#recordBounds.max.fromArray(bounds.max); - return this.#recordBounds.applyMatrix4(matrix); - } - - /** - * The one volume per resident visibility cell, shared by every key: its own XZ footprint, and in Y - * the range its resident placements actually reach once their assets' own bounds are added to them, - * padded by `MAIN_CULL_PAD_METRES`. - * - * It is derived here, on admission, and never a frame — a volume walked every frame to answer a - * question about placements that never move is a frame's work for an admission's answer. - * - * It is deliberately not per key. Every key drew the same cell set, so deriving it per key meant - * ~230 frusta and ~230 sets a frame to learn the same answer; one volume per cell is what makes the - * pass O(cells) instead of O(keys × cells). - * - * The Y is what makes it a volume rather than a slab. It used to be the whole terrain height range - * widened by the tallest asset bound in the world — ~100 m of Y on a map with a 10 m relief, which - * made every cell's bounding sphere a ~90 m radius that a view beside the world still cleared, and - * handed the vertex stage the whole ring. This is per placement instead: a sub-square of flat - * ground with ferns on it is the height of its ferns. - */ - - #cullResident(cell: IWorldCell): void { - const per = this.#cellsPerCullCell; - const side = per * this.#cellSize; - const first = Math.floor(cell.x / per); - const firstZ = Math.floor(cell.z / per); - const name = clusterOf(cell.x, cell.z, per); - let entry = this.#cullCells.get(name); - if (entry === undefined) { - // XZ from the grid, padded, and an empty Y: this cell's own placements are unioned in below, - // and a cell holding none of them yet is never visible rather than visible by its footprint. - const box = new Box3( - new Vector3( - this.#minX + first * side - MAIN_CULL_PAD_METRES, - Number.POSITIVE_INFINITY, - this.#minZ + firstZ * side - MAIN_CULL_PAD_METRES, - ), - new Vector3( - this.#minX + (first + 1) * side + MAIN_CULL_PAD_METRES, - Number.NEGATIVE_INFINITY, - this.#minZ + (firstZ + 1) * side + MAIN_CULL_PAD_METRES, - ), - ); - entry = { box, cells: 0 }; - this.#cullCells.set(name, entry); - } - entry.cells += 1; - const box = entry.box; - for (const run of cell.runs) { - const bounds = this.#manifest.assets[this.#canonical(run.asset)]?.bounds; - if (bounds === undefined || run.count === 0) continue; - const records = cellPlacements(this.#placements, run); - for (let index = 0; index < run.count; index += 1) { - const at = index * PLACEMENT_RECORD_FLOATS; - // The placement, widened by its own asset's bounds at the scale it is drawn at, and by the - // pad — a union, so the pad is counted once however many placements there are. - const scale = records[at + 7] as number; - this.#position.fromArray(records, at); - this.#rotation.fromArray(records, at + 3); - this.#scale.setScalar(scale); - this.#matrix.compose(this.#position, this.#rotation, this.#scale); - box.union( - this.#boundsAt(bounds, this.#matrix).expandByScalar( - MAIN_CULL_PAD_METRES * Math.max(1, Math.abs(scale)), - ), - ); - } - } - } - - /** - * Drop the visibility cell an evicted cell sat in once nothing resident is left in it. Records are - * cleared before this runs, so a batch can never be holding a cell the cull no longer knows: an - * unknown cell is not in the visible set, and that would be a tree nobody draws. - * - * The box goes with it, so a cell that comes back derives its volume again — which is what keeps a - * released asset's height out of a cell that no longer holds it. - */ - #cullEvicted(cell: IResidentCell): void { - const name = clusterOf(cell.x, cell.z, this.#cellsPerCullCell); - const entry = this.#cullCells.get(name); - if (entry === undefined) return; - entry.cells -= 1; - if (entry.cells <= 0) this.#cullCells.delete(name); - } - - /** - * Does this update have to run the residency pass, or is the follow point still inside the half - * metre it last moved less than and the world holding still? - * - * "Still" is every kind of work the pass exists to hand on: a model load in flight or queued, a - * cell build in `#jobs` (which is also what queues refilters), a prewarm mint owed or in flight, a - * terrain admission or collider the admission budget refused, and a companion that has not been - * rebuilt since the follow point last moved. A world standing still on a loaded ring answers none - * of those, so the pass is skipped; the first update after anything changes is a full pass again. - */ - #residencyStale(x: number, z: number): boolean { - if ( - Math.hypot( - x - this.#residencyPoint.x, - this.#followY() - this.#residencyPoint.y, - z - this.#residencyPoint.z, - ) >= SETTLED_FOLLOW_METRES - ) - return true; - return ( - this.#limiter.inFlight > 0 || - this.#limiter.queued > 0 || - this.#jobs.length > 0 || - this.#refilterOwed || - this.#prewarmQueue.length > 0 || - this.#prewarmPending > 0 || - this.#shadowMoved === true || - (this.#terrain?.deferredAdmissions ?? 0) > 0 - ); - } - - /** - * Does this update have to run the `maxDistance`/`lods` refilter pass? - * - * Three things, and the pass runs when any of them holds: the follow point has moved - * `LEVEL_REFILTER_STEP_METRES` since the last one, so a walk re-derives its brackets every two - * metres instead of every half; residency changed, because a cell that arrived or left is the - * bracket the pass is about; and the last pass found more stale cell-assets than - * `rebuildsPerUpdate` could queue, so the ones it could not queue are still owed and a still - * follow point has to keep coming back for them. Anything else is the same brackets, so the pass - * is skipped and the world does nothing for that update. - */ - #followY(): number { - return this.#placementReach === undefined ? 0 : (this.#follow.position.y ?? 0); - } - - #refilterStepMetres(): number { - return this.#placementReach === undefined ? LEVEL_REFILTER_STEP_METRES : 0.75; - } - - #refilterStale(x: number, z: number): boolean { - if (this.#refilterOwed) return true; - if (this.#refilterEpoch !== this.#residencyEpoch) return true; - return ( - Math.hypot( - x - this.#refilterPoint.x, - this.#followY() - this.#refilterPoint.y, - z - this.#refilterPoint.z, - ) >= this.#refilterStepMetres() - ); - } - - /** - * Mint this update's prewarm allowance, then settle the readiness signal. - * - * A batch minted while the world is not in a scene is projected by nothing and builds nothing, so - * the ready count only advances on an update that had somewhere to draw: a `load()` loop filling - * the ring mints the whole ring and still leaves the gate shut, which is the honest answer. - */ - #drainPrewarm(): void { - if (this.#released) return; - // The loading rate drops to the same 2-per-update a fresh mesh costs once the loading screen is - // gone. 8 per update is ~150 ms of node builds on the frame that pays it, and the loading - // screen is the only thing that was covering it: a walk that streams a new asset in afterwards - // used to hand that frame's builds to a frame that is trying to hold its rate. - const perUpdate = this.#prewarmSettled ? 2 : PREWARM_PER_UPDATE; - let minted = 0; - while (minted < perUpdate && this.#prewarmQueue.length > 0) { - const entry = this.#prewarmQueue.shift() as IPrewarmEntry; - this.#prewarmKeys.delete(entry.key); - this.#mintPrewarm(entry); - minted += 1; - } - // A minted batch is only *owed* a draw by `#awaitDraw`; nothing made anything draw it. A main - // batch is drawn by the next main render, but a caster is drawn by a shadow level, and the - // levels render when their window moves or something invalidates them — on a loading screen the - // follow point stands still, so every caster minted after the first frame sat in the graph with - // a node no level had ever asked for and the walk's first window move built all of them in one - // shadow pass. Invalidating while the gate is open, minting or not, is what makes the prewarm - // prewarm: the build is paid by the loading screen, which is the only frame rate hiding it. - if (minted > 0) this.#prewarmWait = 0; - // A caster's draw is a shadow level's, and the levels render at most one per frame, so the gate - // keeps asking them to while one is owed — bounded, because a borrow nothing draws is handed - // back by `#releaseImpossibleBorrows` below and a world that never draws one settles anyway. - const waitingOnCasters = this.parent !== null && this.#prewarmCasterOwed > 0; - if (minted > 0 || waitingOnCasters) this.#invalidateShadows?.(); - else if (this.parent !== null) this.#prewarmWait += 1; - this.#releaseImpossibleBorrows(); - if (this.#prewarmQueue.length > 0 || this.#prewarmPending <= 0) return; - if (this.#prewarmWait < PREWARM_WARM_UPDATES) return; - // A caster still owed a draw is a build the walk would otherwise pay inside a shadow pass, so the - // gate holds for it until its borrow is handed back, which is the bound. - if (waitingOnCasters) return; - // A build still owed draws nothing until it lands. When the scene comes up over a ring built - // without it, that is the whole ring re-seeded: the gate settled over 1,378 owed builds and a - // third of Machinefall's forest arrived in the first 4 s of play (PRD-478). The loading screen - // covers them instead. - if (this.#backlog() > 0) return; - this.#prewarmPending = 0; - // The gate is settled, so nothing is owed a draw. A companion no shadow level covers leaves its - // pair non-zero, and `pendingPrewarm` is documented `0` from here on. - this.#prewarmOwed = 0; - this.#prewarmDrawn = 0; - // Retained first-draw hooks belong to the counters just cleared, not later streamed work. - this.#prewarmEpoch += 1; - this.#prewarmWait = 0; - this.#settlePrewarm(); - } - - /** - * The empty batch for one prewarmed key, built exactly as `#batchFor` builds the real one so a - * placement that later wants it draws into this mesh and keeps the node three built for it. Both - * halves of the split are queued: the main key, and the follow point's own caster cluster, which - * is the one the first shadow level render asks for. - */ - #mintPrewarm(entry: IPrewarmEntry): void { - // A world whose shadow levels draw its keys mints no caster half: the two are the same - // placements by two routes, and a level's map can only draw one of them, so a prewarmed half is - // a build behind the gate that pays for nothing and an owed draw the gate waits on until it - // gives up. Decided at the mint rather than at the queue, which is filled before the first - // renderer hands the GPU scene over. The shadow keys are prewarmed where they are minted - // instead; see `#shadowKeyFor`. - if (entry.role !== "main" && this.#keysForShadow()) return; - // A key the retained set is holding needs no prewarmed batch: `#batchFor` rebinds that mesh - // when the walk comes back, and rebinding is the whole point of keeping it. The prewarm did not - // ask, so it minted a second InstancedMesh for a key that already had one — a uuid three built a - // node for twice, in the main pass and in every shadow pass, for the whole of a walk back. - if (this.#released || this.#shared.has(entry.key) || this.#retired.has(entry.key)) return; - const shared = new SharedBatch( - entry.geometry, - entry.material, - this.#runMax.get(entry.asset) ?? 1, - this.#budgets.residentCells + 1, - entry.key, - this.#extentBounds, - entry.role, - ); - shared.smallCaster = this.#smallCaster(entry.asset); - this.#dressMesh(shared, entry.role === "main" && this.#receiveShadow); - // Dressed here, before the walk that fills it: the prewarm is what mints every key of an asset - // before its first placement is swapped, so this is where the whole gate table comes from. - this.#adoptGpu(shared, entry.asset, entry.key, entry.level, entry.part); - this.#shared.set(entry.key, shared); - this.#attach(shared); - this.#prewarmPending += 1; - this.#prewarmMinted += 1; - // Every role is owed a draw by `#awaitDraw` and is shown while empty until that draw is counted: - // the node, the bindings and the pipeline are built by a submission, and one zero-count - // submission per prewarmed batch is the price of having the walk never pay for it. A caster's - // submission is a shadow level's, not the main pass's — which is why `#drainPrewarm` keeps the - // levels rendering while one is owed, and why `VirtualShadowNode`'s `#probe` renders both caster - // layers when it finds one. A caster that is not awaited here is built on the first shadow draw - // of the walk, inside a shadow pass, which is the cost this prewarm exists to move. - this.#awaitDraw(shared); - } - - /** Count this batch's first submitted draw, once, then hand `onBeforeRender` back to whoever had it. */ - #awaitDraw(shared: SharedBatch): void { - const mesh = shared.mesh; - const caster = shared.role !== "main"; - this.#prewarmOwed += 1; - if (caster) { - this.#prewarmCasterMinted += 1; - this.#prewarmCasterOwed += 1; - } - shared.awaitPrewarmDraw(); - // three's `Object3D` carries an inherited no-op, so reading `onBeforeRender` off a fresh mesh - // hands back a function that was never an own property. Writing it back as one leaves every - // batch that has drawn since looking permanently hooked, and the projection's `hasRenderHook` - // reads own properties — so the borrow has to remember whether there was one to give back. - const hadOwn = Object.hasOwn(mesh, "onBeforeRender"); - this.#borrowDraw(shared, mesh, hadOwn, hadOwn ? mesh.onBeforeRender : undefined); - } - - /** - * The borrow that counts `mesh`'s first submitted draw and then gives the mesh its own hook back, - * keyed by the mesh object — so an object that is replaced has to be handed the borrow of the one - * it replaces; see `#carryPrewarm`. - */ - #borrowDraw( - shared: SharedBatch, - mesh: InstancedMesh, - hadOwn: boolean, - own: unknown, - epoch = this.#prewarmEpoch, - ): void { - const caster = shared.role !== "main"; - const borrow = own as ((...args: unknown[]) => void) | undefined; - const counted = (...args: unknown[]): void => { - if (epoch === this.#prewarmEpoch) this.#prewarmDrawn += 1; - if (caster) { - this.#prewarmCasterOwed -= 1; - this.#prewarmCasterDrawn += 1; - } - this.#handBack(mesh, hadOwn, own); - this.#awaited.delete(mesh); - // The node is built and the pipeline is compiled: the batch owes nothing more, so an empty one - // goes back to hiding. - shared.prewarmDrew(); - borrow?.(...args); - }; - // Engine bookkeeping, not a claim on the mesh: the scene-render projection's `hasRenderHook` - // ignores a marked borrow, so a streamed world's prewarmed batches stay eligible for the - // collapse they exist to be folded into. Unmarked, 202 of them permanently declined it. - markEngineRenderHook(counted); - this.#awaited.set(mesh, { batch: shared, borrow: counted, own, hadOwn, waited: 0, epoch }); - mesh.onBeforeRender = counted as typeof mesh.onBeforeRender; - } - - /** - * Hand back the borrow of a mesh whose prewarm draw can never come, so no hook outlives the - * prewarm it served. - * - * A mesh that is merely empty now draws — `visible` at count 0 is what builds its node and its - * pipeline, and the borrow is counted off that one submission — so emptiness is no longer a - * reason to release anything. What is left is a mesh nothing can draw, and which mesh that is - * depends on the role: a main batch is drawn by the main pass, so a world projected by nothing - * (`parent === null`, which is a `load()` loop's prewarm and settles the moment the world joins a - * scene) still owes it one, while a *caster* is drawn by a shadow level, and a world nothing - * projects has no level to draw it — its visible-at-count-0 exception is handed straight back - * rather than held for the whole load. Otherwise a level is a frame's schedule away and the - * borrow waits `PREWARM_RELEASE_UPDATES` updates for it. That is a ceiling rather than a - * diagnosis — a projection that collapsed the world, a camera that never reached the mesh, a game - * that passed no `shadows.invalidate` for the levels to be told by — and it is here so those - * meshes neither hold a hook nor sit visible at count 0 for the whole walk. - */ - #releaseImpossibleBorrows(): void { - const projected = this.parent !== null; - for (const [mesh, entry] of [...this.#awaited]) { - entry.waited += 1; - const caster = entry.batch.role !== "main"; - if (projected === true && entry.waited < PREWARM_RELEASE_UPDATES) continue; - if (projected === false && caster === false) continue; - if (mesh.onBeforeRender === entry.borrow) this.#handBack(mesh, entry.hadOwn, entry.own); - this.#awaited.delete(mesh); - if (caster) this.#prewarmCasterOwed -= 1; - entry.batch.prewarmDrew(); - } - } - - /** Give a borrowed mesh back the hook it had, or none at all when it never had one of its own. */ - #handBack(mesh: InstancedMesh, hadOwn: boolean, own: unknown): void { - if (hadOwn) mesh.onBeforeRender = own as typeof mesh.onBeforeRender; - // Deleted rather than assigned `undefined`: three calls `object.onBeforeRender(...)` - // unconditionally, and an own `undefined` shadows the prototype's no-op and throws. - // quality-allow: the hook must go back to the prototype, which only `delete` can restore. - // biome-ignore lint/performance/noDelete: restoring the prototype lookup is the point. - else delete (mesh as Partial).onBeforeRender; - } - - /** - * Every level and every part of one asset, queued to be minted empty: the main key, and the one - * caster cluster under the follow point. One square because a cluster per cell of the map would - * be a prewarm of the whole package, and the squares a walk reaches first are minted by - * residency, two an update. - */ - #queuePrewarm(asset: IAssetState): void { - const wideKey = `${asset.id}:*@${WIDE_CLUSTER}`; - for (const [level, parts] of asset.levels.entries()) { - for (const [part, entry] of parts.entries()) { - const key = `${asset.id}:${String(level)}:${String(part)}`; - for (const role of ["main", "cluster", "wide"] as const) { - // Only a level that casts has a caster half at all; see `#casts`. - if (role !== "main" && !this.#casts(level)) continue; - // One whole-asset representation covers the far shadow; see `#wideOwed`. - if (role === "wide" && !this.#wideOwed(asset.id, part)) continue; - // A baked impostor's far half is one mesh per asset, not one per level; prewarm the mesh the - // handoff will keep rather than a per-level key it would retire one frame later. - const name = - role === "main" - ? key - : role === "wide" && asset.impostor !== undefined - ? wideKey - : `${key}@${role === "cluster" ? this.#followCluster() : WIDE_CLUSTER}`; - // A retained key is already paid for; see `#mintPrewarm`. - if (this.#shared.has(name) || this.#retired.has(name) || this.#prewarmKeys.has(name)) - continue; - // The wide half is prewarmed with the same shape the swap draws; see `#wideShape`. - const shape = role === "wide" ? this.#wideShape(asset, level, part, entry) : entry; - this.#prewarmKeys.add(name); - this.#prewarmQueue.push({ - asset: asset.id, - role, - geometry: shape.geometry, - key: name, - level, - material: shape.material, - part, - }); - } - } - } - } - - #settlePrewarm(): void { - if (this.#prewarmSettled) return; - this.#prewarmSettled = true; - // The one line a walk's log needs to say the prewarm paid what it exists to pay: - // `shadowPrewarmed` is the casters whose shadow-context node was built behind the gate, and - // `castersUnbuilt` the ones it could not reach — a non-zero there is the walk-time first shadow - // build the prewarm was supposed to move, named rather than inferred. - console.info( - `TN_WORLD_PREWARM minted=${String(this.#prewarmMinted)} ` + - `shadowPrewarmed=${String(this.#prewarmCasterDrawn)} ` + - `castersUnbuilt=${String(Math.max(0, this.#prewarmCasterMinted - this.#prewarmCasterDrawn))}`, - ); - this.#prewarmResolve(); - } - - /** - * The render-cadence dispatch. The engine hands the frame's render camera over, and this world's - * main windows follow it; see {@link #cullMainPass} for why the decision is not the meshes' own. - */ - /** New batch meshes this update may create: scaled while the startup cover hides the world. */ - #freshAllowance(): number { - return this.#covered ? this.#coveredFreshMeshesPerUpdate : this.#freshMeshesPerUpdate; - } - - process(renderer?: IRendererLike, camera?: Camera, covered = false): void { - this.#covered = covered; - try { - this.update(renderer, camera); - } finally { - this.#covered = false; - } - } - - /** - * The adaptive LOD bias control loop. Reads the main pass's smoothed GPU time from the renderer - * and, once per `LOD_BIAS_STEP_SECONDS`, raises the shared bias while that pass is over its share - * of the frame and decays it back toward 1 while it is comfortably under; between the two it - * holds, which is the band that keeps a level sitting on the budget from oscillating. A window with - * no fresh sample is a hold, never a rise on a stale number, and a rise needs the world itself to be - * the thing being drawn — see {@link #biasOverBudget}, which is also what replaced the warmup - * seconds. The decay is never gated: a world that can afford its main pass gives the bias back. - */ - #adaptLodBias(renderer: IRendererLike | undefined): void { - if (this.#adaptiveLod === false) { - // Byte-identical to the pre-bias selection: the multiplier is pinned at 1 every frame. - this.#applyLodBias(LOD_BIAS_MIN); - return; - } - const now = this.#now(); - if (now - this.#lodBiasStepAt < LOD_BIAS_STEP_SECONDS * 1000) return; - const source = renderer ?? this.#renderer; - // The frame the budget is a share of: the game's resolved target, 60 fps until one is known. - // Never the frame just measured — an expensive pass makes its own frame long and would pass its - // own test, and a fast one shrank the share until a 3 ms pass coarsened a world running at - // 400 fps against a 120 fps target (PRD-478). - const target = source?.targetFps?.(); - const frameMs = 1000 / (target !== undefined && target > 0 ? target : DEFAULT_TARGET_FPS); - const gpuMs = source?.gpuMainMs?.(); - if (gpuMs === undefined) return; - this.#lodBiasStepAt = now; - const affordableMs = this.#mainGpuShare * frameMs; - if (!(affordableMs > 0)) return; - const bias = - gpuMs > affordableMs - ? this.#biasOverBudget() - : gpuMs < LOD_BIAS_DECAY_SHARE * affordableMs - ? Math.max(LOD_BIAS_MIN, this.#lodBias * LOD_BIAS_DECAY) - : this.#lodBias; - if (bias === this.#lodBias) return; - this.#applyLodBias(bias); - if (now - this.#lodBiasToldAt >= LOD_BIAS_MARKER_SECONDS * 1000) { - this.#lodBiasToldAt = now; - const triangles = this.gpuSceneTally()?.triangles; - console.info( - `${LOD_BIAS_MARKER} bias=${bias.toFixed(3)} gpuMs=${gpuMs.toFixed(2)} ` + - `budgetMs=${affordableMs.toFixed(2)} ` + - `tris=${triangles === undefined ? "n/a" : String(triangles)}`, - ); - } - } - - /** - * The next bias while the main pass is over its share of the frame, or the bias unchanged when the - * world is not what that pass is drawing. - * - * A load's GPU time is shader compiles, buffer uploads and the prewarm — none of it main-pass world - * geometry, so a coarser selection cannot take any of it back. What it costs is the first playable - * frame: every tree selects the level authored for its distance times the bias, the coarsest levels - * hold almost nothing, and the shadow casters, which take no bias, leave a band of tree shadows with - * no trees 80-300 m out. Measured on Machinefall (RTX 2080, WebGPU), the bias climbed 1.08 → 2.33 - * over six `TN_LOD_BIAS` steps, all of them before the loading overlay dropped. - * - * So the rise waits for the two measurements that say the world is being drawn: the prewarm gate — - * what a game's loading screen waits on, and `stats().pendingPrewarm` as the same thing as a number - * — and the main pass's own GPU-selected triangle tally, where a tally that landed and says zero is - * a world drawn as nothing. A world with no tally at all (no GPU scene, or nothing landed yet) has no - * opinion about its own triangles, so the prewarm speaks for it alone. Both are measurements of the - * thing being judged, which is what the warmup seconds were standing in for. - */ - #biasOverBudget(): number { - if (this.#prewarmQueue.length > 0 || this.#prewarmOwed > this.#prewarmDrawn) - return this.#lodBias; - if (this.gpuSceneTally()?.triangles === 0) return this.#lodBias; - return Math.min(LOD_BIAS_MAX, this.#lodBias * LOD_BIAS_RISE); - } - - /** Writes the bias to both selection paths: the shared CPU multiplier and the GPU uniform. */ - #applyLodBias(bias: number): void { - this.#lodBias = bias; - setLodBias(bias); - this.#gpuScene.setLodBias(bias); - } - - /** - * Where the ring centres: the follow point led by its smoothed velocity. Samples closer than a - * few milliseconds are the same frame and move nothing; a jump longer than the ring is a teleport - * and resets the velocity rather than reading as speed. - */ - #ahead(x: number, z: number): { x: number; z: number } { - if (this.#prefetchSeconds === 0) return { x, z }; - const now = this.#now(); - const last = this.#lastSample; - if (last === undefined) { - this.#lastSample = { t: now, x, z }; - } else { - const seconds = (now - last.t) / 1000; - if (seconds >= 0.004) { - const dx = x - last.x; - const dz = z - last.z; - if (Math.hypot(dx, dz) > this.#cellSize * this.#ring) { - this.#velocityX = 0; - this.#velocityZ = 0; - } else { - // ~0.25 s smoothing, so one jittery frame does not swing the ring. - const blend = Math.min(1, seconds / 0.25); - this.#velocityX += (dx / seconds - this.#velocityX) * blend; - this.#velocityZ += (dz / seconds - this.#velocityZ) * blend; - } - this.#lastSample = { t: now, x, z }; - } - } - let leadX = this.#velocityX * this.#prefetchSeconds; - let leadZ = this.#velocityZ * this.#prefetchSeconds; - const limit = this.#cellSize * this.#ring * 0.75; - const length = Math.hypot(leadX, leadZ); - if (length > limit) { - leadX *= limit / length; - leadZ *= limit / length; - } - return { x: x + leadX, z: z + leadZ }; - } - - attachRenderer(renderer: IRendererLike): void { - this.#terrain?.attachRenderer(renderer); - } - - /** Current per-asset reference count; an asset absent from the map is `0`. */ - assetRefCounts(): Readonly> { - const counts: Record = {}; - for (const [id, asset] of this.#assets) counts[id] = asset.refcount; - return counts; - } - - /** - * Turn on the GPU-selected tally for the rest of the world's life. - * - * The engine calls this when the frame budget is on, and a `gpuSceneValidate` turns it on without - * it. Set `gpuSceneTally: true` at load instead when a world is driven outside a frame budget. - */ - enableGpuSceneTally(): void { - this.#gpuTally = true; - } - - /** - * The GPU's own main-pass selection from the last landed tally, or `undefined` before one lands. - * - * `triangles` is the GPU-selected count, which `renderer.info.render.triangles` cannot give for an - * indirect draw: three counts the mesh capacity there, an upper bound over what the kernel could - * select. This is what the kernel selected. Main pass only, and `ageFrames` is its staleness. - */ - gpuSceneTally(): IWorldCellsGpuTally | undefined { - const gpu = this.#gpuScene.report(); - if (gpu.gpuTriangles === undefined) return undefined; - return { - ageFrames: gpu.gpuTallyAgeFrames ?? 0, - instances: gpu.gpuInstances ?? 0, - triangles: gpu.gpuTriangles, - }; - } - - stats(): IWorldCellsStats { - const gpu = this.#gpuScene.report(); - const census = this.#gpuCensus(); - return { - admission: { ...this.#admission }, - bundle: { - children: this.#mainBundleChildren() + this.#chunkBundleChildren, - on: - this.#bundlesWanted && (this.#mainBundleChildren() > 0 || this.#chunkBundleChildren > 0), - cpuMeshes: [...this.#shared.values()].filter( - (shared) => shared.bundled && shared.gpu === undefined, - ).length, - gpuMeshes: [...this.#shared.values()].filter( - (shared) => shared.bundled && shared.gpu !== undefined, - ).length, - eligibility: { ...this.#bundleSafety.stats }, - reason: this.#bundleReason, - records: this.#bundleRecords, - }, - evictions: this.#evictions, - failures: this.#failures, - gpuScene: { - dispatches: gpu.dispatches, - instances: gpu.instances, - keys: gpu.keys, - on: gpu.on, - reason: gpu.reason, - dressed: census.dressed, - meshes: census.meshes, - ...(gpu.gpuTriangles === undefined - ? {} - : { - gpuInstances: gpu.gpuInstances, - gpuTallyAgeFrames: gpu.gpuTallyAgeFrames, - gpuTriangles: gpu.gpuTriangles, - }), - ...(gpu.occlusion === undefined ? {} : { occlusion: gpu.occlusion }), - }, - hlod: { - on: this.#hlod, - loaded: [...this.#cellProxies].filter( - (state) => state.prepared !== undefined && !state.cancelled, - ).length, - compiled: [...this.#cellProxies].filter((state) => state.compiled && !state.cancelled) - .length, - observed: [...this.#cellProxies].filter( - (state) => this.#proxyObserved(state) && !state.cancelled, - ).length, - active: [...this.#cellProxies].filter((state) => state.active).length, - pending: [...this.#cellProxies].filter( - (state) => - state.pending || - state.compiling || - (!state.cancelled && !state.compiled && state.prepared !== undefined), - ).length, - bytes: this.#hlodBytes, - refused: this.#hlodRefused, - reason: this.#hlodReason, - failures: this.#hlodFailures, - }, - impostor: this.#impostorStats(), - instances: this.#instances, - loadsInFlight: this.#limiter.inFlight, - loadsQueued: this.#limiter.queued, - mainCull: { - frustums: this.#mainCullFrustums, - repacks: this.#mainCullPacks, - visibleEpoch: this.#visibleEpoch, - windows: this.#mainCullWindows, - }, - // Minted meshes still owed a first draw, not minted meshes. The two used to be the same - // number, so a loading bar read minting as the whole of the prewarm and then stood still for - // the whole draw — the node builds a bar cannot show. The gate is a promise about batches, so - // the settle zeroes this pair: `0` once `prewarmed` has resolved. - pendingPrewarm: this.#prewarmQueue.length + this.#prewarmOwed - this.#prewarmDrawn, - prewarmMinted: this.#prewarmMinted, - pressure: { ...this.#pressure }, - // The pass itself, counted whether or not it found anything: `rebuilds` cannot tell a skipped - // pass from one that scanned every resident cell and found the same brackets, and that gap is - // the whole cost of the 2 m step. See `#refilterStale`. - refilters: this.#refilters, - refilterEntries: this.#staleEntries, - rebuilds: this.#rebuilds, - levelRefilters: this.#levelRefilters, - // The share of `rebuilds` the swap found unchanged, which is the honest report of how much of - // the refilter pass is a bracket that was wider than any placement. - unchanged: this.#unchanged, - residentCells: this.#resident.size, - loadedCells: [...this.#resident.values()].filter((cell) => this.#cellLoaded(cell)).length, - residentKeys: [...this.#resident.keys()].sort(), - residentTiles: this.#terrain?.residentTileCount ?? 0, - residentColliders: this.#hasColliders ? (this.#terrain?.residentColliderKeys.length ?? 0) : 0, - }; - } - - detach(): void { - this.dispose(); - } - - #cellLoaded(cell: IResidentCell): boolean { - return cell.chunksLoaded && cell.completedRuns.size === cell.cell.runs.length; - } - - /** Completed content intersecting a square around a fixed spawn, in world metres. */ - readinessAt( - position: { readonly x: number; readonly z: number }, - radius = 0, - ): IWorldRegionReadiness { - if ( - !Number.isFinite(position.x) || - !Number.isFinite(position.z) || - !Number.isFinite(radius) || - radius < 0 - ) - throw new Error("WorldCells readiness requires finite coordinates and a nonnegative radius."); - const lowX = Math.floor((position.x - radius - this.#minX) / this.#cellSize); - const highX = Math.floor((position.x + radius - this.#minX) / this.#cellSize); - const lowZ = Math.floor((position.z - radius - this.#minZ) / this.#cellSize); - const highZ = Math.floor((position.z + radius - this.#minZ) / this.#cellSize); - let requiredCells = 0; - let loadedCells = 0; - let failures = 0; - for (const cell of this.#cells) { - if (cell.x < lowX || cell.x > highX || cell.z < lowZ || cell.z > highZ) continue; - requiredCells++; - const resident = this.#resident.get(cellKey(cell.x, cell.z)); - if (resident && this.#cellLoaded(resident)) loadedCells++; - if (resident?.chunkFailed) failures++; - for (const run of cell.runs) - if (this.#assets.get(this.#canonical(run.asset))?.failed) failures++; - } - const terrain = this.#terrain?.readinessAt(position, radius) ?? { required: 0, loaded: 0 }; - return { - requiredCells, - loadedCells, - requiredTerrainTiles: terrain.required, - loadedTerrainTiles: terrain.loaded, - failures, - cancelled: this.#released, - ready: - !this.#released && - failures === 0 && - loadedCells === requiredCells && - terrain.loaded === terrain.required, - }; - } - - dispose(): void { - if (this.#released) return; - this.#released = true; - for (const [mesh, entry] of [...this.#awaited]) { - if (mesh.onBeforeRender === entry.borrow) this.#handBack(mesh, entry.hadOwn, entry.own); - } - this.#awaited.clear(); - for (const cell of [...this.#resident.values()]) this.#evict(cell); - this.#shadowMoved = false; - this.#shadowRegion = undefined; - this.#shadowAdmissionRegion = undefined; - this.#shadowFull = false; - // Every chunk is gone by now, so the surfaces the world owns on their behalf are the last thing - // holding them, and they are released here rather than by whichever chunk contributed them. - for (const material of this.#proxyMaterials.values()) - this.#failures += release(material) ? 1 : 0; - this.#proxyMaterials.clear(); - this.#renderer = undefined; - this.#camera = undefined; - this.#recordedWhileBusy.clear(); - // After every asset's release above, so no record is still held by a surface; disposes the cached - // atlases and aborts any bake still in flight. - this.#drainImpostors(); - this.#gpuScene.dispose(); - this.#publishedGpuGates.clear(); - this.#gpuLevelKeys.clear(); - this.#bundleSafety.clear(); - this.#gpuResident.clear(); - if (this.#drainShared() > 0) this.#failures += 1; - // The batches `#drainShared` just retired. Their geometry and material were released by the - // refcount path above, which is why they are disposed and not reused. - for (const shared of this.#retired.values()) shared.mesh.dispose(); - this.#retired.clear(); - this.#retiredBytes = 0; - // A world torn down mid-prewarm must not leave a game awaiting its gate. - this.#prewarmQueue.length = 0; - this.#prewarmKeys.clear(); - this.#settlePrewarm(); - drainMeshPool(); - this.#terrain?.dispose(); - this.removeFromParent(); - } - - #updateResidency(followCell: { x: number; z: number }, x: number, z: number): void { - const wanted: Array<{ cell: IWorldCell; distance: number }> = []; - for (const cell of this.#cells) { - if (Math.max(Math.abs(cell.x - followCell.x), Math.abs(cell.z - followCell.z)) > this.#ring) - continue; - const centerX = this.#minX + (cell.x + 0.5) * this.#cellSize; - const centerZ = this.#minZ + (cell.z + 0.5) * this.#cellSize; - wanted.push({ cell, distance: Math.hypot(x - centerX, z - centerZ) }); - } - wanted.sort((a, b) => a.distance - b.distance || a.cell.z - b.cell.z || a.cell.x - b.cell.x); - - for (const resident of [...this.#resident.values()]) { - if ( - Math.max(Math.abs(resident.x - followCell.x), Math.abs(resident.z - followCell.z)) > - this.#ring + 1 - ) - this.#evict(resident); - } - // A follow point that crossed more than one cell in one pass jumped (a teleport, a review - // camera). Only then does the hysteresis ring yield. Read off the follow point itself, not the - // lookahead `followCell`: that leads by up to 1.5 cells, so a walk that stopped and started moved - // it by two, and yielding there evicted and re-admitted a whole row each time. - const here = { - x: Math.floor((this.#follow.position.x - this.#minX) / this.#cellSize), - z: Math.floor((this.#follow.position.z - this.#minZ) / this.#cellSize), - }; - const last = this.#residencyCell; - const jumped = - last !== undefined && Math.max(Math.abs(last.x - here.x), Math.abs(last.z - here.z)) > 1; - this.#residencyCell = here; - - for (const candidate of wanted) { - const key = cellKey(candidate.cell.x, candidate.cell.z); - if (this.#resident.has(key)) continue; - const instances = candidate.cell.runs.reduce((total, run) => total + run.count, 0); - const bytes = instances * PLACEMENT_RECORD_BYTES; - // After a jump, a cell the one-ring hysteresis above kept is not wanted, so it yields its share - // of the budgets to one that is. Without this the budgets stayed full of the ring the camera - // jumped from and the cells around it were refused until it moved again. - while ( - jumped && - (this.#resident.size >= this.#budgets.residentCells || - this.#instances + this.#farInstances + instances > this.#budgets.instances || - this.#bytes + this.#farBytes + this.#hlodBytes + bytes > this.#budgets.bytes) - ) { - const spare = this.#farthestUnwanted(followCell); - if (spare === undefined) break; - this.#evict(spare); - } - if (this.#resident.size >= this.#budgets.residentCells) { - this.#pressure.cells += 1; - continue; - } - // The retained far aggregates are physical allocations the renderer holds too, so they are - // charged against the same declared limits as the near ring: admitting a cell over the top of - // them would put the world past `budgets.instances`/`budgets.bytes` with the far half invisible - // to this check. See `#farBudgetRefuses`, the far half of the same total. - if (this.#instances + this.#farInstances + instances > this.#budgets.instances) { - this.#pressure.instances += 1; - continue; - } - if (this.#bytes + this.#farBytes + this.#hlodBytes + bytes > this.#budgets.bytes) { - this.#pressure.bytes += 1; - continue; - } - this.#admit(candidate.cell, instances, bytes); - } - } - - /** The resident cell farthest outside the wanted ring, or none when every resident is wanted. */ - #farthestUnwanted(followCell: { x: number; z: number }): IResidentCell | undefined { - let farthest: IResidentCell | undefined; - let reach = this.#ring; - for (const resident of this.#resident.values()) { - const away = Math.max( - Math.abs(resident.x - followCell.x), - Math.abs(resident.z - followCell.z), - ); - if (away > reach) { - reach = away; - farthest = resident; - } - } - return farthest; - } - - #admit(cell: IWorldCell, instances: number, bytes: number): void { - const state: IResidentCell = { - mainChunkMeshes: [], - chunkInstances: new Map(), - completedRuns: new Set(), - chunksLoaded: (cell.chunks?.length ?? 0) === 0, - chunkFailed: false, - batches: [], - bytes, - cell, - chunks: [], - filterFar: Number.NEGATIVE_INFINITY, - filterNear: Number.POSITIVE_INFINITY, - generation: this.#generation++, - instances, - key: cellKey(cell.x, cell.z), - retainedModelPaths: [], - x: cell.x, - z: cell.z, - }; - this.#resident.set(state.key, state); - this.#cullResident(cell); - this.#residencyEpoch += 1; - this.#instances += instances; - this.#bytes += bytes; - for (const run of cell.runs) this.#acquire(run, state); - if (cell.chunks !== undefined && cell.chunks.length > 0) this.#startChunkLoad(state); - } - - /** - * The asset id a run's `asset` really is: itself, or the one canonical id its model is loaded - * under. Every path from a run to the state that holds it goes through here, so a duplicate costs - * one `IAssetState`, one model load, one set of `canonical:level:part` keys and one refcount — - * released when the last run naming any member of the group leaves the ring. A placement keeps its - * own transform, because those are read from the run's own records. - */ - #canonical(id: string): string { - return this.#aliases.get(id) ?? id; - } - - /** One fresh asset state for a definition, registered and its gates noted. */ - #newAsset(id: string, definition: IWorldAsset): IAssetState { - const asset: IAssetState = { - ...assetLevels(definition), - definition, - disposed: false, - id, - levels: [], - pending: false, - failed: false, - refcount: 0, - hlodSafe: false, - retainedPaths: [], - resolvedGlb: "", - spilled: [], - }; - if (this.#lodHysteresis > 0 && definition.lods !== undefined) { - asset.gates = [ - ...asset.gates, - ...asset.distances.slice(1).map((gate) => gate * (1 - this.#lodHysteresis)), - ]; - asset.threshold = Math.min(...asset.gates); - } - this.#assets.set(id, asset); - for (const gate of asset.gates) this.#noteGate(gate); - return asset; - } - - #acquire(run: IWorldRun, cell: IResidentCell): void { - const id = this.#canonical(run.asset); - let asset = this.#assets.get(id); - if (asset === undefined) { - const definition = this.#manifest.assets[id]; - if (definition === undefined) return; - asset = this.#newAsset(id, definition); - } - asset.refcount += 1; - if (asset.levels.length > 0) { - this.#queueBuild(asset, cell, run); - return; - } - if (!asset.pending) this.#startAssetLoad(asset); - } - - /** - * Ask for one cell-asset run to be built, and do not build it yet. - * - * Admission is the only thing in this class that has no frame-time ceiling, so it is the only - * thing that became a queue: the cell keeps its residency slot from `#admit`, the work waits for - * a frame with budget in it, and a game that outruns the backlog sees it as `admission.backlog` - * rather than as a frozen frame. A run already queued is not queued twice, which is what stops a - * refilter — stale for as many frames as it waits — from stacking a second copy of itself. - * - * `replaced` is a refilter's outgoing batches, captured now and kept drawn until the replacement - * is attached, so a rebuild that waits three frames shows the old level for those three frames - * instead of a hole. - */ - #queueBuild( - asset: IAssetState, - cell: IResidentCell, - run: IWorldRun, - replace = false, - force = false, - ): void { - // Keyed on the run, not the asset: one cell can hold two runs of the same canonical asset — - // two names for one model — and each has its own records to build. A token per asset made the - // second run a no-op and dropped its placements on the floor. - if (this.#queued.has(run)) return; - // A cell that already draws this run is not built again, which is where an adopted asset's - // second acquire over a still-resident cell lands. - if (!replace && cell.batches.some((entry) => entry.asset === asset.id && entry.run === run)) - return; - this.#queued.add(run); - this.#jobs.push({ - asset, - batches: undefined, - boxes: undefined, - cell, - force, - gpuByLevel: undefined, - filterY: this.#followY(), - filterX: this.#follow.position.x, - filterZ: this.#follow.position.z, - fresh: [], - replacement: replace, - next: 0, - published: 0, - records: undefined, - replaced: replace - ? cell.batches.filter((entry) => entry.asset === asset.id && entry.run === run) - : [], - roots: undefined, - run, - sources: undefined, - }); - } - - /** - * Spend the frame's budget on queued builds, one unit each, and stop when it is gone. - * - * The queue is served in admission order, which is nearest cell first, and a build is taken to - * completion before the next one starts: the units of one run are cheaper together than the - * per-frame bookkeeping of interleaving them, and the cell behind it is the same distance away. - * A build whose cell or asset has gone is dropped rather than resumed — there is nothing left to - * draw it into — and a frame that runs out of budget leaves the rest of the queue for the next. - */ - #drain(budget: IAdmissionBudget): void { - const index = 0; - while (index < this.#jobs.length) { - const job = this.#jobs[index] as IBuildJob; - if (this.#resident.get(job.cell.key) !== job.cell || job.asset.levels.length === 0) { - this.#forget(index, job); - continue; - } - let finished = false; - if ( - !budget.admit(() => { - finished = this.#step(job); - }) - ) - break; - // Out of fresh meshes this frame: later jobs would only stall the same way. - if (this.#meshStalled) break; - // A finished build leaves the queue at this index, so the next one is served without a skip. - if (finished) { - job.cell.completedRuns.add(job.run); - this.#forget(index, job); - // A camera that moved during this bounded build may already have consumed the global - // refilter step before these batches existed. Owe one pass after publication, even if - // it now stands still, so the coherent old snapshot can converge to the latest pose. - if ( - Math.hypot( - this.#follow.position.x - job.filterX, - this.#followY() - job.filterY, - this.#follow.position.z - job.filterZ, - ) >= this.#refilterStepMetres() - ) - this.#refilterOwed = true; - } - } - } - - #forget(index: number, job: IBuildJob): void { - this.#jobs.splice(index, 1); - this.#queued.delete(job.run); - } - - /** Units of work the queue still owes: placement slices left, plus meshes left to publish. */ - #backlog(): number { - let units = 0; - for (const job of this.#jobs) { - const meshes = job.batches === undefined ? 0 : publishCount(job.batches); - units += - Math.ceil(Math.max(0, job.run.count - job.next) / ADMISSION_SLICE_PLACEMENTS) + - Math.max(0, meshes - job.published); - } - return units; - } - - /** - * One unit of one build: a slice of placements, or one mesh built, or the swap that attaches the - * finished batch and retires what a refilter replaced. `true` means the job is done. - */ - #step(job: IBuildJob): boolean { - // A queued run may wait behind admission or fresh-mesh work while the camera moves. - // Take its filter snapshot when the first placement slice actually starts, then keep - // that snapshot for every slice so one publication never mixes different camera poses. - if (job.next === 0 && job.batches === undefined) { - job.filterX = this.#follow.position.x; - job.filterY = this.#followY(); - job.filterZ = this.#follow.position.z; - } - if (job.next < job.run.count) { - job.batches ??= newBatches(job.asset); - // One box per `(level, part)` beside the batches it measures, so the swap that hands the - // records to the renderer can name where they are; see `#addPlacements`. - job.boxes ??= newBoxes(job.batches); - const end = Math.min(job.next + ADMISSION_SLICE_PLACEMENTS, job.run.count); - this.#addPlacements(job, job.next, end); - job.next = end; - return false; - } - const batches = job.batches; - // A run with nothing placed in it publishes nothing, exactly as an unbounded build would. - if (batches === undefined) return true; - if (job.published < publishCount(batches)) { - this.#buildOne(job, batches); - return false; - } - return this.#swap(job); - } - - /** - * Hand the cell its finished batches and take back the ones they replace, in one step. - * - * This is the no-hole rule. Attaching and detaching in the same synchronous block means no frame - * ever shows the replacement beside the batch it replaces, and a refilter that waits three frames - * for budget shows the level the cell already had for those three frames rather than nothing. - */ - #swap(job: IBuildJob): boolean { - const { cell } = job; - // A refilter that rebuilt byte-for-byte the records the cell already draws must write, clear and - // move nothing. `#staleIn` queues on the *cell's* distance bracket, which is wider than any one - // placement's, so a rebuild whose level or cull answer did not move still comes back identical. - // A matched pair is settled here instead: the outgoing entry keeps its block and its place in - // the cell, only its filter point moves, so the next gate is measured from where the follow - // point is now rather than from where it was when the answer was already right. Everything - // else falls through to the swap below untouched. - for (let index = job.fresh.length - 1; index >= 0 && job.force === false; index -= 1) { - const fresh = job.fresh[index] as ICellBatch; - const at = job.replaced.findIndex( - (old) => - old.level === fresh.level && old.part === fresh.part && old.batch.equals(fresh.batch), - ); - if (at < 0) continue; - const same = job.replaced.splice(at, 1)[0] as ICellBatch; - this.#unchanged += 1; - same.lastFilterY = fresh.lastFilterY; - same.lastFilterX = fresh.lastFilterX; - same.lastFilterZ = fresh.lastFilterZ; - this.#widenFilterRange(cell, fresh.lastFilterX, fresh.lastFilterZ); - job.fresh.splice(index, 1); - // The batch that stays is the one that was already drawing, so its source records are its own - // — and a cell the ring built before the scene came up has none. This is the only path that - // hands them over for a rebuild whose records came out identical, which is every one of them: - // the records are the same, so the fast path is always the one taken. - if (this.#gpuScene.on && same.gpu === undefined) same.gpu = this.#placeSources(job, same); - } - // Every segment first, so a frame out of fresh meshes leaves the old batches drawing whole. - for (const entry of job.fresh) { - if (entry.batch.count === 0) continue; - // Both halves for every fresh entry, whether or not a stalled frame already claimed one: - // skipping an entry that has its main block meant the retry never asked for its cluster, so - // those records reached the main pass and no shadow map. - if (entry.segment < 0) { - const shared = this.#sharedFor(job.asset.id, entry); - const segment = - shared === undefined - ? undefined - : this.#segmentIn(shared, entry.batch.count, this.#cullCellOf(cell)); - if (shared === undefined || segment === undefined) { - this.#meshStalled = true; - return false; - } - entry.shared = shared; - entry.segment = segment; - } - // The caster cluster, when this level casts. A frame that runs out of fresh meshes between - // the main mesh and its cluster leaves the main one drawing whole and takes the cluster next - // frame, which is the same no-hole rule as above: never half a replacement. - if (!this.#claimCaster(job.asset.id, entry, cell)) { - this.#meshStalled = true; - return false; - } - // The wide half, by the same no-hole rule: a key with a cluster and no wide caster draws into - // a level that may already be reading the wide layer with nothing in it. - if (!this.#claimWide(job.asset.id, entry)) { - this.#meshStalled = true; - return false; - } - } - for (const entry of job.replaced) { - const at = cell.batches.indexOf(entry); - if (at >= 0) cell.batches.splice(at, 1); - this.#clearSegment(entry); - } - for (const entry of job.fresh) { - cell.batches.push(entry); - if (entry.shared !== undefined) entry.shared.write(entry.segment, entry.batch); - if (this.#gpuScene.on) entry.gpu = this.#placeSources(job, entry); - if (entry.caster !== undefined) entry.caster.write(entry.casterSegment, entry.batch); - // The wide half draws the whole-asset impostor at the placement root when this entry kept one, - // and the source part shape otherwise; see `ICellBatch.wideRoot`. - if (entry.wide !== undefined) - entry.wide.write(entry.wideSegment, entry.wideRoot ?? entry.batch); - } - // The source meshes and records are now published together. Until this point a queued impostor - // replacement may have already changed the GPU slot table while the old mesh still draws. - this.#publishGpuGates(job.asset); - // A batch the walk just wrote to, cleared or compacted holds new records, so the shadow levels - // that drew the old ones are stale. One flag, told at most once a second. - if (job.fresh.some((entry) => entry.batch.count > 0)) - this.#shadowRecordsMoved(this.#changedBounds(job.fresh), true); - if (job.replaced.some((entry) => entry.batch.count > 0)) - this.#shadowRecordsMoved(this.#changedBounds(job.replaced)); - // This run's near representation is attached, so the far mesh stops drawing its original roots in - // the same synchronous block: a cell is never drawn by both and never by neither. A no-op until - // the asset's atlas lands; see `#syncFarSegments` for the build-time half and `#restoreFar`. - this.#disableFar(job.run); - return true; - } - - /** - * The world bounds of these records. A missing bound keeps the tell a blanket one. - */ - #changedBounds(batches: readonly ICellBatch[]): Box3 | undefined { - let union: Box3 | undefined; - for (const entry of batches) { - if (entry.batch.count === 0) continue; - const box = entry.shadowBounds; - if ( - box === undefined || - box.isEmpty() || - ![...box.min.toArray(), ...box.max.toArray()].every(Number.isFinite) - ) - return undefined; - if (union === undefined) union = box.clone(); - else union.union(box); - } - return union; - } - - /** - * Claim this cell's block in its caster cluster, when the level casts and it does not have one. - * `false` means the frame's fresh allowance is spent and the swap waits, so the main mesh and its - * cluster are never half attached. - */ - #claimCaster(assetId: string, entry: ICellBatch, cell: IResidentCell): boolean { - const level = this.#castLevelOf(entry); - if (entry.casterSegment >= 0 || level === undefined) return true; - // Nothing to claim, and nothing to wait for: a world whose levels draw its keys mints no caster - // half at all (`#casterFor`), so reading that as a spent allowance refused every swap in the - // world — the main mesh was never attached, no cell published its placements, and the main pass - // drew nothing while its meshes sat there prewarmed and empty. - if (this.#keysForShadow()) return true; - const caster = this.#casterFor(assetId, entry, cell, level); - const at = caster === undefined ? undefined : this.#segmentIn(caster, entry.batch.count); - if (caster === undefined || at === undefined) return false; - entry.caster = caster; - entry.casterSegment = at; - return true; - } - - /** - * Claim this cell's block in its key's wide caster mesh, when the level casts and it does not - * have one. `false` means the frame's fresh allowance is spent and the swap waits, so the - * clusters and the wide half are never half attached. - */ - #claimWide(assetId: string, entry: ICellBatch): boolean { - const level = this.#castLevelOf(entry); - if (entry.wideSegment >= 0 || level === undefined) return true; - // A part the asset's one whole-asset representation already covers: the entry keeps its near - // main and cluster, and the far shadow reads part 0's one quad; see `#wideOwed`. - if (!this.#wideOwed(assetId, entry.part)) return true; - // As `#claimCaster`: the key twin is this level's wide half, and there is no mesh to wait on. - if (this.#keysForShadow()) return true; - const wide = this.#wideFor(assetId, entry, level); - // The impostor's far half draws the placement root, not `placement * part local`, so its block - // is sized to the root records the build kept beside the part's own; see `ICellBatch.wideRoot`. - const count = entry.wideRoot?.count ?? entry.batch.count; - const at = wide === undefined ? undefined : this.#segmentIn(wide, count); - if (wide === undefined || at === undefined) return false; - entry.wide = wide; - entry.wideSegment = at; - return true; - } - - #clearSegment(entry: ICellBatch): void { - if (entry.shared !== undefined && entry.segment >= 0) entry.shared.clear(entry.segment); - // The source records go back with the block that held them, on every path a cell's records leave: - // a refilter's replacement, an eviction, and a queued build dropped with its cell. Releasing one - // twice is a no-op, which is what lets a level's parts share a single list. - if (entry.gpu !== undefined) { - const placed = entry.gpu.length; - for (const at of entry.gpu) this.#gpuScene.release(at); - const left = (this.#gpuResident.get(entry.asset) ?? 0) - placed; - if (left > 0) this.#gpuResident.set(entry.asset, left); - else this.#gpuResident.delete(entry.asset); - // Before the halves go below, which is what clears the handles this reads. - this.#countPlaced(entry, -placed); - } - entry.gpu = undefined; - if (entry.caster !== undefined && entry.casterSegment >= 0) - entry.caster.clear(entry.casterSegment); - if (entry.wide !== undefined && entry.wideSegment >= 0) entry.wide.clear(entry.wideSegment); - entry.shared = undefined; - entry.segment = -1; - entry.caster = undefined; - entry.casterSegment = -1; - entry.wide = undefined; - entry.wideSegment = -1; - } - - /** - * One cell's records changed, so the shadow levels that cover it redraw — at most once a second, - * and only for a game that passed the hook. The levels cull clusters themselves now, so nothing - * has to be copied for them; the flag only says "what you last drew is out of date". - * - * `bounds` is the records that changed and is kept until the tell, unioned with whatever else - * changed in the same second. A change with no bounds is a blanket one: it has to be, because - * there is nothing narrower to say. - */ - #shadowRecordsMoved(bounds?: Box3, admission = false): void { - this.#shadowMoved = true; - if ( - bounds === undefined || - bounds.isEmpty() || - ![bounds.min.x, bounds.min.y, bounds.min.z, bounds.max.x, bounds.max.y, bounds.max.z].every( - Number.isFinite, - ) - ) { - this.#shadowFull = true; - return; - } - const current = admission ? this.#shadowAdmissionRegion : this.#shadowRegion; - if (current === undefined) { - const region: IShadowRegion = { - ...(admission ? { admission: true } : {}), - max: { x: bounds.max.x, y: bounds.max.y, z: bounds.max.z }, - min: { x: bounds.min.x, y: bounds.min.y, z: bounds.min.z }, - }; - if (admission) this.#shadowAdmissionRegion = region; - else this.#shadowRegion = region; - return; - } - current.min.x = Math.min(current.min.x, bounds.min.x); - current.min.y = Math.min(current.min.y, bounds.min.y); - current.min.z = Math.min(current.min.z, bounds.min.z); - current.max.x = Math.max(current.max.x, bounds.max.x); - current.max.y = Math.max(current.max.y, bounds.max.y); - current.max.z = Math.max(current.max.z, bounds.max.z); - } - - /** - * Tell the shadow levels in up to two calls, if anything changed and a second has passed. - * - * The companions used to make this the expensive call it still is; the levels redraw from - * scratch, so the cadence is the only lever and once a second is the one that keeps a walk's - * frames off the shadow lane without leaving the ground stale behind a player. The region is - * what the changed records' own bounds add: a hook that forwards it redraws the levels whose - * window covers them, which for a streaming world is usually the finest one alone. - */ - #tellShadows(): void { - if (!this.#shadowMoved) return; - // No hook means there is no level to tell, so the flag is discharged here. Leaving it set made - // `#residencyStale` true forever, and every update ran a full residency pass on a world that had - // nothing left to do. - if (this.#invalidateShadows === undefined) { - this.#shadowMoved = false; - this.#shadowRegion = undefined; - this.#shadowAdmissionRegion = undefined; - this.#shadowFull = false; - return; - } - const now = this.#now(); - if (now - this.#shadowToldAt < 1e3) return; - this.#shadowToldAt = now; - this.#shadowMoved = false; - const region = this.#shadowRegion; - const admission = this.#shadowAdmissionRegion; - const full = this.#shadowFull; - this.#shadowRegion = undefined; - this.#shadowAdmissionRegion = undefined; - this.#shadowFull = false; - if (full || (region === undefined && admission === undefined)) this.#invalidateShadows(); - else { - if (region !== undefined) this.#invalidateShadows(region); - if (admission !== undefined) this.#invalidateShadows(admission); - } - } - - /** - * Publish, on every caster half, whether the main pass will draw the records behind it this frame. - * - * A caster whose main counterpart is not drawable casts the shadow of nothing. With the GPU scene - * on, a dressed main mesh draws from the dispatch's own record, and a batch the scene holds no - * placement for submits that record and draws none of it — while the caster halves are plain CPU - * batches whose `#clustered` is false for every role but `main` (see the constructor), so they keep - * `#drawn`, the whole resident ring, and a shadow level submits all of it. `#seedGpuSources` is - * what puts the placements in, by queueing a rebuild of the ring behind the admission budget, so - * the hole lasts as long as that takes to drain and the picture shows canopy on empty ground for - * every frame of it. - * - * Per caster half, which is the granularity the ring is placed at and the only one that cannot draw - * a shadow for a source the dispatch has not been given: a `@x,z` cluster covers the cells of one - * world-grid square and a `@*` wide mesh every cell of its asset, so each compares its own placed - * count (`SharedBatch#gpuPlaced`, kept by `#placeSources` and `#clearSegment`) with its own live - * records. Per asset was one count too coarse and it showed: near cells of an asset are placed first, - * so a per-asset rule admitted every far cluster of that asset and its unplaced canopies drew - * shadows on empty ground — one capture in four showed dozens of them at the start pose. - * - * The direction is fail-closed, and the only price is a shadow that arrives with its trees: a half - * whose sources are still being placed casts nothing this frame, and casts once they are all there. - * - * Not the camera's frustum, which is the other narrowing and the wrong one: an off-screen caster - * the main pass has no reason to draw is the reason a shadow map exists. - */ - #publishCasterAdmission(): void { - // With the scene off there is no dress to wait for: a main batch draws from its own records and - // its own cull window, so every caster half is admitted whatever the camera is looking at. One - // comparison per half, no name work and no allocation: `live` is the records the main pass would - // have to be drawing for this half to cast at all. - const gpu = this.#gpuScene.on; - for (const shared of this.#shared.values()) { - if (shared.role === "main") continue; - shared.setMainAdmitted(gpu === false || shared.gpuPlaced >= shared.live); - } - } - - /** - * `TN_WORLD_MAIN_CULL`, every `MAIN_CULL_MARKER_MS`: what the main pass holds, what it is drawing, - * how many of its meshes are submitted at all, and how many times the window moved to get there. - * - * `instances` is every live record the main batches hold — the whole resident ring, which is what - * three's whole-mesh test used to hand the vertex stage — and `drawn` is what the cull left in the - * windows. The gap between them is the saving, `repacks` says what it cost to get: a settled camera - * repacks nothing, so a walk that repacks dozens of times a second is a window that is chasing the - * camera, not one that is thrashing. `hidden` and `castersHidden` are the batches with nothing to - * draw that are now not submitted at all: in three r185's WebGPU path a zero-count `InstancedMesh` - * still runs the whole per-draw JS chain, so an empty batch was CPU a frame for no draw — the number - * is the part of the census that no longer costs anything. - * - * `dressed=N/M` is the GPU scene's own claim, repeated every five seconds: a scene that is on over - * meshes nothing is dressed into compacts a set the CPU path is still drawing, and `keys` alone - * cannot say so — a ring built before the scene came up has no keys at all and looks healthy. - */ - #reportMainCull(): void { - const now = this.#now(); - if (now - this.#mainCullToldAt < MAIN_CULL_MARKER_MS) return; - this.#mainCullToldAt = now; - const repacks = this.#mainCullRepacks; - this.#mainCullRepacks = 0; - let instances = 0; - let drawn = 0; - let hidden = 0; - let castersHidden = 0; - let clusterWithheld = 0; - let clusterRecords = 0; - let clusterMissing = 0; - let wideWithheld = 0; - let wideRecords = 0; - let wideMissing = 0; - // What the two caster layers hold between them, per layer: the resident triangle bill a shadow - // level that picked that layer submits. Two numbers that must not be equal is the whole claim — - // the wide half draws the asset's coarsest level, so it is the one that falls. - let clusterTriangles = 0; - let wideTriangles = 0; - let dressed = 0; - let mainMeshes = 0; - for (const shared of this.#shared.values()) { - if (shared.role !== "main") { - if (shared.mesh.visible === false) castersHidden += 1; - if (shared.mesh.mainAdmitted === false && shared.live > 0) { - if (shared.role === "cluster") { - clusterWithheld += 1; - clusterRecords += shared.live; - clusterMissing += Math.max(0, shared.live - shared.gpuPlaced); - } else if (shared.role === "wide") { - wideWithheld += 1; - wideRecords += shared.live; - wideMissing += Math.max(0, shared.live - shared.gpuPlaced); - } - } - // Zero records add zero, so an empty batch needs no test of its own here. - const triangles = levelTriangles(shared.mesh.geometry) * shared.live; - if (shared.role === "cluster") clusterTriangles += triangles; - else wideTriangles += triangles; - continue; - } - mainMeshes += 1; - if (shared.gpu !== undefined) dressed += 1; - instances += shared.live; - drawn += shared.drawn; - if (shared.mesh.visible === false) hidden += 1; - } - const stats = this.stats(); - console.info( - `TN_WORLD_MAIN_CULL instances=${String(instances)} drawn=${String(drawn)} ` + - `hidden=${String(hidden)} castersHidden=${String(castersHidden)} repacks=${String(repacks)} ` + - `refilters=${String(stats.refilters)} levelRefilters=${String(stats.levelRefilters)} ` + - `clusterTris=${String(clusterTriangles)} wideTris=${String(wideTriangles)} ` + - `dressed=${String(dressed)}/${String(mainMeshes)}`, - ); - console.info( - `TN_WORLD_SHADOW_ADMISSION clusterWithheld=${String(clusterWithheld)} ` + - `clusterRecords=${String(clusterRecords)} clusterMissing=${String(clusterMissing)} ` + - `wideWithheld=${String(wideWithheld)} wideRecords=${String(wideRecords)} ` + - `wideMissing=${String(wideMissing)} seeded=${String(this.#gpuSeeded)} ` + - `queued=${String(this.#jobs.length)}`, - ); - const hlod = stats.hlod; - let proxyDraws = 0; - let replacedSources = 0; - for (const state of this.#cellProxies) { - if (!state.active) continue; - proxyDraws += state.prepared?.meshes.length ?? 0; - replacedSources += state.hidden.size + state.mainMasks.size; - } - const why = hlod.reason === "" ? "" : ` reason=${JSON.stringify(hlod.reason)}`; - console.info( - `TN_WORLD_HLOD on=${String(hlod.on)} loaded=${String(hlod.loaded)} ` + - `compiled=${String(hlod.compiled)} observed=${String(hlod.observed)} ` + - `active=${String(hlod.active)} pending=${String(hlod.pending)} ` + - `draws=${String(proxyDraws)} replaced=${String(replacedSources)} ` + - `bytes=${String(hlod.bytes)} refused=${String(hlod.refused)} failures=${String(hlod.failures)}${why}`, - ); - // The bundles on their own line, because the pair reads against a different question: how many - // objects are recorded, and how many times the recording was thrown away and redone. A walk - // streaming and culling holds `records` at the keys and chunks that came and went; a walk that - // repacks it every frame is drawing a moving set, which is the bug a bundle cannot have. `reason` - // is on it because the default is now on: a reader has to be able to tell a run that turned it - // off from a run where recording never happened. - const bundles = stats.bundle; - console.info( - `TN_WORLD_BUNDLE ${bundles.on ? "on" : "off"} reason=${bundles.reason} ` + - `children=${String(bundles.children)} cpuMeshes=${String(bundles.cpuMeshes)} gpuMeshes=${String(bundles.gpuMeshes)} records=${String(bundles.records)}`, - ); - } - - /** - * Filter one slice of a run's placements into the batches they draw from, applying the CPU - * maxDistance/reach filters at the build position and switching level where an asset's `lods` say - * to. GPU admission retains maxDistance candidates for the dispatch; placementReach stays here - * because it is an explicit per-record source boundary. - */ - #addPlacements(job: IBuildJob, from: number, to: number): void { - const { asset, filterX, filterZ } = job; - if (job.replacement && job.next === 0) this.#levelRefilters += 1; - if (job.records === undefined) job.records = cellPlacements(this.#placements, job.run); - const records = job.records; - const inner = cullDistance(asset.definition.maxDistance); - // Half the diagonal of the asset's authored bounds: the sphere the dispatch culls this placement - // with, and the one number about its extent that comes from the package rather than from here. - const authored = asset.definition.bounds; - const boundsRadius = - 0.5 * - Math.hypot( - (authored.max[0] as number) - (authored.min[0] as number), - (authored.max[1] as number) - (authored.min[1] as number), - (authored.max[2] as number) - (authored.min[2] as number), - ); - // Decided once per slice, not per placement: the wide half needs the placement root only for an - // asset that can append a whole-asset impostor. See `ICellBatch.wideRoot`. - const wantRoots = this.#rootsNeeded(asset); - for (let index = from; index < to; index += 1) { - const base = index * PLACEMENT_RECORD_FLOATS; - const x = records[base] as number; - const y = records[base + 1] as number; - const z = records[base + 2] as number; - const distance = - this.#placementReach === undefined - ? Math.hypot(x - filterX, z - filterZ) - : Math.hypot(x - filterX, y - job.filterY, z - filterZ); - if (this.#gpuScene.on === false && inner !== undefined && distance > inner) continue; - const reach = this.#placementReach?.[job.run.offset + index]; - if (reach !== undefined && distance > reach) continue; - this.#position.set(x, y, z); - this.#rotation.set( - records[base + 3] as number, - records[base + 4] as number, - records[base + 5] as number, - records[base + 6] as number, - ); - this.#scale.setScalar(records[base + 7] as number); - this.#matrix.compose(this.#position, this.#rotation, this.#scale); - const scale = this.#scale.x; - // The placement transform, then the part's own offset inside the model: a bark primitive at - // the trunk and a needles primitive higher up both land in the one instance matrix. - // The level is picked by the shared gate rule: authored world-metre gates directly, and a - // whole-asset impostor's terminal gate scaled by this placement's own scale. The bias is the - // same adaptive multiplier the dispatch carries, so a biased walk crosses a switch here too. - let level = levelAtGates( - { distances: asset.distances, impostor: asset.impostor !== undefined }, - biasedLodDistance(distance), - scale, - ); - if (this.#placementLevels !== undefined && asset.definition.lods !== undefined) { - const record = job.run.offset + index; - let previous = this.#placementLevels[record] as number; - // Outward gates remain authored; returning inward retains the previous level until the margin. - if (level > previous) previous = level; - while ( - previous > level && - biasedLodDistance(distance) < - (asset.distances[previous] as number) * (1 - this.#lodHysteresis) - ) - previous--; - level = previous; - this.#placementLevels[record] = level; - } - // The record's own world bounds: the placement widened by the asset's authored bounds at the - // scale it is drawn at. Read here because this loop already holds the placement and the scale, - // and it is the one number the shadow levels' invalidation is tested against — the cell box is - // 64 m of it, so a level whose window is 48 m wide was redrawing over ground nothing in it. - const bounds = asset.definition.bounds; - // The far shadow of an asset that can bake a whole-asset impostor draws one quad per placement - // at the placement root: the parts' own offsets are baked into the atlas, so composing a source - // part's offset again would apply it twice. One root record per placement, beside the per-part - // batches and read only by the wide half; see `ICellBatch.wideRoot`. - if (wantRoots) { - job.roots ??= []; - const roots = job.roots; - let root = roots[level]; - if (root === undefined) { - const anchor = asset.levels[level]?.[0]; - if (anchor !== undefined) { - root = new InstancedBatch({ geometry: anchor.geometry, material: anchor.material }); - roots[level] = root; - } - } - root?.add(this.#matrix); - } - // The GPU scene's source record for this placement: the placement's own transform, the level it - // reached, and a sphere the dispatch tests against the camera's planes. The part offset is the - // key's own, so one record serves every part of the level — see `#placeSources`. - if (this.#gpuScene.on) { - const list = job.sources ?? []; - job.sources = list; - // The sphere is the asset's authored bounds *as placed*, centred where the bounds' centre - // lands under the placement's own transform: a sphere at the placement instead is not a - // bound of the placement, and every prop whose model does not straddle the origin lost the - // half of itself that reaches away from it — a post, a stump, a fern, all of them culled at - // the edge of the view with their bases outside it. The CPU path's own gate is the cell's box - // over the same widened bounds, and it is conservative in exactly the way this is now. The - // centre is the matrix applied to the authored centre, so the placement's quaternion rotates - // it: composing the offsets by hand dropped the rotation and put a turned off-centre prop's - // sphere where the unturned one would be. The radius is rotation-invariant and takes the - // scale alone, which is uniform. - const centre = this.#sourceCentre - .set( - ((bounds.min[0] as number) + (bounds.max[0] as number)) * 0.5, - ((bounds.min[1] as number) + (bounds.max[1] as number)) * 0.5, - ((bounds.min[2] as number) + (bounds.max[2] as number)) * 0.5, - ) - .applyMatrix4(this.#matrix); - list.push({ - level, - matrix: this.#matrix.clone(), - // `boundsRadius` is already the asset's authored bounds radius (half the diagonal), so the - // placement's sphere radius is that times its own uniform scale — halving it again put the - // sphere inside the prop and culled the half of it that reaches away from the placement. - radius: boundsRadius * Math.abs(scale), - // The magnitude, finite-sanitised: the dispatch's impostor gate takes `|scale|` exactly as - // `levelAtGates` does, so a mirrored placement reaches the atlas the same way. - scale: Number.isFinite(scale) ? Math.abs(scale) : 1, - x: centre.x, - y: centre.y, - z: centre.z, - }); - } - // Authored bounds include model-part offsets; apply the whole placement transform so - // rotated, mirrored and off-centre casters cannot fall outside visibility/shadow regions. - this.#boundsAt(bounds, this.#matrix); - const parts = asset.levels[level] as readonly IAssetPart[]; - const levelBatches = job.batches?.[level] as InstancedBatch[]; - const levelBoxes = job.boxes?.[level] as Box3[] | undefined; - for (const [part, entry] of parts.entries()) { - this.#instance.multiplyMatrices(this.#matrix, entry.local); - (levelBatches[part] as InstancedBatch).add(this.#instance); - const box = levelBoxes?.[part]; - if (box === undefined) continue; - box.union(this.#recordBounds); - } - } - } - - /** - * Build one (level, part) batch into a mesh. One mesh is one unit, because this is where every - * instance matrix is written and the batch's bounds are first computed — the part of admission the - * renderer pays for again on the frame it first draws the result. The mesh joins the cell in - * {@link #swap}, with the rest of the job, so it is never half a replacement. - * - * ponytail: a hard switch, no crossfade — a placement crosses a level boundary by being rebuilt - * into the other level's batch, so the swap pops. A blend needs a per-instance mix the - * InstancedBatch has no slot for; add one when a game's swap is visible enough to pay for it. - */ - /** - * Fold one batch's own filter point into the cell's bracket, which only ever widens. See - * `IResidentCell.filterNear` and `#staleIn`. - */ - #widenFilterRange(cell: IResidentCell, filterX: number, filterZ: number): void { - const [near, far] = this.#span(cell, filterX, filterZ); - if (near < cell.filterNear) cell.filterNear = near; - if (far > cell.filterFar) cell.filterFar = far; - } - - #buildOne(job: IBuildJob, batches: readonly InstancedBatch[][]): void { - const { asset } = job; - const { cell } = job; - this.#widenFilterRange(cell, job.filterX, job.filterZ); - let part = job.published; - for (const [level, levelBatches] of batches.entries()) { - if (part >= levelBatches.length) { - part -= levelBatches.length; - continue; - } - const box = job.boxes?.[level]?.[part]; - job.fresh.push({ - asset: asset.id, - batch: levelBatches[part] as InstancedBatch, - caster: undefined, - casterSegment: -1, - gpu: undefined, - wide: undefined, - wideSegment: -1, - wideRoot: job.roots?.[level], - lastFilterY: job.filterY, - lastFilterX: job.filterX, - lastFilterZ: job.filterZ, - level, - part, - run: job.run, - segment: -1, - shared: undefined, - // Where these records are, for the shadow invalidation this entry's swap hands over. - shadowBounds: box === undefined || box.isEmpty() ? undefined : box, - threshold: this.#reachRanges.get(cell.cell)?.has(asset.id) - ? Math.min(asset.threshold ?? Number.POSITIVE_INFINITY, 6) - : asset.threshold, - }); - job.published += 1; - return; - } - } - - /** The main pass's one mesh for an asset part at a level, covering every resident cell. */ - #sharedFor(assetId: string, entry: ICellBatch): SharedBatch | undefined { - return this.#batchFor(assetId, entry, this.#keyOf(entry), "main", this.#receiveShadow); - } - - /** - * The shadow-caster cluster for one asset part at one level, or `undefined` when the level does - * not cast or the frame's fresh allowance is spent. - * - * One per `(key, cluster)`, on `VIRTUAL_SHADOW_CASTER_LAYER` and off the main camera, so a level - * culls the clusters its own window covers while the main pass keeps drawing one mesh per key. - */ - #casterFor( - assetId: string, - entry: ICellBatch, - cell: IResidentCell, - level = entry.level, - ): SharedBatch | undefined { - // A world whose shadow levels draw its keys mints no cluster: the two are the same placements by - // two routes, and a level's map can only draw one of them. - if (!this.#casts(level) || this.#keysForShadow()) return undefined; - return this.#batchFor( - assetId, - entry, - `${this.#keyOf(entry, level)}@${this.#clusterOf(cell)}`, - "cluster", - false, - level, - ); - } - - /** - * The wide caster for one asset part at one level, or `undefined` when the level does not cast or - * the frame's fresh allowance is spent. - * - * One per key, holding every cell's records, on the wide caster layer: a level whose window - * covers the whole resident ring pays one draw for the key instead of one per square, and the fine - * levels that cull clusters never see this layer. - */ - #wideFor(assetId: string, entry: ICellBatch, level = entry.level): SharedBatch | undefined { - // As `#casterFor`: the key twin replaces both halves, so neither is minted. - if (!this.#casts(level) || this.#keysForShadow()) return undefined; - // A whole-asset impostor is one shape for every part and level, so its far shadow is ONE mesh per - // asset holding one root record per placement, not one mesh per level whose records a source part - // offset would then double. Every other asset keeps the per-key mesh its per-part shapes need. - const impostor = this.#assets.get(assetId)?.impostor !== undefined; - const key = impostor - ? `${assetId}:*@${WIDE_CLUSTER}` - : `${this.#keyOf(entry, level)}@${WIDE_CLUSTER}`; - return this.#batchFor(assetId, entry, key, "wide", false, level); - } - - /** `asset:level:part`, the main pass's one mesh per key. */ - #keyOf(entry: ICellBatch, level = entry.level): string { - return `${entry.asset}:${String(level)}:${String(entry.part)}`; - } - - /** - * The level a cell batch casts with: its own when that level casts, otherwise the coarsest - * casting level, when that level holds the same parts — a chain's derived levels do, authored - * `lods` need not — so a tree past `castLevels` keeps its shadow in the last shape that has a - * caster rather than leaving a hole in the map (PRD-541). `undefined`: it casts nothing. - */ - #castLevelOf(entry: ICellBatch): number | undefined { - if (this.#casts(entry.level)) return entry.level; - if (this.#castShadowLevels === 0) return undefined; - const asset = this.#assets.get(entry.asset); - if (asset === undefined || asset.definition.lods !== undefined) return undefined; - const level = Math.min(entry.level, this.#castShadowLevels) - 1; - return asset.levels[level]?.[entry.part] === undefined ? undefined : level; - } - - /** - * Whether a shadow level draws this world's keys instead of its two caster halves: the GPU scene - * is up *and* a provider registered on it, which `?tnShadowGpuKeys=1` is what asks for. Both, or - * neither — a provider with the scene off is a world whose shadow maps have nothing to draw, so - * the world keeps the caster path until both are true. - */ - #keysForShadow(): boolean { - return this.#gpuScene.on && this.#gpuScene.shadowKeys; - } - - /** - * The shadow twin of one main key: a second mesh on the key layer, over the shadow twin of the - * dispatch's output, so a level's map submits one indirect draw for this key instead of one per - * world-grid square the two caster halves split it into. - * - * Its records are never written — the twin buffers are the dispatch's, and this mesh's count is - * its region's capacity — so it holds no instance buffer of its own and is minted at one slot. - * Minted for the levels that cast, which is what the caster halves did and what the CPU path - * already meant: a placement the dispatch routes to a level with no mesh casts nothing here, - * exactly as it casts nothing from a caster half nobody minted. - */ - #shadowKeyFor(key: IKeyDescriptor, part: IAssetPart): void { - // Registered here rather than at construction, because it is a question about keys that exist: - // a level's dispatch asks the provider for the keys a map draws, and the answer is this world's. - // Without a provider the scene registers nothing, which is what keeps the flag from minting - // anything at all; see `shadowGpuKeysRequested`. - this.#gpuScene.shadowKeysFrom(() => this.#shadowKeyNames); - if (this.#keysForShadow() === false || this.#casts(key.level) === false) return; - const name = `${key.key}${GPU_KEY_SUFFIX}`; - this.#shadowKeyNames.add(key.key); - let shared = this.#shared.get(name); - if (shared === undefined) { - // A released twin keeps its uuid and the node three built for it, for the same reason a - // caster half's does: rebinding is cheaper than a fresh mesh, and a fresh mesh is a fresh - // node build inside a shadow pass. - const released = this.#retired.get(name); - if (released !== undefined) { - this.#retired.delete(name); - this.#retiredBytes -= this.#heldBytes(released); - shared = released; - } else { - // A fresh mesh is charged against the frame's allowance like any other; the walk that runs - // out of it comes back for this key rather than paying its build inside a shadow render. - if (this.#freshThisUpdate >= this.#freshAllowance()) return; - this.#freshThisUpdate += 1; - shared = new SharedBatch( - part.geometry, - part.material, - 1, - 1, - name, - this.#extentBounds, - "key", - ); - } - shared.rebind(part.geometry, part.material); - shared.mesh.name = name; - this.#dressMesh(shared, false); - this.#shared.set(name, shared); - this.#attach(shared); - // Owed a draw, and shown because it is empty: a key is submitted by a shadow level, so the - // build behind the gate is the prewarm's whole job. Only while the gate is still open — a key - // streamed in afterwards owes nothing, exactly as a streamed caster half does not. - if (this.#prewarmSettled === false) this.#awaitDraw(shared); - } - this.#dressShadowKey(shared, key.key); - } - - /** - * Point a shadow key's mesh at the twins, with the same shape and the same reasons as - * {@link #dressGpu}: the mesh's `instanceMatrix` becomes the scene's twin compaction buffer, its - * geometry a view carrying this key's twin record over the same attributes, and its whole-mesh - * frustum test off, because the dispatch tested every instance and the mesh's own bounds are the - * resident ring. Idempotent, so a settled walk reads one comparison — and so a regrown twin, which - * is a new attribute and a new pipeline like the main pass's, is re-dressed rather than left - * pointing at the buffer the dispatch replaced. - */ - #dressShadowKey(shared: SharedBatch, key: string): void { - const scene = this.#gpuScene; - // The twin record has to exist before a mesh can be dressed against it: a record minted inside - // the render it would draw in is one render late for every key. Idempotent, and a no-op without - // a provider. - scene.shadowKey(key); - const region = scene.shadowRegionOf(key); - const drawn = scene.shadowDrawn; - const args = scene.shadowArgs; - const geometry = shared.mesh.geometry; - if ( - drawn !== undefined && - shared.mesh.instanceMatrix === drawn && - shared.mesh.frustumCulled === false && - shared.gpuCount === region?.capacity && - geometry.indirect === args && - geometry.indirectOffset === (region?.argsIndex ?? -1) * DRAW_ARGS_BYTES - ) - return; - if (region === undefined || drawn === undefined || args === undefined) return; - // The submission bound: three skips an `InstancedMesh` at count 0 before the GPU reads the - // record, so a key with no instances of its own is submitted at its region's capacity and the - // record decides. See `gpuCount`. - shared.gpuCount = region.capacity; - const mesh = shared.mesh; - const view = indirectView(geometry, args, region.argsIndex * DRAW_ARGS_BYTES); - // A fresh object for the same reason the main pass needs one: three binds the `instanceMatrix` - // an object held when its node was built and keeps that binding for the object's whole life. - if (mesh.instanceMatrix !== drawn) { - const next = freshMeshFor(mesh, view, mesh.count) as ICasterScaleMesh; - redressMaterial(next); - this.#carryPrewarm(mesh, next); - this.#seat(shared.replaceMesh(next), next); - next.instanceMatrix = drawn; - next.frustumCulled = false; - } else { - mesh.geometry = view; - mesh.instanceMatrix = drawn; - mesh.frustumCulled = false; - } - // Shown and submitted whatever the records say: a key's own count is its region's capacity, and - // the twin record inside is what decides how many instances this map draws of it. See - // `SharedBatch#publish`. Never bundled — a bundle's record is fixed when it is recorded and only - // the main pass's draws are replayed from it, so a shadow key in the group would be drawn by the - // main camera's replay from the wrong buffer. See `#attach`. - mesh.count = shared.gpuCount; - mesh.visible = true; - } - - /** - * The geometry and material one caster half draws, and the one place the coarse shape is chosen. - * - * Only the wide half is coarse. A shadow level picks its granularity by bill — one mesh per square - * on `VIRTUAL_SHADOW_CASTER_LAYER`, one mesh per key on the wide layer — so the wide half is what - * the levels whose window holds the resident ring submit, and their texels are metres across: the - * shape such a map resolves cannot tell level 0 from the asset's coarsest level, so it draws the - * coarsest one per part and the triangles it saves are triangles no fragment ever sampled. A - * cluster is the fine half, culled down to the squares one level's own window covers, so it keeps - * the level the placement selected. An asset with one level is its own coarsest, and every half - * draws what it draws today. - */ - /** - * The shape the coarse wide half draws for one `(level, part)`, or `fallback` when the level has no - * honest coarse counterpart. Shared by {@link #shapeFor} and the prewarm, so the mesh minted behind - * the gate carries the same geometry the swap would later ask for — a prewarm built from the - * coarsest level's part 0 and then handed back unchanged is the wrong shape for good. - */ - #wideShape( - asset: IAssetState | undefined, - level: number, - part: number, - fallback: IBatchShape, - ): IBatchShape { - if (this.#preserveAuthoredParts) return fallback; - const terminal = asset?.levels.at(-1); - // The whole-asset impostor is the one case where a part index has no meaning: it is a single - // representation of every LOD0 part, so every part routes through it. `terminal?.[0]` is only - // that shape. On the source-only path the terminal must have a part at this index with the same - // role — an alpha slot drawn with an opaque part's shape (or vice versa) is a different object, - // and a terminal with fewer parts must not fall back to part 0's, which duplicates it. - if (asset?.impostor !== undefined) { - const coarse = terminal?.[0]; - if (coarse !== undefined) return coarse; - } - const coarse = terminal?.[part]; - const source = asset?.levels[level]?.[part]; - if ( - coarse !== undefined && - source !== undefined && - isAlphaFoliage(source.material) === isAlphaFoliage(coarse.material) - ) - return coarse; - return fallback; - } - - #shapeFor( - entry: ICellBatch, - role: "cluster" | "key" | "main" | "wide", - level = entry.level, - ): IBatchShape { - // A batch cast at a finer level than its own (`#castLevelOf`) draws that level's part. - const own = - level === entry.level - ? { geometry: entry.batch.geometry, material: entry.batch.material } - : (this.#assets.get(entry.asset)?.levels[level]?.[entry.part] as IAssetPart); - if (role === "wide") - return this.#wideShape(this.#assets.get(entry.asset), level, entry.part, { - geometry: own.geometry, - material: own.material, - }); - return { geometry: own.geometry, material: own.material }; - } - - /** - * Whether this entry still owes a wide caster. A terminal whole-asset impostor is one part, and one - * wide mesh per level already holds every placement: only part 0 opens it, so a walk pays one quad - * per placement on the far shadow instead of one per bark/needle part. - */ - #wideOwed(assetId: string, part: number): boolean { - if (part === 0) return true; - return this.#assets.get(assetId)?.impostor === undefined; - } - - /** The `@x,z` half of a caster cluster key, for a resident cell. */ - #clusterOf(cell: IResidentCell): string { - return clusterOf(cell.x, cell.z, this.#cellsPerCluster); - } - - /** - * The `@x,z` half of the main cull's own visibility-cell key, for a resident cell. This is what a - * main batch's blocks are grouped by and what `#cullMainPass` tests, so it is the granularity the - * main pass is culled at; the caster clusters above are the shadow levels' and are left alone. - */ - #cullCellOf(cell: IResidentCell): string { - return clusterOf(cell.x, cell.z, this.#cellsPerCullCell); - } - - /** - * The shared batch for one key, created (a new mesh: counted against the frame's fresh allowance) - * the first time any cell draws it. `undefined` means the allowance is spent and the caller waits - * a frame. A caster batch carries the shadow flags and the caster layer; a main batch casts - * nothing, because its records reach the shadow maps through the caster cluster. - * - * The buffer starts with a block per cell that can hold this key, plus the one a refilter holds - * alongside the blocks it replaces — the cluster's own cells for a caster cluster, the whole ring - * for the main pass's one mesh per key — not the eight it used to start with. Sizing a main batch - * by its cluster's cells left it with two blocks for a ring of dozens, so a walk filled the - * buffer, `grow` minted a fresh mesh — a uuid three had never built a node for, in every shadow - * pass too — and put the old one in an array nothing ever read. Those two sizes are ceilings, so - * this is what stops growing. Cost: a main buffer is ring-sized for assets that never fill it; - * give `residentCells` a smaller value if that outweighs a re-mint. - */ - #batchFor( - assetId: string, - entry: ICellBatch, - key: string, - role: "cluster" | "key" | "main" | "wide", - receiveShadow: boolean, - level = entry.level, - ): SharedBatch | undefined { - const existing = this.#shared.get(key); - if (existing !== undefined) return existing; - const shape = this.#shapeFor(entry, role, level); - const smallCaster = this.#smallCaster(assetId); - // The batch released when this asset's last cell left the ring is the one to draw into again, - // so the mesh keeps the uuid and the node three built for it. It costs no fresh allowance, - // because it is not a fresh mesh. - const released = this.#retired.get(key); - if (released !== undefined) { - this.#retired.delete(key); - this.#retiredBytes -= this.#heldBytes(released); - released.rebind(shape.geometry, shape.material); - released.mesh.name = key; - released.smallCaster = smallCaster; - this.#dressMesh(released, receiveShadow); - // A rebind swaps the geometry and may swap the buffer, so the key is dressed again: the indirect - // record and the shared matrix buffer belong to the mesh, not to the batch. - this.#adoptGpu(released, entry.asset, key, level, entry.part); - this.#shared.set(key, released); - this.#attach(released); - return released; - } - if (this.#freshThisUpdate >= this.#freshAllowance()) return undefined; - this.#freshThisUpdate += 1; - const shared = new SharedBatch( - shape.geometry, - shape.material, - this.#runMax.get(assetId) ?? entry.batch.count, - // A caster cluster's block count is bounded by the cluster's own cells; a wide caster and the - // main pass's one mesh per key are both bounded by the whole ring, so either way the one a - // refilter holds beside the block it replaces. - role === "cluster" - ? this.#cellsPerCluster * this.#cellsPerCluster + 1 - : this.#budgets.residentCells + 1, - key, - this.#extentBounds, - role, - ); - shared.smallCaster = smallCaster; - this.#dressMesh(shared, receiveShadow); - this.#adoptGpu(shared, entry.asset, key, level, entry.part); - this.#shared.set(key, shared); - this.#attach(shared); - return shared; - } - - /** - * The shadow half of the split, and the two granularities of it. A caster mesh lives alone on - * `VIRTUAL_SHADOW_CASTER_LAYER` — one per world-grid square, which a level whose window is a - * fraction of the ring culls down to the squares it covers — and a wide mesh alone on - * `VIRTUAL_SHADOW_WIDE_CASTER_LAYER`, one per key, which a level whose window covers the ring - * submits once. The level cameras draw one of the two layers and never both, so a key costs the - * main pass one draw, a fine level a few and a wide level one. Neither receives: nothing but a - * shadow level ever sees either. A main mesh keeps layer 0 and casts nothing, because its records - * reach the shadow maps only through a caster half. - */ - #dressMesh(shared: SharedBatch, receiveShadow: boolean): void { - const mesh = shared.mesh; - const role = shared.role; - // Every role's records are written into the instance buffer, never moved in the world, and the - // mesh is created and released per role — so this is the one place that covers a fresh mesh, a - // rebound one and the three roles. Static here is only safe because every write announces - // itself: `instanceMatrix.needsUpdate` bumps the attribute's version, which is what a settled - // draw watches to know it still has to scan. - markStatic(mesh); - if (role !== "main") { - mesh.layers.set( - role === "cluster" - ? VIRTUAL_SHADOW_CASTER_LAYER - : role === "key" - ? VIRTUAL_SHADOW_KEY_LAYER - : shared.smallCaster - ? VIRTUAL_SHADOW_SMALL_CASTER_LAYER - : VIRTUAL_SHADOW_WIDE_CASTER_LAYER, - ); - mesh.castShadow = true; - mesh.receiveShadow = false; - return; - } - mesh.layers.set(0); - mesh.castShadow = false; - mesh.receiveShadow = receiveShadow; - // The origin a draw of this mesh is counted under until the GPU scene dresses it - // (`RenderPassBudget`). `#dressGpu` runs straight after this and overwrites it with `gpuScene` - // when the key really is dressed, so what survives here is a main batch the CPU path draws per - // object — a backend with no compute, `gpuScene: false`, or a dress that gave up. A caster half - // returns above and keeps no origin: it lives off layer 0, so the main pass never counts it. - mesh.userData.tnDrawSource = "instanced"; - // Nothing to hang off a main mesh: the window is narrowed by `#cullMainPass` for the whole set, - // because a batch that drew nothing is hidden and three never reaches an invisible mesh's own - // hooks — a mesh that could only re-narrow itself in its `onBeforeRender` would stay hidden for - // as long as the camera looked away. That leaves `onBeforeRender` to the prewarm borrow alone. - } - - /** - * Parent one dressed main mesh under the world's single `BundleGroup`, so a settled frame replays - * its draw instead of re-walking three's per-object path for it. - * - * A mesh is shown once, here, and never hidden again: the bundle's render list is fixed when it is - * recorded, the dispatch already decided which instances each key draws, and an indirect draw of - * zero instances is close to free on the GPU — where a `visible` write would force the whole group - * to be recorded again. The prewarm draw this batch may still owe comes out of that same record. - * - * CPU joins and changed material revisions invalidate the record here, including a repaired mesh - * joining on a settled frame. GPU dressing invalidates its completed record at the caller. - * - * A mesh whose draw needs a live pass is refused rather than recorded — a transmissive impostor or - * a GPU batch reaching the framebuffer, the same answer a chunk's draws get. It keeps the per-object - * path and the coarse gate that hides it, which is the path it was already on. See `bundleSafe`. - */ - #bundleIn(shared: SharedBatch, reuseSafety = false): void { - if (this.#bundlesWanted === false) return; - // Shared batches normally carry one material. A mutable array keeps the existing per-object path - // rather than compressing several material identities/revisions into an incomplete signature. - const arrayMaterial = Array.isArray(shared.mesh.material); - const checked = - reuseSafety && !arrayMaterial - ? this.#bundleSafety.test(shared.mesh.material as Material) - : undefined; - shared.bundlable = reuseSafety - ? checked !== undefined && - !ownHook(shared.mesh, "onBeforeRender") && - !ownHook(shared.mesh, "onAfterRender") && - Reflect.get(shared.mesh, "isSkinnedMesh") !== true && - shared.mesh.morphTargetInfluences === undefined && - checked.safe - : bundleSafe(shared.mesh); - if (shared.bundlable === false) { - if (shared.bundled) this.#bundleOut(shared); - // A previously culled key may already be detached. Refusal still owes its live source draw. - if (shared.mesh.parent !== this) this.add(shared.mesh); - return; - } - const group = this.#shardOf(shared); - if (checked !== undefined) { - if (shared.bundled && shared.bundleMaterialRevision !== checked.revision) - this.#bumpBundle(group); - shared.bundleMaterialRevision = checked.revision; - } - if (shared.mesh.parent !== group) { - group.add(shared.mesh); - if (reuseSafety) this.#bumpBundle(group); - } - shared.bundled = true; - shared.bundledCount = shared.mesh.count; - // A recorded draw was cut from this camera's frustum, so the mesh's own whole-mesh test would drop - // it the moment the camera turned away from a window the record still holds. Off, like a chunk's. - shared.mesh.frustumCulled = false; - shared.mesh.visible = true; - // Read by `VirtualShadowNode#probe`, which must not hide a bundled mesh: the render list a bundle - // recorded is fixed, and `visible = false` would take the mesh out of every record after the next - // re-record. See `bundled`. - shared.mesh.userData.tnBundled = true; - } - - /** - * Take a mesh back out of the group, because the key it was minted under is leaving the world. - * - * A retire is a structural change — the recorded bundle still holds this render object, and - * replaying it would draw a key nothing owns — so it is one of the two events that re-records. - */ - #bundleOut(shared: SharedBatch): void { - if (shared.bundled === false) return; - shared.bundled = false; - shared.bundledCount = -1; - shared.mesh.userData.tnBundled = false; - // Back on the per-object path, so the mesh's own whole-mesh test is its gate again. - shared.mesh.frustumCulled = true; - const group = this.#bundles.get(mainShard(shared.mesh.name)); - if (group === undefined) return; - if (shared.mesh.parent === group) group.remove(shared.mesh); - this.#bumpBundle(group); - } - - /** One re-record, counted: `BundleGroup.needsUpdate` is a version bump, and nothing else. */ - #bumpBundle(group: BundleGroup): void { - this.#bundleRecords += 1; - group.needsUpdate = true; - if (this.#compileBusy) this.#recordedWhileBusy.add(group); - } - - /** - * Put one CPU-path main batch into the world's record, or take it out, by the coarse gate's answer. - * - * A main batch the GPU scene dresses is recorded from `#dressGpu`, because its submission bound is - * its key's region capacity — a number only the dress writes, so the entry cannot go stale. A batch - * the scene does not own submits its own CPU window instead, and that window is per frame: the gate - * narrows it, a residency change repacks it, and a batch it narrows to nothing must draw nothing. - * A record fixes its render list, so all three are the record's business rather than a `visible` - * write the replay ignores. Hence this: the gate's answer is a join or a leave, a window that moved - * or a count that changed is one more record, and `frustumCulled` is off because the record's own - * list was cut by the same frustum when it was made. - * - * The trade is the one a cell's chunk record already makes and the visual A/B already judged: a - * batch whose window is half the view draws the half that faced the camera when the record was cut. - */ - #recordCpuMain(shared: SharedBatch, outcome: "settled" | "narrowed" | "repacked"): void { - if (this.#bundlesWanted === false) return; - // `#publish` has just written this frame's answer onto the mesh, which is the only place it is. - if (shared.mesh.visible === false) { - this.#bundleOut(shared); - return; - } - this.#bundleIn(shared, true); - if (shared.bundled === false) return; - // The count a record freezes, and the layout behind it: a repack moves records inside the window - // without moving its length, so the epoch answer and the count are both read here. - if (outcome === "settled" && shared.mesh.count === shared.bundledCount) return; - shared.bundledCount = shared.mesh.count; - this.#bumpBundle(this.#shardOf(shared)); - } - - /** - * Where a dressed main batch's mesh belongs: its key's main record shard, or the world itself when there is - * none — a world with bundles off, a draw no record can replay, and the very first dress of a batch - * the mint has not attached yet. - * - * It answers with the batch's own last refusal rather than re-asking, so the settled walk compares - * parentage without walking a material graph per key per frame. See `bundleSafe`. - */ - #bundleHome(shared: SharedBatch): Object3D | null { - if (this.#bundlesWanted === false || shared.bundlable === false) return this; - return this.#bundles.get(mainShard(shared.mesh.name)) ?? null; - } - - /** - * Attach one prepared chunk: to its cell's own record, or to the world when `bundles` is off. - * - * The group is minted with the first chunk the cell attaches, and a chunk added to it is a - * structural change — the recorded list does not hold this render object and the replay would draw - * the cell's chunks without it — so this is also the one re-record. See the `bundles` option for - * why the group is per cell. - * - * Only the draws a bundle can replay are recorded, and they carry the `tnBundled` marker and the - * frozen cull with them; the rest keep the per-object path. See `bundleSafe`. - */ - #addChunk(cell: IResidentCell, chunk: Object3D): void { - if (this.#bundlesWanted === false) { - this.add(chunk); - return; - } - // What the record can hold, and whether anything had to be left behind. A chunk whose every draw - // is recorded goes in whole, which is one entry per chunk; a chunk holding a draw a bundle cannot - // replay stays in the world and hands its safe draws to the record on their own, so one glass - // window does not put the whole building back on the per-object path. See `bundleSafe`. - const draws = chunkDraws(chunk); - const recorded = draws.filter((mesh) => bundleSafe(mesh)); - if (recorded.length === 0) { - this.add(chunk); - return; - } - let entry = this.#chunkBundles.get(cell.key); - if (entry === undefined) { - const group = new BundleGroup(); - group.name = `${CHUNK_NAME}-bundles:${cell.key}`; - this.add(group); - entry = { cluster: clusterOf(cell.x, cell.z, this.#cellsPerCullCell), group }; - this.#chunkBundles.set(cell.key, entry); - } - const whole = recorded.length === draws.length; - if (whole) entry.group.add(chunk); - else { - // The chunk keeps its own hierarchy around whatever it could not record, and each recorded - // draw is attached rather than added so it keeps the world transform it was merged at. The root - // it came out of travels with it, because that is what releases the draw again. - this.add(chunk); - for (const mesh of recorded) { - mesh.userData.tnChunkRoot = chunk; - entry.group.attach(mesh); - } - } - // A recorded draw is frozen where it stands: the group is a child of the world at identity, so - // its matrix will never move, and the freeze is what lets three's settled-object path skip the - // per-object work for the draw a frame is not re-recording anyway. See `markStatic`. - for (const mesh of recorded) { - mesh.userData.tnBundled = true; - mesh.frustumCulled = false; - if (whole === false) markStatic(mesh); - } - this.#chunkBundleChildren += whole ? 1 : recorded.length; - this.#bumpBundle(entry.group); - } - - /** - * Take a cell's chunk record out of the world, because the cell that owned every chunk in it has - * left: a record nothing owns must not be replayed, and the cell coming back mints its own. - * - * A chunk the bake split left its recorded draws in the group rather than under their own root, so - * each goes back to the root it came out of first: the caller's own disposal walk finds them there, - * and a draw released by neither is a buffer nothing ever asks for again. - */ - #dropChunkBundles(cell: IResidentCell): void { - const entry = this.#chunkBundles.get(cell.key); - if (entry === undefined) return; - this.#chunkBundles.delete(cell.key); - const roots = new Set(cell.chunks); - // Counted before anything leaves the group, because that is what it is holding. - this.#chunkBundleChildren -= entry.group.children.length; - for (const child of entry.group.children) { - const root = child.userData.tnChunkRoot as Object3D | undefined; - if (roots.has(child) === false && root !== undefined) { - child.userData.tnChunkRoot = undefined; - root.attach(child); - } - } - entry.group.removeFromParent(); - this.#bumpBundle(entry.group); - } - - /** The main-record shard a batch's key lands in, minted as a child of the world on first use. */ - #shardOf(shared: SharedBatch): BundleGroup { - const shard = mainShard(shared.mesh.name); - let group = this.#bundles.get(shard); - if (group === undefined) { - group = new BundleGroup(); - group.name = "world-main-bundles"; - this.add(group); - this.#bundles.set(shard, group); - } - return group; - } - - /** The recorded main batches, over every shard. */ - #mainBundleChildren(): number { - let children = 0; - for (const group of this.#bundles.values()) children += group.children.length; - return children; - } - - /** - * Where a freshly bound or grown main batch's mesh goes: the bundle group when it has been dressed - * into one, the world otherwise. Three re-parents on `add`, so a bundled mesh has to be asked for - * or it silently leaves the group it was recorded in. - */ - #attach(shared: SharedBatch): void { - // A grow replaces the mesh object, so the marker `#bundleIn` set has to follow the batch's own - // flag onto it. See `bundled`. - shared.mesh.userData.tnBundled = shared.bundled; - if (shared.bundled === true) this.#shardOf(shared).add(shared.mesh); - else this.add(shared.mesh); - // A record holds the object it was cut from, so a mesh that arrived by a rebind or a grow is not - // the one the last record named: one re-record, and the record counts this mesh from here. - if (shared.bundled === true) { - shared.mesh.frustumCulled = false; - shared.bundledCount = shared.mesh.count; - this.#bumpBundle(this.#shardOf(shared)); - } - } - - /** - * Name one main key to the GPU scene, and dress its mesh against the scene's live buffers. - * - * A caster or wide key is named `@x,z` or `@*` and belongs to the shadow passes, which the GPU scene - * does not touch, so those are left exactly as they were. - * - * The key is recorded whether or not the scene is on, because a prewarm mints a whole ring before - * the first frame that can answer whether this backend can run the scene, and a key recorded only - * when the dress happened is a key the scene is never told about. `#seedGpuKeys` comes back for - * those; see there. - */ - #adoptGpu(shared: SharedBatch, assetId: string, key: string, level: number, part: number): void { - if (shared.role !== "main") return; - const named = { asset: this.#canonical(assetId), key, level, part }; - shared.main = named; - if (this.#gpuScene.on === false) return; - shared.gpu = named; - this.#dressGpu(shared, named); + this.#prewarm.queuePrewarm(asset); + for (const cell of [...this.#resident.values()]) { + for (const run of cell.cell.runs) { + if (this.#canonical(run.asset) === asset.id) this.#queueBuild(asset, cell, run, true); + } + } + }, + residentCell: (key) => this.#resident.get(key), + canonical: (id) => this.#canonical(id), + shadowRecordsMoved: () => this.#render.shadowRecordsMoved(), + residency: () => ({ + instances: this.#instances, + bytes: this.#bytes, + hlodBytes: this.#chunks.bytes, + }), + add: (object) => { + this.add(object); + }, + remove: (object) => { + this.remove(object); + }, + addFailure: () => { + this.#failures += 1; + }, + markReleasedPart: (part) => { + this.#models.markReleasedPart(part); + }, + }, + ); + if (init.terrain === false) return; + // Terrain can reach further than the props do, so its radius is its own option; the collider + // radius stays on the ring, which is how far a player can actually walk into this world. + const streamRadius = nonNegativeInteger( + init.terrain?.streamRadius ?? this.#ring, + "terrain.streamRadius", + ); + const colliderRadius = nonNegativeInteger( + init.terrain?.colliderRadius ?? this.#ring, + "terrain.colliderRadius", + ); + const tileCount = (2 * streamRadius + 1) ** 2; + this.#terrain = new TerrainTiles({ + ...(init.createCollider === undefined + ? {} + : { colliderRadius, createCollider: init.createCollider }), + receiveShadow: init.shadows?.receive === true, + ...(init.terrain?.lodDistances === undefined + ? {} + : { lodDistances: init.terrain.lodDistances }), + ...(init.terrain?.lodFactors === undefined ? {} : { lodFactors: init.terrain.lodFactors }), + ...(init.terrain?.skirtDepth === undefined ? {} : { skirtDepth: init.terrain.skirtDepth }), + // Sized from the terrain stream radius: the wanted square fits the tile budget. ponytail: no + // terrain byte cap (a tile's bytes are deterministic from the radius, and the cells own the + // memory budget); give `TerrainTiles` its own byte budget when terrain is not radius-bounded. + // A tile that still cannot fit is caught in `update` and reported as pressure. + residentByteBudget: Number.MAX_SAFE_INTEGER, + residentTileBudget: tileCount, + sampleHeight: heightSamplerFromHeightmap( + init.manifest.terrain, + init.manifest.extent, + init.heightmap, + ), + streamRadius, + surface: init.surface, + tileResolution: init.terrain?.tileResolution ?? DEFAULT_TILE_RESOLUTION, + tileSize: init.terrain?.tileSize ?? this.#cellSize, + }); + this.add(this.#terrain); } - /** - * Point one main key's mesh at the GPU scene's buffers, and hand the asset's gate table over. - * - * The mesh's `instanceMatrix` becomes the shared compacted matrix buffer, its geometry a clone that - * shares the part's own attributes and carries this key's indirect record, and its whole-mesh - * frustum test is off — the dispatch already tested every instance against the camera's six - * planes, and the mesh's own bounds are the whole resident ring. Idempotent: a settled frame's - * buffers are the ones it was dressed against, and a regrow, a rebind or a `grow` replaces them, - * which is the only thing that brings a dressed mesh back through here. - * - * A key whose placements outgrew its region is regrown here rather than silently dropping instances - * against the capacity guard, which is what a fixed ceiling did to any key a walk filled past. - * - * The buffers are read after `scene.key` rather than before it, because minting the first key is - * what allocates them: a dress that gave up on their absence was a dress that never happened, and - * the only frame that would have retried it was one whose fast path had already found the mesh - * dressed. A regrow replaces them, so the attribute handed to the mesh is the one a draw will read. - */ - #dressGpu(shared: SharedBatch, key: IKeyDescriptor): void { - const scene = this.#gpuScene; - const asset = this.#assets.get(key.asset); - const part = asset?.levels[key.level]?.[key.part]; - if (asset === undefined || part === undefined) return; - // The asset's own resident placements, not one level's share of them: a placement's level is - // decided per frame from where the camera is, so every level's region has to hold all of them. - const capacity = Math.max(shared.liveCeiling, this.#gpuResident.get(key.asset) ?? 0); - const settled = scene.drawn; - const held = scene.regionOf(key.key); - // The args buffer and the drawn buffer are allocated and grown independently: minting a key - // grows `args` and leaves `drawn` the object it was, so the mesh's `instanceMatrix` and its - // indirect record can point at two different generations. The fast path used to watch only - // `drawn`, and a mesh left holding an old args record was one the dispatch no longer wrote — - // a forest that drew nothing while the readback said the counts were correct. - const geometry = shared.mesh.geometry; - const liveArgs = scene.args; - const bound = - liveArgs !== undefined && - geometry.indirect === liveArgs && - geometry.indirectOffset === (held?.argsIndex ?? -1) * DRAW_ARGS_BYTES; + static async load(options: IWorldCellsLoadOptions): Promise { + if (options.preserveAuthoredParts && options.impostors) + throw new Error("WorldCells preserveAuthoredParts requires impostors:false."); if ( - settled !== undefined && - shared.mesh.instanceMatrix === settled && - shared.mesh.frustumCulled === false && - (held?.capacity ?? 0) >= capacity && - bound && - // Where the mesh hangs is part of what this pass owns: a batch that was retired and handed back - // by a rebind is a child of the world again, and the bundle that last recorded it no longer - // draws it. Without this the walk would re-mint a key into a bundle that is missing it. - shared.mesh.parent === this.#bundleHome(shared) - ) { - // A sibling may have grown the level's capacity without moving this mesh, so the submission - // bound follows the live region rather than the dress that wrote it. See `gpuCount`. - shared.gpuCount = held?.capacity ?? shared.gpuCount; - this.#shadowKeyFor(key, part); - return; - } - const region = scene.key(key.key, new Float32Array(part.local.elements), capacity, { - group: `${key.asset}:${String(key.level)}`, - part: key.part, - // The whole run at once, so a part minted later lands in the slot `firstKey + part` names - // rather than at the end of the key space, where another level's key is in between. - parts: (asset.levels[key.level] as readonly IAssetPart[]).length, + options.lodHysteresis !== undefined && + (!Number.isFinite(options.lodHysteresis) || + options.lodHysteresis < 0 || + options.lodHysteresis >= 1 || + options.gpuScene !== false || + options.impostors === true) + ) + throw new Error( + "WorldCells lodHysteresis must be in [0, 1) with gpuScene:false and impostors:false.", + ); + const assets = options.assets ?? createAssetLoader(); + const baseUrl = options.url.slice(0, options.url.lastIndexOf("/") + 1); + // Logical paths, which is what the loader keys its manifest by: the authored name with no + // leading slash, and the directory the manifest itself sits in as their base. + const manifestPath = options.url.replace(/^\//u, ""); + const logicalBase = manifestPath.slice(0, manifestPath.lastIndexOf("/") + 1); + const manifest = + options.data?.manifest ?? + (await loadLogical( + assets, + manifestPath, + async (url) => (await (await fetchOk(url)).json()) as IWorldPackage, + )); + const placements = + options.data?.placements ?? + (await loadLogical(assets, resolveRelative(logicalBase, manifest.placements), async (url) => + (await fetchOk(url)).arrayBuffer(), + )); + const heightmap = + options.terrain === false + ? new Uint16Array(0) + : (options.data?.heightmap ?? + (await loadLogical( + assets, + resolveRelative(logicalBase, manifest.terrain.heightmap), + loadWorldHeightmap, + ))); + const validation = validateWorldPackage(manifest, { + ...(options.terrain === false ? {} : { heightmapByteLength: heightmap.length * 2 }), + placementsByteLength: placements.byteLength, }); - if (region === undefined) return; - const levels = this.#gpuLevelKeys.get(key.asset) ?? []; - levels[key.level] = { - firstKey: region - key.part, - parts: asset.levels[key.level]?.length ?? 0, - }; - this.#gpuLevelKeys.set(key.asset, levels); - const drawn = scene.drawn; - const args = scene.args; - const args2 = scene.regionOf(key.key); - if (drawn === undefined || args === undefined || args2 === undefined) return; - // Before the mesh is dressed, so the republish a replacement does takes the dressed branch and - // submits at this bound rather than at a zero the CPU never drew. See `gpuCount`. - shared.gpuCount = args2.capacity; - // Before the mesh is dressed, so the republish a replacement does takes the dressed branch: a - // batch that draws from the scene's buffers is shown by the per-asset gate in `#cullMainPass`, - // not by its own count. - shared.gpu = key; - const mesh = shared.mesh; - // Its own geometry object, so the indirect record is this mesh's, over the SAME attributes: the - // original is the shape the caster halves and a rebind still draw with. `clone()` would copy - // every vertex buffer of every key a second time. - const view = indirectView(mesh.geometry, args, args2.argsIndex * DRAW_ARGS_BYTES); - // A mesh three has already compiled cannot be re-dressed by writing fields onto it: its node - // binds the `instanceMatrix` the object held at that compile, and three keeps that binding for - // the object's whole life. A regrow is the same case — a new `drawn` attribute is a new binding - // for the same reason — so both take the same road: a fresh object carrying everything the old - // one had, and the old one let go. Measured on machinefall, where the prewarm had compiled every - // main batch on the CPU path: 3,724 indirect draws submitted in 2 s over records that were - // correct at draw time, and a dressed forest that drew nothing, until the meshes were fresh - // objects; a material of its own (below) never changed that on its own. - if (mesh.instanceMatrix !== drawn) { - const next = freshMeshFor(mesh, view, mesh.count) as ICasterScaleMesh; - redressMaterial(next); - this.#carryPrewarm(mesh, next); - this.#seat(shared.replaceMesh(next), next); - next.instanceMatrix = drawn; - next.frustumCulled = false; - } else { - mesh.geometry = view; - mesh.instanceMatrix = drawn; - mesh.frustumCulled = false; - } - // What the record has to say for the draw to name a triangle: the view's own index count, which - // is the shape this key draws. The scene is told rather than left to guess, so its validation can - // hold the record against it. - scene.indexCount(key.key, view.index?.count ?? 0); - // The asset's gate table is built from the keys minted so far, so a level whose key is not minted - // yet carries no parts and the dispatch draws that level nowhere. Re-registering is a no-op while - // the table holds, and a rewrite when a key joined it. - scene.slot(key.asset, this.#gatesOf(key.asset, asset)); - // The origin this key's main-pass draws are counted under, read off the object at the draw - // (`RenderPassBudget`). Written after the mesh swap above, so a re-dressed fresh object carries - // it, and before `#bundleIn`, which supersedes it with `tnBundled` for a world with bundles on. - shared.mesh.userData.tnDrawSource = "gpuScene"; - // The bundle the last settled frame replayed is now wrong: this pass minted or regrown a key, - // swapped the geometry, or took a fresh mesh object onto the scene's new buffers. Reaching here - // is one of the three, which is why this is the only place a re-record happens — the fast path - // above is every other frame of the walk. - this.#bundleIn(shared); - if (shared.bundled === true) this.#bumpBundle(this.#shardOf(shared)); - // The shadow twin of this key, minted and dressed on the same pass that named the main one: a - // level that draws keys finds its mesh in the same frame the main pass found its buffer, and a - // regrown twin is re-dressed here rather than left on the buffer the dispatch replaced. - this.#shadowKeyFor(key, part); - } - - /** - * Hand a replaced mesh's prewarm borrow to the object that replaced it. - * - * The borrow is keyed by the mesh object and counts *that* object's first submitted draw, so an - * object that goes away before its draw would take the owed draw with it: the batch would sit - * visible at count 0 for the rest of the load, and the owed counters would never balance. Nothing - * new is prewarmed here — the batch still owes exactly the one draw it owed. - */ - #carryPrewarm(old: InstancedMesh, next: InstancedMesh): void { - const entry = this.#awaited.get(old); - if (entry === undefined) return; - this.#awaited.delete(old); - this.#borrowDraw(entry.batch, next, entry.hadOwn, entry.own, entry.epoch); - } - - /** - * The replacement takes the old object's place, and the old one is let go: `InstancedMesh.dispose` - * frees nothing the replacement shares — the vertex buffers and the part's own material belong to - * the refcount path that made them. - * - * Through `add` and `removeFromParent` rather than by writing `parent` and `children`, because - * three fires `childadded`/`childremoved` on the way and the clustered-mesh tracker answers the - * world's arrivals and departures by those alone. The old slot is restored afterwards, so the - * parent's traversal order — and so every draw's order within a frame — is what it was. - */ - #seat(old: InstancedMesh, next: InstancedMesh): void { - const parent = old.parent; - if (parent === null) this.add(next); - else { - const at = parent.children.indexOf(old); - old.removeFromParent(); - parent.add(next); - const here = parent.children.indexOf(next); - if (at >= 0 && here >= 0) { - parent.children.splice(here, 1); - parent.children.splice(at, 0, next); + if (!validation.ok) { + const codes = new Set(); + const details: string[] = []; + for (const entry of validation.errors) { + codes.add(entry.code); + details.push(`${entry.path} ${entry.message}`); } + const error = new Error( + `World package '${options.url}' failed validation: ${[...codes].join(", ")}. ${details.join(" ")}`, + ); + error.name = "WorldPackageValidationError"; + throw error; } - old.dispose(); - } - - /** - * The asset's own gates, as the kernel reads them: the CPU path's switch distances and cull - * distance, and the scene key each level's parts draw into. A level with no minted key has no parts, - * so the dispatch draws nothing at that level rather than reading a key that is not there. - */ - #gatesOf(id: string, asset: IAssetState): IAssetSlot { - const gates: { firstKey: number; parts: number }[] = []; - // The level's own contiguous run, which is what the kernel's `firstKey + part` addresses. The - // owner records it when dressing the key; reading it back from the scene would make the mesh - // check agree with a corrupted GPU gate table instead of checking that table independently. - const keyed = this.#gpuLevelKeys.get(id); - for (const [level] of asset.levels.entries()) - gates.push(keyed?.[level] ?? { firstKey: 0, parts: 0 }); - return { - cull: cullDistance(asset.definition.maxDistance), - distances: asset.distances, - // The dispatch scales the whole-asset impostor's terminal gate by each placement's own scale; - // the same flag the live-state reference reads, so the two cannot cross it differently. - impostor: asset.impostor !== undefined, - levels: gates, - }; - } - - /** Copy the CPU gates at the same publication boundary as the source records and meshes. */ - #publishGpuGates(asset: IAssetState): void { - const gates = this.#gatesOf(asset.id, asset); - this.#publishedGpuGates.set(asset.id, { - cull: gates.cull, - distances: [...gates.distances], - impostor: gates.impostor, - levels: gates.levels.map((level) => ({ ...level })), - locals: asset.levels.map((parts) => - parts.map((part) => Float32Array.from(part.local.elements)), - ), - }); - } - - /** - * Rebuild every resident run once, so a scene that came up after the ring was built is given the - * source records it was built without. - * - * `WorldCells.update` can be called before it has a renderer, and a renderer that is not WebGPU - * never turns the scene on at all; either way the cells that were swapped in while it was off hold - * placements the dispatches have never heard of, and nothing else in the class would ever hand them - * over. One rebuild of the ring is the whole fix, and it is the same build every first-seen asset - * takes: `replace` keeps the old batches drawn until each new one is attached, so the rebuild is - * not a frame of holes. - */ - #seedGpuSources(): void { - // A pending build may already have consumed CPU-only slices. Restart it before any swap can - // publish a partial source list; its outgoing batches keep drawing until the new build lands. - const pending = this.#jobs; - this.#jobs = []; - for (const job of pending) { - this.#queued.delete(job.run); - for (const entry of job.fresh) this.#clearSegment(entry); - this.#queueBuild(job.asset, job.cell, job.run, true, job.force); + const reach = options.placementReach; + if (reach !== undefined) { + if ( + !(reach instanceof Float32Array) || + reach.length !== placements.byteLength / PLACEMENT_RECORD_BYTES + ) + throw new Error("WorldCells placementReach length must match the placement record count."); } - for (const cell of this.#resident.values()) - for (const batch of cell.batches) { - const asset = this.#assets.get(batch.asset); - if (asset === undefined || asset.levels.length === 0) continue; - // One rebuild per run, not per batch: a run's placements are one build, and queueing it per - // batch would rebuild the same placements once per level and part. - this.#queueBuild(asset, cell, batch.run, true); + // One line, at the one moment the whole package is known: how many assets it names and how many + // of them are one model under several names. `canonical` equal to `assets` is a package with no + // duplicates, which is most of them. + const aliases = await assetAliases(assets, manifest, logicalBase); + const reachRanges = new Map< + IWorldCell, + Map + >(); + if (reach !== undefined) { + // Cap each load-time unit, then yield when the measured slice budget is spent. + // One host turn per cheap chunk delays registration behind hundreds of loading frames. + function* validationChunks() { + for (let start = 0; start < (reach as Float32Array).length; start += 512) yield start; } + await addInSlices( + validationChunks(), + (start) => { + for (let index = start; index < Math.min(start + 512, reach.length); index++) { + const value = reach[index] as number; + if (!(value >= 0) || (value !== Number.POSITIVE_INFINITY && !Number.isFinite(value))) + throw new Error( + "WorldCells placementReach values must be nonnegative metres or Infinity.", + ); + } + }, + { marker: false }, + ); + // Start indexing in a fresh host turn instead of combining two separate slice budgets. + await yieldToHost(); + const records = new Float32Array(placements); + function* chunks() { + for (const cell of manifest.cells) + for (const run of cell.runs) + for (let start = run.offset; start < run.offset + run.count; start += 512) + yield { cell, run, start, end: Math.min(start + 512, run.offset + run.count) }; + } + await addInSlices( + chunks(), + ({ cell, run, start, end }) => { + const id = aliases.get(run.asset) ?? run.asset; + const ranges = + reachRanges.get(cell) ?? + new Map(); + const range = ranges.get(id) ?? { + min: Number.POSITIVE_INFINITY, + max: 0, + minY: Number.POSITIVE_INFINITY, + maxY: Number.NEGATIVE_INFINITY, + }; + for (let index = start; index < end; index++) { + const value = reach[index] as number; + if (value !== Number.POSITIVE_INFINITY) { + range.min = Math.min(range.min, value); + range.max = Math.max(range.max, value); + } + const y = records[index * PLACEMENT_RECORD_FLOATS + 1] as number; + range.minY = Math.min(range.minY, y); + range.maxY = Math.max(range.maxY, y); + } + ranges.set(id, range); + reachRanges.set(cell, ranges); + }, + { marker: false }, + ); + } + const named = Object.keys(manifest.assets).length; + console.info( + `TN_WORLD_ASSET_ALIAS assets=${String(named)} canonical=${String(named - aliases.size)}`, + ); + return new WorldCells({ + ...options, + aliases, + reachRanges, + assets, + baseUrl, + heightmap, + logicalBase, + manifest, + // The loader made here when the game supplied none is this world's to release; one the game + // passed in is the game's, and `#releaseModelPaths` never touches its cache. + ownsAssets: options.assets === undefined, + placements, + }); } - /** - * Dress every main batch the world already holds, the mirror of {@link #seedGpuSources} for the - * keys rather than the sources: the keys a prewarm minted on the loading screen were minted with - * the scene off, and a key is only named at mint time, so a ring built that way came up with an - * empty region table and every batch still drawing from its own buffer. The dispatch was drawing - * 21,094 placements into no region at all, and the validation marker said so — `compared=0` — for - * a scene that reported itself on. - * - * The pool is seeded with the rest: a retained key's mesh is handed back by a rebind rather than - * re-minted, and rebinding replaces the instance buffer, so a parked batch is undressed by the - * park and dressing it here is what leaves the walk with a key that is already in the table. From - * here every main batch carries `gpu`, which is what puts it on the dispatch's per-instance answer - * and off the CPU repack and the refilter. - */ - #seedGpuKeys(): void { - for (const shared of [...this.#shared.values()]) this.#seedGpuKey(shared); - for (const shared of this.#retired.values()) this.#seedGpuKey(shared); - } - - /** Dress one undressed main batch, or leave a batch the scene does not touch. */ - #seedGpuKey(shared: SharedBatch): void { - // `main` is the key; `gpu` is the mesh pointing at the scene's buffers. A dressed mesh is left - // alone, and `#dressGpu` says so itself — it is idempotent against the buffers it was given. - if (shared.main === undefined || shared.gpu !== undefined) return; - this.#dressGpu(shared, shared.main); - } - - /** - * The main meshes the scene owns and the main meshes the world holds, live and pooled, which is - * the `dressed=N/M` the marker carries. A scene reporting itself on over a ring it never dressed - * read `dressed=0/21094` on nothing, so the number is the owner's: the scene cannot see the meshes. - */ - #gpuCensus(): { readonly dressed: number; readonly meshes: number } { - let dressed = 0; - let meshes = 0; - for (const batch of [...this.#shared.values(), ...this.#retired.values()]) { - if (batch.role !== "main") continue; - meshes += 1; - if (batch.gpu !== undefined) dressed += 1; - } - return { dressed, meshes }; + get released(): boolean { + return this.#released; } - /** - * Every dressed main mesh's own draw, and what the CPU-published source says it must be holding. - * - * The scene's own validation mirrors its own kernel, so a key, a record, a gate table and a mesh - * that do not correspond are one both sides reproduce and a check that reports `ok` over a forest - * drawn with another tree's geometry. These are the three numbers that are not the scene's: the - * mesh's own name, the record its own geometry points at (`indirectOffset`, which a dress wrote), - * and the instances the owner's part transforms compose for that name. The level and cull gates - * come from the CPU snapshot published with the current source records, because a replacement - * keeps its old mesh drawing while the GPU slot may already name the future representation; the - * camera's own six planes over the scene's own placements and each part's own offset remain - * independently composed here. - * - * Not the per-cell records this path's own refilter left behind: the GPU scene bypasses that - * refilter, and a queued replacement deliberately leaves those records and their old mesh live - * until the swap. The dispatch's table is deliberately not reused for these gates: the check must - * still fail when that table is wrong while the CPU-published source is unchanged. - */ - #gpuDraws(): readonly IMeshDraw[] { - const camera = this.#camera; - if (camera === undefined) return []; - const lives = this.#liveGates(); - if (lives.size === 0) return []; - // The dispatch's own camera, its own planes: this is asked for inside the dispatch that is - // running, so the eye and the six planes are the ones the kernel is reading now. - const planes = this.#gpuPlanes; - _cullFrustum.setFromProjectionMatrix( - _cullProjScreen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse), - ); - for (const [index, plane] of _cullFrustum.planes.entries()) { - const at = index * 4; - planes[at] = plane.normal.x; - planes[at + 1] = plane.normal.y; - planes[at + 2] = plane.normal.z; - planes[at + 3] = plane.constant; - } - _gpuEye.setFromMatrixPosition(camera.matrixWorld); - const expected = liveKeyInstances( - this.#gpuScene.placements, - (slot) => { - const id = this.#gpuScene.slotAsset(slot); - return id === undefined ? undefined : lives.get(id); - }, - { planes, x: _gpuEye.x, y: _gpuEye.y, z: _gpuEye.z }, - ); - const draws: IMeshDraw[] = []; - const args = this.#gpuScene.args; - for (const shared of this.#shared.values()) { - if (shared.role !== "main" || shared.gpu === undefined) continue; - const geometry = shared.mesh.geometry; - // `indirectOffset` is typed as an offset or a list of them; a scene's own record is one - // number, and a list is not a record this check can name, so it is not compared. - const offset = geometry.indirectOffset; - if (geometry.indirect === undefined || typeof offset !== "number") continue; - const region = this.#gpuScene.regionOf(shared.mesh.name); - draws.push({ - // The actual binding, asked of the mesh and the scene at this turn, not the record number: - // an offset can name the right record while the mesh reads an older args buffer with the - // same layout, and the field is what lets the check see a stale binding at all. - bound: - args !== undefined && - geometry.indirect === args && - offset === (region?.argsIndex ?? -1) * DRAW_ARGS_BYTES, - instances: expected.get(shared.mesh.name) ?? new Float32Array(0), - name: shared.mesh.name, - record: offset / DRAW_ARGS_BYTES, - }); - } - return draws; + get warmupNodes(): readonly unknown[] { + return this.#terrain?.warmupNodes ?? []; } /** - * The CPU-published gates with the owner's part offsets, keyed by asset id. + * Resolves once every minted batch has been drawn at least once, main and shadow context. * - * A queued source replacement may update the GPU slot before its old mesh and source records are - * swapped. The publication copy stays old until that swap, while the local matrices still come - * from the owner rather than from the scene's own reference data. + * The loading gate a game with a loading screen waits on, so the shader builds the prewarm exists + * to move happen behind it instead of in the first seconds of play. It counts updates the world + * was in a scene for, because a batch added while the world is not in a scene is projected by + * nothing and builds nothing. `dispose` settles it too: a world torn down mid-load must not leave + * a game awaiting it forever. */ - #liveGates(): Map { - const out = new Map(); - const slots = this.#gpuScene.gates(); - for (const [slot] of slots.entries()) { - const id = this.#gpuScene.slotAsset(slot); - const asset = id === undefined ? undefined : this.#assets.get(id); - const published = id === undefined ? undefined : this.#publishedGpuGates.get(id); - if (asset === undefined || published === undefined) continue; - out.set(asset.id, { - cull: published.cull, - distances: published.distances, - id: asset.id, - impostor: published.impostor, - locals: published.locals, - }); - } - return out; + get prewarmed(): Promise { + return this.#prewarm.prewarmed; } /** - * The source records one swapped-in entry draws from: one per placement, not per part, because the - * part's own offset is the key's and the dispatch composes it. Every part of the level shares the - * list, which is why releasing one is idempotent and a whole level's records are handed back by the - * first of its entries that leaves. + * Fence one just-rendered GPU dispatch for a diagnostic walk, resolving after both readbacks land. + * Call after {@link update} and the render, then await it before advancing the measured frame. */ - #placeSources(job: IBuildJob, entry: ICellBatch): number[] | undefined { - const sources = job.sources; - if (sources === undefined) return undefined; - const asset = this.#assets.get(entry.asset); - if (asset === undefined) return undefined; - const slot = this.#gpuScene.slot(entry.asset, this.#gatesOf(entry.asset, asset)); - if (slot < 0) return undefined; - const byLevel = job.gpuByLevel ?? new Map(); - job.gpuByLevel = byLevel; - const held = byLevel.get(entry.level); - // A sibling part of this level shares the list already placed, and counts its own halves for - // itself: `#gpuResident` is the asset's, so this half's records are counted here. - if (held !== undefined) { - this.#countPlaced(entry, held.length); - return held; - } - const records: number[] = []; - for (const source of sources) - if (source.level === entry.level) { - const at = this.#gpuScene.place( - slot, - source.matrix, - source.x, - source.y, - source.z, - source.radius, - source.scale, - ); - if (at >= 0) records.push(at); - } - byLevel.set(entry.level, records); - // What a key of this asset has to be able to hold, on any level: see `#dressGpu`. - this.#gpuResident.set(entry.asset, (this.#gpuResident.get(entry.asset) ?? 0) + records.length); - this.#countPlaced(entry, records.length); - return records; + validateGpuFrame(frame: number): Promise { + if (this.#released) + return Promise.reject(new Error("World GPU frame validation cannot run after dispose.")); + if (this.#chunks.renderer === undefined) + return Promise.reject(new Error("World GPU frame validation needs a renderer.")); + return this.#render.validateGpuFrame(this.#chunks.renderer, frame); } /** - * The placed sources one cell's block contributed, carried onto the two caster halves behind it — - * `gpuPlaced`, the count `#publishCasterAdmission` measures a half against its own live records. + * Per-frame residency step; call it wherever `TerrainTiles.process` is called. * - * One place for both halves and both directions, because `#gpuResident` counts the asset once and a - * half counts only what its own block contributed: every part of a level shares one list of placed - * sources and each half needs its own answer. Negative is the release path's way back down. - */ - #countPlaced(entry: ICellBatch, placed: number): void { - if (entry.caster !== undefined) entry.caster.gpuPlaced += placed; - if (entry.wide !== undefined) entry.wide.gpuPlaced += placed; - } - - /** The world-grid square the follow point is in, as the `@x,z` half of a cluster key. */ - #followCluster(): string { - return clusterOf( - Math.floor((this.#follow.position.x - this.#minX) / this.#cellSize), - Math.floor((this.#follow.position.z - this.#minZ) / this.#cellSize), - this.#cellsPerCluster, - ); - } - - /** What a released batch costs to keep: its instance buffer, which is all of it. */ - #heldBytes(shared: SharedBatch): number { - return (shared.mesh.instanceMatrix.array as Float32Array).byteLength; - } - - /** - * Keeps a released batch for the walk back, or drops it. + * Reads the follow target, keeps the in-ring cells, evicts cells beyond the hysteresis ring, and + * spends one admission budget on everything the ring newly wants: cell-asset batches, terrain + * tiles, colliders and the `maxDistance`/`lods` refilters the follow point has moved far enough to + * have changed. Whatever does not fit waits for the next call — nothing is dropped, and + * `stats().admission` is the honest report of what is waiting. A terrain budget throw is caught + * and counted, and so is a teardown that throws while releasing what left; `failures` carries + * both. Every other error, the game's included, escapes. + * + * The residency pass itself is skipped while the follow point has moved less than half a metre + * since the last one and nothing is pending (a load, a cell build, a deferred terrain admission, a + * prewarm mint or a shadow level not yet told), because the pass would re-derive the set the + * world already holds. The drain, the prewarm, the invalidation and the main cull below it run + * every time. * - * Empty is the precondition, and `#evict` guarantees it: it clears a cell's own block before the - * refcount falls, and the refcount is what calls this. A batch that still holds records means the - * buffer state cannot be trusted, so it goes the same way as one over the byte budget. + * `camera` is the frame's render camera, which the engine's render-cadence dispatch hands over + * because it is the one this world draws with. A game that drives this world itself can pass it, + * and a game that does not keeps every resident record drawn. */ - #retire(key: string, shared: SharedBatch): void { - // Live records, not the draw window: a main batch whose squares are all behind the camera still - // holds its records, and that is not a batch to dispose. - if (shared.live > 0) { - shared.mesh.dispose(); - return; + update(renderer?: IRendererLike, camera?: Camera): void { + if (this.#released) return; + // Kept for the chunk warm-up below: a chunk is loaded asynchronously and compiled when it + // arrives, which can be a frame or a loading screen after the frame that gave us these. + if (renderer !== undefined) this.#chunks.renderer = renderer; + if (camera !== undefined) this.#chunks.camera = camera; + this.#render.flushCompiles(renderer); + // The adaptive LOD bias, before the dispatch below reads the gates it scales. + this.#adaptLodBias(renderer); + this.#render.enableGpu( + renderer, + this.#gpuWanted, + this.#gpuValidate, + this.#gpuTally, + this.#occlusion, + ); + // The scene coming up under a ring that was already built: every placement swapped in before it + // was on has no source record, and the dispatch draws nothing it is not given. One rebuild of the + // resident ring puts them in, through the same build every first-seen asset takes. + if (this.#render.gpuOn && this.#render.gpuSeeded === false) { + this.#render.gpuSeeded = true; + this.#seedGpuSources(); + this.#render.seedGpuKeys(); + // After the keys, because that is the only point at which the marker can say how many of the + // world's main meshes are dressed: printed at enable it would read the ring as it was a frame + // earlier, which is the state the run that found this was in. + this.#render.announceGpu(); } - // Parked before the budget is measured, or the retained set is charged for buffers nothing keeps - // and the byte budget evicts entries as they arrive — the walk then finds nothing to hand back - // and mints a second mesh for every key it returns to. - shared.park(); - const bytes = this.#heldBytes(shared); - this.#retired.delete(key); - this.#retired.set(key, shared); - this.#retiredBytes += bytes; - for (const [oldest, held] of this.#retired) { - if (this.#retiredBytes <= RETIRED_SHARED_BYTES) break; - this.#retired.delete(oldest); - this.#retiredBytes -= this.#heldBytes(held); - held.mesh.dispose(); + // A new upload epoch, so no batch's pending span outlives the render that consumed it. See + // `SharedBatch#touched`. + advanceWriteEpoch(); + const x = this.#follow.position.x; + const z = this.#follow.position.z; + // Terrain admits first, and a tile is one unit of several milliseconds, so after a jump (a + // teleport, a review camera) the 17 x 17 terrain ring spent the whole allowance every frame for + // seconds and the prop queue got nothing: the forest never built where the camera landed. While + // props are queued the terrain is held to half, and the props get whatever it left. + const budgetMs = this.#covered ? this.#coveredBudgetMs : this.#budgetMs; + const terrainMs = this.#jobs.length > 0 ? budgetMs / 2 : budgetMs; + const budget = new AdmissionBudget(terrainMs, this.#now); + this.#render.resetFresh(); + // The residency pass is a function of the follow point, so a follow point that has not moved + // and a world with nothing pending has the same answer as the last update: hundreds of cells + // walked and re-sorted, every resident cell refiltered, every terrain tile re-levelled, all of + // it to reach the set it already holds. Skipped while both hold. A move, a load, a build, a + // deferred admission or a shadow level not yet told runs the pass exactly as before. + const terrainView = + this.#terrain !== undefined && + this.#chunks.camera !== undefined && + this.#chunks.renderer?.domElement?.height !== undefined + ? { + camera: this.#chunks.camera, + viewportHeight: this.#chunks.renderer.domElement.height, + maxPixelError: this.#autoLodMaxPixelError, + hysteresis: this.#lodHysteresis, + } + : undefined; + if (this.#residencyStale(x, z)) { + this.#residencyPoint.set(x, this.#followY(), z); + try { + this.#terrain?.follow({ x, z }, budget, terrainView); + this.#terrain?.process(renderer, camera, undefined, terrainView); + } catch (error) { + if (!(error instanceof TerrainTileBudgetError)) throw error; + this.#pressure.bytes += 1; + } + const ahead = this.#ahead(x, z); + this.#updateResidency( + { + x: Math.floor((ahead.x - this.#minX) / this.#cellSize), + z: Math.floor((ahead.z - this.#minZ) / this.#cellSize), + }, + ahead.x, + ahead.z, + ); + // The refilter is a scan of every resident cell, so it runs on its own cadence rather than + // with the residency pass above; see `#refilterStale` and `LEVEL_REFILTER_STEP_METRES`. + // GPU source admission keeps every resident maxDistance candidate, so the dispatch's slot + // cull and level gates handle a moving camera without a CPU replacement. `#staleIn` still + // watches placementReach, whose per-record admission boundary has no GPU slot equivalent. + if (this.#refilterStale(x, z)) { + this.#refilterPoint.set(x, this.#followY(), z); + this.#refilterEpoch = this.#residencyEpoch; + this.#updateMaxDistance(x, z); + } + } else if ( + // A blend in flight is not a settled ring, and the blend is advanced by `process` alone — + // `follow` only re-levels tiles. A follow point standing still used to skip both, so a + // three-frame tile morph froze on its first frame until the player moved again. + // With a render view, settled tiles also recheck cached errors against the current projection. + terrainView !== undefined || + (this.#terrain?.blendingTiles ?? 0) > 0 + ) { + this.#terrain?.process(renderer, camera, undefined, terrainView); } + const props = new AdmissionBudget(budgetMs - Math.min(budget.spentMs, terrainMs), this.#now); + this.#drain(props); + // The prewarm runs outside the admission budget on purpose — it is not residency, it is the + // shader builds the residency is about to need, and the allowance that spreads those is + // `PREWARM_PER_UPDATE`. + this.#prewarm.drainPrewarm(); + // One bake view per render update, after the prewarm so its own atlas allocation never delays a + // node build the walk is waiting on, and before the levels are told so a level the bake just + // appended is in this frame's window. + this.#impostorState.stepImpostor(renderer); + // After the bake handoff, so a wide block it deferred to this frame's exhausted allowance is + // claimed now; a no-op on every frame with nothing waiting. + this.#impostorState.retryWide(); + // After the near work, so a species no resident run has asked for still bakes and reaches the + // far mesh without the camera ever traversing its cell. Bounded to one asset per idle frame. + this.#impostorState.pumpFarAcquisition(); + // After the drain, so the levels are told about this update's records and not the last one's. + this.#render.tellShadows(); + // After the drain too, so a record admitted this update is in the window the camera draws and + // not one frame behind it. + this.#chunks.restoreSources(); + this.#render.cullMainPass(camera); + // With it, because it reads the drain's own answer: a caster whose main counterpart is not + // drawable this frame must not cast before the levels render this frame's maps. + this.#render.publishCasterAdmission(); + // After main publication/culling: an exclusive source key stays fully resident, so returning + // near detail never waits for admission or an instance allocation. Proxy loads/staging alone + // consume admission units; the synchronous visibility commit always protects the current view. + this.#chunks.stepCellProxies( + this.#resident.values(), + this.matrixWorld, + props, + renderer, + camera, + ); + this.#render.reportMainCull(); + this.#admission = { + backlog: this.#backlog(), + deferred: this.#jobs.length, + spentMs: budget.spentMs + props.spentMs, + }; + // After this frame's admissions, so a region admitted by this update answers its waiters in it. + this.#settleSpawnWaiters(); } - /** - * Whether this level's own batches cast. Only the caster clusters ask; a main mesh never casts, so - * the shadow map holds exactly the cluster meshes a level's window covers. - */ - #casts(level: number): boolean { - return this.#castShadowLevels > level; - } - - /** - * An asset too small to resolve in any window but the finest one's, so its wide caster goes on - * that level's layer alone. Measured on the authored bounds the package carries, which is the one - * number a world has for every asset it never loaded a tree for. - */ - #smallCaster(assetId: string): boolean { - const bounds = this.#manifest.assets[assetId]?.bounds; - if (bounds === undefined) return false; - return bounds.max[1] - bounds.min[1] < this.#smallCasterMetres; + /** Borrowed scratch box: consume its transformed volume before the next bounds query. */ + #boundsAt(bounds: IWorldAsset["bounds"], matrix: Matrix4): Box3 { + this.#recordBounds.min.fromArray(bounds.min); + this.#recordBounds.max.fromArray(bounds.max); + return this.#recordBounds.applyMatrix4(matrix); } /** - * A block in `shared` for one cell's `count` records, growing the buffer when it is full. `cluster` - * is the world-grid square the cell sits in, and only a main batch keeps it: its draw window is - * narrowed per square, and a square is a grouping, not a position. + * Does this update have to run the residency pass, or is the follow point still inside the half + * metre it last moved less than and the world holding still? + * + * "Still" is every kind of work the pass exists to hand on: a model load in flight or queued, a + * cell build in `#jobs` (which is also what queues refilters), a prewarm mint owed or in flight, a + * terrain admission or collider the admission budget refused, and a companion that has not been + * rebuilt since the follow point last moved. A world standing still on a loaded ring answers none + * of those, so the pass is skipped; the first update after anything changes is a full pass again. */ - #segmentIn(shared: SharedBatch, count: number, cluster?: string): number | undefined { - const segment = shared.allocate(count, cluster); - if (segment !== undefined) return segment; - // A dressed batch's grow is logical: it raises the ceiling the GPU scene sizes this key's region - // from and mints no mesh, so the fresh-mesh allowance — which bounds new pooled meshes and their - // node builds — does not gate it. A CPU grow is a real replacement mesh and still respects it. - if (shared.gpu === undefined && this.#freshThisUpdate >= this.#freshAllowance()) - return undefined; - const old = shared.grow(); - // Only a real replacement is a new object to dress, attach and hand to the pool; a logical GPU - // grow keeps the live mesh, which `#dressGpu` re-points at the grown region before the draw. - if (old !== undefined) { - this.#freshThisUpdate += 1; - // The replacement is a different object, so the layer and the cull hook `grow` copied neither of - // have to be dressed onto it again — a grown caster that kept layer 0 would be drawn by the main - // camera and lost to the shadow level, and a grown main batch would stop narrowing at all. - this.#dressMesh(shared, shared.role === "main" && this.#receiveShadow); - this.#attach(shared); - // The mesh this grow replaced goes back to the pool: a cached shadow level can still replay a - // draw of it until its window next moves, and the pool keeps the buffer and the uuid rather - // than letting one array of retired meshes grow without bound. - parkMesh(old); - } - return shared.allocate(count, cluster); + #residencyStale(x: number, z: number): boolean { + if ( + Math.hypot( + x - this.#residencyPoint.x, + this.#followY() - this.#residencyPoint.y, + z - this.#residencyPoint.z, + ) >= SETTLED_FOLLOW_METRES + ) + return true; + return ( + this.#models.loadsInFlight > 0 || + this.#models.loadsQueued > 0 || + this.#jobs.length > 0 || + this.#refilterOwed || + this.#prewarm.queued > 0 || + this.#prewarm.pending > 0 || + this.#render.shadowMoved === true || + (this.#terrain?.deferredAdmissions ?? 0) > 0 + ); } /** - * One model, from the loader the package was given. `loadModel` keeps the override and takes the - * authored url, so a game's own GLB loader is unchanged; without it the logical path goes to - * `assets.model`, which is what makes a compiled package and its KTX2 textures work. + * Does this update have to run the `maxDistance`/`lods` refilter pass? + * + * Three things, and the pass runs when any of them holds: the follow point has moved + * `LEVEL_REFILTER_STEP_METRES` since the last one, so a walk re-derives its brackets every two + * metres instead of every half; residency changed, because a cell that arrived or left is the + * bracket the pass is about; and the last pass found more stale cell-assets than + * `rebuildsPerUpdate` could queue, so the ones it could not queue are still owed and a still + * follow point has to keep coming back for them. Anything else is the same brackets, so the pass + * is skipped and the world does nothing for that update. */ - #model(path: string): Promise { - return this.#loadModel === undefined - ? loadModelWith(this.#loader, path) - : this.#loadModel(resolveRelative(this.#baseUrl, path)); + #followY(): number { + return this.#placementReach === undefined ? 0 : (this.#follow.position.y ?? 0); } - /** - * Every level of one asset, through the same limiter and the same resolution as `lod0`, so a - * package's compiled output and compressed textures reach the renderer the game booted with. One - * level refusing is not the asset refusing: it is counted and `#adoptAsset` falls back to the - * level below it, so the far placements still draw. - */ - #startAssetLoad(asset: IAssetState): void { - asset.pending = true; - // The source models this state asks `assets.model` for are this world's to hand back; the - // loader caches by logical path and would otherwise pin every texture for the world's life. - // Counted once per state, so a load retried after an empty answer does not double-count. - // `loadModel` overrides the cache entirely, so it retains nothing. - if (this.#loadModel === undefined && asset.retainedPaths.length === 0) { - this.#retainModelPaths( - Array.from(asset.glbs, (glb) => resolveRelative(this.#logicalBase, glb)), - asset.retainedPaths, - ); - } - const wanted = (): boolean => - !this.#released && this.#assets.get(asset.id) === asset && !asset.disposed; - // `Promise.all` hands back the levels in `glbs` order, and the limiter is what bounds them: it - // already caps every load in flight across the resident cells, assets and chunks together. - const loads: Array> = []; - for (const glb of asset.glbs) { - const path = resolveRelative(this.#logicalBase, glb); - loads.push( - this.#limiter - .load(() => this.#model(path), wanted) - .catch((error: unknown) => { - asset.failed = true; - this.#failures += 1; - this.#reportCellFailure("asset", asset.id, [path], error); - return undefined; - }), - ); - } - // The cooked url LOD0 was actually fetched from, resolved the same way `assetSignature` does, - // so the atlas key names the bytes rather than the package entry. It is needed before adoption, - // so the two promises are awaited together; a path the loader cannot resolve falls back to the - // logical url, exactly as `assetSignature`'s caller does. - const lod0 = resolveRelative(this.#logicalBase, asset.glbs[0] as string); - const resolved = this.#loader - .resolve(lod0) - .then((candidates) => candidates[0] ?? lod0) - .catch(() => lod0); - void Promise.all([Promise.all(loads), resolved]).then(([models, url]) => { - // The state stays, refcount and all, whatever the loads answered: cells still resident are - // holding it, and the next acquire retries. Dropping it here would let a later cell refcount - // from zero and hand an eviction of an old cell the geometry a still-resident cell draws. - asset.resolvedGlb = url; - asset.pending = false; - this.#adoptAsset(asset, models); - }); + #refilterStepMetres(): number { + return this.#placementReach === undefined ? LEVEL_REFILTER_STEP_METRES : 0.75; } - /** One loaded model's drawable parts, or `undefined` when it carries nothing to draw. */ - #partsOf(model: Object3D | undefined): readonly IAssetPart[] | undefined { - if (model === undefined) return undefined; - const parts = renderableParts(model, (material) => this.#surfaceFor(material)); - if (parts.length === 0) { - // Same as a refused load: counted, released, and the next acquire retries. - this.#failures += 1; - this.#failures += this.#disposeLoaded(model); - return undefined; - } - return parts; + #refilterStale(x: number, z: number): boolean { + if (this.#refilterOwed) return true; + if (this.#refilterEpoch !== this.#residencyEpoch) return true; + return ( + Math.hypot( + x - this.#refilterPoint.x, + this.#followY() - this.#refilterPoint.y, + z - this.#refilterPoint.z, + ) >= this.#refilterStepMetres() + ); } - /** - * One part list per loaded model: level 0 is the asset's own shape, and every level below it - * falls back to the one above when it did not load. `undefined` is the asset refusing to exist — - * no `lod0` to fall back to — and the levels that did load are released rather than held by an - * asset that draws nothing, cutout clones included. The refcounted state stays either way, so the - * next acquire retries. - */ - #adoptLevels( - models: readonly (Object3D | undefined)[], - ): readonly (readonly IAssetPart[])[] | undefined { - const levels: (readonly IAssetPart[])[] = []; - for (const [index, model] of models.entries()) { - const level = this.#partsOf(model) ?? levels[index - 1]; - if (level === undefined) { - for (const model of models.slice(index)) this.#failures += this.#disposeLoaded(model); - for (const parts of levels) this.#failures += this.#releaseParts(parts); - return undefined; - } - levels.push(level); + process(renderer?: IRendererLike, camera?: Camera, covered = false): void { + this.#covered = covered; + try { + this.update(renderer, camera); + } finally { + this.#covered = false; } - return levels; } /** - * An asset's levels are in, so every resident cell holding it needs its batches — queued, not - * built. This is the path that used to be worst: adopting one asset rebuilt every resident cell's - * batches for it in a single frame, and a 218-asset package pays it over and over as the ring - * moves. The queue turns it into the same per-frame allowance every other admission path gets. + * The adaptive LOD bias control loop. Reads the main pass's smoothed GPU time from the renderer + * and, once per `LOD_BIAS_STEP_SECONDS`, raises the shared bias while that pass is over its share + * of the frame and decays it back toward 1 while it is comfortably under; between the two it + * holds, which is the band that keeps a level sitting on the budget from oscillating. A window with + * no fresh sample is a hold, never a rise on a stale number, and a rise needs the world itself to be + * the thing being drawn — see {@link #biasOverBudget}, which is also what replaced the warmup + * seconds. The decay is never gated: a world that can afford its main pass gives the bias back. */ - #adoptAsset(asset: IAssetState, models: readonly (Object3D | undefined)[]): void { - if (this.#released || this.#assets.get(asset.id) !== asset || asset.disposed) { - for (const model of models) this.#failures += this.#disposeLoaded(model); - return; - } - asset.hlodSafe = models[0] !== undefined && cellProxySourceSafe(models[0]); - const adopted = this.#adoptLevels(models); - if (adopted === undefined) { - // A far-only asset that would not load has nothing to bake; drop the temporary reference so - // the source it did fetch is not held for the world's whole life. - if (this.#farTemp.has(asset.id)) this.#releaseFarTemp(asset.id); - return; - } - // Before the prewarm and the queued builds, both of which read the levels, the distances and - // the gates: an asset whose chain widened it is batched at those levels from its first build. - asset.levels = this.#widenWithChain(asset, adopted); - // A far-only asset is drawn by no near run, so it takes no prewarm and queues no cell build: - // its whole job is the atlas behind the aggregate, and its source is released once that lands. - if (this.#farTemp.has(asset.id)) { - this.#maybeQueueImpostor(asset); - // Nothing to bake (no alpha-cutout LOD0) and nothing queued: release the source now. - if (asset.impostor === undefined && !this.#impostorQueued.has(asset.id)) - this.#releaseFarTemp(asset.id); + #adaptLodBias(renderer: IRendererLike | undefined): void { + if (this.#adaptiveLod === false) { + // Byte-identical to the pre-bias selection: the multiplier is pinned at 1 every frame. + this.#applyLodBias(LOD_BIAS_MIN); return; } - // After the chain, so the impostor's switch sits beyond every synthesized gate; before the - // prewarm, so a cached atlas appends its level in this same admission. - this.#maybeQueueImpostor(asset); - // Every level and part of the asset now, before any of them is asked for. A level with no - // placement in it is not drawn and would otherwise mint its mesh — and pay its node build — on - // the frame a walk first moves far enough to switch into it. - this.#queuePrewarm(asset); - for (const cell of [...this.#resident.values()]) { - for (const run of cell.cell.runs) { - if (this.#canonical(run.asset) === asset.id) this.#queueBuild(asset, cell, run); - } + const now = this.#now(); + if (now - this.#lodBiasStepAt < LOD_BIAS_STEP_SECONDS * 1000) return; + const source = renderer ?? this.#chunks.renderer; + // The frame the budget is a share of: the game's resolved target, 60 fps until one is known. + // Never the frame just measured — an expensive pass makes its own frame long and would pass its + // own test, and a fast one shrank the share until a 3 ms pass coarsened a world running at + // 400 fps against a 120 fps target (PRD-478). + const target = source?.targetFps?.(); + const frameMs = 1000 / (target !== undefined && target > 0 ? target : DEFAULT_TARGET_FPS); + const gpuMs = source?.gpuMainMs?.(); + if (gpuMs === undefined) return; + this.#lodBiasStepAt = now; + const affordableMs = this.#mainGpuShare * frameMs; + if (!(affordableMs > 0)) return; + const bias = + gpuMs > affordableMs + ? this.#biasOverBudget() + : gpuMs < LOD_BIAS_DECAY_SHARE * affordableMs + ? Math.max(LOD_BIAS_MIN, this.#lodBias * LOD_BIAS_DECAY) + : this.#lodBias; + if (bias === this.#lodBias) return; + this.#applyLodBias(bias); + if (now - this.#lodBiasToldAt >= LOD_BIAS_MARKER_SECONDS * 1000) { + this.#lodBiasToldAt = now; + const triangles = this.gpuSceneTally()?.triangles; + console.info( + `${LOD_BIAS_MARKER} bias=${bias.toFixed(3)} gpuMs=${gpuMs.toFixed(2)} ` + + `budgetMs=${affordableMs.toFixed(2)} ` + + `tris=${triangles === undefined ? "n/a" : String(triangles)}`, + ); } } /** - * An asset's loaded levels, widened with the ones its model's baked AutoLOD chain carries. + * The next bias while the main pass is over its share of the frame, or the bias unchanged when the + * world is not what that pass is drawing. + * + * A load's GPU time is shader compiles, buffer uploads and the prewarm — none of it main-pass world + * geometry, so a coarser selection cannot take any of it back. What it costs is the first playable + * frame: every tree selects the level authored for its distance times the bias, the coarsest levels + * hold almost nothing, and the shadow casters, which take no bias, leave a band of tree shadows with + * no trees 80-300 m out. Measured on Machinefall (RTX 2080, WebGPU), the bias climbed 1.08 → 2.33 + * over six `TN_LOD_BIAS` steps, all of them before the loading overlay dropped. * - * An asset with authored `lods` keeps its own switch distances and its own middle geometry: each - * level draws the shape the pipeline authored for it, with the root LOD0's alpha cutouts appended - * to any level that authored fewer of them so no middle loses its leaves; see - * `coverAuthoredLevels`. A chain the loader also happens to have registered is a fallback for a - * package that named no `lods`, not a second opinion over one that did. Everything downstream is - * unchanged either way — a level is a level, and its switch is a gate like any other — so the - * batch keys, the prewarm, the caster clusters and the retire/rebind pool all see the derived - * levels as ordinary ones. + * So the rise waits for the two measurements that say the world is being drawn: the prewarm gate — + * what a game's loading screen waits on, and `stats().pendingPrewarm` as the same thing as a number + * — and the main pass's own GPU-selected triangle tally, where a tally that landed and says zero is + * a world drawn as nothing. A world with no tally at all (no GPU scene, or nothing landed yet) has no + * opinion about its own triangles, so the prewarm speaks for it alone. Both are measurements of the + * thing being judged, which is what the warmup seconds were standing in for. */ - #widenWithChain( - asset: IAssetState, - levels: readonly (readonly IAssetPart[])[], - ): readonly (readonly IAssetPart[])[] { - if ((asset.definition.lods?.length ?? 0) > 0) - return this.#preserveAuthoredParts ? levels : coverAuthoredLevels(levels); - // An empty list names no authored rung. Prepared static parts can use their existing chain, - // while sectioned/deforming or replaced-attribute copies keep their supplied source shape. - if (asset.definition.lods !== undefined && !(levels[0] ?? []).every(preparedChainSafe)) - return levels; - const chained = chainLevels( - levels[0] as readonly IAssetPart[], - this.#autoLodPixelsPerUnit, - this.#autoLodMaxPixelError, - asset.definition.maxDistance, - ); - if (chained === undefined) return levels; - asset.distances = chained.distances; - asset.gates = [...chained.distances.slice(1), ...asset.gates]; - for (const gate of asset.gates) this.#noteGate(gate); - asset.threshold = asset.gates.length === 0 ? undefined : Math.min(...asset.gates); - this.#reportChainLod(asset.id, chained); - return chained.levels; + #biasOverBudget(): number { + if (this.#prewarm.queued > 0 || this.#prewarm.owed > this.#prewarm.drawn) return this.#lodBias; + if (this.gpuSceneTally()?.triangles === 0) return this.#lodBias; + return Math.min(LOD_BIAS_MAX, this.#lodBias * LOD_BIAS_RISE); } - /** - * Whether this asset can append a whole-asset impostor, so its builds keep the placement-root - * records the far quad draws from. The same test `#maybeQueueImpostor` makes, minus the cache: an - * asset with no alpha-cutout LOD0 part never bakes, so it never needs the extra records. - */ - #rootsNeeded(asset: IAssetState): boolean { - if (this.#impostors === false) return false; - const parts = asset.levels[0]; - return parts !== undefined && this.#impostorSource(parts) !== undefined; + /** Writes the bias to both selection paths: the shared CPU multiplier and the GPU uniform. */ + #applyLodBias(bias: number): void { + this.#lodBias = bias; + setLodBias(bias); + this.#render.setLodBias(bias); } /** - * The first LOD0 part that is alpha foliage, and the cutoff its own material authored — or - * `undefined` when no part is. A BLEND part carries `alphaTest 0`, and the scatter path draws it - * through the same cutout a MASK part gets, so the contract records that default rather than a - * zero that would bake a solid impostor. + * Where the ring centres: the follow point led by its smoothed velocity. Samples closer than a + * few milliseconds are the same frame and move nothing; a jump longer than the ring is a teleport + * and resets the velocity rather than reading as speed. */ - #impostorSource( - parts: readonly IAssetPart[], - ): { readonly source: Material; readonly alphaTest: number } | undefined { - for (const part of parts) { - const material = part.material; - if (!isAlphaFoliage(material)) continue; - return { - source: material, - alphaTest: material.alphaTest > 0 ? material.alphaTest : DEFAULT_CUTOUT_ALPHA, - }; + #ahead(x: number, z: number): { x: number; z: number } { + if (this.#prefetchSeconds === 0) return { x, z }; + const now = this.#now(); + const last = this.#lastSample; + if (last === undefined) { + this.#lastSample = { t: now, x, z }; + } else { + const seconds = (now - last.t) / 1000; + if (seconds >= 0.004) { + const dx = x - last.x; + const dz = z - last.z; + if (Math.hypot(dx, dz) > this.#cellSize * this.#ring) { + this.#velocityX = 0; + this.#velocityZ = 0; + } else { + // ~0.25 s smoothing, so one jittery frame does not swing the ring. + const blend = Math.min(1, seconds / 0.25); + this.#velocityX += (dx / seconds - this.#velocityX) * blend; + this.#velocityZ += (dz / seconds - this.#velocityZ) * blend; + } + this.#lastSample = { t: now, x, z }; + } + } + let leadX = this.#velocityX * this.#prefetchSeconds; + let leadZ = this.#velocityZ * this.#prefetchSeconds; + const limit = this.#cellSize * this.#ring * 0.75; + const length = Math.hypot(leadX, leadZ); + if (length > limit) { + leadX *= limit / length; + leadZ *= limit / length; } - return undefined; + return { x: x + leadX, z: z + leadZ }; } - /** - * The content key one impostor atlas is cached under: the loader's resolved cooked url of LOD0, - * plus every LOD0 source part's surface/content key and its exact cutout contract. Two package - * entries that fetch the same cooked model and draw the same parts with the same cutoffs are one - * bake, which is the canonical-asset idea applied to the atlas rather than to the levels — the - * package's own `lods`/`maxDistance` metadata may have stopped the two entries aliasing, but the - * atlas is built from LOD0 alone, so it must be shared anyway. - * - * The cutoff is the exact number the bake uses, never a rounded one: `0.42` and `0.420001` are - * different silhouettes and must not collide in the cache. - */ - #impostorKey(asset: IAssetState): string { - const parts = asset.levels[0] ?? []; - const contract: string[] = []; - for (const part of parts) { - const cutoff = isAlphaFoliage(part.material) - ? part.material.alphaTest > 0 - ? part.material.alphaTest - : DEFAULT_CUTOUT_ALPHA - : 0; - contract.push(`${part.surface}@${String(cutoff)}`); - } - return `${asset.resolvedGlb}|${this.#transparentScatter}|${contract.join(",")}`; + attachRenderer(renderer: IRendererLike): void { + this.#terrain?.attachRenderer(renderer); } - /** Mark a cached atlas touched, so the least recently used inactive record is the one evicted. */ - #touchImpostor(key: string, record: IImpostorRecord): void { - this.#impostorRecords.delete(key); - this.#impostorRecords.set(key, record); + /** Current per-asset reference count; an asset absent from the map is `0`. */ + assetRefCounts(): Readonly> { + const counts: Record = {}; + for (const [id, asset] of this.#models.entries()) counts[id] = asset.refcount; + return counts; } /** - * Queue one adopted asset's whole-asset impostor, or append it at once from a cached atlas. + * Turn on the GPU-selected tally for the rest of the world's life. * - * Called after `#widenWithChain` and before the prewarm, so the terminal level is known to the same - * prewarm and rebuild machinery every adoption uses. An asset with no alpha-cutout LOD0 part is - * never baked: a solid impostor is not the thing it replaces. + * The engine calls this when the frame budget is on, and a `gpuSceneValidate` turns it on without + * it. Set `gpuSceneTally: true` at load instead when a world is driven outside a frame budget. */ - #maybeQueueImpostor(asset: IAssetState): void { - if (this.#impostors === false || asset.impostor !== undefined) return; - const parts = asset.levels[0]; - if (parts === undefined) return; - const source = this.#impostorSource(parts); - if (source === undefined) return; - const key = this.#impostorKey(asset); - const cached = this.#impostorRecords.get(key); - if (cached !== undefined) { - this.#touchImpostor(key, cached); - this.#finishImpostor(asset, cached); - return; - } - if (this.#impostorQueued.has(asset.id)) return; - this.#impostorQueued.add(asset.id); - this.#impostorQueue.push(asset); + enableGpuSceneTally(): void { + this.#gpuTally = true; } /** - * One view of the bake per render-cadence update, then the terminal level when it completes. + * The GPU's own main-pass selection from the last landed tally, or `undefined` before one lands. * - * The renderer is asked for its raw seam and the bake waits for one that can run it: a world that - * has no renderer yet — or a backend whose raw seam is not the layered-target API — simply does - * not advance, and nothing downstream of the bake is owed until it does. No landmark is already - * waited on, so a bake that never runs never deadlocks a loading screen. + * `triangles` is the GPU-selected count, which `renderer.info.render.triangles` cannot give for an + * indirect draw: three counts the mesh capacity there, an upper bound over what the kernel could + * select. This is what the kernel selected. Main pass only, and `ageFrames` is its staleness. */ - #stepImpostor(renderer: IRendererLike | undefined): void { - if (this.#impostors === false || this.#released) return; - // A renderer that exists but carries no layered bake seam is a real answer, not a delay. Waiting - // on it would leave every queued asset pending and its atlas bytes reserved for the world's whole - // life, so the bake is cancelled, the reservation and queue dropped, and each asset reported: - // the source LODs stay, which is the documented fallback. A world with no renderer yet keeps - // waiting — that is the loading-screen case the wait exists for. - const current = renderer ?? this.#renderer; - if (current !== undefined && impostorRawRenderer(current) === undefined) { - this.#dropUnsupportedImpostors(); - return; - } - if (this.#impostorBaking === undefined) { - while (this.#impostorQueue.length > 0) { - const asset = this.#impostorQueue.shift() as IAssetState; - this.#impostorQueued.delete(asset.id); - if (this.#assets.get(asset.id) !== asset || asset.disposed || asset.impostor !== undefined) - continue; - const parts = asset.levels[0]; - if (parts === undefined || this.#impostorSource(parts) === undefined) continue; - // Re-check the completed cache when DEQUEUED, not only when queued: two entries whose bake - // was queued before either finished share one atlas instead of baking it twice. - const key = this.#impostorKey(asset); - const cached = this.#impostorRecords.get(key); - if (cached !== undefined) { - this.#touchImpostor(key, cached); - this.#finishImpostor(asset, cached); - continue; - } - // Enforce the declared colour+mip budget BEFORE the atlas is allocated, and never at the - // cost of an atlas a live asset draws from: inactive LRU records make room first. - if ( - this.#impostorRecordBytes + this.#impostorReservedBytes + IMPOSTOR_ATLAS_BYTES > - this.#impostorBudgetBytes - ) - this.#evictImpostors(IMPOSTOR_ATLAS_BYTES); - if (this.#impostorRecordBytes + IMPOSTOR_ATLAS_BYTES > this.#impostorBudgetBytes) { - // Out of room: keep the source LODs and say why, rather than overrun the budget with one - // more atlas or evict one still in use. - this.#reportImpostor(asset, "budget"); - // A far-only species fetched for the far half alone has no near run to fall back on, and - // nothing here retries an atlas-budget refusal, so keeping its source GLB alive buys - // nothing. Release it; the near path re-acquires it normally if the follow point reaches it. - if (this.#farTemp.has(asset.id)) this.#releaseFarTemp(asset.id); - continue; - } - const staged: IImpostorPart[] = impostorParts(parts); - try { - this.#baker.begin(asset.id, staged, { pixels: IMPOSTOR_FRAME_PIXELS }); - } catch { - this.#failures += 1; - this.#reportImpostor(asset, "begin-failed"); - continue; - } - this.#impostorReservedBytes = IMPOSTOR_ATLAS_BYTES; - this.#impostorBaking = asset; - break; - } - } - const baking = this.#impostorBaking; - if (baking === undefined) return; - const raw = impostorRawRenderer(renderer); - if (raw === undefined) return; - let atlas: WorldImpostorAtlas | undefined; - try { - atlas = this.#baker.step(raw); - } catch { - this.#failures += 1; - this.#impostorBaking = undefined; - this.#impostorReservedBytes = 0; - this.#reportImpostor(baking, "bake-failed"); - return; - } - if (atlas === undefined) return; - this.#impostorBaking = undefined; - this.#adoptImpostorAtlas(baking, atlas); + gpuSceneTally(): IWorldCellsGpuTally | undefined { + const gpu = this.#render.report(); + if (gpu.gpuTriangles === undefined) return undefined; + return { + ageFrames: gpu.gpuTallyAgeFrames ?? 0, + instances: gpu.gpuInstances ?? 0, + triangles: gpu.gpuTriangles, + }; } - /** - * Drop every queued and in-flight bake because the renderer cannot run the layered capture, and - * report each asset once. The reservation is released with the abort, so a later supported renderer - * (a device swap, a backend that only comes up after a frame) can still bake: the queue is empty, - * but a fresh adoption re-queues, and an asset already refused here is left with its source LODs. - */ - #dropUnsupportedImpostors(): void { - if (this.#impostorQueue.length === 0 && this.#impostorBaking === undefined) return; - const dropped: IAssetState[] = []; - if (this.#impostorBaking !== undefined) { - dropped.push(this.#impostorBaking); - this.#reportImpostor(this.#impostorBaking, "unsupported"); - this.#baker.abort(); - this.#impostorBaking = undefined; - this.#impostorReservedBytes = 0; - } - const queued = this.#impostorQueue.splice(0, this.#impostorQueue.length); - this.#impostorQueued.clear(); - dropped.push(...queued); - for (const asset of queued) this.#reportImpostor(asset, "unsupported"); - // A far-only species cannot bake on a backend that has none, so it must not keep holding the - // source it fetched for a bake that will never land. Its atlas cache key stays for a later backend. - for (const asset of dropped) if (this.#farTemp.has(asset.id)) this.#releaseFarTemp(asset.id); + stats(): IWorldCellsStats { + const gpu = this.#render.report(); + const census = this.#render.gpuCensus(); + return { + admission: { ...this.#admission }, + bundle: this.#render.bundleStats(), + evictions: this.#evictions, + failures: this.#failures, + gpuScene: { + dispatches: gpu.dispatches, + instances: gpu.instances, + keys: gpu.keys, + on: gpu.on, + reason: gpu.reason, + dressed: census.dressed, + meshes: census.meshes, + ...(gpu.gpuTriangles === undefined + ? {} + : { + gpuInstances: gpu.gpuInstances, + gpuTallyAgeFrames: gpu.gpuTallyAgeFrames, + gpuTriangles: gpu.gpuTriangles, + }), + ...(gpu.occlusion === undefined ? {} : { occlusion: gpu.occlusion }), + }, + hlod: this.#chunks.stats(), + impostor: this.#impostorState.impostorStats(), + instances: this.#instances, + loadsInFlight: this.#models.loadsInFlight, + loadsQueued: this.#models.loadsQueued, + mainCull: this.#render.mainCullStats(), + // Minted meshes still owed a first draw, not minted meshes. The two used to be the same + // number, so a loading bar read minting as the whole of the prewarm and then stood still for + // the whole draw — the node builds a bar cannot show. The gate is a promise about batches, so + // the settle zeroes this pair: `0` once `prewarmed` has resolved. + pendingPrewarm: this.#prewarm.queued + this.#prewarm.owed - this.#prewarm.drawn, + prewarmMinted: this.#prewarm.minted, + pressure: { ...this.#pressure }, + // The pass itself, counted whether or not it found anything: `rebuilds` cannot tell a skipped + // pass from one that scanned every resident cell and found the same brackets, and that gap is + // the whole cost of the 2 m step. See `#refilterStale`. + refilters: this.#refilters, + refilterEntries: this.#staleEntries, + rebuilds: this.#rebuilds, + levelRefilters: this.#levelRefilters, + // The share of `rebuilds` the swap found unchanged, which is the honest report of how much of + // the refilter pass is a bracket that was wider than any placement. + unchanged: this.#unchanged, + residentCells: this.#resident.size, + residentBytes: this.#bytes, + loadedCells: [...this.#resident.values()].filter((cell) => this.#cellLoaded(cell)).length, + residentKeys: [...this.#resident.keys()].sort(), + residentTiles: this.#terrain?.residentTileCount ?? 0, + residentColliders: this.#hasColliders ? (this.#terrain?.residentColliderKeys.length ?? 0) : 0, + }; } - /** Store a finished atlas and hand the asset its terminal level. */ #adoptImpostorAtlas( - asset: IAssetState, - atlas: WorldImpostorAtlas, - ): void { - this.#impostorReservedBytes = 0; - const parts = asset.levels[0]; - const source = parts === undefined ? undefined : this.#impostorSource(parts); - if (parts === undefined || source === undefined || asset.impostor !== undefined) { - atlas.dispose(); - return; - } - const key = this.#impostorKey(asset); - const existing = this.#impostorRecords.get(key); - if (existing !== undefined) { - atlas.dispose(); - this.#touchImpostor(key, existing); - this.#finishImpostor(asset, existing); - return; - } - const staged: IImpostorPart[] = impostorParts(parts); - const bounds = impostorBounds(staged); - const record: IImpostorRecord = { - atlas, - bytes: atlas.bytes, - center: bounds.center, - key, - radius: bounds.radius, - users: 0, - }; - this.#impostorRecords.set(key, record); - this.#impostorRecordBytes += record.bytes; - // Acquire the active user BEFORE any eviction, so the fresh record cannot be the eviction - // victim and `#finishImpostor` never builds a surface that borrows a disposed atlas. Admission - // already reserved this record's bytes, so the trim below only drops older inactive records. - this.#finishImpostor(asset, record); - this.#evictImpostors(); + detach(): void { + this.dispose(); } - /** - * Append the whole-asset terminal level: one part, identity local, two triangles, and a switch at - * the projection the 128 px atlas sphere reaches — never a fixed metre, and strictly beyond the - * last authored or chain gate so `levelAt` cannot pick it before the level that gate named. - * - * The surface's own geometry and material are the part's, so the level is a normal level to every - * consumer downstream: the prewarm mints its keys, the swap rebuilds the resident runs, and the - * caster halves read it through `#shapeFor` like any other. The atlas is borrowed and released by - * the record; see `#releaseImpostor`. - */ - #finishImpostor(asset: IAssetState, record: IImpostorRecord): void { - if (asset.impostor !== undefined || asset.disposed) return; - if (this.#assets.get(asset.id) !== asset) return; - const parts = asset.levels[0]; - if (parts === undefined) return; - const source = this.#impostorSource(parts); - if (source === undefined) return; - let surface: WorldImpostorSurface; - try { - surface = new WorldImpostorSurface({ - alphaTest: source.alphaTest, - atlas: record.atlas, - center: record.center, - radius: record.radius, - source: source.source, - }); - } catch { - this.#failures += 1; - this.#reportImpostor(asset, "surface-failed"); - return; - } - // The terminal gate is stored as the BASE-sphere projected distance — the distance at which the - // asset's own LOD0 sphere, at scale 1, covers the 128 px atlas — and the placement's own scale is - // applied per placement at selection (`levelAtGates`). A placement scaled 8-12x therefore reaches - // the atlas 8-12x as far instead of switching at the unit-scale distance. There is no fixed - // margin: `levelAtGates` floors the terminal one representable Float32 step past the last source - // gate, so the source level keeps a window while the ordering survives the f32 store. - const switchAt = (2 * record.radius * this.#autoLodPixelsPerUnit) / IMPOSTOR_FRAME_PIXELS; - if (!Number.isFinite(switchAt) || switchAt <= 0) { - surface.dispose(); - return; + #cellLoaded(cell: IResidentCell): boolean { + return cellLoaded(cell); + } + + /** Completed content intersecting a square around a fixed spawn, in world metres. */ + readinessAt( + position: { readonly x: number; readonly z: number }, + radius = 0, + ): IWorldRegionReadiness { + if ( + !Number.isFinite(position.x) || + !Number.isFinite(position.z) || + !Number.isFinite(radius) || + radius < 0 + ) + throw new Error("WorldCells readiness requires finite coordinates and a nonnegative radius."); + const lowX = Math.floor((position.x - radius - this.#minX) / this.#cellSize); + const highX = Math.floor((position.x + radius - this.#minX) / this.#cellSize); + const lowZ = Math.floor((position.z - radius - this.#minZ) / this.#cellSize); + const highZ = Math.floor((position.z + radius - this.#minZ) / this.#cellSize); + let requiredCells = 0; + let loadedCells = 0; + let failures = 0; + for (const cell of this.#cells) { + if (cell.x < lowX || cell.x > highX || cell.z < lowZ || cell.z > highZ) continue; + requiredCells++; + const resident = this.#resident.get(cellKey(cell.x, cell.z)); + if (resident && this.#cellLoaded(resident)) loadedCells++; + if (resident?.chunkFailed) failures++; + for (const run of cell.runs) + if (this.#models.asset(this.#canonical(run.asset))?.failed) failures++; } - const part: IAssetPart = { - geometry: surface.geometry, - local: new Matrix4(), - material: surface.material, - surface: "", + const terrain = this.#terrain?.readinessAt(position, radius) ?? { required: 0, loaded: 0 }; + return { + requiredCells, + loadedCells, + requiredTerrainTiles: terrain.required, + loadedTerrainTiles: terrain.loaded, + failures, + cancelled: this.#released, + ready: + !this.#released && + failures === 0 && + loadedCells === requiredCells && + terrain.loaded === terrain.required, }; - asset.levels = [...asset.levels, [part]]; - asset.distances = [...asset.distances, switchAt]; - asset.gates = [...asset.gates, switchAt]; - this.#noteGate(switchAt); - asset.threshold = asset.gates.length === 0 ? undefined : Math.min(...asset.gates); - asset.impostor = { part, record, surface }; - record.users += 1; - // The whole-map far aggregate for this atlas key, before the rebuild loop below: its segments - // exist when the swaps land, and any run already near-ready is handed over immediately. A budget - // refusal keeps only the far cohort's metadata and surface in `#farRetry`; see `#buildFar`. - this.#buildFar(asset, record); - this.#reportImpostor(asset, "ready"); - // A far-only asset has no near batches or casters to hand over; once its aggregate is up its - // temporary source has done its job and goes, while the atlas the aggregate borrows stays. A - // refusal releases the source too — the deferred cohort in `#farRetry` holds only metadata and a - // surface, never the GLB — so a far species that cannot fit is not kept alive wholesale. - if (this.#farTemp.has(asset.id)) { - this.#releaseFarTemp(asset.id); - return; - } - // The far casters of the levels that were already built: part 0's key is re-pointed at the one - // whole-asset shape, and the per-part keys the terminal level has no counterpart for are dropped, - // so the far shadow draws one quad per placement rather than a duplicate per bark/needle part. - this.#retargetWideCasters(asset); - if (this.#gpuScene.on) { - // Sources already carry each placement's root transform. Publish the new terminal key and - // gates together; the dispatch selects it without rebuilding resident runs on the CPU. - const level = asset.levels.length - 1; - this.#mintPrewarm({ - asset: asset.id, - geometry: part.geometry, - key: `${asset.id}:${String(level)}:0`, - level, - material: part.material, - part: 0, - role: "main", - }); - this.#publishGpuGates(asset); - this.#queuePrewarm(asset); - return; - } - // CPU fallback still regroups the resident placements into the new terminal level. - this.#queuePrewarm(asset); - for (const cell of [...this.#resident.values()]) { - for (const run of cell.cell.runs) { - if (this.#canonical(run.asset) === asset.id) this.#queueBuild(asset, cell, run, true); - } - } } /** - * The whole-map far aggregate for one exact atlas cache key: every ORIGINAL placement root of every - * canonical asset that bakes to that key, in one `InstancedMesh`, independent of the near ring and - * of the near source GLBs. + * Resolves when the content a fixed spawn needs has finished loading. * - * One aggregate per EXACT atlas key, never one per `maxDistance`: a shared atlas can back assets - * with different authored cutoffs, and each placement's own value rides the instance buffer (see - * {@link IFarSegment.cull}) rather than splitting the key into a second aggregate — the exact key - * is what the atlas cache already bounds, and a split would build a second surface for one atlas. - * The first asset to bake a key builds the aggregate's own surface and instance buffer; a later - * canonical asset that shares the key appends its own runs to the same mesh, growing the buffer at - * most once per asset. A key already carrying every one of this asset's runs is left untouched, so - * re-adopting an asset from the cached atlas after its near source was released rebuilds nothing. - * Never `part.local`: the atlas bakes each part's own transform, so the far quad is the placement - * root alone. + * The one-shot gate a streamed game waits on before it releases its loading screen. Readiness is + * `readinessAt` and nothing else, so there is one authority for "the spawn's data is loaded": every + * required cell and terrain tile in `radius` loaded with no failure. The position is snapshotted + * here, so a caller may keep mutating the object it passed; `update` observes the held waiters + * right after the frame's admissions, so a region an update admits settles its waiter in it. + * + * Fail closed: malformed coordinates, a negative radius or an unsupported timer deadline reject + * before a waiter is armed. A failed asset and a torn-down world reject with their own named + * error, and the deadline rejects with the budget it named. Every settlement removes the waiter + * and clears its timer, so a settled waiter holds neither the deadline nor the world, and a + * region evicted after its waiter resolved changes nothing. */ - #buildFar(asset: IAssetState, record: IImpostorRecord): boolean { - const parts = asset.levels[0]; - const source = parts === undefined ? undefined : this.#impostorSource(parts); - if (parts === undefined || source === undefined) return false; - // Every run this asset owns anywhere in the map: the far half covers the whole map, not the ring. - const runs = this.#farRuns(asset.id); - if (runs.length === 0) return true; - return this.#buildFarCohort({ - alphaTest: source.alphaTest, - cull: cullDistance(asset.definition.maxDistance) ?? Number.POSITIVE_INFINITY, - id: asset.id, - key: record.key, - record, - runs, - source: source.source, + whenSpawnReady( + position: { readonly x: number; readonly z: number }, + radius = 0, + timeoutMs = 120_000, + ): Promise { + if ( + !Number.isFinite(position.x) || + !Number.isFinite(position.z) || + !Number.isFinite(radius) || + radius < 0 + ) + return Promise.reject( + new Error( + "WorldCells spawn readiness requires finite coordinates and a nonnegative radius.", + ), + ); + if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2 ** 31 - 1) + return Promise.reject( + new Error("WorldCells spawn readiness requires a timeout in (0, 2147483647] ms."), + ); + if (this.#released) return Promise.reject(new Error("WorldCells disposed")); + const snapshot = { x: position.x, z: position.z }; + const immediate = this.readinessAt(snapshot, radius); + if (immediate.failures > 0) + return Promise.reject(new Error("WorldCells spawn assets failed to load")); + if (immediate.ready) return Promise.resolve(immediate); + return new Promise((resolve, reject) => { + const waiter: ISpawnWaiter = { + position: snapshot, + radius, + resolve, + reject, + timer: setTimeout(() => { + this.#spawnWaiters.delete(waiter); + reject(new Error(`WorldCells spawn exceeded ${String(timeoutMs)} ms`)); + }, timeoutMs), + }; + this.#spawnWaiters.add(waiter); }); } - /** Every run of one canonical asset anywhere in the map, the whole set a far aggregate holds. */ - #farRuns(id: string): Array<{ cell: IWorldCell; run: IWorldRun }> { - const runs: Array<{ cell: IWorldCell; run: IWorldRun }> = []; - for (const held of this.#cells) - for (const run of held.runs) { - if (this.#canonical(run.asset) !== id || run.count <= 0) continue; - runs.push({ cell: held, run }); + /** Settle the waiters this frame's admissions can answer; a non-Disposed, non-Failed region waits. */ + #settleSpawnWaiters(): void { + if (this.#spawnWaiters.size === 0) return; + for (const waiter of this.#spawnWaiters) { + const readiness = this.readinessAt(waiter.position, waiter.radius); + if (readiness.failures > 0) { + this.#spawnWaiters.delete(waiter); + clearTimeout(waiter.timer); + waiter.reject(new Error("WorldCells spawn assets failed to load")); + } else if (readiness.ready) { + this.#spawnWaiters.delete(waiter); + clearTimeout(waiter.timer); + waiter.resolve(readiness); } - return runs; + } } - /** - * Build or grow one far cohort's aggregate, or defer it against the hard budget. - * - * The budget check counts the growth transient, not the net new count: a grow allocates the new - * larger buffer while the old one is still alive, so the peak is the new buffer's size — the old - * buffer is already charged in `#farBytes`. See {@link #farBudgetRefuses}. A refusal is remembered - * as metadata plus a built surface, never the source GLB; see {@link #deferFar}. - */ - #buildFarCohort(cohort: IFarCohort): boolean { - const key = cohort.key; - const aggregate = this.#far.get(key); - const missing = - aggregate === undefined - ? cohort.runs - : cohort.runs.filter((entry) => !(aggregate as IFarAggregate).segments.has(entry.run)); - if (missing.length === 0) { - this.#dropFarRetry(cohort.id); - return true; - } - const extra = missing.reduce((total, entry) => total + entry.run.count, 0); - const peak = aggregate === undefined ? extra : (aggregate as IFarAggregate).capacity + extra; - if (this.#farBudgetRefuses(peak)) { - // Hard finite budget: say why rather than overrun it, and keep only the metadata the retry - // needs. Never the full source GLB; see `#deferFar`. - this.#reportFarBudget(key, cohort.id, extra); - this.#deferFar(cohort); - return false; + dispose(): void { + if (this.#released) return; + this.#released = true; + // A world torn down mid-wait must not leave a game awaiting its spawn gate, nor a deadline + // outliving the world that armed it. + for (const waiter of this.#spawnWaiters) { + clearTimeout(waiter.timer); + waiter.reject(new Error("WorldCells spawn cancelled")); + } + this.#spawnWaiters.clear(); + this.#prewarm.releaseHooks(); + for (const cell of [...this.#resident.values()]) this.#evict(cell); + this.#render.resetShadows(); + // Every chunk is gone by now, so the surfaces the world owns on their behalf are the last thing + // holding them, and they are released here rather than by whichever chunk contributed them. + this.#chunks.releaseMaterials(); + this.#chunks.renderer = undefined; + this.#chunks.camera = undefined; + this.#render.clearCompileRecords(); + // After every asset's release above, so no record is still held by a surface; disposes the cached + // atlases and aborts any bake still in flight. + this.#impostorState.drainImpostors(); + this.#render.releaseGpu(); + if (this.#render.drainShared() > 0) this.#failures += 1; + // The batches `#drainShared` just retired. Their geometry and material were released by the + // refcount path above, which is why they are disposed and not reused. + this.#render.releaseRetired(); + // A world torn down mid-prewarm must not leave a game awaiting its gate. + this.#prewarm.settleDisposal(); + drainMeshPool(); + this.#terrain?.dispose(); + this.removeFromParent(); + } + + #updateResidency(followCell: { x: number; z: number }, x: number, z: number): void { + const wanted: Array<{ cell: IWorldCell; distance: number }> = []; + for (const cell of this.#cells) { + if (Math.max(Math.abs(cell.x - followCell.x), Math.abs(cell.z - followCell.z)) > this.#ring) + continue; + const centerX = this.#minX + (cell.x + 0.5) * this.#cellSize; + const centerZ = this.#minZ + (cell.z + 0.5) * this.#cellSize; + wanted.push({ cell, distance: Math.hypot(x - centerX, z - centerZ) }); } - let built = aggregate as IFarAggregate | undefined; - const deferred = this.#farRetry.get(cohort.id); - // The surface actually drawn by the aggregate after this call. A new aggregate adopts the - // deferred surface; a grow reuses the aggregate's own and leaves the deferred one unused. - let usedSurface: WorldImpostorSurface | undefined; - if (built === undefined) { - usedSurface = deferred?.surface ?? this.#makeFarSurface(cohort); - if (usedSurface === undefined) return false; - built = { - capacity: extra, - key, - mesh: this.#newFarMesh(usedSurface, extra, cohort.id), - record: cohort.record, - segments: new Map(), - surface: usedSurface, - uploads: 0, - }; - cohort.record.users += 1; - this.#far.set(key, built); - this.#farInstances += extra; - this.#farBytes += extra * FAR_INSTANCE_BYTES; - } else { - this.#growFar(built, extra); + wanted.sort((a, b) => a.distance - b.distance || a.cell.z - b.cell.z || a.cell.x - b.cell.x); + + for (const resident of [...this.#resident.values()]) { + if ( + Math.max(Math.abs(resident.x - followCell.x), Math.abs(resident.z - followCell.z)) > + this.#ring + 1 + ) + this.#evict(resident); } - this.#dropFarRetry(cohort.id, usedSurface); - let at = this.#farAllocated(built); - for (const { cell: held, run } of missing) { - const segment: IFarSegment = { - cell: held, - count: run.count, - cull: cohort.cull, - live: true, - run, - start: at, - }; - at += run.count; - built.segments.set(run, segment); - this.#farSegments.set(run, built); - this.#writeFarSegment(built, segment); + // A follow point that crossed more than one cell in one pass jumped (a teleport, a review + // camera). Only then does the hysteresis ring yield. Read off the follow point itself, not the + // lookahead `followCell`: that leads by up to 1.5 cells, so a walk that stopped and started moved + // it by two, and yielding there evicted and re-admitted a whole row each time. + const here = { + x: Math.floor((this.#follow.position.x - this.#minX) / this.#cellSize), + z: Math.floor((this.#follow.position.z - this.#minZ) / this.#cellSize), + }; + const last = this.#residencyCell; + const jumped = + last !== undefined && Math.max(Math.abs(last.x - here.x), Math.abs(last.z - here.z)) > 1; + this.#residencyCell = here; + + for (const candidate of wanted) { + const key = cellKey(candidate.cell.x, candidate.cell.z); + if (this.#resident.has(key)) continue; + const instances = candidate.cell.runs.reduce((total, run) => total + run.count, 0); + const bytes = instances * PLACEMENT_RECORD_BYTES; + // After a jump, a cell the one-ring hysteresis above kept is not wanted, so it yields its share + // of the budgets to one that is. Without this the budgets stayed full of the ring the camera + // jumped from and the cells around it were refused until it moved again. + while ( + jumped && + (this.#resident.size >= this.#budgets.residentCells || + this.#instances + this.#impostorState.farInstances + instances > + this.#budgets.instances || + this.#bytes + this.#impostorState.farBytes + this.#chunks.bytes + bytes > + this.#budgets.bytes) + ) { + const spare = this.#farthestUnwanted(followCell); + if (spare === undefined) break; + this.#evict(spare); + } + if (this.#resident.size >= this.#budgets.residentCells) { + this.#pressure.cells += 1; + continue; + } + // The retained far aggregates are physical allocations the renderer holds too, so they are + // charged against the same declared limits as the near ring: admitting a cell over the top of + // them would put the world past `budgets.instances`/`budgets.bytes` with the far half invisible + // to this check. See `#farBudgetRefuses`, the far half of the same total. + if ( + this.#instances + this.#impostorState.farInstances + instances > + this.#budgets.instances + ) { + this.#pressure.instances += 1; + continue; + } + if ( + this.#bytes + this.#impostorState.farBytes + this.#chunks.bytes + bytes > + this.#budgets.bytes + ) { + this.#pressure.bytes += 1; + continue; + } + this.#admit(candidate.cell, instances, bytes); } - this.#syncFarSegments(built); - // The far half just gained records, so the shadow levels drawing the ground they stand on are - // stale; the same blanket tell a near swap makes. See `#shadowRecordsMoved`. - this.#shadowRecordsMoved(); - if (!this.#farReported.has(key)) { - this.#farReported.add(key); - const split = this.#farSplit(built); - console.info( - `${WORLD_IMPOSTOR_FAR_MARKER} key=${key} asset=${cohort.id} ` + - `instances=${String(built.capacity)} live=${String(split.live)} ` + - `nearOwned=${String(split.nearOwned)} bytes=${String(built.capacity * FAR_INSTANCE_BYTES)} ` + - `uploads=${String(built.uploads)}`, + } + + /** The resident cell farthest outside the wanted ring, or none when every resident is wanted. */ + #farthestUnwanted(followCell: { x: number; z: number }): IResidentCell | undefined { + let farthest: IResidentCell | undefined; + let reach = this.#ring; + for (const resident of this.#resident.values()) { + const away = Math.max( + Math.abs(resident.x - followCell.x), + Math.abs(resident.z - followCell.z), ); + if (away > reach) { + reach = away; + farthest = resident; + } } - return true; + return farthest; } - /** The aggregate's own far surface: the record's atlas, the cohort's material twin, per-instance cull. */ - #makeFarSurface(cohort: IFarCohort): WorldImpostorSurface | undefined { - if (cohort.source === undefined) return undefined; - try { - return new WorldImpostorSurface({ - alphaTest: cohort.alphaTest, - atlas: cohort.record.atlas, - center: cohort.record.center, - cull: true, - radius: cohort.record.radius, - source: cohort.source, - }); - } catch { - this.#failures += 1; - return undefined; - } + #admit(cell: IWorldCell, instances: number, bytes: number): void { + const state: IResidentCell = { + mainChunkMeshes: [], + chunkInstances: new Map(), + completedRuns: new Set(), + chunksLoaded: (cell.chunks?.length ?? 0) === 0, + chunkFailed: false, + batches: [], + bytes, + cell, + chunks: [], + filterFar: Number.NEGATIVE_INFINITY, + filterNear: Number.POSITIVE_INFINITY, + generation: this.#generation++, + instances, + key: cellKey(cell.x, cell.z), + retainedModelPaths: [], + x: cell.x, + z: cell.z, + }; + this.#resident.set(state.key, state); + this.#render.cullResident(cell); + this.#residencyEpoch += 1; + this.#instances += instances; + this.#bytes += bytes; + for (const run of cell.runs) this.#models.acquire(run, state); + if (cell.chunks !== undefined && cell.chunks.length > 0) this.#chunks.startChunkLoad(state); } /** - * Keep a refused far cohort as metadata alone: the atlas pinned by one borrowed user, the runs, and - * a built surface. The asset's own geometry and materials are released by the caller, so a refused - * far species never holds a full GLB alive; `#retryFar` rebuilds from this when a near release frees - * room. Idempotent: a repeat refusal keeps the surface already built. + * The asset id a run's `asset` really is: itself, or the one canonical id its model is loaded + * under. Every path from a run to the state that holds it goes through here, so a duplicate costs + * one `IAssetState`, one model load, one set of `canonical:level:part` keys and one refcount — + * released when the last run naming any member of the group leaves the ring. A placement keeps its + * own transform, because those are read from the run's own records. */ - #deferFar(cohort: IFarCohort): void { - if (this.#farRetry.has(cohort.id)) return; - const surface = this.#makeFarSurface(cohort); - if (surface === undefined) return; - cohort.record.users += 1; - this.#farRetry.set(cohort.id, { - alphaTest: cohort.alphaTest, - cull: cohort.cull, - id: cohort.id, - key: cohort.key, - record: cohort.record, - runs: cohort.runs, - surface, - }); - } - - /** Drop a deferred cohort, releasing its surface and the atlas user it pinned. */ - #dropFarRetry(id: string, consumedSurface?: WorldImpostorSurface): void { - const entry = this.#farRetry.get(id); - if (entry === undefined) return; - this.#farRetry.delete(id); - if (consumedSurface !== entry.surface) entry.surface.dispose(); - entry.record.users = Math.max(0, entry.record.users - 1); - this.#evictImpostors(); + #canonical(id: string): string { + return this.#aliases.get(id) ?? id; } - /** Retry the far aggregates a full budget refused, when a near cell freed its allocation. */ - #retryFar(): void { - if (this.#farRetry.size === 0) return; - for (const entry of [...this.#farRetry.values()]) this.#buildFarCohort(entry); + /** + * Ask for one cell-asset run to be built, and do not build it yet. + * + * Admission is the only thing in this class that has no frame-time ceiling, so it is the only + * thing that became a queue: the cell keeps its residency slot from `#admit`, the work waits for + * a frame with budget in it, and a game that outruns the backlog sees it as `admission.backlog` + * rather than as a frozen frame. A run already queued is not queued twice, which is what stops a + * refilter — stale for as many frames as it waits — from stacking a second copy of itself. + * + * `replaced` is a refilter's outgoing batches, captured now and kept drawn until the replacement + * is attached, so a rebuild that waits three frames shows the old level for those three frames + * instead of a hole. + */ + #queueBuild( + asset: IAssetState, + cell: IResidentCell, + run: IWorldRun, + replace = false, + force = false, + ): void { + // Keyed on the run, not the asset: one cell can hold two runs of the same canonical asset — + // two names for one model — and each has its own records to build. A token per asset made the + // second run a no-op and dropped its placements on the floor. + if (this.#queued.has(run)) return; + // A cell that already draws this run is not built again, which is where an adopted asset's + // second acquire over a still-resident cell lands. + if (!replace && cell.batches.some((entry) => entry.asset === asset.id && entry.run === run)) + return; + this.#queued.add(run); + this.#jobs.push({ + asset, + batches: undefined, + boxes: undefined, + cell, + force, + gpuByLevel: undefined, + filterY: this.#followY(), + filterX: this.#follow.position.x, + filterZ: this.#follow.position.z, + fresh: [], + replacement: replace, + next: 0, + published: 0, + records: undefined, + replaced: replace + ? cell.batches.filter((entry) => entry.asset === asset.id && entry.run === run) + : [], + roots: undefined, + run, + sources: undefined, + }); } /** - * Adopt one far-only species per idle frame: a canonical asset the map places but no resident run - * has asked for yet. Its LOD0 is loaded through the same limiter, classified by its actual - * materials, baked on the same queue, and its source released once the aggregate lands. Gated on an - * empty near queue so loading the unseen never delays the near ring or holds its overlay open. + * Spend the frame's budget on queued builds, one unit each, and stop when it is gone. + * + * The queue is served in admission order, which is nearest cell first, and a build is taken to + * completion before the next one starts: the units of one run are cheaper together than the + * per-frame bookkeeping of interleaving them, and the cell behind it is the same distance away. + * A build whose cell or asset has gone is dropped rather than resumed — there is nothing left to + * draw it into — and a frame that runs out of budget leaves the rest of the queue for the next. */ - #pumpFarAcquisition(): void { - if (this.#impostors === false || this.#released || this.#farExhausted) return; - if (this.#farCandidateIds.length === 0) return; - // Near work first: a queued build, an in-flight near load, a bake in flight or waiting. Only a - // settled frame spends itself on the unseen far half. - if ( - this.#jobs.length > 0 || - this.#limiter.inFlight > 0 || - this.#limiter.queued > 0 || - this.#prewarmQueue.length > 0 || - this.#impostorQueue.length > 0 || - this.#impostorBaking !== undefined - ) - return; - // The bake needs a renderer that can run it; acquiring before one exists would hold source GLBs - // for a bake with no way to land. A renderer that is present but unsupported is the same answer. - const renderer = this.#renderer; - if (renderer === undefined || impostorRawRenderer(renderer) === undefined) return; - const count = this.#farCandidateIds.length; - for (let step = 0; step < count; step += 1) { - const at = (this.#farCursor + step) % count; - const id = this.#farCandidateIds[at] as string; - if (this.#farSeen.has(id) || this.#assets.has(id) || this.#farRetry.has(id)) continue; - this.#farCursor = (at + 1) % count; - const definition = this.#manifest.assets[id]; - if (definition === undefined) { - this.#farSeen.add(id); + #drain(budget: IAdmissionBudget): void { + const index = 0; + while (index < this.#jobs.length) { + const job = this.#jobs[index] as IBuildJob; + if (this.#resident.get(job.cell.key) !== job.cell || job.asset.levels.length === 0) { + this.#forget(index, job); continue; } - const asset = this.#newAsset(id, definition); - asset.refcount += 1; - this.#farTemp.add(id); - this.#startAssetLoad(asset); - return; + let finished = false; + if ( + !budget.admit(() => { + finished = this.#step(job); + }) + ) + break; + // Out of fresh meshes this frame: later jobs would only stall the same way. + if (this.#render.meshStalled) break; + // A finished build leaves the queue at this index, so the next one is served without a skip. + if (finished) { + job.cell.completedRuns.add(job.run); + this.#forget(index, job); + // A camera that moved during this bounded build may already have consumed the global + // refilter step before these batches existed. Owe one pass after publication, even if + // it now stands still, so the coherent old snapshot can converge to the latest pose. + if ( + Math.hypot( + this.#follow.position.x - job.filterX, + this.#followY() - job.filterY, + this.#follow.position.z - job.filterZ, + ) >= this.#refilterStepMetres() + ) + this.#refilterOwed = true; + } } - // A full pass with nothing startable means every species is either baked, held for a retry, or - // classified as having no atlas. Static placements add none later, so the scan stops here. - this.#farExhausted = true; - } - - /** - * Drop a far-only asset's temporary reference once its atlas is held by an aggregate or has been - * deferred. The deferred cohort keeps its own pinned atlas user, so it is not dropped here. - */ - #releaseFarTemp(id: string): void { - this.#farTemp.delete(id); - this.#farSeen.add(id); - this.#release(id); } - /** - * Whether an incoming far allocation would overrun the world's instance or byte budget. - * - * `count` is the peak, not the net: a grow allocates the new larger buffer while the old one is - * still alive, so the caller passes the new buffer's full size — the old buffer is already inside - * `#farInstances`/`#farBytes`. A net-new count would understate the transient a resize holds. - */ - #farBudgetRefuses(count: number): boolean { - return ( - this.#instances + this.#farInstances + count > this.#budgets.instances || - this.#bytes + this.#farBytes + this.#hlodBytes + count * FAR_INSTANCE_BYTES > - this.#budgets.bytes - ); + #forget(index: number, job: IBuildJob): void { + this.#jobs.splice(index, 1); + this.#queued.delete(job.run); } - /** One bounded line for a far aggregate the world's own budgets refused, once per atlas key. */ - #reportFarBudget(key: string, assetId: string, count: number): void { - if (this.#farReported.has(key)) return; - this.#farReported.add(key); - console.info( - `${WORLD_IMPOSTOR_FAR_MARKER} key=${key} asset=${assetId} ` + - `instances=${String(count)} reason=budget`, - ); + /** Units of work the queue still owes: placement slices left, plus meshes left to publish. */ + #backlog(): number { + let units = 0; + for (const job of this.#jobs) { + const meshes = job.batches === undefined ? 0 : publishCount(job.batches); + units += + Math.ceil(Math.max(0, job.run.count - job.next) / ADMISSION_SLICE_PLACEMENTS) + + Math.max(0, meshes - job.published); + } + return units; } /** - * One far mesh: the whole-asset quad, every original root, drawn regardless of how far the camera - * resolves it. `frustumCulled = false` is what keeps the draw: the projected-size gate exempts it - * before it ever reads bounds, so no `alwaysRender` marker is needed when a game configured no - * shadows to exempt it under `renderer.minimumProjectedPixels`. The per-placement cull rides the - * geometry's instance attribute, one static value per placement; see - * {@link IMPOSTOR_FAR_CULL_ATTRIBUTE}. + * One unit of one build: a slice of placements, or one mesh built, or the swap that attaches the + * finished batch and retires what a refilter replaced. `true` means the job is done. */ - #newFarMesh(surface: WorldImpostorSurface, capacity: number, assetId: string): InstancedMesh { - const mesh = new InstancedMesh(surface.geometry, surface.material, capacity); - mesh.count = capacity; - mesh.frustumCulled = false; - // Three's default usage uploads by version; grow/write/zero announce every matrix change through - // needsUpdate. DynamicDrawUsage would upload the same whole-map buffer on every settled draw. - mesh.name = `tn-far:${assetId}`; - // Drawn on layer 0, so this is a main-pass draw the frame budget counts under the impostor - // aggregate's own source rather than under `other` (`RenderPassBudget`). - mesh.userData.tnDrawSource = "proxies"; - if (surface.cull) this.#attachFarCull(surface.geometry, capacity); - // Far trees cast like near ones, into the coarse wide caster layer. The same mesh draws the main - // pass (layer 0) and the virtual-shadow levels (wide layer), so one representation covers both - // and a run hands its far segment over to the near ring exactly once; see `#disableFar`. The - // per-asset maxDistance gate is a main-pass shader gate, so the shadow half still reaches the map. - if (this.#castShadowLevels > 0) { - mesh.layers.enable(VIRTUAL_SHADOW_WIDE_CASTER_LAYER); - mesh.castShadow = true; + #step(job: IBuildJob): boolean { + // A queued run may wait behind admission or fresh-mesh work while the camera moves. + // Take its filter snapshot when the first placement slice actually starts, then keep + // that snapshot for every slice so one publication never mixes different camera poses. + if (job.next === 0 && job.batches === undefined) { + job.filterX = this.#follow.position.x; + job.filterY = this.#followY(); + job.filterZ = this.#follow.position.z; } - // Receives like the near ring: a far tree that never receives is lit full-bright beside near - // trees shaded by their neighbours, a lighter band of canopy where the impostors begin. - mesh.receiveShadow = this.#receiveShadow; - this.add(mesh); - return mesh; - } - - /** Install a fresh per-instance cull buffer on the aggregate's shared geometry, all gates open. */ - #attachFarCull(geometry: BufferGeometry, capacity: number): void { - geometry.setAttribute( - IMPOSTOR_FAR_CULL_ATTRIBUTE, - new InstancedBufferAttribute(new Float32Array(capacity).fill(Number.POSITIVE_INFINITY), 1), - ); - } - - /** Append `extra` instance slots to an aggregate, one mesh swap, charged to the budgets. */ - #growFar(aggregate: IFarAggregate, extra: number): void { - const old = aggregate.mesh; - const capacity = aggregate.capacity + extra; - // Capture the cull values before the replacement mesh attaches a fresh buffer to the SHARED - // geometry; the old attribute object is replaced in place there. - const oldCull = aggregate.surface.cull - ? ( - aggregate.surface.geometry.getAttribute(IMPOSTOR_FAR_CULL_ATTRIBUTE)?.array as - | Float32Array - | undefined - )?.slice(0, aggregate.capacity) - : undefined; - const next = this.#newFarMesh(aggregate.surface, capacity, old.name.replace(/^tn-far:/u, "")); - (next.instanceMatrix.array as Float32Array).set( - (old.instanceMatrix.array as Float32Array).subarray(0, aggregate.capacity * 16), - ); - next.instanceMatrix.needsUpdate = true; - if (oldCull !== undefined) { - const attribute = next.geometry.getAttribute(IMPOSTOR_FAR_CULL_ATTRIBUTE) as - | InstancedBufferAttribute - | undefined; - if (attribute !== undefined) (attribute.array as Float32Array).set(oldCull, 0); + if (job.next < job.run.count) { + job.batches ??= newBatches(job.asset); + // One box per `(level, part)` beside the batches it measures, so the swap that hands the + // records to the renderer can name where they are; see `#addPlacements`. + job.boxes ??= newBoxes(job.batches); + const end = Math.min(job.next + ADMISSION_SLICE_PLACEMENTS, job.run.count); + this.#addPlacements(job, job.next, end); + job.next = end; + return false; } - this.remove(old); - old.dispose(); - aggregate.mesh = next; - aggregate.capacity = capacity; - aggregate.uploads += 1; - this.#farInstances += extra; - this.#farBytes += extra * FAR_INSTANCE_BYTES; - } - - /** Slots allocated in an aggregate: the sum of its segment counts, the offset the next one takes. */ - #farAllocated(aggregate: IFarAggregate): number { - let at = 0; - for (const segment of aggregate.segments.values()) at += segment.count; - return at; + const batches = job.batches; + // A run with nothing placed in it publishes nothing, exactly as an unbounded build would. + if (batches === undefined) return true; + if (job.published < publishCount(batches)) { + this.#buildOne(job, batches); + return false; + } + return this.#render.swap(job); } - /** Compose one run's ORIGINAL placement roots into its segment, exactly as `#addPlacements` does. */ - #writeFarSegment(aggregate: IFarAggregate, segment: IFarSegment): void { - const records = cellPlacements(this.#placements, segment.run); - const array = aggregate.mesh.instanceMatrix.array as Float32Array; - const base = segment.start * 16; - for (let index = 0; index < segment.count; index += 1) { - const at = index * PLACEMENT_RECORD_FLOATS; - this.#position.set( - records[at] as number, - records[at + 1] as number, - records[at + 2] as number, + /** + * Filter one slice of a run's placements into the batches they draw from, applying the CPU + * maxDistance/reach filters at the build position and switching level where an asset's `lods` say + * to. GPU admission retains maxDistance candidates for the dispatch; placementReach stays here + * because it is an explicit per-record source boundary. + */ + #addPlacements(job: IBuildJob, from: number, to: number): void { + const { asset, filterX, filterZ } = job; + if (job.replacement && job.next === 0) this.#levelRefilters += 1; + if (job.records === undefined) job.records = cellPlacements(this.#placements, job.run); + const records = job.records; + const inner = cullDistance(asset.definition.maxDistance); + // Half the diagonal of the asset's authored bounds: the sphere the dispatch culls this placement + // with, and the one number about its extent that comes from the package rather than from here. + const authored = asset.definition.bounds; + const boundsRadius = + 0.5 * + Math.hypot( + (authored.max[0] as number) - (authored.min[0] as number), + (authored.max[1] as number) - (authored.min[1] as number), + (authored.max[2] as number) - (authored.min[2] as number), ); + // Decided once per slice, not per placement: the wide half needs the placement root only for an + // asset that can append a whole-asset impostor. See `ICellBatch.wideRoot`. + const wantRoots = this.#impostorState.rootsNeeded(asset); + for (let index = from; index < to; index += 1) { + const base = index * PLACEMENT_RECORD_FLOATS; + const x = records[base] as number; + const y = records[base + 1] as number; + const z = records[base + 2] as number; + const distance = + this.#placementReach === undefined + ? Math.hypot(x - filterX, z - filterZ) + : Math.hypot(x - filterX, y - job.filterY, z - filterZ); + if (this.#render.gpuOn === false && inner !== undefined && distance > inner) continue; + const reach = this.#placementReach?.[job.run.offset + index]; + if (reach !== undefined && distance > reach) continue; + this.#position.set(x, y, z); this.#rotation.set( - records[at + 3] as number, - records[at + 4] as number, - records[at + 5] as number, - records[at + 6] as number, + records[base + 3] as number, + records[base + 4] as number, + records[base + 5] as number, + records[base + 6] as number, ); - this.#scale.setScalar(records[at + 7] as number); + this.#scale.setScalar(records[base + 7] as number); this.#matrix.compose(this.#position, this.#rotation, this.#scale); - this.#matrix.toArray(array, base + index * 16); - } - aggregate.mesh.instanceMatrix.needsUpdate = true; - // The per-placement gate is static, so it is written with the matrices and re-uploaded only when - // a handoff rewrites this segment. A whole-map surface reads it so every canonical asset keeps - // its own authored cutoff over the shared atlas. See {@link IFarSegment.cull}. - const cull = aggregate.mesh.geometry.getAttribute(IMPOSTOR_FAR_CULL_ATTRIBUTE) as - | InstancedBufferAttribute - | undefined; - if (cull !== undefined) { - (cull.array as Float32Array).fill(segment.cull, segment.start, segment.start + segment.count); - cull.needsUpdate = true; + const scale = this.#scale.x; + // The placement transform, then the part's own offset inside the model: a bark primitive at + // the trunk and a needles primitive higher up both land in the one instance matrix. + // The level is picked by the shared gate rule: authored world-metre gates directly, and a + // whole-asset impostor's terminal gate scaled by this placement's own scale. The bias is the + // same adaptive multiplier the dispatch carries, so a biased walk crosses a switch here too. + let level = levelAtGates( + { distances: asset.distances, impostor: asset.impostor !== undefined }, + biasedLodDistance(distance), + scale, + ); + if (this.#placementLevels !== undefined && asset.definition.lods !== undefined) { + const record = job.run.offset + index; + let previous = this.#placementLevels[record] as number; + // Outward gates remain authored; returning inward retains the previous level until the margin. + if (level > previous) previous = level; + while ( + previous > level && + biasedLodDistance(distance) < + (asset.distances[previous] as number) * (1 - this.#lodHysteresis) + ) + previous--; + level = previous; + this.#placementLevels[record] = level; + } + // The record's own world bounds: the placement widened by the asset's authored bounds at the + // scale it is drawn at. Read here because this loop already holds the placement and the scale, + // and it is the one number the shadow levels' invalidation is tested against — the cell box is + // 64 m of it, so a level whose window is 48 m wide was redrawing over ground nothing in it. + const bounds = asset.definition.bounds; + // The far shadow of an asset that can bake a whole-asset impostor draws one quad per placement + // at the placement root: the parts' own offsets are baked into the atlas, so composing a source + // part's offset again would apply it twice. One root record per placement, beside the per-part + // batches and read only by the wide half; see `ICellBatch.wideRoot`. + if (wantRoots) { + job.roots ??= []; + const roots = job.roots; + let root = roots[level]; + if (root === undefined) { + const anchor = asset.levels[level]?.[0]; + if (anchor !== undefined) { + root = new InstancedBatch({ geometry: anchor.geometry, material: anchor.material }); + roots[level] = root; + } + } + root?.add(this.#matrix); + } + // The GPU scene's source record for this placement: the placement's own transform, the level it + // reached, and a sphere the dispatch tests against the camera's planes. The part offset is the + // key's own, so one record serves every part of the level — see `#placeSources`. + if (this.#render.gpuOn) { + const list = job.sources ?? []; + job.sources = list; + // The sphere is the asset's authored bounds *as placed*, centred where the bounds' centre + // lands under the placement's own transform: a sphere at the placement instead is not a + // bound of the placement, and every prop whose model does not straddle the origin lost the + // half of itself that reaches away from it — a post, a stump, a fern, all of them culled at + // the edge of the view with their bases outside it. The CPU path's own gate is the cell's box + // over the same widened bounds, and it is conservative in exactly the way this is now. The + // centre is the matrix applied to the authored centre, so the placement's quaternion rotates + // it: composing the offsets by hand dropped the rotation and put a turned off-centre prop's + // sphere where the unturned one would be. The radius is rotation-invariant and takes the + // scale alone, which is uniform. + const centre = this.#sourceCentre + .set( + ((bounds.min[0] as number) + (bounds.max[0] as number)) * 0.5, + ((bounds.min[1] as number) + (bounds.max[1] as number)) * 0.5, + ((bounds.min[2] as number) + (bounds.max[2] as number)) * 0.5, + ) + .applyMatrix4(this.#matrix); + list.push({ + level, + matrix: this.#matrix.clone(), + // `boundsRadius` is already the asset's authored bounds radius (half the diagonal), so the + // placement's sphere radius is that times its own uniform scale — halving it again put the + // sphere inside the prop and culled the half of it that reaches away from the placement. + radius: boundsRadius * Math.abs(scale), + // The magnitude, finite-sanitised: the dispatch's impostor gate takes `|scale|` exactly as + // `levelAtGates` does, so a mirrored placement reaches the atlas the same way. + scale: Number.isFinite(scale) ? Math.abs(scale) : 1, + x: centre.x, + y: centre.y, + z: centre.z, + }); + } + // Authored bounds include model-part offsets; apply the whole placement transform so + // rotated, mirrored and off-centre casters cannot fall outside visibility/shadow regions. + this.#boundsAt(bounds, this.#matrix); + const parts = asset.levels[level] as readonly IAssetPart[]; + const levelBatches = job.batches?.[level] as InstancedBatch[]; + const levelBoxes = job.boxes?.[level] as Box3[] | undefined; + for (const [part, entry] of parts.entries()) { + this.#instance.multiplyMatrices(this.#matrix, entry.local); + (levelBatches[part] as InstancedBatch).add(this.#instance); + const box = levelBoxes?.[part]; + if (box === undefined) continue; + box.union(this.#recordBounds); + } } - aggregate.uploads += 1; - this.#farBoundsStale(aggregate); - } - - /** - * Zero one segment's records: the run is drawn by the near ring now, not the far mesh. Each record - * keeps a homogeneous `w` of 1 while its xyz/scale terms collapse to 0, so the instance projects - * to the origin and draws nothing. An all-zero record would leave `w = 0`, and Three's - * `Box3.applyMatrix4` divides by it, so `computeBoundingBox` over the shared buffer would return - * NaN and every caster in the aggregate would be dropped by the shadow levels. - */ - #zeroFarSegment(aggregate: IFarAggregate, segment: IFarSegment): void { - const array = aggregate.mesh.instanceMatrix.array as Float32Array; - const from = segment.start * 16; - const to = (segment.start + segment.count) * 16; - array.fill(0, from, to); - for (let at = from; at < to; at += 16) array[at + 15] = 1; - aggregate.mesh.instanceMatrix.needsUpdate = true; - aggregate.uploads += 1; - this.#farBoundsStale(aggregate); - } - - /** - * A far matrix write changes the mesh's instance extent, so drop the cached pair and let Three - * rebuild it on the next read; this is the native `null` convention `computeBoundingBox` honors. - */ - #farBoundsStale(aggregate: IFarAggregate): void { - aggregate.mesh.boundingBox = null; - aggregate.mesh.boundingSphere = null; - } - - /** The near ring has attached this run's full representation; the far mesh stops drawing it. */ - #disableFar(run: IWorldRun): void { - const aggregate = this.#farSegments.get(run); - if (aggregate === undefined) return; - const segment = aggregate.segments.get(run); - if (segment === undefined || segment.live === false) return; - this.#zeroFarSegment(aggregate, segment); - segment.live = false; - } - - /** This run is leaving the ring; the far mesh draws it again before the near batches are cleared. */ - #restoreFar(run: IWorldRun): void { - const aggregate = this.#farSegments.get(run); - if (aggregate === undefined) return; - const segment = aggregate.segments.get(run); - if (segment === undefined || segment.live === true) return; - this.#writeFarSegment(aggregate, segment); - segment.live = true; } /** - * Hand over every run of an aggregate a resident cell already draws. A run is near-ready exactly - * when the cell holds one of its batches; the whole job is attached in one swap, so presence is the - * answer. This is the build-time half of the atomic handoff; `#swap` is the per-frame half. + * Build one (level, part) batch into a mesh. One mesh is one unit, because this is where every + * instance matrix is written and the batch's bounds are first computed — the part of admission the + * renderer pays for again on the frame it first draws the result. The mesh joins the cell in + * {@link #swap}, with the rest of the job, so it is never half a replacement. + * + * ponytail: a hard switch, no crossfade — a placement crosses a level boundary by being rebuilt + * into the other level's batch, so the swap pops. A blend needs a per-instance mix the + * InstancedBatch has no slot for; add one when a game's swap is visible enough to pay for it. */ - #syncFarSegments(aggregate: IFarAggregate): void { - for (const segment of aggregate.segments.values()) { - if (segment.live === false) continue; - const resident = this.#resident.get(cellKey(segment.cell.x, segment.cell.z)); - if (resident === undefined) continue; - if (!resident.batches.some((entry) => entry.run === segment.run)) continue; - this.#zeroFarSegment(aggregate, segment); - segment.live = false; - } - } - - /** An aggregate's live and near-owned instance counts. */ - #farSplit(aggregate: IFarAggregate): { live: number; nearOwned: number } { - let live = 0; - let nearOwned = 0; - for (const segment of aggregate.segments.values()) { - if (segment.live) live += segment.count; - else nearOwned += segment.count; - } - return { live, nearOwned }; - } - - /** The far block {@link stats} reports: aggregates, physical instances/bytes and their split. */ - #farStats(): IWorldCellsStats["impostor"]["far"] { - let instances = 0; - let live = 0; - let nearOwned = 0; - let uploads = 0; - for (const aggregate of this.#far.values()) { - instances += aggregate.capacity; - uploads += aggregate.uploads; - const split = this.#farSplit(aggregate); - live += split.live; - nearOwned += split.nearOwned; - } - return { - aggregates: this.#far.size, - bytes: this.#farBytes, - instances, - live, - nearOwned, - uploads, - }; - } - - /** Dispose every far aggregate: its own mesh and surface, and its borrowed atlas user. */ - #drainFar(): void { - for (const aggregate of this.#far.values()) { - aggregate.mesh.removeFromParent(); - aggregate.mesh.dispose(); - aggregate.surface.dispose(); - aggregate.record.users = Math.max(0, aggregate.record.users - 1); - } - this.#far.clear(); - this.#farSegments.clear(); - this.#farInstances = 0; - this.#farBytes = 0; - } - /** - * Move this asset's already-built wide casters into the one whole-asset mesh, with root records. - * - * Before the bake landed the far shadow held one wide mesh per (level, part 0), each carrying - * `placement * part 0 local` for its own level's placements. The whole-asset representation is one - * root draw per placement, so every one of those segments is dropped and re-claimed from the root - * records the builds kept (`ICellBatch.wideRoot`) in the single mesh `#wideFor` now names. When a - * block cannot be taken in this update's fresh-mesh allowance the asset is retried next frame; the - * old per-level meshes, empty by then, are retired so the far shadow stops submitting them. + * Fold one batch's own filter point into the cell's bracket, which only ever widens. See + * `IResidentCell.filterNear` and `#staleIn`. */ - #retargetWideCasters(asset: IAssetState): void { - const impostor = asset.impostor; - if (impostor === undefined) return; - const globalKey = `${asset.id}:*@${WIDE_CLUSTER}`; - for (const cell of this.#resident.values()) - for (const entry of cell.batches) { - if (entry.asset !== asset.id) continue; - if (entry.wide !== undefined && entry.wide.mesh.name === globalKey) continue; - // The old segment's shape and its doubled part offset are both wrong now; hand it back. Its - // placed count goes with the records, so the mesh left behind does not read as fully placed - // and the global one does not read as unplaced: see `SharedBatch#gpuPlaced`. - const placed = entry.gpu?.length ?? 0; - if (entry.wide !== undefined && entry.wideSegment >= 0) { - entry.wide.gpuPlaced -= placed; - entry.wide.clear(entry.wideSegment); - } - entry.wide = undefined; - entry.wideSegment = -1; - if (!this.#casts(entry.level) || !this.#wideOwed(asset.id, entry.part)) continue; - const source = entry.wideRoot; - if (source === undefined) continue; - const wide = this.#batchFor(asset.id, entry, globalKey, "wide", false); - const at = wide === undefined ? undefined : this.#segmentIn(wide, source.count); - if (wide === undefined || at === undefined) { - this.#wideRetry.add(asset.id); - continue; - } - entry.wide = wide; - entry.wideSegment = at; - wide.gpuPlaced += placed; - wide.write(at, source); - } - this.#retireWideKeys(asset.id, globalKey); - } - - /** Give the asset's pre-bake per-key wide meshes back, once the handoff has emptied them. */ - #retireWideKeys(assetId: string, keep: string): void { - const prefix = `${assetId}:`; - for (const [key, shared] of [...this.#shared]) { - if (key === keep || shared.role !== "wide" || !key.startsWith(prefix)) continue; - if (shared.live > 0) continue; - shared.mesh.removeFromParent(); - this.#bundleOut(shared); - this.#shared.delete(key); - this.#retire(key, shared); - } - } - - /** Retry the handoffs a frame's fresh-mesh allowance deferred; one bounded pass per update. */ - #retryWide(): void { - if (this.#wideRetry.size === 0) return; - const ids = [...this.#wideRetry]; - this.#wideRetry.clear(); - for (const id of ids) { - const asset = this.#assets.get(id); - if (asset === undefined || asset.impostor === undefined) continue; - this.#retargetWideCasters(asset); - } - } - - /** The impostor block {@link stats} reports: assets landed, bakes pending, atlas bytes, terminal tris. */ - #impostorStats(): IWorldCellsStats["impostor"] { - let assets = 0; - let terminalTriangles = 0; - for (const asset of this.#assets.values()) { - if (asset.impostor === undefined) continue; - assets += 1; - terminalTriangles += levelTriangles(asset.impostor.part.geometry); - } - return { - assets, - atlasBytes: this.#impostorRecordBytes, - budgetBytes: this.#impostorBudgetBytes, - far: this.#farStats(), - pending: this.#impostorQueue.length + (this.#impostorBaking === undefined ? 0 : 1), - terminalTriangles, - }; + #widenFilterRange(cell: IResidentCell, filterX: number, filterZ: number): void { + const [near, far] = this.#span(cell, filterX, filterZ); + if (near < cell.filterNear) cell.filterNear = near; + if (far > cell.filterFar) cell.filterFar = far; } - /** Drop queued/baking work for an asset whose source geometry is about to be released. */ - #cancelImpostor(asset: IAssetState): void { - this.#impostorQueued.delete(asset.id); - const at = this.#impostorQueue.indexOf(asset); - if (at >= 0) this.#impostorQueue.splice(at, 1); - if (this.#impostorBaking === asset) { - // Before the borrowed source geometry is released: the baker stages it, and a view drawn after - // the teardown reads a disposed buffer. - this.#baker.abort(); - this.#impostorBaking = undefined; - this.#impostorReservedBytes = 0; + #buildOne(job: IBuildJob, batches: readonly InstancedBatch[][]): void { + const { asset } = job; + const { cell } = job; + this.#widenFilterRange(cell, job.filterX, job.filterZ); + let part = job.published; + for (const [level, levelBatches] of batches.entries()) { + if (part >= levelBatches.length) { + part -= levelBatches.length; + continue; + } + const box = job.boxes?.[level]?.[part]; + job.fresh.push({ + asset: asset.id, + batch: levelBatches[part] as InstancedBatch, + caster: undefined, + casterSegment: -1, + gpu: undefined, + wide: undefined, + wideSegment: -1, + wideRoot: job.roots?.[level], + lastFilterY: job.filterY, + lastFilterX: job.filterX, + lastFilterZ: job.filterZ, + level, + part, + run: job.run, + segment: -1, + shared: undefined, + // Where these records are, for the shadow invalidation this entry's swap hands over. + shadowBounds: box === undefined || box.isEmpty() ? undefined : box, + threshold: this.#reachRanges.get(cell.cell)?.has(asset.id) + ? Math.min(asset.threshold ?? Number.POSITIVE_INFINITY, 6) + : asset.threshold, + }); + job.published += 1; + return; } } - /** Release an asset's terminal level and give its atlas back to the cache; see `#release`. */ - #releaseImpostor(asset: IAssetState): void { - const impostor = asset.impostor; - if (impostor === undefined) return; - asset.impostor = undefined; - // Marked first, so `#releaseParts` skips the part whose geometry and material the surface owns. - releasedParts.add(impostor.part); - dropPooledFor(impostor.part.geometry, impostor.part.material); - impostor.surface.dispose(); - impostor.record.users = Math.max(0, impostor.record.users - 1); - this.#evictImpostors(); - } - /** - * Drop the oldest inactive atlases until the cache is back under budget; active ones are kept. + * Where a dressed main batch's mesh belongs: its key's main record shard, or the world itself when there is + * none — a world with bundles off, a draw no record can replay, and the very first dress of a batch + * the mint has not attached yet. * - * `reserve` is the declared bytes of a bake about to start: the caller asks whether evicting - * inactive records can make room for it. A record with a live user is never dropped, so a full - * cache of active atlases simply leaves the reservation unmet and the caller refuses the bake. + * It answers with the batch's own last refusal rather than re-asking, so the settled walk compares + * parentage without walking a material graph per key per frame. See `bundleSafe`. */ - #evictImpostors(reserve = 0): void { - if (this.#impostorRecordBytes + reserve <= this.#impostorBudgetBytes) return; - for (const [key, record] of [...this.#impostorRecords]) { - if (this.#impostorRecordBytes + reserve <= this.#impostorBudgetBytes) break; - if (record.users > 0) continue; - this.#impostorRecords.delete(key); - this.#impostorRecordBytes -= record.bytes; - record.atlas.dispose(); - } - } - - /** Dispose every cached atlas and any in-flight bake; the world's teardown path calls it once. */ - #drainImpostors(): void { - // Far-only sources no cell run owns would otherwise never reach `#evict`; release them here so - // their geometry goes the way every near asset's does. - for (const id of [...this.#farTemp]) this.#release(id); - this.#farTemp.clear(); - // Deferred far cohorts own a surface and pin an atlas user; release both before the records are - // disposed below, so no surface is left pointing at a disposed atlas. - for (const entry of this.#farRetry.values()) { - entry.surface.dispose(); - entry.record.users = Math.max(0, entry.record.users - 1); - } - this.#farRetry.clear(); - this.#farSeen.clear(); - // The far aggregates own surfaces over these atlases and hold a user on their records; they go - // first, so no far surface is left pointing at an atlas disposed below it. - this.#drainFar(); - this.#impostorQueue.length = 0; - this.#impostorQueued.clear(); - this.#impostorBaking = undefined; - this.#impostorReservedBytes = 0; - this.#baker.dispose(); - for (const record of this.#impostorRecords.values()) record.atlas.dispose(); - this.#impostorRecords.clear(); - this.#impostorRecordBytes = 0; + #bundleHome(shared: SharedBatch): Object3D | null { + if (this.#bundlesWanted === false || shared.bundlable === false) return this; + return this.#render.bundleGroup(mainShard(shared.mesh.name)) ?? null; } /** - * `TN_WORLD_IMPOSTOR`, one bounded line per asset: its terminal level, the metres it takes over at, - * the views the atlas holds, its GPU bytes, and — when the world can name it — why it did not land. + * Rebuild every resident run once, so a scene that came up after the ring was built is given the + * source records it was built without. + * + * `WorldCells.update` can be called before it has a renderer, and a renderer that is not WebGPU + * never turns the scene on at all; either way the cells that were swapped in while it was off hold + * placements the dispatches have never heard of, and nothing else in the class would ever hand them + * over. One rebuild of the ring is the whole fix, and it is the same build every first-seen asset + * takes: `replace` keeps the old batches drawn until each new one is attached, so the rebuild is + * not a frame of holes. */ - #reportImpostor(asset: IAssetState, reason: string): void { - if (this.#impostorReported.has(asset.id)) return; - this.#impostorReported.add(asset.id); - const impostor = asset.impostor; - const atlas = impostor?.record.atlas ?? this.#baker.pending?.atlas; - console.info( - `${WORLD_IMPOSTOR_MARKER} asset=${asset.id} level=${String(asset.levels.length - 1)} ` + - `switch=${(asset.distances.at(-1) ?? 0).toFixed(1)} ` + - `views=${String(atlas === undefined ? 0 : IMPOSTOR_VIEWS)} ` + - `bytes=${String(atlas?.bytes ?? 0)} reason=${reason}`, - ); + #seedGpuSources(): void { + // A pending build may already have consumed CPU-only slices. Restart it before any swap can + // publish a partial source list; its outgoing batches keep drawing until the new build lands. + const pending = this.#jobs; + this.#jobs = []; + for (const job of pending) { + this.#queued.delete(job.run); + for (const entry of job.fresh) this.#render.clearSegment(entry); + this.#queueBuild(job.asset, job.cell, job.run, true, job.force); + } + for (const cell of this.#resident.values()) + for (const batch of cell.batches) { + const asset = this.#models.asset(batch.asset); + if (asset === undefined || asset.levels.length === 0) continue; + // One rebuild per run, not per batch: a run's placements are one build, and queueing it per + // batch would rebuild the same placements once per level and part. + this.#queueBuild(asset, cell, batch.run, true); + } } /** @@ -9395,31 +2316,6 @@ export class WorldCells extends Group implements IComputeDriven { ); } - /** - * `TN_WORLD_LOD_CHAIN`, one line per asset that was widened: how many levels it is now drawn at, - * where each takes over in metres to one decimal, and what each submits in triangles. Level 0 - * leads, so the first triangle count is what the asset submits today and the last is what it - * submits past the last switch: the reduction is the whole point of the line. - */ - #reportChainLod(id: string, chained: IChainLevels): void { - this.#chainLodAssets += 1; - if (this.#chainLodAssets > CHAIN_LOD_MARKER_LIMIT) { - if (this.#chainLodSummarised) return; - this.#chainLodSummarised = true; - console.info( - `TN_WORLD_LOD_CHAIN more than ${String(CHAIN_LOD_MARKER_LIMIT)} assets carry a chain; ` + - `the rest are one line: assets=${String(this.#chainLodAssets)}`, - ); - return; - } - const listed: string[] = []; - for (const distance of chained.distances) listed.push(distance.toFixed(1)); - console.info( - `TN_WORLD_LOD_CHAIN ${id}: levels=${String(chained.levels.length)} ` + - `distances=${listed.join(",")} tris=${chained.triangles.join(",")}`, - ); - } - /** * The `[nearest, farthest]` distance from `(x, z)` to the rectangle a cell's placements lie in. * @@ -9470,12 +2366,10 @@ export class WorldCells extends Group implements IComputeDriven { (entry.threshold ?? 6) / 8 ) continue; - const asset = this.#assets.get(entry.asset); + const asset = this.#models.asset(entry.asset); if (asset === undefined) continue; - // GPU admission retains every resident maxDistance candidate, and the slot's cull plus level - // gates select it in the dispatch. Only the CPU path refilters those gates here; an explicit - // placementReach still queues a replacement because it remains a source-admission boundary. - const gates = this.#gpuScene.on ? [] : asset.gates; + // GPU dispatch handles maxDistance and LOD gates; placementReach remains a CPU boundary. + const gates = this.#render.gpuOn ? [] : asset.gates; const [builtNear, builtFar] = this.#span(cell, entry.lastFilterX, entry.lastFilterZ); let low = Math.min(near, builtNear); // Ends included, so a placement exactly on a gate is one of the reasons to rebuild. @@ -9541,7 +2435,7 @@ export class WorldCells extends Group implements IComputeDriven { // point has to keep coming back for it, or a walk that stopped mid-drain never finished. this.#refilterOwed = stale.length > this.#rebuildsPerUpdate; for (const job of stale.slice(0, this.#rebuildsPerUpdate)) { - const asset = this.#assets.get(job.id); + const asset = this.#models.asset(job.id); if (asset === undefined || asset.levels.length === 0) continue; // Every run of the group, not just the one that happens to carry the canonical's name: a cell // holding two names for one model has two stale answers, and rebuilding only the named one @@ -9579,668 +2473,13 @@ export class WorldCells extends Group implements IComputeDriven { ); } - #proxyObserved(state: ICellProxyState): boolean { - return state.prepared !== undefined && state.observed.size === state.prepared.meshes.length; - } - - #releaseCellProxy(state: ICellProxyState): void { - if (!this.#cellProxies.delete(state)) return; - state.prepared?.root.removeFromParent(); - for (const mesh of state.prepared?.meshes ?? []) { - mesh.onBeforeRender = Mesh.prototype.onBeforeRender; - mesh.onAfterRender = Mesh.prototype.onAfterRender; - this.#failures += release(mesh.geometry) ? 1 : 0; - } - // Cached model ownership uses the same global path holders as chunks and source LODs. - // A compilation never races this release: cancellation retains everything until settlement. - if ( - this.#loadModel !== undefined || - state.retainedPaths.every((path) => (this.#modelPaths.get(path) ?? 0) <= 1) - ) - this.#failures += this.#disposeLoaded(state.model); - this.#releaseModelPaths(state.retainedPaths); - this.#hlodBytes -= state.bytes; - state.bytes = 0; - } - - #beginCellProxy(cell: IResidentCell): void { - const proxy = cell.cell.proxy; - if (proxy === undefined) return; - // IO has the existing bounded transient decode allowance. Reserve only resident headroom; - // decoded source + conservative fixed-copy costs must fit it before private geometry is allocated. - const reserve = Math.min( - CHUNK_MERGE_MAX_BYTES * 2, - this.#budgets.bytes - this.#bytes - this.#farBytes - this.#hlodBytes, - ); - // Another proxy may still own worst-case headroom. Retry via the existing admission loop - // after it shrinks or releases; temporary pressure must not permanently cancel this cell. - if (reserve <= 0) return; - const state: ICellProxyState = { - cell, - selector: new CellProxySelection(proxy), - retainedPaths: [], - pending: true, - compiling: false, - cancelled: false, - compiled: false, - active: false, - bytes: 0, - observed: new Set(), - hidden: new Map(), - mainMasks: new Map(), - }; - const source = this.#cellProxySource(state); - // A retained neighboring key or a cheaper source LOD cannot benefit. Reconsider after later - // residency changes without allocating another copy or running a proxy load meanwhile. - if ( - !source.compatible || - proxy.triangles >= source.triangles || - proxy.materialGroups > source.draws - ) - return; - cell.proxy = state; - state.bytes = reserve; - this.#hlodBytes += reserve; - this.#cellProxies.add(state); - const path = resolveRelative(this.#logicalBase, proxy.glb); - if (this.#loadModel === undefined) this.#retainModelPaths([path], state.retainedPaths); - void this.#limiter - .load( - () => this.#model(path), - () => this.#cellLive(cell, cell.generation), - ) - .then( - (model) => { - state.pending = false; - state.model = model; - if (!this.#cellLive(cell, cell.generation) || state.cancelled || model === undefined) { - state.cancelled = true; - this.#releaseCellProxy(state); - return; - } - try { - if (measureCellProxy(model, proxy, true) > state.bytes) - throw new Error("Cell proxy exceeds its reserved decoded budget."); - } catch (error) { - state.cancelled = true; - this.#hlodRefused++; - this.#hlodReason = `${state.cell.key}: ${String(error instanceof Error ? error.message : error).slice(0, 160)}`; - this.#releaseCellProxy(state); - return; - } - // Preparation waits for an admission unit on a rendered update. - }, - () => { - state.pending = false; - state.cancelled = true; - this.#hlodFailures++; - this.#releaseCellProxy(state); - }, - ); - } - - #prepareCellProxy(state: ICellProxyState): void { - try { - const prepared = prepareCellProxy( - state.model as Object3D, - state.cell.cell.proxy as NonNullable, - state.bytes, - ); - state.prepared = prepared; - if (prepared.bytes > state.bytes) - throw new Error("Cell proxy exceeds its reserved decoded budget."); - this.#hlodBytes += prepared.bytes - state.bytes; - state.bytes = prepared.bytes; - prepared.root.name = `world-cell-proxy:${state.cell.key}`; - for (const mesh of prepared.meshes) mesh.receiveShadow = this.#receiveShadow; - this.add(prepared.root); - } catch (error) { - state.cancelled = true; - this.#hlodRefused++; - this.#hlodReason = `${state.cell.key}: ${String(error instanceof Error ? error.message : error).slice(0, 160)}`; - this.#releaseCellProxy(state); - } - } - - #compileCellProxy(state: ICellProxyState, renderer: IRendererLike, camera: Camera): void { - const prepared = state.prepared; - if (prepared === undefined) return; - let scene: Object3D = this; - while (scene.parent !== null) scene = scene.parent; - if (Reflect.get(scene, "isScene") !== true) return; - state.compiling = true; - // No timeout counts as cancellation. This original promise owns every staged resource until it - // settles, even if the cell/world has already left. Refusal keeps source and releases afterward. - void Promise.resolve() - .then(() => renderer.compileAsync(prepared.root, camera, scene)) - .then( - () => { - state.compiling = false; - if (state.cancelled || !this.#cellLive(state.cell, state.cell.generation)) { - state.cancelled = true; - this.#releaseCellProxy(state); - return; - } - this.#uploadAttributes(prepared.root); - state.compiled = true; - // Three calls render hooks during compile too, and may swallow pipeline errors. Observe a - // matching main-camera backend submission only AFTER this compile settles. Zero-count - // submission proves readiness of that route, not full-triangle execution or visual quality. - for (const mesh of prepared.meshes) { - let before: number | undefined; - const calls = (): number | undefined => { - try { - const info = renderer.info as { render?: { drawCalls?: number; calls?: number } }; - const value = info.render?.drawCalls ?? info.render?.calls; - return Number.isSafeInteger(value) && (value as number) >= 0 ? value : undefined; - } catch { - return undefined; - } - }; - mesh.onBeforeRender = (_raw, _scene, drawnCamera) => { - before = - drawnCamera === this.#camera && renderer.compiling !== true ? calls() : undefined; - }; - markEngineRenderHook(mesh.onBeforeRender); - mesh.onAfterRender = (_raw, _scene, drawnCamera) => { - const after = calls(); - if ( - drawnCamera === this.#camera && - renderer.compiling !== true && - before !== undefined && - after !== undefined && - after > before - ) - state.observed.add(mesh); - before = undefined; - }; - markEngineRenderHook(mesh.onAfterRender); - } - }, - () => { - state.compiling = false; - state.cancelled = true; - this.#hlodFailures++; - this.#releaseCellProxy(state); - }, - ) - .catch(() => { - state.compiling = false; - state.cancelled = true; - this.#hlodFailures++; - this.#releaseCellProxy(state); - }); - } - - #cellSourceDrawn(mesh: Mesh): boolean { - if ( - this.#camera === undefined || - (mesh instanceof InstancedMesh && mesh.count === 0) || - !this.#camera.layers.test(mesh.layers) || - Array.isArray(mesh.material) || - !mesh.material.visible - ) - return false; - for (let object: Object3D | null = mesh; object !== null; object = object.parent) - if (!object.visible) return false; - if (mesh.frustumCulled && !_cullFrustum.intersectsObject(mesh)) return false; - return true; - } - - #cellProxySource(state: ICellProxyState): { - compatible: boolean; - triangles: number; - draws: number; - meshes: Mesh[]; - } { - const cell = state.cell; - const meshes = [...cell.mainChunkMeshes]; - for (const chunk of cell.chunks) - chunk.traverse((node) => { - if (node instanceof Mesh && isLodJoinProxy(node)) meshes.push(node); - }); - const materials = new Set(); - const staticInstances = new Set(); - for (const [mesh, baseline] of cell.chunkInstances) - if ( - mesh.instanceMatrix === baseline.matrix && - mesh.instanceMatrix.version === baseline.version && - mesh.count === baseline.count - ) - staticInstances.add(mesh); - let triangles = 0; - let draws = 0; - let compatible = - this.#cellLoaded(cell) && (state.cell.cell.proxy?.scope !== "cell" || !this.#gpuScene.on); - for (const mesh of meshes) { - if ( - !cellProxySourceSafe(mesh, staticInstances) || - Array.isArray(mesh.material) || - !cellProxyGeometrySafe(mesh.geometry) || - !cellProxyMaterialSafe(mesh.material as Material) - ) - compatible = false; - else materials.add(mesh.material as Material); - if (this.#cellSourceDrawn(mesh)) { - triangles += - levelTriangles(mesh.geometry) * (mesh instanceof InstancedMesh ? mesh.count : 1); - draws++; - } - } - if (state.cell.cell.proxy?.scope === "cell") { - const counts = new Map(); - const costs = new Map(); - for (const run of cell.cell.runs) { - const asset = this.#assets.get(this.#canonical(run.asset)); - if ( - asset === undefined || - !asset.hlodSafe || - asset.impostor !== undefined || - asset.definition.maxDistance !== undefined || - this.#placementReach !== undefined - ) { - compatible = false; - continue; - } - for (const [part, shape] of (asset.levels[0] ?? []).entries()) { - const entries = cell.batches.filter((entry) => entry.run === run && entry.part === part); - if ( - entries.length !== 1 || - entries[0]?.level !== 0 || - entries[0]?.batch.count !== run.count || - !cellProxyMaterialSafe(shape.material) - ) { - compatible = false; - continue; - } - materials.add(shape.material); - } - } - for (const entry of cell.batches) { - if (entry.shared === undefined || entry.segment < 0 || entry.level !== 0) { - compatible = false; - continue; - } - counts.set(entry.shared, (counts.get(entry.shared) ?? 0) + entry.batch.count); - costs.set( - entry.shared, - (costs.get(entry.shared) ?? 0) + levelTriangles(entry.batch.geometry) * entry.batch.count, - ); - } - for (const [shared, count] of counts) { - // Retain the segment, hence near restoration is a flag write with no budget dependency. - // A neighboring holder keeps this key drawn; such a key cannot be retired for this cell. - if (shared.live !== count) compatible = false; - else { - if (this.#cellSourceDrawn(shared.mesh)) { - draws++; - triangles += costs.get(shared) ?? 0; - } - meshes.push(shared.mesh); - } - } - } - for (const mesh of state.prepared?.meshes ?? []) { - if ( - ![...materials].some((material) => - cellProxyMaterialMatches(mesh.material as Material, material), - ) - ) - compatible = false; - } - return { compatible, triangles, draws, meshes }; - } - - #restoreCellProxySource(state: ICellProxyState): void { - for (const [mesh, visible] of state.hidden) mesh.visible = visible; - state.hidden.clear(); - for (const [mesh, mask] of state.mainMasks) setModelLodLayers(mesh, mask); - state.mainMasks.clear(); - } - - #showCellProxy(state: ICellProxyState, active: boolean, meshes?: readonly Mesh[]): void { - const prepared = state.prepared; - if (prepared === undefined) return; - const changed = state.active !== active; - // AutoLOD owns chunk visibility. Retire only its main pass, retaining the selected source - // geometry as a caster; joined rungs inherit this same pass ownership on activation. - this.#restoreCellProxySource(state); - if (active) - for (const mesh of meshes ?? this.#cellProxySource(state).meshes) { - if (mesh.layers.isEnabled(0)) { - if (state.cell.mainChunkMeshes.includes(mesh) || isLodJoinProxy(mesh)) { - state.mainMasks.set(mesh, mesh.layers.mask); - let mask = mesh.layers.mask & ~1; - if (mesh.castShadow) - mask |= (1 << VIRTUAL_SHADOW_CASTER_LAYER) | (1 << VIRTUAL_SHADOW_WIDE_CASTER_LAYER); - setModelLodLayers(mesh, mask); - } else { - state.hidden.set(mesh, mesh.visible); - mesh.visible = false; - } - } - } - prepared.root.visible = active || !this.#proxyObserved(state); - prepared.meshes.forEach((mesh, index) => { - const range = prepared.ranges[index]; - if (range !== undefined) mesh.geometry.setDrawRange(range.start, active ? range.count : 0); - }); - state.active = active; - if (changed) { - for (const group of this.#bundles.values()) this.#bumpBundle(group); - const chunks = this.#chunkBundles.get(state.cell.key)?.group; - if (chunks !== undefined) this.#bumpBundle(chunks); - } - } - - #stepCellProxies(budget: IAdmissionBudget, renderer?: IRendererLike, camera?: Camera): void { - if (!this.#hlod) return; - for (const cell of this.#resident.values()) { - if (cell.cell.proxy === undefined) continue; - if (cell.proxy === undefined) { - if (this.#cellLoaded(cell)) budget.admit(() => this.#beginCellProxy(cell)); - continue; - } - const state = cell.proxy; - if (state.cancelled || state.pending) continue; - if (state.model !== undefined && state.prepared === undefined) - budget.admit(() => this.#prepareCellProxy(state)); - if (state.cancelled || state.prepared === undefined) continue; - if (!state.compiled && !state.compiling && renderer !== undefined && camera !== undefined) - budget.admit(() => this.#compileCellProxy(state, renderer, camera)); - // Cost the current AutoLOD source representation, not last frame's HLOD suppression. - this.#restoreCellProxySource(state); - const source = this.#cellProxySource(state); - const scale = cellProxyWorldScale(this.matrixWorld); - const sphere = worldSphere(state.prepared.bounds, this); - sphere.radius = state.prepared.bounds.radius * scale; - const view = - camera === undefined - ? undefined - : conservativeViewDepth( - camera, - sphere.center, - sphere.radius, - Reflect.get(camera, "near") ?? 0.1, - ); - const height = renderer?.domElement.height; - const selected = state.selector.select({ - sourceReady: this.#cellLoaded(cell), - proxyReady: state.compiled && this.#proxyObserved(state), - sourceCompatible: source.compatible && scale > 0, - sourceTriangles: source.triangles, - replaceableSourceDraws: source.draws, - views: - camera !== undefined && - height !== undefined && - Number.isFinite(height) && - height > 0 && - view !== undefined - ? [ - { - camera, - viewportHeight: height, - ...view, - finest: Reflect.get(camera, "isOrthographicCamera") === true || view.degenerate, - }, - ] - : [], - // Collapsing a valid Three transform retains detail; a zero error scale cannot select LOD. - errorScale: scale === 0 ? 1 : scale, - maxPixelError: this.#autoLodMaxPixelError, - }); - this.#showCellProxy(state, selected === "proxy", source.meshes); - } - } - - #startChunkLoad(cell: IResidentCell): void { - const generation = cell.generation; - const paths: string[] = []; - for (const chunk of cell.cell.chunks ?? []) - paths.push(resolveRelative(this.#logicalBase, chunk)); - // A chunk's source is the same loader cache entry an asset's level would be: held while this - // cell is resident and handed back with the last cell naming the path, through the one - // `#modelPaths` map. A `loadModel` override parses per load and reaches no cache, so it owns - // nothing here either; see `#releaseModelPaths`. - if (this.#loadModel === undefined) this.#retainModelPaths(paths, cell.retainedModelPaths); - loadAll( - paths, - (path) => - this.#limiter.load( - () => this.#model(path), - () => this.#cellLive(cell, generation), - ), - { marker: false }, - ).then( - (models) => { - void this.#attachChunks( - cell, - generation, - models.filter((model): model is Object3D => model !== undefined), - ); - }, - (error: unknown) => { - cell.chunkFailed = true; - this.#failures += 1; - this.#reportCellFailure("chunk", cell.key, paths, error); - }, - ); - } - - /** - * Hand the chunk's own buffers to the renderer now, and report how many it took. - * - * A compile builds pipelines, not geometry, so the bake's new attributes reached the device on - * whichever frame first drew them — 826 buffers and 228.8 MB on an 8 s walk, 33 of them over a - * megabyte, lining up exactly with the p95 and max frame spikes. The backend's own attribute - * creation is idempotent, so this is a move rather than a second upload, and a renderer with no - * such seam answers 0 and behaves as it did. - */ - #uploadAttributes(chunk: Object3D): number { - const upload = this.#renderer?.uploadAttributes; - if (upload === undefined) return 0; - const geometries = new Set(); - chunk.traverse((object) => { - const mesh = object as Mesh; - if (mesh.isMesh === true) geometries.add(mesh.geometry); - }); - return upload(geometries); - } - - /** - * Hand this chunk's own textures to the device now, and report how many it took. - * - * The same argument as the buffers above, one resource class over: a compile builds pipelines, not - * pixels, so the bake's textures reached the device inside `_renderObjectDirect` on the frame the - * chunk first drew — 87 ms of a 94 ms first draw in one frame of a Machinefall map-walk, and 79 ms - * of 131 ms in another. A texture the world already shares with what is on screen is one early - * return inside three and no work here. - */ - #uploadTextures(chunk: Object3D): number { - const upload = this.#renderer?.uploadTextures; - if (upload === undefined) return 0; - return upload(chunk); - } - - /** - * Compile a merged chunk before anything can draw it, and report whether the compile came back. - * - * A chunk's first draw is the most expensive frame a streaming world has: its own first draw built - * nodes and a render pipeline synchronously, and one chunk cost **248 ms of a 291 ms walk** in the - * census that found it. The engine already owns this work — `warmUpScene` drives the renderer's - * own `compileAsync`, and this is that same call with the world as the target scene, so the chunk's - * materials resolve the same lights, environment and clipping the real frame will resolve them - * against. A pipeline compiled against no lights would be a different pipeline, and the frame would - * still pay for it. - * - * It is bounded by `within`, the warm-up's own bound: a compile that never settles is abandoned and - * the chunk is attached anyway, because a missing chunk is worse than a slow frame. A world that is - * in no scene is skipped outright — nothing projects it, so a compile would build nothing, which is - * the same answer `load()` gets, where the loading screen is about to draw it anyway. - */ - async #warmChunk(chunk: Object3D): Promise { - const renderer = this.#renderer; - const camera = this.#camera; - if (renderer === undefined || camera === undefined || this.parent === null) return false; - // The rejected compile is a pipeline this warm-up could not build, not a reason to fail the - // admission: the frame that needs it will try again and fail there, where the error belongs. - return within( - renderer.compileAsync(chunk, camera, this), - CHUNK_WARM_TIMEOUT_MS, - yieldToHost, - warmClock, - ); - } - - async #attachChunks( - cell: IResidentCell, - generation: number, - models: readonly Object3D[], - ): Promise { - const live = (): boolean => this.#cellLive(cell, generation); - let attached = 0; - // Merged and shaped, not yet in the world: the compile below runs between the two, and a cell - // that left the ring in the middle takes its prepared chunks with it. - const prepared: { readonly object: Object3D; readonly proxies: number }[] = []; - try { - const report = await addInSlices( - models, - (object) => { - if (!live()) return; - object.name = CHUNK_NAME; - // One draw per material before the chunk is ever added, so the main pass and every shadow - // level that redraws it submit the chunk's materials rather than its nodes. A refusal - // leaves the chunk exactly as authored and is counted: it never costs the chunk itself. - const merge = mergeChunk( - object, - this.#chunkMergeMaxTriangles, - // A `loadModel` override parses per load, so a chunk's shapes are this world's to - // release; `assets.model` caches by path and hands the same scene back, so they are not. - this.#loadModel !== undefined, - this.#castShadowLevels > 0, - this.#proxyMaterials, - ); - // Where the frame budget counts this chunk's main-pass draws (`RenderPassBudget`). Written - // after the bake and over the whole subtree, because that is the only point where all three - // outcomes exist at once: a merged group, an instanced mesh the bake kept, and a chunk it - // refused whole are all one chunk's draws. The proxies the bake built are off layer 0 and - // keep the `proxies` origin they were given. - object.traverse((node) => { - if ((node as Mesh).isMesh !== true || node.layers.isEnabled(0) === false) return; - node.userData.tnDrawSource = "chunks"; - }); - // The buffers the bake just made, uploaded here rather than on the chunk's first draw, - // which is the frame that uploads them today: 228.8 MB over 826 `createAttribute` calls - // in a browser walk, and up to 230 ms inside the frames that first submitted them. - // `addInSlices` is the admission budget, so this is one chunk per update, never a batch. - const uploaded = this.#uploadAttributes(object); - this.#uploadTextures(object); - if (merge === undefined) this.#failures += 1; - else { - this.#failures += merge.failed; - console.info( - `TN_WORLD_CHUNK_MERGE meshes=${String(merge.meshes)} draws=${String(merge.draws)} ` + - `bytes=${String(merge.bytes)} uploaded=${String(uploaded)} ` + - `instancedExpanded=${String(merge.expanded)} keptInstanced=${String(merge.keptInstanced)} ` + - `shadowDraws=${String(merge.shadowDraws)}`, - ); - } - // Hand-placed chunks are the buildings and set dressing: full shape, so they cast with the - // near scatter and receive like everything else when the game asks for shadows. A merged - // opaque mesh the proxy covers keeps receiving and stops casting: the depth pass reads the - // proxy, so casting from both would pay the bill the proxy was built to remove. The proxy - // itself is off layer 0 and already carries what the level cameras need of it. - if (this.#castShadowLevels > 0 || this.#receiveShadow) { - const covered = new Set(merge?.shadowCovered); - object.traverse((node) => { - if (node.layers.isEnabled(0) === false) return; - node.castShadow = this.#castShadowLevels > 0 && !covered.has(node as Mesh); - node.receiveShadow = this.#receiveShadow; - }); - } - prepared.push({ object, proxies: merge?.shadowDraws ?? 0 }); - }, - { marker: false, while: live }, - ); - if (report.stopped) - for (let i = report.added; i < models.length; i += 1) - this.#failures += this.#disposeLoaded(models[i] as Object3D); - for (const { object, proxies } of prepared) { - // The compile is the whole point of the wait, and the wait is the only thing between the - // merge and the attach. Everything the world does not own is released when the cell that - // asked for the chunk is gone, so a chunk that arrives after its cell left is torn down here - // rather than compiled and thrown at a world that is not looking. - if (live()) await this.#warmChunk(object); - if (!live()) { - this.#failures += this.#disposeLoaded(object); - continue; - } - cell.chunks.push(object); - object.traverse((node) => { - if (node instanceof Mesh && node.layers.isEnabled(0)) cell.mainChunkMeshes.push(node); - if (node instanceof InstancedMesh && node.layers.isEnabled(0)) - cell.chunkInstances.set(node, { - matrix: node.instanceMatrix, - version: node.instanceMatrix.version, - count: node.count, - }); - }); - // The chunk's own box, from the merged geometry's boxes rather than its vertices: a chunk - // is loaded once and both consumers of it are only asked to be no wider than the chunk. Read - // before `#addChunk`, which may hand part of the subtree to a cell's record and leave the - // rest behind, so the box is the chunk's whatever ends up where. - const box = - proxies > 0 || this.#bundlesWanted ? new Box3().setFromObject(object, false) : undefined; - this.#addChunk(cell, object); - // A loaded chunk is placed once and never rewritten: its transforms, geometry and material - // are the ones the export gave it, so it is the one subtree here with nothing to announce. - markStatic(object); - // The proxies' shadow pipelines are the one thing a compile cannot build — three creates a - // shadow variant on the first pass that draws the caster, and the pass is the levels'. So - // the levels are told a caster arrived, exactly as a prewarmed caster cluster tells them: - // the first level whose window covers the chunk submits the proxy and builds it. With one - // material per `side` world-wide, that is one pipeline for the world's first chunk of each - // side and a node binding for every chunk after it, not a compile per chunk. - if (box !== undefined) { - if (proxies > 0) this.#shadowRecordsMoved(box, true); - // A bundled chunk is drawn or not by its cell's record, and that record is answered from - // the cell's cull volume — which is built from the cell's placements. A chunk is a cell's - // other half, so a cell holding chunks and no scatter would have an empty volume and never - // be drawn at all. Widened here rather than shrunk on eviction: the volume outlives one cell - // whenever a neighbour still holds the cluster, and a stale box only ever shows a cell that - // is there. - if (this.#bundlesWanted) - this.#cullCells.get(clusterOf(cell.x, cell.z, this.#cellsPerCullCell))?.box.union(box); - } - attached += 1; - } - } catch (error) { - cell.chunkFailed = true; - this.#failures += 1; - this.#reportCellFailure( - "attach", - cell.key, - Array.from(cell.cell.chunks ?? [], (chunk) => resolveRelative(this.#logicalBase, chunk)), - error, - ); - for (let i = attached; i < models.length; i += 1) - this.#failures += this.#disposeLoaded(models[i] as Object3D); - } - if (this.#cellLive(cell, generation) && !cell.chunkFailed) cell.chunksLoaded = true; - } - #evict(cell: IResidentCell): void { - if (cell.proxy !== undefined) { - this.#showCellProxy(cell.proxy, false); - cell.proxy.cancelled = true; - cell.proxy.prepared?.root.removeFromParent(); - if (!cell.proxy.pending && !cell.proxy.compiling) this.#releaseCellProxy(cell.proxy); - } + this.#chunks.evictProxy(cell); this.#residencyEpoch += 1; // Every run this cell held goes back to the far mesh BEFORE the near batches below are cleared: // the far mesh owns the ORIGINAL roots and this is the same synchronous block, so no frame draws // the cell twice or not at all. Skipped at teardown, where the far aggregates are drained whole. - if (!this.#released) for (const run of cell.cell.runs) this.#restoreFar(run); + if (!this.#released) for (const run of cell.cell.runs) this.#impostorState.restoreFar(run); // Work queued for a cell that has left is dropped, not resumed: a later frame would build // batches into a graph that no longer holds the cell, and the residency slot is already gone. this.#jobs = this.#jobs.filter((job) => { @@ -10251,207 +2490,37 @@ export class WorldCells extends Group implements IComputeDriven { // keeps them reserved and unwritten, so `mesh.count` never falls to zero, the asset's release // retires a batch that still counts records and drops the key outright, and the walk back // mints a second mesh for it. - for (const entry of job.fresh) this.#clearSegment(entry); + for (const entry of job.fresh) this.#render.clearSegment(entry); return false; }); - for (const entry of cell.batches) this.#clearSegment(entry); + for (const entry of cell.batches) this.#render.clearSegment(entry); // The same invalidation `#swap` makes, for the eviction path: the records that just left the // ring are ground the levels were drawing, and they are named one batch at a time. - this.#shadowRecordsMoved(this.#changedBounds(cell.batches)); + this.#render.shadowRecordsMoved(this.#render.changedBounds(cell.batches)); cell.batches.length = 0; // The cell's record of those chunks goes with them, so a replay can never draw a cell the world // has released — before they leave the group, which is where it counts what it holds. See // `#addChunk`. - this.#dropChunkBundles(cell); + this.#render.dropChunkBundles(cell); for (const chunk of cell.chunks) { chunk.removeFromParent(); - this.#failures += this.#disposeLoaded(chunk); + this.#failures += this.#models.disposeLoaded(chunk); } cell.chunks.length = 0; // The cell's chunk sources go back to the loader here, on the last cell that named them, exactly // as an asset's levels do in `#release`. Run even at teardown: the loader is never cleared, so a // path this world asked for must be handed back however the world leaves. - this.#releaseModelPaths(cell.retainedModelPaths); + this.#models.releaseModelPaths(cell.retainedModelPaths); this.#resident.delete(cell.key); - this.#cullEvicted(cell); + this.#render.cullEvicted(cell); this.#instances -= cell.instances; this.#bytes -= cell.bytes; this.#evictions += 1; // Room just freed: retry the far aggregates a full budget refused, so a species held back while // the ring was tight appears without another scan. Skipped at teardown, where the far half drains. - if (!this.#released) this.#retryFar(); + if (!this.#released) this.#impostorState.retryFar(); // One per run, so the canonical's refcount reaches zero exactly when the last run naming any // member of its group has left the ring. - for (const run of cell.cell.runs) this.#release(this.#canonical(run.asset)); - } - - /** - * Release every part's shape and every surface it owns — its own material and, for a scattered - * transparent part, the cutout clone made for it. - * - * At most once per resource, the same contract `release` gives the single-shape case: two parts - * of one GLB routinely share a material, a level that fell back shares the level below's whole - * part list, and every cell batch of the asset drew the same one. A clone is a resource this - * class made, so it is released here and nowhere else. - * - * @returns how many teardowns threw, for the caller to count. - */ - /** - * Tear down a model the loader handed this world, respecting who owns the loader. - * - * A model from the loader this world created is the world's to dispose. A model from an - * explicitly supplied loader is the caller's — another world or game may hold the same cached - * scene — so this world drops its batches but leaves the shapes and surfaces alone; the caller's - * own `release` ends that lifetime. See `#releaseModelPaths`. - */ - #disposeLoaded(model: Object3D | undefined): number { - if (model === undefined) return 0; - return this.#ownsLoader ? disposeModel(model) : 0; - } - - /** - * Release an adopted source shape or authored surface, unless a caller-supplied loader owns it. - * The caller's cache holds another world's copy too, so only a world-owned loader disposes what - * it adopted; a cutout this world built is always its own. See `#disposeLoaded`. - */ - #releaseSource(target: { dispose: () => void } | undefined): boolean { - return this.#ownsLoader && release(target); - } - - #releaseParts(parts: readonly IAssetPart[]): number { - let failed = 0; - for (const part of parts) { - if (releasedParts.has(part)) continue; - releasedParts.add(part); - // A parked mesh holds the part's own geometry and material, so once those are released the - // parked mesh can never be handed out again — it would give the next cell that asks for one a - // buffer waiting on a disposed resource. Dropped here, so it does not sit in the pool counting - // against the meshes a walk can park. - dropPooledFor(part.geometry, part.material); - if (this.#releaseSource(part.geometry)) failed += 1; - failed += this.#releaseSurface(part.surface); - } - return failed; - } - - /** - * The shared surface for a material's content: the first material seen for a key becomes the - * canonical one (converted to a cutout when it is transparent scatter), and every later material - * with the same content is released on arrival and draws with it instead. - */ - #surfaceFor(source: Material): { readonly key: string; readonly material: Material } { - const key = this.#preserveAuthoredParts ? source.uuid : materialKey(source); - const shared = this.#surfaces.get(key); - if (shared !== undefined) { - shared.users += 1; - if (!shared.owned.includes(source) && this.#releaseSource(source)) this.#failures += 1; - return { key, material: shared.material }; - } - const drawn = scatterMaterial(source, this.#transparentScatter); - this.#surfaces.set(key, { material: drawn.material, owned: drawn.owned, users: 1 }); - return { key, material: drawn.material }; - } - - /** One user gone; the last one tears the shared surface down. Returns teardowns that threw. */ - #releaseSurface(key: string): number { - const shared = this.#surfaces.get(key); - if (shared === undefined) return 0; - shared.users -= 1; - if (shared.users > 0) return 0; - this.#surfaces.delete(key); - let failed = 0; - // A caller-supplied loader's authored surfaces belong to the caller's cache, like its shapes. - // The cutout clones this world built are still the world's to release — `worldOwned` is the - // marker. See `#releaseSource`. - for (const material of shared.owned) - if ((this.#ownsLoader || worldOwned.has(material)) && release(material)) failed += 1; - return failed; - } - - #release(id: string): void { - const asset = this.#assets.get(id); - if (asset === undefined) return; - asset.refcount -= 1; - if (asset.refcount > 0) return; - this.#assets.delete(id); - asset.disposed = true; - // Before anything is released: a pending bake stages this asset's borrowed geometry, and a view - // drawn after the teardown reads a disposed buffer. Then the terminal level's own surface, whose - // geometry and material `#releaseParts` must skip; see `#releaseImpostor`. - this.#cancelImpostor(asset); - this.#releaseImpostor(asset); - // The last cell drawing this asset is gone, so this is the one teardown its levels get. It is - // also the one a lost device makes expensive, which is why every part goes through `release`: - // at most once per resource, and a level that fell back shares the level below's parts, so one - // shape is torn down once however many levels point at it. One refusal is counted per asset, - // the way one refused `lod0` load was. - const levels = asset.levels; - const spilled = asset.spilled; - asset.levels = []; - asset.spilled = []; - let failed = 0; - failed += this.#drainShared(id); - for (const parts of levels) failed += this.#releaseParts(parts); - // The middle levels' own reduced foliage cards, taken out of DRAW but still owned here. The - // released-parts set makes the ones a draw level also holds single-teardown, so a shared surface - // is never dropped early. - failed += this.#releaseParts(spilled); - if (failed > 0) this.#failures += 1; - this.#releaseModelPaths(asset.retainedPaths); - } - - /** - * Note the exact `assets.model` cache paths an owner (an asset's levels, a cell's chunks) asks the - * loader for, in the one world-wide `#modelPaths` map, so the last owner to let a path go can hand - * it back. Both owners share the map, so an asset and a chunk that name the same bytes keep the - * entry alive until neither holds it. - */ - #retainModelPaths(paths: readonly string[], into: string[]): void { - for (const path of paths) { - this.#modelPaths.set(path, (this.#modelPaths.get(path) ?? 0) + 1); - into.push(path); - } - } - - /** - * Hand back the exact `assets.model` cache paths this owner requested, so the loader drops the - * source scenes and their textures instead of pinning them for the world's whole life. - * - * A path another owner still names is not released: two definitions that fetch the same bytes but - * carry different `lods`/`maxDistance` are distinct assets, and the one leaving the ring must not - * tear out a cached source the other still draws with. Keyed on the requested logical path, so a - * path this world never asked for is never touched. - * - * Only the loader this world created is released. An explicitly supplied `assets` loader is the - * caller's: another game or world may hold the same cached model, so this world never drops a - * cache entry it does not own — the caller's own `release` is where that lifetime ends. - */ - #releaseModelPaths(paths: string[]): void { - for (const path of paths) { - const holders = (this.#modelPaths.get(path) ?? 1) - 1; - if (holders > 0) { - this.#modelPaths.set(path, holders); - continue; - } - this.#modelPaths.delete(path); - if (this.#ownsLoader) this.#loader.release("model", path); - } - paths.length = 0; - } - - /** Releases the shared meshes of one asset (or of every asset), keeping them for the walk back. */ - #drainShared(assetId?: string): number { - const failed = 0; - for (const [key, shared] of this.#shared) { - if (assetId !== undefined && !key.startsWith(`${assetId}:`)) continue; - shared.mesh.removeFromParent(); - // The mesh is out of the world, so it is out of the bundle the world recorded. A key that comes - // back is minted or rebound into the group again, and each of those is one re-record. - this.#bundleOut(shared); - this.#shared.delete(key); - // Retired, not dropped, so the cell that comes back draws into the same mesh. - this.#retire(key, shared); - } - return failed; + for (const run of cell.cell.runs) this.#models.release(this.#canonical(run.asset)); } } diff --git a/packages/core/src/world-chunk-shadow.ts b/packages/core/src/world-chunk-shadow.ts new file mode 100644 index 0000000000..f3b9216078 --- /dev/null +++ b/packages/core/src/world-chunk-shadow.ts @@ -0,0 +1,305 @@ +import { + BufferAttribute, + BufferGeometry, + type Camera, + Frustum, + InstancedMesh, + type Material, + Matrix4, + Mesh, + type Object3D, + Sphere, + Vector3, +} from "three"; +import { markStatic } from "./static-transform.js"; + +export const CHUNK_NAME = "world-chunk"; + +/** + * The layer the level shadow cameras render and the main camera does not; see + * `VirtualShadowNode`'s `VIRTUAL_SHADOW_CASTER_LAYER`. Duplicated from `index.ts` rather than + * imported, because the world chunk is separate from the main one and this is the one value the two + * halves of the cascade have to agree on. + */ +export const VIRTUAL_SHADOW_CASTER_LAYER = 28; +/** + * The layer the wide shadow levels render and the fine ones do not: one caster mesh per + * `asset:level:part` holding every cell's records, for a level whose window covers the whole + * resident ring, where a cluster per square is a draw per square for the same pixels. Duplicated + * from `index.ts` like the layer above, and a bit of its own so a level camera renders one caster + * granularity or the other and never both. + */ +export const VIRTUAL_SHADOW_WIDE_CASTER_LAYER = 27; + +/** + * The one surface each `side` of a chunk shadow proxy draws with, world-wide. + * + * A depth pass cannot tell one material from another: it reads `side`, `alphaTest` and position, + * and a proxy covers only opaque, non-alphaTest meshes, so every covered material with the same + * `side` produces the same shadow pipeline. Borrowing one per covered group built a new one for + * every chunk that loaded — 23 shadow draws over 125 ms and a 18 ms worst case on the walk this + * measured, each one compiling a pipeline the one before it had already proven was identical. The + * first covered material seen for a side is the world's, and every later proxy of that side borrows + * it, so a world pays at most one shadow pipeline per side over its whole life. + * + * The materials are the world's own, so `disposeModel` must not release them with the chunk that + * happened to contribute them. + */ +export const worldOwned = new WeakSet(); + +/** + * A depth pass cannot tell one material from another, so a chunk's shadow bill is its material + * count, not its triangle count: ~100 shadow draws a level render on the map, each a per-material + * mesh walked through the light's frustum. Positions are all it needs, and a position-only geometry + * grouped by `side` — the one thing a depth material does read — collapses that bill to one draw per + * side. + * + * Every proxy sits on both caster halves: covered originals no longer cast, so choosing + * key-wide scatter must retain the buildings, towers and fences represented by the proxy. + * Either way it is counted by the same `#probe` window test as the scatter caster + * clusters, so a level whose window does not reach the chunk drops it for free. Nothing here decides + * how anything looks: the covered meshes keep the game's own materials and keep drawing the main pass + * with them, and the proxy carries the world's material for its `side` by reference — the group it + * was grouped *by* is the material's own `side`, so the depth pass reads exactly what the covered + * mesh's own depth material would have read, and this file still constructs no material. + * + * Alpha-tested and transparent meshes keep casting themselves. Retained static instances use + * separate per-side proxies, so their per-part gates do not change the existing merged proxies. + */ +export function buildChunkShadowProxies( + chunk: Object3D, + merged: readonly Mesh[], + label: string, + proxyMaterials: Map, + selectParts = false, +): { readonly covered: Mesh[]; readonly proxies: Mesh[] } { + const sides = new Map(); + for (const mesh of merged) { + const material = mesh.material; + if (Array.isArray(material) || material.transparent === true || material.alphaTest > 0) + continue; + const side = material.side; + const group = sides.get(side); + if (group === undefined) sides.set(side, { material, meshes: [mesh] }); + else group.meshes.push(mesh); + } + const covered: Mesh[] = []; + const proxies: Mesh[] = []; + for (const [side, group] of sides) { + // Three's sides occupy 0..2; retained sources have their own compatible material pool. + const key = side + (selectParts ? 3 : 0); + const held = proxyMaterials.get(key); + const material = held ?? group.material; + if (held === undefined) { + proxyMaterials.set(key, material); + worldOwned.add(material); + } + const proxy = new Mesh(shadowProxyGeometry(group.meshes, label, chunk), material); + if (selectParts) selectChunkShadowParts(proxy, group.meshes); + proxy.name = `${CHUNK_NAME}-shadow`; + // The origin a draw of this mesh is counted under (`RenderPassBudget`). A proxy on the caster + // layers never reaches the main pass, so this only says anything where the world's own cull + // left one on layer 0 — and it is written here rather than inferred, so the tag cannot rot. + proxy.userData.tnDrawSource = "proxies"; + proxy.layers.set(VIRTUAL_SHADOW_CASTER_LAYER); + proxy.layers.enable(VIRTUAL_SHADOW_WIDE_CASTER_LAYER); + proxy.castShadow = true; + proxy.receiveShadow = false; + chunk.add(proxy); + markStatic(proxy); + proxies.push(proxy); + for (const mesh of group.meshes) { + mesh.castShadow = false; + covered.push(mesh); + } + } + return { covered, proxies }; +} + +/** + * One position-only geometry holding every covered mesh's vertices, indices preserved. + * + * Read through the attribute's own accessors, because a cooked model's positions can be quantized + * and interleaved (`KHR_mesh_quantization`) and the raw array behind them is not what three would + * have drawn. One pass at load time; the result is a depth buffer's whole input. + */ +/** + * One mesh's vertices, placed into `positions` from `vertexAt`. + * + * A merged mesh's positions came out of `mergeByMaterial` as one plain float array, so that is three + * reads, three multiplies and three writes per vertex. The arithmetic is three's, term for term and + * including `applyMatrix4`'s homogeneous divide, so a proxy holds the vertices it held before. A + * retained source can still be quantized and interleaved (`KHR_mesh_quantization`), and it takes the + * accessor loop, which is what that arithmetic resolves to. + */ +function placeVertices( + position: BufferAttribute, + matrix: Matrix4, + positions: Float32Array, + vertexAt: number, +): void { + if ( + position.array instanceof Float32Array && + !position.normalized && + !("isInterleavedBufferAttribute" in position) + ) { + const values = position.array as Float32Array; + const e = matrix.elements; + for (let index = 0; index < position.count; index += 1) { + const x = values[index * 3] as number; + const y = values[index * 3 + 1] as number; + const z = values[index * 3 + 2] as number; + const w = 1 / (e[3] * x + e[7] * y + e[11] * z + e[15]); + const out = (vertexAt + index) * 3; + positions[out] = (e[0] * x + e[4] * y + e[8] * z + e[12]) * w; + positions[out + 1] = (e[1] * x + e[5] * y + e[9] * z + e[13]) * w; + positions[out + 2] = (e[2] * x + e[6] * y + e[10] * z + e[14]) * w; + } + return; + } + const vertex = new Vector3(); + for (let index = 0; index < position.count; index += 1) { + vertex.fromBufferAttribute(position, index).applyMatrix4(matrix); + vertex.toArray(positions, (vertexAt + index) * 3); + } +} + +function shadowProxyGeometry( + meshes: readonly Mesh[], + label: string, + chunk: Object3D, +): BufferGeometry { + chunk.updateMatrixWorld(true); + const toRoot = chunk.matrixWorld.clone().invert(); + const place = new Matrix4(); + const instance = new Matrix4(); + const matrix = new Matrix4(); + let vertices = 0; + let drawn = 0; + for (const mesh of meshes) { + const position = mesh.geometry.getAttribute("position"); + if (position === undefined) + throw new Error(`Chunk shadow proxy (${label}): a merged mesh carries no position.`); + const copies = mesh instanceof InstancedMesh ? mesh.count : 1; + const total = mesh.geometry.getIndex()?.count ?? position.count; + const start = Math.min(total, Math.max(0, mesh.geometry.drawRange.start)); + vertices += position.count * copies; + drawn += Math.max(0, Math.min(total, start + mesh.geometry.drawRange.count) - start) * copies; + } + const positions = new Float32Array(vertices * 3); + const indices = new Uint32Array(drawn); + let vertexAt = 0; + let indexAt = 0; + const geometry = new BufferGeometry(); + for (const mesh of meshes) { + const position = mesh.geometry.getAttribute("position"); + if (position === undefined) + throw new Error(`Chunk shadow proxy (${label}): a merged mesh carries no position.`); + const source = mesh.geometry.getIndex(); + const total = source?.count ?? position.count; + const start = Math.min(total, Math.max(0, mesh.geometry.drawRange.start)); + const end = Math.min(total, start + mesh.geometry.drawRange.count); + const groupAt = indexAt; + place.multiplyMatrices(toRoot, mesh.matrixWorld); + for (let copy = 0; copy < (mesh instanceof InstancedMesh ? mesh.count : 1); copy += 1) { + if (mesh instanceof InstancedMesh) mesh.getMatrixAt(copy, instance); + else instance.identity(); + matrix.multiplyMatrices(place, instance); + placeVertices(position as BufferAttribute, matrix, positions, vertexAt); + for (let index = start; index < end; index += 1) + indices[indexAt++] = (source?.getX(index) ?? index) + vertexAt; + vertexAt += position.count; + } + geometry.addGroup(groupAt, indexAt - groupAt); + } + geometry.setAttribute("position", new BufferAttribute(positions, 3)); + geometry.setIndex(new BufferAttribute(indices, 1)); + geometry.computeBoundingSphere(); + return geometry; +} + +/** Keep retained sources' visibility, frustum and texel decisions on each level's single draw. */ +function selectChunkShadowParts(proxy: Mesh, sources: readonly Mesh[]): void { + const caster = proxy as Mesh & { chunkShadowProxy?: boolean; casterMinDiameter?: number }; + caster.chunkShadowProxy = true; + // The source frustums below decide the exact inputs; the union must not veto that decision. + proxy.frustumCulled = false; + const geometry = proxy.geometry; + const complete = ((geometry.getIndex() as BufferAttribute).array as Uint32Array).slice(); + const levels = new Map(); + const disposeLevels = (event: { target: unknown }) => { + const geometries = new Set([geometry]); + for (const level of levels.values()) geometries.add(level.geometry); + for (const held of geometries) held.removeEventListener("dispose", disposeLevels); + for (const held of geometries) if (held !== event.target) held.dispose(); + levels.clear(); + }; + geometry.addEventListener("dispose", disposeLevels); + const frustum = new Frustum(); + const projection = new Matrix4(); + const sphere = new Sphere(); + proxy.onBeforeRender = function (_renderer, _scene, camera) { + let level = levels.get(camera); + if (level === undefined) { + if (levels.size === 3) { + // ponytail: three resident camera buffers; extra cameras reuse the oldest slot. + const oldest = levels.keys().next().value as Camera; + level = levels.get(oldest) as { geometry: BufferGeometry; selected: Uint8Array }; + levels.delete(oldest); + level.selected.fill(255); + } else { + const perLevel = levels.size === 0 ? geometry : new BufferGeometry(); + if (perLevel !== geometry) { + perLevel.setAttribute("position", geometry.getAttribute("position")); + perLevel.setIndex(new BufferAttribute(new Uint32Array(complete.length), 1)); + perLevel.boundingSphere = geometry.boundingSphere; + perLevel.addEventListener("dispose", disposeLevels); + } + level = { geometry: perLevel, selected: new Uint8Array(sources.length).fill(255) }; + } + levels.set(camera, level); + } + // Three fetches the render object after this hook and detects geometry identity changes, + // even when different cameras share a render object. Exact projection stand-ins use `this`. + this.geometry = level.geometry; + const index = level.geometry.getIndex() as BufferAttribute; + const selected = level.selected; + frustum.setFromProjectionMatrix( + projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse), + camera.coordinateSystem, + camera.reversedDepth, + ); + let changed = false; + let count = 0; + for (let part = 0; part < sources.length; part += 1) { + const source = sources[part] as InstancedMesh; + if (source.boundingSphere === null) source.computeBoundingSphere(); + sphere.copy(source.boundingSphere as Sphere).applyMatrix4(source.matrixWorld); + let visible = true; + for (let node: Object3D | null = source; node !== null; node = node.parent) + if (!node.visible) visible = false; + const draws = + visible && + sphere.radius * 2 >= (caster.casterMinDiameter ?? 0) && + (!source.frustumCulled || frustum.intersectsObject(source)); + const next = draws ? 1 : 0; + if (selected[part] !== next) changed = true; + selected[part] = next; + if (draws) count += geometry.groups[part]?.count ?? 0; + } + if (changed) { + let at = 0; + for (let part = 0; part < sources.length; part += 1) { + const range = geometry.groups[part]; + if (selected[part] !== 1 || range === undefined) continue; + (index.array as Uint32Array).set( + complete.subarray(range.start, range.start + range.count), + at, + ); + at += range.count; + } + index.needsUpdate = true; + } + level.geometry.setDrawRange(0, count); + }; +} diff --git a/packages/core/src/world-gpu-scene.ts b/packages/core/src/world-gpu-scene.ts index 0ff7063578..dd2264d7e5 100644 --- a/packages/core/src/world-gpu-scene.ts +++ b/packages/core/src/world-gpu-scene.ts @@ -1,3 +1,44 @@ +import { + DRAW_ARGS_WORDS, + LIVE_MATRIX, + LOCAL_WORDS, + MATRIX_REPORTED_KEYS, + MATRIX_TOLERANCE, + MEMORY_FIELDS, + VALIDATE_REPORTED_KEYS, + compareMeshDraws, + compose, + sameGates, + sameLocal, + sameMatrices, + validationReport, + word, + xyz, + zeroComparisonProbe, +} from "./world-gpu-validation.js"; + +import type { + GpuSceneMemory, + GpuSceneVerdict, + IGpuSceneComparison, + IGpuSceneValidation, + IMeshDraw, +} from "./world-gpu-validation.js"; + +export type { + IMeshDraw, + IMeshDrawComparison, + GpuSceneVerdict, + IGpuSceneValidation, + GpuSceneMemoryField, + GpuSceneMemory, + IValidationNearest, + IValidationProbe, + IGpuSceneComparison, +} from "./world-gpu-validation.js"; + +export { compareMeshDraws, validationReport } from "./world-gpu-validation.js"; + import { type BufferAttribute, type Camera, @@ -58,8 +99,6 @@ import type { IRendererLike } from "./renderer.js"; /** Words one placement record holds: `mat4` + `centre` (xyz, radius) + `info` (asset slot, …; not `meta`, a reserved WGSL keyword). */ const PLACEMENT_WORDS = 24; -/** `DrawIndexedIndirect`: indexCount, instanceCount, firstIndex, baseVertex, firstInstance. */ -const DRAW_ARGS_WORDS = 5; /** The same record in bytes, which is the unit `BufferGeometry.setIndirect` takes its offset in. */ export const DRAW_ARGS_BYTES = DRAW_ARGS_WORDS * 4; @@ -76,8 +115,6 @@ function argsWords(keys: number): number { } /** One `vec4` per key, per asset slot and per level gate. */ const VEC4_WORDS = 4; -/** `mat4` per key: the part's own offset inside the model, the same matrix the CPU composes. */ -const LOCAL_WORDS = 16; /** Slots a placement may name before the kernel is refused rather than read out of bounds. */ const SLOT_NONE = -1; @@ -142,16 +179,6 @@ const VALIDATE_EVERY = 30; * landed bytes feed the tally, and when it is off this is the interval alone. */ const TALLY_EVERY = 30; -/** Keys a mismatch line names before the rest are counted and not printed. */ -const VALIDATE_REPORTED_KEYS = 10; -/** Keys a matrix or indirect-record line names, which carry more numbers than a count line does. */ -const MATRIX_REPORTED_KEYS = 5; -/** Meshes a `mesh=` line names, on the same budget as the record lines above. */ -const MESH_REPORTED_MESHES = 5; -/** Two matrices within this of each other are the same one written through a `mat4` product. */ -const MATRIX_TOLERANCE = 1e-3; -/** Characters of a cause a `cause=` line carries, so one message cannot own the report. */ -const CAUSE_CHARS = 160; /** * A `mat4` world matrix, the placement's bounding-sphere centre and radius, and the asset slot whose @@ -343,7 +370,7 @@ export interface IKernelResult { * `compared=0` over 21,147 resident placements. Copied at dispatch time, the reference is a pure * function of the snapshot and nothing that happens afterwards can change it. */ -interface IValidationSnapshot extends IKernelInput { +export interface IValidationSnapshot extends IKernelInput { /** The indirect records this dispatch wrote, which is what its readback must name. */ readonly args: unknown; /** The shared matrix buffer this dispatch compacted into, which its other readback must name. */ @@ -687,348 +714,6 @@ export function liveKeyInstances( return new Map([...words].map(([key, run]) => [key, Float32Array.from(run)])); } -/** The one matrix `liveKeyInstances` composes into, reused per placement as the kernel's own is. */ -const LIVE_MATRIX = new Float32Array(LOCAL_WORDS); - -/** - * Whether two gate tables are the same one, so a level key minted after the table was written - * rewrites it and a swap that arrives with nothing new does not. A level the world has not minted a - * key for carries `parts: 0`, which is what the kernel's own loop then draws for it: nothing. - */ -function sameGates(one: IAssetSlot, other: IAssetSlot): boolean { - if (one.cull !== other.cull) return false; - if ((one.impostor === true) !== (other.impostor === true)) return false; - if (one.distances.length !== other.distances.length) return false; - for (const [index, distance] of one.distances.entries()) - if (distance !== other.distances[index]) return false; - if (one.levels.length !== other.levels.length) return false; - for (const [index, level] of one.levels.entries()) { - const gate = other.levels[index]; - if (gate === undefined || gate.firstKey !== level.firstKey || gate.parts !== level.parts) - return false; - } - return true; -} - -/** One word of an indirect readback, out of range being the zero a record that was never written holds. */ -function word(readback: Uint32Array, at: number): number { - return (readback[at] ?? 0) as number; -} - -/** The translation of the `mat4` at slot `at`, which is what a draw places and a line can name. */ -function xyz(matrices: Float32Array, at: number): string { - const matrix = at * LOCAL_WORDS; - if (matrix + LOCAL_WORDS > matrices.length) return "[]"; - return ( - `[${(matrices[matrix + 12] as number).toFixed(3)},` + - `${(matrices[matrix + 13] as number).toFixed(3)},` + - `${(matrices[matrix + 14] as number).toFixed(3)}]` - ); -} - -/** - * Whether two runs of `mat4` hold the same matrices, as a set and not as a sequence. - * - * The kernel appends through an atomic, so the order two dispatches fill a region's run in is the - * order the device scheduled them in and is not the order the reference walked the placements. The - * counts agree over a set that agrees; a set that does not is a run one slot out, a matrix that was - * never written, or a `firstInstance` the draw reads from a different place — none of which a count - * can see. Sorted by translation, because that is what orders a placement, and compared with a - * tolerance, because a matrix written through a product of two floats is not bit-equal to either. - */ -function sameMatrices( - gpu: Float32Array, - gpuStart: number, - gpuCount: number, - cpu: Float32Array, - cpuStart: number, - cpuCount: number, - tolerance: number, -): boolean { - const one = sorted(gpu, gpuStart, gpuCount); - const other = sorted(cpu, cpuStart, cpuCount); - if (one.length !== other.length) return false; - for (const [index, matrix] of one.entries()) { - const against = other[index] as Float32Array; - for (let word = 0; word < LOCAL_WORDS; word += 1) - if (Math.abs((matrix[word] as number) - (against[word] as number)) > tolerance) return false; - } - return true; -} - -/** - * Whether two part offsets are the same matrix word for word. A re-dress copies the part's `Matrix4` - * into a fresh array, so a re-registration of the same key is the same `local` by value and not by - * reference; comparing the bytes is what makes it a no-op. - */ -function sameLocal(one: Float32Array, other: Float32Array): boolean { - if (one.length !== other.length) return false; - for (let word = 0; word < one.length; word += 1) - if ((one[word] as number) !== (other[word] as number)) return false; - return true; -} - -/** One run of `mat4` as views, ordered by translation, and short reads truncated rather than read past. */ -function sorted(matrices: Float32Array, start: number, count: number): Float32Array[] { - const out: Float32Array[] = []; - for (let taken = 0; taken < count; taken += 1) { - const at = (start + taken) * LOCAL_WORDS; - if (at < 0 || at + LOCAL_WORDS > matrices.length) break; - out.push(matrices.subarray(at, at + LOCAL_WORDS)); - } - out.sort( - (one, other) => - (one[12] as number) - (other[12] as number) || (one[14] as number) - (other[14] as number), - ); - return out; -} - -/** `out * into` at `at`, column-major, which is the element order both matrices are written in. */ function compose( - out: Float32Array, - into: Float32Array, - target: Float32Array, - at: number, -): void { - for (let column = 0; column < 4; column += 1) - for (let row = 0; row < 4; row += 1) { - let sum = 0; - for (let k = 0; k < 4; k += 1) - sum += (out[k * 4 + row] as number) * (into[column * 4 + k] as number); - target[at + column * 4 + row] = sum; - } -} - -/** - * One dressed main mesh's draw, as the owner's own live state says it, and nothing from this file's - * tables. - * - * This is the check that cannot be wrong in the same way the kernel is. {@link cullAndSelect} - * addresses a level's parts as `levels[gate.x + level].y + part`, and the gate table holds the - * `firstKey` the source owner registered for that level, so a key/record/mesh mapping that is - * wrong is reproduced on both sides of the comparison and reads as agreement: a real WebGPU walk - * printed `ok` over a forest it was drawing with another tree's geometry. The three numbers here - * come from the other side of the seam — the mesh's own `geometry.indirect` record, the mesh's own - * name, and the instances the owner's own gates and the camera's own planes select for that name — - * so the one question they can answer is the one that matters: does the record this mesh draws from - * hold exactly the instances that mesh's key is owed. - */ -export interface IMeshDraw { - /** The dressed mesh's own name, which is the main key: `asset:level:part`. */ - readonly name: string; - /** `geometry.indirectOffset / DRAW_ARGS_BYTES` — the record the draw reads, not one the tables name. */ - readonly record: number; - /** - * Every instance the owner's live gates and the camera's planes select for this key, `mat4` after - * `mat4`: the whole answer, and never the per-cell records this path's own refilter left behind. - */ - readonly instances: Float32Array; - /** - * Whether the mesh's own geometry reads the args buffer the readback came from, at the record above: - * `geometry.indirect === scene.args` and `geometry.indirectOffset === region.argsIndex * - * DRAW_ARGS_BYTES`, answered by the owner from the actual mesh. A record offset can agree with the - * live buffer while the mesh still points at an older buffer with the same numeric offset — the - * args buffer is allocated independently of `drawn`, and a mint can replace it under a dressed mesh - * — so an offset check alone reads `ok` over a mesh drawing a buffer nothing writes. A missing value - * is not a pass: only `true` is bound. - */ - readonly bound?: boolean; -} - -/** What the independent per-mesh check found, in the shape the report folds in. */ -export interface IMeshDrawComparison { - /** Meshes whose own record was read back and held against the live-state instances. */ - readonly compared: number; - readonly mismatched: number; - readonly mismatches: readonly string[]; -} - -/** - * Every dressed main mesh's own record, against the instances its own key is owed, as a set rather - * than a sequence — the kernel appends through an atomic, so a device's order is not the CPU's. - * - * Equality, and not containment, because the reference is now the whole answer: the owner's gates - * and the camera's planes over the scene's own placements, which is everything the dispatch reads. - * Containment was the right relation when the other side was the CPU path's per-cell records, which - * are the records a refilter left and go stale the moment the GPU scene bypasses it — a reference - * that can be a superset cannot name a level selected at the wrong distance, and a far tree drawn - * at its near level is what that looks like in the picture. - */ -export function compareMeshDraws( - gpu: Uint32Array, - drawn: Float32Array, - draws: readonly IMeshDraw[], - regions: readonly IRegion[], -): IMeshDrawComparison { - const mismatches: string[] = []; - let mismatched = 0; - for (const draw of draws) { - const record = draw.record * DRAW_ARGS_WORDS; - const landed = word(gpu, record + 1); - const first = word(gpu, record + 4); - // A stale binding is a mismatch the counts and matrices cannot see: the mesh's own geometry - // points at an args buffer the readback did not come from, so whatever this record holds is not - // what that mesh draws. Only an explicit `true` is bound, so a caller that forgets the field - // fails closed rather than reading as agreement. - if (draw.bound !== true) { - mismatched += 1; - if (mismatches.length >= MESH_REPORTED_MESHES) continue; - const owner = regions.find((region) => region.argsIndex === draw.record); - mismatches.push( - `mesh=${draw.name} record=${String(draw.record)} ` + - `region=${owner?.name ?? "none"} indirect=stale count gpu=${String(landed)} ` + - `live=${String(draw.instances.length / LOCAL_WORDS)}`, - ); - continue; - } - if ( - sameMatrices( - drawn, - first, - landed, - draw.instances, - 0, - draw.instances.length / LOCAL_WORDS, - MATRIX_TOLERANCE, - ) - ) - continue; - mismatched += 1; - if (mismatches.length >= MESH_REPORTED_MESHES) continue; - // The region that owns the record the mesh read, which is the name that says whether the mesh - // and the scene ever agreed on which record this key is. - const owner = regions.find((region) => region.argsIndex === draw.record); - mismatches.push( - `mesh=${draw.name} record=${String(draw.record)} ` + - `region=${owner?.name ?? "none"} count gpu=${String(landed)} ` + - `live=${String(draw.instances.length / LOCAL_WORDS)} ` + - `firstMatrix gpu=${xyz(drawn, first)} live=${xyz(draw.instances, 0)}`, - ); - } - return { compared: draws.length, mismatched, mismatches }; -} - -/** What one landed readback concluded, in the three words a log line can carry. */ -export type GpuSceneVerdict = "ok" | "mismatch" | "error"; - -/** One landed readback's verdict, its one line, and the detail lines under it. */ -export interface IGpuSceneValidation { - readonly verdict: GpuSceneVerdict; - readonly line: string; - readonly lines: readonly string[]; -} - -/** - * The memory numbers a validation line can carry, all of them optional. - * - * The first seven are three's `info.memory` fields (`WebGPURenderer`, 0.185); a backend that tracks - * none of them reports none, which is why every field is optional and only finite numbers are - * printed — an absent reading is not a zero. `footprintBytes` is the sum of this scene's own storage - * buffer `footprint()` bytes, the one number here Three did not write. - */ -export type GpuSceneMemoryField = - | "storageAttributes" - | "storageAttributesSize" - | "indirectStorageAttributes" - | "indirectStorageAttributesSize" - | "readbackBuffers" - | "readbackBuffersSize" - | "total" - | "footprintBytes"; - -/** One validation's memory reading; a field is absent when the backend did not offer a finite one. */ -export type GpuSceneMemory = Partial>; - -const MEMORY_FIELDS = [ - "storageAttributes", - "storageAttributesSize", - "indirectStorageAttributes", - "indirectStorageAttributesSize", - "readbackBuffers", - "readbackBuffersSize", - "total", - "footprintBytes", -] as const satisfies readonly GpuSceneMemoryField[]; - -/** - * The one placement nearest the camera (in XZ) a zero/zero comparison was handed, as the dispatch's - * own snapshot held it. Every number is the snapshot's, never the live scene's: the camera's eye and - * this placement's sphere, root translation, slot, slot cull, horizontal distance and the margin to - * the nearest of the six frustum planes are what say whether the snapshot held no live source or - * held one the camera, sphere, range or frustum rejected. A field is omitted, not printed as a fake - * zero, when the snapshot has no finite number for it. - */ -export interface IValidationNearest { - /** The bounding sphere: X, Y, Z and the radius. */ - readonly centre: readonly [number, number, number]; - readonly radius: number; - /** The placement matrix's translation, which is what a draw places. */ - readonly root: readonly [number, number, number]; - /** The asset slot the placement names. */ - readonly slot: number; - /** The slot's authored cull distance, or absent when it has none. */ - readonly cull?: number; - /** `hypot(cx - eye.x, cz - eye.z)`, the distance the level and cull gates read. */ - readonly distance: number; - /** The least `plane·centre + plane.constant + radius` over the six planes: negative is rejected. */ - readonly margin: number; -} - -/** - * Why a comparison that landed both counts at zero was handed nothing: the snapshot's own camera eye, - * how many placements it held and how many of them were live, and the one live placement nearest the - * camera in XZ. Taken from the snapshot, so nothing that streams while the bytes are in flight can - * rewrite the answer. Attached only when `instancesGpu` and `instancesCpu` are both zero. - */ -export interface IValidationProbe { - readonly eye: readonly [number, number, number]; - /** Placements the snapshot held, live and released. */ - readonly snapshot: number; - /** Placements whose slot was not `SLOT_NONE`, the only ones the kernel considers. */ - readonly live: number; - /** Absent when the snapshot held no live placement to name. */ - readonly nearest?: IValidationNearest; -} - -/** What a landed readback is judged on, gathered from the scene before it is formatted. */ -export interface IGpuSceneComparison { - /** Keys whose GPU instance count was read back and held against the reference. */ - readonly compared: number; - readonly instancesGpu: number; - readonly instancesCpu: number; - /** Up to {@link VALIDATE_REPORTED_KEYS} `name gpu=… cpu=…` lines; the count is the whole total. */ - readonly mismatches: readonly string[]; - readonly mismatched: number; - /** - * Keys whose drawn matrices, `firstInstance` or `indexCount` disagreed, and up to - * {@link MATRIX_REPORTED_KEYS} of them by name. The counts above are what the dispatch promised; - * these are what the draw reads, which is a different question and the one the picture answers. - */ - readonly matricesMismatched: number; - readonly matrixMismatches: readonly string[]; - /** - * Dressed main meshes whose own record was read back and held against the CPU path's own records - * for that mesh's key, and up to {@link MESH_REPORTED_MESHES} of them by name. This is the check - * the reference above cannot make: a key/record/mesh mapping that is wrong is one both sides - * reproduce, and a mesh drawing another asset's placements out of its own record is the picture - * that proves it. - */ - readonly meshMismatched: number; - readonly meshMismatches: readonly string[]; - /** Resident placements the scene held when the readback was issued. */ - readonly placed: number; - /** The device's last uncaptured error, or `""` for none since the last check reported one. */ - readonly deviceError: string; - /** What stopped the check — a rejected readback, say. Non-empty is an `error` on its own. */ - readonly cause?: string; - /** - * What the device and this scene held when the check reported. Absent when neither offered a - * finite number, and a field is omitted rather than printed as zero when its backend has none. - */ - readonly memory?: GpuSceneMemory; - /** The snapshot's own camera and nearest live placement, when both instance totals landed zero. */ - readonly probe?: IValidationProbe; -} - /** One completed manual frame check, identified by the caller's frame and this scene's dispatch. */ export interface IWorldGpuFrameValidation extends IGpuSceneComparison { /** The caller's rendered-frame identity, never inferred from readback timing. */ @@ -1038,159 +723,6 @@ export interface IWorldGpuFrameValidation extends IGpuSceneComparison { readonly verdict: GpuSceneVerdict; } -/** - * The verdict and the lines one landed readback reports, as `TN_WORLD_GPU_SCENE_VALIDATE`. - * - * `ok` is a claim that the GPU drew what the reference draws, so it takes three things: every key's - * count landing equal, every key's record and matrices landing where the reference put them, and - * something to land. A dispatch the device refused leaves the args buffer holding what the clear - * pass wrote, which compares equal to a scene with nothing in it — a real WebGPU run printed - * `ok keys=0` 956 times while the cull pipeline was being refused for a binding 48 bytes short, which - * is a check that reports success precisely when the kernel does nothing. So comparing nothing while - * placements exist is an `error`, and a device that raised anything uncaptured is an `error` - * whatever the counts say, because every count in a buffer the device has already invalidated is not - * evidence of anything. - * - * The three mismatch families are counted apart and all are a `mismatch`: a key that drew the right - * number of instances at the wrong place or through a record naming no triangle is the failure a - * count cannot see, and a walk that printed `mismatched=0` over a forest it never drew is what that - * reads like. The third is the one the two above cannot find at all: every key the reference names - * and every record it reads is named by this same file, so a key/record/mesh mapping that is wrong is - * one they both reproduce, and the dressed mesh's own record, against the CPU path's own records, - * is the only pair of numbers here that were not written by the same hand. - * - * An `error` always names its cause on one `cause=` line: the checks that fail here fail silently - * otherwise, and a line reading `error` with nothing after it is the report that hid a refused - * pipeline in the first place. - */ -/** One `[x,y,z]` at three decimals, the precision `xyz` names a matrix translation with. */ -function triple(values: readonly [number, number, number]): string { - return ( - `[${(values[0] as number).toFixed(3)},` + - `${(values[1] as number).toFixed(3)},${(values[2] as number).toFixed(3)}]` - ); -} - -/** The `key=value` fields a zero/zero comparison's probe adds after the memory fields. */ -function probeFields(probe: IValidationProbe): string[] { - const out = [ - `eye=${triple(probe.eye)}`, - `snapshot=${String(probe.snapshot)}`, - `live=${String(probe.live)}`, - ]; - const near = probe.nearest; - if (near === undefined) return out; - out.push( - `nearSphere=${triple(near.centre)}`, - `nearRadius=${near.radius.toFixed(3)}`, - `nearRoot=${triple(near.root)}`, - `nearSlot=${String(near.slot)}`, - ); - if (near.cull !== undefined) out.push(`nearCull=${near.cull.toFixed(3)}`); - out.push(`nearXZ=${near.distance.toFixed(3)}`, `nearMargin=${near.margin.toFixed(3)}`); - return out; -} - -/** - * The snapshot's own camera and nearest live placement, for a comparison both of whose counts landed - * zero. The scan only picks the nearest; the six-plane margin is computed for that one placement - * alone, not for every placement the reference walked, and a field the snapshot has no finite number - * for is omitted rather than printed as a fake zero. - */ -function zeroComparisonProbe(snapshot: IValidationSnapshot): IValidationProbe { - const eye: readonly [number, number, number] = [ - snapshot.camera.x, - snapshot.camera.y, - snapshot.camera.z, - ]; - let live = 0; - let nearest: IGpuPlacement | undefined; - let nearestDistance = Number.POSITIVE_INFINITY; - for (const placement of snapshot.placements) { - if (placement.slot < 0) continue; - live += 1; - const at = placement.centre; - const distance = Math.hypot( - (at[0] as number) - snapshot.camera.x, - (at[2] as number) - snapshot.camera.z, - ); - if (Number.isFinite(distance) && distance < nearestDistance) { - nearestDistance = distance; - nearest = placement; - } - } - const probe: IValidationProbe = { eye, live, snapshot: snapshot.count }; - if (nearest === undefined) return probe; - const at = nearest.centre; - const radius = at[3] as number; - const centre: readonly [number, number, number] = [ - at[0] as number, - at[1] as number, - at[2] as number, - ]; - const root: readonly [number, number, number] = [ - nearest.matrix[12] as number, - nearest.matrix[13] as number, - nearest.matrix[14] as number, - ]; - let margin = Number.POSITIVE_INFINITY; - for (let plane = 0; plane < 6; plane += 1) { - const offset = plane * 4; - const signed = - (snapshot.camera.planes[offset] as number) * (at[0] as number) + - (snapshot.camera.planes[offset + 1] as number) * (at[1] as number) + - (snapshot.camera.planes[offset + 2] as number) * (at[2] as number) + - (snapshot.camera.planes[offset + 3] as number); - margin = Math.min(margin, signed + radius); - } - const numbers = [...centre, ...root, radius, nearestDistance, margin]; - if (numbers.some((value) => !Number.isFinite(value))) return probe; - const cull = snapshot.slots[nearest.slot]?.cull; - return { - ...probe, - nearest: { - centre, - distance: nearestDistance, - margin, - radius, - root, - slot: nearest.slot, - ...(cull === undefined ? {} : { cull }), - }, - }; -} - -export function validationReport(input: IGpuSceneComparison): IGpuSceneValidation { - const blind = input.compared === 0 && input.placed > 0; - const cause = - input.deviceError !== "" - ? `device-error ${input.deviceError}` - : (input.cause ?? (blind ? `compared-0-with-placed=${String(input.placed)}` : "")); - const verdict: GpuSceneVerdict = - cause !== "" - ? "error" - : input.mismatched > 0 || input.matricesMismatched > 0 || input.meshMismatched > 0 - ? "mismatch" - : "ok"; - const memory: string[] = []; - if (input.memory !== undefined) - for (const name of MEMORY_FIELDS) { - const value = input.memory[name]; - if (typeof value === "number" && Number.isFinite(value)) - memory.push(`${name}=${String(value)}`); - } - const details = [...memory, ...(input.probe === undefined ? [] : probeFields(input.probe))]; - const suffix = details.length === 0 ? "" : ` ${details.join(" ")}`; - const line = - `TN_WORLD_GPU_SCENE_VALIDATE ${verdict} compared=${String(input.compared)} ` + - `instancesGpu=${String(input.instancesGpu)} instancesCpu=${String(input.instancesCpu)} ` + - `mismatched=${String(input.mismatched)} matricesMismatched=${String(input.matricesMismatched)} ` + - `meshMismatched=${String(input.meshMismatched)}${suffix}`; - const lines: string[] = [...input.mismatches, ...input.matrixMismatches, ...input.meshMismatches]; - if (cause !== "") lines.push(`cause=${cause.slice(0, CAUSE_CHARS)}`); - return { verdict, line, lines }; -} - /** The message an uncaptured WebGPU error event carries, flattened to one line. */ function uncapturedMessage(event: unknown): string { const error = (event as { error?: { message?: string; constructor?: { name?: string } } }).error; diff --git a/packages/core/src/world-gpu-validation.ts b/packages/core/src/world-gpu-validation.ts new file mode 100644 index 0000000000..575bb3d84a --- /dev/null +++ b/packages/core/src/world-gpu-validation.ts @@ -0,0 +1,517 @@ +import type { IAssetSlot, IGpuPlacement, IRegion, IValidationSnapshot } from "./world-gpu-scene.js"; + +/** `DrawIndexedIndirect`: indexCount, instanceCount, firstIndex, baseVertex, firstInstance. */ +export const DRAW_ARGS_WORDS = 5; + +/** `mat4` per key: the part's own offset inside the model, the same matrix the CPU composes. */ +export const LOCAL_WORDS = 16; + +/** Keys a mismatch line names before the rest are counted and not printed. */ +export const VALIDATE_REPORTED_KEYS = 10; + +/** Keys a matrix or indirect-record line names, which carry more numbers than a count line does. */ +export const MATRIX_REPORTED_KEYS = 5; + +/** Meshes a `mesh=` line names, on the same budget as the record lines above. */ +export const MESH_REPORTED_MESHES = 5; + +/** Two matrices within this of each other are the same one written through a `mat4` product. */ +export const MATRIX_TOLERANCE = 1e-3; + +/** Characters of a cause a `cause=` line carries, so one message cannot own the report. */ +export const CAUSE_CHARS = 160; + +/** The one matrix `liveKeyInstances` composes into, reused per placement as the kernel's own is. */ +export const LIVE_MATRIX = new Float32Array(LOCAL_WORDS); + +/** + * Whether two gate tables are the same one, so a level key minted after the table was written + * rewrites it and a swap that arrives with nothing new does not. A level the world has not minted a + * key for carries `parts: 0`, which is what the kernel's own loop then draws for it: nothing. + */ +export function sameGates(one: IAssetSlot, other: IAssetSlot): boolean { + if (one.cull !== other.cull) return false; + if ((one.impostor === true) !== (other.impostor === true)) return false; + if (one.distances.length !== other.distances.length) return false; + for (const [index, distance] of one.distances.entries()) + if (distance !== other.distances[index]) return false; + if (one.levels.length !== other.levels.length) return false; + for (const [index, level] of one.levels.entries()) { + const gate = other.levels[index]; + if (gate === undefined || gate.firstKey !== level.firstKey || gate.parts !== level.parts) + return false; + } + return true; +} + +/** One word of an indirect readback, out of range being the zero a record that was never written holds. */ +export function word(readback: Uint32Array, at: number): number { + return (readback[at] ?? 0) as number; +} + +/** The translation of the `mat4` at slot `at`, which is what a draw places and a line can name. */ +export function xyz(matrices: Float32Array, at: number): string { + const matrix = at * LOCAL_WORDS; + if (matrix + LOCAL_WORDS > matrices.length) return "[]"; + return ( + `[${(matrices[matrix + 12] as number).toFixed(3)},` + + `${(matrices[matrix + 13] as number).toFixed(3)},` + + `${(matrices[matrix + 14] as number).toFixed(3)}]` + ); +} + +/** + * Whether two runs of `mat4` hold the same matrices, as a set and not as a sequence. + * + * The kernel appends through an atomic, so the order two dispatches fill a region's run in is the + * order the device scheduled them in and is not the order the reference walked the placements. The + * counts agree over a set that agrees; a set that does not is a run one slot out, a matrix that was + * never written, or a `firstInstance` the draw reads from a different place — none of which a count + * can see. Sorted by translation, because that is what orders a placement, and compared with a + * tolerance, because a matrix written through a product of two floats is not bit-equal to either. + */ +export function sameMatrices( + gpu: Float32Array, + gpuStart: number, + gpuCount: number, + cpu: Float32Array, + cpuStart: number, + cpuCount: number, + tolerance: number, +): boolean { + const one = sorted(gpu, gpuStart, gpuCount); + const other = sorted(cpu, cpuStart, cpuCount); + if (one.length !== other.length) return false; + for (const [index, matrix] of one.entries()) { + const against = other[index] as Float32Array; + for (let word = 0; word < LOCAL_WORDS; word += 1) + if (Math.abs((matrix[word] as number) - (against[word] as number)) > tolerance) return false; + } + return true; +} + +/** + * Whether two part offsets are the same matrix word for word. A re-dress copies the part's `Matrix4` + * into a fresh array, so a re-registration of the same key is the same `local` by value and not by + * reference; comparing the bytes is what makes it a no-op. + */ +export function sameLocal(one: Float32Array, other: Float32Array): boolean { + if (one.length !== other.length) return false; + for (let word = 0; word < one.length; word += 1) + if ((one[word] as number) !== (other[word] as number)) return false; + return true; +} + +/** One run of `mat4` as views, ordered by translation, and short reads truncated rather than read past. */ +export function sorted(matrices: Float32Array, start: number, count: number): Float32Array[] { + const out: Float32Array[] = []; + for (let taken = 0; taken < count; taken += 1) { + const at = (start + taken) * LOCAL_WORDS; + if (at < 0 || at + LOCAL_WORDS > matrices.length) break; + out.push(matrices.subarray(at, at + LOCAL_WORDS)); + } + out.sort( + (one, other) => + (one[12] as number) - (other[12] as number) || (one[14] as number) - (other[14] as number), + ); + return out; +} + +/** `out * into` at `at`, column-major, which is the element order both matrices are written in. */ export function compose( + out: Float32Array, + into: Float32Array, + target: Float32Array, + at: number, +): void { + for (let column = 0; column < 4; column += 1) + for (let row = 0; row < 4; row += 1) { + let sum = 0; + for (let k = 0; k < 4; k += 1) + sum += (out[k * 4 + row] as number) * (into[column * 4 + k] as number); + target[at + column * 4 + row] = sum; + } +} + +/** + * One dressed main mesh's draw, as the owner's own live state says it, and nothing from this file's + * tables. + * + * This is the check that cannot be wrong in the same way the kernel is. {@link cullAndSelect} + * addresses a level's parts as `levels[gate.x + level].y + part`, and the gate table holds the + * `firstKey` the scene itself reported through `levelKeys`, so a key/record/mesh mapping that is + * wrong is reproduced on both sides of the comparison and reads as agreement: a real WebGPU walk + * printed `ok` over a forest it was drawing with another tree's geometry. The three numbers here + * come from the other side of the seam — the mesh's own `geometry.indirect` record, the mesh's own + * name, and the instances the owner's own gates and the camera's own planes select for that name — + * so the one question they can answer is the one that matters: does the record this mesh draws from + * hold exactly the instances that mesh's key is owed. + */ +export interface IMeshDraw { + /** The dressed mesh's own name, which is the main key: `asset:level:part`. */ + readonly name: string; + /** `geometry.indirectOffset / DRAW_ARGS_BYTES` — the record the draw reads, not one the tables name. */ + readonly record: number; + /** + * Every instance the owner's live gates and the camera's planes select for this key, `mat4` after + * `mat4`: the whole answer, and never the per-cell records this path's own refilter left behind. + */ + readonly instances: Float32Array; + /** + * Whether the mesh's own geometry reads the args buffer the readback came from, at the record above: + * `geometry.indirect === scene.args` and `geometry.indirectOffset === region.argsIndex * + * DRAW_ARGS_BYTES`, answered by the owner from the actual mesh. A record offset can agree with the + * live buffer while the mesh still points at an older buffer with the same numeric offset — the + * args buffer is allocated independently of `drawn`, and a mint can replace it under a dressed mesh + * — so an offset check alone reads `ok` over a mesh drawing a buffer nothing writes. A missing value + * is not a pass: only `true` is bound. + */ + readonly bound?: boolean; +} + +/** What the independent per-mesh check found, in the shape the report folds in. */ +export interface IMeshDrawComparison { + /** Meshes whose own record was read back and held against the live-state instances. */ + readonly compared: number; + readonly mismatched: number; + readonly mismatches: readonly string[]; +} + +/** + * Every dressed main mesh's own record, against the instances its own key is owed, as a set rather + * than a sequence — the kernel appends through an atomic, so a device's order is not the CPU's. + * + * Equality, and not containment, because the reference is now the whole answer: the owner's gates + * and the camera's planes over the scene's own placements, which is everything the dispatch reads. + * Containment was the right relation when the other side was the CPU path's per-cell records, which + * are the records a refilter left and go stale the moment the GPU scene bypasses it — a reference + * that can be a superset cannot name a level selected at the wrong distance, and a far tree drawn + * at its near level is what that looks like in the picture. + */ +export function compareMeshDraws( + gpu: Uint32Array, + drawn: Float32Array, + draws: readonly IMeshDraw[], + regions: readonly IRegion[], +): IMeshDrawComparison { + const mismatches: string[] = []; + let mismatched = 0; + for (const draw of draws) { + const record = draw.record * DRAW_ARGS_WORDS; + const landed = word(gpu, record + 1); + const first = word(gpu, record + 4); + // A stale binding is a mismatch the counts and matrices cannot see: the mesh's own geometry + // points at an args buffer the readback did not come from, so whatever this record holds is not + // what that mesh draws. Only an explicit `true` is bound, so a caller that forgets the field + // fails closed rather than reading as agreement. + if (draw.bound !== true) { + mismatched += 1; + if (mismatches.length >= MESH_REPORTED_MESHES) continue; + const owner = regions.find((region) => region.argsIndex === draw.record); + mismatches.push( + `mesh=${draw.name} record=${String(draw.record)} ` + + `region=${owner?.name ?? "none"} indirect=stale count gpu=${String(landed)} ` + + `live=${String(draw.instances.length / LOCAL_WORDS)}`, + ); + continue; + } + if ( + sameMatrices( + drawn, + first, + landed, + draw.instances, + 0, + draw.instances.length / LOCAL_WORDS, + MATRIX_TOLERANCE, + ) + ) + continue; + mismatched += 1; + if (mismatches.length >= MESH_REPORTED_MESHES) continue; + // The region that owns the record the mesh read, which is the name that says whether the mesh + // and the scene ever agreed on which record this key is. + const owner = regions.find((region) => region.argsIndex === draw.record); + mismatches.push( + `mesh=${draw.name} record=${String(draw.record)} ` + + `region=${owner?.name ?? "none"} count gpu=${String(landed)} ` + + `live=${String(draw.instances.length / LOCAL_WORDS)} ` + + `firstMatrix gpu=${xyz(drawn, first)} live=${xyz(draw.instances, 0)}`, + ); + } + return { compared: draws.length, mismatched, mismatches }; +} + +/** What one landed readback concluded, in the three words a log line can carry. */ +export type GpuSceneVerdict = "ok" | "mismatch" | "error"; + +/** One landed readback's verdict, its one line, and the detail lines under it. */ +export interface IGpuSceneValidation { + readonly verdict: GpuSceneVerdict; + readonly line: string; + readonly lines: readonly string[]; +} + +/** + * The memory numbers a validation line can carry, all of them optional. + * + * The first seven are three's `info.memory` fields (`WebGPURenderer`, 0.185); a backend that tracks + * none of them reports none, which is why every field is optional and only finite numbers are + * printed — an absent reading is not a zero. `footprintBytes` is the sum of this scene's own storage + * buffer `footprint()` bytes, the one number here Three did not write. + */ +export type GpuSceneMemoryField = + | "storageAttributes" + | "storageAttributesSize" + | "indirectStorageAttributes" + | "indirectStorageAttributesSize" + | "readbackBuffers" + | "readbackBuffersSize" + | "total" + | "footprintBytes"; + +/** One validation's memory reading; a field is absent when the backend did not offer a finite one. */ +export type GpuSceneMemory = Partial>; + +export const MEMORY_FIELDS = [ + "storageAttributes", + "storageAttributesSize", + "indirectStorageAttributes", + "indirectStorageAttributesSize", + "readbackBuffers", + "readbackBuffersSize", + "total", + "footprintBytes", +] as const satisfies readonly GpuSceneMemoryField[]; + +/** + * The one placement nearest the camera (in XZ) a zero/zero comparison was handed, as the dispatch's + * own snapshot held it. Every number is the snapshot's, never the live scene's: the camera's eye and + * this placement's sphere, root translation, slot, slot cull, horizontal distance and the margin to + * the nearest of the six frustum planes are what say whether the snapshot held no live source or + * held one the camera, sphere, range or frustum rejected. A field is omitted, not printed as a fake + * zero, when the snapshot has no finite number for it. + */ +export interface IValidationNearest { + /** The bounding sphere: X, Y, Z and the radius. */ + readonly centre: readonly [number, number, number]; + readonly radius: number; + /** The placement matrix's translation, which is what a draw places. */ + readonly root: readonly [number, number, number]; + /** The asset slot the placement names. */ + readonly slot: number; + /** The slot's authored cull distance, or absent when it has none. */ + readonly cull?: number; + /** `hypot(cx - eye.x, cz - eye.z)`, the distance the level and cull gates read. */ + readonly distance: number; + /** The least `plane·centre + plane.constant + radius` over the six planes: negative is rejected. */ + readonly margin: number; +} + +/** + * Why a comparison that landed both counts at zero was handed nothing: the snapshot's own camera eye, + * how many placements it held and how many of them were live, and the one live placement nearest the + * camera in XZ. Taken from the snapshot, so nothing that streams while the bytes are in flight can + * rewrite the answer. Attached only when `instancesGpu` and `instancesCpu` are both zero. + */ +export interface IValidationProbe { + readonly eye: readonly [number, number, number]; + /** Placements the snapshot held, live and released. */ + readonly snapshot: number; + /** Placements whose slot was not `SLOT_NONE`, the only ones the kernel considers. */ + readonly live: number; + /** Absent when the snapshot held no live placement to name. */ + readonly nearest?: IValidationNearest; +} + +/** What a landed readback is judged on, gathered from the scene before it is formatted. */ +export interface IGpuSceneComparison { + /** Keys whose GPU instance count was read back and held against the reference. */ + readonly compared: number; + readonly instancesGpu: number; + readonly instancesCpu: number; + /** Up to {@link VALIDATE_REPORTED_KEYS} `name gpu=… cpu=…` lines; the count is the whole total. */ + readonly mismatches: readonly string[]; + readonly mismatched: number; + /** + * Keys whose drawn matrices, `firstInstance` or `indexCount` disagreed, and up to + * {@link MATRIX_REPORTED_KEYS} of them by name. The counts above are what the dispatch promised; + * these are what the draw reads, which is a different question and the one the picture answers. + */ + readonly matricesMismatched: number; + readonly matrixMismatches: readonly string[]; + /** + * Dressed main meshes whose own record was read back and held against the CPU path's own records + * for that mesh's key, and up to {@link MESH_REPORTED_MESHES} of them by name. This is the check + * the reference above cannot make: a key/record/mesh mapping that is wrong is one both sides + * reproduce, and a mesh drawing another asset's placements out of its own record is the picture + * that proves it. + */ + readonly meshMismatched: number; + readonly meshMismatches: readonly string[]; + /** Resident placements the scene held when the readback was issued. */ + readonly placed: number; + /** The device's last uncaptured error, or `""` for none since the last check reported one. */ + readonly deviceError: string; + /** What stopped the check — a rejected readback, say. Non-empty is an `error` on its own. */ + readonly cause?: string; + /** + * What the device and this scene held when the check reported. Absent when neither offered a + * finite number, and a field is omitted rather than printed as zero when its backend has none. + */ + readonly memory?: GpuSceneMemory; + /** The snapshot's own camera and nearest live placement, when both instance totals landed zero. */ + readonly probe?: IValidationProbe; +} + +/** + * The verdict and the lines one landed readback reports, as `TN_WORLD_GPU_SCENE_VALIDATE`. + * + * `ok` is a claim that the GPU drew what the reference draws, so it takes three things: every key's + * count landing equal, every key's record and matrices landing where the reference put them, and + * something to land. A dispatch the device refused leaves the args buffer holding what the clear + * pass wrote, which compares equal to a scene with nothing in it — a real WebGPU run printed + * `ok keys=0` 956 times while the cull pipeline was being refused for a binding 48 bytes short, which + * is a check that reports success precisely when the kernel does nothing. So comparing nothing while + * placements exist is an `error`, and a device that raised anything uncaptured is an `error` + * whatever the counts say, because every count in a buffer the device has already invalidated is not + * evidence of anything. + * + * The three mismatch families are counted apart and all are a `mismatch`: a key that drew the right + * number of instances at the wrong place or through a record naming no triangle is the failure a + * count cannot see, and a walk that printed `mismatched=0` over a forest it never drew is what that + * reads like. The third is the one the two above cannot find at all: every key the reference names + * and every record it reads is named by this same file, so a key/record/mesh mapping that is wrong is + * one they both reproduce, and the dressed mesh's own record, against the CPU path's own records, + * is the only pair of numbers here that were not written by the same hand. + * + * An `error` always names its cause on one `cause=` line: the checks that fail here fail silently + * otherwise, and a line reading `error` with nothing after it is the report that hid a refused + * pipeline in the first place. + */ +/** One `[x,y,z]` at three decimals, the precision `xyz` names a matrix translation with. */ +export function triple(values: readonly [number, number, number]): string { + return ( + `[${(values[0] as number).toFixed(3)},` + + `${(values[1] as number).toFixed(3)},${(values[2] as number).toFixed(3)}]` + ); +} + +/** The `key=value` fields a zero/zero comparison's probe adds after the memory fields. */ +export function probeFields(probe: IValidationProbe): string[] { + const out = [ + `eye=${triple(probe.eye)}`, + `snapshot=${String(probe.snapshot)}`, + `live=${String(probe.live)}`, + ]; + const near = probe.nearest; + if (near === undefined) return out; + out.push( + `nearSphere=${triple(near.centre)}`, + `nearRadius=${near.radius.toFixed(3)}`, + `nearRoot=${triple(near.root)}`, + `nearSlot=${String(near.slot)}`, + ); + if (near.cull !== undefined) out.push(`nearCull=${near.cull.toFixed(3)}`); + out.push(`nearXZ=${near.distance.toFixed(3)}`, `nearMargin=${near.margin.toFixed(3)}`); + return out; +} + +/** + * The snapshot's own camera and nearest live placement, for a comparison both of whose counts landed + * zero. The scan only picks the nearest; the six-plane margin is computed for that one placement + * alone, not for every placement the reference walked, and a field the snapshot has no finite number + * for is omitted rather than printed as a fake zero. + */ +export function zeroComparisonProbe(snapshot: IValidationSnapshot): IValidationProbe { + const eye: readonly [number, number, number] = [ + snapshot.camera.x, + snapshot.camera.y, + snapshot.camera.z, + ]; + let live = 0; + let nearest: IGpuPlacement | undefined; + let nearestDistance = Number.POSITIVE_INFINITY; + for (const placement of snapshot.placements) { + if (placement.slot < 0) continue; + live += 1; + const at = placement.centre; + const distance = Math.hypot( + (at[0] as number) - snapshot.camera.x, + (at[2] as number) - snapshot.camera.z, + ); + if (Number.isFinite(distance) && distance < nearestDistance) { + nearestDistance = distance; + nearest = placement; + } + } + const probe: IValidationProbe = { eye, live, snapshot: snapshot.count }; + if (nearest === undefined) return probe; + const at = nearest.centre; + const radius = at[3] as number; + const centre: readonly [number, number, number] = [ + at[0] as number, + at[1] as number, + at[2] as number, + ]; + const root: readonly [number, number, number] = [ + nearest.matrix[12] as number, + nearest.matrix[13] as number, + nearest.matrix[14] as number, + ]; + let margin = Number.POSITIVE_INFINITY; + for (let plane = 0; plane < 6; plane += 1) { + const offset = plane * 4; + const signed = + (snapshot.camera.planes[offset] as number) * (at[0] as number) + + (snapshot.camera.planes[offset + 1] as number) * (at[1] as number) + + (snapshot.camera.planes[offset + 2] as number) * (at[2] as number) + + (snapshot.camera.planes[offset + 3] as number); + margin = Math.min(margin, signed + radius); + } + const numbers = [...centre, ...root, radius, nearestDistance, margin]; + if (numbers.some((value) => !Number.isFinite(value))) return probe; + const cull = snapshot.slots[nearest.slot]?.cull; + return { + ...probe, + nearest: { + centre, + distance: nearestDistance, + margin, + radius, + root, + slot: nearest.slot, + ...(cull === undefined ? {} : { cull }), + }, + }; +} + +export function validationReport(input: IGpuSceneComparison): IGpuSceneValidation { + const blind = input.compared === 0 && input.placed > 0; + const cause = + input.deviceError !== "" + ? `device-error ${input.deviceError}` + : (input.cause ?? (blind ? `compared-0-with-placed=${String(input.placed)}` : "")); + const verdict: GpuSceneVerdict = + cause !== "" + ? "error" + : input.mismatched > 0 || input.matricesMismatched > 0 || input.meshMismatched > 0 + ? "mismatch" + : "ok"; + const memory: string[] = []; + if (input.memory !== undefined) + for (const name of MEMORY_FIELDS) { + const value = input.memory[name]; + if (typeof value === "number" && Number.isFinite(value)) + memory.push(`${name}=${String(value)}`); + } + const details = [...memory, ...(input.probe === undefined ? [] : probeFields(input.probe))]; + const suffix = details.length === 0 ? "" : ` ${details.join(" ")}`; + const line = + `TN_WORLD_GPU_SCENE_VALIDATE ${verdict} compared=${String(input.compared)} ` + + `instancesGpu=${String(input.instancesGpu)} instancesCpu=${String(input.instancesCpu)} ` + + `mismatched=${String(input.mismatched)} matricesMismatched=${String(input.matricesMismatched)} ` + + `meshMismatched=${String(input.meshMismatched)}${suffix}`; + const lines: string[] = [...input.mismatches, ...input.matrixMismatches, ...input.meshMismatches]; + if (cause !== "") lines.push(`cause=${cause.slice(0, CAUSE_CHARS)}`); + return { verdict, line, lines }; +} diff --git a/packages/core/src/world-load-limiter.ts b/packages/core/src/world-load-limiter.ts new file mode 100644 index 0000000000..a51b8516e0 --- /dev/null +++ b/packages/core/src/world-load-limiter.ts @@ -0,0 +1,106 @@ +import type { Object3D } from "three"; +import type { IAdmissionBudget } from "./world-tiles.js"; + +interface IQueuedModelLoad { + readonly run: () => Promise; + readonly wanted: () => boolean; + readonly resolve: (model: Object3D | undefined) => void; + readonly reject: (error: unknown) => void; +} + +/** + * One bounded lane for every model load a `WorldCells` starts. Assets and chunks share it, so the + * number of `loadModel` calls in flight at once cannot grow with the number of admitted cells. + * Queued loads keep admission order, which is nearest-cell first, and one whose cell left (or + * whose asset was released) before its turn resolves `undefined` without touching the loader. + */ +export class ModelLoadLimiter { + readonly #concurrency: number; + readonly #queue: IQueuedModelLoad[] = []; + #inFlight = 0; + + constructor(concurrency: number) { + this.#concurrency = concurrency; + } + + get inFlight(): number { + return this.#inFlight; + } + + get queued(): number { + return this.#queue.length; + } + + load(run: () => Promise, wanted: () => boolean): Promise { + return new Promise((resolve, reject) => { + this.#queue.push({ reject, resolve, run, wanted }); + this.#pump(); + }); + } + + #pump(): void { + while (this.#inFlight < this.#concurrency && this.#queue.length > 0) { + const job = this.#queue.shift() as IQueuedModelLoad; + if (!job.wanted()) { + job.resolve(undefined); + continue; + } + this.#inFlight += 1; + try { + job.run().then( + (model) => { + this.#inFlight -= 1; + job.resolve(model); + this.#pump(); + }, + (error: unknown) => { + this.#inFlight -= 1; + job.reject(error); + this.#pump(); + }, + ); + } catch (error) { + // A loader that throws instead of rejecting must not strand its lane. + this.#inFlight -= 1; + job.reject(error); + this.#pump(); + } + } + } +} + +/** + * One frame's admission allowance, shared by every path that puts streamed content on screen. + * + * A streaming world has no frame-time ceiling anywhere else: a cell row arriving builds a batch per + * asset run over every placement in it, a terrain tile builds every LOD level, and the game's + * `createCollider` runs for every resident tile — all in the one frame that discovers them. That is + * the 100-330 ms hitch a 60 m/s camera feels every time the ring moves, and it is admission cost, + * not streaming: the loads never fail. + * + * The budget is opened once per `update` and drawn down by the units it admits. A unit always + * finishes, so a frame spends at most the limit plus the unit that crossed it, and the rest of the + * backlog waits for the next frame rather than being dropped. + */ +export class AdmissionBudget implements IAdmissionBudget { + readonly #limitMs: number; + readonly #now: () => number; + #spentMs = 0; + + constructor(limitMs: number, now: () => number) { + this.#limitMs = limitMs; + this.#now = now; + } + + get spentMs(): number { + return this.#spentMs; + } + + admit(work: () => void): boolean { + if (this.#spentMs >= this.#limitMs) return false; + const startedAt = this.#now(); + work(); + this.#spentMs += this.#now() - startedAt; + return true; + } +} diff --git a/packages/core/src/world-terrain-splat.ts b/packages/core/src/world-terrain-splat.ts index 460dfe0ff5..018ba8e800 100644 --- a/packages/core/src/world-terrain-splat.ts +++ b/packages/core/src/world-terrain-splat.ts @@ -5,7 +5,9 @@ import { LinearFilter, LinearMipmapLinearFilter, type Material, + Mesh, NoColorSpace, + type Object3D, RGBAFormat, RepeatWrapping, type Texture, @@ -46,6 +48,14 @@ export interface ITerrainSplatLayer { readonly id: string; readonly tile: number; readonly tint: readonly [number, number, number]; + /** Package-relative image paths; omitted entries retain `/_.jpg`. + * Normal, ORM and height maps are still loaded only when their flags request them. */ + readonly maps?: { + readonly diffuse?: string; + readonly normal?: string; + readonly orm?: string; + readonly height?: string; + }; readonly normal?: boolean; /** `_orm.jpg`: occlusion in r, roughness in g, metalness in b, read as linear data. */ readonly orm?: boolean; @@ -116,6 +126,33 @@ export interface ITerrainSplatWeightContext { readonly index: number; } +/** Loaded images for one game-authored sample; index 0 is the base, then table.layers in order. + * The loader owns these images until the returned material is disposed; do not dispose them. */ +export interface ITerrainSplatSampleContext { + readonly layer: ITerrainSplatLayer; + readonly index: number; + readonly diffuse: Texture; + readonly normal: Texture | undefined; + readonly orm: Texture | undefined; + readonly height: Texture | undefined; +} + +/** A game's projected layer. An absent normal leaves the accumulated normal unchanged. + * Normals are encoded OpenGL for the default factory; a custom factory owns their encoding. + * An absent ORM uses the layer's existing map or scalar response. */ +export interface ITerrainSplatSample { + readonly color: Node<"vec3">; + readonly normal?: Node<"vec3">; + readonly orm?: Node<"vec3">; +} + +/** Engine-blended channels; color includes the table's macro variation. */ +export interface ITerrainSplatMaterialContext { + readonly color: Node<"vec3">; + readonly normal: Node<"vec3">; + readonly orm: Node<"vec3">; +} + export interface ILoadTerrainSplatOptions { readonly assets: IAssetLoader; /** @@ -123,6 +160,12 @@ export interface ILoadTerrainSplatOptions { * which need one; without it every layer keeps its own sampler and the marker says so. */ readonly renderer?: IRendererLike; + /** Precomputed for every layer before layerWeight runs; replaces the package's projection. + * Custom sampling retains the loaded images and skips texture-array stacking. */ + readonly layerSample?: (context: ITerrainSplatSampleContext) => ITerrainSplatSample; + /** Replaces final material assembly, retaining the engine's ordered channel blend. + * Loader-owned textures are released with the returned material, including composition errors. */ + readonly materialFactory?: (context: ITerrainSplatMaterialContext) => Material; /** * Replaces each layer's blend weight, given the mask's own (remapped, 0..1) and the layer's * height sample. The game owns the curve, for example a height blend; the package mixes colour, @@ -400,165 +443,289 @@ export async function loadTerrainSplat(options: ILoadTerrainSplatOptions): Promi `TN_TERRAIN_SPLAT: ${unstated.join(", ")} states no orm map and no roughness/metalness, so they answer light as roughness ${String(DEFAULT_ROUGHNESS)} and metalness 0; re-export the package to author them.`, ); + const owned = + options.layerSample !== undefined || options.materialFactory !== undefined + ? new Set([splat]) + : undefined; + let released = false; + const release = () => { + if (released) return; + released = true; + for (const map of owned ?? []) map.dispose(); + }; + const load = async ( layer: ITerrainSplatLayer, kind: "diff" | "nrm" | "orm" | "h", ): Promise => { - const map = await assets.texture(`${dir}${table.textures}/${layer.id}_${kind}.jpg`, { - data: kind !== "diff", - }); + if ( + layer.maps !== undefined && + (layer.maps === null || typeof layer.maps !== "object" || Array.isArray(layer.maps)) + ) + throw new Error(`loadTerrainSplat: layer '${layer.id}' maps needs a path object.`); + const paths = { + diff: layer.maps?.diffuse, + nrm: layer.maps?.normal, + orm: layer.maps?.orm, + h: layer.maps?.height, + }; + const explicitPath = paths[kind]; + if ( + explicitPath !== undefined && + (typeof explicitPath !== "string" || + explicitPath.trim() === "" || + /^(?:[/\\]|[a-z][a-z0-9+.-]*:)/iu.test(explicitPath)) + ) + throw new Error( + `loadTerrainSplat: layer '${layer.id}' ${kind} map needs a package-relative path.`, + ); + const path = + explicitPath === undefined ? `${table.textures}/${layer.id}_${kind}.jpg` : explicitPath; + const map = await assets.texture(`${dir}${path}`, { data: kind !== "diff" }); map.wrapS = RepeatWrapping; map.wrapT = RepeatWrapping; map.anisotropy = 8; map.needsUpdate = true; + if (released && owned?.has(map) === false) map.dispose(); + owned?.add(map); return map; }; - const withNormals = all.filter((layer) => layer.normal === true); - const withOrm = all.filter((layer) => layer.orm === true); - const diffuseMaps = await Promise.all(all.map((layer) => load(layer, "diff"))); - const normalMaps = await Promise.all(withNormals.map((layer) => load(layer, "nrm"))); - const ormMaps = await Promise.all(withOrm.map((layer) => load(layer, "orm"))); - const withHeight = table.layers.filter((layer) => layer.height === true); - const heightMaps = await Promise.all(withHeight.map((layer) => load(layer, "h"))); - - // One sampler per set when the layers stack; separate textures (and a warning) when they do not. - let samplers = 1; - let stackedSets = 0; - const sampler = ( - maps: readonly Texture[], - what: string, - ): ((layer: number, uv: Node<"vec2">) => Node<"vec3">) => { - const stacked = stackLayers(maps, options.renderer); - if (stacked !== undefined) { - samplers += 1; - stackedSets += 1; - return (layer, uv) => texture(stacked, uv).depth(int(layer)).rgb; - } - samplers += maps.length; - if (maps.length > 1) - console.warn( - `TN_TERRAIN_SPLAT: ${what} layers could not stack into one array (mixed format or size, or no renderer to copy them with); they bind ${String(maps.length)} samplers, and WebGPU guarantees 16 a stage.`, - ); - return (layer, uv) => texture(maps[layer] as Texture, uv).rgb; - }; - const diffuseAt = sampler(diffuseMaps, "albedo"); - const normalAt = withNormals.length === 0 ? undefined : sampler(normalMaps, "normal"); - const ormAt = withOrm.length === 0 ? undefined : sampler(ormMaps, "ORM"); - const heightAt = withHeight.length === 0 ? undefined : sampler(heightMaps, "height"); - - const ground = vec2(positionWorld.x, positionWorld.z.negate()); - const maskOf = (layer: ITerrainSplatMaskedLayer): Node<"float"> => { - const where = table.splat.masks[layer.mask]; - if (where === undefined) throw new Error(`loadTerrainSplat: unknown mask '${layer.mask}'.`); - const source = planes[where[0]]; - if (source === undefined) - throw new Error(`loadTerrainSplat: mask '${layer.mask}' names a missing plane.`); - return source[where[1] === "rgb" ? layer.channel : where[1]]; - }; - const breakup = mx_noise_float(vec3(ground.mul(table.breakup.scale), 0)) - .mul(0.5) - .add(0.5); - const push = breakup.mul(2 * table.breakup.push).sub(table.breakup.push); - - // Box projection for cliffs: three axis-aligned samples blended by the surface normal. - const triplanar = (at: (uv: Node<"vec2">) => Node<"vec3">, tile: number): Node<"vec3"> => { - const weights = normalWorld.abs(); - const w = weights.div(weights.x.add(weights.y).add(weights.z)); - const p = positionWorld.div(tile); - return at(p.zy).mul(w.x).add(at(p.xz).mul(w.y)).add(at(p.xy).mul(w.z)); - }; + try { + const withNormals = all.filter((layer) => layer.normal === true); + const withOrm = all.filter((layer) => layer.orm === true); + const diffuseMaps = await Promise.all(all.map((layer) => load(layer, "diff"))); + const normalMaps = await Promise.all(withNormals.map((layer) => load(layer, "nrm"))); + const ormMaps = await Promise.all(withOrm.map((layer) => load(layer, "orm"))); + const withHeight = table.layers.filter((layer) => layer.height === true); + const heightMaps = await Promise.all(withHeight.map((layer) => load(layer, "h"))); - const albedo = (layer: ITerrainSplatLayer, index: number): Node<"vec3"> => { - let sample: Node<"vec3"> = - layer.triplanar === true - ? triplanar((uv) => diffuseAt(index, uv), layer.tile) - : diffuseAt(index, ground.div(layer.tile)); - const far = layer.far; - if (far !== undefined && layer.triplanar !== true) { - if (!(far.tile > 0 && far.from < far.to && far.amount >= 0 && far.amount <= 1)) - throw new Error( - `loadTerrainSplat: layer '${layer.id}' far needs tile > 0, from < to and amount in 0..1.`, + // One sampler per set when the layers stack; separate textures (and a warning) when they do not. + let samplers = 1; + let stackedSets = 0; + const sampler = ( + maps: readonly Texture[], + what: string, + ): ((layer: number, uv: Node<"vec2">) => Node<"vec3">) => { + if (options.layerSample !== undefined) { + samplers += maps.length; + return (layer, uv) => texture(maps[layer] as Texture, uv).rgb; + } + const stacked = stackLayers(maps, options.renderer); + if (stacked !== undefined) { + samplers += 1; + stackedSets += 1; + for (const map of maps) owned?.delete(map); + owned?.add(stacked); + return (layer, uv) => texture(stacked, uv).depth(int(layer)).rgb; + } + samplers += maps.length; + if (maps.length > 1) + console.warn( + `TN_TERRAIN_SPLAT: ${what} layers could not stack into one array (mixed format or size, or no renderer to copy them with); they bind ${String(maps.length)} samplers, and WebGPU guarantees 16 a stage.`, ); - const reach = smoothstep(far.from, far.to, positionWorld.distance(cameraPosition)); - sample = mix(sample, diffuseAt(index, ground.div(far.tile)), reach.mul(far.amount)); - } - if (layer.saturation !== undefined) { - const luma = dot(sample, vec3(0.2126, 0.7152, 0.0722)); - sample = mix(vec3(luma), sample, layer.saturation); - } - return sample.mul(vec3(...layer.tint)); - }; - const normalOf = (layer: ITerrainSplatLayer): Node<"vec3"> | undefined => { - const slot = withNormals.indexOf(layer); - return slot === -1 || normalAt === undefined - ? undefined - : normalAt(slot, ground.div(layer.tile)); - }; - /** - * A layer's answer to light, as occlusion in r, roughness in g and metalness in b: its ORM map - * where it ships one, else the two numbers the table states (and no occlusion to apply). - */ - const ormOf = (layer: ITerrainSplatLayer): Node<"vec3"> => { - const slot = withOrm.indexOf(layer); - if (slot !== -1 && ormAt !== undefined) return ormAt(slot, ground.div(layer.tile)); - return vec3(1, layer.roughness ?? DEFAULT_ROUGHNESS, layer.metalness ?? 0); - }; + return (layer, uv) => texture(maps[layer] as Texture, uv).rgb; + }; + const diffuseAt = sampler(diffuseMaps, "albedo"); + const normalAt = withNormals.length === 0 ? undefined : sampler(normalMaps, "normal"); + const ormAt = withOrm.length === 0 ? undefined : sampler(ormMaps, "ORM"); + const heightAt = withHeight.length === 0 ? undefined : sampler(heightMaps, "height"); - /** A height-carrying layer's own height, on the projection its albedo uses. */ - const heightOf = (layer: ITerrainSplatMaskedLayer): Node<"float"> | undefined => { - const slot = withHeight.indexOf(layer); - if (slot === -1 || heightAt === undefined) return undefined; - const at = (uv: Node<"vec2">): Node<"vec3"> => heightAt(slot, uv); - return (layer.triplanar === true ? triplanar(at, layer.tile) : at(ground.div(layer.tile))).r; - }; + const ground = vec2(positionWorld.x, positionWorld.z.negate()); + const maskOf = (layer: ITerrainSplatMaskedLayer): Node<"float"> => { + const where = table.splat.masks[layer.mask]; + if (where === undefined) throw new Error(`loadTerrainSplat: unknown mask '${layer.mask}'.`); + const source = planes[where[0]]; + if (source === undefined) + throw new Error(`loadTerrainSplat: mask '${layer.mask}' names a missing plane.`); + return source[where[1] === "rgb" ? layer.channel : where[1]]; + }; + const breakup = mx_noise_float(vec3(ground.mul(table.breakup.scale), 0)) + .mul(0.5) + .add(0.5); + const push = breakup.mul(2 * table.breakup.push).sub(table.breakup.push); + + // Box projection for cliffs: three axis-aligned samples blended by the surface normal. + const triplanar = (at: (uv: Node<"vec2">) => Node<"vec3">, tile: number): Node<"vec3"> => { + const weights = normalWorld.abs(); + const w = weights.div(weights.x.add(weights.y).add(weights.z)); + const p = positionWorld.div(tile); + return at(p.zy).mul(w.x).add(at(p.xz).mul(w.y)).add(at(p.xy).mul(w.z)); + }; + + const albedo = (layer: ITerrainSplatLayer, index: number): Node<"vec3"> => { + let sample: Node<"vec3"> = + layer.triplanar === true + ? triplanar((uv) => diffuseAt(index, uv), layer.tile) + : diffuseAt(index, ground.div(layer.tile)); + const far = layer.far; + if (far !== undefined && layer.triplanar !== true) { + if (!(far.tile > 0 && far.from < far.to && far.amount >= 0 && far.amount <= 1)) + throw new Error( + `loadTerrainSplat: layer '${layer.id}' far needs tile > 0, from < to and amount in 0..1.`, + ); + const reach = smoothstep(far.from, far.to, positionWorld.distance(cameraPosition)); + sample = mix(sample, diffuseAt(index, ground.div(far.tile)), reach.mul(far.amount)); + } + if (layer.saturation !== undefined) { + const luma = dot(sample, vec3(0.2126, 0.7152, 0.0722)); + sample = mix(vec3(luma), sample, layer.saturation); + } + return sample.mul(vec3(...layer.tint)); + }; + const normalOf = (layer: ITerrainSplatLayer): Node<"vec3"> | undefined => { + const slot = withNormals.indexOf(layer); + return slot === -1 || normalAt === undefined + ? undefined + : normalAt(slot, ground.div(layer.tile)); + }; + /** + * A layer's answer to light, as occlusion in r, roughness in g and metalness in b: its ORM map + * where it ships one, else the two numbers the table states (and no occlusion to apply). + */ + const ormOf = (layer: ITerrainSplatLayer): Node<"vec3"> => { + const slot = withOrm.indexOf(layer); + if (slot !== -1 && ormAt !== undefined) return ormAt(slot, ground.div(layer.tile)); + return vec3(1, layer.roughness ?? DEFAULT_ROUGHNESS, layer.metalness ?? 0); + }; - let color = albedo(table.base, 0); - let normalSample: Node<"vec3"> = normalOf(table.base) ?? vec3(0.5, 0.5, 1); - let ormSample: Node<"vec3"> = ormOf(table.base); - table.layers.forEach((layer, offset) => { - const masked = clamp( - maskOf(layer) - .add(push) - .sub(layer.lo) - .div(layer.hi - layer.lo), - 0, - 1, + /** A height-carrying layer's own height, on the projection its albedo uses. */ + const heightOf = (layer: ITerrainSplatMaskedLayer): Node<"float"> | undefined => { + const slot = withHeight.indexOf(layer); + if (slot === -1 || heightAt === undefined) return undefined; + const at = (uv: Node<"vec2">): Node<"vec3"> => heightAt(slot, uv); + return (layer.triplanar === true ? triplanar(at, layer.tile) : at(ground.div(layer.tile))).r; + }; + + const samples = all.map( + (layer, index): ITerrainSplatSample => + options.layerSample === undefined + ? { color: albedo(layer, index), normal: normalOf(layer), orm: ormOf(layer) } + : options.layerSample({ + layer, + index, + diffuse: diffuseMaps[index] as Texture, + normal: normalMaps[withNormals.indexOf(layer)], + orm: ormMaps[withOrm.indexOf(layer)], + height: heightMaps[withHeight.indexOf(layer as ITerrainSplatMaskedLayer)], + }), ); - const weight = - options.layerWeight === undefined - ? masked - : options.layerWeight(masked, { height: heightOf(layer), index: offset, layer }); - color = mix(color, albedo(layer, offset + 1), weight); - const nrm = normalOf(layer); - if (nrm !== undefined) normalSample = mix(normalSample, nrm, weight); - ormSample = mix(ormSample, ormOf(layer), weight); + let color = (samples[0] as ITerrainSplatSample).color; + let normalSample: Node<"vec3"> = samples[0]?.normal ?? vec3(0.5, 0.5, 1); + let ormSample: Node<"vec3"> = samples[0]?.orm ?? ormOf(table.base); + table.layers.forEach((layer, offset) => { + const masked = clamp( + maskOf(layer) + .add(push) + .sub(layer.lo) + .div(layer.hi - layer.lo), + 0, + 1, + ); + const weight = + options.layerWeight === undefined + ? masked + : options.layerWeight(masked, { height: heightOf(layer), index: offset, layer }); + const sample = samples[offset + 1] as ITerrainSplatSample; + color = mix(color, sample.color, weight); + const nrm = sample.normal; + if (nrm !== undefined) normalSample = mix(normalSample, nrm, weight); + ormSample = mix(ormSample, sample.orm ?? ormOf(layer), weight); + }); + const macro = mx_noise_float(vec3(ground.mul(table.macro.scale), 0)) + .mul(0.5) + .add(0.5) + .mul(table.macro.max - table.macro.min) + .add(table.macro.min); + + // Every layer's own light response, blended by the same weights as its colour: the material + // holds no scalar of its own, and `DEFAULT_ROUGHNESS` only answers for a layer that states + // nothing at all. + let material: Material; + if (options.materialFactory === undefined) { + const standard = new MeshStandardNodeMaterial(); + standard.name = "terrain-splat"; + standard.colorNode = color.mul(macro); + // A top-down projection's tangent frame: +x along u, ground +y (three -z) along v, OpenGL green + // up v, re-orthogonalised against the tile's own normal. + const n = normalWorld; + const tangent = vec3(1, 0, 0).sub(n.mul(n.x)).normalize(); + const bitangent = n.cross(tangent).normalize(); + const t = normalSample.mul(2).sub(1); + const perturbed = tangent.mul(t.x).add(bitangent.mul(t.y)).add(n.mul(t.z)).normalize(); + standard.normalNode = cameraViewMatrix.mul(vec4(perturbed, 0)).xyz.normalize(); + standard.aoNode = ormSample.r; + standard.roughnessNode = ormSample.g; + standard.metalnessNode = ormSample.b; + material = standard; + } else { + material = options.materialFactory({ + color: color.mul(macro), + normal: normalSample, + orm: ormSample, + }); + } + if (owned !== undefined) material.addEventListener("dispose", release); + // One line, printed and kept on the material: what this surface costs, readable by a game that + // wants to assert it rather than scrape a console. + material.userData.TN_TERRAIN_SPLAT = + options.layerSample === undefined + ? `layers=${String(all.length)} samplers=${String(samplers)} stacked=${String(stackedSets)}` + : `layers=${String(all.length)} loadedSamplerUpperBound=${String(samplers)} stacked=0 sampling=custom`; + console.info(`TN_TERRAIN_SPLAT ${material.userData.TN_TERRAIN_SPLAT as string}`); + return material; + } catch (error) { + if (owned !== undefined) release(); + throw error; + } +} + +/** + * Apply a cooked GLB's package-authored layered surface to its marked terrain meshes. + * `url` is the loaded GLB URL; extras pointers are relative to it. Uses the same asset/fetch + * path as loadTerrainSplat on web and native. The caller owns the assigned materials' lifetime. + * @situation apply the authored layered terrain surface after loading a cooked world GLB + * @constraint url names the loaded GLB; tnTerrainSurface extras name a relative world package JSON + * @override every appearance input comes from the package table or loadTerrainSplat options + * @example + * await applyTerrainSurface(world.scene, { assets: ctx.assets, renderer: ctx.renderer, url: "world/world.glb" }); + */ +export async function applyTerrainSurface( + root: Object3D, + options: ILoadTerrainSplatOptions, +): Promise { + const targets = new Map(); + const retained = new Set(); + root.traverse((object) => { + if (!(object instanceof Mesh)) return; + let owner: Object3D | null = object; + while (owner !== null && owner.userData.tnTerrainSurface === undefined && owner !== root) + owner = owner.parent; + const pointer: unknown = owner?.userData.tnTerrainSurface; + if (pointer === undefined) { + for (const material of Array.isArray(object.material) ? object.material : [object.material]) + retained.add(material); + return; + } + if ( + typeof pointer !== "string" || + !/^(?:[a-zA-Z0-9_-]+\/)*[a-zA-Z0-9_.-]+\.json$/u.test(pointer) + ) + throw new Error(`applyTerrainSurface: invalid terrain surface pointer '${String(pointer)}'.`); + targets.set(object, packageDir(options.url) + pointer); }); - const macro = mx_noise_float(vec3(ground.mul(table.macro.scale), 0)) - .mul(0.5) - .add(0.5) - .mul(table.macro.max - table.macro.min) - .add(table.macro.min); - - // Every layer's own light response, blended by the same weights as its colour: the material - // holds no scalar of its own, and `DEFAULT_ROUGHNESS` only answers for a layer that states - // nothing at all. - const material = new MeshStandardNodeMaterial(); - material.name = "terrain-splat"; - material.colorNode = color.mul(macro); - // A top-down projection's tangent frame: +x along u, ground +y (three -z) along v, OpenGL green - // up v, re-orthogonalised against the tile's own normal. - const n = normalWorld; - const tangent = vec3(1, 0, 0).sub(n.mul(n.x)).normalize(); - const bitangent = n.cross(tangent).normalize(); - const t = normalSample.mul(2).sub(1); - const perturbed = tangent.mul(t.x).add(bitangent.mul(t.y)).add(n.mul(t.z)).normalize(); - material.normalNode = cameraViewMatrix.mul(vec4(perturbed, 0)).xyz.normalize(); - material.aoNode = ormSample.r; - material.roughnessNode = ormSample.g; - material.metalnessNode = ormSample.b; - // One line, printed and kept on the material: what this surface costs, readable by a game that - // wants to assert it rather than scrape a console. - material.userData.TN_TERRAIN_SPLAT = `layers=${String(all.length)} samplers=${String(samplers)} stacked=${String(stackedSets)}`; - console.info(`TN_TERRAIN_SPLAT ${material.userData.TN_TERRAIN_SPLAT as string}`); - return material; + const surfaces = new Map(); + try { + for (const url of new Set(targets.values())) + surfaces.set(url, await loadTerrainSplat({ ...options, url })); + } catch (error) { + for (const surface of surfaces.values()) surface.dispose(); + throw error; + } + const replaced = new Set(); + for (const [mesh, url] of targets) { + for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) + replaced.add(material); + mesh.material = surfaces.get(url) as Material; + } + for (const material of replaced) if (!retained.has(material)) material.dispose(); } diff --git a/packages/core/src/world-tile-observation.ts b/packages/core/src/world-tile-observation.ts new file mode 100644 index 0000000000..372b0aaa2e --- /dev/null +++ b/packages/core/src/world-tile-observation.ts @@ -0,0 +1,566 @@ +import type { + BufferAttribute, + BufferGeometry, + InterleavedBufferAttribute, + Matrix4, + Object3D, +} from "three"; + +import { Vector3 } from "three"; + +import type { ITerrainEdgeFrame } from "./terrain-jobs.js"; +import { edgeWorldPoint } from "./terrain-jobs.js"; +import type { + IEdgeSamples, + ILevelGeometry, + IResidentTile, + IStitchBridge, + NeighborPair, +} from "./world-tiles.js"; + +import type { Heightfield } from "./world.js"; + +export const BRIDGE_COORDINATE_EPSILON = 1e-4; + +export const BRIDGE_COORDINATE_RELATIVE_EPSILON = 2 ** -22; + +export const BRIDGE_STRIP_INDEX_PATTERN = [0, 1, 2, 2, 1, 3, 2, 1, 0, 3, 1, 2] as const; + +export type PositionAttribute = BufferAttribute | InterleavedBufferAttribute; + +export function bridgeCoordinateMatches(actual: number, expected: number): boolean { + return ( + Math.abs(actual - expected) <= + Math.max( + BRIDGE_COORDINATE_EPSILON, + Math.max(1, Math.abs(actual), Math.abs(expected)) * BRIDGE_COORDINATE_RELATIVE_EPSILON, + ) + ); +} + +/** One field's own rectangle and side, which is all a world point along an edge needs. */ +export function tileEdgeFrame(field: Heightfield, side: keyof IEdgeSamples): ITerrainEdgeFrame { + return { depth: field.depth, origin: field.origin, side, width: field.width }; +} + +function edgeVertexIndex(level: ILevelGeometry, side: keyof IEdgeSamples, index: number): number { + const row = side === "north" ? 0 : side === "south" ? level.resolution - 1 : index; + const column = side === "west" ? 0 : side === "east" ? level.resolution - 1 : index; + return row * level.resolution + column; +} + +export function opposingEdge( + a: IResidentTile, + b: IResidentTile, +): [keyof IEdgeSamples, keyof IEdgeSamples] { + if (a.tileX < b.tileX) return ["east", "west"]; + if (a.tileX > b.tileX) return ["west", "east"]; + if (a.tileZ < b.tileZ) return ["south", "north"]; + return ["north", "south"]; +} + +export function renderedLevel(tile: IResidentTile): ILevelGeometry | undefined { + return tile.levels.find(({ mesh }) => mesh.visible) ?? tile.levels[tile.lodLevel]; +} + +/** + * An attribute's vertex count while every component it draws is finite, `NaN` once one is not. + * Every writer this package owns goes through `needsUpdate`, which bumps `version`, so a version + * alone cannot see a writer that reached past the attribute — and `NaN` never compares equal to the + * finite state the last reconcile recorded, so a settled seam still fails closed on it. The scan is + * the cost of that, against the per-sample walk it keeps off a settled frame, so it is validation: + * with `validate` off the count alone is compared, and a writer that reached past `needsUpdate` + * leaves a geometry the next reconcile reads the same way it always did. + */ +function positionFingerprint(position: BufferAttribute | undefined, validate: boolean): number { + if (position === undefined) return -1; + if (!validate) return position.count; + const values = position.array; + for (let index = 0; index < values.length; index += 1) + if (!Number.isFinite(values[index])) return Number.NaN; + return position.count; +} + +/** + * What a pair's seam was reconciled against: each side's rendered level, its buffers and where its + * mesh sits. Every writer to a level sets `needsUpdate`, which bumps the version, so an unchanged + * state means neither facing edge can have moved since — and an edge is sampled through + * `level.mesh.matrixWorld`, so a level mesh moved from outside changed what the observation reads + * without touching a single version. Its own transform is what that matrix composes from, and it is + * what any outside writer moves: an ancestor of it is either the tiles group, whose own movement the + * bridge state already observes, or the LOD this package owns and never moves, and a seam exists + * only where a bridge is. Numbers, not a string, and pushed rather than returned: this runs for + * every resident pair every frame (544 pairs on a 289-tile ring) into a reused buffer, and the + * intermediate array per level and per bridge was itself the cost the settled ring was measured on. + */ +export function pushLevelState(state: number[], level: ILevelGeometry): void { + const geometry = level.geometry; + const { mesh } = level; + state.push( + geometry.id, + (geometry.getAttribute("position") as BufferAttribute).version, + (geometry.getAttribute("normal") as BufferAttribute).version, + mesh.position.x, + mesh.position.y, + mesh.position.z, + mesh.quaternion.x, + mesh.quaternion.y, + mesh.quaternion.z, + mesh.quaternion.w, + mesh.scale.x, + mesh.scale.y, + mesh.scale.z, + ); +} + +/** Whether both sides of a pair have a rendered level, which is what its state needs to exist. */ +export function pushPairState(state: number[], pair: NeighborPair): boolean { + const aLevel = renderedLevel(pair[0]); + const bLevel = renderedLevel(pair[1]); + if (aLevel === undefined || bLevel === undefined) return false; + pushLevelState(state, aLevel); + pushLevelState(state, bLevel); + return true; +} + +/** + * Everything a seam observation reads: the pair's state plus the bridge's placement, parent, draw + * range and buffers. A diagnostic exists to catch a bridge moved, detached, emptied or rewritten, + * so each of those has to change this state and force a fresh observation. + */ +export function pushBridgeState(state: number[], bridge: IStitchBridge, validate: boolean): void { + const { mesh } = bridge; + // The mesh's own geometry rather than the record's: a bridge retargeted to foreign geometry reads + // perfectly well from the record, and `bridgeCoverageContext` is the only thing that rejects it. + const geometry = mesh.geometry; + const position = geometry.getAttribute("position") as BufferAttribute | undefined; + const normal = geometry.getAttribute("normal") as BufferAttribute | undefined; + const index = geometry.getIndex(); + mesh.updateMatrixWorld(); + state.push(mesh.id, mesh.parent?.id ?? -1, mesh.visible ? 1 : 0); + const elements = mesh.matrixWorld.elements; + for (let element = 0; element < elements.length; element += 1) + state.push(elements[element] as number); + state.push( + geometry.id, + position?.version ?? -1, + positionFingerprint(position, validate), + normal?.id ?? -1, + index?.id ?? -1, + index?.version ?? -1, + geometry.drawRange.start, + geometry.drawRange.count, + ); +} + +export function seamState( + pair: NeighborPair, + bridge: IStitchBridge | undefined, + validate: boolean, +): number[] | undefined { + const state: number[] = []; + if (!pushPairState(state, pair)) return undefined; + if (bridge !== undefined) pushBridgeState(state, bridge, validate); + return state; +} + +function sameState(a: readonly number[] | undefined, b: readonly number[] | undefined): boolean { + if (a === undefined || b === undefined || a.length !== b.length) return false; + for (let index = 0; index < a.length; index += 1) if (a[index] !== b[index]) return false; + return true; +} + +function edgeVertexHeight(level: ILevelGeometry, side: keyof IEdgeSamples, index: number): number { + const value = level.geometry.getAttribute("position").getY(edgeVertexIndex(level, side, index)); + if (!Number.isFinite(value)) + throw new Error("TerrainTiles seam diagnostic edge height must be finite."); + return value; +} + +export function edgeWorldVertexHeight( + level: ILevelGeometry, + side: keyof IEdgeSamples, + index: number, + target: Vector3, +): number { + const position = level.geometry.getAttribute("position"); + const vertex = edgeVertexIndex(level, side, index); + const localX = position.getX(vertex); + const localY = position.getY(vertex); + const localZ = position.getZ(vertex); + if (!Number.isFinite(localX) || !Number.isFinite(localY) || !Number.isFinite(localZ)) + throw new Error("TerrainTiles seam diagnostic bridge coordinates must be finite."); + target.set(localX, localY, localZ).applyMatrix4(level.mesh.matrixWorld); + if (!Number.isFinite(target.x) || !Number.isFinite(target.y) || !Number.isFinite(target.z)) + throw new Error("TerrainTiles seam diagnostic bridge world coordinates must be finite."); + return target.y; +} + +export function edgeWorldHeight( + level: ILevelGeometry, + side: keyof IEdgeSamples, + normalized: number, + target: Vector3, +): number { + const position = Math.max(0, Math.min(1, normalized)) * (level.resolution - 1); + const lower = Math.floor(position); + const upper = Math.min(level.resolution - 1, lower + 1); + const mix = position - lower; + return ( + edgeWorldVertexHeight(level, side, lower, target) * (1 - mix) + + edgeWorldVertexHeight(level, side, upper, target) * mix + ); +} + +export function bridgeEndpointWorldHeight( + position: PositionAttribute, + matrixWorld: Matrix4, + vertex: number, + expectedX: number, + expectedY: number, + expectedZ: number, + worldPosition: Vector3, +): number { + const localX = position.getX(vertex); + const localY = position.getY(vertex); + const localZ = position.getZ(vertex); + if (!Number.isFinite(localX) || !Number.isFinite(localY) || !Number.isFinite(localZ)) + throw new Error("TerrainTiles seam diagnostic bridge coordinates must be finite."); + worldPosition.set(localX, localY, localZ).applyMatrix4(matrixWorld); + if ( + !Number.isFinite(worldPosition.x) || + !Number.isFinite(worldPosition.y) || + !Number.isFinite(worldPosition.z) + ) + throw new Error("TerrainTiles seam diagnostic bridge world coordinates must be finite."); + if ( + !Number.isFinite(expectedX) || + !Number.isFinite(expectedY) || + !Number.isFinite(expectedZ) || + !bridgeCoordinateMatches(worldPosition.x, expectedX) || + !bridgeCoordinateMatches(worldPosition.y, expectedY) || + !bridgeCoordinateMatches(worldPosition.z, expectedZ) + ) + throw new Error( + "TerrainTiles seam diagnostic bridge topology does not match current neighboring edges.", + ); + return worldPosition.y; +} + +export function bridgeEndpointHeight( + bridge: IStitchBridge, + position: PositionAttribute, + sample: number, + endpoint: number, + tile: IResidentTile, + side: keyof IEdgeSamples, + level: ILevelGeometry, + worldPosition: Vector3, + expectedWorldPosition: Vector3, +): number { + const sampleNormalized = sample / (bridge.resolution - 1); + const [expectedX, , expectedZ] = edgeWorldPoint( + tileEdgeFrame(tile.field, side), + sampleNormalized, + 0, + ); + const expectedY = edgeWorldHeight(level, side, sampleNormalized, expectedWorldPosition); + return bridgeEndpointWorldHeight( + position, + bridge.mesh.matrixWorld, + sample * 2 + endpoint, + expectedX, + expectedY, + expectedZ, + worldPosition, + ); +} + +export interface IBridgeEndpoint { + readonly level: ILevelGeometry; + readonly side: keyof IEdgeSamples; + readonly tile: IResidentTile; +} + +export function bridgeEndpointPair( + a: IResidentTile, + aSide: keyof IEdgeSamples, + b: IResidentTile, + bSide: keyof IEdgeSamples, +): readonly [IBridgeEndpoint, IBridgeEndpoint] { + const aLevel = renderedLevel(a); + const bLevel = renderedLevel(b); + if (aLevel === undefined || bLevel === undefined) + throw new Error( + "TerrainTiles seam diagnostic bridge topology has no rendered neighboring edge.", + ); + const aEndpoint = { level: aLevel, side: aSide, tile: a }; + const bEndpoint = { level: bLevel, side: bSide, tile: b }; + return aLevel.resolution > bLevel.resolution ? [aEndpoint, bEndpoint] : [bEndpoint, aEndpoint]; +} + +export function validateBridgeTriangleTopology( + geometry: BufferGeometry, + expectedVertexCount?: number, +): PositionAttribute { + const position = geometry.getAttribute("position"); + const index = geometry.getIndex(); + const drawRange = geometry.drawRange; + const positionArray = position?.array; + if ( + position === undefined || + position.itemSize !== 3 || + !Number.isInteger(position.count) || + position.count < 6 || + position.count % 2 !== 0 || + positionArray === undefined || + positionArray.length < position.count * position.itemSize || + (expectedVertexCount !== undefined && position.count !== expectedVertexCount) || + index === null || + index.itemSize !== 1 || + !Number.isInteger(index.count) || + index.array.length !== index.count || + !(index.array instanceof Uint16Array || index.array instanceof Uint32Array) || + !Number.isInteger(drawRange.start) || + drawRange.start !== 0 || + (drawRange.count !== Number.POSITIVE_INFINITY && + (!Number.isInteger(drawRange.count) || + drawRange.count < index.count || + drawRange.count % 3 !== 0)) + ) + throw new Error( + "TerrainTiles seam diagnostic bridge topology has invalid rendered triangle data.", + ); + const vertexCount = position.count; + const stripResolution = vertexCount / 2; + const expectedIndexCount = (stripResolution - 1) * BRIDGE_STRIP_INDEX_PATTERN.length; + if (index.count !== expectedIndexCount) + throw new Error( + "TerrainTiles seam diagnostic bridge topology has invalid rendered triangle data.", + ); + for (let vertex = 0; vertex < vertexCount; vertex += 1) { + if ( + !Number.isFinite(position.getX(vertex)) || + !Number.isFinite(position.getY(vertex)) || + !Number.isFinite(position.getZ(vertex)) + ) + throw new Error("TerrainTiles seam diagnostic bridge coordinates must be finite."); + } + for (let offset = 0; offset < index.count; offset += 1) { + const segment = Math.floor(offset / BRIDGE_STRIP_INDEX_PATTERN.length); + const pair = segment * 2; + const expected = + pair + (BRIDGE_STRIP_INDEX_PATTERN[offset % BRIDGE_STRIP_INDEX_PATTERN.length] as number); + const vertex = index.getX(offset); + if (vertex !== expected) + throw new Error("TerrainTiles seam diagnostic bridge topology has invalid index data."); + } + return position; +} + +export function seamCoverageDepth(a: IResidentTile, b: IResidentTile): number { + const aLevel = a.levels[a.lodLevel]; + const bLevel = b.levels[b.lodLevel]; + if (aLevel === undefined || bLevel === undefined) return 0; + return Math.min(aLevel.skirtDepth, bLevel.skirtDepth); +} + +/** + * One edge of one level, addressed in the position buffer directly. + * + * The seam diagnostic reads two edges once per sample of every pair, four times a frame, so the + * per-read cost is the per-frame cost: resolving the attribute and calling `getY` per vertex was + * ~40% of a settled 289-tile frame. The Y of edge vertex `index` sits at `first + index * span` + * in the interleaved array, and stepping an edge by one vertex steps the array by `span`. + */ +export interface IEdgeWalk { + readonly first: number; + readonly heights: Float32Array; + readonly resolution: number; + readonly span: number; +} + +export function edgeWalk(level: ILevelGeometry, side: keyof IEdgeSamples): IEdgeWalk { + const position = level.geometry.getAttribute("position"); + const array = position.array as Float32Array; + const last = level.resolution - 1; + const first = + side === "north" ? 0 : side === "south" ? last * level.resolution : side === "west" ? 0 : last; + return { + first: first * 3 + 1, + heights: array, + resolution: level.resolution, + span: (side === "north" || side === "south" ? 1 : level.resolution) * 3, + }; +} + +export function edgeWalkHeight(walk: IEdgeWalk, index: number): number { + const lower = walk.heights[walk.first + index * walk.span] as number; + if (!Number.isFinite(lower)) + throw new Error("TerrainTiles seam diagnostic edge height must be finite."); + return lower; +} + +/** The walk's height at `normalized`, interpolating exactly as `edgeHeight` does. */ +export function edgeWalkAt(walk: IEdgeWalk, normalized: number): number { + const position = normalized * (walk.resolution - 1); + const lower = Math.floor(position); + const mix = position - lower; + const lowerValue = edgeWalkHeight(walk, lower); + if (mix === 0) return lowerValue; + return ( + lowerValue * (1 - mix) + edgeWalkHeight(walk, Math.min(walk.resolution - 1, lower + 1)) * mix + ); +} + +/** A pair's bridge, validated and placed once so the per-sample loop only measures. */ +export interface IBridgeCoverage { + readonly bridge: IStitchBridge; + readonly coarser: IBridgeEndpoint; + readonly finer: IBridgeEndpoint; + readonly position: PositionAttribute; +} + +export function bridgeCoverageContext( + bridge: IStitchBridge, + a: IResidentTile, + aSide: keyof IEdgeSamples, + b: IResidentTile, + bSide: keyof IEdgeSamples, +): IBridgeCoverage { + if (bridge.mesh.geometry !== bridge.geometry) + throw new Error("TerrainTiles seam diagnostic bridge geometry is not attached to its mesh."); + if (!Number.isInteger(bridge.resolution) || bridge.resolution < 2) + throw new Error("TerrainTiles seam diagnostic bridge resolution is invalid."); + if ( + bridge.keys[0] !== (a.key < b.key ? a.key : b.key) || + bridge.keys[1] !== (a.key < b.key ? b.key : a.key) + ) + throw new Error("TerrainTiles seam diagnostic bridge topology does not match its tile pair."); + const position = validateBridgeTriangleTopology(bridge.mesh.geometry, bridge.resolution * 2); + if (position.count !== bridge.resolution * 2) + throw new Error("TerrainTiles seam diagnostic bridge topology has invalid coverage."); + const [finer, coarser] = bridgeEndpointPair(a, aSide, b, bSide); + // Nothing inside the sample loop moves these, so deriving them per sample only repeated work: + // three world-matrix updates and a full revalidation of the bridge's own topology for every + // edge vertex of every pair, four times a frame. + bridge.mesh.updateWorldMatrix(true, false); + finer.level.mesh.updateWorldMatrix(true, false); + coarser.level.mesh.updateWorldMatrix(true, false); + return { bridge, coarser, finer, position }; +} + +export function bridgeCoverageAt( + coverage: IBridgeCoverage, + normalized: number, + worldPosition: Vector3, + expectedWorldPosition: Vector3, +): number { + if (!Number.isFinite(normalized)) + throw new Error("TerrainTiles seam diagnostic bridge sample coordinate must be finite."); + const { bridge, coarser, finer, position } = coverage; + const samplePosition = Math.max(0, Math.min(1, normalized)) * (bridge.resolution - 1); + const lower = Math.floor(samplePosition); + const upper = Math.min(bridge.resolution - 1, lower + 1); + const mix = samplePosition - lower; + const fine = + bridgeEndpointHeight( + bridge, + position, + lower, + 0, + finer.tile, + finer.side, + finer.level, + worldPosition, + expectedWorldPosition, + ) * + (1 - mix) + + bridgeEndpointHeight( + bridge, + position, + upper, + 0, + finer.tile, + finer.side, + finer.level, + worldPosition, + expectedWorldPosition, + ) * + mix; + const coarse = + bridgeEndpointHeight( + bridge, + position, + lower, + 1, + coarser.tile, + coarser.side, + coarser.level, + worldPosition, + expectedWorldPosition, + ) * + (1 - mix) + + bridgeEndpointHeight( + bridge, + position, + upper, + 1, + coarser.tile, + coarser.side, + coarser.level, + worldPosition, + expectedWorldPosition, + ) * + mix; + const covered = Math.abs(fine - coarse); + if (!Number.isFinite(covered)) + throw new Error("TerrainTiles seam diagnostic bridge coverage must be finite."); + return covered; +} + +export function seamObservation( + a: IResidentTile, + b: IResidentTile, + bridge: IStitchBridge | undefined, + owner: Object3D, + includeBridgeCoverage: boolean, +): { gap: number; visualGap: number } { + const [aSide, bSide] = opposingEdge(a, b); + const aLevel = renderedLevel(a); + const bLevel = renderedLevel(b); + if (aLevel === undefined || bLevel === undefined) + throw new Error("TerrainTiles seam diagnostic observation has no rendered level."); + const samples = Math.max(aLevel.resolution, bLevel.resolution); + const aWalk = edgeWalk(aLevel, aSide); + const bWalk = edgeWalk(bLevel, bSide); + const coverage = + includeBridgeCoverage && + bridge !== undefined && + bridge.mesh.parent === owner && + bridge.mesh.visible + ? bridgeCoverageContext(bridge, a, aSide, b, bSide) + : undefined; + const skirt = seamCoverageDepth(a, b); + const worldPosition = new Vector3(); + const expectedWorldPosition = new Vector3(); + let gap = 0; + let visualGap = 0; + for (let index = 0; index < samples; index += 1) { + const normalized = samples === 1 ? 0 : index / (samples - 1); + const currentGap = Math.abs(edgeWalkAt(aWalk, normalized) - edgeWalkAt(bWalk, normalized)); + if (!Number.isFinite(currentGap)) + throw new Error("TerrainTiles seam diagnostic observation must be finite."); + gap = Math.max(gap, currentGap); + const covered = + coverage === undefined + ? 0 + : bridgeCoverageAt(coverage, normalized, worldPosition, expectedWorldPosition); + visualGap = Math.max(visualGap, Math.max(0, currentGap - Math.max(skirt, covered))); + } + if (!Number.isFinite(visualGap)) + throw new Error("TerrainTiles visual seam diagnostic observation must be finite."); + return { gap, visualGap }; +} + +export { edgeVertexIndex, positionFingerprint, sameState, edgeVertexHeight }; diff --git a/packages/core/src/world-tiles.ts b/packages/core/src/world-tiles.ts index cf099f14e1..cca8ea8ec5 100644 --- a/packages/core/src/world-tiles.ts +++ b/packages/core/src/world-tiles.ts @@ -1,3 +1,17 @@ +import { + edgeVertexHeight, + edgeVertexIndex, + opposingEdge, + pushBridgeState, + pushLevelState, + renderedLevel, + sameState, + seamObservation, + seamState, + tileEdgeFrame, + validateBridgeTriangleTopology, +} from "./world-tile-observation.js"; + import { Box3, BufferAttribute, @@ -162,7 +176,7 @@ export interface IWorldTilesOptions { readonly topologyObservation?: IWorldTilesTopologyObservation; } -interface ILevelGeometry { +export interface ILevelGeometry { surfaceError: number; readonly edgeSamples: IEdgeSamples; geometry: BufferGeometry; @@ -172,14 +186,14 @@ interface ILevelGeometry { readonly skirtVertexCount: number; } -interface IEdgeSamples { +export interface IEdgeSamples { readonly east: Float32Array; readonly north: Float32Array; readonly south: Float32Array; readonly west: Float32Array; } -interface IResidentTile extends Omit { +export interface IResidentTile extends Omit { readonly assetKey?: string; collider: IWorldTileCollider | undefined; readonly levels: readonly ILevelGeometry[]; @@ -199,7 +213,7 @@ interface ILodTransition { remainingFrames: number; } -interface IStitchBridge { +export interface IStitchBridge { readonly keys: readonly [string, string]; geometry: BufferGeometry; readonly mesh: Mesh; @@ -249,7 +263,6 @@ const CONSTRUCTION_CHUNK_SAMPLES = 256; const TERRAIN_MERGE_BLOCK = 4; const TERRAIN_MERGE_FLAG = "TN_TERRAIN_MERGE"; const TERRAIN_TILE_MARKER_MS = 5_000; -const BRIDGE_COORDINATE_EPSILON = 1e-4; /** * Whether this launch asked for terrain tile merging to be turned off. @@ -290,19 +303,6 @@ function blockCoordinates(key: string): { lod: number; blockX: number; blockZ: n blockZ: Number(key.slice(comma + 1)), }; } -const BRIDGE_COORDINATE_RELATIVE_EPSILON = 2 ** -22; -const BRIDGE_STRIP_INDEX_PATTERN = [0, 1, 2, 2, 1, 3, 2, 1, 0, 3, 1, 2] as const; -type PositionAttribute = BufferAttribute | InterleavedBufferAttribute; - -function bridgeCoordinateMatches(actual: number, expected: number): boolean { - return ( - Math.abs(actual - expected) <= - Math.max( - BRIDGE_COORDINATE_EPSILON, - Math.max(1, Math.abs(actual), Math.abs(expected)) * BRIDGE_COORDINATE_RELATIVE_EPSILON, - ) - ); -} class EmptyCollider implements IWorldTileCollider { #disposed = false; @@ -469,11 +469,6 @@ function edgeSamplesFor(values: readonly number[], resolution: number): IEdgeSam return { east, north, south, west }; } -/** One field's own rectangle and side, which is all a world point along an edge needs. */ -function tileEdgeFrame(field: Heightfield, side: keyof IEdgeSamples): ITerrainEdgeFrame { - return { depth: field.depth, origin: field.origin, side, width: field.width }; -} - /** * Apply a job's result, whenever it arrives. An inline host resolves inside the call, so the caller * keeps the synchronous shape it always had; a worker host applies in its reply. A reply that throws @@ -504,12 +499,6 @@ function timedSpan(id: SpanId, run: () => T): T { } } -function edgeVertexIndex(level: ILevelGeometry, side: keyof IEdgeSamples, index: number): number { - const row = side === "north" ? 0 : side === "south" ? level.resolution - 1 : index; - const column = side === "west" ? 0 : side === "east" ? level.resolution - 1 : index; - return row * level.resolution + column; -} - function appendQuad( indices: number[], topLeft: number, @@ -764,256 +753,6 @@ function inspectSkirtGeometry( return { depth, vertexCount }; } -function opposingEdge( - a: IResidentTile, - b: IResidentTile, -): [keyof IEdgeSamples, keyof IEdgeSamples] { - if (a.tileX < b.tileX) return ["east", "west"]; - if (a.tileX > b.tileX) return ["west", "east"]; - if (a.tileZ < b.tileZ) return ["south", "north"]; - return ["north", "south"]; -} - -function renderedLevel(tile: IResidentTile): ILevelGeometry | undefined { - return tile.levels.find(({ mesh }) => mesh.visible) ?? tile.levels[tile.lodLevel]; -} - -/** - * An attribute's vertex count while every component it draws is finite, `NaN` once one is not. - * Every writer this package owns goes through `needsUpdate`, which bumps `version`, so a version - * alone cannot see a writer that reached past the attribute — and `NaN` never compares equal to the - * finite state the last reconcile recorded, so a settled seam still fails closed on it. The scan is - * the cost of that, against the per-sample walk it keeps off a settled frame, so it is validation: - * with `validate` off the count alone is compared, and a writer that reached past `needsUpdate` - * leaves a geometry the next reconcile reads the same way it always did. - */ -function positionFingerprint(position: BufferAttribute | undefined, validate: boolean): number { - if (position === undefined) return -1; - if (!validate) return position.count; - const values = position.array; - for (let index = 0; index < values.length; index += 1) - if (!Number.isFinite(values[index])) return Number.NaN; - return position.count; -} - -/** - * What a pair's seam was reconciled against: each side's rendered level, its buffers and where its - * mesh sits. Every writer to a level sets `needsUpdate`, which bumps the version, so an unchanged - * state means neither facing edge can have moved since — and an edge is sampled through - * `level.mesh.matrixWorld`, so a level mesh moved from outside changed what the observation reads - * without touching a single version. Its own transform is what that matrix composes from, and it is - * what any outside writer moves: an ancestor of it is either the tiles group, whose own movement the - * bridge state already observes, or the LOD this package owns and never moves, and a seam exists - * only where a bridge is. Numbers, not a string, and pushed rather than returned: this runs for - * every resident pair every frame (544 pairs on a 289-tile ring) into a reused buffer, and the - * intermediate array per level and per bridge was itself the cost the settled ring was measured on. - */ -function pushLevelState(state: number[], level: ILevelGeometry): void { - const geometry = level.geometry; - const { mesh } = level; - state.push( - geometry.id, - (geometry.getAttribute("position") as BufferAttribute).version, - (geometry.getAttribute("normal") as BufferAttribute).version, - mesh.position.x, - mesh.position.y, - mesh.position.z, - mesh.quaternion.x, - mesh.quaternion.y, - mesh.quaternion.z, - mesh.quaternion.w, - mesh.scale.x, - mesh.scale.y, - mesh.scale.z, - ); -} - -/** Whether both sides of a pair have a rendered level, which is what its state needs to exist. */ -function pushPairState(state: number[], pair: NeighborPair): boolean { - const aLevel = renderedLevel(pair[0]); - const bLevel = renderedLevel(pair[1]); - if (aLevel === undefined || bLevel === undefined) return false; - pushLevelState(state, aLevel); - pushLevelState(state, bLevel); - return true; -} - -/** - * Everything a seam observation reads: the pair's state plus the bridge's placement, parent, draw - * range and buffers. A diagnostic exists to catch a bridge moved, detached, emptied or rewritten, - * so each of those has to change this state and force a fresh observation. - */ -function pushBridgeState(state: number[], bridge: IStitchBridge, validate: boolean): void { - const { mesh } = bridge; - // The mesh's own geometry rather than the record's: a bridge retargeted to foreign geometry reads - // perfectly well from the record, and `bridgeCoverageContext` is the only thing that rejects it. - const geometry = mesh.geometry; - const position = geometry.getAttribute("position") as BufferAttribute | undefined; - const normal = geometry.getAttribute("normal") as BufferAttribute | undefined; - const index = geometry.getIndex(); - mesh.updateMatrixWorld(); - state.push(mesh.id, mesh.parent?.id ?? -1, mesh.visible ? 1 : 0); - const elements = mesh.matrixWorld.elements; - for (let element = 0; element < elements.length; element += 1) - state.push(elements[element] as number); - state.push( - geometry.id, - position?.version ?? -1, - positionFingerprint(position, validate), - normal?.id ?? -1, - index?.id ?? -1, - index?.version ?? -1, - geometry.drawRange.start, - geometry.drawRange.count, - ); -} - -function seamState( - pair: NeighborPair, - bridge: IStitchBridge | undefined, - validate: boolean, -): number[] | undefined { - const state: number[] = []; - if (!pushPairState(state, pair)) return undefined; - if (bridge !== undefined) pushBridgeState(state, bridge, validate); - return state; -} - -function sameState(a: readonly number[] | undefined, b: readonly number[] | undefined): boolean { - if (a === undefined || b === undefined || a.length !== b.length) return false; - for (let index = 0; index < a.length; index += 1) if (a[index] !== b[index]) return false; - return true; -} - -function edgeVertexHeight(level: ILevelGeometry, side: keyof IEdgeSamples, index: number): number { - const value = level.geometry.getAttribute("position").getY(edgeVertexIndex(level, side, index)); - if (!Number.isFinite(value)) - throw new Error("TerrainTiles seam diagnostic edge height must be finite."); - return value; -} - -function edgeWorldVertexHeight( - level: ILevelGeometry, - side: keyof IEdgeSamples, - index: number, - target: Vector3, -): number { - const position = level.geometry.getAttribute("position"); - const vertex = edgeVertexIndex(level, side, index); - const localX = position.getX(vertex); - const localY = position.getY(vertex); - const localZ = position.getZ(vertex); - if (!Number.isFinite(localX) || !Number.isFinite(localY) || !Number.isFinite(localZ)) - throw new Error("TerrainTiles seam diagnostic bridge coordinates must be finite."); - target.set(localX, localY, localZ).applyMatrix4(level.mesh.matrixWorld); - if (!Number.isFinite(target.x) || !Number.isFinite(target.y) || !Number.isFinite(target.z)) - throw new Error("TerrainTiles seam diagnostic bridge world coordinates must be finite."); - return target.y; -} - -function edgeWorldHeight( - level: ILevelGeometry, - side: keyof IEdgeSamples, - normalized: number, - target: Vector3, -): number { - const position = Math.max(0, Math.min(1, normalized)) * (level.resolution - 1); - const lower = Math.floor(position); - const upper = Math.min(level.resolution - 1, lower + 1); - const mix = position - lower; - return ( - edgeWorldVertexHeight(level, side, lower, target) * (1 - mix) + - edgeWorldVertexHeight(level, side, upper, target) * mix - ); -} - -function bridgeEndpointWorldHeight( - position: PositionAttribute, - matrixWorld: Matrix4, - vertex: number, - expectedX: number, - expectedY: number, - expectedZ: number, - worldPosition: Vector3, -): number { - const localX = position.getX(vertex); - const localY = position.getY(vertex); - const localZ = position.getZ(vertex); - if (!Number.isFinite(localX) || !Number.isFinite(localY) || !Number.isFinite(localZ)) - throw new Error("TerrainTiles seam diagnostic bridge coordinates must be finite."); - worldPosition.set(localX, localY, localZ).applyMatrix4(matrixWorld); - if ( - !Number.isFinite(worldPosition.x) || - !Number.isFinite(worldPosition.y) || - !Number.isFinite(worldPosition.z) - ) - throw new Error("TerrainTiles seam diagnostic bridge world coordinates must be finite."); - if ( - !Number.isFinite(expectedX) || - !Number.isFinite(expectedY) || - !Number.isFinite(expectedZ) || - !bridgeCoordinateMatches(worldPosition.x, expectedX) || - !bridgeCoordinateMatches(worldPosition.y, expectedY) || - !bridgeCoordinateMatches(worldPosition.z, expectedZ) - ) - throw new Error( - "TerrainTiles seam diagnostic bridge topology does not match current neighboring edges.", - ); - return worldPosition.y; -} - -function bridgeEndpointHeight( - bridge: IStitchBridge, - position: PositionAttribute, - sample: number, - endpoint: number, - tile: IResidentTile, - side: keyof IEdgeSamples, - level: ILevelGeometry, - worldPosition: Vector3, - expectedWorldPosition: Vector3, -): number { - const sampleNormalized = sample / (bridge.resolution - 1); - const [expectedX, , expectedZ] = edgeWorldPoint( - tileEdgeFrame(tile.field, side), - sampleNormalized, - 0, - ); - const expectedY = edgeWorldHeight(level, side, sampleNormalized, expectedWorldPosition); - return bridgeEndpointWorldHeight( - position, - bridge.mesh.matrixWorld, - sample * 2 + endpoint, - expectedX, - expectedY, - expectedZ, - worldPosition, - ); -} - -interface IBridgeEndpoint { - readonly level: ILevelGeometry; - readonly side: keyof IEdgeSamples; - readonly tile: IResidentTile; -} - -function bridgeEndpointPair( - a: IResidentTile, - aSide: keyof IEdgeSamples, - b: IResidentTile, - bSide: keyof IEdgeSamples, -): readonly [IBridgeEndpoint, IBridgeEndpoint] { - const aLevel = renderedLevel(a); - const bLevel = renderedLevel(b); - if (aLevel === undefined || bLevel === undefined) - throw new Error( - "TerrainTiles seam diagnostic bridge topology has no rendered neighboring edge.", - ); - const aEndpoint = { level: aLevel, side: aSide, tile: a }; - const bEndpoint = { level: bLevel, side: bSide, tile: b }; - return aLevel.resolution > bLevel.resolution ? [aEndpoint, bEndpoint] : [bEndpoint, aEndpoint]; -} - function refreshEdgeSamples(level: ILevelGeometry): void { for (const side of ["east", "north", "south", "west"] as const) { const samples = level.edgeSamples[side]; @@ -1117,65 +856,6 @@ function geometryBytes(geometry: BufferGeometry): number { ); } -function validateBridgeTriangleTopology( - geometry: BufferGeometry, - expectedVertexCount?: number, -): PositionAttribute { - const position = geometry.getAttribute("position"); - const index = geometry.getIndex(); - const drawRange = geometry.drawRange; - const positionArray = position?.array; - if ( - position === undefined || - position.itemSize !== 3 || - !Number.isInteger(position.count) || - position.count < 6 || - position.count % 2 !== 0 || - positionArray === undefined || - positionArray.length < position.count * position.itemSize || - (expectedVertexCount !== undefined && position.count !== expectedVertexCount) || - index === null || - index.itemSize !== 1 || - !Number.isInteger(index.count) || - index.array.length !== index.count || - !(index.array instanceof Uint16Array || index.array instanceof Uint32Array) || - !Number.isInteger(drawRange.start) || - drawRange.start !== 0 || - (drawRange.count !== Number.POSITIVE_INFINITY && - (!Number.isInteger(drawRange.count) || - drawRange.count < index.count || - drawRange.count % 3 !== 0)) - ) - throw new Error( - "TerrainTiles seam diagnostic bridge topology has invalid rendered triangle data.", - ); - const vertexCount = position.count; - const stripResolution = vertexCount / 2; - const expectedIndexCount = (stripResolution - 1) * BRIDGE_STRIP_INDEX_PATTERN.length; - if (index.count !== expectedIndexCount) - throw new Error( - "TerrainTiles seam diagnostic bridge topology has invalid rendered triangle data.", - ); - for (let vertex = 0; vertex < vertexCount; vertex += 1) { - if ( - !Number.isFinite(position.getX(vertex)) || - !Number.isFinite(position.getY(vertex)) || - !Number.isFinite(position.getZ(vertex)) - ) - throw new Error("TerrainTiles seam diagnostic bridge coordinates must be finite."); - } - for (let offset = 0; offset < index.count; offset += 1) { - const segment = Math.floor(offset / BRIDGE_STRIP_INDEX_PATTERN.length); - const pair = segment * 2; - const expected = - pair + (BRIDGE_STRIP_INDEX_PATTERN[offset % BRIDGE_STRIP_INDEX_PATTERN.length] as number); - const vertex = index.getX(offset); - if (vertex !== expected) - throw new Error("TerrainTiles seam diagnostic bridge topology has invalid index data."); - } - return position; -} - function updateStitchBridge(bridge: IStitchBridge, data: ITerrainBridgeAttributes): void { const position = validateBridgeTriangleTopology(bridge.geometry, bridge.resolution * 2); if (bridge.resolution !== data.positions.length / 6) { @@ -1623,212 +1303,7 @@ function* coarsestSelectableLevel( return coarsest; } -function seamCoverageDepth(a: IResidentTile, b: IResidentTile): number { - const aLevel = a.levels[a.lodLevel]; - const bLevel = b.levels[b.lodLevel]; - if (aLevel === undefined || bLevel === undefined) return 0; - return Math.min(aLevel.skirtDepth, bLevel.skirtDepth); -} - -/** - * One edge of one level, addressed in the position buffer directly. - * - * The seam diagnostic reads two edges once per sample of every pair, four times a frame, so the - * per-read cost is the per-frame cost: resolving the attribute and calling `getY` per vertex was - * ~40% of a settled 289-tile frame. The Y of edge vertex `index` sits at `first + index * span` - * in the interleaved array, and stepping an edge by one vertex steps the array by `span`. - */ -interface IEdgeWalk { - readonly first: number; - readonly heights: Float32Array; - readonly resolution: number; - readonly span: number; -} - -function edgeWalk(level: ILevelGeometry, side: keyof IEdgeSamples): IEdgeWalk { - const position = level.geometry.getAttribute("position"); - const array = position.array as Float32Array; - const last = level.resolution - 1; - const first = - side === "north" ? 0 : side === "south" ? last * level.resolution : side === "west" ? 0 : last; - return { - first: first * 3 + 1, - heights: array, - resolution: level.resolution, - span: (side === "north" || side === "south" ? 1 : level.resolution) * 3, - }; -} - -function edgeWalkHeight(walk: IEdgeWalk, index: number): number { - const lower = walk.heights[walk.first + index * walk.span] as number; - if (!Number.isFinite(lower)) - throw new Error("TerrainTiles seam diagnostic edge height must be finite."); - return lower; -} - -/** The walk's height at `normalized`, interpolating exactly as `edgeHeight` does. */ -function edgeWalkAt(walk: IEdgeWalk, normalized: number): number { - const position = normalized * (walk.resolution - 1); - const lower = Math.floor(position); - const mix = position - lower; - const lowerValue = edgeWalkHeight(walk, lower); - if (mix === 0) return lowerValue; - return ( - lowerValue * (1 - mix) + edgeWalkHeight(walk, Math.min(walk.resolution - 1, lower + 1)) * mix - ); -} - -/** A pair's bridge, validated and placed once so the per-sample loop only measures. */ -interface IBridgeCoverage { - readonly bridge: IStitchBridge; - readonly coarser: IBridgeEndpoint; - readonly finer: IBridgeEndpoint; - readonly position: PositionAttribute; -} - -function bridgeCoverageContext( - bridge: IStitchBridge, - a: IResidentTile, - aSide: keyof IEdgeSamples, - b: IResidentTile, - bSide: keyof IEdgeSamples, -): IBridgeCoverage { - if (bridge.mesh.geometry !== bridge.geometry) - throw new Error("TerrainTiles seam diagnostic bridge geometry is not attached to its mesh."); - if (!Number.isInteger(bridge.resolution) || bridge.resolution < 2) - throw new Error("TerrainTiles seam diagnostic bridge resolution is invalid."); - if ( - bridge.keys[0] !== (a.key < b.key ? a.key : b.key) || - bridge.keys[1] !== (a.key < b.key ? b.key : a.key) - ) - throw new Error("TerrainTiles seam diagnostic bridge topology does not match its tile pair."); - const position = validateBridgeTriangleTopology(bridge.mesh.geometry, bridge.resolution * 2); - if (position.count !== bridge.resolution * 2) - throw new Error("TerrainTiles seam diagnostic bridge topology has invalid coverage."); - const [finer, coarser] = bridgeEndpointPair(a, aSide, b, bSide); - // Nothing inside the sample loop moves these, so deriving them per sample only repeated work: - // three world-matrix updates and a full revalidation of the bridge's own topology for every - // edge vertex of every pair, four times a frame. - bridge.mesh.updateWorldMatrix(true, false); - finer.level.mesh.updateWorldMatrix(true, false); - coarser.level.mesh.updateWorldMatrix(true, false); - return { bridge, coarser, finer, position }; -} - -function bridgeCoverageAt( - coverage: IBridgeCoverage, - normalized: number, - worldPosition: Vector3, - expectedWorldPosition: Vector3, -): number { - if (!Number.isFinite(normalized)) - throw new Error("TerrainTiles seam diagnostic bridge sample coordinate must be finite."); - const { bridge, coarser, finer, position } = coverage; - const samplePosition = Math.max(0, Math.min(1, normalized)) * (bridge.resolution - 1); - const lower = Math.floor(samplePosition); - const upper = Math.min(bridge.resolution - 1, lower + 1); - const mix = samplePosition - lower; - const fine = - bridgeEndpointHeight( - bridge, - position, - lower, - 0, - finer.tile, - finer.side, - finer.level, - worldPosition, - expectedWorldPosition, - ) * - (1 - mix) + - bridgeEndpointHeight( - bridge, - position, - upper, - 0, - finer.tile, - finer.side, - finer.level, - worldPosition, - expectedWorldPosition, - ) * - mix; - const coarse = - bridgeEndpointHeight( - bridge, - position, - lower, - 1, - coarser.tile, - coarser.side, - coarser.level, - worldPosition, - expectedWorldPosition, - ) * - (1 - mix) + - bridgeEndpointHeight( - bridge, - position, - upper, - 1, - coarser.tile, - coarser.side, - coarser.level, - worldPosition, - expectedWorldPosition, - ) * - mix; - const covered = Math.abs(fine - coarse); - if (!Number.isFinite(covered)) - throw new Error("TerrainTiles seam diagnostic bridge coverage must be finite."); - return covered; -} - -function seamObservation( - a: IResidentTile, - b: IResidentTile, - bridge: IStitchBridge | undefined, - owner: Object3D, - includeBridgeCoverage: boolean, -): { gap: number; visualGap: number } { - const [aSide, bSide] = opposingEdge(a, b); - const aLevel = renderedLevel(a); - const bLevel = renderedLevel(b); - if (aLevel === undefined || bLevel === undefined) - throw new Error("TerrainTiles seam diagnostic observation has no rendered level."); - const samples = Math.max(aLevel.resolution, bLevel.resolution); - const aWalk = edgeWalk(aLevel, aSide); - const bWalk = edgeWalk(bLevel, bSide); - const coverage = - includeBridgeCoverage && - bridge !== undefined && - bridge.mesh.parent === owner && - bridge.mesh.visible - ? bridgeCoverageContext(bridge, a, aSide, b, bSide) - : undefined; - const skirt = seamCoverageDepth(a, b); - const worldPosition = new Vector3(); - const expectedWorldPosition = new Vector3(); - let gap = 0; - let visualGap = 0; - for (let index = 0; index < samples; index += 1) { - const normalized = samples === 1 ? 0 : index / (samples - 1); - const currentGap = Math.abs(edgeWalkAt(aWalk, normalized) - edgeWalkAt(bWalk, normalized)); - if (!Number.isFinite(currentGap)) - throw new Error("TerrainTiles seam diagnostic observation must be finite."); - gap = Math.max(gap, currentGap); - const covered = - coverage === undefined - ? 0 - : bridgeCoverageAt(coverage, normalized, worldPosition, expectedWorldPosition); - visualGap = Math.max(visualGap, Math.max(0, currentGap - Math.max(skirt, covered))); - } - if (!Number.isFinite(visualGap)) - throw new Error("TerrainTiles visual seam diagnostic observation must be finite."); - return { gap, visualGap }; -} - -type NeighborPair = readonly [IResidentTile, IResidentTile]; +export type NeighborPair = readonly [IResidentTile, IResidentTile]; /** * One pair's bridge, as the seam job carries it: the two edges it is made of, and what the bridge it diff --git a/packages/core/src/world.ts b/packages/core/src/world.ts index 20a3acfde0..299ef4b63f 100644 --- a/packages/core/src/world.ts +++ b/packages/core/src/world.ts @@ -747,13 +747,16 @@ export type { } from "./snow-field.js"; export { heightSamplerFromHeightmap, loadWorldHeightmap } from "./world-heightmap.js"; -export { loadTerrainSplat } from "./world-terrain-splat.js"; +export { applyTerrainSurface, loadTerrainSplat } from "./world-terrain-splat.js"; export type { ILoadTerrainSplatOptions, ITerrainSplatLayer, ITerrainSplatMaskedLayer, ITerrainSplatTable, ITerrainSplatWeightContext, + ITerrainSplatSampleContext, + ITerrainSplatSample, + ITerrainSplatMaterialContext, } from "./world-terrain-splat.js"; export { WorldCells } from "./world-cells.js"; diff --git a/packages/core/three-patch/pin.json b/packages/core/three-patch/pin.json new file mode 100644 index 0000000000..2708823332 --- /dev/null +++ b/packages/core/three-patch/pin.json @@ -0,0 +1,363 @@ +{ + "version": "0.185.1", + "release": "r185", + "commit": "2431a09f46f34c560bc8e44b33be0e567723d5b9", + "packageJsonSha256": "1d5c438acdc1fe5a52fb8ede2e417afd91fec91d22275c40f7b739591b24e4df", + "packageLockSha256": "ad871796b84315cc243f63d6885e2fe397c0db3a40de3b2fcca66bd23afbf9da", + "archiveSha256": "904bfa6979764988420b09720e21bbdadb5eb4077812155789830360af9ead4c", + "frozenCompatPairs": [ + { + "path": "build/three.webgpu.js", + "oldHash": "148869ee7f18c62570ac1eafb7df5775e5a89581", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "5c5ef3c06885c724c1c2288295e7e68483ee1f6e", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "7aa62adb9e78ba8f0a67709d3141bf93c67419cc", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "bdffb9b4069f7beafa85913b155e92d3e83f12d0", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "efd0e16719cc7bbef1fbc18c241948e25eb85881", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "1245f0da9bb6a0f731b8c75a64490d7c91238700", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "1b3a6e96291af0077f7f247040e657cf4aa0cb33", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "3d47c1151d563f1e0ae2bb4b92edd057fe0df2b3", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "81c24b2e05e5047e1a02410a2056949d39b1a05c", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "b8a7b2fd0a2e95911e7e12fd5674592107f012de", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/nodes/accessors/Instance.js", + "oldHash": "ed00e75ba4fd7b49343cfada26eacdef8940a4f4", + "newHash": "18e2b7943393fd07b7989b1e3a5231a61ca24187" + }, + { + "path": "src/nodes/accessors/Skinning.js", + "oldHash": "aaeea633a8ece291be5faf868e312867ddad01b6", + "newHash": "76303e765bc29853cfd7f139dd3ec76545b70798" + }, + { + "path": "src/renderers/common/Renderer.js", + "oldHash": "89b33efb80ca1dcb9db931071dfda62ffdba3526", + "newHash": "b1293bfbe2ccf0583711813c625ddd5c5ba78620" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "69b8b67fdeec5be24de34d8382beda9020642f77", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUAttributeUtils.js", + "oldHash": "83bbe189915c8450c4e14108e6a3f4ed98c4b016", + "newHash": "16bd72d0c0f1d693481efc98140a0f1b73905149" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUTextureUtils.js", + "oldHash": "ff8ee2dbace2816105aa7152891c04f2333f33e8", + "newHash": "57c1eb62f1e0de063f22e08ec879e31d2a3240b6" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "9166d6f2dd1ac1505fa27b625f1976dff6b8ebc1", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "9e7c7f74be1191b761711bd70ee59cc033e586cb", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/nodes/accessors/TextureNode.js", + "oldHash": "f886ed21397ab39d6fdbc58e5ac57729f1871f8a", + "newHash": "1a138b1a055d0867e7686531930d4d557e1a20df" + }, + { + "path": "src/nodes/accessors/TextureSizeNode.js", + "oldHash": "d4668281657ef8b1acad761e0d86a0716909eee4", + "newHash": "43eccaa01bcb12ea6e02c94232d5ee76bcac0d01" + }, + { + "path": "src/nodes/display/PassNode.js", + "oldHash": "cc827a55cb82a6325296dcc5c55a4246f4f70aed", + "newHash": "3cdf66cfe0760066bc062bf7ce3f3a8000eb2b0d" + }, + { + "path": "src/renderers/common/Renderer.js", + "oldHash": "51590d5a23a93365e5cd9fb0360678750270d5bf", + "newHash": "b1293bfbe2ccf0583711813c625ddd5c5ba78620" + }, + { + "path": "src/renderers/webgpu/nodes/WGSLNodeBuilder.js", + "oldHash": "2acc832a628f43adac49614dad8e593ef058c398", + "newHash": "1cbc9b3e02c9832d69a8aadb3a6003392b443c8d" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUBindingUtils.js", + "oldHash": "a5da7ca09f746c0b2859ef173df663ccdc851ebc", + "newHash": "c479d0a88794b01bca531e7cbfb780cf93fc4721" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "941ec68ef0b8174a633054043edb94d9d3d58f5e", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "57f69c17352b936a9eca118a9801805c95c401b6", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "ef853fd6f7382a8c7ff66cda43471344aeb20770", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "e7956e565579aa6ec550ca001336d5a0d76c652a", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/renderers/common/Renderer.js", + "oldHash": "730ce0ccd0f56d70c4a7abe7c514c5077d544d1e", + "newHash": "b1293bfbe2ccf0583711813c625ddd5c5ba78620" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "927e3a1ad5a4665d77238ffbab4c57a603b86828", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUTextureUtils.js", + "oldHash": "c46f6ab0e06d93f124c9fc62ece8859529461d5e", + "newHash": "57c1eb62f1e0de063f22e08ec879e31d2a3240b6" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "e594543d1e604de1ac9f45c6260e213ad0e2d7b5", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "6581a8b0e69dea0ce5bfdc644f75a40f6aaac963", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "00ff55f538873dc1b342d899073a8f07401a8824", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "25918de19dcb52504e13e7ecd3adbd64e8b64d65", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "9b290e496a0951d1af111052bcfe12c97dad4762", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "f9ea33ac39db20e96822fda6b90e2bed02817435", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "e10b2c19633d2690b5dd852319c935735de06d30", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "93cedce745291edfbd333fb76d8154b8eff685bd", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/nodes/accessors/Instance.js", + "oldHash": "3ff041a260cde0e5fb1880daddd4fc700a1c945e", + "newHash": "18e2b7943393fd07b7989b1e3a5231a61ca24187" + }, + { + "path": "src/renderers/common/RenderObject.js", + "oldHash": "4f8d58950b80cd56bbec22a61cf39a7aae5bd731", + "newHash": "a3dcba2f1fb0df19f3ddb3039a97ae408e2938b3" + }, + { + "path": "src/renderers/common/RenderObjects.js", + "oldHash": "91fba38f3bafa35806bd3c82d1c79671a4a6b00d", + "newHash": "72569d0339314d26eb7a35ff2a8462a803c852a6" + }, + { + "path": "src/renderers/common/RenderPipeline.js", + "oldHash": "94e54740e13088a9fa40ccfd30000232df7c2eea", + "newHash": "ab3085cfdca456ffb3de6945d7e7f693e4ed33ae" + }, + { + "path": "src/renderers/common/Renderer.js", + "oldHash": "bc495cb324ed9923008fa5685aede53aee44b6d8", + "newHash": "b1293bfbe2ccf0583711813c625ddd5c5ba78620" + }, + { + "path": "src/renderers/common/Textures.js", + "oldHash": "be47645f5662a2be8002c23de8ff4092bf561049", + "newHash": "5b305173fa4a2814ec74563d2ae5cdc70e7505e1" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "7ebe3404830ecea2e304baa368f04bfa474ff0e1", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "add7f3284d411fd3eb188c2568775595b85204f5", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUAttributeUtils.js", + "oldHash": "ded852d9df7e709c62c9fc3a6109c31737650d97", + "newHash": "16bd72d0c0f1d693481efc98140a0f1b73905149" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUBindingUtils.js", + "oldHash": "7ff28660e2ea3e72eb545c5bbe95bd8d8435a58e", + "newHash": "c479d0a88794b01bca531e7cbfb780cf93fc4721" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUPipelineUtils.js", + "oldHash": "d5872652cc969dbea62c69944044a9eeff1f3ac3", + "newHash": "3480fea1e86f1f237cb6ee2ea87b5826d4fc404d" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUTextureUtils.js", + "oldHash": "03998e6e9067462c50bd402ffda5b29d24634be8", + "newHash": "57c1eb62f1e0de063f22e08ec879e31d2a3240b6" + }, + { + "path": "src/renderers/webgpu/utils/WebGPUTextureUtils.js", + "oldHash": "f6d510b59151e02c46928689b2be99486c1dfcd8", + "newHash": "57c1eb62f1e0de063f22e08ec879e31d2a3240b6" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "5975e257a43fe139b69c249d8c8c1edfe91e12a0", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "b86e40b357823cf3919d9fdf3c2554f14bcd9931", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "ee394e98d798be749776cce1fa3e043fb9951769", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "a0fab1c7c37cfaf270d9ce36dd3d91453ae6a44c", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "94b48d64ed8f2e57f00cb5fd5bd0112da085aeae", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "2a70eb319b4235c958c7b5b1ede148c808d0b4b2", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "bfa242835dfef49019dd368e3593a62a221dda3d", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "5dbb9bf2ed43fa055c0f7dd2859c146485825817", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + }, + { + "path": "build/three.webgpu.js", + "oldHash": "7e34d05be672a7fa2e38b19c035f744a8b486446", + "newHash": "a308a892c59283b3b9a71477b3b02d64db40becd" + }, + { + "path": "build/three.webgpu.nodes.js", + "oldHash": "4b7a3baea4fc2a99f9453211d2807131f8a86dd4", + "newHash": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb" + }, + { + "path": "src/renderers/common/Renderer.js", + "oldHash": "769c909b0a63708dea7d22efc5379c39bdf57d92", + "newHash": "b1293bfbe2ccf0583711813c625ddd5c5ba78620" + }, + { + "path": "src/renderers/webgpu/WebGPUBackend.js", + "oldHash": "0fbcc307d39cbf6d74d9dbc5f199f2b0e4f58b33", + "newHash": "5636b40e6708a490e926e5f590675834d23bf326" + } + ], + "baseFinalHashes": { + "build/three.cjs": "b0f917241f1f2cd25baa21dec20edda32176ef6a", + "build/three.core.js": "868a634b4294be5f32664912a50813d55d6dd6e7", + "build/three.webgpu.js": "a308a892c59283b3b9a71477b3b02d64db40becd", + "build/three.webgpu.nodes.js": "95669d28b54250a3857dcb7d1b1b0e30dd19e0cb", + "src/materials/nodes/manager/NodeMaterialObserver.js": "7e2cb9926878b43aa2b8b3a146e3427711be7210", + "src/nodes/accessors/Batch.js": "2cf4bb6039853d5c922e89041419250b243694ef", + "src/nodes/accessors/Instance.js": "18e2b7943393fd07b7989b1e3a5231a61ca24187", + "src/nodes/accessors/Skinning.js": "76303e765bc29853cfd7f139dd3ec76545b70798", + "src/nodes/accessors/TextureNode.js": "1a138b1a055d0867e7686531930d4d557e1a20df", + "src/nodes/accessors/TextureSizeNode.js": "43eccaa01bcb12ea6e02c94232d5ee76bcac0d01", + "src/nodes/accessors/VelocityNode.js": "c48002abbce20ee8b29908cf314d7b03afe0db03", + "src/nodes/core/NodeBuilder.js": "1990da8fe8f1f29a7b53030756dfd966d8de3cb6", + "src/nodes/display/PassNode.js": "3cdf66cfe0760066bc062bf7ce3f3a8000eb2b0d", + "src/nodes/lighting/PointShadowNode.js": "f9019a72890a30fded9f7ce76982722c2f7d6a3f", + "src/nodes/lighting/ShadowFilterNode.js": "fe8b50c8f76d0b6ce3d2fa1452751b2469a821e4", + "src/objects/BatchedMesh.js": "35cc0ffe83a69ea9fa5fca6dec2d820ca193f88d", + "src/renderers/common/Bindings.js": "a3cec4ed844ab88f870b46714c56c941e0cc7a8e", + "src/renderers/common/Geometries.js": "979da2ffc4d0dcda6fdd72747a7e7125e3f8a3a3", + "src/renderers/common/RenderContexts.js": "7319e25e280452b69c6f0c4c2f8c93c955ff9be3", + "src/renderers/common/RenderObject.js": "a3dcba2f1fb0df19f3ddb3039a97ae408e2938b3", + "src/renderers/common/RenderObjects.js": "72569d0339314d26eb7a35ff2a8462a803c852a6", + "src/renderers/common/Renderer.js": "b1293bfbe2ccf0583711813c625ddd5c5ba78620", + "src/renderers/common/Textures.js": "5b305173fa4a2814ec74563d2ae5cdc70e7505e1", + "src/renderers/common/nodes/NodeManager.js": "033128c396c2cad5c5d1d362f2d261e9adbd6bdc", + "src/renderers/webgpu/WebGPUBackend.js": "5636b40e6708a490e926e5f590675834d23bf326", + "src/renderers/webgpu/nodes/WGSLNodeBuilder.js": "1cbc9b3e02c9832d69a8aadb3a6003392b443c8d", + "src/renderers/webgpu/utils/WebGPUAttributeUtils.js": "16bd72d0c0f1d693481efc98140a0f1b73905149", + "src/renderers/webgpu/utils/WebGPUBindingUtils.js": "c479d0a88794b01bca531e7cbfb780cf93fc4721", + "src/renderers/webgpu/utils/WebGPUPipelineUtils.js": "3480fea1e86f1f237cb6ee2ea87b5826d4fc404d", + "src/renderers/webgpu/utils/WebGPUTextureUtils.js": "57c1eb62f1e0de063f22e08ec879e31d2a3240b6", + "src/renderers/common/RenderPipeline.js": "ab3085cfdca456ffb3de6945d7e7f693e4ed33ae" + } +} diff --git a/packages/core/three-patch/source-delta.patch b/packages/core/three-patch/source-delta.patch new file mode 100644 index 0000000000..d09271eea4 --- /dev/null +++ b/packages/core/three-patch/source-delta.patch @@ -0,0 +1,2233 @@ +diff --git a/src/materials/nodes/manager/NodeMaterialObserver.js b/src/materials/nodes/manager/NodeMaterialObserver.js +index 1f4c2546c..7e2cb9926 100644 +--- a/src/materials/nodes/manager/NodeMaterialObserver.js ++++ b/src/materials/nodes/manager/NodeMaterialObserver.js +@@ -133,6 +133,15 @@ class NodeMaterialObserver { + */ + this.renderId = 0; + ++ /** ++ * Whether the current render produces motion vectors. Objects keep refreshing ++ * fully while this is the case. ++ * ++ * @type {boolean} ++ * @default false ++ */ ++ this.renderingVelocity = false; ++ + } + + /** +@@ -232,6 +241,88 @@ class NodeMaterialObserver { + + } + ++ /** ++ * Returns `true` if the given render object is a static object whose per-object state - world ++ * matrix, material, geometry and the scene environment - did not change since the last ++ * refresh. A settled object is still drawn every frame and its FRAME and RENDER nodes still ++ * have to be updated; what it may skip is the work whose inputs it froze: its OBJECT node ++ * updates, its object uniform group upload and its per-draw attribute scan. ++ * ++ * @param {RenderObject} renderObject - The render object. ++ * @return {boolean} Whether the given render object is settled or not. ++ */ ++ isSettled( renderObject ) { ++ ++ const data = this.renderObjects.get( renderObject ); ++ ++ return data !== undefined && data.settledState === true; ++ ++ } ++ ++ /** ++ * Recomputes {@link NodeMaterialObserver#isSettled} for the given render object, once per draw, ++ * so every step of that draw reads the same answer. ++ * ++ * @param {RenderObject} renderObject - The render object. ++ * @return {boolean} Whether the given render object is settled or not. ++ */ ++ updateSettled( renderObject ) { ++ ++ const { object, material, geometry, scene } = renderObject; ++ const data = this.getRenderObjectData( renderObject ); ++ ++ if ( object.static !== true || this.renderingVelocity === true ) { ++ ++ data.settledState = false; ++ ++ return false; ++ ++ } ++ ++ let settled = data.settled; ++ ++ if ( settled === undefined ) { ++ ++ data.settled = settled = { ++ matrix: object.matrixWorld.clone(), ++ environmentRotation: scene.environmentRotation.clone(), ++ environmentIntensity: scene.environmentIntensity, ++ materialVersion: material.version, ++ geometryVersion: geometry.version ++ }; ++ ++ data.settledState = false; ++ ++ return false; ++ ++ } ++ ++ if ( settled.matrix.equals( object.matrixWorld ) !== true || ++ settled.environmentRotation.equals( scene.environmentRotation ) !== true || ++ settled.environmentIntensity !== scene.environmentIntensity || ++ settled.materialVersion !== material.version || ++ settled.geometryVersion !== geometry.version ) { ++ ++ // re-arm with the current state, so the object settles again once it stops changing ++ ++ settled.matrix.copy( object.matrixWorld ); ++ settled.environmentRotation.copy( scene.environmentRotation ); ++ settled.environmentIntensity = scene.environmentIntensity; ++ settled.materialVersion = material.version; ++ settled.geometryVersion = geometry.version; ++ ++ data.settledState = false; ++ ++ return false; ++ ++ } ++ ++ data.settledState = true; ++ ++ return true; ++ ++ } ++ + /** + * Returns an attribute data structure holding the attributes versions for + * monitoring. +@@ -298,7 +389,6 @@ class NodeMaterialObserver { + + data = { + _renderId: - 1, +- _equal: false, + + attributes: this.getAttributesData( geometry.attributes ), + indexId: geometry.index ? geometry.index.id : null, +@@ -327,7 +417,7 @@ class NodeMaterialObserver { + + if ( data === undefined ) { + +- data = { _renderId: - 1, _equal: false }; ++ data = { _renderId: - 1 }; + + for ( const property of this.refreshUniforms ) { + +@@ -411,7 +501,6 @@ class NodeMaterialObserver { + + value.copy( mtlValue ); + +- materialData._equal = false; + return false; + + } +@@ -423,7 +512,6 @@ class NodeMaterialObserver { + value.id = mtlValue.id; + value.version = mtlValue.version; + +- materialData._equal = false; + return false; + + } +@@ -432,7 +520,6 @@ class NodeMaterialObserver { + + materialData[ property ] = mtlValue; + +- materialData._equal = false; + return false; + + } +@@ -448,18 +535,12 @@ class NodeMaterialObserver { + renderObjectData.bufferWidth = width; + renderObjectData.bufferHeight = height; + +- materialData._equal = false; + return false; + + } + + } + +- materialData._equal = true; +- +- } else { +- +- if ( materialData._equal === false ) return false; + + } + +@@ -502,7 +583,6 @@ class NodeMaterialObserver { + // attribute was removed + delete storedAttributes[ name ]; + +- geometryData._equal = false; + return false; + + } +@@ -515,7 +595,6 @@ class NodeMaterialObserver { + storedAttributeData.id = id; + storedAttributeData.version = version; + +- geometryData._equal = false; + return false; + + } +@@ -526,7 +605,6 @@ class NodeMaterialObserver { + + geometryData.attributes = this.getAttributesData( attributes ); + +- geometryData._equal = false; + return false; + + } +@@ -544,7 +622,6 @@ class NodeMaterialObserver { + geometryData.indexId = currentIndexId; + geometryData.indexVersion = currentIndexVersion; + +- geometryData._equal = false; + return false; + + } +@@ -556,16 +633,10 @@ class NodeMaterialObserver { + geometryData.drawRange.start = geometry.drawRange.start; + geometryData.drawRange.count = geometry.drawRange.count; + +- geometryData._equal = false; + return false; + + } + +- geometryData._equal = true; +- +- } else { +- +- if ( geometryData._equal === false ) return false; + + } + +@@ -716,7 +787,14 @@ class NodeMaterialObserver { + */ + needsRefresh( renderObject, nodeFrame ) { + +- if ( this.hasNode || this.hasAnimation || this.firstInitialization( renderObject ) || this.needsVelocity( nodeFrame.renderer ) ) ++ this.renderingVelocity = this.needsVelocity( nodeFrame.renderer ); ++ ++ // Answered for the whole draw, so updateBefore, the geometry scan, the node updates and ++ // the object uniform upload of this object all skip or all refresh. ++ ++ this.updateSettled( renderObject ); ++ ++ if ( this.hasNode || this.hasAnimation || this.firstInitialization( renderObject ) || this.renderingVelocity ) + return true; + + const { renderId } = nodeFrame; +@@ -729,10 +807,14 @@ class NodeMaterialObserver { + + } + +- const isStatic = renderObject.object.static === true; + const isBundle = renderObject.bundle !== null && renderObject.bundle.static === true && this.getRenderObjectData( renderObject ).version === renderObject.bundle.version; + +- if ( isStatic || isBundle ) ++ // `object.static` is not the question - it is a switch, and a brand new object has it set ++ // before it has been drawn once, so answering from it skips the draw that creates this ++ // object's bind groups and leaves the pipeline reading a layout that was never built. A ++ // static object that moved, or whose material or geometry was edited, has to refresh like ++ // any other; only a settled one may skip, and nothing is settled before its first refresh. ++ if ( this.isSettled( renderObject ) === true || isBundle ) + return false; + + const lightsData = this.getLights( renderObject.lightsNode, renderId ); +diff --git a/src/nodes/accessors/Batch.js b/src/nodes/accessors/Batch.js +index ab62e98da..2cf4bb603 100644 +--- a/src/nodes/accessors/Batch.js ++++ b/src/nodes/accessors/Batch.js +@@ -1,6 +1,6 @@ + + import { normalLocal } from './Normal.js'; +-import { positionLocal } from './Position.js'; ++import { positionLocal, positionPrevious } from './Position.js'; + import { vec3, mat3, mat4, int, ivec2, float, Fn } from '../tsl/TSLBase.js'; + import { textureLoad } from './TextureNode.js'; + import { textureSize } from './TextureSizeNode.js'; +@@ -64,6 +64,7 @@ export const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => { + const indirectId = getIndirectIndex( batchMesh._indirectTexture, int( batchingIdNode ) ); + + const matricesTexture = batchMesh._matricesTexture; ++ const previousMatricesTexture = batchMesh._previousMatricesTexture ?? matricesTexture; + + const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst(); + const j = float( indirectId ).mul( 4 ).toInt().toConst(); +@@ -90,6 +91,19 @@ export const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => { + const bm = mat3( batchingMatrix ); + + positionLocal.assign( batchingMatrix.mul( positionLocal ) ); ++ if ( builder.needsPreviousData() ) { ++ ++ const previousBatchingMatrix = mat4( ++ textureLoad( previousMatricesTexture, ivec2( x, y ) ), ++ textureLoad( previousMatricesTexture, ivec2( x.add( 1 ), y ) ), ++ textureLoad( previousMatricesTexture, ivec2( x.add( 2 ), y ) ), ++ textureLoad( previousMatricesTexture, ivec2( x.add( 3 ), y ) ) ++ ); ++ ++ positionPrevious.assign( previousBatchingMatrix.mul( positionPrevious ).xyz ); ++ ++ } ++ + + const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) ); + +@@ -104,5 +118,3 @@ export const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => { + } + + }, 'void' ); +- +- +diff --git a/src/nodes/accessors/Instance.js b/src/nodes/accessors/Instance.js +index b89834b91..3ff041a26 100644 +--- a/src/nodes/accessors/Instance.js ++++ b/src/nodes/accessors/Instance.js +@@ -1,6 +1,6 @@ + + import { vec3, mat4, Fn } from '../tsl/TSLBase.js'; +-import { OnFrameUpdate, OnObjectUpdate } from '../utils/EventNode.js'; ++import { OnBeforeFrameUpdate, OnBeforeObjectUpdate, OnObjectUpdate } from '../utils/EventNode.js'; + import { normalLocal, transformNormal } from './Normal.js'; + import { positionLocal, positionPrevious } from './Position.js'; + import { varyingProperty } from '../core/PropertyNode.js'; +@@ -40,7 +40,8 @@ function createInstanceMatrixNode( builder, instanceMatrix ) { + + const uniformBufferSize = matrixCount * 16 * 4; + +- if ( uniformBufferSize <= builder.getUniformBufferLimit() ) { ++ // threenative: the uniform path re-uploads every draw; the attribute path uploads on version change ++ if ( false ) { + + instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', matrixCount ).element( instanceIndex ); + +@@ -134,7 +135,8 @@ export const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder + + const uniformBufferSize = Math.max( matrices.count, 1 ) * 16 * 4; + +- if ( uniformBufferSize > builder.getUniformBufferLimit() ) { ++ // threenative: the uniform path re-uploads every draw; the attribute path uploads on version change ++ if ( true ) { + + interleavedMatrix = _matrixBuffers.get( matrices ); + +@@ -175,7 +177,7 @@ export const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder + // Synchronization of dynamic buffer updates per frame + if ( interleavedMatrix !== null || interleavedColor !== null ) { + +- OnFrameUpdate( () => { ++ OnBeforeFrameUpdate( () => { + + if ( interleavedMatrix !== null ) { + +@@ -216,11 +218,32 @@ export const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder + + const instancedMesh = builder.object; + +- OnObjectUpdate( ( { object } ) => { ++ const updatePrevious = ( object, array ) => { ++ ++ const history = _previousInstanceMatrices.get( object ); ++ if ( array !== matrices.array && history.lastSource === array && history.lastVersion === matrices.version ) return; ++ const { previousInstanceMatrix } = history; ++ previousInstanceMatrix.array.set( array ); ++ history.lastSource = array; ++ history.lastVersion = matrices.version; ++ previousInstanceMatrix.needsUpdate = true; ++ const interleaved = _matrixBuffers.get( previousInstanceMatrix ); ++ if ( interleaved !== undefined ) interleaved.needsUpdate = true; ++ ++ }; ++ ++ // Scheduled history must reach the GPU in this draw, before the geometry uploads. ++ OnBeforeObjectUpdate( ( { object } ) => { + +- const previousInstanceData = _previousInstanceMatrices.get( object ); ++ const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ]; ++ if ( scheduledPrevious !== undefined ) updatePrevious( object, scheduledPrevious ); ++ ++ } ); ++ ++ // Without a scheduler, retain Three's draw-time snapshot for the next draw. ++ OnObjectUpdate( ( { object } ) => { + +- previousInstanceData.previousInstanceMatrix.array.set( matrices.array ); ++ if ( object[ Symbol.for( 'threenative.velocity.previousInstanceMatrices' ) ] === undefined ) updatePrevious( object, matrices.array ); + + } ); + +diff --git a/src/nodes/accessors/Skinning.js b/src/nodes/accessors/Skinning.js +index 2356aed86..76303e765 100644 +--- a/src/nodes/accessors/Skinning.js ++++ b/src/nodes/accessors/Skinning.js +@@ -117,7 +117,15 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve + + } + +- return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); ++ // The previous bones and world matrix must use the same frame's bind inverse. ++ const previousBindMatrixInverse = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ).onObjectUpdate( ( { object } ) => { ++ ++ const mesh = object?.isSkinnedMesh === true ? object : skinnedMesh; ++ return mesh[ Symbol.for( 'threenative.velocity.previousBindMatrixInverse' ) ] ?? mesh.bindMatrixInverse; ++ ++ } ); ++ ++ return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, previousBindMatrixInverse, skinIndexNode, skinWeightNode ); + + } + +@@ -149,7 +157,8 @@ export const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => { + + if ( skeletonData !== undefined ) { + +- skeletonData.previousBoneMatrices.set( skeleton.boneMatrices ); ++ const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousBoneMatrices' ) ]; ++ skeletonData.previousBoneMatrices.set( scheduledPrevious ?? skeleton.boneMatrices ); + + } + +@@ -216,7 +225,8 @@ export const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = n + + if ( state !== undefined ) { + +- state.previousBoneMatrices.set( skeleton.boneMatrices ); ++ const scheduledPrevious = skinnedMesh[ Symbol.for( 'threenative.velocity.previousBoneMatrices' ) ]; ++ state.previousBoneMatrices.set( scheduledPrevious ?? skeleton.boneMatrices ); + + } + +diff --git a/src/nodes/accessors/TextureNode.js b/src/nodes/accessors/TextureNode.js +index f886ed213..1a138b1a0 100644 +--- a/src/nodes/accessors/TextureNode.js ++++ b/src/nodes/accessors/TextureNode.js +@@ -524,7 +524,7 @@ class TextureNode extends UniformNode { + + } else if ( this.sampler === false ) { + +- snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ); ++ snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet, this.isRawTextureNode === true ); + + } else if ( levelSnippet ) { + +diff --git a/src/nodes/accessors/TextureSizeNode.js b/src/nodes/accessors/TextureSizeNode.js +index d46682816..43eccaa01 100644 +--- a/src/nodes/accessors/TextureSizeNode.js ++++ b/src/nodes/accessors/TextureSizeNode.js +@@ -57,7 +57,8 @@ class TextureSizeNode extends Node { + const textureProperty = this.textureNode.build( builder, 'property' ); + const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); + +- return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); ++ const mip = this.textureNode.isRawTextureNode === true ? '' : `, ${ level }`; ++ return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }${ mip } )`, this.getNodeType( builder ), output ); + + } + +diff --git a/src/nodes/accessors/VelocityNode.js b/src/nodes/accessors/VelocityNode.js +index 373548b0b..c48002abb 100644 +--- a/src/nodes/accessors/VelocityNode.js ++++ b/src/nodes/accessors/VelocityNode.js +@@ -106,7 +106,8 @@ class VelocityNode extends TempNode { + + const previousModelMatrix = getPreviousMatrix( object ); + +- this.previousModelWorldMatrix.value.copy( previousModelMatrix ); ++ const scheduledPrevious = object[ Symbol.for( 'threenative.velocity.previousWorldMatrix' ) ]; ++ this.previousModelWorldMatrix.value.copy( scheduledPrevious ?? previousModelMatrix ); + + // + +diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js +index be00b81ef..1990da8fe 100644 +--- a/src/nodes/core/NodeBuilder.js ++++ b/src/nodes/core/NodeBuilder.js +@@ -3450,7 +3450,7 @@ class NodeBuilder { + + const mrt = this.renderer.getMRT(); + +- return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( this.object ).useVelocity === true; ++ return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( this.object ).useVelocity === true || this.object.userData?.useVelocity === true; + + } + +diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js +index cc827a55c..3cdf66cfe 100644 +--- a/src/nodes/display/PassNode.js ++++ b/src/nodes/display/PassNode.js +@@ -95,7 +95,7 @@ class PassMultipleTextureNode extends PassTextureNode { + * @param {string} textureName - The output texture name. + * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. + */ +- constructor( passNode, textureName, previousTexture = false ) { ++ constructor( passNode, textureName, previousTexture = false, rawTexture = false ) { + + // null is passed to the super call since this class does not + // use an external texture for rendering pass data into. Instead +@@ -116,6 +116,8 @@ class PassMultipleTextureNode extends PassTextureNode { + * @type {boolean} + */ + this.previousTexture = previousTexture; ++ this.isRawTextureNode = rawTexture; ++ if ( rawTexture ) this.setSampler( false ); + + /** + * This flag can be used for type testing. +@@ -137,17 +139,33 @@ class PassMultipleTextureNode extends PassTextureNode { + + } + ++ getUniformHash( builder ) { ++ ++ const hash = super.getUniformHash( builder ); ++ return this.isRawTextureNode ? `${ this.passNode.uuid }:${ this.textureName }:raw4:${ hash }` : hash; ++ ++ } ++ + setup( builder ) { + + this.updateTexture(); + ++ if ( this.isRawTextureNode ) { ++ ++ if ( this.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); ++ if ( this.passNode.renderTarget.samples !== 4 ) throw new Error( 'Raw pass attachments require four-sample multisampling.' ); ++ if ( this.sampler || this.biasNode || this.compareNode || this.gradNode || this.gatherNode || this.depthNode ) throw new Error( 'Raw pass attachments support integer loads only.' ); ++ if ( builder.renderer.backend.isWebGPUBackend !== true ) throw new Error( 'Raw pass attachments require WebGPU.' ); ++ ++ } ++ + return super.setup( builder ); + + } + + clone() { + +- const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); ++ const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture, this.isRawTextureNode ); + newNode.uvNode = this.uvNode; + newNode.levelNode = this.levelNode; + newNode.biasNode = this.biasNode; +@@ -328,6 +346,8 @@ class PassNode extends TempNode { + * @type {Object} + */ + this._textureNodes = {}; ++ this._rawTextureNodes = {}; ++ this._rawTexturesDisposed = false; + + /** + * A dictionary holding the internal depth nodes. +@@ -634,6 +654,7 @@ class PassNode extends TempNode { + this._previousTextures[ name ] = texture; + + this._textureNodes[ name ].updateTexture(); ++ if ( this._rawTextureNodes[ name ] ) this._rawTextureNodes[ name ].updateTexture(); + this._previousTextureNodes[ name ].updateTexture(); + + } +@@ -646,6 +667,17 @@ class PassNode extends TempNode { + * @param {string} [name='output'] - The output name to get the texture node for. + * @return {TextureNode} The texture node. + */ ++ getRawTextureNode( name = 'output' ) { ++ ++ if ( this._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); ++ if ( this.renderTarget.samples !== 4 || this.renderTarget.depth !== 1 ) throw new Error( 'Raw pass attachments require four-sample 2D multisampling.' ); ++ let node = this._rawTextureNodes[ name ]; ++ if ( node === undefined ) node = this._rawTextureNodes[ name ] = new PassMultipleTextureNode( this, name, false, true ); ++ node.updateTexture(); ++ return node; ++ ++ } ++ + getTextureNode( name = 'output' ) { + + let textureNode = this._textureNodes[ name ]; +@@ -988,6 +1020,7 @@ class PassNode extends TempNode { + */ + dispose() { + ++ this._rawTexturesDisposed = true; + this.renderTarget.dispose(); + + } +diff --git a/src/nodes/lighting/PointShadowNode.js b/src/nodes/lighting/PointShadowNode.js +index 01d6909fe..f9019a728 100644 +--- a/src/nodes/lighting/PointShadowNode.js ++++ b/src/nodes/lighting/PointShadowNode.js +@@ -2,6 +2,7 @@ import ShadowNode from './ShadowNode.js'; + import { uniform } from '../core/UniformNode.js'; + import { float, vec3, If, Fn } from '../tsl/TSLBase.js'; + import { reference } from '../accessors/ReferenceNode.js'; ++import { getShadowUniform } from './ShadowFilterNode.js'; + import { cubeTexture } from '../accessors/CubeTextureNode.js'; + import { renderGroup } from '../core/UniformGroupNode.js'; + import { Matrix4 } from '../../math/Matrix4.js'; +@@ -65,8 +66,8 @@ export const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, + */ + export const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, shadow } ) => { + +- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); +- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); ++ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); ++ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); + + const texelSize = radius.div( mapSize.x ); + +@@ -102,9 +103,9 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo + const shadowPositionAbs = shadowPosition.abs().toConst(); + const viewZ = shadowPositionAbs.x.max( shadowPositionAbs.y ).max( shadowPositionAbs.z ); + +- const shadowCameraNear = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); +- const shadowCameraFar = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); +- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); ++ const shadowCameraNear = getShadowUniform( shadow, 'cameraNear', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ) ); ++ const shadowCameraFar = getShadowUniform( shadow, 'cameraFar', () => uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ) ); ++ const bias = getShadowUniform( shadow, 'bias', () => reference( 'bias', 'float', shadow ).setGroup( renderGroup ) ); + + const result = float( 1.0 ).toVar(); + +diff --git a/src/nodes/lighting/ShadowFilterNode.js b/src/nodes/lighting/ShadowFilterNode.js +index 56dde4c38..fe8b50c8f 100644 +--- a/src/nodes/lighting/ShadowFilterNode.js ++++ b/src/nodes/lighting/ShadowFilterNode.js +@@ -11,6 +11,46 @@ import { NoBlending } from '../../constants.js'; + + const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); + ++const _shadowUniforms = /*@__PURE__*/ new WeakMap(); ++ ++/** ++ * Returns the uniform `create` makes for this shadow and name, and makes it only once. ++ * ++ * A filter body runs once per node builder. A uniform that the body makes directly is a new node ++ * for each material, and a new render-group node gives each material its own copy of the shared ++ * render group: a buffer that each material compares and writes on each render call. One node per ++ * shadow lets every material that reads it share one render-group buffer. ++ * ++ * @private ++ * @param {LightShadow} shadow - The shadow that owns the uniform. ++ * @param {string} name - The uniform's name within that shadow. ++ * @param {Function} create - Makes the uniform the first time. ++ * @return {Node} The uniform node. ++ */ ++export const getShadowUniform = ( shadow, name, create ) => { ++ ++ let uniforms = _shadowUniforms.get( shadow ); ++ ++ if ( uniforms === undefined ) { ++ ++ uniforms = new Map(); ++ _shadowUniforms.set( shadow, uniforms ); ++ ++ } ++ ++ let node = uniforms.get( name ); ++ ++ if ( node === undefined ) { ++ ++ node = create(); ++ uniforms.set( name, node ); ++ ++ } ++ ++ return node; ++ ++}; ++ + /** + * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map + * with a binary `[0,1]` result. +@@ -65,8 +105,8 @@ export const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, + + }; + +- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); +- const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); ++ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); ++ const radius = getShadowUniform( shadow, 'radius', () => reference( 'radius', 'float', shadow ).setGroup( renderGroup ) ); + + const texelSize = vec2( 1 ).div( mapSize ); + const radiusScaled = radius.mul( texelSize.x ); +@@ -97,7 +137,7 @@ export const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, + */ + export const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { + +- const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); ++ const mapSize = getShadowUniform( shadow, 'mapSize', () => reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ) ); + + const texelSize = vec2( 1 ).div( mapSize ); + +diff --git a/src/objects/BatchedMesh.js b/src/objects/BatchedMesh.js +index 2ef7cb257..35cc0ffe8 100644 +--- a/src/objects/BatchedMesh.js ++++ b/src/objects/BatchedMesh.js +@@ -181,6 +181,8 @@ function copyArrayContents( src, target ) { + */ + class BatchedMesh extends Mesh { + ++ static threeNativeBatchedVelocityPatch = true; ++ + /** + * Constructs a new batched mesh. + * +diff --git a/src/renderers/common/Bindings.js b/src/renderers/common/Bindings.js +index 5f3d213a0..a3cec4ed8 100644 +--- a/src/renderers/common/Bindings.js ++++ b/src/renderers/common/Bindings.js +@@ -145,7 +145,7 @@ class Bindings extends DataMap { + */ + updateForRender( renderObject ) { + +- this._updateBindings( this.getForRender( renderObject ) ); ++ this._updateBindings( this.getForRender( renderObject ), this.nodes.isSettled( renderObject ) ); + + } + +@@ -206,10 +206,14 @@ class Bindings extends DataMap { + + } else if ( binding.isSampledTexture ) { + ++ // Async node compilation can yield while a viewport node changes targets. ++ // Refresh its reference before creating the first GPU binding, not after. ++ binding.update(); + this.textures.updateTexture( binding.texture ); + + } else if ( binding.isSampler ) { + ++ binding.update(); + this.textures.updateSampler( binding ); + + } else if ( binding.isStorageBuffer ) { +@@ -292,12 +296,13 @@ class Bindings extends DataMap { + * Updates the given array of bindings. + * + * @param {Array} bindings - The bind groups. ++ * @param {boolean} [settled] - Whether the render object is a settled static object. + */ +- _updateBindings( bindings ) { ++ _updateBindings( bindings, settled ) { + + for ( const bindGroup of bindings ) { + +- this._update( bindGroup, bindings ); ++ this._update( bindGroup, bindings, settled ); + + } + +@@ -308,11 +313,27 @@ class Bindings extends DataMap { + * + * @param {BindGroup} bindGroup - The bind group to update. + * @param {Array} bindings - The bind groups. ++ * @param {boolean} [settled] - Whether the render object is a settled static object. + */ +- _update( bindGroup, bindings ) { ++ _update( bindGroup, bindings, settled ) { + + const { backend } = this; + ++ // A group a node added after this object's first draw has no buffers and no layout yet, and ++ // the pipeline reads that layout whatever this draw decides to do with it - so it is built ++ // here, on every draw. A group that did not exist cannot have been written either, so it is ++ // written now: `settled` is what tells the loop below to write it, not to skip it. ++ if ( this.get( bindGroup ).bindGroup === undefined ) { ++ ++ this._createBindings( [ bindGroup ] ); ++ ++ settled = false; ++ ++ } ++ ++ // a settled object keeps the values it uploaded the last time it refreshed, so an object ++ // group it has already written is the one thing in here it may skip ++ + let needsBindingsUpdate = false; + let cacheBindings = true; + let cacheIndex = 0; +@@ -322,7 +343,7 @@ class Bindings extends DataMap { + + for ( const binding of bindGroup.bindings ) { + +- const updatedGroup = this.nodes.updateGroup( binding ); ++ const updatedGroup = this.nodes.updateGroup( binding, settled ); + + // every uniforms group is a uniform buffer. So if no update is required, + // we move one with the next binding. Otherwise the next if block will update the group. +diff --git a/src/renderers/common/Geometries.js b/src/renderers/common/Geometries.js +index eb332c415..979da2ffc 100644 +--- a/src/renderers/common/Geometries.js ++++ b/src/renderers/common/Geometries.js +@@ -139,6 +139,14 @@ class Geometries extends DataMap { + */ + this._geometryDisposeListeners = new Map(); + ++ /** ++ * This Weak Map holds the attribute versions a settled render object was last drawn with, ++ * so a write into one of them is what brings the object back out of the settled path. ++ * ++ * @type {WeakMap>} ++ */ ++ this._settledAttributes = new WeakMap(); ++ + } + + /** +@@ -164,10 +172,69 @@ class Geometries extends DataMap { + + if ( this.has( renderObject ) === false ) this.initGeometry( renderObject ); + ++ // A settled static object has no attribute that could have changed since the last refresh, ++ // unless a writer bumped one. An InstancedMesh's instance buffer is a vertex attribute of ++ // this draw, and a BufferGeometry carries no version of its own to announce the write, so ++ // the attributes' own versions are the signal - and the only one there is. ++ ++ const monitor = renderObject.getMonitor(); ++ ++ if ( monitor !== undefined && monitor.isSettled( renderObject ) === true && this.attributesChanged( renderObject ) === false ) return; ++ + this.updateAttributes( renderObject ); + + } + ++ /** ++ * Returns `true` if an attribute of the given render object - or its index - was written to ++ * since the last time its versions were read, and records the versions it holds now. ++ * ++ * @param {RenderObject} renderObject - The render object. ++ * @return {boolean} Whether any of the render object's versions moved. ++ */ ++ attributesChanged( renderObject ) { ++ ++ const attributes = renderObject.getAttributes(); ++ const index = this.getIndex( renderObject ); ++ const settled = this._settledAttributes.get( renderObject ); ++ ++ if ( settled !== undefined && settled.size === attributes.length + ( index === null ? 0 : 1 ) ) { ++ ++ let changed = false; ++ ++ for ( const attribute of attributes ) { ++ ++ if ( settled.get( attribute.isInterleavedBufferAttribute ? attribute.data : attribute ) !== attribute.version ) { ++ ++ changed = true; ++ break; ++ ++ } ++ ++ } ++ ++ if ( changed === false && ( index === null || settled.get( index ) === index.version ) ) return false; ++ ++ } ++ ++ const versions = new Map(); ++ ++ for ( const attribute of attributes ) { ++ ++ const data = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; ++ ++ versions.set( data, data.version ); ++ ++ } ++ ++ if ( index !== null ) versions.set( index, index.version ); ++ ++ this._settledAttributes.set( renderObject, versions ); ++ ++ return true; ++ ++ } ++ + /** + * Initializes the geometry of the given render object. + * +diff --git a/src/renderers/common/RenderContexts.js b/src/renderers/common/RenderContexts.js +index 64ecdeeea..7319e25e2 100644 +--- a/src/renderers/common/RenderContexts.js ++++ b/src/renderers/common/RenderContexts.js +@@ -77,7 +77,8 @@ class RenderContexts { + + } + +- if ( renderTarget !== null ) renderState.sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; ++ const sampleCount = renderTarget !== null ? renderTarget.samples : this.renderer.currentSamples; ++ renderState.sampleCount = sampleCount === 0 ? 1 : sampleCount; + + renderState.clearDepthValue = this.renderer.getClearDepth(); + renderState.clearStencilValue = this.renderer.getClearStencil(); +diff --git a/src/renderers/common/RenderObject.js b/src/renderers/common/RenderObject.js +index c900fa4c9..4f8d58950 100644 +--- a/src/renderers/common/RenderObject.js ++++ b/src/renderers/common/RenderObject.js +@@ -299,6 +299,18 @@ class RenderObject { + */ + this._sourceMaterial = renderer._currentSourceMaterial; + ++ /** ++ * The version of `_sourceMaterial` this render object was last validated against. ++ * ++ * An override material is shared by every object in the pass and is used as a per-object ++ * scratchpad, so its own `version` moves for reasons that have nothing to do with this ++ * render object. The source material's version is the signal that does. ++ * ++ * @type {number} ++ * @default 0 ++ */ ++ this.sourceVersion = ( this._sourceMaterial === null || this._sourceMaterial === undefined ) ? 0 : this._sourceMaterial.version; ++ + /** + * An event listener which is defined by `RenderObjects`. It performs + * clean up tasks when `dispose()` on this render object. +@@ -486,7 +498,7 @@ class RenderObject { + */ + getChainArray() { + +- return [ this.object, this.material, this.context, this.lightsNode ]; ++ return [ this.object, this.material, this.context, this.lightsNode, this._sourceMaterial ?? this.material ]; + + } + +@@ -671,7 +683,7 @@ class RenderObject { + * + * @return {string} The geometry cache key. + */ +- getGeometryCacheKey() { ++ getGeometryCacheKey( useMorphTargetIds = true ) { + + const { geometry } = this; + +@@ -699,12 +711,14 @@ class RenderObject { + const targets = geometry.morphAttributes[ name ]; + + cacheKey += 'morph-' + name + ','; ++ if ( ! useMorphTargetIds ) cacheKey += targets.length + ',' + geometry.morphTargetsRelative + ','; + + for ( let i = 0, l = targets.length; i < l; i ++ ) { + + const attribute = targets[ i ]; + +- cacheKey += attribute.id + ','; ++ cacheKey += useMorphTargetIds ? attribute.id + ',' : ++ attribute.count + ',' + attribute.itemSize + ','; + + } + +@@ -936,6 +950,7 @@ class RenderObject { + } + + cacheKey = hash( cacheKey, this.renderer.contextNode.id, this.renderer.contextNode.version ); ++ cacheKey = hash( cacheKey, this.context.sampleCount ); + + return cacheKey; + +diff --git a/src/renderers/common/RenderObjects.js b/src/renderers/common/RenderObjects.js +index 676cc2bd4..91fba38f3 100644 +--- a/src/renderers/common/RenderObjects.js ++++ b/src/renderers/common/RenderObjects.js +@@ -97,6 +97,8 @@ class RenderObjects { + _chainKeys[ 1 ] = material; + _chainKeys[ 2 ] = renderContext; + _chainKeys[ 3 ] = lightsNode; ++ // Material groups share the shadow override, but must retain separate GPU bindings. ++ _chainKeys[ 4 ] = this.renderer._currentSourceMaterial ?? material; + + // + +@@ -118,15 +120,26 @@ class RenderObjects { + + renderObject.updateClipping( clippingContext ); + +- if ( renderObject.needsGeometryUpdate ) { ++ const geometryChanged = renderObject.needsGeometryUpdate; ++ ++ if ( geometryChanged ) { + + renderObject.setGeometry( object.geometry ); + + } + +- if ( renderObject.version !== material.version || renderObject.needsUpdate ) { ++ // When the renderer is drawing through an override material, that material is shared by ++ // the whole pass and mutated per object, so its `version` alone answers the wrong ++ // question. The source material this object actually carries answers the right one. ++ ++ const sourceMaterial = this.renderer._currentSourceMaterial; ++ const sourceTracked = sourceMaterial !== null && sourceMaterial !== undefined; ++ const sourceReplaced = sourceTracked && renderObject._sourceMaterial !== sourceMaterial; ++ const sourceChanged = sourceTracked && ( sourceReplaced || renderObject.sourceVersion !== sourceMaterial.version ); + +- if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) { ++ if ( geometryChanged || renderObject.version !== material.version || sourceChanged || renderObject.needsUpdate ) { ++ ++ if ( sourceReplaced || renderObject.initialCacheKey !== renderObject.getCacheKey() ) { + + renderObject.dispose(); + +@@ -136,6 +149,8 @@ class RenderObjects { + + renderObject.version = material.version; + ++ if ( sourceTracked ) renderObject.sourceVersion = sourceMaterial.version; ++ + } + + } +@@ -148,6 +163,7 @@ class RenderObjects { + _chainKeys[ 1 ] = null; + _chainKeys[ 2 ] = null; + _chainKeys[ 3 ] = null; ++ _chainKeys[ 4 ] = null; + + // + +diff --git a/src/renderers/common/RenderPipeline.js b/src/renderers/common/RenderPipeline.js +index ab3085cfd..94e54740e 100644 +--- a/src/renderers/common/RenderPipeline.js ++++ b/src/renderers/common/RenderPipeline.js +@@ -137,7 +137,20 @@ class RenderPipeline { + const currentXR = renderer.xr.enabled; + renderer.xr.enabled = false; + +- this._quadMesh.render( renderer ); ++ const previousOwner = renderer._tnOutputOwner; ++ renderer._tnOutputOwner = currentXR === false ? this._quadMesh : null; ++ try { ++ ++ this._quadMesh.render( renderer ); ++ ++ } finally { ++ ++ renderer._tnOutputOwner = previousOwner; ++ renderer.xr.enabled = currentXR; ++ renderer.toneMapping = toneMapping; ++ renderer.outputColorSpace = outputColorSpace; ++ ++ } + + renderer.xr.enabled = currentXR; + +diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js +index 8dfc2ae05..f9903c26a 100644 +--- a/src/renderers/common/Renderer.js ++++ b/src/renderers/common/Renderer.js +@@ -1738,6 +1738,7 @@ class Renderer { + // + + renderContext.camera = camera; ++ renderContext._tnOutputOwner = scene === this._tnOutputOwner ? scene : null; + this.backend.beginRender( renderContext ); + + // process render lists +@@ -1750,13 +1751,23 @@ class Renderer { + opaque: opaqueObjects + } = renderList; + ++ let renderComplete = false; ++ try { ++ + if ( bundles.length > 0 ) this._renderBundles( bundles, sceneRef, lightsNode ); + if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode ); + if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode ); + +- // finish render pass ++ renderComplete = true; ++ ++ } finally { ++ ++ try { ++ ++ if ( renderComplete ) this.backend.finishRender( renderContext ); ++ else if ( this.backend.get( renderContext )._tnDepth ) this.backend._abortTransientDepth( renderContext ); + +- this.backend.finishRender( renderContext ); ++ } finally { + + // restore render tree + +@@ -1771,6 +1782,9 @@ class Renderer { + + this._callDepth --; + ++ } ++ } ++ + if ( frameBufferTarget !== null ) { + + this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel ); +@@ -1937,6 +1951,18 @@ class Renderer { + + } + ++ /** ++ * Releases an exclusively owned attribute through the backend and memory accounting. ++ * Wait for asynchronous readers before releasing storage shared by compute dispatches. ++ * @param {BufferAttribute} attribute - The owned attribute to release. ++ * @return {?Object} The removed attribute data, or null when absent/uninitialized. ++ */ ++ deleteAttribute( attribute ) { ++ ++ return this._attributes === null ? null : this._attributes.delete( attribute ); ++ ++ } ++ + /** + * Can be used to transfer buffer data from a storage buffer attribute + * from the GPU to the CPU in context of compute shaders. +@@ -3550,6 +3576,7 @@ class Renderer { + let materialDisplacementMap; + let materialDisplacementScale; + let materialDisplacementBias; ++ let materialAlphaTest; + + const previousSourceMaterial = this._currentSourceMaterial; + +@@ -3575,6 +3602,7 @@ class Renderer { + materialDisplacementMap = overrideMaterial.displacementMap; + materialDisplacementScale = overrideMaterial.displacementScale; + materialDisplacementBias = overrideMaterial.displacementBias; ++ materialAlphaTest = overrideMaterial.alphaTest; + + if ( material.positionNode && material.positionNode.isNode ) { + +@@ -3582,7 +3610,15 @@ class Renderer { + + } + +- overrideMaterial.alphaTest = material.alphaTest; ++ // `Material.alphaTest`'s setter bumps `version` whenever alpha-test-ness flips, and this ++ // override material is shared by every caster in the pass. A scene that mixes alpha-tested ++ // casters with opaque ones therefore moves that shared version several times per frame, ++ // and every OTHER render object drawn with it then fails the version gate in ++ // `RenderObjects.get` and recomputes a full material cache key that always matches. ++ // Writing the backing field leaves the `alphaTest` getter — and so the cache key — still ++ // reporting this caster's value, while each render object tracks the staleness signal ++ // that belongs to it: its own source material's version. ++ overrideMaterial._alphaTest = material.alphaTest; + overrideMaterial.alphaMap = material.alphaMap; + overrideMaterial.displacementMap = material.displacementMap; + overrideMaterial.displacementScale = material.displacementScale; +@@ -3644,6 +3680,7 @@ class Renderer { + scene.overrideMaterial.displacementMap = materialDisplacementMap; + scene.overrideMaterial.displacementScale = materialDisplacementScale; + scene.overrideMaterial.displacementBias = materialDisplacementBias; ++ scene.overrideMaterial._alphaTest = materialAlphaTest; + + } + +@@ -3705,18 +3742,17 @@ class Renderer { + + // + +- const needsRefresh = this._nodes.needsRefresh( renderObject ); +- +- if ( needsRefresh ) { +- +- this._nodes.updateBefore( renderObject ); +- +- this._geometries.updateForRender( renderObject ); ++ this._nodes.needsRefresh( renderObject ); + +- this._nodes.updateForRender( renderObject ); +- this._bindings.updateForRender( renderObject ); +- +- } ++ // Every step of a draw runs, settled or not. What a settled static object skips is decided ++ // inside each of them - its OBJECT node updates, its object uniform group write and its ++ // attribute scan - because each of those answers for itself, and a step left out here ++ // cannot: a group a node added after this object's first draw still has to be created, and ++ // the pipeline reads that group's layout whatever this draw decided to do with it. ++ this._nodes.updateBefore( renderObject ); ++ this._geometries.updateForRender( renderObject ); ++ this._nodes.updateForRender( renderObject ); ++ this._bindings.updateForRender( renderObject ); + + this._pipelines.updateForRender( renderObject ); + +@@ -3726,7 +3762,7 @@ class Renderer { + + this.backend.draw( renderObject, this.info ); + +- if ( needsRefresh ) this._nodes.updateAfter( renderObject ); ++ this._nodes.updateAfter( renderObject ); + + } + +diff --git a/src/renderers/common/Textures.js b/src/renderers/common/Textures.js +index f42b90eac..be47645f5 100644 +--- a/src/renderers/common/Textures.js ++++ b/src/renderers/common/Textures.js +@@ -215,6 +215,21 @@ class Textures extends DataMap { + + backend.destroyTexture( texture ); + ++ // delete cached bind groups so they don't point to the destroyed texture ++ ++ if ( textureData.bindGroups ) { ++ ++ for ( const bindGroup of textureData.bindGroups ) { ++ ++ const bindingsData = this.backend.get( bindGroup ); ++ ++ bindingsData.groups = undefined; ++ bindingsData.versions = undefined; ++ ++ } ++ ++ } ++ + } + + // +diff --git a/src/renderers/common/nodes/NodeManager.js b/src/renderers/common/nodes/NodeManager.js +index f6d4f0b54..033128c39 100644 +--- a/src/renderers/common/nodes/NodeManager.js ++++ b/src/renderers/common/nodes/NodeManager.js +@@ -109,15 +109,17 @@ class NodeManager extends DataMap { + * Returns `true` if the given node uniforms group must be updated or not. + * + * @param {NodeUniformsGroup} nodeUniformsGroup - The node uniforms group. ++ * @param {boolean} [settled] - Whether the render object is a settled static object. + * @return {boolean} Whether the node uniforms group requires an update or not. + */ +- updateGroup( nodeUniformsGroup ) { ++ updateGroup( nodeUniformsGroup, settled ) { + + const groupNode = nodeUniformsGroup.groupNode; + +- // groups that are updated per object always require an update so no further checks are needed ++ // groups that are updated per object always require an update so no further checks are needed. ++ // A settled static object keeps the values it uploaded the last time it was refreshed. + +- if ( groupNode.updateType === NodeUpdateType.OBJECT ) return true; ++ if ( groupNode.updateType === NodeUpdateType.OBJECT ) return settled !== true; + + // check for update + +@@ -921,6 +923,21 @@ class NodeManager extends DataMap { + + } + ++ /** ++ * Returns `true` if the per-object updates of the given render object can be skipped ++ * because the object is static and nothing it depends on changed since its last refresh. ++ * ++ * @param {RenderObject} renderObject - The render object. ++ * @return {boolean} Whether the render object is settled or not. ++ */ ++ isSettled( renderObject ) { ++ ++ const monitor = renderObject.getMonitor(); ++ ++ return monitor !== undefined ? monitor.isSettled( renderObject ) : false; ++ ++ } ++ + /** + * Triggers the call of `updateBefore()` methods + * for all nodes of the given render object. +@@ -930,9 +947,14 @@ class NodeManager extends DataMap { + updateBefore( renderObject ) { + + const nodeBuilder = renderObject.getNodeBuilderState(); ++ const settled = this.isSettled( renderObject ); + + for ( const node of nodeBuilder.updateBeforeNodes ) { + ++ // a settled static object has nothing to update per object ++ ++ if ( settled === true && node.getUpdateBeforeType() === NodeUpdateType.OBJECT ) continue; ++ + // update frame state for each node + + this.getNodeFrameForRender( renderObject ).updateBeforeNode( node ); +@@ -950,10 +972,11 @@ class NodeManager extends DataMap { + updateAfter( renderObject ) { + + const nodeBuilder = renderObject.getNodeBuilderState(); ++ const settled = this.isSettled( renderObject ); + + for ( const node of nodeBuilder.updateAfterNodes ) { + +- // update frame state for each node ++ if ( settled === true && node.getUpdateAfterType() === NodeUpdateType.OBJECT ) continue; + + this.getNodeFrameForRender( renderObject ).updateAfterNode( node ); + +@@ -990,9 +1013,14 @@ class NodeManager extends DataMap { + + const nodeFrame = this.getNodeFrameForRender( renderObject ); + const nodeBuilder = renderObject.getNodeBuilderState(); ++ const settled = this.isSettled( renderObject ); + + for ( const node of nodeBuilder.updateNodes ) { + ++ // a settled static object keeps the per-object values it uploaded last time ++ ++ if ( settled === true && node.getUpdateType() === NodeUpdateType.OBJECT ) continue; ++ + nodeFrame.updateNode( node ); + + } +diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js +index 06e203693..1366ed87b 100644 +--- a/src/renderers/webgpu/WebGPUBackend.js ++++ b/src/renderers/webgpu/WebGPUBackend.js +@@ -102,6 +102,27 @@ class WebGPUBackend extends Backend { + */ + this.compatibilityMode = null; + ++ /** ++ * A reference to the WebGPU instance that creates adapters. ++ * ++ * @type {?GPU} ++ * @default null ++ */ ++ this.gpu = null; ++ ++ /** ++ * A reference to the adapter this device was requested from. ++ * ++ * The adapter owns the wire instance every operation in flight completes on. Chromium ++ * collects an adapter the page stops referencing and the instance goes with it, so every ++ * operation still running completes with a shutdown the page reads as ++ * 'A valid external Instance reference no longer exists.' and presentation stops for. ++ * ++ * @type {?GPUAdapter} ++ * @default null ++ */ ++ this.adapter = null; ++ + /** + * A reference to the device. + * +@@ -214,7 +235,11 @@ class WebGPUBackend extends Backend { + xrCompatible: renderer.xr.enabled + }; + +- const adapter = ( typeof navigator !== 'undefined' ) ? await navigator.gpu.requestAdapter( adapterOptions ) : null; ++ this.gpu = ( typeof navigator !== 'undefined' ) ? navigator.gpu : null; ++ ++ const adapter = this.gpu !== null ? await this.gpu.requestAdapter( adapterOptions ) : null; ++ ++ this.adapter = adapter; + + if ( adapter === null ) { + +@@ -441,7 +466,9 @@ class WebGPUBackend extends Backend { + + let descriptor = canvasData.descriptor; + +- if ( descriptor === undefined || canvasData.samples !== samples ) { ++ if ( descriptor === undefined || canvasData.samples !== samples || ++ canvasData.depth !== renderer.depth || canvasData.stencil !== renderer.stencil || ++ canvasData.reversedDepthBuffer !== renderer.reversedDepthBuffer ) { + + descriptor = new GPURenderPassDescriptor(); + descriptor.colorAttachments.push( new GPURenderPassColorAttachment() ); +@@ -468,6 +495,9 @@ class WebGPUBackend extends Backend { + + canvasData.descriptor = descriptor; + canvasData.samples = samples; ++ canvasData.depth = renderer.depth; ++ canvasData.stencil = renderer.stencil; ++ canvasData.reversedDepthBuffer = renderer.reversedDepthBuffer; + + } + +@@ -784,6 +814,150 @@ class WebGPUBackend extends Backend { + * + * @param {RenderContext} renderContext - The render context. + */ ++ // Only the renderer-owned presentation scene may speculate. Canonical textures stay public. ++ _isFullTransientViewport( context ) { ++ ++ const v = context.viewportValue; ++ return ! context.scissor && ( ! context.viewport || ( v.x === 0 && v.y === 0 && v.width === context.width && v.height === context.height && v.minDepth === 0 && v.maxDepth === 1 ) ); ++ ++ } ++ ++ _beginTransientDepth( context, descriptor ) { ++ ++ const original = descriptor.depthStencilAttachment; ++ if ( ! this.device.features?.has( 'transient-attachments' ) || ! context._tnOutputOwner || context.textures !== null || ! context.depth || context.stencil || context.camera?.isArrayCamera || this.renderer.xr?.isPresenting || this.renderer.reversedDepthBuffer || context.occlusionQueryCount !== 0 || ! this._isFullTransientViewport( context ) || original?.depthLoadOp !== 'clear' || original.depthClearValue !== 1 || original.depthStoreOp !== 'store' ) return descriptor; ++ ++ const samples = this.utils.getSampleCountRenderContext( context ); ++ if ( samples <= 1 ) return descriptor; ++ const canonical = this.renderer.getCanvasTarget().depthTexture; ++ const cache = this.textureUtils._depthBuffers.get( canonical ); ++ const format = this.get( canonical ).format; ++ if ( cache === undefined || ! format ) return descriptor; ++ if ( cache._tnPrivateDepth === undefined ) { ++ ++ const texture = this.device.createTexture( { label: 'tn-private-output-depth', size: { width: context.width, height: context.height }, format, sampleCount: samples, usage: GPUTextureUsage.RENDER_ATTACHMENT, _tnPassLocalMSAA: true } ); ++ cache._tnPrivateDepth = { texture, used: false }; ++ ++ } ++ const source = cache._tnPrivateDepth; ++ const attachment = { ...original, view: source.texture.createView(), depthLoadOp: 'clear', depthClearValue: 1, depthStoreOp: 'discard' }; ++ if ( format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'clear', stencilClearValue: 0, stencilStoreOp: 'discard' } ); ++ this.get( context )._tnDepth = { original: { ...original }, source, cache, canonical, active: true, logical: 1 }; ++ return { ...descriptor, colorAttachments: descriptor.colorAttachments.map( a => ( { ...a } ) ), depthStencilAttachment: attachment }; ++ ++ } ++ ++ _finishTransientDepth( context ) { ++ ++ const data = this.get( context ); ++ const state = data._tnDepth; ++ if ( ! state?.active ) return; ++ state.active = false; ++ const attachment = { ...state.original, depthLoadOp: 'clear', depthClearValue: state.logical, depthStoreOp: 'store' }; ++ // A combined texture's original stencil is never changed by this depth-only compatibility clear. ++ if ( attachment.stencilLoadOp !== undefined || this.get( state.canonical ).format.includes( 'stencil' ) ) Object.assign( attachment, { stencilLoadOp: 'load', stencilStoreOp: 'store', stencilReadOnly: false } ); ++ data.encoder.beginRenderPass( { colorAttachments: [], depthStencilAttachment: attachment } ).end(); ++ data.descriptor.depthStencilAttachment = { ...state.original, depthLoadOp: 'load' }; ++ ++ } ++ ++ _restoreTransientDepth( context ) { ++ ++ const data = this.get( context ); ++ if ( ! data._tnDepth?.active ) return; ++ data.currentPass.end(); ++ this._finishTransientDepth( context ); ++ this._resumeTransientDepth( context ); ++ ++ } ++ ++ _resumeTransientDepth( context ) { ++ ++ const data = this.get( context ); ++ const descriptor = { ...data.descriptor, timestampWrites: undefined, colorAttachments: data.descriptor.colorAttachments.map( a => ( { ...a, loadOp: 'load' } ) ) }; ++ data.descriptor = descriptor; ++ data.currentPass = data.encoder.beginRenderPass( descriptor ); ++ data.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; ++ if ( context.viewport ) this.updateViewport( context ); ++ if ( context.scissor ) this.updateScissor( context ); ++ ++ } ++ ++ _abortTransientDepth( context ) { ++ ++ const data = this.get( context ); ++ const state = data._tnDepth; ++ try { ++ ++ data.currentPass.end(); ++ data.encoder.finish(); // Close and discard failed work without submitting or restoring depth. ++ ++ } finally { ++ ++ data.descriptor.depthStencilAttachment = { ...state.original }; ++ delete data.currentPass; ++ delete data.encoder; ++ this._releaseTransientDepth( context ); ++ ++ } ++ ++ } ++ ++ _releaseTransientDepth( context ) { ++ ++ const data = this.get( context ); ++ const state = data._tnDepth; ++ if ( state && ! state.source.used && state.cache._tnPrivateDepth === state.source ) { ++ ++ state.source.texture.destroy(); ++ delete state.cache._tnPrivateDepth; ++ ++ } ++ delete data._tnDepth; ++ ++ } ++ ++ _validateTransientDepth( renderObject, params, vertexBuffers ) { ++ ++ const { object, context, material, pipeline } = renderObject; ++ const proof = this.get( pipeline )._tnDepthProof; ++ const position = object.geometry.attributes.position; ++ const uploaded = position && this.get( position ); ++ if ( object !== context._tnOutputOwner || ! this._isFullTransientViewport( context ) || ! proof || proof.pipeline !== this.get( pipeline ).pipeline || proof.material !== material || proof.version !== material.version || material.vertexNode !== null || material.positionNode !== null || material.depthNode !== null || material.depthTest !== true || material.depthWrite !== true || material.depthFunc !== 3 || material.alphaToCoverage || material.polygonOffset || material.stencilWrite || params.vertexCount !== 3 || params.firstVertex !== 0 || params.instanceCount !== 1 || renderObject.geometry !== object.geometry || renderObject.getIndex() !== null || object.geometry.index !== null || object.isBatchedMesh || object.isInstancedMesh || ! vertexBuffers.includes( position ) || renderObject.getIndirect() !== null || ! uploaded?._tnFullTriangle || uploaded._tnTriangleVersion !== position.version || uploaded._tnTriangleBuffer !== uploaded.buffer || position.array.length !== 9 || ! [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ].every( ( n, i ) => position.array[ i ] === n ) ) return false; ++ if ( proof.vertex !== this.get( pipeline.vertexProgram )._tnShaderCode || proof.fragment !== this.get( pipeline.fragmentProgram )._tnShaderCode ) return false; ++ if ( /\b(discard|frag_depth|sample_mask|clip_distances)\b/.test( proof.vertex + proof.fragment ) ) return false; ++ // Admit only Three's shipped projection chain. Custom code anywhere in the vertex stage falls back. ++ const code = proof.vertex.replace( /\/\/[^\n]*/g, '' ).replace( /\s+/g, '' ); ++ if ( ( code.match( /fn/g ) || [] ).length !== 1 ) return false; ++ const main = /@vertexfnmain\((.*?)\)->VaryingsStruct\{(.*)\}$/.exec( code ); ++ if ( ! main || ! /^(@location\(\d+\)(position:vec3|uv:vec2),?)+$/.test( main[ 1 ] ) || ! main[ 1 ].includes( '@location(' + proof.positionIndex + ')position:vec3' ) ) return false; ++ let body = main[ 2 ]; ++ // UV passthrough affects colour only; no other extra vertex operation is accepted. ++ body = body.replace( /varyings\.(v_uv|nodeVarying\d+)=uv;/g, '' ); ++ const chain = /^modelViewMatrix=\(render\.cameraViewMatrix\*object\.(nodeUniform\d+)\);positionLocal=position;v_positionView=\(modelViewMatrix\*vec4\(positionLocal,1\.0\)\)\.xyz;(VERTEX_nodeVar\d+)=\(render\.cameraProjectionMatrix\*vec4\(v_positionView,1\.0\)\);VERTEX_v_modelViewProjection=\2;varyings\.builtinClipSpace=VERTEX_v_modelViewProjection;returnvaryings;$/; ++ const match = chain.exec( body ); ++ if ( ! match ) return false; ++ const matrices = new Map(); ++ for ( const group of renderObject.getBindings() ) { ++ ++ const groupData = this.get( group ); ++ if ( ! groupData._tnBindings || ! groupData._tnBindings.every( ( b, i ) => b.binding === group.bindings[ i ] && b.buffer === this.get( b.binding ).buffer ) ) return false; ++ for ( const entry of groupData._tnBindings ) { ++ ++ const binding = entry.binding; ++ const snapshot = this.get( binding )._tnUniformBytes; ++ if ( ! snapshot ) continue; ++ for ( const uniform of entry.uniforms || [] ) if ( uniform.itemSize === 16 ) matrices.set( group.name + '.' + uniform.name, Array.from( snapshot.slice( uniform.offset, uniform.offset + 16 ) ) ); ++ ++ } ++ ++ } ++ const identity = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; ++ const projection = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 ]; ++ return [ [ 'render.cameraViewMatrix', identity ], [ 'object.' + match[ 1 ], identity ], [ 'render.cameraProjectionMatrix', projection ] ].every( ( [ name, expected ] ) => matrices.get( name )?.length === 16 && matrices.get( name ).every( ( value, i ) => value === expected[ i ] ) ); ++ ++ } ++ + beginRender( renderContext ) { + + const renderContextData = this.get( renderContext ); +@@ -936,10 +1110,15 @@ class WebGPUBackend extends Backend { + + // + ++ descriptor = this._beginTransientDepth( renderContext, descriptor ); ++ + _commandEncoderDescriptor.label = 'renderContext_' + renderContext.id; + const encoder = device.createCommandEncoder( _commandEncoderDescriptor ); + _commandEncoderDescriptor.reset(); + ++ renderContextData.descriptor = descriptor; ++ renderContextData.encoder = encoder; ++ + // Layered render targets: prepare bundle encoders for each camera in the array camera. + + if ( this._isRenderCameraDepthArray( renderContext ) === true ) { +@@ -1233,6 +1412,8 @@ class WebGPUBackend extends Backend { + + } + ++ this._finishTransientDepth( renderContext ); ++ + if ( occlusionQueryCount > 0 ) { + + const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results +@@ -1275,7 +1456,16 @@ class WebGPUBackend extends Backend { + + } + +- submit( this.device, renderContextData.encoder.finish() ); ++ try { ++ ++ submit( this.device, renderContextData.encoder.finish() ); ++ if ( renderContextData._tnDepth?.logical === 0 ) renderContextData._tnDepth.source.used = true; ++ ++ } finally { ++ ++ this._releaseTransientDepth( renderContext ); ++ ++ } + + + // +@@ -1369,6 +1559,8 @@ class WebGPUBackend extends Backend { + */ + updateViewport( renderContext ) { + ++ if ( ! this._isFullTransientViewport( renderContext ) ) this._restoreTransientDepth( renderContext ); ++ + const { currentPass } = this.get( renderContext ); + const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; + +@@ -1383,6 +1575,8 @@ class WebGPUBackend extends Backend { + */ + updateScissor( renderContext ) { + ++ this._restoreTransientDepth( renderContext ); ++ + const { currentPass } = this.get( renderContext ); + const { x, y, width, height } = renderContext.scissorValue; + +@@ -1749,13 +1943,12 @@ class WebGPUBackend extends Backend { + for ( let i = 0, l = bindings.length; i < l; i ++ ) { + + const bindGroup = bindings[ i ]; ++ const bindingsData = this.get( bindGroup ); + +- if ( currentBindingGroups[ i ] !== bindGroup.id ) { +- +- const bindingsData = this.get( bindGroup ); ++ if ( currentBindingGroups[ i ] !== bindingsData.group ) { + + passEncoderGPU.setBindGroup( i, bindingsData.group ); +- currentBindingGroups[ i ] = bindGroup.id; ++ currentBindingGroups[ i ] = bindingsData.group; + + } + +@@ -1881,6 +2074,11 @@ class WebGPUBackend extends Backend { + } else { + + passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 ); ++ if ( renderContextData._tnDepth?.active ) { ++ ++ renderContextData._tnDepth.logical = 0; ++ ++ } + + } + +@@ -1916,6 +2114,19 @@ class WebGPUBackend extends Backend { + // vertex buffers + + const vertexBuffers = renderObject.getVertexBuffers(); ++ if ( renderContextData._tnDepth?.active ) { ++ ++ if ( this._validateTransientDepth( renderObject, drawParams, vertexBuffers ) ) { ++ ++ // _draw records the constant only after the actual GPU draw is encoded. ++ ++ } else { ++ ++ this._restoreTransientDepth( context ); ++ ++ } ++ ++ } + + if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) { + +@@ -2054,6 +2265,7 @@ class WebGPUBackend extends Backend { + const utils = this.utils; + + const sampleCount = utils.getSampleCountRenderContext( renderObject.context ); ++ const bindGroupLayoutKeys = renderObject.getBindings().map( bindGroup => this.get( bindGroup ).layoutKey ).join( '|' ); + const colorSpace = utils.getCurrentColorSpace( renderObject.context ); + const colorFormat = utils.getCurrentColorFormat( renderObject.context ); + const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context ); +@@ -2075,7 +2287,8 @@ class WebGPUBackend extends Backend { + data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat || + data.primitiveTopology !== primitiveTopology || + data.frontFaceCW !== frontFaceCW || +- data.clippingContextCacheKey !== renderObject.clippingContextCacheKey ++ data.clippingContextCacheKey !== renderObject.clippingContextCacheKey || ++ data.bindGroupLayoutKeys !== bindGroupLayoutKeys + ) { + + data.material = material; data.materialVersion = material.version; +@@ -2095,6 +2308,7 @@ class WebGPUBackend extends Backend { + data.primitiveTopology = primitiveTopology; + data.frontFaceCW = frontFaceCW; + data.clippingContextCacheKey = renderObject.clippingContextCacheKey; ++ data.bindGroupLayoutKeys = bindGroupLayoutKeys; + + needsUpdate = true; + +@@ -2116,6 +2330,7 @@ class WebGPUBackend extends Backend { + + const utils = this.utils; + const renderContext = renderObject.context; ++ const bindGroupLayoutKeys = renderObject.getBindings().map( bindGroup => this.get( bindGroup ).layoutKey ).join( '|' ); + + // meshes with negative scale have a different frontFace render pipeline + // descriptor value so the following must be honored in the cache key +@@ -2134,9 +2349,10 @@ class WebGPUBackend extends Backend { + material.side, + frontFaceCW, + utils.getSampleCountRenderContext( renderContext ), ++ bindGroupLayoutKeys, + utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), + utils.getPrimitiveTopology( object, material ), +- renderObject.getGeometryCacheKey(), ++ renderObject.getGeometryCacheKey( false ), + renderObject.clippingContextCacheKey + ].join(); + +@@ -2305,6 +2521,8 @@ class WebGPUBackend extends Backend { + _shaderModuleDescriptor.label = program.stage + ( program.name !== '' ? `_${ program.name }` : '' ); + _shaderModuleDescriptor.code = program.code; + ++ programGPU._tnShaderCode = program.code; ++ + programGPU.module = { + module: this.device.createShaderModule( _shaderModuleDescriptor ), + entryPoint: 'main' +@@ -2452,6 +2670,7 @@ class WebGPUBackend extends Backend { + _bufferDescriptor.reset(); + + uniformBufferData.buffer = bufferGPU; ++ if ( this.device.features?.has( 'transient-attachments' ) && uniformBuffer.uniforms?.some( u => u.itemSize === 16 ) ) uniformBufferData._tnUniformBytes = new Float32Array( byteLength / 4 ); + + } + +@@ -2718,6 +2937,7 @@ class WebGPUBackend extends Backend { + + submit( this.device, encoder.finish() ); + ++ + if ( dstLevel === 0 && dstTexture.generateMipmaps ) { + + this.textureUtils.generateMipmaps( dstTexture ); +@@ -2735,6 +2955,8 @@ class WebGPUBackend extends Backend { + */ + copyFramebufferToTexture( texture, renderContext, rectangle ) { + ++ this._restoreTransientDepth( renderContext ); ++ + const renderContextData = this.get( renderContext ); + + let sourceGPU = null; +diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +index be0753630..1cbc9b3e0 100644 +--- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js ++++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +@@ -723,7 +723,7 @@ class WGSLNodeBuilder extends NodeBuilder { + * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. + * @return {string} The WGSL snippet. + */ +- generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet ) { ++ generateTextureLoad( texture, textureProperty, uvIndexSnippet, levelSnippet, depthSnippet, offsetSnippet, rawTexture = false ) { + + if ( levelSnippet === null ) levelSnippet = '0u'; + +@@ -741,7 +741,14 @@ class WGSLNodeBuilder extends NodeBuilder { + + } else { + +- snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, u32( ${ levelSnippet } ) )`; ++ // naga v25 (wgpu-native v25.0.2.2) rejects a u32 sample index on a ++ // multisampled textureLoad; i32 is accepted by every backend. Mip-level ++ // operands (including the array form above) stay u32. ++ const sampleData = texture ? this.renderer?.backend?.utils?.getTextureSampleData?.( texture ) : null; ++ const backendMultisampled = sampleData !== null && sampleData !== undefined && sampleData.primarySamples > 1; ++ const targetMultisampled = Boolean( texture && texture.renderTarget && texture.renderTarget.samples > 1 ); ++ const indexType = ( rawTexture || backendMultisampled || targetMultisampled ) ? 'i32' : 'u32'; ++ snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ indexType }( ${ levelSnippet } ) )`; + + if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { + +@@ -1282,7 +1289,7 @@ class WGSLNodeBuilder extends NodeBuilder { + + // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) + // Also textureGather always need sampler. +- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null; ++ const needsSampler = node.isRawTextureNode !== true && ( node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null ); + + if ( needsSampler ) { + +@@ -1323,7 +1330,8 @@ class WGSLNodeBuilder extends NodeBuilder { + + uniformGPU = buffer; + +- uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id; ++ // The base builder supplies a unique name within this program. A process-wide ++ // allocation id changes otherwise identical shader source for every buffer. + + } else { + +@@ -1711,7 +1719,8 @@ ${ flowData.code } + + for ( const { name, property, type } of builtins.values() ) { + +- snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); ++ const invariant = shaderStage === 'vertex' && name === 'position' && this.context.invariantPosition === true ? '@invariant ' : ''; ++ snippets.push( `${ invariant }@builtin( ${name} ) ${property} : ${type}` ); + + } + +@@ -1967,6 +1976,8 @@ ${ flowData.code } + getVaryings( shaderStage ) { + + const snippets = []; ++ const policy = this.context.sampleInterpolation; ++ const sampleInterpolation = typeof policy === 'function' ? policy( this.material ) === true : policy === true; + + if ( shaderStage === 'vertex' ) { + +@@ -1989,7 +2000,12 @@ ${ flowData.code } + + let attributesSnippet = `@location( ${ varyingIndex ++ } )`; + +- if ( varying.interpolationType ) { ++ if ( sampleInterpolation && ! /^(int|uint|ivec|uvec)/.test( varying.type ) && varying.interpolationType !== 'flat' ) { ++ ++ if ( varying.interpolationSampling !== null && varying.interpolationSampling !== 'center' && varying.interpolationSampling !== 'sample' ) throw new Error( 'Sample interpolation policy cannot replace explicit centroid sampling.' ); ++ attributesSnippet += ` @interpolate( ${ varying.interpolationType || 'perspective' }, sample )`; ++ ++ } else if ( varying.interpolationType ) { + + const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; + +@@ -2066,9 +2082,12 @@ ${ flowData.code } + const textureNode = uniform.node; + const texture = textureNode.value; + +- // Cube textures always need samplers (they use textureSampleLevel, not textureLoad) +- // Also textureGather always need sampler. +- const needsSampler = texture.isCubeTexture === true || ( this.isUnfilterable( texture ) === false && textureNode.isStorageTextureNode !== true ) || textureNode.gatherNode !== null; ++ // The bind group layout freezes sampler inclusion once, in getUniformFromNode ++ // (nodeData.uniformGPU). Re-deriving it here from live texture filter state can ++ // disagree with that layout and shift every later binding index. ++ const nodeData = this.getDataFromNode( textureNode, shaderStage, this.globalCache ); ++ const uniformGPU = nodeData.uniformGPU; ++ const needsSampler = Array.isArray( uniformGPU ) && uniformGPU[ 0 ].isSampler === true && uniformGPU[ 0 ].isSampledTexture !== true; + + if ( needsSampler ) { + +@@ -2090,7 +2109,7 @@ ${ flowData.code } + + const { primarySamples } = backend.utils.getTextureSampleData( texture ); + +- if ( primarySamples > 1 ) { ++ if ( primarySamples > 1 || textureNode.isRawTextureNode === true ) { + + multisampled = '_multisampled'; + +diff --git a/src/renderers/webgpu/utils/WebGPUAttributeUtils.js b/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +index f683d74a3..ded852d9d 100644 +--- a/src/renderers/webgpu/utils/WebGPUAttributeUtils.js ++++ b/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +@@ -3,6 +3,7 @@ import { submit } from './WebGPUUtils.js'; + import GPUBufferDescriptor from '../descriptors/GPUBufferDescriptor.js'; + import GPUCommandEncoderDescriptor from '../descriptors/GPUCommandEncoderDescriptor.js'; + ++import { DynamicDrawUsage } from '../../../constants.js'; + import { Float16BufferAttribute } from '../../../core/BufferAttribute.js'; + import { isTypedArray, error } from '../../../utils.js'; + +@@ -173,7 +174,19 @@ class WebGPUAttributeUtils { + + buffer.unmap(); + ++ bufferData._tnFullTriangle = backend.device.features?.has( 'transient-attachments' ) && attribute.itemSize === 3 && attribute.count === 3 && attribute.normalized === false && array instanceof Float32Array && array.length === 9 && ! attribute.isInterleavedBufferAttribute && ! attribute.isStorageBufferAttribute && [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ].every( ( value, i ) => array[ i ] === value ); ++ bufferData._tnTriangleVersion = bufferAttribute.version; ++ bufferData._tnTriangleBuffer = buffer; + bufferData.buffer = buffer; ++ bufferData.version = bufferAttribute.version; ++ bufferData.uploadCall = backend.renderer.info.calls; ++ ++ } else if ( bufferData.version < bufferAttribute.version || ++ ( bufferAttribute.usage === DynamicDrawUsage && bufferData.uploadCall !== backend.renderer.info.calls ) ) { ++ ++ // A rebuilt shader can create a fresh attribute wrapper for this existing buffer. ++ // Synchronize its dirty bytes before Attributes records the wrapper as current. ++ this.updateAttribute( attribute ); + + } + +@@ -194,6 +207,13 @@ class WebGPUAttributeUtils { + const bufferData = backend.get( bufferAttribute ); + const buffer = backend.get( bufferAttribute ).buffer; + ++ // A fresh wrapper may already have uploaded this shared buffer in the same render or compute call. ++ // Do not repeat that write after its partial ranges have been consumed. ++ if ( bufferData.version === bufferAttribute.version && ++ ( bufferAttribute.usage !== DynamicDrawUsage || bufferData.uploadCall === backend.renderer.info.calls ) ) return; ++ ++ bufferData._tnFullTriangle = false; ++ + let array = bufferAttribute.array; + + const itemSize = bufferData._itemSize; +@@ -275,6 +295,9 @@ class WebGPUAttributeUtils { + + } + ++ bufferData.version = bufferAttribute.version; ++ bufferData.uploadCall = backend.renderer.info.calls; ++ + } + + /** +@@ -361,9 +384,15 @@ class WebGPUAttributeUtils { + destroyAttribute( attribute ) { + + const backend = this.backend; +- const data = backend.get( this._getBufferAttribute( attribute ) ); ++ const bufferAttribute = this._getBufferAttribute( attribute ); ++ const data = backend.get( bufferAttribute ); ++ ++ // An interleaved attribute's GPU buffer lives on its InterleavedBuffer, and several attributes ++ // share it: destroy it once and forget it there too, as the WebGL backend does, or the next ++ // createAttribute() finds the destroyed buffer and submits it (threenative patch). ++ if ( data.buffer !== undefined ) data.buffer.destroy(); + +- data.buffer.destroy(); ++ if ( bufferAttribute !== attribute ) backend.delete( bufferAttribute ); + + backend.delete( attribute ); + +diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js +index da4bd473b..7ff28660e 100644 +--- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js ++++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js +@@ -97,18 +97,16 @@ class WebGPUBindingUtils { + + const bindingsData = backend.get( bindGroup ); + +- // check if the the bind group already has a layout ++ const entries = this._createLayoutEntries( bindGroup ); ++ const bindGroupLayoutKey = hashString( JSON.stringify( entries ) ); + + if ( bindingsData.layout ) { + +- return bindingsData.layout.layoutGPU; ++ if ( bindingsData.layoutKey === bindGroupLayoutKey ) return bindingsData.layout.layoutGPU; + +- } ++ this.deleteBindGroupData( bindGroup ); + +- // if not, assing one +- +- const entries = this._createLayoutEntries( bindGroup ); +- const bindGroupLayoutKey = hashString( JSON.stringify( entries ) ); ++ } + + // try to find an existing layout in the cache + +@@ -181,6 +179,7 @@ class WebGPUBindingUtils { + + } + ++ bindingsData._tnBindings = this._tnGroups?.get( bindGroupGPU ); + bindingsData.group = bindGroupGPU; + + } +@@ -200,6 +199,7 @@ class WebGPUBindingUtils { + + const updateRanges = binding.updateRanges; + ++ + if ( updateRanges.length === 0 ) { + + device.queue.writeBuffer( +@@ -253,6 +253,15 @@ class WebGPUBindingUtils { + + } + ++ // Keep bytes actually sent to the GPU, including partial writes; never certify mutable CPU values. ++ if ( backend.device.features?.has( 'transient-attachments' ) && binding.uniforms?.some( u => u.itemSize === 16 ) ) { ++ ++ const data = backend.get( binding ); ++ const values = new Float32Array( array.buffer, array.byteOffset, array.byteLength / 4 ); ++ if ( updateRanges.length === 0 ) data._tnUniformBytes = values.slice(); ++ else if ( data._tnUniformBytes ) for ( const range of updateRanges ) data._tnUniformBytes.set( values.subarray( range.start, range.start + range.count ), range.start ); ++ ++ } + } + + /** +@@ -329,7 +338,20 @@ class WebGPUBindingUtils { + + let resourceGPU; + +- if ( textureData.externalTexture !== undefined ) { ++ if ( binding.textureNode?.isRawTextureNode === true ) { ++ ++ if ( binding.textureNode.passNode._rawTexturesDisposed ) throw new Error( 'Raw pass attachment owner is disposed.' ); ++ const raw = binding.texture.isDepthTexture ? textureData.texture : textureData.msaaTexture; ++ if ( binding.texture.renderTarget?.samples !== 4 || raw === undefined ) throw new Error( 'Raw multisample attachment is unavailable.' ); ++ if ( textureData.rawTextureViewSource !== raw ) { ++ ++ textureData.rawTextureView = raw.createView( { dimension: '2d', aspect: binding.texture.isDepthTexture ? 'depth-only' : 'all', baseMipLevel: 0, mipLevelCount: 1 } ); ++ textureData.rawTextureViewSource = raw; ++ ++ } ++ resourceGPU = textureData.rawTextureView; ++ ++ } else if ( textureData.externalTexture !== undefined ) { + + resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); + +@@ -403,6 +425,12 @@ class WebGPUBindingUtils { + } + + const bindGroupGPU = device.createBindGroup( _bindGroupDescriptor ); ++ if ( device.features?.has( 'transient-attachments' ) ) { ++ ++ this._tnGroups ||= new WeakMap(); ++ this._tnGroups.set( bindGroupGPU, bindGroup.bindings.map( binding => ( { binding, buffer: backend.get( binding ).buffer, uniforms: binding.uniforms?.map( u => ( { name: u.name, offset: u.offset, itemSize: u.itemSize } ) ) } ) ) ); ++ ++ } + + _bindGroupDescriptor.reset(); + +@@ -500,7 +528,7 @@ class WebGPUBindingUtils { + + const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); + +- if ( primarySamples > 1 ) { ++ if ( primarySamples > 1 || binding.textureNode?.isRawTextureNode === true ) { + + texture.multisampled = true; + +@@ -538,7 +566,7 @@ class WebGPUBindingUtils { + + } else if ( type === FloatType ) { + +- if ( this.backend.hasFeature( 'float32-filterable' ) ) { ++ if ( texture.multisampled !== true && this.backend.hasFeature( 'float32-filterable' ) ) { + + texture.sampleType = GPUTextureSampleType.Float; + +@@ -630,6 +658,9 @@ class WebGPUBindingUtils { + + } + ++ bindingsData.groups = []; ++ bindingsData.versions = []; ++ + } + + /** +diff --git a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +index 764267c51..d5872652c 100644 +--- a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js ++++ b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +@@ -248,6 +248,16 @@ class WebGPUPipelineUtils { + + } + ++ if ( device.features?.has( 'transient-attachments' ) ) { ++ ++ // Capture the actual immutable GPU descriptor and compiled programs, after graph updates. ++ const positionIndex = renderObject.getAttributes().indexOf( geometry.attributes.position ); ++ const positionLayout = vertexBuffers.find( layout => layout.attributes.some( a => a.shaderLocation === positionIndex ) ); ++ const positionSlot = positionLayout?.attributes.find( a => a.shaderLocation === positionIndex ); ++ pipelineData._tnDepthProof = depthStencil.depthWriteEnabled === true && depthStencil.depthCompare === 'less-equal' && ! depthStencil.depthBias && ! depthStencil.depthBiasSlopeScale && ! _renderPipelineDescriptor.multisample.alphaToCoverageEnabled && ( _renderPipelineDescriptor.multisample.mask === undefined || _renderPipelineDescriptor.multisample.mask === 0xffffffff ) && primitiveState.topology === 'triangle-list' && ( primitiveState.cullMode === 'none' || ( primitiveState.cullMode === 'back' && primitiveState.frontFace === 'ccw' ) ) && positionLayout?.arrayStride === 12 && positionLayout.stepMode === 'vertex' && positionSlot?.format === 'float32x3' && positionSlot.offset === 0 ? { material, version: material.version, positionIndex, vertex: backend.get( vertexProgram )._tnShaderCode, fragment: backend.get( fragmentProgram )._tnShaderCode } : null; ++ ++ } ++ + // create pipeline + + device.pushErrorScope( 'validation' ); +@@ -261,6 +271,7 @@ class WebGPUPipelineUtils { + if ( promises === null ) { + + pipelineData.pipeline = device.createRenderPipeline( _renderPipelineDescriptor ); ++ if ( pipelineData._tnDepthProof ) pipelineData._tnDepthProof.pipeline = pipelineData.pipeline; + + _renderPipelineDescriptor.reset(); + +@@ -299,6 +310,15 @@ class WebGPUPipelineUtils { + + _renderPipelineDescriptor.reset(); + ++ // Close the scope before yielding so unrelated render commands cannot enter it. ++ // Handle rejection immediately even if the pipeline promise is still pending. ++ const errorScopePromise = device.popErrorScope().catch( ( err ) => { ++ ++ asyncError ??= err; ++ return null; ++ ++ } ); ++ + if ( pipelinePromise !== null ) { + + try { +@@ -313,7 +333,7 @@ class WebGPUPipelineUtils { + + } + +- const errorScope = await device.popErrorScope(); ++ const errorScope = await errorScopePromise; + + if ( errorScope !== null || asyncError !== null ) { + +diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js +index ff8ee2dba..036202ae7 100644 +--- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js ++++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js +@@ -41,6 +41,7 @@ import { + Compatibility + } from '../../../constants.js'; + import { CubeTexture } from '../../../textures/CubeTexture.js'; ++import { DepthTexture } from '../../../textures/DepthTexture.js'; + import { Texture } from '../../../textures/Texture.js'; + import { warn, error } from '../../../utils.js'; + +@@ -143,6 +144,9 @@ class WebGPUTextureUtils { + */ + this._samplerCache = new Map(); + ++ // Own default-canvas depth variants until resize, explicit disposal or teardown. ++ this._depthBuffers = new Map(); ++ + } + + /** +@@ -359,7 +363,12 @@ class WebGPUTextureUtils { + + } + +- if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { ++ // WebGPU has no 3D render attachment, so wgpu-native rejects a 3D texture that asks for ++ // one: a compute volume, or a `.cube` grade table. Only a rendered-into 3D texture keeps it. ++ const notAnAttachment3D = dimension === GPUTextureDimension.ThreeD && ++ texture.isRenderTargetTexture !== true && options.renderTarget === undefined && options.needsMipmaps !== true; ++ ++ if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat && ! notAnAttachment3D ) { + + usage |= GPUTextureUsage.RENDER_ATTACHMENT; + +@@ -432,6 +441,23 @@ class WebGPUTextureUtils { + destroyTexture( texture, isDefaultTexture = false ) { + + const backend = this.backend; ++ ++ const depthBuffers = this._depthBuffers.get( texture ); ++ ++ if ( depthBuffers !== undefined ) { ++ ++ depthBuffers._tnPrivateDepth?.texture.destroy(); ++ this._depthBuffers.delete( texture ); ++ depthBuffers.canvasTarget.removeEventListener( 'resize', depthBuffers.onDispose ); ++ depthBuffers.canvasTarget.removeEventListener( 'dispose', depthBuffers.onDispose ); ++ texture.removeEventListener( 'dispose', depthBuffers.onDispose ); ++ for ( const buffer of depthBuffers.textures.values() ) this.destroyTexture( buffer ); ++ backend.get( depthBuffers.canvasTarget ).descriptor = undefined; ++ backend.delete( texture ); ++ return; ++ ++ } ++ + const textureData = backend.get( texture ); + + if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); +@@ -472,7 +498,7 @@ class WebGPUTextureUtils { + const colorTexture = canvasTarget.colorTexture; + const colorTextureData = backend.get( colorTexture ); + +- if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { ++ if ( colorTextureData.texture !== undefined && colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { + + return colorTextureData.texture; + +@@ -497,8 +523,8 @@ class WebGPUTextureUtils { + + // + +- colorTexture.source.width = width; +- colorTexture.source.height = height; ++ colorTexture.image.width = width; ++ colorTexture.image.height = height; + colorTexture.samples = samples; + + colorTextureData.texture = colorBuffer; +@@ -521,64 +547,59 @@ class WebGPUTextureUtils { + const canvasTarget = backend.renderer.getCanvasTarget(); + const { width, height } = backend.getDrawingBufferSize(); + const samples = backend.renderer.currentSamples; +- ++ const sampleCount = backend.utils.getSampleCount( samples ); + const depthTexture = canvasTarget.depthTexture; ++ const format = stencil ? DepthStencilFormat : DepthFormat; ++ const type = backend.renderer.reversedDepthBuffer === true ? FloatType : ( stencil ? UnsignedInt248Type : UnsignedIntType ); + +- if ( depthTexture.width === width && +- depthTexture.height === height && +- depthTexture.samples === samples && +- depthTexture.depth === depth && +- depthTexture.stencil === stencil ) { +- +- return backend.get( depthTexture ).texture; ++ let cache = this._depthBuffers.get( depthTexture ); + +- } +- +- // ++ if ( cache !== undefined && ( cache.width !== width || cache.height !== height || cache.format !== format || cache.type !== type ) ) { + +- const depthTextureGPU = backend.get( depthTexture ).texture; +- +- let format, type; +- +- if ( stencil ) { ++ this.destroyTexture( depthTexture ); ++ cache = undefined; + +- format = DepthStencilFormat; +- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; ++ } + +- } else if ( depth ) { ++ if ( cache === undefined ) { + +- format = DepthFormat; +- type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; ++ cache = { canvasTarget, width, height, format, type, textures: new Map(), onDispose: () => this.destroyTexture( depthTexture ) }; ++ this._depthBuffers.set( depthTexture, cache ); ++ canvasTarget.addEventListener( 'resize', cache.onDispose ); ++ canvasTarget.addEventListener( 'dispose', cache.onDispose ); ++ depthTexture.addEventListener( 'dispose', cache.onDispose ); + + } + +- if ( depthTextureGPU !== undefined ) { ++ // Output and direct overlay passes alternate effective samples 4 and 1. ++ // Retain at most those two buffers for this canvas size and depth format. ++ let buffer = cache.textures.get( sampleCount ); + +- if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { ++ if ( buffer === undefined ) { + +- return depthTextureGPU; +- +- } +- +- this.destroyTexture( depthTexture ); ++ buffer = new DepthTexture( width, height, type ); ++ buffer.name = 'depthBuffer'; ++ buffer.format = format; ++ buffer.samples = sampleCount; ++ this.createTexture( buffer, { width, height } ); ++ cache.textures.set( sampleCount, buffer ); + + } + +- // recreate +- ++ // Preserve the canonical texture identity and its currently selected GPU data. + depthTexture.name = 'depthBuffer'; + depthTexture.format = format; + depthTexture.type = type; + depthTexture.image.width = width; + depthTexture.image.height = height; + depthTexture.samples = samples; ++ Object.assign( backend.get( depthTexture ), backend.get( buffer ) ); + +- this.createTexture( depthTexture, { width, height } ); +- +- return backend.get( depthTexture ).texture; ++ return backend.get( buffer ).texture; + + } + ++ + /** + * Uploads the updated texture data to the GPU. + * +@@ -806,6 +827,8 @@ class WebGPUTextureUtils { + */ + dispose() { + ++ for ( const texture of this._depthBuffers.keys() ) this.destroyTexture( texture ); ++ + this._samplerCache.clear(); + + } diff --git a/packages/create-threenative/__tests__/doctor.spec.ts b/packages/create-threenative/__tests__/doctor.spec.ts index 2a465e3b95..4e3fcea2ae 100644 --- a/packages/create-threenative/__tests__/doctor.spec.ts +++ b/packages/create-threenative/__tests__/doctor.spec.ts @@ -335,6 +335,20 @@ describe("threenative doctor", () => { expect(serverChecks.map(({ detail }) => detail).join(" ")).toMatch(/threenative-engine/); }); + it("tells a project without the optional terrain addon exactly what to install", () => { + const report = diagnoseProject( + snapshot({ + readText: (relative) => (relative === ".mcp.json" ? MCP_CONFIG : "export default {}"), + resolvePackageDirectory: () => undefined, + }), + ); + const terrain = check(report, "capability search: @threenative/terrain"); + expect(terrain.status).toBe("warn"); + expect(terrain.detail).toContain("optional"); + expect(terrain.fix).toBe("Run: npm i -D @threenative/terrain"); + expect(terrain.fix).not.toContain("@0."); + }); + it("fails a resolved MCP server whose transport does not initialize", () => { const report = diagnoseProject( snapshot({ @@ -1098,6 +1112,12 @@ describe("threenative doctor edge coverage", () => { version: string; }; for (const spec of MCP_SERVER_SPECS) { + // Terrain is an optional addon the shim resolves from the project, never a core dependency + // (core -> assets -> terrain -> core would be a cycle), so its pin follows its own package. + if (spec.packageName === "@threenative/terrain") { + expect(spec.version, spec.packageName).toBe(await workspaceVersion(spec.packageName)); + continue; + } const declared = spec.packageName === "@threenative/core" ? core.version diff --git a/packages/create-threenative/__tests__/looks.spec.ts b/packages/create-threenative/__tests__/looks.spec.ts index e150f20b3c..6f76b05665 100644 --- a/packages/create-threenative/__tests__/looks.spec.ts +++ b/packages/create-threenative/__tests__/looks.spec.ts @@ -1,5 +1,6 @@ import { readFile, readdir } from "node:fs/promises"; import path from "node:path"; +import { VirtualShadowNode } from "@threenative/core"; import { Color, DataUtils, @@ -11,11 +12,12 @@ import { Scene, UnsignedByteType, } from "three"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { flat } from "../templates/platformer/src/render/materials.js"; import { palette } from "../templates/platformer/src/render/palette.js"; import { setupSky, skyTexture } from "../templates/platformer/src/render/sky.js"; import { skyEnvironment } from "../templates/platformer/src/render/skyEnvironment.js"; +import { setupLighting } from "../templates/starter/src/render/lighting.js"; import { qualityPreset } from "../templates/starter/src/render/quality.js"; const starter = path.resolve("packages/create-threenative/templates/starter"); @@ -49,6 +51,36 @@ describe("starter visual floor", () => { expect(source).not.toContain("@threenative/"); }); + it("should keep one arena shadow map unless a world names cached shadow extents", () => { + const scene = new Scene(); + const renderer = { shadowMap: { enabled: false, type: 0 } }; + const arena = setupLighting(scene, renderer); + expect(arena.key.shadow.mapSize.width).toBe(4096); + expect(arena.key.shadow.camera.right).toBe(14); + expect(arena.key.shadow.shadowNode).toBeUndefined(); + expect(arena.shadows).toBeUndefined(); + arena.dispose(); + expect(arena.key.parent).toBeNull(); + + // The phone map size and the 14 m near window carry over; the world names only the wider ones. + const forest = setupLighting(scene, renderer, true, [48, 160], VirtualShadowNode); + const shadows = forest.shadows; + if (shadows === undefined) throw new Error("expected the cached shadow override"); + expect(forest.key.shadow.shadowNode).toBe(shadows); + expect(shadows.options.clipExtents).toEqual([14, 48, 160]); + expect(shadows.options.mapSize).toBe(2048); + const disposeShadows = vi.spyOn(shadows, "dispose"); + forest.dispose(); + expect(disposeShadows).toHaveBeenCalledOnce(); + expect(forest.key.parent).toBeNull(); + + expect(() => setupLighting(scene, renderer, false, [14], VirtualShadowNode)).toThrow( + RangeError, + ); + expect(() => setupLighting(scene, renderer, false, [48])).toThrow(/VirtualShadowNode/u); + expect(scene.children).toHaveLength(0); + }); + it("should route setupPost through the framework output node", async () => { const [post, environment] = await Promise.all([ readFile(path.join(starter, "src/render/postprocessing.ts"), "utf8"), diff --git a/packages/create-threenative/__tests__/publication.spec.ts b/packages/create-threenative/__tests__/publication.spec.ts index d1a076ce40..841a7815f6 100644 --- a/packages/create-threenative/__tests__/publication.spec.ts +++ b/packages/create-threenative/__tests__/publication.spec.ts @@ -17,7 +17,7 @@ const roots = publicWorkspacePackages(path.resolve("packages", "..")).map(({ dir interface IManifest { dependencies?: Record; devDependencies?: Record; - bin?: Record; + bin?: string | Record; exports?: Record>; name: string; optionalDependencies?: Record; @@ -225,7 +225,12 @@ describe("registry publication", () => { `${packageJson.name} ${entry} -> ${target}`, ).resolves.toBeUndefined(); } - for (const [name, target] of Object.entries(packageJson.bin ?? {})) + // npm's string form names the one binary after the package. + const bins = + typeof packageJson.bin === "string" + ? { [packageJson.name]: packageJson.bin } + : (packageJson.bin ?? {}); + for (const [name, target] of Object.entries(bins)) await expect( access(path.resolve("packages", root, target)), `${packageJson.name} bin ${name} -> ${target}`, diff --git a/packages/create-threenative/__tests__/scaffold.spec.ts b/packages/create-threenative/__tests__/scaffold.spec.ts index 6c6de1cf0a..a4308d0649 100644 --- a/packages/create-threenative/__tests__/scaffold.spec.ts +++ b/packages/create-threenative/__tests__/scaffold.spec.ts @@ -189,8 +189,14 @@ const BUG_REPORT_SKILL_PATHS = [ // Three patch. Restoring the previous patch recovers every prior fingerprint. // Recomputed from clean committed c22e5ba8d for the independently reviewed bounded-upload // package patch embedded by every scaffold. No template, capability or appearance change. +// PRD-584: the terrain MCP server was added to every template (2026-10-11), so all thirteen trees move. +// PRD-591/593: measured all thirteen actual trees after readiness manifest and generated patch changes. +// Restoring only the previous shared patch/manifest/reference bytes recovers every prior hash. +// PR483 integration: measured all thirteen trees after merging develop and regenerating the shared patch. +// Recomputed after f5fc41965: only the canonical Three patch copied into all thirteen kits changed. +// Recomputed after adaeb514f: overlaying only its parent Three patch in generated scratch trees +// recovers all thirteen prior fingerprints; current trees retain the geometry-cache fix. const PRD_201_PARENT_SCAFFOLD_HASHES: Readonly> = { - // PRD-569 merge: measured all thirteen createProject trees; action-rpg retains onCull. // PRD-563: every kit's render/quality.ts and scenes carry the GPU-class start tier. // Measured by the actual thirteen-tree createProject equality test on the Oct 6 // af6dfc8 + ba72eed publication merge, after capability and canonical patch generation. @@ -234,39 +240,21 @@ const PRD_201_PARENT_SCAFFOLD_HASHES: Readonly> = { // strata branch: measured through createProject on the merged tree. // Recomputed 2026-10-10 after integrating develop 1a5314a1: measured through createProject on // the merged tree, including its latest template changes. - // PRD-560 (2026-10-10): the eleven FogExp2 kits carry height fog (heightFog.ts, sky.ts, AGENTS.md); - // rain and tower-defense had no height-fog change in that merge. Seven kits at the 150 m scale then - // took density 0.0018 so ridges read clearer than low ground (AC-1b). - // Recomputed through all thirteen actual createProject trees after merging develop 2d2124792 - // into PRD-571; these fingerprints cover both the exposure and landed base changes. - // Recomputed 2026-10-11 after merging develop b4e74627f into PRD-563: measured through - // createProject on the merged tree, including the GPU-class start tier in every kit. - // Recomputed 2026-10-11 for the PRD-563 render-chain tier fix: ten kits' medium presets now request - // the medium chain, the four flat-high scenarios gained family and software rows, and every - // render-chain scenario names the GPU families. Measured by the same actual-tree test; snow moves not. - // Recomputed 2026-10-11 for the PRD-563 grade table upload as half floats: only starter's grade.ts - // and postprocessing.ts changed, and the actual-tree test moved only starter. - // PRD-560: fingerprints include both height fog and PRD-571 histogram exposure. - // Recomputed 2026-10-11 on the merge of develop e6ed27869 into PRD-563: measured through - // createProject on the merged tree. - // PRD-566/569/609 merge: measured all thirteen trees; action-rpg carries emitParticle bursts and onCull instructions. - // Recomputed 2026-10-13: the onCull paragraph in action-rpg AGENTS.md folded to one line to meet the 100-line budget. - // PRD-565: only the copied canonical Three patch changes; substituting its parent bytes - // recovers all thirteen prior hashes. Measured through the actual createProject tree test. - "action-rpg": "2ad57690b87fe80faa26931ea11d6ea210ac787bb58c7bb0f83e7b653426eeb7", - minimal: "6e9d255a6033e239e73dfabec172dfebb4a88849c01ca461d476ab8b17b91cbd", - platformer: "2ccf93236a0d6c1b8aa00bd3f86585427149f3788640dccb491f8d451c2c6894", - puzzle: "a25a9129f070b8a4cd25c1d3a174950b0367eba85d5a414b62325a571e2c40f3", - racing: "0fb3b6a6eacfc54acd74ae086cb5502789fdfe83e0466306202053843314f663", - rain: "9523312a5c0573b6ae886bd63ade36922a86f7a9b8e3730f7ead743609357bd7", - rts: "8a8d80b543d77717243274896b5b7a6a723b92e3a3f252fd470b2fb6dcfc12c8", - runner: "b14123d911dc3eb7d73e1ab3f91269b480a028938efff4a2bea4f56f2e570906", + // Recomputed after integrating develop 5f7143895 (PRD-609 particle bursts). + "action-rpg": "b6aecbc65d7a87a19a2f2cbca65c611a0e78d78fa9eb540714f61c6b49579c9d", + minimal: "927005ce50373794e0a59084bac5c5cceec70826d32e6cffd6440ccbea33428c", + platformer: "c05b9fd1a2544f53fd4a65dfd5977586f4a8e5618b90b3d47efa47454060d732", + puzzle: "e720807ac1787067ccb21b2b7adaab039c2a2ba70c3c22010bcad4c4f27d9b9c", + racing: "11c80833c613f4fab7b4761cf6a021d47a9662fe71315b5c49aecb98011d389c", + rain: "c95078148f342b29c9ab4d49b8c04df4f57b3123e294799cd1b7efbd658acec9", + rts: "f1c8ddc4fc8ad6aa7c235e17cbb6ff87832f27a70e2513b4f07abc949c49b7d2", + runner: "3649044ab94688fb8c5cb807ff558d710898337ebf1637f3799802a3fa365729", // Initial finite-height readiness plus its scene-owned lifecycle helper and mirrored docs. - sailing: "73da167ba1fdc6360c58e61b51a2f3dba047fbe1c4e88648e9bce4abb64325da", - shooter: "ca09b0b25f3ba689af53772d82be0978d82cdaff8f997b04b160874428ec3e54", - snow: "482c1c7f1cd2f233d43cc3fd378c983b4ab8f13dca4bd6fca008e5b120cb0705", - starter: "74df32f5ea8f1aeeea4d2eaf9c423f2fca93fbb1fa392de7acdb6b8bd994db8c", - "tower-defense": "7d816e530aee8842027a1666782d96c51b2cbfde55c4c23a7cdd1b0fd6a267ac", + sailing: "7362b2a5b8314a1bb7c7df176dbc47018389d9ff824ea39d984774516c96e9e6", + shooter: "60e3aacb4fe2e398dbcd59485fdcd6aa5108b507fd899a4efe81614596312598", + snow: "d7ded1eea863a08e164407fe3061c865151c09cde3627de15d39571e2b5f244c", + starter: "204402ed9b34b907bf2e4c047e0f929f98afe6500e6ab10342dee6942937822b", + "tower-defense": "b387b2fa3e7c0a719a99d8ececb4f3964d881415a8b1fe036b2f7c0413318fb4", }; const GENERATED_SCAFFOLD_METADATA = @@ -541,6 +529,25 @@ describe("create-threenative", () => { expect(source.match(/replaceAll\(placeholder, value\)/gu)).toHaveLength(1); }); + it.each([undefined, {}])( + "pins the supported packageManager in generated projects (%j)", + async (packageSources) => { + const root = await makeTempDir("threenative-scaffold-pm-"); + try { + const created = await createProject( + { install: false, packageSources, target: "pm-game", template: "starter" }, + root, + ); + const manifest = JSON.parse( + await readFile(path.join(created.target, "package.json"), "utf8"), + ) as { packageManager?: string }; + expect(manifest.packageManager).toBe("pnpm@10.25.0"); + } finally { + await rm(root, { recursive: true, force: true }); + } + }, + ); + it("keeps every no-install scaffold tree byte-stable against the PRD parent", async () => { const root = await makeTempDir("threenative-scaffold-stability-"); try { @@ -1534,6 +1541,10 @@ describe("create-threenative", () => { command: "node", args: [`${CORE_SHIM}/blender.mjs`], }, + "threenative-terrain": { + command: "node", + args: [`${CORE_SHIM}/terrain.mjs`], + }, }, }); await withBrokenTemplateFile("starter/.mcp.json", broken, async (templates) => { @@ -1570,6 +1581,10 @@ describe("create-threenative", () => { command: "node", args: [`${CORE_SHIM}/blender.mjs`], }, + "threenative-terrain": { + command: "node", + args: [`${CORE_SHIM}/terrain.mjs`], + }, }, }); await withBrokenTemplateFile("starter/.mcp.json", broken, async (templates) => { diff --git a/packages/create-threenative/__tests__/terrain-kit.spec.ts b/packages/create-threenative/__tests__/terrain-kit.spec.ts new file mode 100644 index 0000000000..f255fb20a1 --- /dev/null +++ b/packages/create-threenative/__tests__/terrain-kit.spec.ts @@ -0,0 +1,322 @@ +import { execFile } from "node:child_process"; +import { + copyFile, + cp, + lstat, + mkdir, + readFile, + readdir, + rm, + symlink, + writeFile, +} from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { beforeAll, describe, expect, it } from "vitest"; +import { makeTempDir } from "../../../test-support/temp-dir.js"; +import { copyTerrainKit } from "../src/terrain-kit.js"; + +const run = promisify(execFile); +const packageRoot = fileURLToPath(new URL("..", import.meta.url)); +const templates = path.join(packageRoot, "templates"); +const staged = path.join(packageRoot, "template-assets", "terrain"); +const starter = fileURLToPath(new URL("../../terrain/starter", import.meta.url)); +const KITS = ["alpine", "coastal", "desert", "forest", "tundra"]; +const INPUTS = ["assets.json", "bake.mjs", "heightmap.r16", "recipe.json", "surface.json"]; +const COPIED = [...INPUTS, "credits.json"].sort(); +const SKY_HDRI = "hdri/kloofendal_48d_partly_cloudy_1k.hdr"; +const starterCredits = fileURLToPath( + new URL("../../terrain/starter-assets/credits.json", import.meta.url), +); + +async function scratch(): Promise { + return makeTempDir("terrain-kit-"); +} + +// Declared credits for a test. It names no provider and is not a provenance record. +function fixtureCredits(kit: string): string { + return `${JSON.stringify({ kit, note: "Test stand-in. Not a provenance record.", sets: {}, prepared: { assets: [] } }, null, 2)}\n`; +} + +// Per-test packaged kits. Each kit copies the original starter inputs and declares a credits file. +async function packagedKits(root: string, kits: readonly string[]): Promise { + for (const kit of kits) { + const kitDir = path.join(root, "template-assets", "terrain", kit); + await mkdir(kitDir, { recursive: true }); + for (const file of INPUTS) + await copyFile(path.join(starter, kit, file), path.join(kitDir, file)); + await writeFile(path.join(kitDir, "credits.json"), fixtureCredits(kit)); + } + return path.join(root, "templates"); +} + +async function expectStarterBytes(target: string, kit: string): Promise { + for (const file of INPUTS) { + expect(await readFile(path.join(target, file))).toEqual( + await readFile(path.join(starter, kit, file)), + ); + } +} + +describe("terrain kit staged integration", () => { + beforeAll(async () => { + await run(process.execPath, [path.join(packageRoot, "scripts", "stage-terrain-kits.mjs")]); + }, 120_000); + + it("stages the five inputs and one credits record per starter kit, byte-identical", async () => { + expect((await readdir(staged)).sort()).toEqual(KITS); + for (const kit of KITS) { + expect((await readdir(path.join(staged, kit))).sort()).toEqual(COPIED); + for (const file of INPUTS) { + expect(await readFile(path.join(staged, kit, file))).toEqual( + await readFile(path.join(starter, kit, file)), + ); + } + } + }); + + it("keeps only the sets and props each kit's own tables cite", async () => { + const alpine = JSON.parse(await readFile(path.join(staged, "alpine", "credits.json"), "utf8")); + const coastal = JSON.parse( + await readFile(path.join(staged, "coastal", "credits.json"), "utf8"), + ); + expect(Object.keys(alpine.sets).sort()).toEqual([ + "forest_ground_04", + "rock_boulder_dry", + "snow_02", + ]); + expect(new Set(alpine.prepared.assets.map((asset: { url: string }) => asset.url))).toEqual( + new Set(["https://polyhaven.com/a/boulder_01"]), + ); + expect(coastal.prepared.assets.map((asset: { url: string }) => asset.url)).toContain( + "https://polyhaven.com/a/fir_tree_01", + ); + }); + + it("cites the sky HDRI record in every kit, deep-equal to its starter-assets record", async () => { + const source = JSON.parse(await readFile(starterCredits, "utf8")); + const record = source.files.find((entry: { file: string }) => entry.file === SKY_HDRI); + expect(record, SKY_HDRI).toBeDefined(); + for (const kit of KITS) { + const credits = JSON.parse(await readFile(path.join(staged, kit, "credits.json"), "utf8")); + const cited = credits.files.filter((entry: { file: string }) => entry.file === SKY_HDRI); + expect(cited, kit).toEqual([record]); + } + }); + + it("copies only the requested kit, byte-identical to its staged files", async () => { + const target = path.join(await scratch(), "terrain", "alpine"); + await copyTerrainKit("alpine", target, templates); + expect((await readdir(target)).sort()).toEqual(COPIED); + for (const file of COPIED) { + expect(await readFile(path.join(target, file))).toEqual( + await readFile(path.join(staged, "alpine", file)), + ); + } + }); + + it("ships the staged kits in the packed tarball, and copies one from the packed install", async () => { + const archives = path.join(await scratch(), "archive"); + await mkdir(archives, { recursive: true }); + await run("pnpm", ["pack", "--pack-destination", archives], { cwd: packageRoot }); + const archive = (await readdir(archives)).find((entry) => entry.endsWith(".tgz")); + if (archive === undefined) throw new Error("create-threenative pack produced no tarball"); + await run("tar", [ + "-xzf", + path.join(archives, archive), + "-C", + archives, + "package/template-assets/terrain", + ]); + const packed = path.join(archives, "package", "template-assets", "terrain"); + expect((await readdir(packed)).sort()).toEqual(KITS); + for (const kit of KITS) { + expect((await readdir(path.join(packed, kit))).sort()).toEqual(COPIED); + for (const file of INPUTS) { + expect(await readFile(path.join(packed, kit, file))).toEqual( + await readFile(path.join(starter, kit, file)), + ); + } + } + + // The consumer sits outside this workspace; its only create-threenative is the tarball. + const consumer = await scratch(); + const installed = path.join(consumer, "node_modules", "create-threenative"); + await mkdir(installed, { recursive: true }); + await run("tar", [ + "-xzf", + path.join(archives, archive), + "-C", + installed, + "--strip-components=1", + ]); + const script = path.join(consumer, "copy-kit.mjs"); + await writeFile( + script, + 'import { copyTerrainKit } from "create-threenative/terrain-kit";\nawait copyTerrainKit("alpine", process.argv[2]);\n', + ); + const target = path.join(consumer, "kit"); + await run(process.execPath, [script, target], { cwd: consumer }); + expect((await readdir(target)).sort()).toEqual(COPIED); + for (const file of COPIED) { + expect(await readFile(path.join(target, file))).toEqual( + await readFile(path.join(packed, "alpine", file)), + ); + } + }, 300_000); +}); + +describe("terrain kit copying", () => { + it.each(KITS)("copies %s byte-identical to its packaged inputs", async (kit) => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, [kit]); + const target = path.join(root, "kit"); + await copyTerrainKit(kit, target, templatesRoot); + expect((await readdir(target)).sort()).toEqual(COPIED); + await expectStarterBytes(target, kit); + expect(await readFile(path.join(target, "credits.json"))).toEqual( + await readFile(path.join(root, "template-assets", "terrain", kit, "credits.json")), + ); + }); + + it("copies only the requested kit when all five are packaged", async () => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, KITS); + const target = path.join(root, "kit"); + await copyTerrainKit("desert", target, templatesRoot); + expect((await readdir(target)).sort()).toEqual(COPIED); + await expectStarterBytes(target, "desert"); + }); + + it("copies no world files, asset binaries or sibling files", async () => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, ["alpine"]); + const kitDir = path.join(root, "template-assets", "terrain", "alpine"); + for (const sibling of ["world.ts", "sky.ts", "README.md"]) { + await copyFile(path.join(starter, "alpine", sibling), path.join(kitDir, sibling)); + } + await mkdir(path.join(kitDir, "assets")); + await writeFile(path.join(kitDir, "assets", "rock.bin"), Buffer.from([0, 1, 2, 3])); + const target = path.join(root, "kit"); + await copyTerrainKit("alpine", target, templatesRoot); + expect((await readdir(target)).sort()).toEqual(COPIED); + }); + + it.each(["", "../alpine", "alpine/../coastal", "/alpine", "Alpine", "alpine.bak", "missing"])( + "rejects kit name %j and writes nothing", + async (kit) => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, ["alpine"]); + const target = path.join(root, "kit"); + await expect(copyTerrainKit(kit, target, templatesRoot)).rejects.toThrow( + /TN_TERRAIN_KIT_NAME/u, + ); + await expect(lstat(target)).rejects.toMatchObject({ code: "ENOENT" }); + }, + ); + + it("rejects a name that climbs out of the packaged terrain root", async () => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, ["alpine"]); + // A complete decoy beside the terrain root. Without the name check, "../alpine" would copy it. + await cp( + path.join(root, "template-assets", "terrain", "alpine"), + path.join(root, "template-assets", "alpine"), + { + recursive: true, + }, + ); + const target = path.join(root, "kit"); + await expect(copyTerrainKit("../alpine", target, templatesRoot)).rejects.toThrow( + /TN_TERRAIN_KIT_NAME/u, + ); + await expect(lstat(target)).rejects.toMatchObject({ code: "ENOENT" }); + }); + + it("fails closed when no kit is packaged", async () => { + const target = path.join(await scratch(), "kit"); + const root = path.join(await scratch(), "templates"); + await expect(copyTerrainKit("alpine", target, root)).rejects.toThrow(/TN_TERRAIN_KIT_UNKNOWN/u); + await expect(lstat(target)).rejects.toMatchObject({ code: "ENOENT" }); + }); + + it("never overwrites an existing target", async () => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, ["alpine"]); + const target = path.join(root, "kit"); + await mkdir(target); + await writeFile(path.join(target, "recipe.json"), "mine"); + await expect(copyTerrainKit("alpine", target, templatesRoot)).rejects.toThrow( + /TN_TERRAIN_KIT_EXISTS/u, + ); + expect(await readdir(target)).toEqual(["recipe.json"]); + expect(await readFile(path.join(target, "recipe.json"), "utf8")).toBe("mine"); + }); + + it("rejects a symlink target and writes nothing through it", async () => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, ["alpine"]); + const real = path.join(root, "real"); + await mkdir(real); + const target = path.join(root, "kit"); + await symlink(real, target, "dir"); + await expect(copyTerrainKit("alpine", target, templatesRoot)).rejects.toThrow( + /TN_TERRAIN_KIT_SYMLINK/u, + ); + expect(await readdir(real)).toEqual([]); + }); + + it("rejects a target under a symlinked parent and writes nothing through it", async () => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, ["alpine"]); + const real = path.join(root, "real"); + await mkdir(real); + await symlink(real, path.join(root, "link"), "dir"); + const target = path.join(root, "link", "kit"); + await expect(copyTerrainKit("alpine", target, templatesRoot)).rejects.toThrow( + /TN_TERRAIN_KIT_SYMLINK/u, + ); + expect(await readdir(real)).toEqual([]); + }); + + it("rejects a symlink above missing parents and creates no folders through it", async () => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, ["alpine"]); + const real = path.join(root, "real"); + await mkdir(real); + await symlink(real, path.join(root, "link"), "dir"); + const target = path.join(root, "link", "missing", "deeper", "kit"); + await expect(copyTerrainKit("alpine", target, templatesRoot)).rejects.toThrow( + /TN_TERRAIN_KIT_SYMLINK/u, + ); + expect(await readdir(real)).toEqual([]); + }); + + it("rejects a packaged input that is a symlink", async () => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, ["alpine"]); + const kitDir = path.join(root, "template-assets", "terrain", "alpine"); + await rm(path.join(kitDir, "recipe.json")); + await symlink(path.join(starter, "alpine", "recipe.json"), path.join(kitDir, "recipe.json")); + const target = path.join(root, "kit"); + await expect(copyTerrainKit("alpine", target, templatesRoot)).rejects.toThrow( + /TN_TERRAIN_KIT_SOURCE/u, + ); + await expect(lstat(target)).rejects.toMatchObject({ code: "ENOENT" }); + }); + + it("normalizes a raw target that climbs through a symlink, and writes nothing outside it", async () => { + const root = await scratch(); + const templatesRoot = await packagedKits(root, ["alpine"]); + const outside = path.join(root, "outside"); + await mkdir(path.join(outside, "inner"), { recursive: true }); + await symlink(path.join(outside, "inner"), path.join(root, "link"), "dir"); + // Raw string on purpose. path.join would normalize the ".." before the call. + const target = `${root}/link/../kit`; + await copyTerrainKit("alpine", target, templatesRoot); + expect((await readdir(path.join(root, "kit"))).sort()).toEqual(COPIED); + expect(await readdir(outside)).toEqual(["inner"]); + expect(await readdir(path.join(outside, "inner"))).toEqual([]); + }); +}); diff --git a/packages/create-threenative/agent-docs/references/capability-reference.md b/packages/create-threenative/agent-docs/references/capability-reference.md index ca37b8710a..f8d717860f 100644 --- a/packages/create-threenative/agent-docs/references/capability-reference.md +++ b/packages/create-threenative/agent-docs/references/capability-reference.md @@ -70,6 +70,20 @@ export async function compileAssets( options: IAssetCompileOptions = { … } const result = await compileAssets({ source: "assets", output: "public" }); ``` +### `cookTerrainWorld` + +`function` — Bake evaluated terrain and static species into an instanced GLB and streaming world package. + +```ts +export function cookTerrainWorld( options: IPreparedTerrainWorldOptions, ): Promise< { … } +``` + +- **Use when:** bake a terrain world headlessly into a GLB with instanced trees and rocks · generate a forest world for my game + +```ts +await cookTerrainWorld({ state, species, out: "assets/world" }); +``` + ### `dedupeMaterials` `function` — Collapse materials that became identical once their textures shared an atlas page, and count the buckets a merge would find — before and after — so the promise can be checked rather than made. @@ -419,7 +433,7 @@ if (!uvsTile(uv)) rewriteUvs(uv, transforms.get(source)!); ### `addInSlices` -`function` — Attach hundreds of built objects to the scene in slices, presenting a frame between each. +`function` — Attach hundreds of built objects to the scene in slices with an 8 ms default budget, presenting a frame between each. ```ts export async function addInSlices( objects: Iterable, add: (object: T, index: number) => void, options: IAddInSlicesOptions = { … } @@ -2966,6 +2980,22 @@ const mirror = subscribeUiState(bridge); ## `@threenative/core/world` +### `applyTerrainSurface` + +`function` — Apply a cooked GLB's package-authored layered surface to its marked terrain meshes. `url` is the loaded GLB URL; extras pointers are relative to it. Uses the same asset/fetch path as loadTerrainSplat on web and native. The caller owns the assigned materials' lifetime. + +```ts +export async function applyTerrainSurface( root: Object3D, options: ILoadTerrainSplatOptions, ): Promise { … } +``` + +- **Use when:** apply the authored layered terrain surface after loading a cooked world GLB +- **Constraints:** url names the loaded GLB; tnTerrainSurface extras name a relative world package JSON +- **Overrides:** every appearance input comes from the package table or loadTerrainSplat options + +```ts +await applyTerrainSurface(world.scene, { assets: ctx.assets, renderer: ctx.renderer, url: "world/world.glb" }); +``` + ### `cellPlacements` `function` — Borrow the run's placement records as a live view over the placement buffer. @@ -3155,7 +3185,7 @@ export class WorldCells extends Group implements IComputeDriven { … } ``` - **Use when:** stream a large Blender-authored world by cell instead of one huge GLB · keep scattered props and hand-placed chunks resident around a moving player · honour per-asset draw distances and hard streaming budgets without a mid-frame throw -- **Constraints:** surface is the game's; this class creates no material, colour or geometry · budgets are hard caps that report pressure instead of over-committing · model loads are bounded by `concurrency` (default 12) across every resident cell, not per cell · refilters are bounded by `rebuildsPerUpdate` (default 16) per update, nearest cell first · admission is bounded by `admissionBudgetMs` (default 2) per update across every path, plus at most one unit each for terrain and props; while props are queued terrain takes at most half, so neither starves the other, and a deferred cell keeps drawing what it has · SkinnedMesh parts are skipped; an instanced copy would draw one rest pose · a baked chain's switch distances are measured against `autoLod` (default 4 px of error over 60° and 1080 raster rows), because an instanced draw cannot select a level per instance; an asset with nonempty authored `lods` never consults it; explicit empty lists enroll only unchanged static geometry/material parts · eligible static sources can use cooked HLOD (chunk-only alongside GPU scatter, whole-cell on CPU) after compilation and an observed main-context backend submission; projected-error hysteresis requires actual source triangle reduction and no extra material draws, unsupported or shared source stays resident, and collision/shadow paths retain source representations; stats().hlod counts lifecycle and charged resources, not GPU timing or physical allocation · `prewarmed` resolves once every prewarmed shared batch has been drawn; a game with a loading screen waits on it, and `stats().pendingPrewarm` is the same gate as a number · every `asset:level:part` is one InstancedMesh for the main pass, plus one caster InstancedMesh per world-grid square of `clusterSize` on the shadow caster layer, so the main pass draws one mesh per key and a shadow level submits only the squares it covers · two definitions the asset loader resolves to one model — the same cooked `glb`, the same `lods` at the same distances, the same `maxDistance` and bounds — are one asset under the lexicographically smallest id: one model load, one set of `asset:level:part` keys, one prewarm and one refcount, released when the last cell holding any member of the group leaves the ring; `TN_WORLD_ASSET_ALIAS` reports how many of the package's assets are really distinct · the main pass mesh draws only the squares the render camera's frustum covers — on by default, narrowed once per frame for every main batch by the engine's render-cadence dispatch, never for an orthographic camera — a batch with nothing to draw is hidden rather than submitted at `count 0`, and `TN_WORLD_MAIN_CULL` reports both every five seconds · a loaded chunk is merged by material before it is added, so it submits one draw per material rather than one per node; a skinned, multi-material or morph-target mesh, one carrying a baked AutoLOD chain, and an instanced mesh past `chunkMergeMaxTriangles` (default 43,690 triangles) or with a shape over 2,048 triangles, all keep their own geometry; a material group crossing 131,072 vertices (4 MiB of position + normal + uv) is split into several meshes in traversal order instead of one giant upload, indexed parts keep their index, and `TN_WORLD_CHUNK_MERGE` reports what the merge did and the bytes it left · `shadows.castDistance` is accepted and ignored (clusters replaced it); `shadows.invalidate` is called at most once a second after streamed records changed +- **Constraints:** surface is the game's; this class creates no material, colour or geometry · budgets are hard caps that report pressure instead of over-committing · `whenSpawnReady(position, radius, timeoutMs)` snapshots a fixed region and settles from existing updates or disposal; it does not drive streaming, and `prewarmed` remains the separate draw gate · model loads are bounded by `concurrency` (default 12) across every resident cell, not per cell · refilters are bounded by `rebuildsPerUpdate` (default 16) per update, nearest cell first · admission is bounded by `admissionBudgetMs` (default 2) per update across every path, plus at most one unit each for terrain and props; while props are queued terrain takes at most half, so neither starves the other, and a deferred cell keeps drawing what it has · SkinnedMesh parts are skipped; an instanced copy would draw one rest pose · a baked chain's switch distances are measured against `autoLod` (default 4 px of error over 60° and 1080 raster rows), because an instanced draw cannot select a level per instance; an asset with nonempty authored `lods` never consults it; explicit empty lists enroll only unchanged static geometry/material parts · eligible static sources can use cooked HLOD (chunk-only alongside GPU scatter, whole-cell on CPU) after compilation and an observed main-context backend submission; projected-error hysteresis requires actual source triangle reduction and no extra material draws, unsupported or shared source stays resident, and collision/shadow paths retain source representations; stats().hlod counts lifecycle and charged resources, not GPU timing or physical allocation · `prewarmed` resolves once every prewarmed shared batch has been drawn; a game with a loading screen waits on it, and `stats().pendingPrewarm` is the same gate as a number · every `asset:level:part` is one InstancedMesh for the main pass, plus one caster InstancedMesh per world-grid square of `clusterSize` on the shadow caster layer, so the main pass draws one mesh per key and a shadow level submits only the squares it covers · two definitions the asset loader resolves to one model — the same cooked `glb`, the same `lods` at the same distances, the same `maxDistance` and bounds — are one asset under the lexicographically smallest id: one model load, one set of `asset:level:part` keys, one prewarm and one refcount, released when the last cell holding any member of the group leaves the ring; `TN_WORLD_ASSET_ALIAS` reports how many of the package's assets are really distinct · the main pass mesh draws only the squares the render camera's frustum covers — on by default, narrowed once per frame for every main batch by the engine's render-cadence dispatch, never for an orthographic camera — a batch with nothing to draw is hidden rather than submitted at `count 0`, and `TN_WORLD_MAIN_CULL` reports both every five seconds · a loaded chunk is merged by material before it is added, so it submits one draw per material rather than one per node; a skinned, multi-material or morph-target mesh, one carrying a baked AutoLOD chain, and an instanced mesh past `chunkMergeMaxTriangles` (default 43,690 triangles) or with a shape over 2,048 triangles, all keep their own geometry; a material group crossing 131,072 vertices (4 MiB of position + normal + uv) is split into several meshes in traversal order instead of one giant upload, indexed parts keep their index, and `TN_WORLD_CHUNK_MERGE` reports what the merge did and the bytes it left · `shadows.castDistance` is accepted and ignored (clusters replaced it); `shadows.invalidate` is called at most once a second after streamed records changed - **Overrides:** `hlod: false` keeps source rendering; ring, budgets, terrain tile size/resolution, terrain stream and collider radius, `transparentScatter`, `clusterSize` and `shadows.invalidate`, load `concurrency`, `rebuildsPerUpdate`, `admissionBudgetMs` and the package's per-asset maxDistance ```ts @@ -5872,7 +5902,7 @@ const gradient = gradientAt(new Terrain({ resolution: 17 }).evaluate(), 0, 0); `function` — Recovered numerical/project exports; GLB/runtime outputs contain terrain only. ```ts -export async function makeExport( state: ITerrainState, recipe: ITerrainDocument, kind: ExportKind, ): Promise { … } +export function makeExport( state: ITerrainState | null, recipe: ITerrainDocument, kind: "project", ): Promise; ``` - **Use when:** export a terrain recipe and its numerical runtime data @@ -6033,6 +6063,70 @@ export function checkEnvironmentAssets( environment: IEnvironment, assets: reado checkEnvironmentAssets({ sky: { image: "dusk" } }, document.assets ?? []); ``` +### `createEditorSelection` + +`function` — Gesture and transaction mechanics; the caller owns the controls, proxy and all rendering. + +```ts +export function createEditorSelection( { … } +``` + +- **Use when:** drive terrain-editor selection, gizmo drags, undo and saved transactions +- **Constraints:** the caller supplies the TransformControls, proxy, controller and every drawing callback +- **Requires:** npm i -D @threenative/terrain + +```ts +const selection = createEditorSelection({ gizmo, proxy, controller, grounding, surface, defaultFootprintSize, refreshPose, drawFootprint, restorePlacement, previewPlacement, setSaving, selectedChanged, setNavigation, message }); +``` + +### `editorPlacementCenter` + +`function` — Transform the caller's measured geometry centre into the evaluated placement's world space. + +```ts +export function editorPlacementCenter(point: Vector3, pose: IPlacementOverride): Vector3 { … } +``` + +- **Use when:** find the world-space centre of a placed terrain-editor instance +- **Constraints:** the point is multiplied in place; the pose needs finite position, quaternion and scale +- **Requires:** npm i -D @threenative/terrain + +```ts +const centre = editorPlacementCenter(localCentre.clone(), placement); +``` + +### `editorPointToClient` + +`function` — Project an injected world point to client pixels in the caller's viewport. + +```ts +export function editorPointToClient( point: Vector3, camera: Camera, box: { … } +``` + +- **Use when:** convert a terrain-editor world point to viewport client pixels +- **Constraints:** the point is projected in place; pass a clone to keep the original +- **Requires:** npm i -D @threenative/terrain + +```ts +const [x, y] = editorPointToClient(point.clone(), camera, canvas.getBoundingClientRect()); +``` + +### `editorSurfaceHeight` + +`function` — Query the rendered heightfield, including its triangulation rather than an interpolated bake. + +```ts +export function editorSurfaceHeight( ctx: { … } +``` + +- **Use when:** query terrain-editor surface elevation at world X/Z coordinates +- **Constraints:** the supplied mesh and raycast determine the surface; missing intersections return undefined +- **Requires:** npm i -D @threenative/terrain + +```ts +const height = editorSurfaceHeight(ctx, terrainMesh, x, z); +``` + ### `focusCamera` `function` — Frame a point, prop, landmark or region for the live viewport and projection. @summary Frame a focus target with an editor observation camera @@ -6084,6 +6178,22 @@ export function inspectSpatial( state: ITerrainState, revision: string, query: I const observation = inspectSpatial(state, revision, { kind: "point", at: [0, 0] }); ``` +### `landformPose` + +`function` — Recipe footprints are heights over X/Z, never independently transformed solids. + +```ts +export function landformPose(layer: Layer, worldSize: number, defaultSize: number) { … } +``` + +- **Use when:** derive a terrain-editor gizmo pose from a landform layer +- **Constraints:** the caller supplies world and default footprint sizes; unsupported layers return undefined +- **Requires:** npm i -D @threenative/terrain + +```ts +const pose = landformPose(layer, terrain.size, footprintSize); +``` + ### `mountTerrainEditor` `function` — Mount recovered terrain controls around the project-owned ThreeNative view. @@ -6101,6 +6211,38 @@ export async function mountTerrainEditor(options: { … } await mountTerrainEditor({ createView: createEditorView, materialColours: terrainPalette }); ``` +### `pickEditorPlacement` + +`function` — A foreground terrain hit occludes placements; matching uses the evaluated world position. + +```ts +export function pickEditorPlacement( ctx: Parameters[0], terrain: Mesh, meshes: readonly Object3D[], placements: Iterable< { … } +``` + +- **Use when:** pick a placed instance in the terrain editor from viewport client coordinates +- **Constraints:** the caller supplies the raycast context, terrain mesh, instanced meshes and evaluated placements +- **Requires:** npm i -D @threenative/terrain + +```ts +const id = pickEditorPlacement(ctx, terrainMesh, meshes, placements, event.clientX, event.clientY); +``` + +### `pickEditorSurface` + +`function` — Convert viewport client coordinates once; the installed picker owns ray intersection. + +```ts +export function pickEditorSurface( ctx: { … } +``` + +- **Use when:** pick a terrain-editor surface from viewport client coordinates +- **Constraints:** the caller supplies the raycast implementation and ordered targets +- **Requires:** npm i -D @threenative/terrain + +```ts +const hit = pickEditorSurface(ctx, [terrainMesh], event.clientX, event.clientY); +``` + ### `probeTerrain` `function` — Probe the evaluated surface at one world point, separating bilinear from triangle values. @@ -6203,6 +6345,22 @@ export class TerrainEditorController { … } const controller = new TerrainEditorController(editorUrl); const snapshot = await controller.snapshot(); ``` +### `transformedLandform` + +`function` — Convert a terrain-editor gizmo pose into a heightfield landform footprint. + +```ts +export function transformedLandform( layer: Layer, position: Vector3, quaternion: Quaternion, scale: Vector3, anchor: number, ): Layer { … } +``` + +- **Use when:** apply a terrain-editor gizmo transform to a landform layer +- **Constraints:** finite coordinates and positive scales are required; X/Z rotations that overhang throw +- **Requires:** npm i -D @threenative/terrain + +```ts +const next = transformedLandform(layer, position, quaternion, scale, surfaceHeight); +``` + ### `validateAsset` `function` — Validate one saved asset registration exactly as a document commit stores it. @summary Validate a registered terrain-editor asset entry @@ -6360,21 +6518,88 @@ const document = new TerrainEditorDocument("/project/terrain/world.json"); const ## `@threenative/terrain/export` -### `exportWorldGLB` +### `prepareWorldGLTF` + +`function` — Prepare the game's validated, frozen portable scene; the server cook owns GLB encoding. + +```ts +export async function prepareWorldGLTF(input: IWorldGLBInput): Promise< { … } +``` + +- **Use when:** export the terrain as a glb for another three.js project via the headless cook · prepare the terrain editor's resolved world for headless portable GLB cooking +- **Constraints:** browser authoring FileReader/canvas; all appearance and coherent water come from the game +- **Requires:** npm i -D @threenative/terrain + +```ts +const prepared = await prepareWorldGLTF(input); +``` + +## `@threenative/terrain/kit` + +### `bakeKit` + +`function` — Bake a copied kit, optionally using the caller's installed world cook. + +```ts +export async function bakeKit( url: string, { … } +``` + +- **Use when:** bake a copied terrain starter kit into its offline world package +- **Constraints:** Node authoring only; recipes, asset tables and sky paths stay in the game +- **Requires:** npm i -D @threenative/terrain + +```ts +await bakeKit(import.meta.url, { sky: "hdri/game-sky.hdr" }); +``` + +### `starterAssetsDir` + +`function` — Resolve shared starter assets locally, or download the package's pinned archive on demand. + +```ts +export async function starterAssetsDir(): Promise { … } +``` + +- **Use when:** resolve terrain kit models and textures for offline baking +- **Constraints:** Node authoring only; verifies archive size and SHA256 before atomic extraction +- **Requires:** npm i -D @threenative/terrain + +```ts +const assets = await starterAssetsDir(); +``` + +## `@threenative/terrain/starter` + +### `addStarterWorld` + +`function` — Load caller-authored starter data with one scene lifetime; all appearance comes from spec. + +```ts +export async function addStarterWorld { … } +export class PropColliders extends Object3D implements IComputeDriven { … } ``` -- **Use when:** export terrain, resolved models and final manual placement transforms as a portable GLB · export the whole terrain world as one glb for another three.js project -- **Constraints:** browser authoring (FileReader/canvas); caller supplies all appearance and coherent baked water; root and /three remain headless +- **Use when:** attach nearby prop collision bodies from baked placement records +- **Constraints:** Shapes and reach are supplied by the game; detach releases every body - **Requires:** npm i -D @threenative/terrain -- **Overrides:** actual static models, final matrices and baked PBR maps are supplied by the game ```ts -const output = await exportWorldGLB({ revision, snapshotTime: 0, state, terrain, assets, transforms }); +new PropColliders(ctx, follow, manifest, records, shapes, reach, name); ``` ## `@threenative/terrain/three` diff --git a/packages/create-threenative/agent-docs/references/terrain-authoring.md b/packages/create-threenative/agent-docs/references/terrain-authoring.md index eea015be6b..517187ddad 100644 --- a/packages/create-threenative/agent-docs/references/terrain-authoring.md +++ b/packages/create-threenative/agent-docs/references/terrain-authoring.md @@ -125,7 +125,8 @@ atomic JSON saves also update the shared view. Invalid disk saves retain the las valid preview and show a diagnostic; restore a valid save before submitting more patches. Editor imports, workers, watchers and the document stay out of the game runtime. Bake committed data before handing it to the game. The editor's GLB action and -`exportWorldGLB` from `@threenative/terrain/export` write the whole resolved world (terrain, +`prepareWorldGLTF` from `@threenative/terrain/export` prepares the resolved world for +Node `cookTerrainWorld({ preparedWorld: prepared.json })` from `@threenative/assets` (terrain, props, final transforms, embedded PBR maps); the root `encodeGLB` stays terrain-only. The full operation list, units and patch rules ship in `node_modules/@threenative/terrain/AGENT_GUIDE.md`. diff --git a/packages/create-threenative/capabilities.json b/packages/create-threenative/capabilities.json index d84efdd9c0..f5ee62434d 100644 --- a/packages/create-threenative/capabilities.json +++ b/packages/create-threenative/capabilities.json @@ -84,6 +84,23 @@ "supersedes": [], "symbol": "compileAssets" }, + { + "aliases": [], + "constraints": [], + "example": "await cookTerrainWorld({ state, species, out: \"assets/world\" });", + "importPath": "@threenative/assets", + "kind": "function", + "overrides": [], + "package": "@threenative/assets", + "signature": "export function cookTerrainWorld( options: IPreparedTerrainWorldOptions, ): Promise< { … }", + "situations": [ + "bake a terrain world headlessly into a GLB with instanced trees and rocks", + "generate a forest world for my game" + ], + "summary": "Bake evaluated terrain and static species into an instanced GLB and streaming world package.", + "supersedes": [], + "symbol": "cookTerrainWorld" + }, { "aliases": [], "constraints": [ @@ -552,7 +569,7 @@ "add hundreds of built objects to the scene without one multi-second frame", "stream a detail tier in behind a loading curtain without the page looking hung" ], - "summary": "Attach hundreds of built objects to the scene in slices, presenting a frame between each.", + "summary": "Attach hundreds of built objects to the scene in slices with an 8 ms default budget, presenting a frame between each.", "supersedes": [], "symbol": "addInSlices" }, @@ -3814,6 +3831,24 @@ "supersedes": [], "symbol": "subscribeUiState" }, + { + "aliases": [], + "constraints": [ + "url names the loaded GLB; tnTerrainSurface extras name a relative world package JSON" + ], + "example": "await applyTerrainSurface(world.scene, { assets: ctx.assets, renderer: ctx.renderer, url: \"world/world.glb\" });", + "importPath": "@threenative/core/world", + "kind": "function", + "overrides": [ + "every appearance input comes from the package table or loadTerrainSplat options" + ], + "package": "@threenative/core", + "signature": "export async function applyTerrainSurface( root: Object3D, options: ILoadTerrainSplatOptions, ): Promise { … }", + "situations": ["apply the authored layered terrain surface after loading a cooked world GLB"], + "summary": "Apply a cooked GLB's package-authored layered surface to its marked terrain meshes. `url` is the loaded GLB URL; extras pointers are relative to it. Uses the same asset/fetch path as loadTerrainSplat on web and native. The caller owns the assigned materials' lifetime.", + "supersedes": [], + "symbol": "applyTerrainSurface" + }, { "aliases": [], "constraints": [ @@ -4041,6 +4076,7 @@ "constraints": [ "surface is the game's; this class creates no material, colour or geometry", "budgets are hard caps that report pressure instead of over-committing", + "`whenSpawnReady(position, radius, timeoutMs)` snapshots a fixed region and settles from existing updates or disposal; it does not drive streaming, and `prewarmed` remains the separate draw gate", "model loads are bounded by `concurrency` (default 12) across every resident cell, not per cell", "refilters are bounded by `rebuildsPerUpdate` (default 16) per update, nearest cell first", "admission is bounded by `admissionBudgetMs` (default 2) per update across every path, plus at most one unit each for terrain and props; while props are queued terrain takes at most half, so neither starves the other, and a deferred cell keeps drawing what it has", @@ -7121,7 +7157,7 @@ "overrides": [], "package": "@threenative/terrain", "requires": ["npm i @threenative/terrain"], - "signature": "export async function makeExport( state: ITerrainState, recipe: ITerrainDocument, kind: ExportKind, ): Promise { … }", + "signature": "export function makeExport( state: ITerrainState | null, recipe: ITerrainDocument, kind: \"project\", ): Promise;", "situations": ["export a terrain recipe and its numerical runtime data"], "summary": "Recovered numerical/project exports; GLB/runtime outputs contain terrain only.", "supersedes": [], @@ -7284,6 +7320,72 @@ "supersedes": [], "symbol": "checkEnvironmentAssets" }, + { + "aliases": [], + "constraints": [ + "the caller supplies the TransformControls, proxy, controller and every drawing callback" + ], + "example": "const selection = createEditorSelection({ gizmo, proxy, controller, grounding, surface, defaultFootprintSize, refreshPose, drawFootprint, restorePlacement, previewPlacement, setSaving, selectedChanged, setNavigation, message });", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function createEditorSelection( { … }", + "situations": ["drive terrain-editor selection, gizmo drags, undo and saved transactions"], + "summary": "Gesture and transaction mechanics; the caller owns the controls, proxy and all rendering.", + "supersedes": [], + "symbol": "createEditorSelection" + }, + { + "aliases": [], + "constraints": [ + "the point is multiplied in place; the pose needs finite position, quaternion and scale" + ], + "example": "const centre = editorPlacementCenter(localCentre.clone(), placement);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function editorPlacementCenter(point: Vector3, pose: IPlacementOverride): Vector3 { … }", + "situations": ["find the world-space centre of a placed terrain-editor instance"], + "summary": "Transform the caller's measured geometry centre into the evaluated placement's world space.", + "supersedes": [], + "symbol": "editorPlacementCenter" + }, + { + "aliases": [], + "constraints": ["the point is projected in place; pass a clone to keep the original"], + "example": "const [x, y] = editorPointToClient(point.clone(), camera, canvas.getBoundingClientRect());", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function editorPointToClient( point: Vector3, camera: Camera, box: { … }", + "situations": ["convert a terrain-editor world point to viewport client pixels"], + "summary": "Project an injected world point to client pixels in the caller's viewport.", + "supersedes": [], + "symbol": "editorPointToClient" + }, + { + "aliases": [], + "constraints": [ + "the supplied mesh and raycast determine the surface; missing intersections return undefined" + ], + "example": "const height = editorSurfaceHeight(ctx, terrainMesh, x, z);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function editorSurfaceHeight( ctx: { … }", + "situations": ["query terrain-editor surface elevation at world X/Z coordinates"], + "summary": "Query the rendered heightfield, including its triangulation rather than an interpolated bake.", + "supersedes": [], + "symbol": "editorSurfaceHeight" + }, { "aliases": [], "constraints": ["framing only; an unknown target returns a named diagnostic and no camera"], @@ -7341,6 +7443,23 @@ "supersedes": [], "symbol": "inspectSpatial" }, + { + "aliases": [], + "constraints": [ + "the caller supplies world and default footprint sizes; unsupported layers return undefined" + ], + "example": "const pose = landformPose(layer, terrain.size, footprintSize);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function landformPose(layer: Layer, worldSize: number, defaultSize: number) { … }", + "situations": ["derive a terrain-editor gizmo pose from a landform layer"], + "summary": "Recipe footprints are heights over X/Z, never independently transformed solids.", + "supersedes": [], + "symbol": "landformPose" + }, { "aliases": [], "constraints": [ @@ -7358,6 +7477,40 @@ "supersedes": [], "symbol": "mountTerrainEditor" }, + { + "aliases": [], + "constraints": [ + "the caller supplies the raycast context, terrain mesh, instanced meshes and evaluated placements" + ], + "example": "const id = pickEditorPlacement(ctx, terrainMesh, meshes, placements, event.clientX, event.clientY);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function pickEditorPlacement( ctx: Parameters[0], terrain: Mesh, meshes: readonly Object3D[], placements: Iterable< { … }", + "situations": [ + "pick a placed instance in the terrain editor from viewport client coordinates" + ], + "summary": "A foreground terrain hit occludes placements; matching uses the evaluated world position.", + "supersedes": [], + "symbol": "pickEditorPlacement" + }, + { + "aliases": [], + "constraints": ["the caller supplies the raycast implementation and ordered targets"], + "example": "const hit = pickEditorSurface(ctx, [terrainMesh], event.clientX, event.clientY);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function pickEditorSurface( ctx: { … }", + "situations": ["pick a terrain-editor surface from viewport client coordinates"], + "summary": "Convert viewport client coordinates once; the installed picker owns ray intersection.", + "supersedes": [], + "symbol": "pickEditorSurface" + }, { "aliases": [], "constraints": ["authoring data only; non-finite, malformed and out-of-extent queries throw"], @@ -7464,6 +7617,23 @@ "supersedes": [], "symbol": "TerrainEditorController" }, + { + "aliases": [], + "constraints": [ + "finite coordinates and positive scales are required; X/Z rotations that overhang throw" + ], + "example": "const next = transformedLandform(layer, position, quaternion, scale, surfaceHeight);", + "importPath": "@threenative/terrain/editor", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export function transformedLandform( layer: Layer, position: Vector3, quaternion: Quaternion, scale: Vector3, anchor: number, ): Layer { … }", + "situations": ["apply a terrain-editor gizmo transform to a landform layer"], + "summary": "Convert a terrain-editor gizmo pose into a heightfield landform footprint.", + "supersedes": [], + "symbol": "transformedLandform" + }, { "aliases": [], "constraints": [ @@ -7636,24 +7806,86 @@ { "aliases": [], "constraints": [ - "browser authoring (FileReader/canvas); caller supplies all appearance and coherent baked water; root and /three remain headless" + "browser authoring FileReader/canvas; all appearance and coherent water come from the game" ], - "example": "const output = await exportWorldGLB({ revision, snapshotTime: 0, state, terrain, assets, transforms });", + "example": "const prepared = await prepareWorldGLTF(input);", "importPath": "@threenative/terrain/export", "kind": "function", - "overrides": [ - "actual static models, final matrices and baked PBR maps are supplied by the game" + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export async function prepareWorldGLTF(input: IWorldGLBInput): Promise< { … }", + "situations": [ + "export the terrain as a glb for another three.js project via the headless cook", + "prepare the terrain editor's resolved world for headless portable GLB cooking" + ], + "summary": "Prepare the game's validated, frozen portable scene; the server cook owns GLB encoding.", + "supersedes": [], + "symbol": "prepareWorldGLTF" + }, + { + "aliases": [], + "constraints": ["Node authoring only; recipes, asset tables and sky paths stay in the game"], + "example": "await bakeKit(import.meta.url, { sky: \"hdri/game-sky.hdr\" });", + "importPath": "@threenative/terrain/kit", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export async function bakeKit( url: string, { … }", + "situations": ["bake a copied terrain starter kit into its offline world package"], + "summary": "Bake a copied kit, optionally using the caller's installed world cook.", + "supersedes": [], + "symbol": "bakeKit" + }, + { + "aliases": [], + "constraints": [ + "Node authoring only; verifies archive size and SHA256 before atomic extraction" ], + "example": "const assets = await starterAssetsDir();", + "importPath": "@threenative/terrain/kit", + "kind": "function", + "overrides": [], + "package": "@threenative/terrain", + "requires": ["npm i -D @threenative/terrain"], + "signature": "export async function starterAssetsDir(): Promise { … }", + "situations": ["resolve terrain kit models and textures for offline baking"], + "summary": "Resolve shared starter assets locally, or download the package's pinned archive on demand.", + "supersedes": [], + "symbol": "starterAssetsDir" + }, + { + "aliases": [], + "constraints": ["Runtime subpath; the game supplies every appearance and collision choice"], + "example": "await addStarterWorld(ctx, follow, spec);", + "importPath": "@threenative/terrain/starter", + "kind": "function", + "overrides": [], "package": "@threenative/terrain", "requires": ["npm i -D @threenative/terrain"], - "signature": "export async function exportWorldGLB(input: IWorldGLBInput): Promise { … }", + "signature": "export async function addStarterWorld entry.file === file); + if (!record) throw missingRecord(kit, "sky HDRI", file); + for (const field of SKY_FIELDS) { + if (!record[field]) { + throw new Error( + `TN_TERRAIN_STAGE_METADATA: ${kit} sky HDRI ${file} has no ${field}. starter-assets/credits.json must set it.`, + ); + } + } + return record; +} + +// Selects the credit records one kit cites. It writes nothing. +async function planKit(kit, credits) { + const dir = path.join(terrainRoot, "starter", kit); + const surface = JSON.parse(await readFile(path.join(dir, "surface.json"), "utf8")); + const assets = JSON.parse(await readFile(path.join(dir, "assets.json"), "utf8")); + + const files = new Map(); + for (const ref of strings(surface.sources)) { + const record = credits.files.find((entry) => entry.file === ref); + if (!record) throw missingRecord(kit, "texture", ref); + files.set(record.file, record); + } + + const sets = {}; + for (const { set } of files.values()) { + if (!Object.hasOwn(credits.sets, set)) throw missingRecord(kit, "set", set); + sets[set] = credits.sets[set]; + } + + // The sky record names no set, so it joins the files after the set lookup. + const sky = await skyRecord(kit, dir, credits); + files.set(sky.file, sky); + + // A prop folder matches a prepared asset by its URL slug, such as fir_tree_01. + const preparedAssets = new Set(); + for (const ref of strings(assets)) { + const slug = ref.split("/")[0]; + const matches = credits.prepared.assets.filter((asset) => asset.url.split("/").pop() === slug); + if (matches.length === 0) throw missingRecord(kit, "prop", ref); + for (const match of matches) preparedAssets.add(match); + } + + return { + kit, + credit: { + version: credits.version, + kit, + purpose: + "Credits for the sets, textures, props and sky this kit cites. Each record is copied from starter-assets/credits.json.", + source: credits.source, + sourceUrl: credits.sourceUrl, + license: credits.license, + licenseUrl: credits.licenseUrl, + sets, + files: [...files.values()], + prepared: { assets: [...preparedAssets] }, + }, + }; +} + +async function writeKit(kit, credit) { + const out = path.join(packageRoot, "template-assets", "terrain", kit); + await mkdir(out, { recursive: true }); + for (const file of INPUTS) { + await copyFile(path.join(terrainRoot, "starter", kit, file), path.join(out, file)); + } + await writeFile(path.join(out, "credits.json"), `${JSON.stringify(credit, null, 2)}\n`); +} + +const credits = JSON.parse( + await readFile(path.join(await starterAssetsDir(), "credits.json"), "utf8"), +); +const plans = []; +for (const kit of KITS) plans.push(await planKit(kit, credits)); +for (const { kit, credit } of plans) await writeKit(kit, credit); diff --git a/packages/create-threenative/src/doctor.ts b/packages/create-threenative/src/doctor.ts index 48e0646f4d..15f408c15d 100644 --- a/packages/create-threenative/src/doctor.ts +++ b/packages/create-threenative/src/doctor.ts @@ -199,6 +199,9 @@ function requiredAndroidSdkFromRoot(root: string | undefined): number { interface IMcpServerSpec { readonly configName: string; readonly expectedArgs: string; + /** An addon the project opts into: its shim resolves the package from the project, so a project + * without it is incomplete rather than broken. */ + readonly optional: boolean; readonly packageName: string; readonly version: string; } @@ -215,6 +218,8 @@ const CORE_RESOLVED_SERVERS: ReadonlySet = new Set([ "threenative-blender", ]); +const OPTIONAL_ADDON_SERVERS: ReadonlySet = new Set(["threenative-terrain"]); + export const MCP_SERVER_SPECS: readonly IMcpServerSpec[] = Object.entries(MCP_SERVERS).map( ([configName, server]) => { const resolvesToCore = CORE_RESOLVED_SERVERS.has(configName); @@ -228,6 +233,7 @@ export const MCP_SERVER_SPECS: readonly IMcpServerSpec[] = Object.entries(MCP_SE return { configName, expectedArgs: serverEntryPath(server), + optional: OPTIONAL_ADDON_SERVERS.has(configName), packageName: resolvesToCore ? CORE_PACKAGE_NAME : (declared as IMcpPackage).name, version: resolvesToCore ? CORE_PACKAGE_VERSION : (declared as IMcpPackage).version, }; @@ -632,6 +638,14 @@ function mcpServerCheck( }; } const packageDirectory = resolvePackageDirectory(snapshot, spec.packageName); + if (packageDirectory === undefined && spec.optional) { + return { + detail: `${spec.configName} is optional and ${spec.packageName} is not installed in this project`, + fix: `Run: npm i -D ${spec.packageName}`, + name, + status: "warn", + }; + } if (packageDirectory === undefined) { return { detail: `${spec.configName} is reachable by npx only: ${spec.packageName}@${spec.version} is not installed (npx --yes ${spec.packageName}@${spec.version})`, @@ -2184,6 +2198,10 @@ export async function readProject(root: string): Promise { const mcpServerHealth = new Map(); if (SHAPE_VERIFIABLE_HOSTS.some(({ file }) => files.has(file))) { for (const spec of MCP_SERVER_SPECS) { + // An optional addon that is not installed would send the probe's shim to npx for an + // unpublished package, and report a transport failure for a project that is merely without it. + if (spec.optional && resolvePackageDirectoryFrom(projectRoot, spec.packageName) === undefined) + continue; if (existsSync(path.resolve(projectRoot, spec.expectedArgs))) { mcpServerHealth.set(spec.configName, probeMcpServer(projectRoot, spec)); } diff --git a/packages/create-threenative/src/index.ts b/packages/create-threenative/src/index.ts index d33d411377..470823d29f 100644 --- a/packages/create-threenative/src/index.ts +++ b/packages/create-threenative/src/index.ts @@ -64,6 +64,7 @@ const TEXT_FILE_EXTENSIONS = new Set([".css", ".html", ".json", ".md", ".svg", " const TEMPLATE_NAME = /^[a-z][a-z0-9-]*$/u; const SHARED_AGENT_MARKER_LINE = /^\r?\n?/gmu; const DEFAULT_TEMPLATE = "starter"; +const SUPPORTED_PACKAGE_MANAGER = "pnpm@10.25.0"; export function templateRoot(): string { return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../templates"); @@ -523,14 +524,19 @@ async function applyPackageSources( target: string, packageSources: IScaffoldOptions["packageSources"], ): Promise { - if (packageSources === undefined) return; const packagePath = path.join(target, "package.json"); const packageJson = JSON.parse(await readFile(packagePath, "utf8")) as { dependencies?: Record; devDependencies?: Record; optionalDependencies?: Record; + packageManager?: string; pnpm?: { overrides?: Record }; }; + packageJson.packageManager = SUPPORTED_PACKAGE_MANAGER; + if (packageSources === undefined) { + await writeFile(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`); + return; + } const localSource = (source: string): string => { const filePath = source.startsWith("file:") ? source.slice("file:".length) : source; return `file:${path.resolve(filePath)}`; diff --git a/packages/create-threenative/src/terrain-kit.ts b/packages/create-threenative/src/terrain-kit.ts new file mode 100644 index 0000000000..1ce9cda178 --- /dev/null +++ b/packages/create-threenative/src/terrain-kit.ts @@ -0,0 +1,98 @@ +import { lstat, mkdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +// The five starter kits. The allowlist also blocks path escape: a name reaches the disk only when it matches. +const KITS = ["alpine", "coastal", "desert", "forest", "tundra"]; +// These six files are game source. bake.mjs uses @threenative/terrain/kit to resolve pinned assets on demand. +const COPIED = [ + "assets.json", + "bake.mjs", + "credits.json", + "heightmap.r16", + "recipe.json", + "surface.json", +]; +const DEFAULT_TEMPLATES_ROOT = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "../templates", +); + +// Checks each existing parent of the target, nearest first, up to the filesystem root. +// The check skips a missing parent. It still finds a symlink above missing folders. +async function assertNoSymlinkAncestor(target: string): Promise { + let dir = path.dirname(path.resolve(target)); + for (;;) { + const info = await lstat(dir).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }); + if (info?.isSymbolicLink()) { + throw new Error( + `TN_TERRAIN_KIT_SYMLINK: A parent folder is a symlink: ${dir}. Choose a target path with real parent folders.`, + ); + } + const parent = path.dirname(dir); + if (parent === dir) return; + dir = parent; + } +} + +/** + * Copies one packaged terrain kit into a new game-owned folder. + * It checks the name, the packaged inputs and the target before it writes. + */ +export async function copyTerrainKit( + kit: string, + targetPath: string, + templatesRoot = DEFAULT_TEMPLATES_ROOT, +): Promise { + const target = path.resolve(targetPath); + if (!KITS.includes(kit)) { + throw new Error( + `TN_TERRAIN_KIT_NAME: "${kit}" is not a packaged terrain kit. Use one of: ${KITS.join(", ")}.`, + ); + } + const sourceDir = path.join(path.resolve(templatesRoot, "..", "template-assets", "terrain"), kit); + const sourceStat = await lstat(sourceDir).catch(() => null); + if (!sourceStat) + throw new Error( + `TN_TERRAIN_KIT_UNKNOWN: The packaged folder for "${kit}" is missing: ${sourceDir}.`, + ); + if (!sourceStat.isDirectory()) { + throw new Error( + `TN_TERRAIN_KIT_SOURCE: The packaged folder is not a real directory: ${sourceDir}.`, + ); + } + + // Read every input before the target exists. A symlink is not a regular file, so it fails here. + const bytes = new Map(); + for (const file of COPIED) { + const source = path.join(sourceDir, file); + if (!(await lstat(source).catch(() => null))?.isFile()) { + throw new Error( + `TN_TERRAIN_KIT_SOURCE: The packaged input is missing or not a regular file: ${source}.`, + ); + } + bytes.set(file, await readFile(source)); + } + + const targetStat = await lstat(target).catch(() => null); + if (targetStat?.isSymbolicLink()) { + throw new Error( + `TN_TERRAIN_KIT_SYMLINK: The target is a symlink. Choose a real folder: ${target}.`, + ); + } + if (targetStat) + throw new Error( + `TN_TERRAIN_KIT_EXISTS: The target already exists. Choose a new folder: ${target}.`, + ); + + // shortcut: the lstat checks are not race-proof. A parent can become a symlink after its check. + // The packaged inputs are trusted, so this limit stays. + // Add O_NOFOLLOW and a realpath walk if the roots become untrusted. + await assertNoSymlinkAncestor(target); + await mkdir(path.dirname(target), { recursive: true }); + await mkdir(target); + for (const [file, data] of bytes) await writeFile(path.join(target, file), data, { flag: "wx" }); +} diff --git a/packages/create-threenative/template-playtests/starter-forest/forest.native.playtest.json b/packages/create-threenative/template-playtests/starter-forest/forest.native.playtest.json new file mode 100644 index 0000000000..5d13c9db2a --- /dev/null +++ b/packages/create-threenative/template-playtests/starter-forest/forest.native.playtest.json @@ -0,0 +1,71 @@ +{ + "name": "native-starter-forest-walk", + "target": "desktop", + "schemaVersion": 1, + "viewport": { + "width": 1920, + "height": 1080 + }, + "warmupFrames": 60, + "subject": "player", + "steps": [ + { + "kind": "wait", + "label": "forest-loads", + "waitTicks": 240, + "release": true + }, + { + "kind": "input", + "label": "walk-forward", + "press": "ArrowUp", + "holdTicks": 300, + "release": true + } + ], + "assert": { + "components": [ + { + "allowTrivial": "Held invariant: the spawn stands on the cooked forest floor, so the figure starts attached. The end-of-walk sample proves it stays attached while the player walks.", + "component": "visualAttached", + "entity": "player", + "equals": true + }, + { + "allowTrivial": "Held invariant: the spawn rests on the cooked forest floor within 0.1 m. The end-of-walk sample proves the feet stay on the surface while the player walks.", + "component": "groundClearance", + "entity": "player", + "gte": -0.1, + "lte": 0.1 + }, + { + "allowTrivial": "Held invariant: the spawn stands on the terrain, so the body starts grounded. The end-of-walk sample proves the player still stands on the terrain.", + "component": "grounded", + "entity": "player", + "equals": true + }, + { + "allowTrivial": "Held invariant: correction is enabled while the body is grounded. The end-of-walk sample proves correction stays enabled on the terrain.", + "component": "groundCorrectionEnabled", + "entity": "player", + "equals": true + }, + { + "allowTrivial": "Held invariant: the figure starts facing north. Walking north must preserve that heading after animation updates.", + "component": "facingZ", + "entity": "player", + "lte": -0.95 + } + ], + "movement": { + "entity": "player", + "minAxisDelta": { + "axis": "-z", + "min": 0.5 + } + }, + "performance": { + "maxFrameMsP95": 16.7 + } + } +} diff --git a/packages/create-threenative/template-playtests/starter-forest/forest.playtest.json b/packages/create-threenative/template-playtests/starter-forest/forest.playtest.json new file mode 100644 index 0000000000..3ee1791b63 --- /dev/null +++ b/packages/create-threenative/template-playtests/starter-forest/forest.playtest.json @@ -0,0 +1,76 @@ +{ + "name": "starter-forest-walk", + "target": "web", + "schemaVersion": 1, + "viewport": { + "width": 1920, + "height": 1080 + }, + "warmupFrames": 60, + "subject": "player", + "steps": [ + { + "kind": "wait", + "label": "forest-loads", + "waitTicks": 240, + "release": true + }, + { + "kind": "input", + "label": "walk-forward", + "press": "ArrowUp", + "holdTicks": 300, + "release": true + } + ], + "assert": { + "components": [ + { + "allowTrivial": "Held invariant: the spawn stands on the cooked forest floor, so the figure starts attached. The end-of-walk sample proves it stays attached while the player walks.", + "component": "visualAttached", + "entity": "player", + "equals": true + }, + { + "allowTrivial": "Held invariant: the spawn rests on the cooked forest floor within 0.1 m. The end-of-walk sample proves the feet stay on the surface while the player walks.", + "component": "groundClearance", + "entity": "player", + "gte": -0.1, + "lte": 0.1 + }, + { + "allowTrivial": "Held invariant: the spawn stands on the terrain, so the body starts grounded. The end-of-walk sample proves the player still stands on the terrain.", + "component": "grounded", + "entity": "player", + "equals": true + }, + { + "allowTrivial": "Held invariant: correction is enabled while the body is grounded. The end-of-walk sample proves correction stays enabled on the terrain.", + "component": "groundCorrectionEnabled", + "entity": "player", + "equals": true + }, + { + "allowTrivial": "Held invariant: the figure starts facing north. Walking north must preserve that heading after animation updates.", + "component": "facingZ", + "entity": "player", + "lte": -0.95 + } + ], + "diagnostics": { + "noConsoleErrors": true, + "noNetworkErrors": true, + "runtimeReady": true + }, + "movement": { + "entity": "player", + "minAxisDelta": { + "axis": "-z", + "min": 0.5 + } + }, + "performance": { + "maxFrameMsP95": 16.7 + } + } +} diff --git a/packages/create-threenative/templates/action-rpg/.codex/config.toml b/packages/create-threenative/templates/action-rpg/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/action-rpg/.codex/config.toml +++ b/packages/create-threenative/templates/action-rpg/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/action-rpg/.cursor/mcp.json b/packages/create-threenative/templates/action-rpg/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/action-rpg/.cursor/mcp.json +++ b/packages/create-threenative/templates/action-rpg/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/action-rpg/.gemini/settings.json b/packages/create-threenative/templates/action-rpg/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/action-rpg/.gemini/settings.json +++ b/packages/create-threenative/templates/action-rpg/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/action-rpg/.mcp.json b/packages/create-threenative/templates/action-rpg/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/action-rpg/.mcp.json +++ b/packages/create-threenative/templates/action-rpg/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/action-rpg/.vscode/mcp.json b/packages/create-threenative/templates/action-rpg/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/action-rpg/.vscode/mcp.json +++ b/packages/create-threenative/templates/action-rpg/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/action-rpg/.zed/settings.json b/packages/create-threenative/templates/action-rpg/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/action-rpg/.zed/settings.json +++ b/packages/create-threenative/templates/action-rpg/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/action-rpg/opencode.json b/packages/create-threenative/templates/action-rpg/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/action-rpg/opencode.json +++ b/packages/create-threenative/templates/action-rpg/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/minimal/.codex/config.toml b/packages/create-threenative/templates/minimal/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/minimal/.codex/config.toml +++ b/packages/create-threenative/templates/minimal/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/minimal/.cursor/mcp.json b/packages/create-threenative/templates/minimal/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/minimal/.cursor/mcp.json +++ b/packages/create-threenative/templates/minimal/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/minimal/.gemini/settings.json b/packages/create-threenative/templates/minimal/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/minimal/.gemini/settings.json +++ b/packages/create-threenative/templates/minimal/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/minimal/.mcp.json b/packages/create-threenative/templates/minimal/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/minimal/.mcp.json +++ b/packages/create-threenative/templates/minimal/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/minimal/.vscode/mcp.json b/packages/create-threenative/templates/minimal/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/minimal/.vscode/mcp.json +++ b/packages/create-threenative/templates/minimal/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/minimal/.zed/settings.json b/packages/create-threenative/templates/minimal/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/minimal/.zed/settings.json +++ b/packages/create-threenative/templates/minimal/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/minimal/opencode.json b/packages/create-threenative/templates/minimal/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/minimal/opencode.json +++ b/packages/create-threenative/templates/minimal/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/platformer/.codex/config.toml b/packages/create-threenative/templates/platformer/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/platformer/.codex/config.toml +++ b/packages/create-threenative/templates/platformer/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/platformer/.cursor/mcp.json b/packages/create-threenative/templates/platformer/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/platformer/.cursor/mcp.json +++ b/packages/create-threenative/templates/platformer/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/platformer/.gemini/settings.json b/packages/create-threenative/templates/platformer/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/platformer/.gemini/settings.json +++ b/packages/create-threenative/templates/platformer/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/platformer/.mcp.json b/packages/create-threenative/templates/platformer/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/platformer/.mcp.json +++ b/packages/create-threenative/templates/platformer/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/platformer/.vscode/mcp.json b/packages/create-threenative/templates/platformer/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/platformer/.vscode/mcp.json +++ b/packages/create-threenative/templates/platformer/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/platformer/.zed/settings.json b/packages/create-threenative/templates/platformer/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/platformer/.zed/settings.json +++ b/packages/create-threenative/templates/platformer/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/platformer/opencode.json b/packages/create-threenative/templates/platformer/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/platformer/opencode.json +++ b/packages/create-threenative/templates/platformer/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/puzzle/.codex/config.toml b/packages/create-threenative/templates/puzzle/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/puzzle/.codex/config.toml +++ b/packages/create-threenative/templates/puzzle/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/puzzle/.cursor/mcp.json b/packages/create-threenative/templates/puzzle/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/puzzle/.cursor/mcp.json +++ b/packages/create-threenative/templates/puzzle/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/puzzle/.gemini/settings.json b/packages/create-threenative/templates/puzzle/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/puzzle/.gemini/settings.json +++ b/packages/create-threenative/templates/puzzle/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/puzzle/.mcp.json b/packages/create-threenative/templates/puzzle/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/puzzle/.mcp.json +++ b/packages/create-threenative/templates/puzzle/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/puzzle/.vscode/mcp.json b/packages/create-threenative/templates/puzzle/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/puzzle/.vscode/mcp.json +++ b/packages/create-threenative/templates/puzzle/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/puzzle/.zed/settings.json b/packages/create-threenative/templates/puzzle/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/puzzle/.zed/settings.json +++ b/packages/create-threenative/templates/puzzle/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/puzzle/opencode.json b/packages/create-threenative/templates/puzzle/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/puzzle/opencode.json +++ b/packages/create-threenative/templates/puzzle/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/racing/.codex/config.toml b/packages/create-threenative/templates/racing/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/racing/.codex/config.toml +++ b/packages/create-threenative/templates/racing/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/racing/.cursor/mcp.json b/packages/create-threenative/templates/racing/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/racing/.cursor/mcp.json +++ b/packages/create-threenative/templates/racing/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/racing/.gemini/settings.json b/packages/create-threenative/templates/racing/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/racing/.gemini/settings.json +++ b/packages/create-threenative/templates/racing/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/racing/.mcp.json b/packages/create-threenative/templates/racing/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/racing/.mcp.json +++ b/packages/create-threenative/templates/racing/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/racing/.vscode/mcp.json b/packages/create-threenative/templates/racing/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/racing/.vscode/mcp.json +++ b/packages/create-threenative/templates/racing/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/racing/.zed/settings.json b/packages/create-threenative/templates/racing/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/racing/.zed/settings.json +++ b/packages/create-threenative/templates/racing/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/racing/opencode.json b/packages/create-threenative/templates/racing/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/racing/opencode.json +++ b/packages/create-threenative/templates/racing/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/rain/.codex/config.toml b/packages/create-threenative/templates/rain/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/rain/.codex/config.toml +++ b/packages/create-threenative/templates/rain/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/rain/.cursor/mcp.json b/packages/create-threenative/templates/rain/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/rain/.cursor/mcp.json +++ b/packages/create-threenative/templates/rain/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/rain/.gemini/settings.json b/packages/create-threenative/templates/rain/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/rain/.gemini/settings.json +++ b/packages/create-threenative/templates/rain/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/rain/.mcp.json b/packages/create-threenative/templates/rain/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/rain/.mcp.json +++ b/packages/create-threenative/templates/rain/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/rain/.vscode/mcp.json b/packages/create-threenative/templates/rain/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/rain/.vscode/mcp.json +++ b/packages/create-threenative/templates/rain/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/rain/.zed/settings.json b/packages/create-threenative/templates/rain/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/rain/.zed/settings.json +++ b/packages/create-threenative/templates/rain/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/rain/opencode.json b/packages/create-threenative/templates/rain/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/rain/opencode.json +++ b/packages/create-threenative/templates/rain/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/rts/.codex/config.toml b/packages/create-threenative/templates/rts/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/rts/.codex/config.toml +++ b/packages/create-threenative/templates/rts/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/rts/.cursor/mcp.json b/packages/create-threenative/templates/rts/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/rts/.cursor/mcp.json +++ b/packages/create-threenative/templates/rts/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/rts/.gemini/settings.json b/packages/create-threenative/templates/rts/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/rts/.gemini/settings.json +++ b/packages/create-threenative/templates/rts/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/rts/.mcp.json b/packages/create-threenative/templates/rts/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/rts/.mcp.json +++ b/packages/create-threenative/templates/rts/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/rts/.vscode/mcp.json b/packages/create-threenative/templates/rts/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/rts/.vscode/mcp.json +++ b/packages/create-threenative/templates/rts/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/rts/.zed/settings.json b/packages/create-threenative/templates/rts/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/rts/.zed/settings.json +++ b/packages/create-threenative/templates/rts/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/rts/opencode.json b/packages/create-threenative/templates/rts/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/rts/opencode.json +++ b/packages/create-threenative/templates/rts/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/runner/.codex/config.toml b/packages/create-threenative/templates/runner/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/runner/.codex/config.toml +++ b/packages/create-threenative/templates/runner/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/runner/.cursor/mcp.json b/packages/create-threenative/templates/runner/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/runner/.cursor/mcp.json +++ b/packages/create-threenative/templates/runner/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/runner/.gemini/settings.json b/packages/create-threenative/templates/runner/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/runner/.gemini/settings.json +++ b/packages/create-threenative/templates/runner/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/runner/.mcp.json b/packages/create-threenative/templates/runner/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/runner/.mcp.json +++ b/packages/create-threenative/templates/runner/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/runner/.vscode/mcp.json b/packages/create-threenative/templates/runner/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/runner/.vscode/mcp.json +++ b/packages/create-threenative/templates/runner/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/runner/.zed/settings.json b/packages/create-threenative/templates/runner/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/runner/.zed/settings.json +++ b/packages/create-threenative/templates/runner/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/runner/opencode.json b/packages/create-threenative/templates/runner/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/runner/opencode.json +++ b/packages/create-threenative/templates/runner/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/sailing/.codex/config.toml b/packages/create-threenative/templates/sailing/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/sailing/.codex/config.toml +++ b/packages/create-threenative/templates/sailing/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/sailing/.cursor/mcp.json b/packages/create-threenative/templates/sailing/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/sailing/.cursor/mcp.json +++ b/packages/create-threenative/templates/sailing/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/sailing/.gemini/settings.json b/packages/create-threenative/templates/sailing/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/sailing/.gemini/settings.json +++ b/packages/create-threenative/templates/sailing/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/sailing/.mcp.json b/packages/create-threenative/templates/sailing/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/sailing/.mcp.json +++ b/packages/create-threenative/templates/sailing/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/sailing/.vscode/mcp.json b/packages/create-threenative/templates/sailing/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/sailing/.vscode/mcp.json +++ b/packages/create-threenative/templates/sailing/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/sailing/.zed/settings.json b/packages/create-threenative/templates/sailing/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/sailing/.zed/settings.json +++ b/packages/create-threenative/templates/sailing/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/sailing/opencode.json b/packages/create-threenative/templates/sailing/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/sailing/opencode.json +++ b/packages/create-threenative/templates/sailing/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/shooter/.codex/config.toml b/packages/create-threenative/templates/shooter/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/shooter/.codex/config.toml +++ b/packages/create-threenative/templates/shooter/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/shooter/.cursor/mcp.json b/packages/create-threenative/templates/shooter/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/shooter/.cursor/mcp.json +++ b/packages/create-threenative/templates/shooter/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/shooter/.gemini/settings.json b/packages/create-threenative/templates/shooter/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/shooter/.gemini/settings.json +++ b/packages/create-threenative/templates/shooter/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/shooter/.mcp.json b/packages/create-threenative/templates/shooter/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/shooter/.mcp.json +++ b/packages/create-threenative/templates/shooter/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/shooter/.vscode/mcp.json b/packages/create-threenative/templates/shooter/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/shooter/.vscode/mcp.json +++ b/packages/create-threenative/templates/shooter/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/shooter/.zed/settings.json b/packages/create-threenative/templates/shooter/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/shooter/.zed/settings.json +++ b/packages/create-threenative/templates/shooter/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/shooter/opencode.json b/packages/create-threenative/templates/shooter/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/shooter/opencode.json +++ b/packages/create-threenative/templates/shooter/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/snow/.codex/config.toml b/packages/create-threenative/templates/snow/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/snow/.codex/config.toml +++ b/packages/create-threenative/templates/snow/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/snow/.cursor/mcp.json b/packages/create-threenative/templates/snow/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/snow/.cursor/mcp.json +++ b/packages/create-threenative/templates/snow/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/snow/.gemini/settings.json b/packages/create-threenative/templates/snow/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/snow/.gemini/settings.json +++ b/packages/create-threenative/templates/snow/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/snow/.mcp.json b/packages/create-threenative/templates/snow/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/snow/.mcp.json +++ b/packages/create-threenative/templates/snow/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/snow/.vscode/mcp.json b/packages/create-threenative/templates/snow/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/snow/.vscode/mcp.json +++ b/packages/create-threenative/templates/snow/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/snow/.zed/settings.json b/packages/create-threenative/templates/snow/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/snow/.zed/settings.json +++ b/packages/create-threenative/templates/snow/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/snow/opencode.json b/packages/create-threenative/templates/snow/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/snow/opencode.json +++ b/packages/create-threenative/templates/snow/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/starter/.codex/config.toml b/packages/create-threenative/templates/starter/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/starter/.codex/config.toml +++ b/packages/create-threenative/templates/starter/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/starter/.cursor/mcp.json b/packages/create-threenative/templates/starter/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/starter/.cursor/mcp.json +++ b/packages/create-threenative/templates/starter/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/starter/.gemini/settings.json b/packages/create-threenative/templates/starter/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/starter/.gemini/settings.json +++ b/packages/create-threenative/templates/starter/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/starter/.mcp.json b/packages/create-threenative/templates/starter/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/starter/.mcp.json +++ b/packages/create-threenative/templates/starter/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/starter/.vscode/mcp.json b/packages/create-threenative/templates/starter/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/starter/.vscode/mcp.json +++ b/packages/create-threenative/templates/starter/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/starter/.zed/settings.json b/packages/create-threenative/templates/starter/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/starter/.zed/settings.json +++ b/packages/create-threenative/templates/starter/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/starter/opencode.json b/packages/create-threenative/templates/starter/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/starter/opencode.json +++ b/packages/create-threenative/templates/starter/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/templates/starter/src/entities/Player.ts b/packages/create-threenative/templates/starter/src/entities/Player.ts index 3d4e97a977..9d7c9de2f1 100644 --- a/packages/create-threenative/templates/starter/src/entities/Player.ts +++ b/packages/create-threenative/templates/starter/src/entities/Player.ts @@ -1,6 +1,6 @@ import { type ICtx, SkeletalMesh3D } from "@threenative/core"; import { CharacterBody3D, CollisionShape3D, type IPhysicsContext } from "@threenative/physics"; -import { type AnimationClip, Group, MathUtils, type Mesh, type Object3D, Vector3 } from "three"; +import { type AnimationClip, Group, type Mesh, type Object3D, Quaternion, Vector3 } from "three"; import { type IStarterConventions, preparePlayerConventions } from "../conventions.js"; import type { ITouchInput } from "../render/touch-controls.js"; import type { GameState } from "../state.js"; @@ -54,6 +54,7 @@ export class Player { #landing = 0; #wasGrounded = true; #supportSurfaceY = 0; + #heading = new Quaternion(); #previousPosition = new Vector3(); #bodyWorldPosition = new Vector3(); #visualWorldPosition = new Vector3(); @@ -102,12 +103,7 @@ export class Player { }); } - update( - ctx: GameCtx, - dt: number, - supportSurfaceY?: (position: Pick) => number | undefined, - touch?: ITouchInput, - ): void { + update(ctx: GameCtx, dt: number, touch?: ITouchInput): void { if (this.#hasPreviousPosition) { this.#odometer += this.mesh.position.distanceTo(this.#previousPosition); } @@ -138,11 +134,8 @@ export class Player { this.body.moveAndSlide(dt); this.#hasPreviousPosition = true; if (this.body.grounded) this.#coyoteTime = COYOTE_TIME; - const supportingSurfaceY = supportSurfaceY?.(this.mesh.position); - const canCorrectGrounding = - this.body.grounded && this.body.velocity.y <= 0 && supportingSurfaceY !== undefined; - // The resolver identifies the supporting collider; derive the contact plane from the - // grounded character so a changed platform height never pulls the visual from its body. + const canCorrectGrounding = this.body.grounded && this.body.velocity.y <= 0; + // Derive the contact plane from the grounded body, including terrain outside the arena. if (canCorrectGrounding) this.#supportSurfaceY = this.mesh.position.y - PLAYER_FOOT_OFFSET; this.#conventions.groundSnap.enabled = canCorrectGrounding; this.#conventions.applyGrounding(this.#supportSurfaceY, dt); @@ -160,6 +153,7 @@ export class Player { debug(): { coyoteJumps: number; grounded: boolean; + facingZ: number; groundClearance: number | null; groundCorrectionEnabled: boolean; groundSurfaceY: number; @@ -174,6 +168,7 @@ export class Player { return { coyoteJumps: this.#coyoteJumps, grounded: this.body.grounded, + facingZ: this.character.root.getWorldDirection(new Vector3()).z, groundClearance: this.#conventions.groundSnap.clearance, groundCorrectionEnabled: this.#conventions.groundSnap.enabled, groundSurfaceY: this.#supportSurfaceY, @@ -212,8 +207,9 @@ export class Player { const figure = this.character.root; if (speed > 0.05) { const heading = Math.atan2(this.body.velocity.x, this.body.velocity.z); - const turn = MathUtils.euclideanModulo(heading - figure.rotation.y + Math.PI, Math.PI * 2); - figure.rotation.y += (turn - Math.PI) * Math.min(1, dt * 12); + // Matrix decomposition can represent a half-turn with x/z Euler rotations. + this.#heading.setFromAxisAngle(this.mesh.up, heading); + figure.quaternion.slerp(this.#heading, Math.min(1, dt * 12)); } const grounded = this.body.grounded; this.#landing -= dt; diff --git a/packages/create-threenative/templates/starter/src/render/lighting.ts b/packages/create-threenative/templates/starter/src/render/lighting.ts index 05bdcf10e8..f0d9cdc4cc 100644 --- a/packages/create-threenative/templates/starter/src/render/lighting.ts +++ b/packages/create-threenative/templates/starter/src/render/lighting.ts @@ -4,15 +4,21 @@ // One sun. The sky image in `sky.ts` is the fill light — its environment reaches every face the // sun misses — so there is no hemisphere or ambient light stacked on top to flatten the frame. import { Color, DirectionalLight, PCFSoftShadowMap, type Scene, Vector3 } from "three"; +import type { Node } from "three/webgpu"; import { SUN_DIRECTION } from "./sky.js"; type ShadowRenderer = { shadowMap: { enabled: boolean; type: number } }; +// The engine's `VirtualShadowNode` class. The scene passes it in, because this folder imports no +// framework package. +type VirtualShadowClass = new (light: DirectionalLight, options: { clipExtents: number[] }) => T; -export function setupLighting( +export function setupLighting( scene: Scene, renderer: ShadowRenderer, mobile = false, -): { key: DirectionalLight } { + shadowExtents?: readonly number[], + VirtualShadowNode?: VirtualShadowClass, +): { key: DirectionalLight; shadows?: T; dispose: () => void } { renderer.shadowMap.enabled = true; renderer.shadowMap.type = PCFSoftShadowMap; @@ -36,10 +42,37 @@ export function setupLighting( // Small biases: a large normal bias is what lifted the mannequin's shadow off its own feet. key.shadow.bias = -0.0002; key.shadow.normalBias = 0.005; + // Named override for a world wider than the arena, such as a streamed forest. `shadowExtents` + // are the wider windows in metres that the caller measures from its terrain. The finest window + // stays 14 m, so near shadows keep this map's texel and softness. Each level inherits the map + // size, radius and biases above, re-renders when its window moves, and redraws only the region + // an `invalidateRegion` names. Godrays need an ordinary shadow map and refuse this light by name. + let shadows: T | undefined; + if (shadowExtents !== undefined) { + if (VirtualShadowNode === undefined) + throw new Error("setupLighting: shadowExtents needs the VirtualShadowNode class."); + const clipExtents = [extent, ...shadowExtents]; + if ( + !clipExtents.every((value, i) => Number.isFinite(value) && value > (clipExtents[i - 1] ?? 0)) + ) + throw new RangeError( + `setupLighting: shadowExtents must increase past ${extent} m, got [${shadowExtents.join(", ")}].`, + ); + shadows = new VirtualShadowNode(key, { clipExtents }); + key.shadow.shadowNode = shadows; + } scene.add(key); // The key light is returned because `WorldEnvironment`'s godrays stage raymarches against its // shadow map, so `setupPost` needs the light itself. - return { key }; + return { + key, + shadows, + dispose: () => { + shadows?.dispose(); + key.removeFromParent(); + key.dispose(); + }, + }; } /** Material conventions are game-owned; each scene gets independent mutable controls. */ diff --git a/packages/create-threenative/templates/starter/src/scenes/Play.ts b/packages/create-threenative/templates/starter/src/scenes/Play.ts index a004f657bf..611182e253 100644 --- a/packages/create-threenative/templates/starter/src/scenes/Play.ts +++ b/packages/create-threenative/templates/starter/src/scenes/Play.ts @@ -20,7 +20,7 @@ import { } from "three"; import config from "../../threenative.config.js"; import { Crate } from "../entities/Crate.js"; -import { Goal, ISLAND } from "../entities/Goal.js"; +import { Goal } from "../entities/Goal.js"; import { type IPlayerModel, PLAYER_STAND_Y, Player } from "../entities/Player.js"; import { createArena, platform } from "../render/arena.js"; import { createSpringArm } from "../render/camera.js"; @@ -43,8 +43,7 @@ export type GameCtx = ICtx; const KILL_PLANE = -4; const STARTING_LIVES = 3; const FLOOR_SURFACE_Y = 0; -const FLOOR_BOUNDS = { maxX: 5, minX: -5, maxZ: 2, minZ: -2 } as const; -/** The near platform's own footprint, which the support surface below answers for. */ +/** The near platform's own footprint. */ const FLOOR_SIZE = { depth: 4.2, width: 10 } as const; export class Play extends Scene { @@ -154,11 +153,12 @@ export class Play extends Scene { setupSky(ctx.scene, this.#sky, ctx.renderer.softwareAdapter !== undefined); // isMobile() arrives as an argument because src/render/ imports no framework package: // the platform decision is made here, in portable game code, exactly like createRandom. - const { key } = setupLighting( + const { key, dispose: disposeLighting } = setupLighting( ctx.scene, ctx.renderer.raw as Parameters[1], isMobile(), ); + this.#disposeLighting = disposeLighting; const materialEnvironment = { web: getPlatform().runtime === "web", rendererKind: ctx.renderer.kind, @@ -268,25 +268,6 @@ export class Play extends Scene { this.#environmentSample, ); } - const supportSurfaceY = (position: { readonly x: number; readonly z: number }): - | number - | undefined => { - if ( - position.x >= FLOOR_BOUNDS.minX && - position.x <= FLOOR_BOUNDS.maxX && - position.z >= FLOOR_BOUNDS.minZ && - position.z <= FLOOR_BOUNDS.maxZ - ) - return FLOOR_SURFACE_Y; - if ( - position.x >= ISLAND.x - ISLAND.width / 2 && - position.x <= ISLAND.x + ISLAND.width / 2 && - position.z >= ISLAND.z - ISLAND.depth / 2 && - position.z <= ISLAND.z + ISLAND.depth / 2 - ) - return ISLAND.top; - return undefined; - }; // The area says the character is over the island; the run is only won once it is also // standing on it. Ending on the overlap alone freezes the character in mid-air at the // lip of the island, half a metre short of a landing, which is what it looks like. @@ -350,7 +331,7 @@ export class Play extends Scene { frameCtx.state.set((state) => ({ flagGusts: state.flagGusts + 1 })); } const touch = touchControls?.update(frameCtx.input.raw.pointers, frameCtx.viewport.size); - player.update(frameCtx, dt, supportSurfaceY, touch); + player.update(frameCtx, dt, touch); let respawned = false; let lives = previous.lives; if (player.mesh.position.y < KILL_PLANE) { @@ -398,6 +379,7 @@ export class Play extends Scene { } #post: ReturnType | undefined; + #disposeLighting: (() => void) | undefined; #materialLighting: ReturnType | undefined; override exit(ctx: GameCtx): void { @@ -406,6 +388,9 @@ export class Play extends Scene { // Also releases the medium `setupPost` built, with the graph it composed into. this.#post?.dispose(); this.#post = undefined; + // After the post graph, whose godrays stage reads the sun's shadow map. + this.#disposeLighting?.(); + this.#disposeLighting = undefined; super.exit(ctx); } } diff --git a/packages/create-threenative/templates/tower-defense/.codex/config.toml b/packages/create-threenative/templates/tower-defense/.codex/config.toml index 1906a5c3c9..15c005eb19 100644 --- a/packages/create-threenative/templates/tower-defense/.codex/config.toml +++ b/packages/create-threenative/templates/tower-defense/.codex/config.toml @@ -17,3 +17,7 @@ args = ["./node_modules/@threenative/core/mcp/engine.mjs"] [mcp_servers.threenative-blender] command = "node" args = ["./node_modules/@threenative/core/mcp/blender.mjs"] + +[mcp_servers.threenative-terrain] +command = "node" +args = ["./node_modules/@threenative/core/mcp/terrain.mjs"] diff --git a/packages/create-threenative/templates/tower-defense/.cursor/mcp.json b/packages/create-threenative/templates/tower-defense/.cursor/mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/tower-defense/.cursor/mcp.json +++ b/packages/create-threenative/templates/tower-defense/.cursor/mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/tower-defense/.gemini/settings.json b/packages/create-threenative/templates/tower-defense/.gemini/settings.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/tower-defense/.gemini/settings.json +++ b/packages/create-threenative/templates/tower-defense/.gemini/settings.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/tower-defense/.mcp.json b/packages/create-threenative/templates/tower-defense/.mcp.json index 9f0aaf5a41..c6280383f5 100644 --- a/packages/create-threenative/templates/tower-defense/.mcp.json +++ b/packages/create-threenative/templates/tower-defense/.mcp.json @@ -27,6 +27,12 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/tower-defense/.vscode/mcp.json b/packages/create-threenative/templates/tower-defense/.vscode/mcp.json index bb2c8d72ad..290a553ede 100644 --- a/packages/create-threenative/templates/tower-defense/.vscode/mcp.json +++ b/packages/create-threenative/templates/tower-defense/.vscode/mcp.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "type": "stdio", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/tower-defense/.zed/settings.json b/packages/create-threenative/templates/tower-defense/.zed/settings.json index 0c58b95100..a56f606dfd 100644 --- a/packages/create-threenative/templates/tower-defense/.zed/settings.json +++ b/packages/create-threenative/templates/tower-defense/.zed/settings.json @@ -31,6 +31,13 @@ "args": [ "./node_modules/@threenative/core/mcp/blender.mjs" ] + }, + "threenative-terrain": { + "source": "custom", + "command": "node", + "args": [ + "./node_modules/@threenative/core/mcp/terrain.mjs" + ] } } } diff --git a/packages/create-threenative/templates/tower-defense/opencode.json b/packages/create-threenative/templates/tower-defense/opencode.json index c8226dccec..b9eb4df9e2 100644 --- a/packages/create-threenative/templates/tower-defense/opencode.json +++ b/packages/create-threenative/templates/tower-defense/opencode.json @@ -36,6 +36,14 @@ "./node_modules/@threenative/core/mcp/blender.mjs" ], "enabled": true + }, + "threenative-terrain": { + "type": "local", + "command": [ + "node", + "./node_modules/@threenative/core/mcp/terrain.mjs" + ], + "enabled": true } } } diff --git a/packages/create-threenative/terrain-mcp-tools.json b/packages/create-threenative/terrain-mcp-tools.json new file mode 100644 index 0000000000..7d6ca8aa87 --- /dev/null +++ b/packages/create-threenative/terrain-mcp-tools.json @@ -0,0 +1,65 @@ +{ + "comment": "The terrain MCP surface a generated project gets once it installs @threenative/terrain. `tools` is set-equal to the tool table version 0.1.0 serves. Regenerate with `pnpm tsx scripts/capture-terrain-mcp-tools.ts`.", + "version": "0.1.0", + "serverInfo": { + "name": "threenative-terrain", + "version": "0.1.0" + }, + "recommended": ["terrain_generate", "terrain_patch"], + "tools": ["terrain_generate", "terrain_patch"], + "inputSchemas": { + "terrain_generate": { + "type": "object", + "properties": { + "kit": { + "type": "string", + "enum": ["forest", "coastal", "alpine", "desert", "tundra"] + }, + "request": { + "type": "string", + "minLength": 1 + }, + "sizeMeters": { + "type": "number", + "exclusiveMinimum": 0 + }, + "seed": { + "type": "integer" + }, + "out": { + "type": "string", + "description": "Relative directory that must not exist yet." + } + }, + "anyOf": [ + { + "required": ["request"] + }, + { + "required": ["kit"] + } + ], + "additionalProperties": false + }, + "terrain_patch": { + "type": "object", + "properties": { + "recipe": { + "type": "object" + }, + "commands": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "required": ["recipe", "commands"], + "additionalProperties": false + } + }, + "descriptions": { + "terrain_generate": "Choose from a request or explicit kit and cook one editable starter terrain kit (forest, coastal, alpine, desert or tundra) into a world.glb. Returns the kit as recipe, the world.glb, recipe.json and world.json paths, triangles, instances and draws measured from the produced GLB's default scene, and a ctx.assets.model load snippet. triangles counts every instance of the LOD0 default scene in full, before distance LOD and culling, instances sums the EXT_mesh_gpu_instancing instances, and draws counts primitives across instanced and non-instanced meshes, an instanced primitive counting once.", + "terrain_patch": "Apply editor commands (upsert, update, remove, move, toggle) to a terrain recipe and return the edited recipe." + } +} diff --git a/packages/create-threenative/tsup.config.ts b/packages/create-threenative/tsup.config.ts index da2f56a55a..5ee604ed52 100644 --- a/packages/create-threenative/tsup.config.ts +++ b/packages/create-threenative/tsup.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ banner: { js: "import { createRequire as __createRequire } from 'node:module';\nconst require = __createRequire(import.meta.url);", }, - entry: ["src/index.ts", "src/threenative.ts"], + entry: ["src/index.ts", "src/threenative.ts", "src/terrain-kit.ts"], format: ["esm"], target: "node20", dts: true, diff --git a/packages/engine-mcp/__tests__/terrain-discovery.spec.ts b/packages/engine-mcp/__tests__/terrain-discovery.spec.ts index 14aa0de3b4..ddca435863 100644 --- a/packages/engine-mcp/__tests__/terrain-discovery.spec.ts +++ b/packages/engine-mcp/__tests__/terrain-discovery.spec.ts @@ -34,8 +34,11 @@ describe("terrain authoring discovery", () => { top("procedural heightmap landscape for the game", "mechanic", "Terrain")?.importPath, ).toBe("@threenative/terrain"); expect( - top("export the terrain as a glb for another three.js project", "mechanic", "exportWorldGLB") - ?.importPath, + top( + "export the terrain as a glb for another three.js project", + "mechanic", + "prepareWorldGLTF", + )?.importPath, ).toBe("@threenative/terrain/export"); expect( top( @@ -79,7 +82,7 @@ describe("terrain authoring discovery", () => { for (const required of [ "npm install --save-dev @threenative/terrain", "@threenative/terrain/editor/server", - "exportWorldGLB", + "prepareWorldGLTF", "node_modules/@threenative/terrain/AGENT_GUIDE.md", ]) expect(guide).toContain(required); diff --git a/packages/playtest/__tests__/orphan-cleanup.sh b/packages/playtest/__tests__/orphan-cleanup.sh index b106f8c9ad..19f2597965 100755 --- a/packages/playtest/__tests__/orphan-cleanup.sh +++ b/packages/playtest/__tests__/orphan-cleanup.sh @@ -147,7 +147,8 @@ test_port="$(node --input-type=module -e ' server.close(); }); ')" -timeout 5s node packages/playtest/dist/runner/cli.js \ +# pnpm's persistent compile cache is unrelated to runner teardown; keep it out of this namespace. +DISABLE_V8_COMPILE_CACHE=1 timeout 5s node packages/playtest/dist/runner/cli.js \ --scenario examples/abyss-framework/playtest/moves.json \ --project . \ --url "http://127.0.0.1:$test_port" \ diff --git a/packages/playtest/__tests__/perf.spec.ts b/packages/playtest/__tests__/perf.spec.ts index 207f4139d2..75e34ef933 100644 --- a/packages/playtest/__tests__/perf.spec.ts +++ b/packages/playtest/__tests__/perf.spec.ts @@ -35,6 +35,20 @@ function budgetLine(windowId: number, fps: number, frameP95: number, renderP50: `"shares":{"render":0.55}}`; } +/** The same window with an explicitly reported `surface.compiling` flag. */ +function compilingLine( + windowId: number, + fps: number, + frameP95: number, + renderP50: number, + compiling: boolean, +): string { + return budgetLine(windowId, fps, frameP95, renderP50).replace( + '"shares":{"render":0.55}', + `"shares":{"render":0.55},"surface":{"resolutionScale":1,"scaleSource":"auto","sampleCount":4,"drawingBufferWidth":1920,"drawingBufferHeight":1080,"compiling":${compiling}}`, + ); +} + const hostGapLine = "TN_HOST_GAP:{\"frames\":300,\"periodP50Ms\":48.75,\"periodMeanMs\":49.1," + "\"segments\":{\"present\":{\"p50Ms\":14.57,\"meanMs\":14.6},\"frameReplay\":{\"p50Ms\":7.95,\"meanMs\":8.0}," + "\"devicePoll\":{\"p50Ms\":0.28,\"meanMs\":0.3},\"timers\":{\"p50Ms\":0.001,\"meanMs\":0.002}}," + @@ -161,6 +175,53 @@ describe("assessPerfMarkers", () => { expect(report.pass).toBe(false); }); + it("refuses to count explicitly compiling windows as steady, even when their numbers clear every bound", () => { + // Fast warmup loops cannot establish settled speed, even when their reported bounds pass. + const lines = Array.from({ length: 10 }, (_, index) => compilingLine(index + 1, 120, 4, 0.5, true)); + const parsed = parsePerformanceMarkers(lines.join("\n")); + const report = assessPerfMarkers(parsed, { minFps: 55, maxFrameMsP95: 30, requireWindows: 6 }, "log"); + expect(report.discardedWindows).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(report.violations).toEqual([ + expect.objectContaining({ code: "TN_PERF_WINDOWS_MISSING", observed: 0, bound: 6 }), + ]); + expect(report.pass).toBe(false); + }); + + it("still assesses the settled windows that follow a compiling startup", () => { + const lines = [ + compilingLine(1, 60, 8, 4, true), + compilingLine(2, 60, 8, 4, true), + budgetLine(3, 60, 8, 4), + budgetLine(4, 61, 8, 4), + budgetLine(5, 59, 8, 4), + ]; + const parsed = parsePerformanceMarkers(lines.join("\n")); + const report = assessPerfMarkers(parsed, { minFps: 55, maxFrameMsP95: 30, requireWindows: 2 }, "log"); + expect(report.discardedWindows).toEqual([1, 2]); + expect(report.violations).toEqual([]); + expect(report.pass).toBe(true); + // A window that measured `compiling:false` is steady as before, not discarded. + expect(parsePerformanceMarkers(compilingLine(1, 60, 8, 4, false)).budgets[0]?.surface?.compiling).toBe(false); + expect(assessPerfMarkers(parsePerformanceMarkers(compilingLine(1, 60, 8, 4, false)), { requireWindows: 1 }, "log").pass).toBe(true); + }); + + it("rejects a frame-budget window whose surface.compiling is not a boolean", () => { + const malformed = budgetLine(1, 60, 8, 4).replace( + '"shares":{"render":0.55}', + '"shares":{"render":0.55},"surface":{"resolutionScale":1,"scaleSource":"auto","sampleCount":1,' + + '"drawingBufferWidth":1920,"drawingBufferHeight":1080,"compiling":"pending"}', + ); + expect(() => parsePerformanceMarkers(malformed)).toThrow(/TN_PERF_MARKER_MALFORMED/); + }); + + it.each([null, [], "pending"])("rejects a malformed surface record: %j", (surface) => { + const payload = JSON.parse(budgetLine(1, 60, 8, 4).split("TN_FRAME_BUDGET:")[1] as string); + payload.surface = surface; + expect(() => parsePerformanceMarkers(`TN_FRAME_BUDGET:${JSON.stringify(payload)}`)).toThrow( + /TN_PERF_MARKER_MALFORMED/, + ); + }); + it("fails closed when a frame-p95 bound is requested but the window carries no frame summary", () => { // A meter line without the frame summary cannot prove a frame bound; that is a named // failure, never a silent pass-through of an unmeasured window. @@ -409,7 +470,7 @@ describe("formatPerfReport", () => { expect(text).toContain("perf — 3 window(s)"); expect(text).toContain("present mode: mailbox (vsync=false)"); expect(text).toContain("1*"); - expect(text).toContain("window 1 always lies"); + expect(text).toContain("discarded as startup or active compilation"); expect(text).toContain("frameReplay"); expect(text).toContain("PASS"); }); diff --git a/packages/playtest/src/runner/perf.ts b/packages/playtest/src/runner/perf.ts index bbce1934f8..891053ed6a 100644 --- a/packages/playtest/src/runner/perf.ts +++ b/packages/playtest/src/runner/perf.ts @@ -344,6 +344,7 @@ export function parsePerformanceMarkers(text: string): IPerfMarkerParse { for (const line of text.split("\n")) { const budget = parseMarkerLine(line, FRAME_BUDGET_MARKER); if (budget !== undefined) { + assertBudgetSurface(budget); // Android mirrors console output through MystralStdio and MystralJS. One measured window is // therefore present twice in logcat with the exact same payload; count that observation once // without hiding a second payload whose measurements differ. @@ -436,6 +437,24 @@ function assertProjectionTimings(projection: IProjectionWindowJson): void { } } +/** A malformed compilation flag must not qualify a window as settled. */ +function assertBudgetSurface(budget: IFrameBudgetWindowJson): void { + const surface: unknown = budget.surface; + if (surface === undefined) return; + if (typeof surface !== "object" || surface === null || Array.isArray(surface)) { + throw new Error( + `TN_PERF_MARKER_MALFORMED: a TN_FRAME_BUDGET line carried a non-object surface (${JSON.stringify(surface)}).`, + ); + } + const compiling = (surface as Record).compiling; + if (compiling === undefined) return; + if (typeof compiling !== "boolean") { + throw new Error( + `TN_PERF_MARKER_MALFORMED: a TN_FRAME_BUDGET line carried surface.compiling = ${JSON.stringify(compiling)} — it must be a boolean.`, + ); + } +} + function parseMarkerLine(line: string, marker: string): T | undefined { const start = line.indexOf(marker); if (start === -1) return undefined; @@ -452,10 +471,12 @@ function parseMarkerLine(line: string, marker: string): T | undefined { /** * Apply the one-window-always-lies rule and any requested bounds. The first window of a multi- - * window run is discarded as startup; bounds are checked against every steady window, because a - * bound that passes on the median and fails on the steady tail is a failed bound. Fewer steady - * windows than `requireWindows` is a `TN_PERF_WINDOWS_MISSING` violation — the run did not - * produce enough evidence to assess, which is a failure, not an empty pass. + * window run is discarded as startup, and so is every window whose `surface.compiling` is true: + * asynchronous pipeline compilation is not the steady frame a bound is about. Bounds are checked + * against every steady window, because a bound that passes on the median and fails on the steady + * tail is a failed bound. Fewer steady windows than `requireWindows` is a + * `TN_PERF_WINDOWS_MISSING` violation — the run did not produce enough evidence to assess, which is + * a failure, not an empty pass. */ export function assessPerfMarkers( parse: IPerfMarkerParse, @@ -463,9 +484,10 @@ export function assessPerfMarkers( source: string, display?: { readonly strategy: string; readonly virtual: boolean }, ): IPerfReport { - const discardCount = parse.budgets.length > 1 ? 1 : 0; - const discardedWindows = parse.budgets.slice(0, discardCount).map(({ window }) => window); - const steady = parse.budgets.slice(discardCount); + const discard = (window: IFrameBudgetWindowJson, index: number) => + (index === 0 && parse.budgets.length > 1) || window.surface?.compiling === true; + const discardedWindows = parse.budgets.filter(discard).map(({ window }) => window); + const steady = parse.budgets.filter((window, index) => !discard(window, index)); const violations: IPerfViolation[] = []; /** Windows that counted presents but no present period long enough to carry a rate. */ const unassessableWindows: number[] = []; @@ -868,7 +890,7 @@ export function formatPerfReport(report: IPerfReport): string { ].join(" "), ); } - if (report.discardedWindows.length > 0) lines.push("* discarded as startup (window 1 always lies)"); + if (report.discardedWindows.length > 0) lines.push("* discarded as startup or active compilation"); lines.push(...formatMainPassTriangles(report.budgets)); // A zero in the fps column is true and easy to misread as a frozen game: name the windows where // the display simply showed nothing in a window of loop frames too short to contain a present. diff --git a/packages/runtime-native/conformance/registry.json b/packages/runtime-native/conformance/registry.json index 18ecd85b7e..e1b4b611a1 100644 --- a/packages/runtime-native/conformance/registry.json +++ b/packages/runtime-native/conformance/registry.json @@ -1644,6 +1644,49 @@ "tolerance": { "pixelMismatchRatio": 0.06, "perceptualDeltaE": 9.0 }, "desktopGate": true, "required": true + }, + { + "id": "world-cells-proxy-stream", + "title": "WorldCells two-cell streaming and observed proxy replacement", + "category": "rendering", + "status": "implemented", + "scene": "conformance/scenes/shared/world-cells-proxy-stream.js", + "tolerance": { "pixelMismatchRatio": 0.01, "perceptualDeltaE": 3.0 }, + "browserRecipe": "webgpu", + "captureFrames": 2, + "nativeFrames": 300, + "desktopGate": true, + "required": true, + "meaningfulReason": "Two admitted cells, backend-observed HLOD swaps, near detail restoration and zero scoped validation errors" + }, + { + "id": "virtual-shadow-first-frame", + "title": "virtual shadow first frame GPU submission and texture lifetime", + "category": "rendering", + "status": "implemented", + "scene": "conformance/scenes/shared/virtual-shadow-first-frame.js", + "tolerance": { "pixelMismatchRatio": 0.08, "perceptualDeltaE": 3.0 }, + "browserRecipe": "webgpu", + "captureFrames": 2, + "nativeFrames": 300, + "desktopGate": true, + "required": true, + "meaningfulReason": "First-frame virtual shadow level submission, completed GPU work and zero validation errors including Destroyed texture" + }, + { + "id": "compressed-texture-preparation", + "title": "compressed texture preparation", + "category": "materials", + "status": "implemented", + "scene": "conformance/scenes/shared/compressed-texture-preparation.js", + "tolerance": { + "pixelMismatchRatio": 0.01, + "perceptualDeltaE": 3.0 + }, + "browserRecipe": "webgpu", + "desktopGate": true, + "required": true, + "meaningfulReason": "Public bounded compressed upload and ExternalTexture material replacement on browser and native" } ], "generatedPlaytestProofs": [ diff --git a/packages/runtime-native/conformance/scenes/shared/compressed-texture-preparation.js b/packages/runtime-native/conformance/scenes/shared/compressed-texture-preparation.js new file mode 100644 index 0000000000..8a33c83824 --- /dev/null +++ b/packages/runtime-native/conformance/scenes/shared/compressed-texture-preparation.js @@ -0,0 +1,110 @@ +import { createRenderer } from "../../../../core/src/renderer.ts"; +import { THREE, assertCondition } from "./scene-support.js"; + +export async function startScene(canvas, dimensions) { + const { width, height } = dimensions; + const renderer = await createRenderer({ + canvas, + antialias: false, + preferWebGPU: true, + pixelRatio: 1, + pipelineCensus: false, + }); + + // Each mip holds one 8-byte BC1 block. Endpoint 0 is red (RGB565 0xf800). + // Endpoint 1 is green (0x07e0). Index bytes 0, 0, 85, 85 paint rows 0-1 red + // and rows 2-3 green. + const block = [0, 248, 224, 7, 0, 0, 85, 85]; + const source = new THREE.CompressedTexture( + [ + { data: new Uint8Array(block), width: 4, height: 4 }, + { data: new Uint8Array(block), width: 2, height: 2 }, + { data: new Uint8Array(block), width: 1, height: 1 }, + ], + 4, + 4, + THREE.RGB_S3TC_DXT1_Format, + ); + source.minFilter = THREE.NearestMipmapNearestFilter; + source.magFilter = THREE.NearestFilter; + source.needsUpdate = true; + + // Snapshot the source. Preparation must leave each reference unchanged. + const sourceUuid = source.uuid; + const sourceVersion = source.version; + const mipArray = source.mipmaps; + const mipRefs = source.mipmaps.map((mip) => ({ mip, data: mip.data })); + const originalSource = source.source; + const originalSourceVersion = originalSource.version; + + assertCondition( + typeof renderer.prepareTextures === "function", + "prepareTextures is not a public function", + ); + const replacements = new Map(); + const count = await renderer.prepareTextures( + [source], + undefined, + undefined, + replacements, + ); + assertCondition(count === 1, `prepared ${count} textures, expected 1`); + + const replacement = replacements.get(source); + assertCondition( + replacement?.isExternalTexture === true, + "source did not map to an ExternalTexture", + ); + assertCondition(source.uuid === sourceUuid, "source identity changed"); + assertCondition(source.version === sourceVersion, "source version changed"); + assertCondition( + source.source === originalSource, + "texture source identity changed", + ); + assertCondition( + originalSource.version === originalSourceVersion, + "texture source version changed", + ); + assertCondition( + source.mipmaps === mipArray && source.mipmaps.length === mipRefs.length, + "source mip array changed", + ); + mipRefs.forEach(({ mip, data }, index) => { + assertCondition( + source.mipmaps[index] === mip && mip.data === data, + `mip ${index} changed during preparation`, + ); + }); + + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0x18202f); + const camera = new THREE.PerspectiveCamera(55, width / height, 0.1, 100); + camera.position.z = 3.2; + const mesh = new THREE.Mesh( + new THREE.PlaneGeometry(1.8, 1.8), + new THREE.MeshBasicMaterial({ map: replacement }), + ); + scene.add(mesh); + + renderer.setSize(width, height, false); + renderer.render(scene, camera); + function frame() { + renderer.render(scene, camera); + requestAnimationFrame(frame); + } + requestAnimationFrame(frame); + + // mipLevels counts the submitted mips only. + // This scene does not check GPU readback. + globalThis.__TN_CONFORMANCE = { + ok: true, + type: "compressed-texture-preparation", + detail: { prepared: 1, mipLevels: 3, originalStable: true }, + }; + return { + renderer, + scene, + camera, + subject: { mesh, texture: source, replacement }, + }; +} diff --git a/packages/runtime-native/conformance/scenes/shared/morph-target-animation.js b/packages/runtime-native/conformance/scenes/shared/morph-target-animation.js index 4b0e4064b7..ec88b8b058 100644 --- a/packages/runtime-native/conformance/scenes/shared/morph-target-animation.js +++ b/packages/runtime-native/conformance/scenes/shared/morph-target-animation.js @@ -1,5 +1,6 @@ import { assertCondition, startVisualScene, THREE } from "./scene-support.js"; import { playClipAt } from "./planned-animation-support.js"; +import { assertSubmittedWorkPromise } from "./screenshot-completion.js"; export function createMorphTargetAnimationProof() { const geometry = new THREE.BoxGeometry(0.95, 0.95, 0.95, 2, 2, 2); @@ -39,13 +40,40 @@ export function createMorphTargetAnimationProof() { export function startScene(canvas, dimensions) { const proof = createMorphTargetAnimationProof(); proof.mesh.rotation.set(-0.25, 0.55, 0.1); - return startVisualScene(canvas, dimensions, "morph-target-animation", ({ scene }) => { + return startVisualScene(canvas, dimensions, "morph-target-animation", async ({ renderer, scene, camera }) => { scene.add(proof.mesh); + const queue = renderer.backend?.device?.queue; + assertCondition(typeof queue?.onSubmittedWorkDone === "function", "Morph replacement requires GPU queue completion"); + await new Promise((resolve) => requestAnimationFrame(resolve)); + renderer.render(scene, camera); + const oldCompletion = queue.onSubmittedWorkDone(); + assertSubmittedWorkPromise(oldCompletion, "original-morph-render"); + await oldCompletion; + + const original = proof.mesh.geometry; + const influence = proof.mesh.morphTargetInfluences[0]; + let originalDisposals = 0; + original.addEventListener("dispose", () => { originalDisposals++; }); + proof.mesh.geometry = original.clone(); + proof.mesh.updateMorphTargets(); + proof.mesh.morphTargetInfluences[0] = influence; + original.dispose(); + await new Promise((resolve) => requestAnimationFrame(resolve)); + renderer.render(scene, camera); + const replacementCompletion = queue.onSubmittedWorkDone(); + assertSubmittedWorkPromise(replacementCompletion, "replacement-morph-render"); + await replacementCompletion; + assertCondition(original.uuid !== proof.mesh.geometry.uuid, "Morph geometry must be replaced"); + assertCondition(originalDisposals === 1, "Original morph geometry must be disposed exactly once"); + assertCondition(Math.abs(proof.mesh.morphTargetInfluences[0] - 0.65) < 1e-6, "Replacement must preserve morph influence"); return { ...proof, detail: { clip: proof.clip.name, influence: proof.mesh.morphTargetInfluences[0], + originalGeometry: original.uuid, + replacementGeometry: proof.mesh.geometry.uuid, + originalDisposals, }, }; }); diff --git a/packages/runtime-native/conformance/scenes/shared/temporal-aa.js b/packages/runtime-native/conformance/scenes/shared/temporal-aa.js index a05879aa35..eb6b3794f7 100644 --- a/packages/runtime-native/conformance/scenes/shared/temporal-aa.js +++ b/packages/runtime-native/conformance/scenes/shared/temporal-aa.js @@ -1,9 +1,20 @@ import { createTemporalAAFixture } from "./temporal-aa-fixture.js"; -import { startVisualScene } from "./scene-support.js"; +import { assertCondition, startVisualScene } from "./scene-support.js"; export function startScene(canvas, dimensions) { - return startVisualScene(canvas, dimensions, "temporal-aa", ({ renderer, scene, camera }) => { + return startVisualScene(canvas, dimensions, "temporal-aa", async ({ renderer, scene, camera }) => { const fixture = createTemporalAAFixture(renderer, scene, camera); + for (let frame = 0; frame < 26; frame += 1) { + await new Promise((resolve) => requestAnimationFrame(resolve)); + fixture.render(); + await fixture.sampleVelocity(); + } + fixture.freeze(); + const observation = fixture.observation(); + assertCondition( + observation.frame === 26 && observation.held?.frame === 26, + "Temporal AA capture must hold exactly 26 moving history frames.", + ); return { ...fixture, detail: { milestone: "full-resolution-temporal-aa", qualification: "experimental" } }; }); } diff --git a/packages/runtime-native/conformance/scenes/shared/terrain-inline-jobs.js b/packages/runtime-native/conformance/scenes/shared/terrain-inline-jobs.js index e683b19dcb..f06dd0390a 100644 --- a/packages/runtime-native/conformance/scenes/shared/terrain-inline-jobs.js +++ b/packages/runtime-native/conformance/scenes/shared/terrain-inline-jobs.js @@ -1,4 +1,5 @@ import { MeshStandardMaterial } from "three"; +import { WorldCells } from "../../../../core/src/world-cells.ts"; import { TerrainTiles } from "../../../../core/src/world-tiles.ts"; import { assertCondition, startVisualScene, THREE } from "./scene-support.js"; @@ -154,6 +155,168 @@ export function settleInlineTerrainHash() { } } +/** + * One empty-run cell: no asset and no chunk, so residency alone answers the spawn region. + * + * The decoded manifest and placements mean the real `WorldCells` needs no fetch, no GLTF and no + * browser global, which is the only reason this row can assert the gate on the native host too. + */ +const READINESS_MANIFEST = { + version: 1, + extent: { minX: 0, minZ: 0, sizeX: 64, sizeZ: 64 }, + cellSize: 64, + terrain: { heightmap: "unused.u16", columns: 5, rows: 5, spacing: 16, heightMin: 0, heightMax: 1 }, + placements: "unused.bin", + assets: {}, + cells: [{ x: 0, z: 0, runs: [] }], +}; + +/** A follow point far outside the one-cell ring, so the cell stays unadmitted until the walk. */ +const AWAY = 9_000_000; + +/** + * Each `WorldCells.load` this row makes: caller surface, no terrain, no network. + * + * The surface is the caller's, not the world's: `WorldCells.dispose` releases only what it built, + * so this returns the surface beside the world and every caller disposes it. A rejected load hands + * back no world to own the surface, so it disposes the surface here. + */ +async function readinessWorld(follow) { + const surface = new THREE.MeshBasicMaterial(); + try { + const world = await WorldCells.load({ + url: "world.json", + surface, + follow, + ring: 0, + terrain: false, + data: { manifest: READINESS_MANIFEST, placements: new ArrayBuffer(0) }, + budgets: { bytes: 64, instances: 1, residentCells: 1 }, + }); + return { world, surface }; + } catch (error) { + surface.dispose(); + throw error; + } +} + +/** Await a rejection whose message names the expected fragment, and fail closed on a resolve. */ +async function expectRejection(promise, fragment) { + try { + await promise; + } catch (error) { + const message = String(error instanceof Error ? error.message : error); + assertCondition( + message.includes(fragment), + `terrain-inline-jobs: unexpected spawn rejection '${message}'`, + ); + return; + } + throw new Error(`terrain-inline-jobs: expected a spawn rejection naming '${fragment}'`); +} + +/** + * Lifecycle assertions for `WorldCells.whenSpawnReady` on this host. + * + * The core spec pins the gate in Node with a mocked loader. This drives the real class: a region + * outside the ring pends, the update that admits it settles the waiter for its snapshot, a settled + * region answers the next call once, disposal rejects a waiting world, and an unadmitted region + * meets its finite deadline. The marker prints only after every assertion above it holds. + */ +export async function assertSpawnReadiness() { + // A pending region: not ready at arm time, resolved by the update that admits it. + const follow = { position: { x: AWAY, y: 0, z: AWAY } }; + const { world, surface } = await readinessWorld(follow); + try { + const spawn = { x: 32, z: 32 }; + const before = world.readinessAt(spawn); + assertCondition( + before.ready === false && before.requiredCells === 1 && before.loadedCells === 0, + "terrain-inline-jobs: an unadmitted spawn region must not be ready", + ); + const pending = world.whenSpawnReady(spawn, 0, 5000); + let settled; + let failure; + pending.then( + (readiness) => { + settled = readiness; + }, + (error) => { + failure = error; + }, + ); + // Mutated after the call: the waiter must keep the coordinates it armed with. + spawn.x = AWAY; + world.update(); + await Promise.resolve(); + assertCondition( + settled === undefined && failure === undefined, + "terrain-inline-jobs: an unadmitted region must keep its waiter pending", + ); + // Move the follow point into the ring, then drive updates and microtasks until it settles. + follow.position.x = 32; + follow.position.z = 32; + for (let frame = 0; frame < 64 && settled === undefined && failure === undefined; frame += 1) { + world.update(); + await Promise.resolve(); + } + assertCondition( + failure === undefined, + `terrain-inline-jobs: whenSpawnReady rejected: ${String(failure)}`, + ); + assertCondition( + settled !== undefined && + settled.ready === true && + settled.loadedCells === 1 && + settled.requiredCells === 1 && + settled.failures === 0, + "terrain-inline-jobs: the admitted spawn region did not settle ready", + ); + // One-shot: the settled region answers the next call immediately, with no further update. + const again = await world.whenSpawnReady({ x: 32, z: 32 }, 0, 5000); + assertCondition( + again.ready === true && again.loadedCells === 1, + "terrain-inline-jobs: a ready region must answer immediately", + ); + } finally { + world.dispose(); + surface.dispose(); + } + + // Disposal rejects a waiting world instead of leaving a game awaiting its gate. + const doomedFollow = { position: { x: AWAY, y: 0, z: AWAY } }; + const { world: doomed, surface: doomedSurface } = await readinessWorld(doomedFollow); + try { + const pending = doomed.whenSpawnReady({ x: 32, z: 32 }, 0, 5000); + doomed.update(); + await Promise.resolve(); + doomed.dispose(); + await expectRejection(pending, "WorldCells spawn cancelled"); + } finally { + doomed.dispose(); + doomedSurface.dispose(); + } + + // An unadmitted region meets its finite deadline, and the region stays unready. + const timedFollow = { position: { x: AWAY, y: 0, z: AWAY } }; + const { world: timed, surface: timedSurface } = await readinessWorld(timedFollow); + try { + await expectRejection( + timed.whenSpawnReady({ x: 32, z: 32 }, 0, 25), + "WorldCells spawn exceeded 25 ms", + ); + assertCondition( + timed.readinessAt({ x: 32, z: 32 }).ready === false, + "terrain-inline-jobs: a timed-out region must stay unready", + ); + } finally { + timed.dispose(); + timedSurface.dispose(); + } + + console.info("TN_WORLD_SPAWN_READINESS pending=resolve dispose=reject timeout=25ms"); +} + export async function startScene(canvas, dimensions) { const settled = settleInlineTerrainHash(); assertCondition( @@ -165,6 +328,8 @@ export async function startScene(canvas, dimensions) { `TN_TERRAIN_INLINE_HASH hash=${settled.hash} blocks=${settled.blocks} ` + `bridges=${settled.bridges} expected=${EXPECTED_HASH}`, ); + // The spawn gate the streamed terrain games repeat, proven on the real class before the frame. + await assertSpawnReadiness(); return startVisualScene( canvas, dimensions, diff --git a/packages/runtime-native/conformance/scenes/shared/terrain-splat-array.js b/packages/runtime-native/conformance/scenes/shared/terrain-splat-array.js index ee984fc2ac..d440326fa3 100644 --- a/packages/runtime-native/conformance/scenes/shared/terrain-splat-array.js +++ b/packages/runtime-native/conformance/scenes/shared/terrain-splat-array.js @@ -1,4 +1,4 @@ -import { saturate } from "three/tsl"; +import { positionWorld, saturate, texture, vec3 } from "three/tsl"; import { loadTerrainSplat } from "../../../../core/src/world-terrain-splat.ts"; import { assertCondition, startVisualScene, THREE } from "./scene-support.js"; @@ -28,7 +28,7 @@ function mapRgb(layer, kind, x, y) { const crest = (Math.floor(x / 2) + Math.floor(y / 2) + layer) % 2 === 0; return crest ? [230, 230, 230] : [25, 25, 25]; } - if (kind === "nrm") return [128 + (layer % 5) * 24, 128 + (((layer >> 1) % 5) * 20) % 127, 255]; + if (kind === "nrm") return [128 + (layer % 5) * 24, 128 + ((((layer >> 1) % 5) * 20) % 127), 255]; if (kind === "orm") return [255, 40 + ((layer * 17) % 200), (layer % 3) * 40]; const step = 2 + (layer % 4); const on = (Math.floor(x / step) + Math.floor(y / step) + layer) % 2 === 0; @@ -76,6 +76,7 @@ function layersTable() { id: `layer${slot}`, lo: 0.25, mask: `m${slot}`, + maps: slot === 0 ? { height: "shared/layer0-height.png" } : undefined, normal: true, orm: true, tile: 2, @@ -83,7 +84,18 @@ function layersTable() { }); } return { - base: { id: "base", normal: true, orm: true, tile: 2, tint: [0.85, 0.85, 0.88] }, + base: { + id: "base", + maps: { + diffuse: "shared/base-color.png", + normal: "shared/base-nor_gl.png", + orm: "shared/base-arm.png", + }, + normal: true, + orm: true, + tile: 2, + tint: [0.85, 0.85, 0.88], + }, breakup: { push: 0.06, scale: 0.35 }, layers, macro: { max: 1.1, min: 0.85, scale: 0.12 }, @@ -93,7 +105,7 @@ function layersTable() { } /** world.json, the table and the mask bytes, served the way a game's own package is. */ -function packageAssets() { +function packageAssets(table = layersTable()) { const files = new Map([ [ "world.json", @@ -103,7 +115,7 @@ function packageAssets() { version: 1, }), ], - ["layers/table.json", JSON.stringify(layersTable())], + ["layers/table.json", JSON.stringify(table)], ["layers/splat.rgba8", splatBytes()], ]); return { @@ -113,7 +125,16 @@ function packageAssets() { return [URL.createObjectURL(new Blob([bytes], { type: "application/octet-stream" }))]; }, texture: async (path, options = {}) => { - const match = /^layers\/(?:base|layer(\d+))_(diff|nrm|orm|h)\.jpg$/u.exec(path); + // Explicit package paths resolve to the same fixtures as the convention-named maps. + const aliases = { + "shared/base-color.png": "layers/base_diff.jpg", + "shared/base-nor_gl.png": "layers/base_nrm.jpg", + "shared/base-arm.png": "layers/base_orm.jpg", + "shared/layer0-height.png": "layers/layer0_h.jpg", + }; + const match = /^layers\/(?:base|layer(\d+))_(diff|nrm|orm|h)\.jpg$/u.exec( + aliases[path] ?? path, + ); assertCondition(match !== null, `terrain-splat-array: unexpected map '${path}'`); const texture = layerTexture( match[1] === undefined ? 0 : Number(match[1]), @@ -126,12 +147,101 @@ function packageAssets() { }; } +export function assertCallbackPixels(pixels) { + assertCondition( + pixels instanceof Uint8Array && pixels.length === 64, + "callback readback must be 4x4 RGBA8", + ); + const counts = [0, 0, 0]; + // Linear RGBA8: sRGB albedo at texel (4,4), scaled by callback index; normal/ORM stay data. + const expected = [ + [67, 128, 40], + [135, 128, 40], + [8, 152, 57], + ]; + for (let offset = 0; offset < pixels.length; offset += 4) { + const index = expected.findIndex((rgb) => + rgb.every((value, channel) => Math.abs(pixels[offset + channel] - value) <= 1), + ); + assertCondition( + index !== -1 && pixels[offset + 3] === 255, + `unexpected callback pixel ${pixels.slice(offset, offset + 4)}`, + ); + counts[index] += 1; + } + // Existing plane-0 r/g bytes repeat every four cells; each retained mask paints four texels. + assertCondition(counts.join() === "8,4,4", `callback blend populations: ${counts}`); +} + +async function verifyCallbacks(renderer) { + const table = layersTable(); + table.layers = table.layers.slice(0, 2); + table.breakup.push = 0; + table.macro.min = table.macro.max = 1; + const material = await loadTerrainSplat({ + assets: packageAssets(table), + renderer: { raw: renderer }, + url: "world.json", + layerSample: ({ diffuse, normal, orm, index }) => { + const uv = positionWorld.xz.div(2).add(0.0625); + return { + color: texture(diffuse, uv).rgb.mul((index + 1) / 3), + normal: texture(normal, uv).rgb, + orm: texture(orm, uv).rgb, + }; + }, + materialFactory: ({ color, normal, orm }) => { + const result = new THREE.MeshBasicNodeMaterial(); + result.colorNode = vec3(color.r, normal.r, orm.g); + result.toneMapped = false; + return result; + }, + }); + const target = new THREE.RenderTarget(4, 4); + const previous = renderer.getRenderTarget(); + const geometry = new THREE.PlaneGeometry(HALF * 2, HALF * 2); + try { + assertCondition( + material.userData.TN_TERRAIN_SPLAT === + "layers=3 loadedSamplerUpperBound=12 stacked=0 sampling=custom", + "custom callback sampler bound changed", + ); + const probe = new THREE.Scene(); + const ground = new THREE.Mesh(geometry, material); + ground.rotation.x = -Math.PI / 2; + probe.add(ground); + const camera = new THREE.OrthographicCamera(-HALF, HALF, HALF, -HALF, 0.1, 20); + camera.position.set(0, 8, 0); + camera.up.set(0, 0, -1); + camera.lookAt(0, 0, 0); + renderer.setRenderTarget(target); + renderer.render(probe, camera); + const pixels = new Uint8Array(64); + // Three returns 256-byte-padded rows; single-row reads contain only the requested texels. + for (let row = 0; row < 4; row += 1) { + const data = await renderer.readRenderTargetPixelsAsync(target, 0, row, 4, 1); + assertCondition( + data instanceof Uint8Array && data.length === 16, + "callback row must be RGBA8", + ); + pixels.set(data, row * 16); + } + assertCallbackPixels(pixels); + } finally { + renderer.setRenderTarget(previous); + target.dispose(); + geometry.dispose(); + material.dispose(); + } +} + export async function startScene(canvas, dimensions) { return startVisualScene( canvas, dimensions, "terrain-splat-array", async ({ renderer, scene }) => { + await verifyCallbacks(renderer); const material = await loadTerrainSplat({ assets: packageAssets(), layerWeight: heightBlend, @@ -161,4 +271,4 @@ export async function startScene(canvas, dimensions) { }, }, ); -} \ No newline at end of file +} diff --git a/packages/runtime-native/conformance/scenes/shared/virtual-shadow-first-frame.js b/packages/runtime-native/conformance/scenes/shared/virtual-shadow-first-frame.js new file mode 100644 index 0000000000..a8113b27c8 --- /dev/null +++ b/packages/runtime-native/conformance/scenes/shared/virtual-shadow-first-frame.js @@ -0,0 +1,31 @@ +import { assertGpuValidationApi } from "./gpu-validation-scope.js"; +import { assertCondition, startVisualScene } from "./scene-support.js"; +import { assertSubmittedWorkPromise } from "./screenshot-completion.js"; +import { buildVirtualShadowScene } from "./virtual-shadow.js"; + +export function assertVirtualShadowFirstFrameProof(stats, validationError) { + assertCondition(stats?.frame === 1, "virtual shadow first frame was not observed"); + assertCondition(stats.levels === 3 && stats.rendered > 0 && stats.rendersTotal > 0, + "first frame did not submit virtual shadow levels"); + assertCondition(validationError === null, + "virtual shadow first frame validation observation missing or failed (Destroyed texture)"); +} + +export function startScene(canvas, dimensions) { + return startVisualScene(canvas, dimensions, "virtual-shadow-first-frame", async (context) => { + const { renderer, scene, camera } = context; + const device = renderer.backend?.device; + assertGpuValidationApi(device); + assertCondition(typeof device.queue?.onSubmittedWorkDone === "function", "GPU queue missing"); + device.pushErrorScope("validation"); + const subject = buildVirtualShadowScene(context); + renderer.render(scene, camera); + const stats = subject.node.stats; + const completion = device.queue.onSubmittedWorkDone(); + assertSubmittedWorkPromise(completion, "virtual-shadow-first-frame"); + await completion; + const validationError = await device.popErrorScope(); + assertVirtualShadowFirstFrameProof(stats, validationError); + return { ...subject, detail: { firstFrame: stats.frame, renderedLevels: stats.rendered, validationErrors: 0 } }; + }, { deferInitialRender: true }); +} diff --git a/packages/runtime-native/conformance/scenes/shared/virtual-shadow.js b/packages/runtime-native/conformance/scenes/shared/virtual-shadow.js index d6f45cfdd0..871e61f6f7 100644 --- a/packages/runtime-native/conformance/scenes/shared/virtual-shadow.js +++ b/packages/runtime-native/conformance/scenes/shared/virtual-shadow.js @@ -15,7 +15,10 @@ export function assertVirtualShadowProof({ renderer, light, node }) { } export function startScene(canvas, dimensions) { - return startVisualScene(canvas, dimensions, "virtual-shadow", ({ renderer, scene, camera }) => { + return startVisualScene(canvas, dimensions, "virtual-shadow", buildVirtualShadowScene); +} + +export function buildVirtualShadowScene({ renderer, scene, camera }) { renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; camera.position.set(0, 4, 7); @@ -47,5 +50,4 @@ export function startScene(canvas, dimensions) { scene.add(floor, ball, light, light.target, new THREE.AmbientLight(0xffffff, 0.15)); return { ball, floor, light, node, detail: { levels: node.options.clipExtents.length } }; - }); } diff --git a/packages/runtime-native/conformance/scenes/shared/world-cells-proxy-stream.js b/packages/runtime-native/conformance/scenes/shared/world-cells-proxy-stream.js new file mode 100644 index 0000000000..8b2e98b36c --- /dev/null +++ b/packages/runtime-native/conformance/scenes/shared/world-cells-proxy-stream.js @@ -0,0 +1,115 @@ +import { createRenderer } from "../../../../core/src/renderer.ts"; +import { WorldCells } from "../../../../core/src/world-cells.ts"; +import { assertGpuValidationApi } from "./gpu-validation-scope.js"; +import { THREE, assertCondition } from "./scene-support.js"; +import { assertSubmittedWorkPromise } from "./screenshot-completion.js"; + +export function assertWorldCellsProxyProof(observations, validationError) { + assertCondition(observations?.length === 2, "two streamed cell observations required"); + for (const [index, stats] of observations.entries()) { + assertCondition(stats.residentKeys?.includes(`${index * 2}:0`), "streamed cell missing"); + assertCondition(stats.loadedCells > 0 && stats.instances > 0, "cell source not admitted"); + assertCondition( + stats.hlod?.compiled > 0 && stats.hlod.observed > 0 && stats.hlod.active > 0, + "proxy must compile, submit on the main camera, and replace detail", + ); + assertCondition(stats.failures === 0 && stats.hlod.failures === 0, "streaming failed"); + } + assertCondition(observations[1].evictions > 0, "first cell did not evict"); + assertCondition(validationError === null, "GPU validation observation missing or failed"); +} + +export async function startScene(canvas, dimensions) { + const renderer = await createRenderer({ + canvas, antialias: false, preferWebGPU: true, pixelRatio: 1, pipelineCensus: false, + }); + renderer.setSize(dimensions.width, dimensions.height, false); + const device = renderer.raw.backend?.device; + assertGpuValidationApi(device); + assertCondition(typeof device.queue?.onSubmittedWorkDone === "function", "GPU queue missing"); + device.pushErrorScope("validation"); + + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0x18202f); + const camera = new THREE.PerspectiveCamera(60, dimensions.width / dimensions.height, 0.1, 1000); + const follow = { position: { x: 8, z: 8 } }; + const cells = [0, 2].map((x, index) => ({ + x, z: 0, runs: [{ asset: "box", offset: index, count: 1 }], + proxy: { + glb: `proxy-${x}.glb`, scope: "cell", triangles: 2, materialGroups: 1, error: 0.1, + bounds: { min: [x * 32 + 7, 0, 8], max: [x * 32 + 9, 2, 8] }, + }, + })); + const world = await WorldCells.load({ + url: "/world/world.json", follow, ring: 0, + surface: new THREE.MeshBasicMaterial(), + // HLOD reserves up to 8 MiB for decoded source and fixed proxy copies before IO. + budgets: { bytes: 10000000, instances: 10, residentCells: 2 }, + terrain: false, gpuScene: false, bundles: false, adaptiveLod: false, + data: { + manifest: { + version: 1, extent: { minX: 0, minZ: 0, sizeX: 96, sizeZ: 32 }, cellSize: 32, + terrain: { + heightmap: "height.u16", columns: 4, rows: 2, spacing: 32, heightMin: 0, heightMax: 2, + }, + placements: "placements.bin", + assets: { box: { glb: "box.glb", lods: [], bounds: { min: [-1, -1, -1], max: [1, 1, 1] } } }, + cells, + }, + placements: new Float32Array([8, 1, 8, 0, 0, 0, 1, 1, 72, 1, 8, 0, 0, 0, 1, 1]).buffer, + }, + loadModel: async (url) => { + const proxy = /proxy-(0|2)\.glb$/u.exec(url); + const root = new THREE.Group(); + root.add(new THREE.Mesh( + proxy ? new THREE.PlaneGeometry(2, 2) : new THREE.BoxGeometry(2, 2, 2), + new THREE.MeshBasicMaterial({ color: 0xf6ad55 }), + )); + if (proxy) root.position.set(Number(proxy[1]) * 32 + 8, 1, 8); + return root; + }, + }); + scene.add(world); + const observations = []; + for (const x of [8, 72]) { + follow.position.x = x; + camera.position.set(x, 2, 308); + camera.lookAt(x, 1, 8); + for (let frame = 0; frame < 180; frame++) { + camera.updateMatrixWorld(true); + world.update(renderer, camera); + renderer.render(scene, camera); + const completion = device.queue.onSubmittedWorkDone(); + assertSubmittedWorkPromise(completion, "world-cell-render"); + await completion; + const stats = world.stats(); + if (stats.residentKeys.includes(`${(x - 8) / 32}:0`) && stats.hlod.active > 0) { + observations.push(stats); + break; + } + await new Promise(requestAnimationFrame); + } + assertCondition(observations.length === (x === 8 ? 1 : 2), "proxy stream timed out"); + camera.position.set(x, 2, 15); + camera.lookAt(x, 1, 8); + camera.updateMatrixWorld(true); + world.update(renderer, camera); + assertCondition(world.stats().hlod.active === 0, "near detail did not replace proxy"); + renderer.render(scene, camera); + const completion = device.queue.onSubmittedWorkDone(); + assertSubmittedWorkPromise(completion, "near-detail-render"); + await completion; + } + const validationError = await device.popErrorScope(); + assertWorldCellsProxyProof(observations, validationError); + function frame() { + renderer.render(scene, camera); + requestAnimationFrame(frame); + } + requestAnimationFrame(frame); + globalThis.__TN_CONFORMANCE = { + ok: true, type: "world-cells-proxy-stream", + detail: { streamedCells: 2, proxySwaps: 2, nearRestores: 2, validationErrors: 0 }, + }; + return { renderer, scene, camera, subject: { world } }; +} diff --git a/packages/runtime-native/tests/conformance-runner.test.mjs b/packages/runtime-native/tests/conformance-runner.test.mjs index 60a082a85f..9f5265ab4d 100644 --- a/packages/runtime-native/tests/conformance-runner.test.mjs +++ b/packages/runtime-native/tests/conformance-runner.test.mjs @@ -123,6 +123,15 @@ test("the GPU scene BVH row requires a hardware adapter", () => { assert.equal(row?.requiresHardwareAdapter, true); }); +test("a diagnostic row reusing a scene fixture keeps that scene's visual tolerance", () => { + // Both rows render buildVirtualShadowScene; first-frame GPU assertions add the diagnostic proof. + const registry = JSON.parse(readFileSync(join(root, "conformance/registry.json"), "utf8")); + const scene = registry.tests.find(({ id }) => id === "33-virtual-shadow"); + const diagnostic = registry.tests.find(({ id }) => id === "virtual-shadow-first-frame"); + assert.ok(scene && diagnostic, "both virtual-shadow rows must be registered"); + assert.deepEqual(diagnostic.tolerance, scene.tolerance); +}); + test("World GPU readback gives awaited scene frames time to reach their proof deadline", () => { const registry = JSON.parse(readFileSync(join(root, "conformance/registry.json"), "utf8")); const row = registry.tests.find(({ id }) => id === "world-gpu-scene-readback"); diff --git a/packages/runtime-native/tests/planned-animation-rows.test.mjs b/packages/runtime-native/tests/planned-animation-rows.test.mjs index b52dffd9a1..d601dac701 100644 --- a/packages/runtime-native/tests/planned-animation-rows.test.mjs +++ b/packages/runtime-native/tests/planned-animation-rows.test.mjs @@ -2,10 +2,16 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; -import { test } from "vitest"; +import { test, vi } from "vitest"; import { createMixerAnimationProof } from "../conformance/scenes/shared/mixer-animation.js"; -import { createMorphTargetAnimationProof } from "../conformance/scenes/shared/morph-target-animation.js"; +import { createMorphTargetAnimationProof, startScene as startMorphScene } from "../conformance/scenes/shared/morph-target-animation.js"; +import { startVisualScene, THREE } from "../conformance/scenes/shared/scene-support.js"; + +vi.mock("../conformance/scenes/shared/scene-support.js", async (original) => ({ + ...await original(), + startVisualScene: vi.fn(), +})); import { assertAnimatedGltf, assertPbrHelmet, @@ -81,6 +87,46 @@ test("rows 51 through 53 exercise mixer, skeleton, and morph target semantics", assert.ok(Math.abs(morph.mesh.morphTargetInfluences[0] - 0.65) < 1e-6); }); +test("row 53 replaces rendered morph geometry between completed frames and disposes the original once", async () => { + const events = []; + const geometries = []; + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera(); + const renderer = { + backend: { device: { queue: { onSubmittedWorkDone: async () => { events.push("completed"); } } } }, + render(currentScene, currentCamera) { + assert.equal(currentScene, scene); + assert.equal(currentCamera, camera); + const mesh = scene.children[0]; + assert.ok(Math.abs(mesh.morphTargetInfluences[0] - 0.65) < 1e-6); + assert.deepEqual(mesh.rotation.toArray(), [-0.25, 0.55, 0.1, "XYZ"]); + geometries.push(mesh.geometry); + events.push("render"); + if (geometries.length === 1) mesh.geometry.addEventListener("dispose", () => events.push("disposed")); + }, + }; + startVisualScene.mockImplementationOnce(async (_canvas, _dimensions, _type, build) => build({ renderer, scene, camera })); + vi.stubGlobal("requestAnimationFrame", (callback) => { + events.push("raf"); + queueMicrotask(() => callback(0)); + }); + try { + const subject = await startMorphScene({}, { width: 1280, height: 720 }); + assert.equal(geometries.length, 2, "both old and replacement geometry must be drawn"); + assert.notEqual(geometries[0].uuid, geometries[1].uuid); + assert.equal(subject.mesh.geometry, geometries[1]); + assert.deepEqual(geometries[0].morphAttributes.position[0].array, geometries[1].morphAttributes.position[0].array); + assert.equal(subject.detail.originalGeometry, geometries[0].uuid); + assert.equal(subject.detail.replacementGeometry, geometries[1].uuid); + assert.equal(subject.detail.originalDisposals, 1); + assert.deepEqual(events, ["raf", "render", "completed", "disposed", "raf", "render", "completed"]); + subject.mesh.geometry.dispose(); + subject.mesh.material.dispose(); + } finally { + vi.unstubAllGlobals(); + } +}); + test("all six rows are same-source, fail-closed, deterministic, and target-neutral", () => { const source = sceneNames .map((name) => readFileSync(join(root, "conformance/scenes/shared", name), "utf8")) diff --git a/packages/runtime-native/tests/virtual-shadow-first-frame.test.mjs b/packages/runtime-native/tests/virtual-shadow-first-frame.test.mjs new file mode 100644 index 0000000000..424d6dfe6f --- /dev/null +++ b/packages/runtime-native/tests/virtual-shadow-first-frame.test.mjs @@ -0,0 +1,14 @@ +import assert from "node:assert/strict"; +import { test } from "vitest"; +import { assertVirtualShadowFirstFrameProof } from "../conformance/scenes/shared/virtual-shadow-first-frame.js"; + +test("virtual shadow first frame proof rejects missing submissions and destroyed textures", () => { + const stats = { frame: 1, levels: 3, rendered: 1, rendersTotal: 1 }; + assert.doesNotThrow(() => assertVirtualShadowFirstFrameProof(stats, null)); + assert.throws(() => assertVirtualShadowFirstFrameProof(undefined, null), /not observed/u); + assert.throws(() => assertVirtualShadowFirstFrameProof({}, null), /not observed/u); + assert.throws(() => assertVirtualShadowFirstFrameProof({ ...stats, frame: 2 }, null), /first frame/u); + assert.throws(() => assertVirtualShadowFirstFrameProof({ ...stats, rendered: 0 }, null), /submit/u); + assert.throws(() => assertVirtualShadowFirstFrameProof(stats, undefined), /observation missing/u); + assert.throws(() => assertVirtualShadowFirstFrameProof(stats, { message: "Destroyed texture" }), /Destroyed texture/u); +}); diff --git a/packages/runtime-native/tests/world-cells-proxy-stream.test.mjs b/packages/runtime-native/tests/world-cells-proxy-stream.test.mjs new file mode 100644 index 0000000000..036630c5f6 --- /dev/null +++ b/packages/runtime-native/tests/world-cells-proxy-stream.test.mjs @@ -0,0 +1,88 @@ +import assert from "node:assert/strict"; +import { test, vi } from "vitest"; +import { createRenderer } from "../../core/src/renderer.ts"; +import { assertWorldCellsProxyProof, startScene } from "../conformance/scenes/shared/world-cells-proxy-stream.js"; + +vi.mock("../../core/src/renderer.ts", () => ({ createRenderer: vi.fn() })); + +test.each([true, false])("the scene requires backend proxy submission: %s", async (submitProxy) => { + const frames = []; + const observations = []; + let world; + const renderer = { + kind: "webgpu", + domElement: { height: 720 }, + info: { render: { drawCalls: 0 } }, + raw: { backend: { device: { + pushErrorScope() {}, + popErrorScope: async () => null, + queue: { onSubmittedWorkDone: async () => {} }, + } } }, + setSize() {}, + compileAsync: async () => {}, + uploadAttributes: () => 1, + render(scene, camera) { + world = scene.children.find((child) => typeof child.stats === "function"); + observations.push(world.stats()); + scene.updateMatrixWorld(true); + scene.traverseVisible((mesh) => { + if (!mesh.isMesh || !camera.layers.test(mesh.layers)) return; + mesh.onBeforeRender(renderer.raw, scene, camera, mesh.geometry, mesh.material, null); + // Three's WebGPU backend submits zero-count draws too; no GPU is used here. + if (submitProxy || !mesh.parent?.name.startsWith("world-cell-proxy:")) + renderer.info.render.drawCalls++; + mesh.onAfterRender(renderer.raw, scene, camera, mesh.geometry, mesh.material, null); + }); + }, + }; + createRenderer.mockResolvedValue(renderer); + vi.stubGlobal("requestAnimationFrame", (callback) => frames.push(callback)); + vi.stubGlobal("__TN_CONFORMANCE", undefined); + let settled = false; + const running = startScene({}, { width: 1280, height: 720 }); + void running.then(() => { settled = true; }, () => { settled = true; }); + try { + for (let frame = 0; frame < 400 && !settled; frame++) { + await new Promise(setImmediate); + for (const callback of frames.splice(0)) callback(frame); + } + assert.equal(settled, true, "scene did not settle within its frame bound"); + if (!submitProxy) { + await assert.rejects(running, /proxy stream timed out/u); + assert.equal(world.stats().hlod.compiled, 1); + assert.equal(world.stats().hlod.observed, 0); + assert.equal(world.stats().hlod.active, 0); + return; + } + await running; + for (const key of ["0:0", "2:0"]) { + assert.ok(observations.some((stats) => stats.residentKeys.includes(key) && stats.hlod.active > 0)); + } + assert.equal(world.stats().hlod.active, 0); + assert.equal(world.stats().hlod.refused, 0); + assert.equal(globalThis.__TN_CONFORMANCE?.ok, true); + } finally { + world?.dispose(); + vi.unstubAllGlobals(); + } +}); + +test("WorldCells conformance requires two admitted cells, observed proxy swaps and validation", () => { + const observations = [0, 2].map((x) => ({ + residentKeys: [`${x}:0`], loadedCells: 1, instances: 1, failures: 0, evictions: x, + hlod: { compiled: 1, observed: 1, active: 1, failures: 0 }, + })); + assert.doesNotThrow(() => assertWorldCellsProxyProof(observations, null)); + assert.throws(() => assertWorldCellsProxyProof([], null), /two streamed/u); + assert.throws(() => assertWorldCellsProxyProof(undefined, null), /two streamed/u); + assert.throws(() => assertWorldCellsProxyProof(observations, undefined), /validation/u); + assert.throws(() => assertWorldCellsProxyProof(observations, { message: "GPU error" }), /validation/u); + for (const field of ["compiled", "observed", "active"]) { + const missing = structuredClone(observations); + missing[0].hlod[field] = 0; + assert.throws(() => assertWorldCellsProxyProof(missing, null), /proxy must/u); + } + const missingCell = structuredClone(observations); + missingCell[1].residentKeys = []; + assert.throws(() => assertWorldCellsProxyProof(missingCell, null), /cell missing/u); +}); diff --git a/packages/terrain/AGENT_GUIDE.md b/packages/terrain/AGENT_GUIDE.md index ed166a87ab..c5a9548be8 100644 --- a/packages/terrain/AGENT_GUIDE.md +++ b/packages/terrain/AGENT_GUIDE.md @@ -169,11 +169,11 @@ panel that calls the same code. A write names `baseRevision`; a stale one is a 4 ## Resolved static-world GLB -Import `exportWorldGLB` from `@threenative/terrain/export` in browser authoring source. Its import is DOM-free; encoding requires FileReader and canvas. Root and `/three` remain headless. This entry uses the installed Three.js GLTFExporter rather than a terrain-specific loader format. +Import `prepareWorldGLTF` from `@threenative/terrain/export` in browser authoring source. Its import is DOM-free; scene preparation requires FileReader and canvas. Root and `/three` remain headless. This entry uses the installed Three.js GLTFExporter to prepare glTF JSON; the Node assets cook owns GLB encoding. ```ts -import { exportWorldGLB } from '@threenative/terrain/export'; -const output = await exportWorldGLB({ +import { prepareWorldGLTF } from '@threenative/terrain/export'; +const prepared = await prepareWorldGLTF({ revision, // accepted document's 64-character content hash snapshotTime, // finite seconds, pinned across all baked deformations state, // evaluated committed recipe with applied override records @@ -182,7 +182,10 @@ const output = await exportWorldGLB({ transforms, // Map: actual final grounded root matrices water, // [{ id, object, time: snapshotTime, staleFrames: 0 }] }); -// output.bytes is the self-contained world.glb; persist only after success. +// Send prepared.json to your Node authoring process, then: +// import { cookTerrainWorld } from '@threenative/assets'; +// const { worldGlb } = await cookTerrainWorld({ preparedWorld: prepared.json }); +// worldGlb is the self-contained world.glb; persist only after success. ``` Game source owns all geometry, materials and images. The terrain needs albedo `map`, `normalMap`, `roughnessMap` and `aoMap`; vertex colours alone are insufficient. MeshStandardMaterial metallic/roughness surfaces are supported. Colour/emissive images are sRGB, data maps are NoColorSpace. Supply decoded byte images (at most 16 megapixels); DataTexture images need complete RGBA bytes. Bake alpha into the colour image. Bake displacement, bump/light maps, node shaders, skinning/morphing and wind into the supplied static geometry/material first. Use tangent-space normal maps with equal X/Y scale magnitudes; bake object-space/anisotropic maps first. MeshPhysicalMaterial extensions are outside this bounded encoder's contract. diff --git a/packages/terrain/__tests__/alpine-kit.spec.ts b/packages/terrain/__tests__/alpine-kit.spec.ts deleted file mode 100644 index 360248ef5a..0000000000 --- a/packages/terrain/__tests__/alpine-kit.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { copyFileSync, mkdirSync, readFileSync, symlinkSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { type IWorldPackage, validateWorldPackage } from "@threenative/core/world"; -import { describe, expect, it } from "vitest"; -import { makeTempDirSync } from "../../../test-support/temp-dir.js"; - -const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); -const kit = join(packageRoot, "starter", "alpine"); -const starterAssets = join(packageRoot, "starter-assets"); - -/** Copies the alpine kit into a temp directory whose node_modules resolves the workspace package. */ -function kitInTempDir(): string { - const dir = makeTempDirSync("terrain-alpine-kit-"); - for (const name of ["bake.mjs", "recipe.json", "assets.json", "surface.json"]) - copyFileSync(join(kit, name), join(dir, name)); - mkdirSync(join(dir, "node_modules", "@threenative"), { recursive: true }); - symlinkSync(packageRoot, join(dir, "node_modules", "@threenative", "terrain"), "dir"); - return dir; -} - -function bake(out: string): void { - execFileSync(process.execPath, ["bake.mjs", "--assets", starterAssets, "--out", out], { - cwd: kitInTempDir(), - stdio: "pipe", - }); -} - -describe("alpine starter kit", () => { - it("bakes a world package the engine's validator accepts", () => { - const out = join(makeTempDirSync("terrain-alpine-world-"), "world"); - bake(out); - const manifest = JSON.parse(readFileSync(join(out, "world.json"), "utf8")) as IWorldPackage; - const result = validateWorldPackage(manifest, { - heightmapByteLength: readFileSync(join(out, manifest.terrain.heightmap)).byteLength, - placementsByteLength: readFileSync(join(out, manifest.placements)).byteLength, - }); - expect(result.errors).toEqual([]); - expect(manifest.terrain.columns).toBe(257); - expect(manifest.extent.sizeX).toBe(512); - - const perAsset: Record = {}; - for (const cell of manifest.cells) - for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; - expect(Object.keys(perAsset).sort()).toEqual(["boulder"]); - expect(perAsset.boulder).toBe(90); - }); - - it("bakes the same bytes from the same recipe", () => { - const first = join(makeTempDirSync("terrain-alpine-a-"), "world"); - const second = join(makeTempDirSync("terrain-alpine-b-"), "world"); - bake(first); - bake(second); - for (const name of ["world.json", "heightmap.u16", "placements.bin", "splat.rgba"]) { - expect(readFileSync(join(first, name)).equals(readFileSync(join(second, name))), name).toBe( - true, - ); - } - }); -}); diff --git a/packages/terrain/__tests__/coastal-kit.spec.ts b/packages/terrain/__tests__/coastal-kit.spec.ts deleted file mode 100644 index bfefea2ee3..0000000000 --- a/packages/terrain/__tests__/coastal-kit.spec.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { copyFileSync, mkdirSync, readFileSync, symlinkSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { type IWorldPackage, validateWorldPackage } from "@threenative/core/world"; -import { describe, expect, it } from "vitest"; -import { makeTempDirSync } from "../../../test-support/temp-dir.js"; - -const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); -const kit = join(packageRoot, "starter", "coastal"); -const starterAssets = join(packageRoot, "starter-assets"); - -/** Copies the coastal kit into a temp directory whose node_modules resolves the workspace package. */ -function kitInTempDir(): string { - const dir = makeTempDirSync("terrain-coastal-kit-"); - for (const name of ["bake.mjs", "recipe.json", "assets.json", "surface.json"]) - copyFileSync(join(kit, name), join(dir, name)); - mkdirSync(join(dir, "node_modules", "@threenative"), { recursive: true }); - symlinkSync(packageRoot, join(dir, "node_modules", "@threenative", "terrain"), "dir"); - return dir; -} - -function bake(out: string): void { - execFileSync(process.execPath, ["bake.mjs", "--assets", starterAssets, "--out", out], { - cwd: kitInTempDir(), - stdio: "pipe", - }); -} - -describe("coastal starter kit", () => { - it("bakes a world package the engine's validator accepts, with its ocean", () => { - const out = join(makeTempDirSync("terrain-coastal-world-"), "world"); - bake(out); - const manifest = JSON.parse(readFileSync(join(out, "world.json"), "utf8")) as IWorldPackage; - const result = validateWorldPackage(manifest, { - heightmapByteLength: readFileSync(join(out, manifest.terrain.heightmap)).byteLength, - placementsByteLength: readFileSync(join(out, manifest.placements)).byteLength, - }); - expect(result.errors).toEqual([]); - expect(manifest.terrain.columns).toBe(257); - - const perAsset: Record = {}; - for (const cell of manifest.cells) - for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; - expect(Object.keys(perAsset).sort()).toEqual(["fir"]); - expect(perAsset.fir).toBe(80); - - const water = JSON.parse(readFileSync(join(out, "water.json"), "utf8")) as { - lakes: { id: string; level: number }[]; - }; - expect(water.lakes).toEqual([expect.objectContaining({ id: "ocean", level: 1.5 })]); - }); - - it("bakes the same bytes from the same recipe", () => { - const first = join(makeTempDirSync("terrain-coastal-a-"), "world"); - const second = join(makeTempDirSync("terrain-coastal-b-"), "world"); - bake(first); - bake(second); - for (const name of [ - "world.json", - "heightmap.u16", - "placements.bin", - "splat.rgba", - "water.json", - ]) { - expect(readFileSync(join(first, name)).equals(readFileSync(join(second, name))), name).toBe( - true, - ); - } - }); -}); diff --git a/packages/terrain/__tests__/coastal-runtime.spec.ts b/packages/terrain/__tests__/coastal-runtime.spec.ts deleted file mode 100644 index d801851a89..0000000000 --- a/packages/terrain/__tests__/coastal-runtime.spec.ts +++ /dev/null @@ -1,346 +0,0 @@ -import type { ICtx } from "@threenative/core"; -import type { IWorldCellsLoadOptions, IWorldPackage } from "@threenative/core/world"; -import { type IPhysicsContext, rapier } from "@threenative/physics"; -import { Group, Mesh, MeshBasicMaterial, MeshStandardMaterial, Object3D, Texture } from "three"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { PropColliders, addCoastal } from "../starter/coastal/world.js"; - -const loads = vi.hoisted(() => ({ surface: vi.fn(), sky: vi.fn(), world: vi.fn() })); -vi.mock("@threenative/core/world", async (original) => { - const actual = await original(); - return { ...actual, loadTerrainSplat: loads.surface, WorldCells: { load: loads.world } }; -}); -vi.mock("three/addons/loaders/HDRLoader.js", () => ({ - HDRLoader: class { - loadAsync = loads.sky; - }, -})); - -// The real sea is a water surface over a mesh, which needs WebGPU; the world only adds it and later releases it. -vi.mock("../starter/coastal/sea.js", () => ({ - addCoastalSea: vi.fn(() => ({ mesh: {}, dispose: vi.fn() })), -})); - -type Ctx = ICtx, IPhysicsContext>; -const plugins: ReturnType[] = []; -const lifetimes: Array<() => void> = []; -const worlds: Array = []; -const manifest: IWorldPackage = { - version: 1, - cellSize: 64, - extent: { minX: -128, minZ: -128, sizeX: 256, sizeZ: 256 }, - assets: { fir: { glb: "fir.glb", bounds: { min: [-1, 0, -1], max: [1, 1, 1] } } }, - cells: [{ x: 0, z: 0, runs: [{ asset: "fir", offset: 0, count: 2 }] }], - placements: "placements.bin", - terrain: { - heightmap: "heightmap.u16", - columns: 3, - rows: 3, - spacing: 128, - heightMin: 0, - heightMax: 10, - }, -}; -// Two firs, eight floats each: x, y, z, three unused, a scale in the last slot. One sits at the -// origin and one 130 m east. -const records = new Float32Array([0, 0, 0, 0, 0, 0, 1, 1, 130, 0, 0, 0, 0, 0, 1, 1]); -const heightmap = new Uint16Array([0, 2000, 4000, 6000, 8000, 10000, 12000, 14000, 16000]); -let requests: string[]; -let surface: MeshBasicMaterial; -let sky: Texture; - -async function context() { - const scene = new Group(); - const model = new Group(); - const ctx = { - scene, - assets: { resolve: async (path: string) => [path], model: async () => ({ scene: model }) }, - add: (object: T): T => { - scene.add(object); - return object; - }, - entities: { - add: (_id: string, entity: { dispose?: () => void }) => { - if (entity.dispose) lifetimes.push(entity.dispose); - return entity; - }, - }, - } as unknown as Ctx; - const plugin = rapier(); - await plugin.setup?.(ctx); - plugins.push(plugin); - return { ctx, plugin, model }; -} - -beforeEach(() => { - requests = []; - surface = new MeshBasicMaterial(); - sky = new Texture(); - vi.spyOn(surface, "dispose"); - vi.spyOn(sky, "dispose"); - loads.surface.mockReset().mockResolvedValue(surface); - loads.sky.mockReset().mockResolvedValue(sky); - loads.world.mockReset().mockImplementation(async (options: IWorldCellsLoadOptions) => { - if (!options.data) { - // The real WorldCells API reads these itself unless the consumer supplies decoded data. - await (await fetch(options.url)).json(); - await (await fetch("terrain/coastal/placements.bin")).arrayBuffer(); - await (await fetch("terrain/coastal/heightmap.u16")).arrayBuffer(); - } - const world = Object.assign(new Group(), { - dispose: vi.fn(function (this: Group) { - this.removeFromParent(); - }), - }); - worlds.push(world); - return world; - }); - vi.stubGlobal("fetch", async (input: string) => { - requests.push(input); - if (input.endsWith("world.json")) return new Response(JSON.stringify(manifest)); - if (input.endsWith("placements.bin")) return new Response(records.slice().buffer); - if (input.endsWith("heightmap.u16")) return new Response(heightmap.slice().buffer); - if (input.endsWith("water.json")) - return new Response( - JSON.stringify({ lakes: [{ id: "ocean", radius: 512, level: 1.5 }], rivers: [] }), - ); - throw new Error(`Unexpected coastal request: ${input}`); - }); -}); - -afterEach(() => { - for (const dispose of lifetimes.splice(0)) dispose(); - for (const world of worlds.splice(0)) world.dispose(); - for (const plugin of plugins.splice(0)) plugin.dispose?.({} as Ctx); - vi.unstubAllGlobals(); - vi.restoreAllMocks(); -}); - -describe("coastal runtime", () => { - it("streams fir colliders when only the follow object's parent moves", async () => { - const { ctx, plugin } = await context(); - const parent = new Object3D(); - const follow = new Object3D(); - parent.add(follow); - const colliders = new PropColliders(ctx, follow, manifest, records); - parent.position.x = 130; - colliders.process(); - plugin.update?.(ctx, 1 / 60); - // A fir's sphere sits on its origin, so a ray half a metre up crosses it. - const hit = (x: number) => - ctx.physics.directSpaceState.intersectRay({ - from: { x, y: 0.5, z: -2 }, - to: { x, y: 0.5, z: 2 }, - }); - expect(hit(130)).toBeDefined(); - expect(hit(0)).toBeUndefined(); - colliders.detach(); - }); - - it("adds the world, its ground and the fir colliders near the follow object", async () => { - const { ctx } = await context(); - const coastal = await addCoastal(ctx, new Object3D()); - expect(requests).toContain("terrain/coastal/world.json"); - expect(requests).toContain("terrain/coastal/water.json"); - expect(coastal.colliders.active).toBe(1); - expect(coastal.ground.shape.descriptor.kind).toBe("heightfield"); - expect(ctx.scene.children).toHaveLength(3); - }); - - it("loads each world file once and hands the buffers to the streamer and the ground", async () => { - const { ctx } = await context(); - const coastal = await addCoastal(ctx, new Object3D()); - for (const name of ["world.json", "placements.bin", "heightmap.u16"]) - expect( - requests.filter((path) => path.endsWith(name)), - name, - ).toHaveLength(1); - const data = loads.world.mock.calls[0]?.[0].data; - expect(data.manifest).toEqual(manifest); - expect(new Float32Array(data.placements)).toEqual(records); - expect(data.heightmap).toEqual(heightmap); - expect(coastal.ground.shape.descriptor.heights).toHaveLength(9); - expect(coastal.ground.shape.descriptor.heights?.[4]).toBeCloseTo((8000 / 65535) * 10); - // The scene picks its spawn and cameras from these, so they must read the same terrain and placements. - expect(coastal.field.heightAt(0, 0)).toBeCloseTo((8000 / 65535) * 10); - expect(coastal.placements).toHaveLength(records.length); - expect(coastal.extent).toEqual(manifest.extent); - }); - - it("does not attach a world after cancellation during its async load", async () => { - const { ctx } = await context(); - let current = true; - const load = loads.world.getMockImplementation(); - loads.world.mockImplementation(async (options: IWorldCellsLoadOptions) => { - const world = await load?.(options); - current = false; - return world; - }); - await expect(addCoastal(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(ctx.scene.children).toHaveLength(0); - expect(worlds[0]?.dispose).toHaveBeenCalledOnce(); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(sky.dispose).toHaveBeenCalledOnce(); - }); - - it("stops after a stale surface load before allocating the lighting or world", async () => { - const { ctx } = await context(); - let current = true; - loads.surface.mockImplementation(async () => { - current = false; - return surface; - }); - await expect(addCoastal(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(loads.sky).not.toHaveBeenCalled(); - expect(loads.world).not.toHaveBeenCalled(); - expect(surface.dispose).toHaveBeenCalledOnce(); - }); - - it.each(["placements", "heightmap"])("stops after cancelled %s decoding", async (part) => { - const { ctx } = await context(); - let current = true; - const fetchOriginal = globalThis.fetch; - vi.stubGlobal("fetch", async (input: string) => { - const response = await fetchOriginal(input); - if (input.includes(part)) { - const decode = response.arrayBuffer.bind(response); - response.arrayBuffer = async () => { - const buffer = await decode(); - current = false; - return buffer; - }; - } - return response; - }); - await expect(addCoastal(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(loads.surface).not.toHaveBeenCalled(); - expect(loads.world).not.toHaveBeenCalled(); - expect(ctx.scene.children).toHaveLength(0); - }); - - it.each(["sky", "model"])( - "releases lighting after cancellation during %s loading", - async (part) => { - const { ctx } = await context(); - let current = true; - if (part === "sky") - loads.sky.mockImplementation(async () => { - current = false; - return sky; - }); - else - vi.spyOn(ctx.assets, "model").mockImplementation(async () => { - current = false; - return { scene: new Group() }; - }); - await expect(addCoastal(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject( - { - name: "AbortError", - }, - ); - expect(loads.world).not.toHaveBeenCalled(); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(sky.dispose).toHaveBeenCalledOnce(); - expect(ctx.scene.children).toHaveLength(0); - }, - ); - - it("releases acquired lighting when a model fails before world creation", async () => { - const { ctx } = await context(); - ctx.assets.model = async () => { - throw new Error("missing fir.glb"); - }; - await expect(addCoastal(ctx, new Object3D())).rejects.toThrow("missing fir.glb"); - expect(sky.dispose).toHaveBeenCalledOnce(); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(ctx.scene.children).toHaveLength(0); - }); - - it("releases the ground, props and owned lighting at the scene lifetime boundary", async () => { - const { ctx, plugin } = await context(); - const follow = new Object3D(); - const coastal = await addCoastal(ctx, follow); - expect(coastal.colliders.active).toBe(1); - for (const dispose of lifetimes.splice(0)) dispose(); - plugin.update?.(ctx, 1 / 60); - expect(coastal.colliders.released).toBe(true); - expect(coastal.colliders.active).toBe(0); - follow.position.x = 130; - coastal.colliders.process(); - expect(coastal.colliders.active).toBe(0); - expect(ctx.scene.children).toHaveLength(0); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(sky.dispose).toHaveBeenCalledOnce(); - expect( - ctx.physics.directSpaceState.intersectRay({ - from: { x: 0, y: 50, z: 0 }, - to: { x: 0, y: -50, z: 0 }, - }), - ).toBeUndefined(); - }); - - it("restores shared model lighting without disposing cached model materials", async () => { - const { ctx, model } = await context(); - const priorSky = new Texture(); - const material = new MeshStandardMaterial({ envMap: priorSky, envMapIntensity: 0.25 }); - const mesh = new Mesh(undefined, material); - model.add(mesh); - vi.spyOn(material, "dispose"); - await addCoastal(ctx, new Object3D()); - expect(material.envMap).toBe(sky); - for (const dispose of lifetimes.splice(0)) dispose(); - expect(material.envMap).toBe(priorSky); - expect(material.envMapIntensity).toBe(0.25); - expect(material.dispose).not.toHaveBeenCalled(); - mesh.geometry.dispose(); - priorSky.dispose(); - }); - - it("measures its four metre refresh threshold in the same world coordinates", async () => { - const { ctx } = await context(); - const parent = new Object3D(); - parent.position.x = 100; - const follow = new Object3D(); - parent.add(follow); - const poses = records.slice(); - poses[0] = 100; - poses[8] = 161; - const colliders = new PropColliders(ctx, follow, manifest, poses); - expect(colliders.active).toBe(1); - parent.position.x = 102; - colliders.process(); - expect(colliders.active).toBe(1); - parent.position.x = 105; - colliders.process(); - expect(colliders.active).toBe(2); - colliders.detach(); - }); - - it("releases already created prop bodies if collider construction fails", async () => { - const { ctx, plugin } = await context(); - const create = ctx.physics.simulation.createBody.bind(ctx.physics.simulation); - vi.spyOn(ctx.physics.simulation, "createBody") - .mockImplementationOnce(create) - .mockImplementationOnce(() => { - throw new Error("collider allocation failed"); - }); - const poses = records.slice(); - poses[8] = 20; - expect(() => new PropColliders(ctx, new Object3D(), manifest, poses)).toThrow( - "collider allocation failed", - ); - plugin.update?.(ctx, 1 / 60); - expect( - ctx.physics.directSpaceState.intersectRay({ - from: { x: 0, y: 0.5, z: -2 }, - to: { x: 0, y: 0.5, z: 2 }, - }), - ).toBeUndefined(); - }); -}); diff --git a/packages/terrain/__tests__/desert-kit.spec.ts b/packages/terrain/__tests__/desert-kit.spec.ts deleted file mode 100644 index 49af0ef898..0000000000 --- a/packages/terrain/__tests__/desert-kit.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { copyFileSync, mkdirSync, readFileSync, symlinkSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { type IWorldPackage, validateWorldPackage } from "@threenative/core/world"; -import { describe, expect, it } from "vitest"; -import { makeTempDirSync } from "../../../test-support/temp-dir.js"; - -const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); -const kit = join(packageRoot, "starter", "desert"); -const starterAssets = join(packageRoot, "starter-assets"); - -/** Copies the desert kit into a temp directory whose node_modules resolves the workspace package. */ -function kitInTempDir(): string { - const dir = makeTempDirSync("terrain-desert-kit-"); - for (const name of ["bake.mjs", "recipe.json", "assets.json", "surface.json"]) - copyFileSync(join(kit, name), join(dir, name)); - mkdirSync(join(dir, "node_modules", "@threenative"), { recursive: true }); - symlinkSync(packageRoot, join(dir, "node_modules", "@threenative", "terrain"), "dir"); - return dir; -} - -function bake(out: string): void { - execFileSync(process.execPath, ["bake.mjs", "--assets", starterAssets, "--out", out], { - cwd: kitInTempDir(), - stdio: "pipe", - }); -} - -describe("desert starter kit", () => { - it("bakes a world package the engine's validator accepts, with its placeholder scatter", () => { - const out = join(makeTempDirSync("terrain-desert-world-"), "world"); - bake(out); - const manifest = JSON.parse(readFileSync(join(out, "world.json"), "utf8")) as IWorldPackage; - const result = validateWorldPackage(manifest, { - heightmapByteLength: readFileSync(join(out, manifest.terrain.heightmap)).byteLength, - placementsByteLength: readFileSync(join(out, manifest.placements)).byteLength, - }); - expect(result.errors).toEqual([]); - expect(manifest.terrain.columns).toBe(257); - - const perAsset: Record = {}; - for (const cell of manifest.cells) - for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; - expect(Object.keys(perAsset).sort()).toEqual(["boulder"]); - expect(perAsset.boulder).toBe(140); - - const water = JSON.parse(readFileSync(join(out, "water.json"), "utf8")) as { - lakes: unknown[]; - rivers: unknown[]; - }; - expect(water.lakes).toEqual([]); - expect(water.rivers).toEqual([]); - }); - - it("bakes the same bytes from the same recipe", () => { - const first = join(makeTempDirSync("terrain-desert-a-"), "world"); - const second = join(makeTempDirSync("terrain-desert-b-"), "world"); - bake(first); - bake(second); - for (const name of [ - "world.json", - "heightmap.u16", - "placements.bin", - "splat.rgba", - "water.json", - ]) { - expect(readFileSync(join(first, name)).equals(readFileSync(join(second, name))), name).toBe( - true, - ); - } - }); -}); diff --git a/packages/terrain/__tests__/desert-runtime.spec.ts b/packages/terrain/__tests__/desert-runtime.spec.ts deleted file mode 100644 index b6790b7751..0000000000 --- a/packages/terrain/__tests__/desert-runtime.spec.ts +++ /dev/null @@ -1,335 +0,0 @@ -import type { ICtx } from "@threenative/core"; -import type { IWorldCellsLoadOptions, IWorldPackage } from "@threenative/core/world"; -import { type IPhysicsContext, rapier } from "@threenative/physics"; -import { Group, Mesh, MeshBasicMaterial, MeshStandardMaterial, Object3D, Texture } from "three"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { PropColliders, addDesert } from "../starter/desert/world.js"; - -const loads = vi.hoisted(() => ({ surface: vi.fn(), sky: vi.fn(), world: vi.fn() })); -vi.mock("@threenative/core/world", async (original) => { - const actual = await original(); - return { ...actual, loadTerrainSplat: loads.surface, WorldCells: { load: loads.world } }; -}); -vi.mock("three/addons/loaders/HDRLoader.js", () => ({ - HDRLoader: class { - loadAsync = loads.sky; - }, -})); - -type Ctx = ICtx, IPhysicsContext>; -const plugins: ReturnType[] = []; -const lifetimes: Array<() => void> = []; -const worlds: Array = []; -const manifest: IWorldPackage = { - version: 1, - cellSize: 64, - extent: { minX: -128, minZ: -128, sizeX: 256, sizeZ: 256 }, - assets: { boulder: { glb: "boulder.glb", bounds: { min: [-1, 0, -1], max: [1, 1, 1] } } }, - cells: [{ x: 0, z: 0, runs: [{ asset: "boulder", offset: 0, count: 2 }] }], - placements: "placements.bin", - terrain: { - heightmap: "heightmap.u16", - columns: 3, - rows: 3, - spacing: 128, - heightMin: 0, - heightMax: 10, - }, -}; -// Two boulders, eight floats each: x, y, z, three unused, a scale in the last slot. One sits at the -// origin and one 130 m east. -const records = new Float32Array([0, 0, 0, 0, 0, 0, 1, 1, 130, 0, 0, 0, 0, 0, 1, 1]); -const heightmap = new Uint16Array([0, 2000, 4000, 6000, 8000, 10000, 12000, 14000, 16000]); -let requests: string[]; -let surface: MeshBasicMaterial; -let sky: Texture; - -async function context() { - const scene = new Group(); - const model = new Group(); - const ctx = { - scene, - assets: { resolve: async (path: string) => [path], model: async () => ({ scene: model }) }, - add: (object: T): T => { - scene.add(object); - return object; - }, - entities: { - add: (_id: string, entity: { dispose?: () => void }) => { - if (entity.dispose) lifetimes.push(entity.dispose); - return entity; - }, - }, - } as unknown as Ctx; - const plugin = rapier(); - await plugin.setup?.(ctx); - plugins.push(plugin); - return { ctx, plugin, model }; -} - -beforeEach(() => { - requests = []; - surface = new MeshBasicMaterial(); - sky = new Texture(); - vi.spyOn(surface, "dispose"); - vi.spyOn(sky, "dispose"); - loads.surface.mockReset().mockResolvedValue(surface); - loads.sky.mockReset().mockResolvedValue(sky); - loads.world.mockReset().mockImplementation(async (options: IWorldCellsLoadOptions) => { - if (!options.data) { - // The real WorldCells API reads these itself unless the consumer supplies decoded data. - await (await fetch(options.url)).json(); - await (await fetch("terrain/desert/placements.bin")).arrayBuffer(); - await (await fetch("terrain/desert/heightmap.u16")).arrayBuffer(); - } - const world = Object.assign(new Group(), { - dispose: vi.fn(function (this: Group) { - this.removeFromParent(); - }), - }); - worlds.push(world); - return world; - }); - vi.stubGlobal("fetch", async (input: string) => { - requests.push(input); - if (input.endsWith("world.json")) return new Response(JSON.stringify(manifest)); - if (input.endsWith("placements.bin")) return new Response(records.slice().buffer); - if (input.endsWith("heightmap.u16")) return new Response(heightmap.slice().buffer); - throw new Error(`Unexpected desert request: ${input}`); - }); -}); - -afterEach(() => { - for (const dispose of lifetimes.splice(0)) dispose(); - for (const world of worlds.splice(0)) world.dispose(); - for (const plugin of plugins.splice(0)) plugin.dispose?.({} as Ctx); - vi.unstubAllGlobals(); - vi.restoreAllMocks(); -}); - -describe("desert runtime", () => { - it("streams boulder colliders when only the follow object's parent moves", async () => { - const { ctx, plugin } = await context(); - const parent = new Object3D(); - const follow = new Object3D(); - parent.add(follow); - const colliders = new PropColliders(ctx, follow, manifest, records); - parent.position.x = 130; - colliders.process(); - plugin.update?.(ctx, 1 / 60); - // A boulder's sphere sits on its origin, so a ray half a metre up crosses it. - const hit = (x: number) => - ctx.physics.directSpaceState.intersectRay({ - from: { x, y: 0.5, z: -2 }, - to: { x, y: 0.5, z: 2 }, - }); - expect(hit(130)).toBeDefined(); - expect(hit(0)).toBeUndefined(); - colliders.detach(); - }); - - it("adds the world, its ground and the boulder colliders near the follow object", async () => { - const { ctx } = await context(); - const desert = await addDesert(ctx, new Object3D()); - expect(requests).toContain("terrain/desert/world.json"); - expect(requests).not.toContain("terrain/desert/water.json"); - expect(desert.colliders.active).toBe(1); - expect(desert.ground.shape.descriptor.kind).toBe("heightfield"); - expect(ctx.scene.children).toHaveLength(3); - }); - - it("loads each world file once and hands the buffers to the streamer and the ground", async () => { - const { ctx } = await context(); - const desert = await addDesert(ctx, new Object3D()); - for (const name of ["world.json", "placements.bin", "heightmap.u16"]) - expect( - requests.filter((path) => path.endsWith(name)), - name, - ).toHaveLength(1); - const data = loads.world.mock.calls[0]?.[0].data; - expect(data.manifest).toEqual(manifest); - expect(new Float32Array(data.placements)).toEqual(records); - expect(data.heightmap).toEqual(heightmap); - expect(desert.ground.shape.descriptor.heights).toHaveLength(9); - expect(desert.ground.shape.descriptor.heights?.[4]).toBeCloseTo((8000 / 65535) * 10); - // The scene picks its spawn and cameras from these, so they must read the same terrain and placements. - expect(desert.field.heightAt(0, 0)).toBeCloseTo((8000 / 65535) * 10); - expect(desert.placements).toHaveLength(records.length); - expect(desert.extent).toEqual(manifest.extent); - }); - - it("does not attach a world after cancellation during its async load", async () => { - const { ctx } = await context(); - let current = true; - const load = loads.world.getMockImplementation(); - loads.world.mockImplementation(async (options: IWorldCellsLoadOptions) => { - const world = await load?.(options); - current = false; - return world; - }); - await expect(addDesert(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(ctx.scene.children).toHaveLength(0); - expect(worlds[0]?.dispose).toHaveBeenCalledOnce(); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(sky.dispose).toHaveBeenCalledOnce(); - }); - - it("stops after a stale surface load before allocating the lighting or world", async () => { - const { ctx } = await context(); - let current = true; - loads.surface.mockImplementation(async () => { - current = false; - return surface; - }); - await expect(addDesert(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(loads.sky).not.toHaveBeenCalled(); - expect(loads.world).not.toHaveBeenCalled(); - expect(surface.dispose).toHaveBeenCalledOnce(); - }); - - it.each(["placements", "heightmap"])("stops after cancelled %s decoding", async (part) => { - const { ctx } = await context(); - let current = true; - const fetchOriginal = globalThis.fetch; - vi.stubGlobal("fetch", async (input: string) => { - const response = await fetchOriginal(input); - if (input.includes(part)) { - const decode = response.arrayBuffer.bind(response); - response.arrayBuffer = async () => { - const buffer = await decode(); - current = false; - return buffer; - }; - } - return response; - }); - await expect(addDesert(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(loads.surface).not.toHaveBeenCalled(); - expect(loads.world).not.toHaveBeenCalled(); - expect(ctx.scene.children).toHaveLength(0); - }); - - it.each(["sky", "model"])( - "releases lighting after cancellation during %s loading", - async (part) => { - const { ctx } = await context(); - let current = true; - if (part === "sky") - loads.sky.mockImplementation(async () => { - current = false; - return sky; - }); - else - vi.spyOn(ctx.assets, "model").mockImplementation(async () => { - current = false; - return { scene: new Group() }; - }); - await expect(addDesert(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(loads.world).not.toHaveBeenCalled(); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(sky.dispose).toHaveBeenCalledOnce(); - expect(ctx.scene.children).toHaveLength(0); - }, - ); - - it("releases acquired lighting when a model fails before world creation", async () => { - const { ctx } = await context(); - ctx.assets.model = async () => { - throw new Error("missing boulder.glb"); - }; - await expect(addDesert(ctx, new Object3D())).rejects.toThrow("missing boulder.glb"); - expect(sky.dispose).toHaveBeenCalledOnce(); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(ctx.scene.children).toHaveLength(0); - }); - - it("releases the ground, props and owned lighting at the scene lifetime boundary", async () => { - const { ctx, plugin } = await context(); - const follow = new Object3D(); - const desert = await addDesert(ctx, follow); - expect(desert.colliders.active).toBe(1); - for (const dispose of lifetimes.splice(0)) dispose(); - plugin.update?.(ctx, 1 / 60); - expect(desert.colliders.released).toBe(true); - expect(desert.colliders.active).toBe(0); - follow.position.x = 130; - desert.colliders.process(); - expect(desert.colliders.active).toBe(0); - expect(ctx.scene.children).toHaveLength(0); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(sky.dispose).toHaveBeenCalledOnce(); - expect( - ctx.physics.directSpaceState.intersectRay({ - from: { x: 0, y: 50, z: 0 }, - to: { x: 0, y: -50, z: 0 }, - }), - ).toBeUndefined(); - }); - - it("restores shared model lighting without disposing cached model materials", async () => { - const { ctx, model } = await context(); - const priorSky = new Texture(); - const material = new MeshStandardMaterial({ envMap: priorSky, envMapIntensity: 0.25 }); - const mesh = new Mesh(undefined, material); - model.add(mesh); - vi.spyOn(material, "dispose"); - await addDesert(ctx, new Object3D()); - expect(material.envMap).toBe(sky); - for (const dispose of lifetimes.splice(0)) dispose(); - expect(material.envMap).toBe(priorSky); - expect(material.envMapIntensity).toBe(0.25); - expect(material.dispose).not.toHaveBeenCalled(); - mesh.geometry.dispose(); - priorSky.dispose(); - }); - - it("measures its four metre refresh threshold in the same world coordinates", async () => { - const { ctx } = await context(); - const parent = new Object3D(); - parent.position.x = 100; - const follow = new Object3D(); - parent.add(follow); - const poses = records.slice(); - poses[0] = 100; - poses[8] = 161; - const colliders = new PropColliders(ctx, follow, manifest, poses); - expect(colliders.active).toBe(1); - parent.position.x = 102; - colliders.process(); - expect(colliders.active).toBe(1); - parent.position.x = 105; - colliders.process(); - expect(colliders.active).toBe(2); - colliders.detach(); - }); - - it("releases already created prop bodies if collider construction fails", async () => { - const { ctx, plugin } = await context(); - const create = ctx.physics.simulation.createBody.bind(ctx.physics.simulation); - vi.spyOn(ctx.physics.simulation, "createBody") - .mockImplementationOnce(create) - .mockImplementationOnce(() => { - throw new Error("collider allocation failed"); - }); - const poses = records.slice(); - poses[8] = 20; - expect(() => new PropColliders(ctx, new Object3D(), manifest, poses)).toThrow( - "collider allocation failed", - ); - plugin.update?.(ctx, 1 / 60); - expect( - ctx.physics.directSpaceState.intersectRay({ - from: { x: 0, y: 0.5, z: -2 }, - to: { x: 0, y: 0.5, z: 2 }, - }), - ).toBeUndefined(); - }); -}); diff --git a/packages/terrain/__tests__/editor-document.spec.ts b/packages/terrain/__tests__/editor-document.spec.ts index 2b84cd8606..6e31343e08 100644 --- a/packages/terrain/__tests__/editor-document.spec.ts +++ b/packages/terrain/__tests__/editor-document.spec.ts @@ -2,6 +2,7 @@ import { readFileSync, writeFileSync } from "node:fs"; import { createServer as createHttpServer, request } from "node:http"; import { join } from "node:path"; import { Terrain } from "@threenative/terrain"; +import { TerrainEditorController } from "@threenative/terrain/editor"; import { TerrainEditorDocument, terrainEditor } from "@threenative/terrain/editor/server"; import { type ViteDevServer, createServer } from "vite"; import { afterEach, describe, expect, it } from "vitest"; @@ -68,7 +69,15 @@ function status( return new Promise((resolve, reject) => { const req = request(url, { method, headers }, (response) => { response.resume(); - response.on("end", () => resolve(response.statusCode ?? 0)); + response.on("error", reject); + response.on("end", () => { + const code = response.statusCode ?? 0; + if (req.closed) resolve(code); + else { + req.once("close", () => resolve(code)); + req.destroy(); + } + }); }); req.on("error", reject); req.end(data); @@ -189,6 +198,54 @@ describe("project terrain editor document", () => { expect(readFileSync(path, "utf8")).toBe(disk); expect(() => new TerrainEditorDocument("relative.json")).toThrow("absolute"); }); + it.each(["cameras", "environment", "inspect", "reload", "restore"])( + "rejects a 10 MiB %s body before parsing or changing the document", + async (route) => { + const { base, path } = await api(); + const disk = readFileSync(path, "utf8"); + const response = await fetch(`${base}${route}`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: "{".repeat(10 * 1024 * 1024), + }); + expect(response.status).toBe(413); + expect(await response.json()).toEqual({ code: 413, error: "Request exceeds 1 MiB" }); + expect(readFileSync(path, "utf8")).toBe(disk); + }, + ); + it("bounds chunked small-route bodies without relying on content-length", async () => { + const { base } = await api(); + expect( + await status( + `${base}inspect`, + { "content-type": "application/json", "transfer-encoding": "chunked" }, + "POST", + "{".repeat(10 * 1024 * 1024), + ), + ).toBe(413); + }); + it("returns only a status code and compatible error message for rejected edits", async () => { + const { post, get } = await api(); + const before = await get(); + const response = await post({ baseRevision: "stale", document: before.document }); + expect(response.status).toBe(409); + expect(await response.json()).toEqual({ code: 409, error: "Stale base revision" }); + expect(await get()).toEqual(before); + }); + it("keeps the larger request allowance for documents and asset uploads", async () => { + const { base } = await api(); + for (const route of ["document", "assets"]) { + const response = await fetch(`${base}${route}`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: `${" ".repeat(10 * 1024 * 1024)}{broken`, + }); + expect(response.status).toBe(400); + const result = await response.json(); + expect(Object.keys(result).sort()).toEqual(["code", "error"]); + expect(result.error).not.toContain("Request exceeds"); + } + }); it("streams accepted revisions and observes complete external file saves", async () => { const { path, base, get, post } = await api(); const before = await get(); @@ -218,6 +275,65 @@ describe("project terrain editor document", () => { cancellation.abort(); await reader?.cancel().catch(() => {}); }); + + it("starts the dev server on a malformed world.json, reports diagnostic and recovers via reload after file is fixed", async () => { + const { root, path } = fixture(); + writeFileSync(path, "{broken json"); + const editor = terrainEditor({ documentPath: path }); + const server = await createServer({ + root, + configFile: false, + logLevel: "silent", + server: { host: "127.0.0.1", port: 0 }, + plugins: [editor], + }); + servers.push(server); + await server.listen(); + const address = server.httpServer?.address(); + const controller = new TerrainEditorController( + `http://127.0.0.1:${(address as { port: number }).port}/terrain-editor/`, + ); + + // Dev server started on malformed file in read-only diagnostic state + const initialSnapshot = await controller.snapshot(); + expect(initialSnapshot.diagnostic).toBeTruthy(); + + // Commit is rejected with 409 while document on disk is invalid + await expect( + controller.commit({ + baseRevision: initialSnapshot.revision, + commands: [{ op: "remove", id: "hills" }], + }), + ).rejects.toThrow("Disk document is invalid"); + + // Fix file on disk + const valid = { + version: 1, + recipe: new Terrain({ size: 512, resolution: 257, seed: 42 }).toJSON(), + }; + writeFileSync(path, JSON.stringify(valid, null, 2)); + + // Reload from disk recovers + const reloaded = await controller.reload(); + expect(reloaded.diagnostic).toBeNull(); + expect(reloaded.document.recipe.config.seed).toBe(42); + + // Overwrite with last valid state restores disk if corrupted again + writeFileSync(path, "corrupted again"); + const restored = await controller.restoreLastValid(); + expect(restored.diagnostic).toBeNull(); + expect(JSON.parse(readFileSync(path, "utf8")).recipe.config.seed).toBe(42); + }); + + it("rejects restore when the document never held a valid state and keeps the original", () => { + const { path } = fixture(); + writeFileSync(path, "{broken since the very first read"); + const store = new TerrainEditorDocument(path); + expect(store.snapshot().diagnostic).toBeTruthy(); + expect(() => store.restoreLastValid()).toThrow("No valid save to restore"); + expect(readFileSync(path, "utf8")).toBe("{broken since the very first read"); + }); + it("uses an explicitly configured private forward and rejects a failed forward", async () => { let target: string | undefined; const proxy = createHttpServer((incoming, response) => { @@ -290,4 +406,26 @@ describe("project terrain editor document", () => { await server.listen(); await expect(editor.activate()).rejects.toThrow("not ready"); }); + it("rejects stale and malformed export requests before resolving the project cook", async () => { + const { base, get } = await api(); + const snapshot = await get(); + const send = (value: unknown) => + fetch(`${base}export`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(value), + }); + const stale = await send({ baseRevision: "stale", preparedWorld: {} }); + expect(stale.status).toBe(409); + expect(await stale.json()).toEqual({ code: 409, error: "Stale export revision" }); + const malformed = await send({ baseRevision: snapshot.revision, preparedWorld: [] }); + expect(malformed.status).toBe(400); + expect(await malformed.json()).toEqual({ + code: 400, + error: "Expected preparedWorld glTF JSON", + }); + const missing = await send({ baseRevision: snapshot.revision, preparedWorld: {} }); + expect(missing.status).toBe(400); + expect((await missing.json()).error).toContain("@threenative/assets"); + }); }); diff --git a/packages/terrain/__tests__/editor-keyboard.spec.ts b/packages/terrain/__tests__/editor-keyboard.spec.ts new file mode 100644 index 0000000000..c620bee0e4 --- /dev/null +++ b/packages/terrain/__tests__/editor-keyboard.spec.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; +import { AnnouncementQueue, editorKeyAction } from "../src/editor/app/keyboard.js"; + +describe("editor keyboard and announcements", () => { + it("maps all ten brush shortcuts and selection", () => { + for (let i = 0; i < 10; i++) + expect(editorKeyAction({ key: String((i + 1) % 10) })).toBe(`tool:${i}`); + expect(editorKeyAction({ key: "Q" })).toBe("select"); + expect(editorKeyAction({ key: "v" })).toBe("navigate"); + expect(editorKeyAction({ key: "f" })).toBe("frame"); + }); + it("leaves native controls and modified shortcuts alone", () => { + for (const tag of ["INPUT", "TEXTAREA", "SELECT", "BUTTON", "SUMMARY"]) + for (const key of ["1", "Enter", " ", "ArrowRight"]) + expect(editorKeyAction({ key }, tag)).toBeUndefined(); + expect(editorKeyAction({ key: "1", ctrlKey: true })).toBeUndefined(); + expect(editorKeyAction({ key: "z", metaKey: true })).toBe("undo"); + expect(editorKeyAction({ key: "z", ctrlKey: true, shiftKey: true })).toBe("redo"); + expect(editorKeyAction({ key: "y", ctrlKey: true })).toBe("redo"); + }); + it("maps viewport arrows and activation without stealing button Enter", () => { + for (const key of ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"]) + expect(editorKeyAction({ key }, "DIV", true)).toBe(key.toLowerCase()); + for (const key of ["Enter", " "]) expect(editorKeyAction({ key }, "DIV", true)).toBe("apply"); + expect(editorKeyAction({ key: "Enter" }, "BUTTON")).toBeUndefined(); + expect(editorKeyAction({ key: "Enter" })).toBe("finish"); + // Clicks then Enter finishes a spline in the viewport; Space keeps adding points. + expect(editorKeyAction({ key: "Enter" }, "DIV", true, true)).toBe("finish"); + expect(editorKeyAction({ key: " " }, "DIV", true, true)).toBe("apply"); + }); + it("cancels even with a text field focused", () => { + expect(editorKeyAction({ key: "Escape" }, "INPUT")).toBe("cancel"); + }); + it("queues simultaneous and repeated messages without losing blocking severity", () => { + const queue = new AnnouncementQueue(); + queue.push(" "); + queue.push("Invalid recipe"); + queue.push("Save failed", true); + queue.push("Save failed", true); + expect(queue.shift()).toEqual({ message: "Invalid recipe", blocking: false }); + expect(queue.shift()).toEqual({ message: "Save failed", blocking: true }); + expect(queue.shift()).toEqual({ message: "Save failed", blocking: true }); + expect(queue.shift()).toBeUndefined(); + }); +}); diff --git a/packages/terrain/__tests__/editor-mechanics.spec.ts b/packages/terrain/__tests__/editor-mechanics.spec.ts new file mode 100644 index 0000000000..db45715ee4 --- /dev/null +++ b/packages/terrain/__tests__/editor-mechanics.spec.ts @@ -0,0 +1,435 @@ +import { + BoxGeometry, + InstancedMesh, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + PerspectiveCamera, + Quaternion, + Raycaster, + Scene, + Vector3, +} from "three"; +import { TransformControls } from "three/addons/controls/TransformControls.js"; +import { describe, expect, it, vi } from "vitest"; +import type { IPlacementOverride } from "../src/core/types.js"; +import type { Layer } from "../src/core/types.js"; +import { landformPose, transformedLandform } from "../src/editor/landforms.js"; +import { + editorPlacementCenter, + editorPointToClient, + editorSurfaceHeight, + pickEditorPlacement, + pickEditorSurface, +} from "../src/editor/picking.js"; +import { createEditorSelection } from "../src/editor/selection.js"; +import type { IEditorSnapshot } from "../src/editor/server.js"; + +describe("editor heightfield picking", () => { + it("subtracts the viewport offset and keeps the caller's target order", () => { + const raycast = vi.fn[0]["raycast"]>(); + const renderer = { + domElement: { getBoundingClientRect: () => ({ left: 90, top: 40 }) }, + }; + const targets = [new Mesh(), new Mesh()]; + expect(pickEditorSurface({ renderer, raycast }, targets, 120, 60)).toBeUndefined(); + const query = raycast.mock.calls[0]?.[0]; + expect(query?.screen?.toArray()).toEqual([30, 20]); + expect(query?.targets).toBe(targets); + }); + + it("uses the rendered surface and returns undefined outside it", () => { + const mesh = new Mesh(new BoxGeometry(10, 4, 10)); + const ray = new Raycaster(); + const raycast: Parameters[0]["raycast"] = (query) => { + if (!query?.origin || !query.direction) throw new Error("Missing downward ray"); + ray.set(query.origin, query.direction); + return ray.intersectObject(mesh)[0]; + }; + expect(editorSurfaceHeight({ raycast }, mesh, 0, 0)).toBe(2); + expect(editorSurfaceHeight({ raycast }, mesh, 20, 0)).toBeUndefined(); + expect(editorSurfaceHeight({ raycast }, mesh, 0, 0, 10000)).toBe(2); + mesh.geometry.dispose(); + }); +}); + +describe("landform gizmo math", () => { + const layer: Layer = { + id: "stamp-1", + type: "stamp", + params: { at: [10, 20], radius: [8, 9], offset: 3, scale: 2, rotation: 90 }, + }; + + it("round-trips the terrain rotation convention and removes the surface anchor", () => { + const pose = landformPose(layer, 100, 120); + expect(pose).toBeDefined(); + if (!pose) throw new Error("Missing landform pose"); + const next = transformedLandform( + layer, + new Vector3(...pose.position).add(new Vector3(0, 7, 0)), + pose.quaternion, + new Vector3(...pose.scale), + 7, + ); + expect(next.params).toMatchObject({ at: [10, 20], radius: [8, 9], offset: 3, scale: 2 }); + expect((next.params as typeof layer.params & { rotation: number }).rotation).toBeCloseTo(90); + }); + + it("writes heightmap half extents as full size and keeps unrelated parameters", () => { + const heightmap: Layer = { + id: "map", + type: "heightmap", + params: { blend: "replace", data: { width: 2, height: 2, values: [0, 0, 0, 0] } }, + }; + expect(landformPose(heightmap, 100, 120)?.scale).toEqual([50, 1, 50]); + const next = transformedLandform( + heightmap, + new Vector3(5, 8, 6), + new Quaternion(), + new Vector3(4, 2, 3), + 5, + ); + expect(next.params).toMatchObject({ size: [8, 6], at: [5, 6], offset: 3, blend: "replace" }); + }); + + it("rejects overhangs, nonfinite coordinates and nonpositive scales", () => { + const transform = (position: Vector3, rotation: Quaternion, scale: Vector3) => + transformedLandform(layer, position, rotation, scale, 0); + expect(() => + transform(new Vector3(), new Quaternion(0.1, 0, 0, 1), new Vector3(1, 1, 1)), + ).toThrow("overhanging"); + expect(() => + transform(new Vector3(Number.NaN, 0, 0), new Quaternion(), new Vector3(1, 1, 1)), + ).toThrow("finite"); + expect(() => transform(new Vector3(), new Quaternion(), new Vector3(1, 0, 1))).toThrow( + "positive", + ); + }); +}); + +// At z=10 with a 90-degree camera, a +0.2 NDC X ray crosses z=0 at x=2. +describe("injected editor selection mechanics", () => { + function setup() { + const camera = new PerspectiveCamera(90, 1, 0.1, 100); + camera.position.z = 10; + camera.updateMatrixWorld(true); + const scene = new Scene(); + const proxy = new Object3D(); + scene.add(proxy); + const gizmo = new TransformControls(camera); + scene.add(gizmo.getHelper()); + const snapshot: IEditorSnapshot = { + revision: "base", + diagnostic: null, + document: { + version: 1, + recipe: { version: 1, config: { size: 100, resolution: 3, seed: 1 }, layers: [] }, + }, + }; + const commit = vi.fn[0]["controller"]["commit"]>( + async () => ({ ...snapshot, revision: "next" }), + ); + const previewPlacement = vi.fn(); + const setNavigation = vi.fn(); + const message = vi.fn(); + const pose = { + position: [0, 0, 0], + quaternion: [0, 0, 0, 1], + scale: [1, 1, 1], + grounding: true, + } satisfies IPlacementOverride; + const selection = createEditorSelection({ + gizmo, + proxy, + controller: { commit, snapshot: async () => snapshot }, + grounding: () => true, + surface: () => 7, + defaultFootprintSize: 120, + refreshPose: () => { + const layer = selection.snapshot?.document.recipe.layers.find( + (l) => l.id === selection.selectedLayer, + ); + selection.applyPose(layer, pose); + }, + drawFootprint: vi.fn(), + restorePlacement: vi.fn(), + previewPlacement, + setSaving: vi.fn(), + selectedChanged: vi.fn(), + setNavigation, + message, + }); + selection.sync(snapshot, false); + selection.setActive(true); + selection.select("prop"); + function drag(x: number) { + gizmo.axis = "X"; + scene.updateMatrixWorld(true); + gizmo.pointerDown({ x: 0, y: 0, button: 0 } as PointerEvent); + gizmo.pointerMove({ x, y: 0, button: -1 } as PointerEvent); + } + return { + selection, + snapshot, + gizmo, + proxy, + commit, + previewPlacement, + setNavigation, + message, + drag, + }; + } + + it("blocks alternate pointers and detaches when disabled", () => { + const f = setup(); + expect(f.selection.canPick({ button: 0, altKey: false })).toBe(true); + expect(f.selection.canPick({ button: 2, altKey: false })).toBe(false); + expect(f.selection.canPick({ button: 0, altKey: true })).toBe(false); + f.selection.setActive(false); + expect(f.selection.active).toBe(false); + expect(f.gizmo.object).toBeUndefined(); + expect(f.selection.canPick({ button: 0, altKey: false })).toBe(false); + f.selection.dispose(); + }); + + it("reassigns orphan overrides atomically and refuses to overwrite a destination", async () => { + const f = setup(); + const value: IPlacementOverride = { + position: [1, 2, 3], + quaternion: [0, 0, 0, 1], + scale: [1, 1, 1], + grounding: false, + }; + f.snapshot.document.placementOverrides = { orphan: value }; + await f.selection.reassign("orphan"); + expect(f.commit).toHaveBeenLastCalledWith({ + baseRevision: "base", + document: { ...f.snapshot.document, placementOverrides: { prop: value } }, + }); + f.snapshot.document.placementOverrides.prop = value; + await f.selection.reassign("orphan"); + expect(f.commit).toHaveBeenCalledTimes(1); + expect(f.message).toHaveBeenLastCalledWith("Choose a placement without an existing override."); + f.selection.dispose(); + }); + + it("retains invalid or failed drafts and clears them explicitly", async () => { + const f = setup(); + f.selection.applyFields([1, 2, 3, 0, 0, 0, 1, 0, 1], true); + expect(f.commit).not.toHaveBeenCalled(); + expect(f.message.mock.calls.at(-1)?.[0]).toContain("scale"); + f.commit.mockRejectedValueOnce(new Error("stale revision")); + f.selection.applyFields([1, 2, 3, 0, 0, 0, 1, 1, 1], true); + await vi.waitFor(() => expect(f.selection.saving).toBe(false)); + expect(f.selection.draft?.transform.position).toEqual([1, 2, 3]); + f.selection.clearDrafts(); + expect(f.selection.draft).toBeUndefined(); + f.selection.dispose(); + }); + + it("undoes a placement only while its committed transform still matches", async () => { + const f = setup(); + const value: IPlacementOverride = { + position: [1, 2, 3], + quaternion: [0, 0, 0, 1], + scale: [1, 1, 1], + grounding: false, + }; + await f.selection.save("prop", value); + f.snapshot.document.placementOverrides = { prop: value }; + await f.selection.undo(); + expect(f.commit).toHaveBeenLastCalledWith({ + baseRevision: "base", + document: { ...f.snapshot.document, placementOverrides: {} }, + }); + await f.selection.save("prop", value); + f.snapshot.document.placementOverrides.prop = { ...value, position: [9, 9, 9] }; + await f.selection.undo(); + expect(f.commit).toHaveBeenCalledTimes(3); + expect(f.message).toHaveBeenLastCalledWith( + "Undo conflict: another actor changed this placement; their edit is retained.", + ); + f.selection.dispose(); + }); + + it("commits landform fields and undoes only footprint parameters", async () => { + const f = setup(); + const layer: Layer = { + id: "hill", + type: "stamp", + params: { at: [10, 20], offset: 3, radius: 8, amplitude: 40 }, + }; + f.snapshot.document.recipe.layers.push(layer); + f.selection.selectLayer("hill"); + expect(f.selection.selectedLayer).toBe("hill"); + expect(f.selection.selected).toBeUndefined(); + f.selection.applyFields([12, 5, 22, 0, 90, 0, 6, 2, 7], true); + await vi.waitFor(() => expect(f.selection.saving).toBe(false)); + const request = f.commit.mock.calls[0]?.[0]; + if (!request || !("commands" in request)) throw new Error("Missing landform request"); + const command = request.commands[0]; + if (!command || command.op !== "upsert") throw new Error("Missing landform upsert"); + expect(command.layer.params).toMatchObject({ + at: [12, 22], + offset: 5, + radius: [6, 7], + scale: 2, + amplitude: 40, + }); + f.snapshot.document.recipe.layers = [ + { ...command.layer, params: { ...command.layer.params, amplitude: 90 } } as Layer, + ]; + await f.selection.undo(); + const undoRequest = f.commit.mock.calls[1]?.[0]; + if (!undoRequest || !("commands" in undoRequest)) throw new Error("Missing undo request"); + const undo = undoRequest.commands[0]; + if (!undo || undo.op !== "upsert") throw new Error("Missing landform undo"); + expect(undo.layer.params).toEqual({ ...layer.params, amplitude: 90 }); + f.selection.dispose(); + }); + + it("restricts landform rotation and removes gesture listeners on disposal", () => { + const f = setup(); + f.snapshot.document.recipe.layers.push({ id: "hill", type: "stamp", params: { radius: 8 } }); + f.selection.selectLayer("hill"); + f.gizmo.setMode("rotate"); + f.selection.applyPose(f.snapshot.document.recipe.layers[0]); + expect(f.gizmo.showX).toBe(false); + expect(f.gizmo.showZ).toBe(false); + f.gizmo.axis = "X"; + f.gizmo.dispatchEvent({ type: "mouseDown", mode: "rotate" }); + expect(f.selection.drag).toBeUndefined(); + expect(f.message).toHaveBeenLastCalledWith( + "Only Y rotation is supported: a heightfield cannot overhang.", + ); + f.selection.dispose(); + f.setNavigation.mockClear(); + f.gizmo.dispatchEvent({ type: "dragging-changed", value: true }); + expect(f.setNavigation).not.toHaveBeenCalled(); + }); + + it("turns a known axis ray into a two-metre delta and commits once on release", async () => { + const f = setup(); + f.drag(0.2); + expect(f.previewPlacement.mock.calls[0]?.[1].position[0]).toBeCloseTo(2); + expect(f.commit).not.toHaveBeenCalled(); + expect(f.setNavigation).toHaveBeenLastCalledWith(false); + f.gizmo.pointerUp(null); + await Promise.resolve(); + expect(f.commit).toHaveBeenCalledTimes(1); + expect(f.setNavigation).toHaveBeenLastCalledWith(true); + f.selection.dispose(); + }); + + it("preserves injected translation snapping", () => { + const f = setup(); + f.gizmo.setTranslationSnap(1); + f.drag(0.26); + expect(f.previewPlacement.mock.calls[0]?.[1].position).toEqual([3, 0, 0]); + f.selection.cancel(); + expect(f.commit).not.toHaveBeenCalled(); + f.selection.dispose(); + }); + + it("preserves injected axis clamping", () => { + const f = setup(); + f.gizmo.maxX = 1.5; + f.drag(0.2); + expect(f.previewPlacement.mock.calls[0]?.[1].position).toEqual([1.5, 0, 0]); + f.selection.cancel(); + f.selection.dispose(); + }); + + it("retains the selection during a drag and cancels without saving", () => { + const f = setup(); + f.drag(0.2); + f.selection.select("other"); + expect(f.selection.selected).toBe("prop"); + expect(f.selection.canPick({ button: 0, altKey: false })).toBe(false); + f.selection.cancel(); + f.gizmo.pointerUp(null); + expect(f.selection.draft).toBeUndefined(); + expect(f.commit).not.toHaveBeenCalled(); + expect(f.message).toHaveBeenLastCalledWith("Drag cancelled · no transaction"); + f.selection.dispose(); + }); + + it("applies form values with validation and anchors landform poses", async () => { + const f = setup(); + f.selection.applyFields([1, 2, 3, 0, 0, 0, 2, 3, 4], false); + expect(f.previewPlacement).toHaveBeenCalledWith("prop", { + position: [1, 2, 3], + quaternion: [0, 0, 0, 1], + scale: [2, 3, 4], + grounding: false, + }); + await Promise.resolve(); + const layer: Layer = { + id: "hill", + type: "stamp", + params: { at: [10, 20], offset: 3, radius: 8 }, + }; + expect(f.selection.applyPose(layer)).toEqual([10, 3, 20, 0, 0, 0, 8, 1, 8]); + expect(f.selection.anchor).toBe(4); + expect(f.proxy.position.toArray()).toEqual([10, 7, 20]); + f.selection.dispose(); + }); +}); + +describe("placement picking and projection", () => { + it("resolves instance world matrices and respects terrain occlusion", () => { + const terrain = new Mesh(); + const mesh = new InstancedMesh(new BoxGeometry(), new MeshBasicMaterial(), 1); + mesh.setMatrixAt(0, new Matrix4().makeTranslation(2, 0, 0)); + mesh.position.set(10, 3, 4); + mesh.updateMatrixWorld(true); + const placements = [ + { pose: new Matrix4().makeTranslation(12, 3, 4), placement: { id: "tree" } }, + ]; + const hit = { object: mesh, instanceId: 0, point: new Vector3(12, 3, 4), distance: 1 }; + const raycast = vi.fn[0]["raycast"]>(() => hit); + const ctx = { + renderer: { domElement: { getBoundingClientRect: () => ({ left: 10, top: 20 }) } }, + raycast, + }; + expect(pickEditorPlacement(ctx, terrain, [mesh], placements, 30, 50)).toBe("tree"); + expect(raycast.mock.calls[0]?.[0].targets).toEqual([terrain, mesh]); + expect(pickEditorPlacement(ctx, terrain, [mesh], [], 30, 50)).toBeUndefined(); + const occluded = { + ...ctx, + raycast: () => ({ object: terrain, point: new Vector3(), distance: 0.5 }), + }; + expect(pickEditorPlacement(occluded, terrain, [mesh], placements, 30, 50)).toBeUndefined(); + mesh.geometry.dispose(); + mesh.material.dispose(); + }); + + it("transforms the measured geometry centre with scale, rotation and translation", () => { + const pose: IPlacementOverride = { + position: [10, 3, 4], + quaternion: new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI / 2).toArray(), + scale: [2, 3, 4], + grounding: false, + }; + const center = editorPlacementCenter(new Vector3(1, 2, 3), pose); + expect(center.x).toBeCloseTo(22); + expect(center.y).toBeCloseTo(9); + expect(center.z).toBeCloseTo(2); + }); + + it("projects a known world point through the caller's viewport offset", () => { + const camera = new PerspectiveCamera(90, 1, 0.1, 100); + camera.position.z = 10; + camera.updateMatrixWorld(true); + const pixel = editorPointToClient(new Vector3(2, 0, 0), camera, { + left: 10, + top: 20, + width: 200, + height: 100, + }); + expect(pixel[0]).toBeCloseTo(130); + expect(pixel[1]).toBeCloseTo(70); + }); +}); diff --git a/packages/terrain/__tests__/editor-save-queue.spec.ts b/packages/terrain/__tests__/editor-save-queue.spec.ts new file mode 100644 index 0000000000..bd0e71908b --- /dev/null +++ b/packages/terrain/__tests__/editor-save-queue.spec.ts @@ -0,0 +1,614 @@ +import { MessageChannel } from "node:worker_threads"; +import { Terrain } from "@threenative/terrain"; +import { describe, expect, it } from "vitest"; +import { SaveQueue, diffLayers, externalSnapshotAction } from "../src/editor/saveQueue.js"; +import type { + ISaveDocument, + ISaveSnapshot, + ISaveTransaction, + SaveLayer, +} from "../src/editor/saveQueue.js"; + +function recipe() { + return new Terrain({ size: 512, resolution: 129, seed: 73 }) + .noise({ id: "hills", amplitude: 16 }) + .toJSON(); +} +function doc(recipeValue: unknown): ISaveDocument { + return { version: 1, recipe: recipeValue } as ISaveDocument; +} +function ids(document_: ISaveDocument): string[] { + return (document_.recipe.layers ?? []).map((layer) => layer.id); +} + +/** + * A real in-process document authority: validates nothing itself but applies the same patch + * commands through the shipped `Terrain`, so a queue test exercises the actual command semantics. + * It can hold a commit open (to prove edits made while the first save is pending) and reject a + * chosen number of commits with a real 409, and an external writer can move the revision too. + */ +function makeServer(initial: ISaveDocument) { + let current: ISaveDocument = structuredClone(initial); + let revision = "r0"; + let counter = 0; + let holdCount = 0; + let rejectCount = 0; + const gates: (() => void)[] = []; + const commits: ISaveTransaction[] = []; + return { + commits, + get revision() { + return revision; + }, + get document() { + return structuredClone(current); + }, + snapshot(): ISaveSnapshot { + return { revision, document: structuredClone(current), diagnostic: null }; + }, + hold(count: number) { + holdCount = count; + }, + release() { + holdCount = 0; + for (const gate of gates.splice(0)) gate(); + }, + reject(count: number) { + rejectCount = count; + }, + externalAdd(id: string) { + const terrain = Terrain.fromJSON(current.recipe as never).noise({ id, amplitude: 3 }); + current = { ...current, recipe: terrain.toJSON() as never }; + revision = `r${++counter}`; + }, + externalRemove(id: string) { + const terrain = Terrain.fromJSON(current.recipe as never).remove(id); + current = { ...current, recipe: terrain.toJSON() as never }; + revision = `r${++counter}`; + }, + externalAmplitude(id: string, amplitude: number) { + const terrain = Terrain.fromJSON(current.recipe as never).update(id, { + params: { amplitude }, + }); + current = { ...current, recipe: terrain.toJSON() as never }; + revision = `r${++counter}`; + }, + externalMetadata(patch: Record) { + current = { ...current, ...patch }; + revision = `r${++counter}`; + }, + async commit(transaction: ISaveTransaction): Promise { + commits.push(transaction); + if (holdCount > 0) { + holdCount--; + await new Promise((resolve) => gates.push(resolve)); + } + if (rejectCount > 0) { + rejectCount--; + throw new Error("409: Stale base revision"); + } + if (transaction.baseRevision !== revision) throw new Error("409: Stale base revision"); + if (transaction.document) current = structuredClone(transaction.document); + else { + const terrain = Terrain.fromJSON(current.recipe as never); + terrain.applyPatch(transaction.commands ?? []); + current = { ...current, recipe: terrain.toJSON() as never }; + } + revision = `r${++counter}`; + return { revision, document: structuredClone(current), diagnostic: null }; + }, + }; +} +type FakeServer = ReturnType; + +function makeQueue(server: FakeServer): SaveQueue { + return new SaveQueue({ + base: server.snapshot(), + commit: server.commit, + getSnapshot: async () => server.snapshot(), + }); +} + +function grow(recipeValue: unknown, id: string, amplitude: number) { + return Terrain.fromJSON(recipeValue as never) + .noise({ id, amplitude }) + .toJSON(); +} + +describe("terrain editor save queue", () => { + it("keeps a second edit made while the first save is pending", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.hold(1); + let local = grow(server.document.recipe, "a", 5); + queue.record(doc(local)); + expect(server.commits.length).toBe(1); + local = grow(local, "b", 7); + queue.record(doc(local)); + server.release(); + await queue.flush(); + expect(server.commits.length).toBe(2); + expect(ids(server.document).sort()).toEqual(["a", "b", "hills"]); + expect(queue.unsent.length).toBe(0); + }); + + it("keeps a layer added after a pending whole-document config change", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.hold(1); + const first = grow(server.document.recipe, "a", 5); + queue.record(doc(first)); + expect(server.commits.length).toBe(1); + const config = structuredClone(first); + config.config = { ...config.config, seed: 999 }; + queue.record(doc(config)); + const layered = grow(config, "b", 7); + queue.record(doc(layered)); + server.release(); + await queue.flush(); + // The stroke after a queued config save is its own save now, so the config save is not extended. + expect(server.commits.length).toBe(3); + expect(ids(server.document).sort()).toEqual(["a", "b", "hills"]); + expect(server.document.recipe.config.seed).toBe(999); + expect(queue.unsent.length).toBe(0); + }); + + it("honours an undo made before the first save answers", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + const base = structuredClone(server.document.recipe); + server.hold(1); + queue.record(doc(grow(base, "a", 5))); + queue.record(doc(structuredClone(base))); + server.release(); + await queue.flush(); + expect(ids(server.document)).toEqual(["hills"]); + }); + + it("retries a real 409 once, then keeps the refused edit visible", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + const local = grow(server.document.recipe, "a", 5); + server.reject(2); + queue.record(doc(local)); + await queue.flush(); + expect(server.commits.length).toBe(2); + expect(queue.unsent.length).toBe(1); + const refused = queue.unsent[0]; + if (!refused) throw new Error("expected a refused edit"); + expect(refused.label).toContain("rejected"); + // Discarding drops the change from the local observation, so a later edit never resends it. + queue.discard(refused); + expect(queue.unsent.length).toBe(0); + expect(ids(queue.localDocument)).toEqual(["hills"]); + queue.record(doc(grow(queue.localDocument.recipe, "c", 9))); + await queue.flush(); + expect(ids(server.document).sort()).toEqual(["c", "hills"]); + }); + + it("does not resend a discarded edit in a later whole-document save", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.reject(2); + queue.record(doc(grow(server.document.recipe, "a", 5))); + await queue.flush(); + expect(queue.unsent.length).toBe(1); + queue.discard(editFor(queue, "a")); + const local = structuredClone(queue.localDocument.recipe); + local.config = { ...local.config, seed: 999 }; + queue.record(doc(local)); + await queue.flush(); + expect(ids(server.document)).toEqual(["hills"]); + expect(server.document.recipe.config.seed).toBe(999); + expect(queue.unsent.length).toBe(0); + }); + + it("persists a refused edit when the user retries it", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.reject(2); + queue.record(doc(grow(server.document.recipe, "a", 5))); + await queue.flush(); + expect(queue.unsent.length).toBe(1); + const refused = queue.unsent[0]; + if (!refused) throw new Error("expected a refused edit"); + queue.retry(refused); + await queue.flush(); + expect(ids(server.document).sort()).toEqual(["a", "hills"]); + expect(queue.unsent.length).toBe(0); + }); + + it("merges an unrelated external change when it rebases a conflicted save", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.hold(1); + queue.record(doc(grow(server.document.recipe, "a", 5))); + expect(server.commits.length).toBe(1); + server.externalAdd("b"); + server.release(); + await queue.flush(); + expect(ids(server.document).sort()).toEqual(["a", "b", "hills"]); + expect(server.commits.length).toBe(2); + }); + + it("keeps local config, layer and asset changes over unrelated external edits", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + const localRecipe = structuredClone(server.document.recipe); + localRecipe.config = { ...localRecipe.config, seed: 999 }; + const local = { ...grow(localRecipe, "a", 5), assets: [{ id: "tex", kind: "image" }] }; + server.hold(1); + queue.record(doc(local)); + expect(server.commits.length).toBe(1); + server.externalAdd("b"); + server.externalMetadata({ note: "external" }); + server.release(); + await queue.flush(); + const disk = server.document; + expect(disk.recipe.config.seed).toBe(999); + expect(ids(disk).sort()).toEqual(["a", "b", "hills"]); + expect(disk.recipe).toHaveProperty("assets", [{ id: "tex", kind: "image" }]); + expect(disk).toMatchObject({ note: "external" }); + }); + + it("keeps local edits when an external snapshot arrives during a held save", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.hold(1); + let local = grow(server.document.recipe, "a", 5); + queue.record(doc(local)); + expect(server.commits.length).toBe(1); + server.externalAdd("b"); + queue.adopt(server.snapshot()); + local = grow(queue.localDocument.recipe, "c", 7); + queue.record(doc(local)); + server.release(); + await queue.flush(); + expect(ids(server.document).sort()).toEqual(["a", "b", "c", "hills"]); + expect(queue.unsent.length).toBe(0); + }); + + it("keeps the latest remote amplitude when a later edit follows an adopted snapshot", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.hold(1); + queue.record(doc(grow(server.document.recipe, "a", 5))); + expect(server.commits.length).toBe(1); + server.externalAdd("b"); + queue.adopt(server.snapshot()); + queue.record(doc(grow(queue.localDocument.recipe, "c", 7))); + // The user never edited "b", so the remote amplitude must survive the held save's rebase. + server.externalAmplitude("b", 11); + server.release(); + await queue.flush(); + expect(ids(server.document).sort()).toEqual(["a", "b", "c", "hills"]); + expect(server.document.recipe.layers.find((layer) => layer.id === "b")).toMatchObject({ + type: "noise", + params: { amplitude: 11 }, + }); + expect(queue.unsent.length).toBe(0); + }); + + it("does not make a remote layer local-owned through a config intent queued before adopt", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.hold(1); + queue.record(doc(grow(server.document.recipe, "a", 5))); + const config = structuredClone(queue.localDocument.recipe); + config.config = { ...config.config, seed: 999 }; + queue.record(doc(config)); + server.externalAdd("b"); + queue.adopt(server.snapshot()); + queue.record(doc(grow(queue.localDocument.recipe, "c", 7))); + server.externalAmplitude("b", 11); + server.release(); + await queue.flush(); + expect(ids(server.document).sort()).toEqual(["a", "b", "c", "hills"]); + expect(server.document.recipe.layers.find((layer) => layer.id === "b")).toMatchObject({ + type: "noise", + params: { amplitude: 11 }, + }); + expect(server.document.recipe.config.seed).toBe(999); + expect(queue.unsent.length).toBe(0); + }); + + it("sends every layer mutation route", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + let local = grow(server.document.recipe, "a", 5); + queue.record(doc(local)); + await queue.flush(); + expect(ids(server.document)).toContain("a"); + local = Terrain.fromJSON(local as never) + .update("hills", { params: { amplitude: 42 } }) + .toJSON(); + queue.record(doc(local)); + await queue.flush(); + expect(server.document.recipe.layers.find((layer) => layer.id === "hills")).toMatchObject({ + type: "noise", + params: { amplitude: 42 }, + }); + local = Terrain.fromJSON(local as never) + .toggle("hills", false) + .toJSON(); + queue.record(doc(local)); + await queue.flush(); + expect(server.document.recipe.layers.find((l) => l.id === "hills")?.enabled).toBe(false); + local = Terrain.fromJSON(local as never) + .move("a", 0) + .toJSON(); + queue.record(doc(local)); + await queue.flush(); + expect(ids(server.document)[0]).toBe("a"); + local = Terrain.fromJSON(local as never) + .remove("a") + .toJSON(); + queue.record(doc(local)); + await queue.flush(); + expect(ids(server.document)).toEqual(["hills"]); + }); + + it("sends a config change as the whole document", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + const next = structuredClone(server.document.recipe); + next.config = { ...next.config, seed: 999 }; + queue.record(doc(next)); + await queue.flush(); + expect(server.document.recipe.config.seed).toBe(999); + const first = server.commits[0]; + if (!first) throw new Error("expected a commit"); + expect(first.document).toBeTruthy(); + expect(first.commands).toBeUndefined(); + expect(ids(server.document)).toEqual(["hills"]); + }); + + it("sends a recipe asset change as the whole document", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + const next = { + ...structuredClone(server.document.recipe), + assets: [{ id: "tex", kind: "image" }], + }; + queue.record(doc(next)); + await queue.flush(); + expect(server.document.recipe).toHaveProperty("assets", [{ id: "tex", kind: "image" }]); + expect(ids(server.document)).toEqual(["hills"]); + }); + + it("diffs layers into order-preserving commands", () => { + const base = [{ id: "a" }, { id: "b" }] as unknown as SaveLayer[]; + const next = [{ id: "b" }, { id: "a" }, { id: "c" }] as unknown as SaveLayer[]; + const commands = diffLayers(base, next); + expect(commands).toContainEqual({ op: "upsert", layer: { id: "c" } }); + expect(commands.filter((command) => command.op === "move")).toEqual([ + { op: "move", id: "b", index: 0 }, + { op: "move", id: "a", index: 1 }, + { op: "move", id: "c", index: 2 }, + ]); + }); + + it("moves a layer inserted before an existing layer onto disk in order", () => { + const baseRecipe = new Terrain({ size: 512, resolution: 129, seed: 73 }) + .noise({ id: "a", amplitude: 1 }) + .noise({ id: "b", amplitude: 2 }) + .toJSON(); + const inserted = Terrain.fromJSON(baseRecipe as never) + .noise({ id: "c", amplitude: 3 }) + .move("c", 1); + const commands = diffLayers( + baseRecipe.layers as unknown as SaveLayer[], + inserted.toJSON().layers as unknown as SaveLayer[], + ); + const applied = Terrain.fromJSON(baseRecipe as never); + applied.applyPatch(commands as never); + expect(applied.toJSON().layers.map((layer) => layer.id)).toEqual(["a", "c", "b"]); + }); + + it("emits a reorder for a removed then re-added layer with the same id", () => { + const baseRecipe = new Terrain({ size: 512, resolution: 129, seed: 73 }) + .noise({ id: "a", amplitude: 1 }) + .noise({ id: "b", amplitude: 2 }) + .noise({ id: "c", amplitude: 3 }) + .toJSON(); + const layerB = baseRecipe.layers.find((layer) => layer.id === "b"); + if (!layerB) throw new Error("expected layer b"); + const readded = Terrain.fromJSON(baseRecipe as never) + .remove("b") + .add(structuredClone(layerB)); + const commands = diffLayers( + baseRecipe.layers as unknown as SaveLayer[], + readded.toJSON().layers as unknown as SaveLayer[], + ); + const applied = Terrain.fromJSON(baseRecipe as never); + applied.applyPatch(commands as never); + expect(applied.toJSON().layers.map((layer) => layer.id)).toEqual(["a", "c", "b"]); + }); + + function editFor(queue: SaveQueue, layerId: string) { + const edit = queue.unsent.find((item) => + item.commands.some((command) => command.op === "upsert" && command.layer.id === layerId), + ); + if (!edit) throw new Error(`expected a refused edit that adds ${layerId}`); + return edit; + } + + it("keeps a second refused edit when the first is discarded", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.reject(2); + queue.record(doc(grow(server.document.recipe, "a", 5))); + await queue.flush(); + server.reject(2); + queue.record(doc(grow(queue.localDocument.recipe, "b", 7))); + await queue.flush(); + expect(queue.unsent.length).toBe(2); + queue.discard(editFor(queue, "a")); + expect(queue.unsent.length).toBe(1); + expect(ids(queue.localDocument).sort()).toEqual(["b", "hills"]); + }); + + it("keeps an in-flight edit when an earlier refused edit is discarded, then acks only it", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.reject(2); + queue.record(doc(grow(server.document.recipe, "a", 5))); + await queue.flush(); + expect(queue.unsent.length).toBe(1); + server.hold(1); + queue.record(doc(grow(queue.localDocument.recipe, "b", 7))); + expect(queue.hasLocalIntent()).toBe(true); + queue.discard(editFor(queue, "a")); + expect(ids(queue.localDocument).sort()).toEqual(["b", "hills"]); + server.release(); + await queue.flush(); + expect(ids(server.document).sort()).toEqual(["b", "hills"]); + expect(queue.unsent.length).toBe(0); + expect(ids(queue.localDocument).sort()).toEqual(["b", "hills"]); + const commits = server.commits.length; + await queue.flush(); + expect(server.commits.length).toBe(commits); + }); + + it("defers an external snapshot while any local intent exists", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.hold(1); + queue.record(doc(grow(server.document.recipe, "a", 5))); + expect(externalSnapshotAction(queue, server.snapshot())).toBe("defer"); + server.release(); + await queue.flush(); + server.reject(2); + queue.record(doc(grow(server.document.recipe, "b", 7))); + await queue.flush(); + expect(queue.unsent.length).toBe(1); + expect(externalSnapshotAction(queue, server.snapshot())).toBe("defer"); + queue.discard(editFor(queue, "b")); + expect(externalSnapshotAction(queue, { ...server.snapshot(), revision: "newer" })).toBe( + "apply", + ); + }); + + it("keeps remote fields and layers through a 409 rebase and a later whole save", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.hold(1); + const first = grow(server.document.recipe, "a", 5); + queue.record(doc(first)); + expect(server.commits.length).toBe(1); + const config = structuredClone(first); + config.config = { ...config.config, seed: 999 }; + queue.record(doc(config)); + // While the first commit waits, a remote writer adds a layer, a config field and an asset. + server.externalAdd("b"); + const remote = server.document.recipe; + remote.config = { ...remote.config, size: 1024 }; + server.externalMetadata({ recipe: remote, assets: [{ id: "remote-tex", kind: "image" }] }); + server.release(); + await queue.flush(); + expect(server.commits.length).toBe(3); + const sent = server.commits[2]?.document; + if (!sent) throw new Error("expected a commit for the second intent"); + for (const document_ of [sent, server.document, queue.base.document, queue.localDocument]) { + expect(ids(document_).sort()).toEqual(["a", "b", "hills"]); + expect(document_.recipe.config).toMatchObject({ size: 1024, seed: 999 }); + expect(document_.assets).toEqual([{ id: "remote-tex", kind: "image" }]); + } + expect(queue.unsent.length).toBe(0); + }); + + it("refuses a queued toggle of a layer an external writer removed, then settles", async () => { + const server = makeServer(doc(recipe())); + const queue = makeQueue(server); + server.hold(1); + queue.record(doc(grow(server.document.recipe, "a", 5))); + expect(server.commits.length).toBe(1); + const next = structuredClone(queue.localDocument.recipe); + next.config = { ...next.config, seed: 999 }; + queue.record( + doc( + Terrain.fromJSON(next as never) + .toggle("hills", false) + .toJSON(), + ), + ); + // The external writer removes "hills" while the first commit waits. + // The 409 rebase still adds "a". The queued toggle of "hills" then targets a missing layer. + server.externalRemove("hills"); + server.release(); + await queue.flush(); + expect(queue.hasPending()).toBe(false); + expect(queue.unsent.length).toBe(1); + const refused = queue.unsent[0]; + if (!refused) throw new Error("expected a refused edit"); + expect(refused.label).toContain("rejected"); + expect(server.commits.length).toBe(2); + expect(ids(server.document)).toEqual(["a"]); + expect(server.document.recipe.config.seed).toBe(73); + }); +}); + +describe("editor worker transfer", () => { + it.each(["../src/editor/worker.js", "../dist/editor/worker.js"])( + "%s transfers height, genuine normal and splat buffers and can evaluate again", + async (entry) => { + const { Terrain } = await import("../src/index.js"); + const { bakeMesh } = await import("../src/core/bake.js"); + const recipe = new Terrain({ resolution: 17, size: 16 }).noise({ amplitude: 4 }).toJSON(); + const channel = new MessageChannel(); + const sent: ArrayBuffer[] = []; + const expected: ArrayBufferLike[] = []; + const post = Object.getOwnPropertyDescriptor(globalThis, "postMessage"); + const handler = Object.getOwnPropertyDescriptor(globalThis, "onmessage"); + Object.defineProperty(globalThis, "postMessage", { + configurable: true, + value: ( + message: import("../src/editor/worker.js").EditorWorkerReply, + transfer?: ArrayBuffer[], + ) => { + if (message.type === "progress") return; + if (message.type === "evaluated") { + expected.push(message.state.height.buffer, message.state.splat.buffer); + if (message.normal) expected.push(message.normal.buffer); + } + sent.push(...(transfer ?? [])); + channel.port1.postMessage(message, transfer); + }, + }); + try { + await import(entry); + for (const id of [1, 2]) { + const reply = new Promise( + (resolve) => channel.port2.once("message", resolve), + ); + await ( + globalThis.onmessage as unknown as ( + event: MessageEvent, + ) => Promise + )( + new MessageEvent("message", { data: { id, type: "evaluate", recipe, resolution: 17 } }), + ); + const received = await reply; + expect(received.type).toBe("evaluated"); + if (received.type !== "evaluated") throw new Error("No evaluated state"); + expect(sent).toHaveLength(3 * id); + expect(sent).toEqual(expect.arrayContaining(expected)); + expect(sent.every((buffer) => buffer.byteLength === 0)).toBe(true); + expect(received.state.height.length).toBe(17 * 17); + expect(received.state.splat.length).toBe(17 * 17 * 8); + expect(received.normal).toEqual(bakeMesh(received.state).normals); + expect(received.state.height.some((height) => height !== 0)).toBe(true); + } + expect(sent).toHaveLength(6); + } finally { + channel.port1.close(); + channel.port2.close(); + if (post) Object.defineProperty(globalThis, "postMessage", post); + else Reflect.deleteProperty(globalThis, "postMessage"); + if (handler) Object.defineProperty(globalThis, "onmessage", handler); + else Reflect.deleteProperty(globalThis, "onmessage"); + } + }, + 10_000, + ); +}); diff --git a/packages/terrain/__tests__/editor-source.spec.ts b/packages/terrain/__tests__/editor-source.spec.ts new file mode 100644 index 0000000000..3c071c0e34 --- /dev/null +++ b/packages/terrain/__tests__/editor-source.spec.ts @@ -0,0 +1,27 @@ +import { readFileSync, readdirSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +import { errorMessage } from "../src/editor/app/dom.js"; + +const editorRoot = fileURLToPath(new URL("../src/editor/", import.meta.url)); +const sources = readdirSync(editorRoot, { recursive: true, withFileTypes: true }) + .filter((entry) => entry.isFile()) + .map((entry) => `${entry.parentPath}/${entry.name}`); + +describe("typed terrain editor source", () => { + it("preserves validation and transport error messages", () => { + expect(errorMessage(new Error("invalid layer"))).toBe("invalid layer"); + expect(errorMessage("disconnected")).toBe("disconnected"); + }); + it("contains no JavaScript source", () => { + expect(sources.filter((path) => /\.(?:[cm]?js|jsx)$/.test(path))).toEqual([]); + }); + + it("constructs the DOM without HTML sinks", () => { + const sinks = /\b(?:innerHTML|outerHTML|insertAdjacentHTML)\b/; + expect( + sources.filter((path) => path.endsWith(".ts") && sinks.test(readFileSync(path, "utf8"))), + ).toEqual([]); + }); +}); diff --git a/packages/terrain/__tests__/editor-worker-restart.spec.ts b/packages/terrain/__tests__/editor-worker-restart.spec.ts new file mode 100644 index 0000000000..179702bd31 --- /dev/null +++ b/packages/terrain/__tests__/editor-worker-restart.spec.ts @@ -0,0 +1,152 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { build, cancelBuild, dispatch, newWorker } from "../src/editor/app/evaluation.js"; +import type { EditorSession, IEvaluationRequest } from "../src/editor/app/session.js"; +import type { EditorWorkerReply } from "../src/editor/worker.js"; +import { Terrain, bakeMesh } from "../src/index.js"; + +class FakeWorker { + static instances: FakeWorker[] = []; + jobs: IEvaluationRequest[] = []; + terminated = false; + onmessage?: (event: MessageEvent) => Promise; + onerror?: (event: ErrorEvent) => void; + constructor() { + FakeWorker.instances.push(this); + } + postMessage(job: IEvaluationRequest) { + if (this.terminated) throw new Error("Worker already terminated"); + this.jobs.push(job); + } + terminate() { + this.terminated = true; + } + async reply() { + const job = this.jobs.at(-1); + if (!job) throw Error("No evaluation requested"); + const state = Terrain.fromJSON(job.recipe).evaluate({ resolution: job.resolution }); + await this.onmessage?.({ + data: { id: job.id, type: "evaluated", state, normal: bakeMesh(state).normals, ms: 1 }, + } as MessageEvent); + return state; + } +} +function session() { + const controls = new Map(); + vi.stubGlobal("document", { + getElementById: (id: string) => { + if (!controls.has(id)) controls.set(id, { textContent: "", value: "17" }); + return controls.get(id); + }, + querySelectorAll: () => [], + }); + vi.stubGlobal("Worker", FakeWorker); + FakeWorker.instances = []; + const c = { + disposed: false, + terrain: new Terrain({ size: 32, resolution: 17, seed: 73 }), + sequence: 0, + workerBusy: false, + evaluationRequests: 0, + worker: null, + pending: null, + revision: "saved", + view: { update: vi.fn((state) => state) }, + saveQueue: { hasLocalIntent: () => false }, + setBusy: vi.fn(), + toast: vi.fn(), + onEvaluated: vi.fn(), + attempt: (fn: () => unknown) => fn(), + workerURL: () => new URL("http://localhost/worker.js"), + } as unknown as EditorSession; + c.newWorker = () => newWorker(c); + c.dispatch = () => dispatch(c); + return c; +} +afterEach(() => { + vi.useRealTimers(); + vi.unstubAllGlobals(); +}); + +describe("editor worker restart", () => { + it("terminates a crashed worker and rebuilds the next edit on a new worker", async () => { + vi.useFakeTimers(); + const c = session(); + build(c); + await vi.advanceTimersByTimeAsync(35); + const first = FakeWorker.instances[0]; + expect(c.workerBusy).toBe(true); + first?.onerror?.({ + message: "worker crashed", + preventDefault: vi.fn(), + } as unknown as ErrorEvent); + expect(first?.terminated).toBe(true); + expect(c.worker).toBeNull(); + c.terrain.sculpt({ points: [[0, 0]], radius: 4, strength: 1 }); + build(c); + await vi.advanceTimersByTimeAsync(35); + const second = FakeWorker.instances[1]; + expect(second).toBeDefined(); + const evaluated = await second?.reply(); + expect(c.state).toBe(evaluated); + expect(c.workerBusy).toBe(false); + expect(c.onEvaluated).toHaveBeenCalledOnce(); + expect(c.toast).toHaveBeenCalledWith(expect.stringContaining("worker crashed")); + }); + + it("cancels mid-build and ignores the terminated worker's late reply after the next edit", async () => { + vi.useFakeTimers(); + const c = session(); + build(c); + await vi.advanceTimersByTimeAsync(35); + const first = FakeWorker.instances[0]; + cancelBuild(c); + expect(first?.terminated).toBe(true); + c.terrain.sculpt({ points: [[0, 0]], radius: 4, strength: 1 }); + build(c); + await vi.advanceTimersByTimeAsync(35); + const second = FakeWorker.instances[1]; + await first?.reply(); + expect(c.workerBusy).toBe(true); + expect(c.onEvaluated).not.toHaveBeenCalled(); + const evaluated = await second?.reply(); + expect(c.state).toBe(evaluated); + expect(c.onEvaluated).toHaveBeenCalledOnce(); + }); + it("restarts immediately with the latest request queued during a crash", async () => { + vi.useFakeTimers(); + const c = session(); + build(c); + await vi.advanceTimersByTimeAsync(35); + c.terrain.sculpt({ id: "newest", points: [[0, 0]], radius: 4, strength: 1 }); + build(c); + FakeWorker.instances[0]?.onerror?.({ + message: "crash", + preventDefault: vi.fn(), + } as unknown as ErrorEvent); + const next = FakeWorker.instances[1]; + expect(next?.jobs[0]?.recipe.layers.map((layer) => layer.id)).toEqual(["newest"]); + expect(c.workerBusy).toBe(true); + await next?.reply(); + expect(c.workerBusy).toBe(false); + expect(c.onEvaluated).toHaveBeenCalledOnce(); + }); + + it("drops a worker that cannot accept a message and lets the next edit rebuild", async () => { + vi.useFakeTimers(); + const c = session(); + build(c); + await vi.advanceTimersByTimeAsync(35); + const first = FakeWorker.instances[0]; + await first?.reply(); + first?.terminate(); + build(c); + await vi.advanceTimersByTimeAsync(35); + expect(c.worker).toBeNull(); + c.terrain.sculpt({ points: [[0, 0]], radius: 4, strength: 1 }); + build(c); + await vi.advanceTimersByTimeAsync(35); + const evaluated = await FakeWorker.instances[1]?.reply(); + expect(c.state).toBe(evaluated); + expect(c.workerBusy).toBe(false); + }); +}); diff --git a/packages/terrain/__tests__/erosion-bounds.spec.ts b/packages/terrain/__tests__/erosion-bounds.spec.ts new file mode 100644 index 0000000000..ff511906e3 --- /dev/null +++ b/packages/terrain/__tests__/erosion-bounds.spec.ts @@ -0,0 +1,107 @@ +import { describe, expect, it, vi } from "vitest"; +import { hydraulic, thermal } from "../src/core/erosion.js"; + +// Count every seeded draw, so a test can measure the actual droplet work instead of trusting a +// completion-only assertion that an uncapped default would also satisfy. +const drawn = vi.hoisted(() => ({ count: 0 })); + +vi.mock("../src/core/math.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + random: (seed: number) => { + const rnd = actual.random(seed); + return () => { + drawn.count += 1; + return rnd(); + }; + }, + }; +}); + +describe("erosion grid and parameter bounds validation", () => { + it("thermal erosion rejects n < 2, fractional or non-finite grid resolution", () => { + const h = new Float32Array(4); + expect(() => thermal(new Float32Array(1), 1, 10)).toThrow(RangeError); + expect(() => thermal(h, -1, 10)).toThrow(RangeError); + expect(() => thermal(h, 2.5, 10)).toThrow(RangeError); + expect(() => thermal(h, Number.NaN, 10)).toThrow(RangeError); + expect(() => thermal(h, Number.POSITIVE_INFINITY, 10)).toThrow(RangeError); + }); + + it("thermal erosion rejects out-of-bound, fractional or non-finite iterations", () => { + const h4 = new Float32Array(4); + expect(() => thermal(h4, 2, 10, { iterations: -1 })).toThrow(RangeError); + expect(() => thermal(h4, 2, 10, { iterations: 201 })).toThrow(RangeError); + expect(() => thermal(h4, 2, 10, { iterations: 1.5 })).toThrow(RangeError); + expect(() => thermal(h4, 2, 10, { iterations: Number.NaN })).toThrow(RangeError); + expect(() => thermal(h4, 2, 10, { iterations: Number.POSITIVE_INFINITY })).toThrow(RangeError); + }); + + it("thermal erosion preserves no-op zero and boundary 200 iterations", () => { + const h4 = new Float32Array([1, 2, 3, 4]); + const zero = thermal(h4, 2, 10, { iterations: 0 }); + expect(zero).toEqual(h4); + expect(() => thermal(h4, 2, 10, { iterations: 200 })).not.toThrow(); + }); + + it("hydraulic erosion rejects n < 2, fractional or non-finite grid resolution", () => { + const h = new Float32Array(4); + expect(() => hydraulic(new Float32Array(1), 1, 10)).toThrow(RangeError); + expect(() => hydraulic(h, -1, 10)).toThrow(RangeError); + expect(() => hydraulic(h, 2.5, 10)).toThrow(RangeError); + expect(() => hydraulic(h, Number.NaN, 10)).toThrow(RangeError); + expect(() => hydraulic(h, Number.POSITIVE_INFINITY, 10)).toThrow(RangeError); + }); + + it("hydraulic erosion rejects out-of-bound, fractional or non-finite droplets and steps", () => { + const h4 = new Float32Array(4); + expect(() => hydraulic(h4, 2, 10, { droplets: -1 })).toThrow(RangeError); + expect(() => hydraulic(h4, 2, 10, { droplets: 1.5 })).toThrow(RangeError); + expect(() => hydraulic(h4, 2, 10, { droplets: Number.NaN })).toThrow(RangeError); + expect(() => hydraulic(h4, 2, 10, { droplets: Number.POSITIVE_INFINITY })).toThrow(RangeError); + + expect(() => hydraulic(h4, 2, 10, { maxSteps: 0 })).toThrow(RangeError); + expect(() => hydraulic(h4, 2, 10, { maxSteps: 129 })).toThrow(RangeError); + expect(() => hydraulic(h4, 2, 10, { maxSteps: 1.5 })).toThrow(RangeError); + expect(() => hydraulic(h4, 2, 10, { maxSteps: Number.NaN })).toThrow(RangeError); + expect(() => hydraulic(h4, 2, 10, { maxSteps: Number.POSITIVE_INFINITY })).toThrow(RangeError); + }); + + it("hydraulic erosion supports no-op zero droplets and boundary 128 maxSteps", () => { + const h4 = new Float32Array([1, 2, 3, 4]); + const zero = hydraulic(h4, 2, 10, { droplets: 0 }); + expect(zero).toEqual(h4); + expect(() => hydraulic(h4, 2, 10, { droplets: 1, maxSteps: 128 })).not.toThrow(); + }); + + it("hydraulic erosion accepts large supported grid defaults (513 and 1025)", () => { + const flat513 = new Float32Array(513 * 513); + expect(() => hydraulic(flat513, 513, 512, { maxSteps: 1 })).not.toThrow(); + + const flat1025 = new Float32Array(1025 * 1025); + expect(() => hydraulic(flat1025, 1025, 512, { maxSteps: 1 })).not.toThrow(); + }); + + it("rejects an explicit droplet count above the recipe limit, accepts the limit itself", () => { + const h4 = new Float32Array(4); + expect(() => hydraulic(h4, 2, 10, { droplets: 200_001 })).toThrow(RangeError); + expect(() => hydraulic(h4, 2, 10, { droplets: 200_000, maxSteps: 1 })).not.toThrow(); + expect(() => hydraulic(h4, 2, 10, { droplets: Number.MAX_SAFE_INTEGER + 1 })).toThrow( + RangeError, + ); + }); + + it("bounds the automatic droplet default with an exact work count on an oversized grid", () => { + // The default is one droplet per cell, capped at the largest supported grid (1025 x 1025). + // An uncapped n*n default would run one droplet per cell of the 2048² field, about four times + // the capped work; counting seeded draws compares the two, so removing the cap turns this red. + drawn.count = 0; + hydraulic(new Float32Array(1025 * 1025), 1025, 512, { maxSteps: 1 }); + const capped = drawn.count; + drawn.count = 0; + hydraulic(new Float32Array(2048 * 2048), 2048, 512, { maxSteps: 1 }); + expect(drawn.count).toBe(capped); + expect(capped).toBe(3 * 1025 * 1025); + }); +}); diff --git a/packages/terrain/__tests__/example-imports.spec.ts b/packages/terrain/__tests__/example-imports.spec.ts new file mode 100644 index 0000000000..161f6740ae --- /dev/null +++ b/packages/terrain/__tests__/example-imports.spec.ts @@ -0,0 +1,15 @@ +import { readFileSync, readdirSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { expect, it } from "vitest"; + +it("keeps terrain package tests independent of example source", () => { + const root = fileURLToPath(new URL(".", import.meta.url)); + const importsExample = /\b(?:from\s*|import\s*(?:\(\s*)?)['"][^'"]*examples\//u; + const violations = readdirSync(root, { recursive: true }) + .filter( + (name): name is string => + typeof name === "string" && /\.(?:[cm]?ts|tsx|[cm]?js)$/u.test(name), + ) + .filter((name) => importsExample.test(readFileSync(`${root}${name}`, "utf8"))); + expect(violations).toEqual([]); +}); diff --git a/packages/terrain/__tests__/io.spec.ts b/packages/terrain/__tests__/io.spec.ts new file mode 100644 index 0000000000..2b62101ba4 --- /dev/null +++ b/packages/terrain/__tests__/io.spec.ts @@ -0,0 +1,88 @@ +import { Terrain } from "@threenative/terrain"; +import { describe, expect, it } from "vitest"; +import { makeExport } from "../src/core/export.js"; +import { decodeHeightPNG, encodeHeightPNG } from "../src/core/io.js"; + +const CRC_TABLE = Uint32Array.from({ length: 256 }, (_, initial) => { + let n = initial; + for (let k = 0; k < 8; k++) n = n & 1 ? 0xedb88320 ^ (n >>> 1) : n >>> 1; + return n >>> 0; +}); + +function crc32(bytes: Uint8Array): number { + let c = 0xffffffff; + for (const b of bytes) c = (CRC_TABLE[(c ^ b) & 255] as number) ^ (c >>> 8); + return (c ^ 0xffffffff) >>> 0; +} + +function replaceMetadata(png: Uint8Array, rawJson: string): Uint8Array { + const needle = new TextEncoder().encode("StrataHeightRange\0"); + let pos = -1; + for (let i = 0; i <= png.length - needle.length; i++) { + let match = true; + for (let j = 0; j < needle.length; j++) { + if (png[i + j] !== needle[j]) { + match = false; + break; + } + } + if (match) { + pos = i; + break; + } + } + if (pos < 8) throw new Error("Metadata chunk not found in PNG"); + const chunkOffset = pos - 8; + const oldLength = new DataView(png.buffer, png.byteOffset).getUint32(chunkOffset); + const payload = new TextEncoder().encode(`StrataHeightRange\0${rawJson}`); + const typeAndData = new Uint8Array(4 + payload.length); + typeAndData.set(new TextEncoder().encode("tEXt"), 0); + typeAndData.set(payload, 4); + + const chunk = new Uint8Array(12 + payload.length); + const view = new DataView(chunk.buffer); + view.setUint32(0, payload.length); + chunk.set(typeAndData, 4); + view.setUint32(8 + payload.length, crc32(typeAndData)); + + const afterOffset = chunkOffset + 12 + oldLength; + const corrupted = new Uint8Array(chunkOffset + chunk.length + (png.length - afterOffset)); + corrupted.set(png.subarray(0, chunkOffset), 0); + corrupted.set(chunk, chunkOffset); + corrupted.set(png.subarray(afterOffset), chunkOffset + chunk.length); + return corrupted; +} + +describe("decodeHeightPNG error handling", () => { + it.each([ + ["null", "null"], + ["numeric", " 12345"], + ])("rejects %s metadata with documented error", async (_, invalidJson) => { + const state = new Terrain({ resolution: 17 }).evaluate(); + const validPng = await encodeHeightPNG(state); + const corrupted = replaceMetadata(validPng, invalidJson); + await expect(decodeHeightPNG(corrupted)).rejects.toThrow("Invalid PNG height range metadata"); + }); + + it("rejects truncated chunk with documented error", async () => { + const state = new Terrain({ resolution: 17 }).evaluate(); + const validPng = await encodeHeightPNG(state); + const truncated = validPng.subarray(0, 45); + await expect(decodeHeightPNG(truncated)).rejects.toThrow("Truncated or oversized PNG chunk"); + }); + + it("rejects bad CRC with documented error", async () => { + const state = new Terrain({ resolution: 17 }).evaluate(); + const validPng = await encodeHeightPNG(state); + const corrupted = new Uint8Array(validPng); + const byte = corrupted[16]; + if (byte !== undefined) corrupted[16] = byte ^ 0xff; + await expect(decodeHeightPNG(corrupted)).rejects.toThrow(/PNG CRC mismatch/); + }); +}); + +it("exports a project recipe before the first terrain evaluation", async () => { + const recipe = new Terrain({ resolution: 17 }).toJSON(); + const archive = await makeExport(null, recipe, "project"); + expect(JSON.parse(new TextDecoder().decode(archive.bytes))).toEqual(recipe); +}); diff --git a/packages/terrain/__tests__/kit-assets.spec.ts b/packages/terrain/__tests__/kit-assets.spec.ts new file mode 100644 index 0000000000..849790821e --- /dev/null +++ b/packages/terrain/__tests__/kit-assets.spec.ts @@ -0,0 +1,214 @@ +import { execFile } from "node:child_process"; +import { createHash } from "node:crypto"; +import { copyFile, cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { promisify } from "node:util"; +import { gunzipSync, gzipSync } from "node:zlib"; +import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { makeTempDir } from "../../../test-support/temp-dir.js"; +import { copyTerrainKit } from "../../create-threenative/src/terrain-kit.js"; + +const run = promisify(execFile); +const root = fileURLToPath(new URL("..", import.meta.url)); +const script = join(root, "scripts/pack-starter-assets.mjs"); +const sha = (bytes: Buffer) => createHash("sha256").update(bytes).digest("hex"); + +async function pack(dir: string) { + const file = join(dir, "assets.tar.gz"); + await run(process.execPath, [script, file]); + return { file, bytes: await readFile(file) }; +} + +let packed: Buffer; +beforeAll(async () => { + const directory = await makeTempDir("tn-terrain-pack-"); + try { + packed = (await pack(directory)).bytes; + } finally { + await rm(directory, { recursive: true, force: true }); + } +}, 60_000); + +async function installed(bytes = packed) { + const dir = await makeTempDir("tn-terrain-archive-"); + const archive = { file: join(dir, "assets.tar.gz"), bytes }; + await writeFile(archive.file, bytes); + await mkdir(join(dir, "dist")); + await copyFile(join(root, "dist/kit.js"), join(dir, "dist/kit.js")); + await writeFile( + join(dir, "package.json"), + JSON.stringify({ + name: "@threenative/terrain", + type: "module", + exports: { "./package.json": "./package.json" }, + }), + ); + const manifest = { + version: "0.1.0", + url: "https://invalid.test/assets", + sha256: sha(archive.bytes), + bytes: archive.bytes.length, + }; + await writeFile(join(dir, "starter-assets.json"), JSON.stringify(manifest)); + vi.stubEnv("XDG_CACHE_HOME", join(dir, "cache")); + vi.stubEnv("THREENATIVE_TERRAIN_ASSETS_URL", pathToFileURL(archive.file).href); + const module = await import(pathToFileURL(join(dir, "dist/kit.js")).href); + return { + dir, + archive, + manifest, + resolve: module.starterAssetsDir as () => Promise, + bake: module.bakeKit as (url: string, options: { sky: string }) => Promise, + }; +} + +afterEach(() => { + vi.unstubAllEnvs(); + vi.unstubAllGlobals(); +}); + +describe("pinned starter assets", () => { + it("packs identically twice and matches the committed manifest", async () => { + const first = { bytes: packed }; + const second = await pack(await makeTempDir("tn-terrain-pack-")); + expect(first.bytes.equals(second.bytes)).toBe(true); + const manifest = JSON.parse(await readFile(join(root, "starter-assets.json"), "utf8")); + expect(manifest.sha256).toBe(sha(first.bytes)); + expect(manifest.bytes).toBe(first.bytes.length); + const { version } = JSON.parse(await readFile(join(root, "package.json"), "utf8")); + expect(manifest.version).toBe(version); + expect(manifest.url).toBe( + `https://github.com/ThreeNativeHQ/threenative-engine/releases/download/terrain-starter-assets-v${version}/terrain-starter-assets-${version}.tar.gz`, + ); + expect(first.bytes.readUInt32LE(4)).toBe(0); + }, 30_000); + + it("extracts a file archive and reuses the cache without reading it again", async () => { + const fixture = await installed(); + const cache = await fixture.resolve(); + expect(cache).toBe( + join(fixture.dir, "cache/threenative/terrain-starter-assets", fixture.manifest.sha256), + ); + expect( + (await readFile(join(cache, "fir_tree_01/fir-b-lod2.glb"))).equals( + await readFile(join(root, "starter-assets/fir_tree_01/fir-b-lod2.glb")), + ), + ).toBe(true); + expect(await readFile(join(cache, "credits.json"))).toEqual( + await readFile(join(root, "starter-assets/credits.json")), + ); + await rm(fixture.archive.file); + expect(await fixture.resolve()).toBe(cache); + expect((await readdir(join(cache, ".."))).filter((name) => name.includes(".tmp-"))).toEqual([]); + }, 30_000); + + it("bakes a scaffolded forest kit using the file archive with no bundled assets", async () => { + const fixture = await installed(); + const game = join(fixture.dir, "game"); + const kit = join(fixture.dir, "template-assets/terrain/forest"); + await cp(join(root, "starter/forest"), kit, { recursive: true }); + await copyFile(join(root, "starter-assets/credits.json"), join(kit, "credits.json")); + await copyTerrainKit("forest", game, join(fixture.dir, "templates")); + await fixture.bake(pathToFileURL(join(game, "bake.mjs")).href, { + sky: "hdri/kloofendal_48d_partly_cloudy_1k.hdr", + }); + expect(JSON.parse(await readFile(join(game, "recipe.json"), "utf8"))).toHaveProperty("recipe"); + expect( + (await readFile(join(game, "world/models/fir-b-lod1.glb"))).equals( + await readFile(join(root, "starter-assets/fir_tree_01/fir-b-lod1.glb")), + ), + ).toBe(true); + expect( + (await readFile(join(game, "world/sky.hdr"))).equals( + await readFile(join(root, "starter-assets/hdri/kloofendal_48d_partly_cloudy_1k.hdr")), + ), + ).toBe(true); + }, 30_000); + + it("downloads once over HTTP and then uses the cache", async () => { + const fixture = await installed(); + vi.stubEnv("THREENATIVE_TERRAIN_ASSETS_URL", "https://invalid.test/archive"); + const fetch = vi.fn().mockResolvedValue(new Response(new Uint8Array(packed))); + vi.stubGlobal("fetch", fetch); + const cache = await fixture.resolve(); + expect(await fixture.resolve()).toBe(cache); + expect(fetch).toHaveBeenCalledTimes(1); + }, 30_000); + + it("prefers workspace assets without fetching", async () => { + const fixture = await installed(); + await mkdir(join(fixture.dir, "starter-assets")); + await rm(fixture.archive.file); + expect(await fixture.resolve()).toBe(join(fixture.dir, "starter-assets")); + }, 30_000); + + it("rejects a SHA mismatch before extraction", async () => { + const fixture = await installed(); + fixture.manifest.sha256 = "0".repeat(64); + await writeFile(join(fixture.dir, "starter-assets.json"), JSON.stringify(fixture.manifest)); + await expect(fixture.resolve()).rejects.toThrow("TN_TERRAIN_ASSETS_SHA256"); + await expect(readdir(join(fixture.dir, "cache"))).rejects.toThrow(); + }, 30_000); + + it("rejects a size mismatch before extraction", async () => { + const fixture = await installed(); + fixture.manifest.bytes += 1; + await writeFile(join(fixture.dir, "starter-assets.json"), JSON.stringify(fixture.manifest)); + await expect(fixture.resolve()).rejects.toThrow("TN_TERRAIN_ASSETS_SIZE"); + }, 30_000); + + it("names HTTP failures", async () => { + const fixture = await installed(); + vi.stubEnv("THREENATIVE_TERRAIN_ASSETS_URL", "https://invalid.test/archive"); + vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(null, { status: 404 }))); + await expect(fixture.resolve()).rejects.toThrow("TN_TERRAIN_ASSETS_HTTP: 404"); + }, 30_000); + + it.each(["starter-assets/../escaped", "/escaped", "starter-assets/link"])( + "rejects unsafe archive entry %s atomically", + async (name) => { + const tar = gunzipSync(packed); + tar.fill(0, 0, 100); + tar.write(name, 0, 100); + if (name.endsWith("link")) tar[156] = 50; + tar.fill(32, 148, 156); + const checksum = tar.subarray(0, 512).reduce((sum, byte) => sum + byte, 0); + tar.write(`${checksum.toString(8).padStart(6, "0")}\0 `, 148, 8); + const fixture = await installed(gzipSync(tar, { level: 9 })); + await expect(fixture.resolve()).rejects.toThrow( + name.endsWith("link") ? "TN_TERRAIN_ASSETS_TYPE" : "TN_TERRAIN_ASSETS_PATH", + ); + expect(await readdir(join(fixture.dir, "cache/threenative/terrain-starter-assets"))).toEqual( + [], + ); + await expect(readFile(join(fixture.dir, "escaped"))).rejects.toThrow(); + }, + 30_000, + ); +}); + +it("every starter asset directory has a kit or example reference", async () => { + const references: string[] = []; + for (const kit of await readdir(join(root, "starter"))) { + for (const file of ["assets.json", "surface.json", "bake.mjs"]) + references.push(await readFile(join(root, "starter", kit, file), "utf8")); + } + async function source(dir: string) { + for (const entry of await readdir(dir, { withFileTypes: true })) { + if (entry.isDirectory()) await source(join(dir, entry.name)); + else if (/\.(ts|mjs)$/u.test(entry.name)) + references.push(await readFile(join(dir, entry.name), "utf8")); + } + } + await source(join(root, "../../examples/strata-terrain-preview/src")); + const dirs = (await readdir(join(root, "starter-assets"), { withFileTypes: true })).filter( + (entry) => entry.isDirectory(), + ); + expect(dirs.length).toBeGreaterThan(0); + for (const dir of dirs) + expect( + references.some((text) => text.includes(`${dir.name}/`)), + `${dir.name} has no asset reference`, + ).toBe(true); +}); diff --git a/packages/terrain/__tests__/kit-bake.spec.ts b/packages/terrain/__tests__/kit-bake.spec.ts new file mode 100644 index 0000000000..03ffef28a3 --- /dev/null +++ b/packages/terrain/__tests__/kit-bake.spec.ts @@ -0,0 +1,89 @@ +import { execFileSync } from "node:child_process"; +import { copyFileSync, mkdirSync, readFileSync, symlinkSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { type IWorldPackage, validateWorldPackage } from "@threenative/core/world"; +import { describe, expect, it } from "vitest"; +import { makeTempDirSync } from "../../../test-support/temp-dir.js"; + +const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); +const starterAssets = join(packageRoot, "starter-assets"); +// Per-kit assertion differences: scatter asset/count, water identities, river point count and golden files. +const kits = [ + { name: "alpine", asset: "boulder", count: 90, water: "none", compareWater: false }, + { name: "desert", asset: "boulder", count: 140, water: "empty", compareWater: true }, + { name: "tundra", asset: "boulder", count: 70, water: "kettles", compareWater: true }, + { name: "coastal", asset: "fir", count: 80, water: "ocean", compareWater: true }, +]; +describe.each(kits)("$name starter kit", ({ name, asset, count, water, compareWater }) => { + const kit = join(packageRoot, "starter", name); + /** Copies the alpine kit into a temp directory whose node_modules resolves the workspace package. */ + function kitInTempDir(): string { + const dir = makeTempDirSync(`terrain-${name}-kit-`); + for (const name of ["bake.mjs", "recipe.json", "assets.json", "surface.json", "heightmap.r16"]) + copyFileSync(join(kit, name), join(dir, name)); + mkdirSync(join(dir, "node_modules", "@threenative"), { recursive: true }); + symlinkSync(packageRoot, join(dir, "node_modules", "@threenative", "terrain"), "dir"); + return dir; + } + + function bake(out: string): void { + execFileSync(process.execPath, ["bake.mjs", "--assets", starterAssets, "--out", out], { + cwd: kitInTempDir(), + stdio: "pipe", + }); + } + + it("bakes a world package the engine's validator accepts", () => { + const out = join(makeTempDirSync(`terrain-${name}-world-`), "world"); + bake(out); + const manifest = JSON.parse(readFileSync(join(out, "world.json"), "utf8")) as IWorldPackage; + const result = validateWorldPackage(manifest, { + heightmapByteLength: readFileSync(join(out, manifest.terrain.heightmap)).byteLength, + placementsByteLength: readFileSync(join(out, manifest.placements)).byteLength, + }); + expect(result.errors).toEqual([]); + expect(manifest.terrain.columns).toBe(257); + expect(manifest.extent.sizeX).toBe(512); + + const perAsset: Record = {}; + for (const cell of manifest.cells) + for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; + expect(Object.keys(perAsset).sort()).toEqual([asset]); + expect(perAsset[asset]).toBe(count); + if (water !== "none") { + const data = JSON.parse(readFileSync(join(out, "water.json"), "utf8")) as { + lakes: { id: string; level: number }[]; + rivers: { id: string; points: unknown[] }[]; + }; + if (water === "empty") { + expect(data.lakes).toEqual([]); + expect(data.rivers).toEqual([]); + } else if (water === "kettles") { + expect(data.lakes.map((lake) => lake.id).sort()).toEqual(["far-kettle", "near-kettle"]); + expect(data.rivers.map((river) => river.id).sort()).toEqual(["braid", "meltwater"]); + expect(data.rivers.every((river) => river.points.length > 2)).toBe(true); + } else { + expect(data.lakes).toEqual([expect.objectContaining({ id: "ocean", level: 1.5 })]); + } + } + }); + + it("bakes the same bytes from the same recipe", () => { + const first = join(makeTempDirSync(`terrain-${name}-a-`), "world"); + const second = join(makeTempDirSync(`terrain-${name}-b-`), "world"); + bake(first); + bake(second); + for (const name of [ + "world.json", + "heightmap.u16", + "placements.bin", + "splat.rgba", + ...(compareWater ? ["water.json"] : []), + ]) { + expect(readFileSync(join(first, name)).equals(readFileSync(join(second, name))), name).toBe( + true, + ); + } + }); +}); diff --git a/packages/terrain/__tests__/alpine-runtime.spec.ts b/packages/terrain/__tests__/kit-runtime.spec.ts similarity index 59% rename from packages/terrain/__tests__/alpine-runtime.spec.ts rename to packages/terrain/__tests__/kit-runtime.spec.ts index b5ab3b6ee1..c5e0659df2 100644 --- a/packages/terrain/__tests__/alpine-runtime.spec.ts +++ b/packages/terrain/__tests__/kit-runtime.spec.ts @@ -3,7 +3,10 @@ import type { IWorldCellsLoadOptions, IWorldPackage } from "@threenative/core/wo import { type IPhysicsContext, rapier } from "@threenative/physics"; import { Group, Mesh, MeshBasicMaterial, MeshStandardMaterial, Object3D, Texture } from "three"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { PropColliders, addAlpine } from "../starter/alpine/world.js"; +import { PropColliders as AlpineColliders, addAlpine } from "../starter/alpine/world.js"; +import { PropColliders as CoastalColliders, addCoastal } from "../starter/coastal/world.js"; +import { PropColliders as DesertColliders, addDesert } from "../starter/desert/world.js"; +import { PropColliders as TundraColliders, addTundra } from "../starter/tundra/world.js"; const loads = vi.hoisted(() => ({ surface: vi.fn(), sky: vi.fn(), world: vi.fn() })); vi.mock("@threenative/core/world", async (original) => { @@ -16,104 +19,139 @@ vi.mock("three/addons/loaders/HDRLoader.js", () => ({ }, })); -type Ctx = ICtx, IPhysicsContext>; -const plugins: ReturnType[] = []; -const lifetimes: Array<() => void> = []; -const worlds: Array = []; -const manifest: IWorldPackage = { - version: 1, - cellSize: 64, - extent: { minX: -128, minZ: -128, sizeX: 256, sizeZ: 256 }, - assets: { boulder: { glb: "boulder.glb", bounds: { min: [-1, 0, -1], max: [1, 1, 1] } } }, - cells: [{ x: 0, z: 0, runs: [{ asset: "boulder", offset: 0, count: 2 }] }], - placements: "placements.bin", - terrain: { - heightmap: "heightmap.u16", - columns: 3, - rows: 3, - spacing: 128, - heightMin: 0, - heightMax: 10, +vi.mock("../starter/coastal/sea.js", () => ({ + addCoastalSea: vi.fn(() => ({ mesh: {}, dispose: vi.fn() })), +})); +// Only these columns differed: asset, water response/request and missing-model filename. +const kits = [ + { + kit: "alpine", + addWorld: addAlpine, + Collider: AlpineColliders, + asset: "boulder", + water: undefined, }, -}; -// Two boulders, eight floats each: x, y, z, three unused, a scale in the last slot. One sits at the -// origin and one 130 m east. -const records = new Float32Array([0, 0, 0, 0, 0, 0, 1, 1, 130, 0, 0, 0, 0, 0, 1, 1]); -const heightmap = new Uint16Array([0, 2000, 4000, 6000, 8000, 10000, 12000, 14000, 16000]); -let requests: string[]; -let surface: MeshBasicMaterial; -let sky: Texture; - -async function context() { - const scene = new Group(); - const model = new Group(); - const ctx = { - scene, - assets: { resolve: async (path: string) => [path], model: async () => ({ scene: model }) }, - add: (object: T): T => { - scene.add(object); - return object; + { + kit: "desert", + addWorld: addDesert, + Collider: DesertColliders, + asset: "boulder", + water: undefined, + }, + { + kit: "tundra", + addWorld: addTundra, + Collider: TundraColliders, + asset: "boulder", + water: { lakes: [], rivers: [] }, + }, + { + kit: "coastal", + addWorld: addCoastal, + Collider: CoastalColliders, + asset: "fir", + water: { lakes: [{ id: "ocean", radius: 512, level: 1.5 }], rivers: [] }, + }, +]; +describe.each(kits)("$kit runtime", ({ kit, addWorld, Collider, asset, water }) => { + type Ctx = ICtx, IPhysicsContext>; + const plugins: ReturnType[] = []; + const lifetimes: Array<() => void> = []; + const worlds: Array = []; + const manifest: IWorldPackage = { + version: 1, + cellSize: 64, + extent: { minX: -128, minZ: -128, sizeX: 256, sizeZ: 256 }, + assets: { [asset]: { glb: `${asset}.glb`, bounds: { min: [-1, 0, -1], max: [1, 1, 1] } } }, + cells: [{ x: 0, z: 0, runs: [{ asset, offset: 0, count: 2 }] }], + placements: "placements.bin", + terrain: { + heightmap: "heightmap.u16", + columns: 3, + rows: 3, + spacing: 128, + heightMin: 0, + heightMax: 10, }, - entities: { - add: (_id: string, entity: { dispose?: () => void }) => { - if (entity.dispose) lifetimes.push(entity.dispose); - return entity; + }; + // Two boulders, eight floats each: x, y, z, three unused, a scale in the last slot. One sits at the + // origin and one 130 m east. + const records = new Float32Array([0, 0, 0, 0, 0, 0, 1, 1, 130, 0, 0, 0, 0, 0, 1, 1]); + const heightmap = new Uint16Array([0, 2000, 4000, 6000, 8000, 10000, 12000, 14000, 16000]); + let requests: string[]; + let surface: MeshBasicMaterial; + let sky: Texture; + + async function context() { + const scene = new Group(); + const model = new Group(); + const ctx = { + scene, + assets: { resolve: async (path: string) => [path], model: async () => ({ scene: model }) }, + add: (object: T): T => { + scene.add(object); + return object; }, - }, - } as unknown as Ctx; - const plugin = rapier(); - await plugin.setup?.(ctx); - plugins.push(plugin); - return { ctx, plugin, model }; -} + entities: { + add: (_id: string, entity: { dispose?: () => void }) => { + if (entity.dispose) lifetimes.push(entity.dispose); + return entity; + }, + }, + } as unknown as Ctx; + const plugin = rapier(); + await plugin.setup?.(ctx); + plugins.push(plugin); + return { ctx, plugin, model }; + } -beforeEach(() => { - requests = []; - surface = new MeshBasicMaterial(); - sky = new Texture(); - vi.spyOn(surface, "dispose"); - vi.spyOn(sky, "dispose"); - loads.surface.mockReset().mockResolvedValue(surface); - loads.sky.mockReset().mockResolvedValue(sky); - loads.world.mockReset().mockImplementation(async (options: IWorldCellsLoadOptions) => { - if (!options.data) { - // The real WorldCells API reads these itself unless the consumer supplies decoded data. - await (await fetch(options.url)).json(); - await (await fetch("terrain/alpine/placements.bin")).arrayBuffer(); - await (await fetch("terrain/alpine/heightmap.u16")).arrayBuffer(); - } - const world = Object.assign(new Group(), { - dispose: vi.fn(function (this: Group) { - this.removeFromParent(); - }), + beforeEach(() => { + requests = []; + surface = new MeshBasicMaterial(); + sky = new Texture(); + vi.spyOn(surface, "dispose"); + vi.spyOn(sky, "dispose"); + loads.surface.mockReset().mockResolvedValue(surface); + loads.sky.mockReset().mockResolvedValue(sky); + loads.world.mockReset().mockImplementation(async (options: IWorldCellsLoadOptions) => { + if (!options.data) { + // The real WorldCells API reads these itself unless the consumer supplies decoded data. + await (await fetch(options.url)).json(); + await (await fetch(`terrain/${kit}/placements.bin`)).arrayBuffer(); + await (await fetch(`terrain/${kit}/heightmap.u16`)).arrayBuffer(); + } + const world = Object.assign(new Group(), { + dispose: vi.fn(function (this: Group) { + this.removeFromParent(); + }), + }); + worlds.push(world); + return world; + }); + vi.stubGlobal("fetch", async (input: string) => { + requests.push(input); + if (input.endsWith("world.json")) return new Response(JSON.stringify(manifest)); + if (input.endsWith("placements.bin")) return new Response(records.slice().buffer); + if (input.endsWith("heightmap.u16")) return new Response(heightmap.slice().buffer); + if (input.endsWith("water.json")) return new Response(JSON.stringify(water)); + throw new Error(`Unexpected ${kit} request: ${input}`); }); - worlds.push(world); - return world; - }); - vi.stubGlobal("fetch", async (input: string) => { - requests.push(input); - if (input.endsWith("world.json")) return new Response(JSON.stringify(manifest)); - if (input.endsWith("placements.bin")) return new Response(records.slice().buffer); - if (input.endsWith("heightmap.u16")) return new Response(heightmap.slice().buffer); - throw new Error(`Unexpected alpine request: ${input}`); }); -}); -afterEach(() => { - for (const dispose of lifetimes.splice(0)) dispose(); - for (const world of worlds.splice(0)) world.dispose(); - for (const plugin of plugins.splice(0)) plugin.dispose?.({} as Ctx); - vi.unstubAllGlobals(); - vi.restoreAllMocks(); -}); + afterEach(() => { + for (const dispose of lifetimes.splice(0)) dispose(); + for (const world of worlds.splice(0)) world.dispose(); + for (const plugin of plugins.splice(0)) plugin.dispose?.({} as Ctx); + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); -describe("alpine runtime", () => { it("streams boulder colliders when only the follow object's parent moves", async () => { const { ctx, plugin } = await context(); const parent = new Object3D(); const follow = new Object3D(); parent.add(follow); - const colliders = new PropColliders(ctx, follow, manifest, records); + const colliders = new Collider(ctx, follow, manifest, records); parent.position.x = 130; colliders.process(); plugin.update?.(ctx, 1 / 60); @@ -130,9 +168,10 @@ describe("alpine runtime", () => { it("adds the world, its ground and the boulder colliders near the follow object", async () => { const { ctx } = await context(); - const alpine = await addAlpine(ctx, new Object3D()); - expect(requests).toContain("terrain/alpine/world.json"); - expect(requests).not.toContain("terrain/alpine/water.json"); + const alpine = await addWorld(ctx, new Object3D()); + expect(requests).toContain(`terrain/${kit}/world.json`); + if (water) expect(requests).toContain(`terrain/${kit}/water.json`); + else expect(requests).not.toContain(`terrain/${kit}/water.json`); expect(alpine.colliders.active).toBe(1); expect(alpine.ground.shape.descriptor.kind).toBe("heightfield"); expect(ctx.scene.children).toHaveLength(3); @@ -140,7 +179,7 @@ describe("alpine runtime", () => { it("loads each world file once and hands the buffers to the streamer and the ground", async () => { const { ctx } = await context(); - const alpine = await addAlpine(ctx, new Object3D()); + const alpine = await addWorld(ctx, new Object3D()); for (const name of ["world.json", "placements.bin", "heightmap.u16"]) expect( requests.filter((path) => path.endsWith(name)), @@ -167,7 +206,7 @@ describe("alpine runtime", () => { current = false; return world; }); - await expect(addAlpine(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ + await expect(addWorld(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ name: "AbortError", }); expect(ctx.scene.children).toHaveLength(0); @@ -183,7 +222,7 @@ describe("alpine runtime", () => { current = false; return surface; }); - await expect(addAlpine(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ + await expect(addWorld(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ name: "AbortError", }); expect(loads.sky).not.toHaveBeenCalled(); @@ -207,7 +246,7 @@ describe("alpine runtime", () => { } return response; }); - await expect(addAlpine(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ + await expect(addWorld(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ name: "AbortError", }); expect(loads.surface).not.toHaveBeenCalled(); @@ -230,7 +269,7 @@ describe("alpine runtime", () => { current = false; return { scene: new Group() }; }); - await expect(addAlpine(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ + await expect(addWorld(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ name: "AbortError", }); expect(loads.world).not.toHaveBeenCalled(); @@ -243,9 +282,9 @@ describe("alpine runtime", () => { it("releases acquired lighting when a model fails before world creation", async () => { const { ctx } = await context(); ctx.assets.model = async () => { - throw new Error("missing boulder.glb"); + throw new Error(`missing ${asset}.glb`); }; - await expect(addAlpine(ctx, new Object3D())).rejects.toThrow("missing boulder.glb"); + await expect(addWorld(ctx, new Object3D())).rejects.toThrow(`missing ${asset}.glb`); expect(sky.dispose).toHaveBeenCalledOnce(); expect(surface.dispose).toHaveBeenCalledOnce(); expect(ctx.scene.children).toHaveLength(0); @@ -254,7 +293,7 @@ describe("alpine runtime", () => { it("releases the ground, props and owned lighting at the scene lifetime boundary", async () => { const { ctx, plugin } = await context(); const follow = new Object3D(); - const alpine = await addAlpine(ctx, follow); + const alpine = await addWorld(ctx, follow); expect(alpine.colliders.active).toBe(1); for (const dispose of lifetimes.splice(0)) dispose(); plugin.update?.(ctx, 1 / 60); @@ -281,7 +320,7 @@ describe("alpine runtime", () => { const mesh = new Mesh(undefined, material); model.add(mesh); vi.spyOn(material, "dispose"); - await addAlpine(ctx, new Object3D()); + await addWorld(ctx, new Object3D()); expect(material.envMap).toBe(sky); for (const dispose of lifetimes.splice(0)) dispose(); expect(material.envMap).toBe(priorSky); @@ -300,7 +339,7 @@ describe("alpine runtime", () => { const poses = records.slice(); poses[0] = 100; poses[8] = 161; - const colliders = new PropColliders(ctx, follow, manifest, poses); + const colliders = new Collider(ctx, follow, manifest, poses); expect(colliders.active).toBe(1); parent.position.x = 102; colliders.process(); @@ -321,7 +360,7 @@ describe("alpine runtime", () => { }); const poses = records.slice(); poses[8] = 20; - expect(() => new PropColliders(ctx, new Object3D(), manifest, poses)).toThrow( + expect(() => new Collider(ctx, new Object3D(), manifest, poses)).toThrow( "collider allocation failed", ); plugin.update?.(ctx, 1 / 60); diff --git a/packages/terrain/__tests__/placement-overrides.spec.ts b/packages/terrain/__tests__/placement-overrides.spec.ts index 8e1e1c9bc4..7299943cc3 100644 --- a/packages/terrain/__tests__/placement-overrides.spec.ts +++ b/packages/terrain/__tests__/placement-overrides.spec.ts @@ -20,6 +20,7 @@ const transform = { function fixture() { const terrain = new Terrain({ size: 64, resolution: 17, seed: 73 }).scatter({ id: "trees", + asset: "pine", count: 40, minDistance: 0, avoidWater: false, diff --git a/packages/terrain/__tests__/prop-surfaces.spec.ts b/packages/terrain/__tests__/prop-surfaces.spec.ts deleted file mode 100644 index 8ce14ed4cc..0000000000 --- a/packages/terrain/__tests__/prop-surfaces.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { IAssetLoader } from "@threenative/core"; -import { DataTexture, type Texture } from "three"; -import type { MeshStandardNodeMaterial } from "three/webgpu"; -import { describe, expect, it } from "vitest"; -import { createPropSurfaces } from "../../../examples/strata-terrain-preview/src/render/propMaterials.js"; - -/** Every texture a TSL node tree samples, so a test can see whether a map survived. */ -function sampledTextures(node: unknown): Set { - const found = new Set(); - (node as { traverse?: (visit: (value: unknown) => void) => void } | null)?.traverse?.((value) => { - const texture = (value as { value?: Texture }).value; - if (texture?.isTexture) found.add(texture); - }); - return found; -} - -describe("procedural foliage lighting", () => { - const image = (path: string) => { - const texture = new DataTexture(new Uint8Array([80, 150, 50, 255]), 1, 1); - texture.userData.path = path; - return texture; - }; - const sky = image("sky.hdr"); - const assets = { - resolve: async () => [], - model: async () => { - throw new Error("Optional model absent"); - }, - texture: async (path: string) => image(path), - } as unknown as IAssetLoader; - // Every role the procedural starter draws as an alpha-cut, double-sided surface. An imported pine - // brings its own material; a machine without the licensed pack draws these, and without an - // image-based light a needle card is a flat paddle — the paperboard the engine warns about. - const cutouts = ["crown", "needles", "pine", "impostor", "fern", "petal"] as const; - - it("gives every cutout surface the sky light", async () => { - const surfaces = await createPropSurfaces(assets, undefined, undefined, sky); - try { - for (const role of cutouts) { - const material = surfaces.materials[role] as { envMap?: unknown; envMapIntensity?: number }; - expect(material.envMap, role).toBe(sky); - expect(material.envMapIntensity, role).toBeCloseTo(1.13, 5); - } - } finally { - surfaces.dispose(); - } - }); - - it("keeps the crown's needle relief in its normal", async () => { - const surfaces = await createPropSurfaces(assets); - try { - const crown = surfaces.materials.crown as MeshStandardNodeMaterial; - // The bent crown normal is the geometry's own; the relief bump perturbs it, so the two are one - // node. A bare geometry normal drops the relief and the needles shade as flat card. - const paths = [...sampledTextures(crown.normalNode)].map((texture) => texture.userData.path); - expect(paths).toContain("needle-surface.png"); - } finally { - surfaces.dispose(); - } - }); -}); diff --git a/packages/terrain/__tests__/prop-texture-preparation.spec.ts b/packages/terrain/__tests__/prop-texture-preparation.spec.ts deleted file mode 100644 index 5ec1a33bb3..0000000000 --- a/packages/terrain/__tests__/prop-texture-preparation.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { ICtx } from "@threenative/core"; -import { CompressedTexture, MeshStandardMaterial, RGBA_BPTC_Format } from "three"; -import { texture } from "three/tsl"; -import { MeshPhysicalNodeMaterial } from "three/webgpu"; -import { expect, it, vi } from "vitest"; -import { preparePropTextures } from "../../../examples/strata-terrain-preview/src/render/texturePreparation.js"; - -function compressed() { - const value = new CompressedTexture( - [{ width: 4, height: 4, data: new Uint8Array(16) }], - 4, - 4, - RGBA_BPTC_Format, - ); - value.needsUpdate = true; - return value; -} - -it("gates actual ordinary and TSL texture identities without taking their ownership", async () => { - const map = compressed(); - const normal = compressed(); - const nodeMap = compressed(); - const ordinary = new MeshStandardMaterial({ map, normalMap: normal }); - const nodes = new MeshPhysicalNodeMaterial({ map }); - nodes.colorNode = texture(nodeMap).rgb; - const disposed = vi.spyOn(map, "dispose"); - let release = () => {}; - const gate = new Promise((resolve) => { - release = () => resolve(3); - }); - const prepare = vi.fn((_textures: Iterable, _signal?: AbortSignal) => gate); - let completed = false; - const pending = preparePropTextures({ prepareTextures: prepare } as unknown as ICtx["renderer"], [ - ordinary, - nodes, - ordinary, - ]).then(() => { - completed = true; - }); - await Promise.resolve(); - expect(new Set(prepare.mock.calls[0]?.[0])).toEqual(new Set([map, normal, nodeMap])); - expect(completed).toBe(false); - release(); - await pending; - expect(disposed).not.toHaveBeenCalled(); -}); - -it("forwards scene cancellation and preserves preparation failure", async () => { - const controller = new AbortController(); - const prepare = vi.fn((_textures: Iterable, signal: AbortSignal) => { - expect(signal).toBe(controller.signal); - return Promise.reject(new Error("upload failed")); - }); - await expect( - preparePropTextures( - { prepareTextures: prepare } as unknown as ICtx["renderer"], - [new MeshStandardMaterial({ map: compressed() })], - controller.signal, - ), - ).rejects.toThrow("upload failed"); -}); diff --git a/packages/terrain/__tests__/scatter-identity.spec.ts b/packages/terrain/__tests__/scatter-identity.spec.ts index 2d7cd2a652..73049914b3 100644 --- a/packages/terrain/__tests__/scatter-identity.spec.ts +++ b/packages/terrain/__tests__/scatter-identity.spec.ts @@ -5,6 +5,7 @@ describe("public scatter candidate identity", () => { it("retains candidate coordinates, scale and yaw when earlier candidates are rejected", () => { const terrain = new Terrain({ size: 64, resolution: 17, seed: 73 }).scatter({ id: "trees", + asset: "pine", count: 80, minDistance: 0, avoidWater: false, @@ -25,6 +26,7 @@ describe("public scatter candidate identity", () => { it("gives a new seed different candidate identities and preserves keys on round trip", () => { const terrain = new Terrain({ size: 64, resolution: 17 }).scatter({ id: "trees", + asset: "pine", count: 20, seed: 10, minDistance: 0, diff --git a/packages/terrain/__tests__/starter-kit.spec.ts b/packages/terrain/__tests__/starter-kit.spec.ts index bef2396877..df54756f04 100644 --- a/packages/terrain/__tests__/starter-kit.spec.ts +++ b/packages/terrain/__tests__/starter-kit.spec.ts @@ -4,13 +4,71 @@ import { copyFileSync, mkdirSync, readFileSync, readdirSync, statSync, symlinkSy import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { type IWorldPackage, validateWorldPackage } from "@threenative/core/world"; +import { Terrain, decodeRAW16 } from "@threenative/terrain"; import { describe, expect, it } from "vitest"; import { makeTempDirSync } from "../../../test-support/temp-dir.js"; const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); -const kit = join(packageRoot, "starter", "forest"); const starterAssets = join(packageRoot, "starter-assets"); +const GOLDEN_HASHES: Record> = { + forest: { + "world.glb": "0ccea412a03f6e48231835480889063ca3491a59b5a5d5a4b4b73134047edfa2", + "heightmap.u16": "33b3fc66f63ca4df76531a44a2bd84e1aff6f642eb9ab0c6622ebe87d1057c2f", + "placements.bin": "36ab2fd57e76b4b0bd998c5398f983e5f030968b83d5326c0cb98df1b67e4019", + "splat.rgba": "f5e45e3740eea9e751e94570abe785854670d060fda6fe4b5518ccd4a562c7ec", + "world.json": "3ffa8c08ede40639373e4bff8bf3c707c65df72da6f28f98794fb17119051010", + }, + alpine: { + "heightmap.u16": "bd84cb6c52a1142303dfb0f304190a981f29d77d118d3880e3eb246549838ae1", + "placements.bin": "5c47dd59619843bc056fbc8391ca7bccdef86c9950cd192c53a6928676865c26", + "splat.rgba": "fde8bf22c6841847f58ab523682f29e77a774a364cdd0e34c0025b6ad06c6f42", + "world.json": "5af1fc81a424792ac440b9907b2b7e3d36a239936e7921f93a94f7fccdda021c", + }, + coastal: { + "heightmap.u16": "313539bae3010b949b65e55307736f6140a55255d81fbb8ab5dbae782bdc8482", + "placements.bin": "e10721cc99dfe96345a6b090eeb834ec87890927d8272ec8a8307b31a989b098", + "splat.rgba": "b3dcb6967095049a05fa113968b3cd8d74914c7b906f2c522ff109f6dbfe2b40", + "world.json": "dfd2dc68bab5fd95f5a0284f9df8656580f4544c92989e5f31363faefbe730fd", + }, + desert: { + "heightmap.u16": "a17965c4dd706f2732cc64af64dba086e006b405086d41a36f2ca37c45ee3add", + "placements.bin": "8dace58d5c91d50f8492149a826575edbff53d3ee78e5d892bbb12d8f809f3fe", + "splat.rgba": "a77791af98c157f371dfa1fdc053c712ea3bdb8a9f3f71de33a69b8f06c88d04", + "world.json": "755ae7887abfce8837256190cab8930639d8802343c10603202cfa7fe413e481", + }, + tundra: { + "heightmap.u16": "2fcf74db51407bcd4e65b7ae92e31da6ff1b0c7152301952fecedeea36c80710", + "placements.bin": "05aa8931ff15b96d21999947ca2c5fdb89fe2b0e17228d8f072021767b55a9ff", + "splat.rgba": "981bd12869273d4f959fcaed53b27b325b5748b7ec558ac25c4258b4fd5c9f07", + "world.json": "6986a3d5cc54f2f0690fc40ec4482c6f2a88cb7c5b773976d19dc513643adc47", + }, +}; + +/** SHA256 of each kit's inline heights and evaluated height grid at e12a5d578, as Float32 bytes. */ +const BASELINE_HEIGHTS: Record = { + forest: { + values: "46e9bdddaae82a29f770358e51f282486b41d0cab268a25bd58ba885c70a5ed8", + evaluated: "16db2efbc4f44fa016a69f8240ae5bf9ce92f15070ea67d436e575cea02ad55f", + }, + alpine: { + values: "72995391d8244365e32f8d8c38f80c04e6ec948b819918876d1f5d6eeed66c81", + evaluated: "5000eea008516889508c4efc0074bb8a36c41a0c5e0f1484c3c667d60644fc1c", + }, + coastal: { + values: "726595506d7dda30da88401b74c878375bcb40d88cef0debb4a10f27f47a2d17", + evaluated: "020ce3ba6dadb740886941287303c7ab37e53da2143cfcea3072eb197c934feb", + }, + desert: { + values: "b0f471e65576313f26f98e456323c4c6eb6c4e688c931e4cafc2c66663abce85", + evaluated: "86740e21dad3412d21524d1c85e85d5106d05eccc6e66f010ffd8168d1cdda54", + }, + tundra: { + values: "3fbcbc21b23e03a1c89d8ce0ccc2c8b93380973304e4be7662bc23185a706b58", + evaluated: "3c8e91abeacbd4469974513812c1ffc1703064808ba9facd1dbfb63a59fbdc62", + }, +}; + interface ITable { readonly base: { id: string }; readonly layers: readonly { id: string }[]; @@ -24,18 +82,25 @@ interface IKitWater { readonly rivers: readonly { id: string; width: number; points: number[][] }[]; } -/** Copies the kit into a temp directory whose node_modules resolves the workspace package. */ -function kitInTempDir(): string { - const dir = makeTempDirSync("terrain-starter-kit-"); - for (const name of ["bake.mjs", "recipe.json", "assets.json", "surface.json"]) +/** Copies the named kit into a temp directory whose node_modules resolves the workspace packages. */ +function kitInTempDir(kitName = "forest"): string { + const dir = makeTempDirSync(`terrain-starter-kit-${kitName}-`); + const kit = join(packageRoot, "starter", kitName); + for (const name of ["bake.mjs", "recipe.json", "assets.json", "surface.json", "heightmap.r16"]) copyFileSync(join(kit, name), join(dir, name)); mkdirSync(join(dir, "node_modules", "@threenative"), { recursive: true }); symlinkSync(packageRoot, join(dir, "node_modules", "@threenative", "terrain"), "dir"); + // forest/bake.mjs imports the cook from @threenative/assets, so the fixture links this checkout's. + symlinkSync( + join(packageRoot, "..", "assets"), + join(dir, "node_modules", "@threenative", "assets"), + "dir", + ); return dir; } -function bake(out: string): { files: Map; total: number } { - const dir = kitInTempDir(); +function bake(out: string, kitName = "forest"): { files: Map; total: number } { + const dir = kitInTempDir(kitName); execFileSync(process.execPath, ["bake.mjs", "--assets", starterAssets, "--out", out], { cwd: dir, stdio: "pipe", @@ -72,13 +137,70 @@ describe("forest starter kit", () => { expect(perAsset.fir).toBeGreaterThanOrEqual(2000); expect(perAsset.boulder).toBe(320); expect(Object.keys(perAsset).sort()).toEqual(["boulder", "fir", "fir-c"]); - expect(total).toBeLessThanOrEqual(25 * 1024 * 1024); + // The combined and streamed representations are alternatives, not additive. + const combinedBytes = statSync(join(out, "world.glb")).size; + expect(combinedBytes).toBeLessThanOrEqual(25 * 1024 * 1024); + expect(total - combinedBytes).toBeLessThanOrEqual(25 * 1024 * 1024); + + // Pinned golden hashes for starter-kit forest bake (PRD-592 AC-1) + expect(files.get("heightmap.u16")).toBe( + "33b3fc66f63ca4df76531a44a2bd84e1aff6f642eb9ab0c6622ebe87d1057c2f", + ); + expect(files.get("placements.bin")).toBe( + "36ab2fd57e76b4b0bd998c5398f983e5f030968b83d5326c0cb98df1b67e4019", + ); + expect(files.get("splat.rgba")).toBe( + "f5e45e3740eea9e751e94570abe785854670d060fda6fe4b5518ccd4a562c7ec", + ); + expect(files.get("world.json")).toBe( + "3ffa8c08ede40639373e4bff8bf3c707c65df72da6f28f98794fb17119051010", + ); // One recipe and seed, one package: a second bake is byte-identical. const second = bake(join(makeTempDirSync("terrain-starter-world-"), "world")); expect([...second.files].sort()).toEqual([...files].sort()); }); + it.each(["forest", "alpine", "coastal", "desert", "tundra"] as const)( + "produces pinned golden baseline hashes for %s kit bake (PRD-592 AC-1)", + (kitName) => { + const out = join(makeTempDirSync(`terrain-starter-world-${kitName}-`), "world"); + const { files } = bake(out, kitName); + const expected = GOLDEN_HASHES[kitName] ?? {}; + expect.assertions(Object.keys(expected).length); + for (const [file, hash] of Object.entries(expected)) { + expect(files.get(file), `${kitName} ${file} hash mismatch`).toBe(hash); + } + }, + ); + + it.each(["forest", "alpine", "coastal", "desert", "tundra"] as const)( + "hydrates the %s RAW16 sidecar to the original inline heights, bit for bit (PRD-589 AC-2)", + (kitName) => { + const kit = join(packageRoot, "starter", kitName); + expect(statSync(join(kit, "recipe.json")).size).toBeLessThan(200_000); + const current = JSON.parse(readFileSync(join(kit, "recipe.json"), "utf8")); + const data = current.recipe.layers[0].params.data; + // Decode the sidecar exactly as bake.mjs does, from the path the recipe names. + const sidecar = readFileSync(join(kit, data.raw16)); + expect(sidecar.byteLength).toBe(132098); + // The same rounding as the bake loader: each decoded float returns to the 0.1 m lattice. + data.values = Array.from( + decodeRAW16(sidecar, data).values, + (value) => Math.round(value * 10) / 10, + ); + + // Equal Float32 bytes mean no height moved, not even by one unit in the last place. + const digest = (heights: ArrayLike) => + createHash("sha256") + .update(new Uint8Array(Float32Array.from(heights).buffer)) + .digest("hex"); + const pinned = BASELINE_HEIGHTS[kitName]; + expect(digest(data.values)).toBe(pinned?.values); + expect(digest(Terrain.fromJSON(current.recipe).evaluate().height)).toBe(pinned?.evaluated); + }, + ); + it("writes every model and texture its manifest and table name", () => { const out = join(makeTempDirSync("terrain-starter-world-"), "world"); bake(out); @@ -131,14 +253,14 @@ describe("forest starter kit", () => { expect(water.rivers[0]?.width).toBeGreaterThan(0); }); - it("ships the kit and its shared starter assets in the package", () => { + it("ships kit source and pinned asset metadata without binaries or generated worlds", () => { const packed = JSON.parse( execFileSync("npm", ["pack", "--dry-run", "--json"], { cwd: packageRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], }), - ) as { files: { path: string; size: number }[] }[]; + ) as { size: number; files: { path: string; size: number }[] }[]; const packedFiles = packed[0]?.files ?? []; const files = new Map(packedFiles.map((file) => [file.path, file.size])); for (const path of [ @@ -175,16 +297,46 @@ describe("forest starter kit", () => { "starter/coastal/world.ts", "starter/coastal/sky.ts", "starter/coastal/sea.ts", - "starter-assets/fir_tree_01/fir-b-lod2.glb", + "starter-assets.json", + "starter-credits.json", + "THIRD_PARTY_NOTICES.md", + ...["forest", "alpine", "desert", "tundra", "coastal"].map( + (kit) => `starter/${kit}/heightmap.r16`, + ), ]) expect(files.has(path), `packed tarball is missing ${path}`).toBe(true); - const kitBytes = packedFiles - .filter( - (file) => - file.path.startsWith("starter/forest/") || file.path.startsWith("starter-assets/"), - ) - .reduce((total, file) => total + file.size, 0); - // The 25 MiB budget is per baked world (asserted above); this only catches package bloat. - expect(kitBytes).toBeLessThanOrEqual(40 * 1024 * 1024); + expect(packedFiles.some((file) => file.path.startsWith("starter-assets/"))).toBe(false); + expect(packedFiles.some((file) => /^starter\/[^/]+\/world\//u.test(file.path))).toBe(false); + expect(packed[0]?.size).toBeLessThan(2_000_000); + const archive = JSON.parse(readFileSync(join(packageRoot, "starter-assets.json"), "utf8")); + expect(archive.bytes).toBeGreaterThan(0); + expect(archive.sha256).toMatch(/^[a-f0-9]{64}$/u); + console.log(`starter asset archive: ${archive.bytes} B; ${archive.sha256}`); + expect(readFileSync(join(packageRoot, "starter-credits.json"))).toEqual( + readFileSync(join(starterAssets, "credits.json")), + ); }); + + it.each(["forest", "alpine", "coastal", "desert", "tundra"] as const)( + "refuses a bare, empty or flag-valued --out/--assets before writing (%s)", + (kitName) => { + const dir = makeTempDirSync(`terrain-starter-flags-${kitName}-`); + const bakeWith = (...args: string[]) => + execFileSync( + process.execPath, + [join(packageRoot, "starter", kitName, "bake.mjs"), ...args], + { cwd: dir, stdio: "pipe" }, + ); + + // A bare flag, an empty value and a following flag are all refused, by name. + expect(() => bakeWith("--out")).toThrow(/--out needs a nonempty/); + expect(() => bakeWith("--out", "")).toThrow(/--out needs a nonempty/); + expect(() => bakeWith("--out", "--assets", starterAssets)).toThrow(/--out needs a nonempty/); + expect(() => bakeWith("--assets")).toThrow(/--assets needs a nonempty/); + expect(() => bakeWith("--assets", "")).toThrow(/--assets needs a nonempty/); + expect(() => bakeWith("--assets", "--out", dir)).toThrow(/--assets needs a nonempty/); + // Nothing was written into the working directory. + expect(readdirSync(dir)).toEqual([]); + }, + ); }); diff --git a/packages/terrain/__tests__/strata-loading.spec.ts b/packages/terrain/__tests__/strata-loading.spec.ts deleted file mode 100644 index d80b378315..0000000000 --- a/packages/terrain/__tests__/strata-loading.spec.ts +++ /dev/null @@ -1,466 +0,0 @@ -import { readFileSync } from "node:fs"; -import { runInNewContext } from "node:vm"; -import { - type CanvasTexture, - type Mesh, - type MeshBasicMaterial, - OrthographicCamera, - PerspectiveCamera, - Scene, -} from "three"; -import ts from "typescript"; -import { expect, it, vi } from "vitest"; -import { - createLoadingScreen, - createSpawnReadiness, - spawnReadinessSnapshot, -} from "../../../examples/strata-terrain-preview/src/render/loading.js"; - -function host(ready: Promise) { - return { - camera: new PerspectiveCamera(), - scene: new Scene(), - renderer: { compileAsync: vi.fn(async () => undefined) }, - canvasLayer: { - scene: new Scene(), - camera: new OrthographicCamera(-400, 400, 300, -300, 0, 2), - opaque: false, - keepWorldRendering: false, - renderWorldDuringStartup: true, - }, - startup: { progress: 0.5, whenReady: () => ready }, - }; -} - -it.each(["attached", "cancelled", "released"] as const)( - "defers hidden world draws until prepared streams attach: %s", - async (outcome) => { - const source = ts.createSourceFile( - "game.ts", - readFileSync( - new URL("../../../examples/strata-terrain-preview/src/game.ts", import.meta.url), - "utf8", - ), - ts.ScriptTarget.Latest, - true, - ); - let declaration = ""; - function visit(node: ts.Node): void { - if (ts.isVariableDeclaration(node) && node.name.getText(source) === "buildProps") - declaration = node.getText(source); - ts.forEachChild(node, visit); - } - visit(source); - if (!declaration) throw new Error("Missing production Strata preparation controller"); - const controllerHost = host(new Promise(() => undefined)); - controllerHost.canvasLayer.opaque = true; - controllerHost.canvasLayer.renderWorldDuringStartup = false; - const world = new Scene(); - const attachments: boolean[] = []; - let entered: () => void = () => undefined; - const preparing = new Promise((resolve) => { - entered = resolve; - }); - let finish: () => void = () => undefined; - const prepared = new Promise((resolve) => { - finish = resolve; - }); - let disposed = false; - const parts = new Map(); - const scope = { - exports: {} as { Controller?: new () => { run(): Promise } }, - ctx: { - ...controllerHost, - assets: undefined, - state: { getState: () => ({}) }, - entities: { add: () => undefined }, - add: () => { - attachments.push(controllerHost.canvasLayer.keepWorldRendering); - }, - }, - released: false, - preparedDispose: undefined, - preparedLodBaseSpread: 0, - preparedLevelsWithoutSolid: 0, - surfacesDispose: undefined, - propsStage: "", - completedPropsWork: 0, - preparation: undefined, - props: undefined, - propParts: parts, - world: "forest", - data: { size: 512 }, - biome: {}, - fallbackSaplingHeight: undefined, - scatter: { placements: [], counts: {} }, - groundAt: () => ({ height: 0, offset: 0 }), - textureUploads: { signal: new AbortController().signal }, - PerspectiveCamera, - OrthographicCamera, - loadPreparedProps: async () => ({ parts, dispose() {} }), - loadPack: async () => ({ parts, dispose() {} }), - buildPropVariants: () => parts, - createPropSurfaces: async () => ({ materials: {}, dispose() {} }), - preparePropTextures: async ( - _renderer: unknown, - _materials: unknown, - _signal: unknown, - progress: (completed: number) => void, - ) => { - progress(1); - progress(2); - }, - createStreamedProps: async (options: { onProgress: (progress: object) => void }) => { - for (const [phase, added] of [ - ["grounding", 0], - ["grounding", 50], - ["grounding", 50], - ["models", 0], - ["models", 3], - ] as const) - options.onProgress({ phase, bucket: "trees", added, total: 100 }); - entered(); - await prepared; - return outcome === "cancelled" - ? undefined - : { - worlds: [world], - byId: new Map(), - meshes: [], - dispose: () => { - disposed = true; - }, - }; - }, - }; - runInNewContext( - ts.transpileModule( - `export class Controller { #surfaces; async run() { const ${declaration}; await buildProps(); } }`, - { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }, - ).outputText, - scope, - ); - if (!scope.exports.Controller) - throw new Error("Production preparation controller did not load"); - const work = new scope.exports.Controller().run(); - await preparing; - try { - expect(scope.completedPropsWork).toBe(55); - expect(controllerHost.canvasLayer.keepWorldRendering).toBe(false); - expect(attachments).toEqual([]); - } finally { - scope.released = outcome === "released"; - finish(); - await work; - } - expect(attachments).toEqual(outcome === "attached" ? [false] : []); - expect(controllerHost.canvasLayer.keepWorldRendering).toBe(outcome === "attached"); - expect(disposed).toBe(outcome === "released"); - }, -); - -it.each(["世界", "\u0000", "\uD800"])( - "bounds failure receipts for %j without retaining scene objects or resident key arrays", - (text) => { - const stats = { - residentCells: 16, - loadedCells: 12, - loadsInFlight: 1, - loadsQueued: 2, - pendingPrewarm: 3, - prewarmMinted: 4, - failures: 0, - admission: { spentMs: 1.5, deferred: 2, backlog: 5 }, - residentKeys: ["must-not-copy"], - }; - const stream = { - uuid: "generation".repeat(100), - name: text.repeat(1_000), - parent: { uuid: "scene".repeat(100) }, - released: false, - stats: () => stats, - }; - const receipt = spawnReadinessSnapshot({ - reason: text.repeat(10_000), - atMs: 120_000, - world: "forest", - sceneUuid: "scene-generation", - released: false, - gate: { ready: false, error: "" }, - stage: "streaming", - propsSettled: false, - preparation: { - phase: "world-load", - bucket: text.repeat(1_000), - added: 0, - total: 213_968, - prewarmedWorlds: 3, - worlds: Array.from({ length: 20 }, () => stream) as never, - }, - assets: { - requested: 323, - settled: 300, - requestedBytes: 500, - settledBytes: 450, - pending: Array.from({ length: 20 }, () => text.repeat(10_000)), - }, - startup: { - phase: "collapsing", - progress: 0.95, - compileSettled: true, - timeline: { enteredMs: 10, compileSettledMs: 50 }, - }, - coverage: { required: 16, loaded: 12, failures: 0, ready: false }, - }); - const encoded = JSON.stringify(receipt); - expect(Buffer.byteLength(encoded)).toBeLessThan(16_384); - expect(receipt.preparation.worldCount).toBe(20); - expect(receipt.preparation.prewarmedWorlds).toBe(3); - expect(receipt.coverage).toEqual({ required: 16, loaded: 12, failures: 0, ready: false }); - expect(receipt.worlds).toHaveLength(4); - expect(receipt.assets.pendingCount).toBe(20); - expect(receipt.assets.pending).toHaveLength(4); - expect(encoded).not.toContain("must-not-copy"); - stats.pendingPrewarm = 0; - stream.released = true; - expect(receipt.worlds[0]?.pendingPrewarm).toBe(3); - expect(receipt.worlds[0]?.released).toBe(false); - }, -); - -it("uploads the status canvas only when its displayed text changes", async () => { - const drawn: string[] = []; - const canvas = { - width: 0, - height: 0, - getContext: () => ({ - clearRect: () => undefined, - fillText: (text: string) => drawn.push(text), - }), - }; - vi.stubGlobal("document", { createElement: () => canvas }); - let reject: (reason: Error) => void = () => undefined; - const source = host( - new Promise((_resolve, fail) => { - reject = fail; - }), - ); - const screen = createLoadingScreen(source); - try { - const status = source.canvasLayer.scene.children[3] as Mesh; - const texture = status.material.map as CanvasTexture; - source.startup.progress = 0.95; - screen.update(); - const version = texture.version; - for (let frame = 0; frame < 100; frame += 1) screen.update(); - source.startup.progress = 0.951; - screen.update(); - expect(texture.version).toBe(version); - expect(drawn).toEqual(["0%", "95%"]); - source.startup.progress = 0.96; - screen.update(); - expect(texture.version).toBe(version + 1); - expect(drawn).toEqual(["0%", "95%", "96%"]); - reject(new Error("Forest spawn: missing cell")); - await Promise.resolve(); - screen.update(); - expect(texture.version).toBe(version + 2); - expect(drawn.at(-1)).toBe("Forest spawn: missing cell"); - expect(source.canvasLayer.opaque).toBe(true); - } finally { - screen.finish(); - vi.unstubAllGlobals(); - } -}); - -it.each([false, true])( - "keeps hidden world draws disabled during admission and restores previous flag %s", - (previous) => { - const source = host(new Promise(() => undefined)); - source.canvasLayer.keepWorldRendering = previous; - source.canvasLayer.renderWorldDuringStartup = !previous; - const screen = createLoadingScreen({ ...source, keepWorldRendering: false }); - try { - expect(source.canvasLayer.opaque).toBe(true); - expect(source.canvasLayer.keepWorldRendering).toBe(false); - expect(source.canvasLayer.renderWorldDuringStartup).toBe(false); - screen.update(); - expect(source.canvasLayer.keepWorldRendering).toBe(false); - } finally { - screen.finish(); - } - expect(source.canvasLayer.opaque).toBe(false); - expect(source.canvasLayer.keepWorldRendering).toBe(previous); - expect(source.canvasLayer.renderWorldDuringStartup).toBe(!previous); - expect(source.canvasLayer.scene.children).toHaveLength(0); - }, -); - -it("keeps the curtain and names a failed spawn even after framework readiness fails open", async () => { - let reject: (reason: Error) => void = () => undefined; - const source = host( - new Promise((_resolve, fail) => { - reject = fail; - }), - ); - const screen = createLoadingScreen(source); - reject(new Error("Spawn terrain: missing tile 0:0")); - await Promise.resolve(); - await Promise.resolve(); - screen.update(); - expect(source.canvasLayer.opaque).toBe(true); - expect(source.canvasLayer.keepWorldRendering).toBe(false); - const backdrop = source.canvasLayer.scene.children[0] as Mesh; - expect(backdrop.userData.loadingError).toBe("Spawn terrain: missing tile 0:0"); - const fill = source.canvasLayer.scene.children[2] as Mesh; - expect((fill.material as MeshBasicMaterial).color.getHex()).toBe(0xe36b5c); - screen.finish(); - expect(source.canvasLayer.scene.children).toHaveLength(0); -}); - -it("restores both prior draw flags when opted-out texture preparation rejects", async () => { - let reject: (reason: Error) => void = () => undefined; - const source = host( - new Promise((_done, fail) => { - reject = fail; - }), - ); - source.canvasLayer.keepWorldRendering = true; - const screen = createLoadingScreen({ ...source, keepWorldRendering: false }); - try { - expect(source.canvasLayer.keepWorldRendering).toBe(false); - expect(source.canvasLayer.renderWorldDuringStartup).toBe(false); - reject(new Error("Texture preparation failed")); - await Promise.resolve(); - await Promise.resolve(); - expect(source.canvasLayer.opaque).toBe(true); - expect(source.canvasLayer.keepWorldRendering).toBe(true); - expect(source.canvasLayer.renderWorldDuringStartup).toBe(true); - } finally { - screen.finish(); - } -}); - -it("releases its layer only after the combined spawn and startup promise resolves", async () => { - let resolve: () => void = () => undefined; - const source = host( - new Promise((done) => { - resolve = done; - }), - ); - const screen = createLoadingScreen(source); - screen.update(); - expect(source.canvasLayer.opaque).toBe(true); - resolve(); - await Promise.resolve(); - await Promise.resolve(); - expect(source.canvasLayer.opaque).toBe(false); - expect(source.canvasLayer.scene.children).toHaveLength(0); -}); - -it("latches playable coverage so later spawn eviction never disables controls", async () => { - const spawn = createSpawnReadiness("Strata spawn"); - expect(spawn.ready).toBe(false); - spawn.observe(false); - expect(spawn.ready).toBe(false); - spawn.observe(true); - await spawn.promise; - spawn.observe(false, 1); - expect(spawn.ready).toBe(true); - expect(spawn.error).toBe(""); - spawn.cancel(); -}); - -it("rejects a timed out spawn and never treats later coverage as playable", async () => { - vi.useFakeTimers(); - const spawn = createSpawnReadiness("Forest spawn", 100); - const rejection = expect(spawn.promise).rejects.toThrow("Forest spawn admission exceeded 100 ms"); - await vi.advanceTimersByTimeAsync(100); - await rejection; - spawn.observe(true); - expect(spawn.ready).toBe(false); - expect(spawn.error).toContain("exceeded"); - spawn.cancel(); - vi.useRealTimers(); -}); - -it("captures the pending gate before timeout latches or rejects, once per failure", async () => { - vi.useFakeTimers(); - const events: string[] = []; - const spawn = createSpawnReadiness("Forest spawn", 100, (reason) => { - events.push(`capture:${spawn.ready}:${spawn.error}:${reason}`); - }); - const rejection = spawn.promise.catch((error: Error) => events.push(`rejected:${error.message}`)); - try { - await vi.advanceTimersByTimeAsync(100); - await rejection; - spawn.observe(true); - spawn.fail("late failure"); - spawn.cancel(); - expect(events).toEqual([ - "capture:false::Forest spawn admission exceeded 100 ms", - "rejected:Forest spawn admission exceeded 100 ms", - ]); - expect(spawn.ready).toBe(false); - expect(vi.getTimerCount()).toBe(0); - } finally { - spawn.cancel(); - vi.useRealTimers(); - } -}); - -it("keeps the original admission failure when its diagnostic capture throws", async () => { - vi.useFakeTimers(); - const capture = vi.fn(() => { - throw new Error("diagnostic observer failed"); - }); - const spawn = createSpawnReadiness("Forest spawn", 100, capture); - const rejection = expect(spawn.promise).rejects.toThrow("Forest spawn assets failed to load"); - try { - spawn.observe(false, 1); - await rejection; - expect(capture).toHaveBeenCalledOnce(); - expect(spawn.error).toBe("Forest spawn assets failed to load"); - expect(vi.getTimerCount()).toBe(0); - } finally { - spawn.cancel(); - vi.useRealTimers(); - } -}); - -it.each(["observe", "cancel"])( - "keeps capture reentry through %s from changing failure settlement", - async (action) => { - const spawn = createSpawnReadiness("Controlled spawn", 100, () => { - if (action === "observe") spawn.observe(true); - else spawn.cancel(); - }); - const rejection = expect(spawn.promise).rejects.toThrow("original admission failure"); - spawn.fail("original admission failure"); - await rejection; - expect(spawn.ready).toBe(false); - expect(spawn.error).toBe("original admission failure"); - spawn.cancel(); - }, -); - -it("cancels a pending scene without leaving its readiness promise or timer behind", async () => { - vi.useFakeTimers(); - const spawn = createSpawnReadiness("Forest spawn"); - const rejection = expect(spawn.promise).rejects.toThrow("Forest spawn cancelled"); - spawn.cancel(); - await rejection; - expect(vi.getTimerCount()).toBe(0); - expect(spawn.ready).toBe(false); - vi.useRealTimers(); -}); - -it("keeps even an empty-message rejection failed after late coverage", async () => { - const spawn = createSpawnReadiness("Strata spawn"); - const rejection = expect(spawn.promise).rejects.toThrow("Strata spawn failed"); - spawn.fail(new Error()); - await rejection; - spawn.observe(true); - expect(spawn.ready).toBe(false); - spawn.cancel(); -}); diff --git a/packages/terrain/__tests__/terrain-consumer.spec.ts b/packages/terrain/__tests__/terrain-consumer.spec.ts index e1604267c0..8f4613c52f 100644 --- a/packages/terrain/__tests__/terrain-consumer.spec.ts +++ b/packages/terrain/__tests__/terrain-consumer.spec.ts @@ -1,7 +1,9 @@ import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; import { Mask, Terrain, + TerrainEvaluator, bakeMesh, bakeTerrain, decodeRAW16, @@ -12,6 +14,7 @@ import { import { toGeometry } from "@threenative/terrain/three"; import { BufferGeometry, Mesh, MeshBasicMaterial, Raycaster, Vector3 } from "three"; import { describe, expect, it } from "vitest"; +import { finiteTree } from "../src/core/validation.js"; describe("public terrain consumer", () => { it("returns measured erosion transport, respects masks and isolates cached arrays", () => { @@ -20,6 +23,7 @@ describe("public terrain consumer", () => { .erode({ id: "rain", method: "hydraulic", droplets: 3000, erosion: 0.15 }) .erode({ id: "talus", method: "thermal", iterations: 12, talus: 25 }); const state = terrain.evaluate(); + expect(new TerrainEvaluator({ cacheMB: 0 }).evaluate(terrain.toJSON())).toEqual(state); expect(state.erosion).toBeDefined(); for (const values of Object.values(state.erosion ?? {})) { expect(values.length).toBe(state.height.length); @@ -257,4 +261,42 @@ describe("public terrain consumer", () => { expect(() => encodeGLB(mesh)).toThrow(); expect(() => toGeometry(mesh)).toThrow(); }); + it("names the shipped package as the GLB generator", () => { + const glb = encodeGLB(bakeMesh(new Terrain({ resolution: 17 }).evaluate())); + const jsonLength = new DataView(glb.buffer, glb.byteOffset).getUint32(12, true); + const json = JSON.parse(new TextDecoder().decode(glb.subarray(20, 20 + jsonLength))); + const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")); + expect(json.asset.generator).toBe(`@threenative/terrain ${pkg.version}`); + }); +}); +it("rejects a scatter layer without asset", () => { + expect(() => + new Terrain({ resolution: 17 }).scatter({ + id: "trees", + // @ts-expect-error testing missing asset validation + asset: undefined, + }), + ).toThrow(/asset/); +}); + +describe("finiteTree validation fast-path and edge guards", () => { + it("accepts numeric leaves at depth 32 and rejects them at depth 33", () => { + let d32: unknown = 42; + for (let i = 0; i < 32; i++) d32 = [d32]; + expect(() => finiteTree(d32)).not.toThrow(); + + let d33: unknown = 42; + for (let i = 0; i < 33; i++) d33 = [d33]; + expect(() => finiteTree(d33)).toThrow(`value${".0".repeat(33)}: nesting is too deep`); + }); + + it("names the non-finite array entry", () => { + expect(() => finiteTree([1, 2, Number.NaN], "samples")).toThrow("samples.2 must be finite"); + }); + + it("rejects an unsafe own array key", () => { + const unsafeArray = [1, 2]; + Object.defineProperty(unsafeArray, "constructor", { value: 123, enumerable: true }); + expect(() => finiteTree(unsafeArray, "samples")).toThrow("samples: unsafe object key"); + }); }); diff --git a/packages/terrain/__tests__/terrain-mcp.spec.ts b/packages/terrain/__tests__/terrain-mcp.spec.ts new file mode 100644 index 0000000000..bd048d838b --- /dev/null +++ b/packages/terrain/__tests__/terrain-mcp.spec.ts @@ -0,0 +1,771 @@ +import { spawn } from "node:child_process"; +import { mkdir, readFile, realpath, rm, stat, symlink, writeFile } from "node:fs/promises"; +import { cpus } from "node:os"; +import { join } from "node:path"; +import { + Heightfield, + heightSamplerFromHeightmap, + loadWorldHeightmap, +} from "@threenative/core/world"; +import { Object3D, Scene } from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import * as ts from "typescript"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { makeTempDir } from "../../../test-support/temp-dir.js"; +import { decodeRAW16 } from "../src/core/io.js"; +import type { ITerrainDocument } from "../src/core/types.js"; +import { IsolatedKillError, runIsolated } from "../src/mcp/child.js"; +import { cookKit, loadCooker, parseBounds } from "../src/mcp/cook.js"; +import { measureGlb } from "../src/mcp/glb-stats.js"; +import { + TERRAIN_KITS, + TERRAIN_TOOLS, + assertOutputFree, + parseGenerateInput, + parsePatchInput, + patchRecipe, + resolveAssetSource, + resolveOutput, +} from "../src/mcp/tools.js"; + +// The starter forest document as the forest bake evaluates it: `recipe.json` wraps it in `recipe`, +// and its raw16 heightmap is decoded into `values` first (bake.mjs, same 0.1 m lattice). +async function forestRecipe(): Promise { + const dir = new URL("../starter/forest/", import.meta.url); + const { recipe } = JSON.parse(await readFile(new URL("recipe.json", dir), "utf8")); + for (const layer of recipe.layers) { + const data = layer.params?.data; + if (data?.raw16 === undefined) continue; + const raw = await readFile(new URL(data.raw16, dir)); + data.values = Array.from( + decodeRAW16(raw, data).values, + (value: number) => Math.round(value * 10) / 10, + ); + } + return recipe; +} + +describe("terrain MCP tool contract", () => { + it("lists exactly terrain_generate and terrain_patch with closed input schemas", () => { + expect(TERRAIN_TOOLS.map((tool) => tool.name)).toEqual(["terrain_generate", "terrain_patch"]); + for (const tool of TERRAIN_TOOLS) { + expect(tool.inputSchema.additionalProperties).toBe(false); + } + }); +}); + +describe("terrain_generate input", () => { + it.each([ + ["forest", "pine forest woods"], + ["coastal", "beach coast sea"], + ["alpine", "mountain alpine snowy peaks"], + ["desert", "dunes desert sand"], + ["tundra", "tundra arctic ice"], + ])("selects %s from its name and synonyms", (kit, words) => { + for (const request of words.split(" ")) { + expect(parseGenerateInput({ request: `Generate ${request.toUpperCase()} for me` })).toEqual({ + kit, + }); + } + }); + + it("keeps kit as an explicit override and breaks ties in starter order", () => { + expect(parseGenerateInput({ request: "forest beach", kit: "desert" })).toEqual({ + kit: "desert", + }); + expect(parseGenerateInput({ request: "beach forest" })).toEqual({ kit: "forest" }); + }); + + it("rejects unmatched requests with the available recipes", () => { + expect(() => parseGenerateInput({ request: "castle" })).toThrow( + /forest, coastal, alpine, desert, tundra/, + ); + for (const request of [null, 1, "", " "]) { + expect(() => parseGenerateInput({ request, kit: "forest" })).toThrow(/request/); + } + }); + + it("accepts the forest kit and leaves size, seed and out to the recipe", () => { + expect(parseGenerateInput({ kit: "forest" })).toEqual({ kit: "forest" }); + }); + + it("accepts all five starter kits", () => { + expect([...TERRAIN_KITS].sort()).toEqual(["alpine", "coastal", "desert", "forest", "tundra"]); + for (const kit of TERRAIN_KITS) { + expect(parseGenerateInput({ kit })).toEqual({ kit }); + } + }); + + it("rejects malformed fields before any evaluation starts", () => { + const bad: unknown[] = [ + undefined, + {}, + { kit: "castle" }, + { kit: "forest", sizeMeters: 0 }, + { kit: "forest", sizeMeters: "512" }, + { kit: "forest", seed: 1.5 }, + { kit: "forest", out: "" }, + { kit: "forest", out: "../escape" }, + { kit: "forest", out: "/tmp/absolute" }, + { kit: "forest", unknownField: true }, + ]; + for (const input of bad) { + expect(() => parseGenerateInput(input)).toThrow(); + } + }); +}); + +describe("terrain_generate output guard", () => { + it("derives the asset source from project config and defaults to assets", async () => { + const project = await makeTempDir("tn-terrain-config-"); + expect(await resolveAssetSource(project)).toBe(join(project, "assets")); + await writeFile( + join(project, "threenative.config.ts"), + 'export default { assets: { source: "content" } };', + ); + expect(await resolveAssetSource(project)).toBe(join(project, "content")); + await writeFile( + join(project, "threenative.config.ts"), + "export default { assets: { source: 1 } };", + ); + await expect(resolveAssetSource(project)).rejects.toThrow(/assets.source/); + await writeFile( + join(project, "threenative.config.ts"), + 'export default { assets: { source: "../escape" } };', + ); + await expect(resolveAssetSource(project)).rejects.toThrow(/inside the project/); + }); + + let root: string; + + beforeEach(async () => { + root = await makeTempDir("tn-terrain-mcp-"); + }); + + afterEach(async () => { + await rm(root, { recursive: true, force: true }); + }); + + it("allows a new output directory", async () => { + await expect(assertOutputFree(join(root, "forest"))).resolves.toBeUndefined(); + }); + + it("refuses an existing output directory and leaves its files untouched", async () => { + const out = join(root, "forest"); + await mkdir(out); + await writeFile(join(out, "keep.txt"), "mine"); + await expect(assertOutputFree(out)).rejects.toThrow(/already exists/); + expect(await readFile(join(out, "keep.txt"), "utf8")).toBe("mine"); + }); +}); + +describe("terrain_generate output containment", () => { + let root: string; + let outside: string; + + beforeEach(async () => { + root = await realpath(await makeTempDir("tn-terrain-root-")); + outside = await realpath(await makeTempDir("tn-terrain-outside-")); + }); + + afterEach(async () => { + await rm(root, { recursive: true, force: true }); + await rm(outside, { recursive: true, force: true }); + }); + + it("returns the real path of a new directory under the root", async () => { + expect(await resolveOutput(root, "public/worlds/forest")).toBe( + join(root, "public", "worlds", "forest"), + ); + }); + + it("rejects an output that is itself a symlink leaving the root", async () => { + await symlink(outside, join(root, "link")); + await expect(resolveOutput(root, "link")).rejects.toThrow(); + }); + + it("rejects an output under an ancestor symlink leaving the root", async () => { + await symlink(outside, join(root, "public")); + await expect(resolveOutput(root, "public/worlds/forest")).rejects.toThrow( + /outside the project/, + ); + }); + + it("rejects an output under a dangling symlink", async () => { + await symlink(join(outside, "missing"), join(root, "public")); + await expect(resolveOutput(root, "public/forest")).rejects.toThrow(/dangling symlink/); + }); + + it("allows a symlinked ancestor that stays inside the root", async () => { + await mkdir(join(root, "real")); + await symlink(join(root, "real"), join(root, "alias")); + expect(await resolveOutput(root, "alias/forest")).toBe(join(root, "real", "forest")); + }); +}); + +describe("species bounds", () => { + it("narrows a well-formed box", () => { + expect(parseBounds({ min: [-1, 0, -1], max: [1, 2, 1] }, "fir")).toEqual({ + min: [-1, 0, -1], + max: [1, 2, 1], + }); + }); + + it("fails closed on a missing, malformed or inverted box", () => { + const bad: unknown[] = [ + undefined, + null, + {}, + { min: [0, 0, 0] }, + { min: [0, 0], max: [1, 1, 1] }, + { min: [0, 0, 0], max: [1, "1", 1] }, + { min: [0, 0, 0], max: [1, Number.NaN, 1] }, + { min: [2, 0, 0], max: [1, 1, 1] }, + ]; + for (const value of bad) { + expect(() => parseBounds(value, "fir")).toThrow(/species 'fir' bounds/); + } + }); +}); + +describe("cooker resolution", () => { + it("names the install command when the game project has no @threenative/assets", async () => { + const project = await makeTempDir("tn-terrain-noassets-"); + try { + await expect(loadCooker(project)).rejects.toThrow(/npm i -D @threenative\/assets/); + } finally { + await rm(project, { recursive: true, force: true }); + } + }); +}); + +describe("isolated generation child", () => { + it("never spawns when the signal is already aborted", async () => { + const dir = await makeTempDir("tn-terrain-preabort-"); + const marker = join(dir, "spawned"); + try { + const controller = new AbortController(); + controller.abort(); + await expect( + runIsolated({ + command: process.execPath, + args: ["-e", `require('node:fs').writeFileSync(${JSON.stringify(marker)}, 'x')`], + timeoutMs: 10_000, + signal: controller.signal, + }), + ).rejects.toThrow(IsolatedKillError); + await new Promise((done) => setTimeout(done, 300)); + await expect(readFile(marker)).rejects.toThrow(/ENOENT/); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + it("kills the whole process group on timeout, grandchildren included", async () => { + const dir = await makeTempDir("tn-terrain-group-"); + const pidFile = join(dir, "grandchild.pid"); + const script = ` + const { spawn } = require('node:child_process'); + const grandchild = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 60000)'], { stdio: 'ignore' }); + require('node:fs').writeFileSync(${JSON.stringify(pidFile)}, String(grandchild.pid)); + setTimeout(() => {}, 60000); + `; + try { + await expect( + runIsolated({ command: process.execPath, args: ["-e", script], timeoutMs: 1_000 }), + ).rejects.toThrow(/timed out/); + const pid = Number(await readFile(pidFile, "utf8")); + const alive = () => { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } + }; + for (let wait = 0; wait < 50 && alive(); wait += 1) { + await new Promise((done) => setTimeout(done, 50)); + } + expect(alive()).toBe(false); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + it("resolves with the child stdout on a clean exit", async () => { + const result = await runIsolated({ + command: process.execPath, + args: ["-e", "process.stdout.write('ok')"], + timeoutMs: 10_000, + }); + expect(result.stdout).toBe("ok"); + }); + + it("rejects a non-zero exit with the child stderr", async () => { + await expect( + runIsolated({ + command: process.execPath, + args: ["-e", "process.stderr.write('bad input'); process.exit(3)"], + timeoutMs: 10_000, + }), + ).rejects.toThrow(/exited with code 3: bad input/); + }); + + it("kills a child that outlives its timeout", async () => { + const started = Date.now(); + await expect( + runIsolated({ + command: process.execPath, + args: ["-e", "setTimeout(() => {}, 60000)"], + timeoutMs: 300, + }), + ).rejects.toThrow(/timed out after 300 ms and was killed/); + expect(Date.now() - started).toBeLessThan(10_000); + }); + + it("kills a child when the caller cancels", async () => { + const controller = new AbortController(); + const pending = runIsolated({ + command: process.execPath, + args: ["-e", "setTimeout(() => {}, 60000)"], + timeoutMs: 60_000, + signal: controller.signal, + }); + setTimeout(() => controller.abort(), 100); + await expect(pending).rejects.toThrow(/was cancelled and was killed/); + }); +}); + +describe("terrain_patch recipe edits", () => { + it("applies the editor command vocabulary to the actual forest recipe", async () => { + const recipe = await forestRecipe(); + const ids = recipe.layers.map((layer) => layer.id); + const first = ids[0] as string; + const last = ids.at(-1) as string; + const edited = patchRecipe(recipe, [ + { op: "toggle", id: first, enabled: false }, + { op: "move", id: last, index: 0 }, + { op: "remove", id: ids[1] as string }, + ]); + const layers = edited.layers; + expect(layers.map((layer) => layer.id)).toEqual([last, first, ...ids.slice(2, -1)]); + expect(layers.find((layer) => layer.id === first)?.enabled).toBe(false); + }); + + it("rejects an unknown operation before it reaches the recipe", async () => { + const recipe = await forestRecipe(); + expect(() => parsePatchInput({ recipe, commands: [{ op: "paint-all" }] })).toThrow( + /op must be one of/, + ); + }); + + it("rejects malformed recipes and command payloads at the input boundary", () => { + const recipe = { + version: 1, + config: { size: 512, resolution: 17, seed: 73 }, + layers: [{ id: "hills", type: "noise", params: { amplitude: 16 } }], + }; + for (const invalid of [{}, { ...recipe, config: {} }, { ...recipe, layers: [{}] }]) { + expect(() => parsePatchInput({ recipe: invalid, commands: [] })).toThrow(); + } + for (const command of [ + { op: "upsert", layer: { id: "bad", type: "noise", params: { amplitude: "high" } } }, + { op: "update", id: "hills", patch: { params: { amplitude: "high" } } }, + { op: "update", id: "hills", patch: { mask: { type: "unknown" } } }, + { op: "update", id: "hills", patch: { params: [] } }, + { op: "move", id: "hills", index: "0" }, + { op: "toggle", id: "hills", enabled: "false" }, + ]) { + expect(() => parsePatchInput({ recipe, commands: [command] })).toThrow(); + } + }); + + it("preserves valid payloads and validates commands in transaction order", () => { + const recipe = { version: 1, config: { size: 512, resolution: 17, seed: 73 }, layers: [] }; + const commands = [ + { op: "upsert", layer: { id: "hills", type: "noise", params: { amplitude: 16 } } }, + { op: "update", id: "hills", patch: { params: { amplitude: 20 } } }, + { op: "toggle", id: "hills", enabled: false }, + { op: "move", id: "hills", index: 0 }, + { op: "remove", id: "hills" }, + ]; + expect(parsePatchInput({ recipe, commands })).toEqual({ recipe, commands }); + expect(recipe.layers).toEqual([]); + }); + + it("rejects a command that names a layer the recipe does not have", async () => { + const recipe = await forestRecipe(); + expect(() => patchRecipe(recipe, [{ op: "remove", id: "no-such-layer" }] as never)).toThrow(); + }); +}); + +// A GLB with only a JSON chunk: the counts live in accessor headers, so no buffer data is needed. +function glbOf(json: unknown): Uint8Array { + const text = new TextEncoder().encode(JSON.stringify(json)); + const padded = new Uint8Array(Math.ceil(text.length / 4) * 4).fill(0x20); + padded.set(text); + const bytes = new Uint8Array(20 + padded.length); + const view = new DataView(bytes.buffer); + view.setUint32(0, 0x46546c67, true); + view.setUint32(4, 2, true); + view.setUint32(8, bytes.length, true); + view.setUint32(12, padded.length, true); + view.setUint32(16, 0x4e4f534a, true); + bytes.set(padded, 20); + return bytes; +} + +describe("measureGlb", () => { + const accessors = [{ count: 3 }, { count: 6 }, { count: 4 }, { count: 10 }]; + const meshes = [ + { primitives: [{ attributes: { POSITION: 0 } }, { attributes: { POSITION: 0 }, indices: 1 }] }, + { + primitives: [ + { attributes: { POSITION: 2 }, mode: 5 }, + { attributes: { POSITION: 0 }, mode: 1 }, + ], + }, + ]; + + it("counts triangles per instance and draws per primitive, instanced or not", () => { + const stats = measureGlb( + glbOf({ + asset: { version: "2.0" }, + accessors, + meshes, + nodes: [ + { children: [1, 2] }, + { mesh: 0 }, + { mesh: 1, extensions: { EXT_mesh_gpu_instancing: { attributes: { TRANSLATION: 3 } } } }, + ], + scene: 0, + scenes: [{ nodes: [0] }], + }), + ); + // mesh 0 once: 1 + 2 triangles. mesh 1 ten times: a 4-vertex strip is 2 triangles, the line list none. + expect(stats).toEqual({ draws: 4, instances: 10, triangles: 3 + 2 * 10 }); + }); + + it("ignores the alternate scenes a cook writes beside the default one", () => { + const stats = measureGlb( + glbOf({ + asset: { version: "2.0" }, + accessors, + meshes, + nodes: [{ mesh: 0 }, { mesh: 0 }], + scene: 1, + scenes: [{ nodes: [0] }, { nodes: [1] }], + }), + ); + expect(stats).toEqual({ draws: 2, instances: 0, triangles: 3 }); + }); + + it("fails closed on bytes that are not a GLB and on a dangling accessor", () => { + expect(() => measureGlb(new Uint8Array(32))).toThrow(/not a GLB/); + expect(() => + measureGlb( + glbOf({ + asset: { version: "2.0" }, + meshes: [{ primitives: [{ attributes: { POSITION: 7 } }] }], + nodes: [{ mesh: 0 }], + scenes: [{ nodes: [0] }], + }), + ), + ).toThrow(/accessor 7 is missing/); + }); +}); + +interface IHeightfieldCollider { + readonly columns: number; + readonly heights: Float32Array; + readonly rows: number; + readonly scale: { readonly x: number; readonly y: number; readonly z: number }; +} + +interface IEmittedBody { + readonly entity?: string; + readonly object?: Object3D; + readonly type?: string; +} + +async function runEmittedLoad( + load: string, + servedHeightmap: string, + onResolve: (path: string) => void, +) { + const scene = new Object3D(); + const dispose = vi.fn(); + const worldDispose = vi.fn(); + const surfaceDispose = vi.fn(); + let body: IEmittedBody | undefined; + let collider: IHeightfieldCollider | undefined; + let streamedFollow: { position: Object3D["position"] } | undefined; + let streamedInvalidate: ((region?: object) => void) | undefined; + const modules = { + "@threenative/core/world": { + Heightfield, + heightSamplerFromHeightmap, + loadWorldHeightmap, + loadTerrainSplat: vi.fn(async () => ({ surface: "terrain", dispose: surfaceDispose })), + WorldCells: { + load: vi.fn( + async (options: { + follow: { position: Object3D["position"] }; + surface: unknown; + shadows: { invalidate: (region?: object) => void }; + }) => { + expect(options.surface).toMatchObject({ surface: "terrain" }); + streamedFollow = options.follow; + streamedInvalidate = options.shadows.invalidate; + return Object.assign(new Object3D(), { dispose: worldDispose }); + }, + ), + }, + }, + "@threenative/physics": { + CollisionShape3D: { + heightfield: ( + rows: number, + columns: number, + heights: Float32Array, + scale: IHeightfieldCollider["scale"], + ) => { + collider = { rows, columns, heights, scale }; + return collider; + }, + }, + RigidBody3D: class { + constructor(options: IEmittedBody) { + body = options; + } + dispose = dispose; + }, + }, + three: { Object3D }, + }; + const ctx = { + add: (object: Object3D) => scene.add(object), + assets: { + model: async () => ({ scene: new Object3D() }), + resolve: async (path: string) => { + onResolve(path); + return [servedHeightmap]; + }, + }, + physics: {}, + renderer: {}, + scene, + }; + // Compile the real imports so missing modules, exports and aliases cannot pass by being stripped. + const { outputText } = ts.transpileModule(load, { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, + }); + const execute = new Function( + "modules", + "ctx", + `const exports = {}; const require = (name) => { if (!(name in modules)) throw new Error("Unexpected runtime import: " + name); return modules[name]; }; return (async () => { ${outputText}; return { loadCells }; })();`, + ); + const { loadCells } = await execute(modules, ctx); + const player = new Object3D(); + player.position.set(-2, 19, 7); + const invalidate = vi.fn(); + streamedInvalidate?.(); + loadCells(player, invalidate); + const region = { minX: 0, minZ: 0, maxX: 2, maxZ: 3 }; + streamedInvalidate?.(region); + streamedInvalidate?.(); + expect(invalidate.mock.calls).toEqual([[region], [undefined]]); + expect(streamedFollow?.position).toBe(player.position); + player.position.z -= 10; + expect(streamedFollow?.position.z).toBe(-3); + expect(scene.children).toHaveLength(2); + if (body === undefined || collider === undefined) + throw new Error("The snippet created no terrain collider."); + expect(dispose).not.toHaveBeenCalled(); + scene.clear(); + expect(dispose).toHaveBeenCalledOnce(); + expect(worldDispose).toHaveBeenCalledOnce(); + expect(surfaceDispose).toHaveBeenCalledOnce(); + const failure = new Error("streamed world failed"); + modules["@threenative/core/world"].WorldCells.load.mockRejectedValueOnce(failure); + await expect(execute(modules, ctx)).rejects.toBe(failure); + expect(dispose).toHaveBeenCalledTimes(2); + expect(worldDispose).toHaveBeenCalledOnce(); + expect(surfaceDispose).toHaveBeenCalledTimes(2); + expect(scene.children).toHaveLength(0); + modules["@threenative/core/world"].loadTerrainSplat.mockRejectedValueOnce(failure); + await expect(execute(modules, ctx)).rejects.toBe(failure); + expect(dispose).toHaveBeenCalledTimes(3); + expect(surfaceDispose).toHaveBeenCalledTimes(2); + expect(scene.children).toHaveLength(0); + return { + body, + collider, + streaming: modules["@threenative/core/world"].WorldCells.load.mock.calls[0]?.[0], + }; +} + +describe("terrain_generate result", () => { + it.each(["assets", "content"])( + "returns a manifest-relative forest load snippet under %s", + async (source) => { + const project = await realpath(await makeTempDir("tn-terrain-result-")); + try { + await mkdir(join(project, "node_modules", "@threenative"), { recursive: true }); + await symlink( + new URL("../../assets/", import.meta.url).pathname, + join(project, "node_modules", "@threenative", "assets"), + ); + await writeFile( + join(project, "threenative.config.ts"), + `export default { assets: { source: "${source}" } };`, + ); + const out = join(project, source, "worlds", "forest"); + const result = JSON.parse(await cookKit({ kit: "forest", out, projectRoot: project })); + expect(result).toMatchObject({ + recipe: "forest", + out: `${source}/worlds/forest`, + worldGlb: `${source}/worlds/forest/world.glb`, + recipeJson: `${source}/worlds/forest/recipe.json`, + worldPackage: `${source}/worlds/forest/world.json`, + }); + for (const file of [result.worldGlb, result.recipeJson, result.worldPackage]) { + expect((await stat(join(project, file))).size, file).toBeGreaterThan(0); + } + // The counts are the file's own: measuring the written GLB again gives the same numbers. + const written = new Uint8Array(await readFile(join(project, result.worldGlb))); + expect(measureGlb(written)).toEqual({ + draws: result.draws, + instances: result.instances, + triangles: result.triangles, + }); + expect(result.instances).toBeGreaterThan(0); + expect(result.draws).toBeGreaterThan(0); + expect(result.triangles).toBeGreaterThan(result.instances); + expect(result.load).toContain("WorldCells.load({"); + expect(result.load).toContain('url: "worlds/forest/world.json"'); + expect(result.load).toContain("loadTerrainSplat({ assets: ctx.assets"); + expect(result.load).toContain("ctx.add(world);"); + expect(result.load).not.toContain("ctx.assets.model"); + expect(result.load).not.toContain("applyTerrainSurface"); + // No authoring import leaks into the game: only core/world, physics and three are allowed. + expect( + result.load.split("\n").filter((line: string) => line.includes("@threenative/terrain")), + ).toEqual([]); + // The printed snippet must build the physical ground the visual GLB does not. + const manifest = JSON.parse(await readFile(join(project, result.worldPackage), "utf8")); + const heightmapBytes = new Uint8Array( + await readFile(join(project, source, "worlds", "forest", manifest.terrain.heightmap)), + ); + const served = `data:application/octet-stream;base64,${Buffer.from(heightmapBytes).toString("base64")}`; + const resolved: string[] = []; + const { body, collider, streaming } = await runEmittedLoad(result.load, served, (path) => + resolved.push(path), + ); + expect(resolved).toEqual(Array(3).fill(`worlds/forest/${manifest.terrain.heightmap}`)); + // Exact record budgets are non-restrictive only while this export has no charged proxies. + expect( + manifest.cells.every( + (cell: { proxy?: unknown; chunks?: unknown[] }) => + cell.proxy === undefined && (cell.chunks?.length ?? 0) === 0, + ), + ).toBe(true); + const placementCount = manifest.cells.reduce( + (sum: number, cell: { runs: { count: number }[] }) => + sum + cell.runs.reduce((count, run) => count + run.count, 0), + 0, + ); + expect(streaming).toMatchObject({ + ring: 5, + terrain: { streamRadius: 6 }, + budgets: { + residentCells: manifest.cells.length, + instances: placementCount, + bytes: placementCount * 32, + }, + shadows: { cast: true, receive: true }, + }); + expect(collider.rows).toBe(manifest.terrain.rows); + expect(collider.columns).toBe(manifest.terrain.columns); + expect(collider.scale).toEqual({ + x: manifest.extent.sizeX, + y: 1, + z: manifest.extent.sizeZ, + }); + expect(body.type).toBe("fixed"); + expect(body.entity).toBe("terrain"); + expect(body.object?.position.x).toBe(manifest.extent.minX + manifest.extent.sizeX / 2); + expect(body.object?.position.z).toBe(manifest.extent.minZ + manifest.extent.sizeZ / 2); + const decoded = decodeRAW16(heightmapBytes, { + height: manifest.terrain.rows, + max: manifest.terrain.heightMax, + min: manifest.terrain.heightMin, + width: manifest.terrain.columns, + }); + expect(collider.heights).toHaveLength(manifest.terrain.rows * manifest.terrain.columns); + for (let column = 0; column < manifest.terrain.columns; column += 1) + for (let row = 0; row < manifest.terrain.rows; row += 1) + expect(collider.heights[column * manifest.terrain.rows + row]).toBeCloseTo( + decoded.values[row * manifest.terrain.columns + column] as number, + 4, + ); + // recipe.json is a document `terrain_patch` accepts as is. + const recipe = JSON.parse(await readFile(join(project, result.recipeJson), "utf8")); + expect(patchRecipe(recipe, []).layers).toHaveLength(recipe.layers.length); + } finally { + await rm(project, { recursive: true, force: true }); + } + }, + 120_000, + ); +}); + +describe("terrain_generate cold GLB handoff", () => { + it("generates a pine forest that vanilla GLTFLoader loads within 60 seconds", async () => { + const project = await realpath(await makeTempDir("tn-terrain-cold-")); + await mkdir(join(project, "node_modules", "@threenative"), { recursive: true }); + await symlink( + new URL("../../assets/", import.meta.url).pathname, + join(project, "node_modules", "@threenative", "assets"), + ); + const start = performance.now(); + const child = spawn( + process.execPath, + [new URL("../dist/mcp/server.js", import.meta.url).pathname], + { cwd: project, stdio: ["pipe", "pipe", "inherit"] }, + ); + try { + const response = new Promise((resolve, reject) => { + let buffer = ""; + child.stdout.on("data", (chunk: Buffer) => { + buffer += chunk.toString(); + if (buffer.includes("\n")) resolve(buffer.split("\n")[0] ?? ""); + }); + child.on("error", reject); + child.on("exit", (code) => reject(new Error(`terrain server exited ${code}`))); + }); + child.stdin.write( + `${JSON.stringify({ id: 1, jsonrpc: "2.0", method: "tools/call", params: { name: "terrain_generate", arguments: { request: "a pine forest" } } })}\n`, + ); + const reply = JSON.parse(await response); + expect(reply.result.isError).toBe(false); + const result = JSON.parse(reply.result.content[0].text); + expect(result.worldGlb).toMatch(/world\.glb$/); + const bytes = await readFile(join(project, result.worldGlb)); + vi.stubGlobal("self", globalThis); + vi.stubGlobal("createImageBitmap", async () => ({ width: 1, height: 1, close() {} })); + const gltf = await new GLTFLoader().parseAsync( + bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength), + "", + ); + expect(gltf.scene.children.length).toBeGreaterThan(0); + const ms = performance.now() - start; + console.log(`TN_TERRAIN_COLD_LOAD: ${ms.toFixed(1)} ms; CPU: ${cpus()[0]?.model}`); + expect(ms).toBeLessThan(60_000); + } finally { + child.kill(); + vi.unstubAllGlobals(); + await rm(project, { recursive: true, force: true }); + } + }, 65_000); +}); diff --git a/packages/terrain/__tests__/tundra-kit.spec.ts b/packages/terrain/__tests__/tundra-kit.spec.ts deleted file mode 100644 index 32f553bd08..0000000000 --- a/packages/terrain/__tests__/tundra-kit.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { copyFileSync, mkdirSync, readFileSync, symlinkSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { type IWorldPackage, validateWorldPackage } from "@threenative/core/world"; -import { describe, expect, it } from "vitest"; -import { makeTempDirSync } from "../../../test-support/temp-dir.js"; - -const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); -const kit = join(packageRoot, "starter", "tundra"); -const starterAssets = join(packageRoot, "starter-assets"); - -/** Copies the tundra kit into a temp directory whose node_modules resolves the workspace package. */ -function kitInTempDir(): string { - const dir = makeTempDirSync("terrain-tundra-kit-"); - for (const name of ["bake.mjs", "recipe.json", "assets.json", "surface.json"]) - copyFileSync(join(kit, name), join(dir, name)); - mkdirSync(join(dir, "node_modules", "@threenative"), { recursive: true }); - symlinkSync(packageRoot, join(dir, "node_modules", "@threenative", "terrain"), "dir"); - return dir; -} - -function bake(out: string): void { - execFileSync(process.execPath, ["bake.mjs", "--assets", starterAssets, "--out", out], { - cwd: kitInTempDir(), - stdio: "pipe", - }); -} - -describe("tundra starter kit", () => { - it("bakes a world package the engine's validator accepts, with its rivers and lakes", () => { - const out = join(makeTempDirSync("terrain-tundra-world-"), "world"); - bake(out); - const manifest = JSON.parse(readFileSync(join(out, "world.json"), "utf8")) as IWorldPackage; - const result = validateWorldPackage(manifest, { - heightmapByteLength: readFileSync(join(out, manifest.terrain.heightmap)).byteLength, - placementsByteLength: readFileSync(join(out, manifest.placements)).byteLength, - }); - expect(result.errors).toEqual([]); - expect(manifest.terrain.columns).toBe(257); - - const perAsset: Record = {}; - for (const cell of manifest.cells) - for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; - expect(Object.keys(perAsset).sort()).toEqual(["boulder"]); - expect(perAsset.boulder).toBe(70); - - const water = JSON.parse(readFileSync(join(out, "water.json"), "utf8")) as { - lakes: { id: string }[]; - rivers: { id: string; points: unknown[] }[]; - }; - expect(water.lakes.map((lake) => lake.id).sort()).toEqual(["far-kettle", "near-kettle"]); - expect(water.rivers.map((river) => river.id).sort()).toEqual(["braid", "meltwater"]); - expect(water.rivers.every((river) => river.points.length > 2)).toBe(true); - }); - - it("bakes the same bytes from the same recipe", () => { - const first = join(makeTempDirSync("terrain-tundra-a-"), "world"); - const second = join(makeTempDirSync("terrain-tundra-b-"), "world"); - bake(first); - bake(second); - for (const name of [ - "world.json", - "heightmap.u16", - "placements.bin", - "splat.rgba", - "water.json", - ]) { - expect(readFileSync(join(first, name)).equals(readFileSync(join(second, name))), name).toBe( - true, - ); - } - }); -}); diff --git a/packages/terrain/__tests__/tundra-runtime.spec.ts b/packages/terrain/__tests__/tundra-runtime.spec.ts deleted file mode 100644 index f5c24389c8..0000000000 --- a/packages/terrain/__tests__/tundra-runtime.spec.ts +++ /dev/null @@ -1,337 +0,0 @@ -import type { ICtx } from "@threenative/core"; -import type { IWorldCellsLoadOptions, IWorldPackage } from "@threenative/core/world"; -import { type IPhysicsContext, rapier } from "@threenative/physics"; -import { Group, Mesh, MeshBasicMaterial, MeshStandardMaterial, Object3D, Texture } from "three"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { PropColliders, addTundra } from "../starter/tundra/world.js"; - -const loads = vi.hoisted(() => ({ surface: vi.fn(), sky: vi.fn(), world: vi.fn() })); -vi.mock("@threenative/core/world", async (original) => { - const actual = await original(); - return { ...actual, loadTerrainSplat: loads.surface, WorldCells: { load: loads.world } }; -}); -vi.mock("three/addons/loaders/HDRLoader.js", () => ({ - HDRLoader: class { - loadAsync = loads.sky; - }, -})); - -type Ctx = ICtx, IPhysicsContext>; -const plugins: ReturnType[] = []; -const lifetimes: Array<() => void> = []; -const worlds: Array = []; -const manifest: IWorldPackage = { - version: 1, - cellSize: 64, - extent: { minX: -128, minZ: -128, sizeX: 256, sizeZ: 256 }, - assets: { boulder: { glb: "boulder.glb", bounds: { min: [-1, 0, -1], max: [1, 1, 1] } } }, - cells: [{ x: 0, z: 0, runs: [{ asset: "boulder", offset: 0, count: 2 }] }], - placements: "placements.bin", - terrain: { - heightmap: "heightmap.u16", - columns: 3, - rows: 3, - spacing: 128, - heightMin: 0, - heightMax: 10, - }, -}; -// Two boulders, eight floats each: x, y, z, three unused, a scale in the last slot. One sits at the -// origin and one 130 m east. -const records = new Float32Array([0, 0, 0, 0, 0, 0, 1, 1, 130, 0, 0, 0, 0, 0, 1, 1]); -const heightmap = new Uint16Array([0, 2000, 4000, 6000, 8000, 10000, 12000, 14000, 16000]); -let requests: string[]; -let surface: MeshBasicMaterial; -let sky: Texture; - -async function context() { - const scene = new Group(); - const model = new Group(); - const ctx = { - scene, - assets: { resolve: async (path: string) => [path], model: async () => ({ scene: model }) }, - add: (object: T): T => { - scene.add(object); - return object; - }, - entities: { - add: (_id: string, entity: { dispose?: () => void }) => { - if (entity.dispose) lifetimes.push(entity.dispose); - return entity; - }, - }, - } as unknown as Ctx; - const plugin = rapier(); - await plugin.setup?.(ctx); - plugins.push(plugin); - return { ctx, plugin, model }; -} - -beforeEach(() => { - requests = []; - surface = new MeshBasicMaterial(); - sky = new Texture(); - vi.spyOn(surface, "dispose"); - vi.spyOn(sky, "dispose"); - loads.surface.mockReset().mockResolvedValue(surface); - loads.sky.mockReset().mockResolvedValue(sky); - loads.world.mockReset().mockImplementation(async (options: IWorldCellsLoadOptions) => { - if (!options.data) { - // The real WorldCells API reads these itself unless the consumer supplies decoded data. - await (await fetch(options.url)).json(); - await (await fetch("terrain/tundra/placements.bin")).arrayBuffer(); - await (await fetch("terrain/tundra/heightmap.u16")).arrayBuffer(); - } - const world = Object.assign(new Group(), { - dispose: vi.fn(function (this: Group) { - this.removeFromParent(); - }), - }); - worlds.push(world); - return world; - }); - vi.stubGlobal("fetch", async (input: string) => { - requests.push(input); - if (input.endsWith("world.json")) return new Response(JSON.stringify(manifest)); - if (input.endsWith("placements.bin")) return new Response(records.slice().buffer); - if (input.endsWith("heightmap.u16")) return new Response(heightmap.slice().buffer); - if (input.endsWith("water.json")) - return new Response(JSON.stringify({ lakes: [], rivers: [] })); - throw new Error(`Unexpected tundra request: ${input}`); - }); -}); - -afterEach(() => { - for (const dispose of lifetimes.splice(0)) dispose(); - for (const world of worlds.splice(0)) world.dispose(); - for (const plugin of plugins.splice(0)) plugin.dispose?.({} as Ctx); - vi.unstubAllGlobals(); - vi.restoreAllMocks(); -}); - -describe("tundra runtime", () => { - it("streams boulder colliders when only the follow object's parent moves", async () => { - const { ctx, plugin } = await context(); - const parent = new Object3D(); - const follow = new Object3D(); - parent.add(follow); - const colliders = new PropColliders(ctx, follow, manifest, records); - parent.position.x = 130; - colliders.process(); - plugin.update?.(ctx, 1 / 60); - // A boulder's sphere sits on its origin, so a ray half a metre up crosses it. - const hit = (x: number) => - ctx.physics.directSpaceState.intersectRay({ - from: { x, y: 0.5, z: -2 }, - to: { x, y: 0.5, z: 2 }, - }); - expect(hit(130)).toBeDefined(); - expect(hit(0)).toBeUndefined(); - colliders.detach(); - }); - - it("adds the world, its ground and the boulder colliders near the follow object", async () => { - const { ctx } = await context(); - const tundra = await addTundra(ctx, new Object3D()); - expect(requests).toContain("terrain/tundra/world.json"); - expect(requests).toContain("terrain/tundra/water.json"); - expect(tundra.colliders.active).toBe(1); - expect(tundra.ground.shape.descriptor.kind).toBe("heightfield"); - expect(ctx.scene.children).toHaveLength(3); - }); - - it("loads each world file once and hands the buffers to the streamer and the ground", async () => { - const { ctx } = await context(); - const tundra = await addTundra(ctx, new Object3D()); - for (const name of ["world.json", "placements.bin", "heightmap.u16"]) - expect( - requests.filter((path) => path.endsWith(name)), - name, - ).toHaveLength(1); - const data = loads.world.mock.calls[0]?.[0].data; - expect(data.manifest).toEqual(manifest); - expect(new Float32Array(data.placements)).toEqual(records); - expect(data.heightmap).toEqual(heightmap); - expect(tundra.ground.shape.descriptor.heights).toHaveLength(9); - expect(tundra.ground.shape.descriptor.heights?.[4]).toBeCloseTo((8000 / 65535) * 10); - // The scene picks its spawn and cameras from these, so they must read the same terrain and placements. - expect(tundra.field.heightAt(0, 0)).toBeCloseTo((8000 / 65535) * 10); - expect(tundra.placements).toHaveLength(records.length); - expect(tundra.extent).toEqual(manifest.extent); - }); - - it("does not attach a world after cancellation during its async load", async () => { - const { ctx } = await context(); - let current = true; - const load = loads.world.getMockImplementation(); - loads.world.mockImplementation(async (options: IWorldCellsLoadOptions) => { - const world = await load?.(options); - current = false; - return world; - }); - await expect(addTundra(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(ctx.scene.children).toHaveLength(0); - expect(worlds[0]?.dispose).toHaveBeenCalledOnce(); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(sky.dispose).toHaveBeenCalledOnce(); - }); - - it("stops after a stale surface load before allocating the lighting or world", async () => { - const { ctx } = await context(); - let current = true; - loads.surface.mockImplementation(async () => { - current = false; - return surface; - }); - await expect(addTundra(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(loads.sky).not.toHaveBeenCalled(); - expect(loads.world).not.toHaveBeenCalled(); - expect(surface.dispose).toHaveBeenCalledOnce(); - }); - - it.each(["placements", "heightmap"])("stops after cancelled %s decoding", async (part) => { - const { ctx } = await context(); - let current = true; - const fetchOriginal = globalThis.fetch; - vi.stubGlobal("fetch", async (input: string) => { - const response = await fetchOriginal(input); - if (input.includes(part)) { - const decode = response.arrayBuffer.bind(response); - response.arrayBuffer = async () => { - const buffer = await decode(); - current = false; - return buffer; - }; - } - return response; - }); - await expect(addTundra(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(loads.surface).not.toHaveBeenCalled(); - expect(loads.world).not.toHaveBeenCalled(); - expect(ctx.scene.children).toHaveLength(0); - }); - - it.each(["sky", "model"])( - "releases lighting after cancellation during %s loading", - async (part) => { - const { ctx } = await context(); - let current = true; - if (part === "sky") - loads.sky.mockImplementation(async () => { - current = false; - return sky; - }); - else - vi.spyOn(ctx.assets, "model").mockImplementation(async () => { - current = false; - return { scene: new Group() }; - }); - await expect(addTundra(ctx, new Object3D(), undefined, () => current)).rejects.toMatchObject({ - name: "AbortError", - }); - expect(loads.world).not.toHaveBeenCalled(); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(sky.dispose).toHaveBeenCalledOnce(); - expect(ctx.scene.children).toHaveLength(0); - }, - ); - - it("releases acquired lighting when a model fails before world creation", async () => { - const { ctx } = await context(); - ctx.assets.model = async () => { - throw new Error("missing boulder.glb"); - }; - await expect(addTundra(ctx, new Object3D())).rejects.toThrow("missing boulder.glb"); - expect(sky.dispose).toHaveBeenCalledOnce(); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(ctx.scene.children).toHaveLength(0); - }); - - it("releases the ground, props and owned lighting at the scene lifetime boundary", async () => { - const { ctx, plugin } = await context(); - const follow = new Object3D(); - const tundra = await addTundra(ctx, follow); - expect(tundra.colliders.active).toBe(1); - for (const dispose of lifetimes.splice(0)) dispose(); - plugin.update?.(ctx, 1 / 60); - expect(tundra.colliders.released).toBe(true); - expect(tundra.colliders.active).toBe(0); - follow.position.x = 130; - tundra.colliders.process(); - expect(tundra.colliders.active).toBe(0); - expect(ctx.scene.children).toHaveLength(0); - expect(surface.dispose).toHaveBeenCalledOnce(); - expect(sky.dispose).toHaveBeenCalledOnce(); - expect( - ctx.physics.directSpaceState.intersectRay({ - from: { x: 0, y: 50, z: 0 }, - to: { x: 0, y: -50, z: 0 }, - }), - ).toBeUndefined(); - }); - - it("restores shared model lighting without disposing cached model materials", async () => { - const { ctx, model } = await context(); - const priorSky = new Texture(); - const material = new MeshStandardMaterial({ envMap: priorSky, envMapIntensity: 0.25 }); - const mesh = new Mesh(undefined, material); - model.add(mesh); - vi.spyOn(material, "dispose"); - await addTundra(ctx, new Object3D()); - expect(material.envMap).toBe(sky); - for (const dispose of lifetimes.splice(0)) dispose(); - expect(material.envMap).toBe(priorSky); - expect(material.envMapIntensity).toBe(0.25); - expect(material.dispose).not.toHaveBeenCalled(); - mesh.geometry.dispose(); - priorSky.dispose(); - }); - - it("measures its four metre refresh threshold in the same world coordinates", async () => { - const { ctx } = await context(); - const parent = new Object3D(); - parent.position.x = 100; - const follow = new Object3D(); - parent.add(follow); - const poses = records.slice(); - poses[0] = 100; - poses[8] = 161; - const colliders = new PropColliders(ctx, follow, manifest, poses); - expect(colliders.active).toBe(1); - parent.position.x = 102; - colliders.process(); - expect(colliders.active).toBe(1); - parent.position.x = 105; - colliders.process(); - expect(colliders.active).toBe(2); - colliders.detach(); - }); - - it("releases already created prop bodies if collider construction fails", async () => { - const { ctx, plugin } = await context(); - const create = ctx.physics.simulation.createBody.bind(ctx.physics.simulation); - vi.spyOn(ctx.physics.simulation, "createBody") - .mockImplementationOnce(create) - .mockImplementationOnce(() => { - throw new Error("collider allocation failed"); - }); - const poses = records.slice(); - poses[8] = 20; - expect(() => new PropColliders(ctx, new Object3D(), manifest, poses)).toThrow( - "collider allocation failed", - ); - plugin.update?.(ctx, 1 / 60); - expect( - ctx.physics.directSpaceState.intersectRay({ - from: { x: 0, y: 0.5, z: -2 }, - to: { x: 0, y: 0.5, z: 2 }, - }), - ).toBeUndefined(); - }); -}); diff --git a/packages/terrain/__tests__/world-export.spec.ts b/packages/terrain/__tests__/world-export.spec.ts index 72a6fb5701..78c8ad5210 100644 --- a/packages/terrain/__tests__/world-export.spec.ts +++ b/packages/terrain/__tests__/world-export.spec.ts @@ -15,7 +15,7 @@ import { import { describe, expect, it } from "vitest"; async function fixture() { - const { exportWorldGLB } = await import("@threenative/terrain/export"); + const { prepareWorldGLTF } = await import("@threenative/terrain/export"); const state = new Terrain({ size: 16, resolution: 17 }) .scatter({ id: "trees", asset: "pine", count: 1, avoidWater: false }) .evaluate(); @@ -42,110 +42,115 @@ async function fixture() { assets: new Map([["pine", asset]]), transforms: new Map([[id, new Matrix4()]]), }; - return { exportWorldGLB, input, id }; + return { prepareWorldGLTF, input, id }; } -describe("public full-world GLB export", () => { +describe("public full-world glTF preparation", () => { + it("leaves binary encoding to the assets cook", async () => { + const exports = await import("@threenative/terrain/export"); + expect(exports).not.toHaveProperty("exportWorldGLB"); + }); + it("imports without DOM globals and rejects missing resolved models/matrices", async () => { expect(typeof document).toBe("undefined"); - const { exportWorldGLB, input, id } = await fixture(); - await expect(exportWorldGLB({ ...input, assets: new Map() })).rejects.toThrow("pine"); - await expect(exportWorldGLB({ ...input, transforms: new Map() })).rejects.toThrow(id); + const { prepareWorldGLTF, input, id } = await fixture(); + await expect(prepareWorldGLTF({ ...input, assets: new Map() })).rejects.toThrow("pine"); + await expect(prepareWorldGLTF({ ...input, transforms: new Map() })).rejects.toThrow(id); expect(input.assets.get("pine")?.parent).toBeNull(); }); it("rejects noncanonical terrain, invalid transforms and unsupported shader materials", async () => { - const { exportWorldGLB, input, id } = await fixture(); + const { prepareWorldGLTF, input, id } = await fixture(); const positions = input.terrain.geometry.getAttribute("position"); positions.setY(0, 1); - await expect(exportWorldGLB(input)).rejects.toThrow("terrain"); + await expect(prepareWorldGLTF(input)).rejects.toThrow("terrain"); positions.setY(0, 0); const bad = new Matrix4().makeScale(-1, 1, 1); - await expect(exportWorldGLB({ ...input, transforms: new Map([[id, bad]]) })).rejects.toThrow( + await expect(prepareWorldGLTF({ ...input, transforms: new Map([[id, bad]]) })).rejects.toThrow( id, ); const model: Mesh | undefined = input.assets.get("pine"); if (!model) throw new Error("Model missing"); model.material = new ShaderMaterial(); model.material.name = "unbaked-pine-wind"; - await expect(exportWorldGLB(input)).rejects.toThrow("unbaked-pine-wind"); + await expect(prepareWorldGLTF(input)).rejects.toThrow("unbaked-pine-wind"); }); it("rejects shear, invisible models and unbaked surface displacement", async () => { - const { exportWorldGLB, input, id } = await fixture(); + const { prepareWorldGLTF, input, id } = await fixture(); const sheared = new Matrix4(); sheared.elements[4] = 0.25; await expect( - exportWorldGLB({ ...input, transforms: new Map([[id, sheared]]) }), + prepareWorldGLTF({ ...input, transforms: new Map([[id, sheared]]) }), ).rejects.toThrow("shear"); const asset = input.assets.get("pine"); if (!asset) throw new Error("Model missing"); asset.visible = false; - await expect(exportWorldGLB(input)).rejects.toThrow("no static meshes"); + await expect(prepareWorldGLTF(input)).rejects.toThrow("no static meshes"); asset.visible = true; asset.material.displacementMap = input.terrain.material.map; - await expect(exportWorldGLB(input)).rejects.toThrow("displacementMap"); + await expect(prepareWorldGLTF(input)).rejects.toThrow("displacementMap"); }); it("requires baked terrain PBR maps with correct data colour spaces", async () => { - const { exportWorldGLB, input } = await fixture(); + const { prepareWorldGLTF, input } = await fixture(); input.terrain.material.normalMap = null; - await expect(exportWorldGLB(input)).rejects.toThrow("normalMap"); + await expect(prepareWorldGLTF(input)).rejects.toThrow("normalMap"); input.terrain.material.normalMap = input.terrain.material.map; - await expect(exportWorldGLB(input)).rejects.toThrow("normalMap"); + await expect(prepareWorldGLTF(input)).rejects.toThrow("normalMap"); }); it("requires representable normal maps and complete byte RGBA data", async () => { - const { exportWorldGLB, input } = await fixture(); + const { prepareWorldGLTF, input } = await fixture(); input.terrain.material.normalMapType = ObjectSpaceNormalMap; - await expect(exportWorldGLB(input)).rejects.toThrow("normal"); + await expect(prepareWorldGLTF(input)).rejects.toThrow("normal"); input.terrain.material.normalMapType = TangentSpaceNormalMap; input.terrain.material.normalScale.set(1, 2); - await expect(exportWorldGLB(input)).rejects.toThrow("normal"); + await expect(prepareWorldGLTF(input)).rejects.toThrow("normal"); input.terrain.material.normalScale.set(1, 1); const map = input.terrain.material.normalMap; if (!map) throw new Error("Normal map missing"); map.image = { width: 1, height: 1, data: new Uint8Array(3) }; - await expect(exportWorldGLB(input)).rejects.toThrow("RGBA"); + await expect(prepareWorldGLTF(input)).rejects.toThrow("RGBA"); }); it("resolves a per-placement model before the shared asset model", async () => { - const { exportWorldGLB, input, id } = await fixture(); + const { prepareWorldGLTF, input, id } = await fixture(); // Two variants of one tree share an asset id, so the resolved model is keyed by placement. The // bespoke model is an unsupported material and its own name is what the encoder reports, which // is how this proves the placement's own model was examined rather than the shared `pine` one. const rejected = new Mesh(new BoxGeometry(), new ShaderMaterial()); rejected.name = "bespoke-variant"; - await expect(exportWorldGLB({ ...input, models: new Map([[id, rejected]]) })).rejects.toThrow( + await expect(prepareWorldGLTF({ ...input, models: new Map([[id, rejected]]) })).rejects.toThrow( "bespoke-variant", ); // A models map alone cannot hide a missing asset: with no shared asset, a placement the caller // did not model fails closed rather than exporting an empty node. await expect( - exportWorldGLB({ + prepareWorldGLTF({ ...input, assets: new Map(), models: new Map([["no-such-placement", new Mesh()]]), }), ).rejects.toThrow("pine"); // A placement with no model at all still fails closed rather than exporting an empty node. - await expect(exportWorldGLB({ ...input, assets: new Map() })).rejects.toThrow("pine"); + await expect(prepareWorldGLTF({ ...input, assets: new Map() })).rejects.toThrow("pine"); }); it("fails closed for omitted, stale or incoherent water instead of omitting it", async () => { - const { exportWorldGLB, input } = await fixture(); + const { prepareWorldGLTF, input } = await fixture(); const state = new Terrain({ size: 16, resolution: 17 }) .water({ id: "sea", kind: "ocean", level: 2 }) .evaluate(); const model = new Group(); model.add(new Mesh(new BoxGeometry(), new MeshStandardMaterial())); const base = { ...input, state, transforms: new Map() }; - await expect(exportWorldGLB(base)).rejects.toThrow("sea"); + await expect(prepareWorldGLTF(base)).rejects.toThrow("sea"); await expect( - exportWorldGLB({ ...base, water: [{ id: "sea", object: model, time: 3, staleFrames: 1 }] }), + prepareWorldGLTF({ ...base, water: [{ id: "sea", object: model, time: 3, staleFrames: 1 }] }), ).rejects.toThrow("stale"); await expect( - exportWorldGLB({ ...base, water: [{ id: "sea", object: model, time: 2, staleFrames: 0 }] }), + prepareWorldGLTF({ ...base, water: [{ id: "sea", object: model, time: 2, staleFrames: 0 }] }), ).rejects.toThrow("time"); }); }); diff --git a/packages/terrain/__tests__/world-package.spec.ts b/packages/terrain/__tests__/world-package.spec.ts index 2d6ad6e8e3..60897aae2e 100644 --- a/packages/terrain/__tests__/world-package.spec.ts +++ b/packages/terrain/__tests__/world-package.spec.ts @@ -123,6 +123,33 @@ describe("world package writer", () => { ); }); + it("fails closed on invalid cellSize (0, -1, NaN), non-finite position, and out-of-extent position", () => { + const world = state(); + // cellSize validation + expect(() => bakeWorldPackage(world, { assets: ASSETS, cellSize: 0 })).toThrow(/cellSize/); + expect(() => bakeWorldPackage(world, { assets: ASSETS, cellSize: -1 })).toThrow(/cellSize/); + expect(() => bakeWorldPackage(world, { assets: ASSETS, cellSize: Number.NaN })).toThrow( + /cellSize/, + ); + + // non-finite position + const base = world.instances[0]; + if (base === undefined) throw new Error("Scatter fixture placed nothing"); + const nanPos: ITerrainState = { + ...world, + instances: [{ ...base, position: [Number.NaN, 0, 0] }], + }; + expect(() => bakeWorldPackage(nanPos, { assets: ASSETS })).toThrow(/position/); + + // out-of-extent position + // world size is 256, extent is [-128, 128] + const outPos: ITerrainState = { + ...world, + instances: [{ ...base, position: [200, 0, 0] }], + }; + expect(() => bakeWorldPackage(outPos, { assets: ASSETS })).toThrow(/extent/); + }); + it("fails closed on an asset the options do not name and on a non-uniform scale", () => { const world = state(); const base = world.instances[0]; diff --git a/packages/terrain/package.json b/packages/terrain/package.json index 01bf62e30c..f12a824ea1 100644 --- a/packages/terrain/package.json +++ b/packages/terrain/package.json @@ -16,12 +16,18 @@ "type": "module", "files": [ "dist", - "starter", - "starter-assets", + "starter/*/*.json", + "starter/*/*.r16", + "starter/*/*.mjs", + "starter/*/*.ts", + "starter/*/*.md", + "starter-assets.json", + "starter-credits.json", "README.md", "AGENT_GUIDE.md", "THIRD_PARTY_NOTICES.md" ], + "bin": "./dist/mcp/server.js", "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/index.d.ts", @@ -38,6 +44,10 @@ "types": "./dist/export.d.ts", "import": "./dist/export.js" }, + "./kit": { + "types": "./dist/kit.d.ts", + "import": "./dist/kit.js" + }, "./package.json": "./package.json", "./editor/server": { "types": "./dist/editor/server.d.ts", @@ -46,6 +56,10 @@ "./editor": { "types": "./dist/editor/index.d.ts", "import": "./dist/editor/index.js" + }, + "./starter": { + "types": "./dist/starter.d.ts", + "import": "./dist/starter.js" } }, "publishConfig": { @@ -70,10 +84,14 @@ "@threenative/physics": "workspace:*" }, "peerDependencies": { + "@threenative/core": ">=0.3.4 <0.4.0", + "@threenative/physics": ">=0.3.4 <0.4.0", "three": ">=0.185.0 <0.186.0", "vite": ">=7.0.0 <9.0.0" }, "peerDependenciesMeta": { + "@threenative/core": { "optional": true }, + "@threenative/physics": { "optional": true }, "vite": { "optional": true } diff --git a/packages/terrain/scripts/pack-starter-assets.mjs b/packages/terrain/scripts/pack-starter-assets.mjs new file mode 100644 index 0000000000..b8244e3d15 --- /dev/null +++ b/packages/terrain/scripts/pack-starter-assets.mjs @@ -0,0 +1,78 @@ +import { createHash } from "node:crypto"; +import { lstat, mkdir, readFile, readdir, writeFile } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { gzipSync } from "node:zlib"; + +const root = fileURLToPath(new URL("..", import.meta.url)); + +async function packStarterAssets() { + const source = join(root, "starter-assets"); + const chunks = []; + async function visit(relative) { + const info = await lstat(join(source, relative)); + if (!info.isDirectory() && !info.isFile()) + throw new Error(`TN_TERRAIN_ARCHIVE_TYPE: ${relative}`); + const name = `starter-assets/${relative}${info.isDirectory() && relative ? "/" : ""}`; + const bytes = info.isFile() ? await readFile(join(source, relative)) : Buffer.alloc(0); + const header = Buffer.alloc(512); + if (Buffer.byteLength(name) > 100) throw new Error(`TN_TERRAIN_ARCHIVE_PATH: ${name}`); + header.write(name, 0, 100); + for (const [offset, length, value] of [ + [100, 8, info.isDirectory() ? 0o755 : 0o644], + [108, 8, 0], + [116, 8, 0], + [124, 12, bytes.length], + [136, 12, 0], + ]) { + header.write(`${value.toString(8).padStart(length - 1, "0")}\0`, offset, length); + } + header.fill(32, 148, 156); + header[156] = info.isDirectory() ? 53 : 48; + header.write("ustar\0", 257, 6); + header.write("00", 263, 2); + header.write( + `${header + .reduce((sum, byte) => sum + byte, 0) + .toString(8) + .padStart(6, "0")}\0 `, + 148, + 8, + ); + chunks.push(header, bytes, Buffer.alloc((512 - (bytes.length % 512)) % 512)); + if (info.isDirectory()) + for (const child of (await readdir(join(source, relative))).sort()) + await visit(`${relative ? `${relative}/` : ""}${child}`); + } + await visit(""); + chunks.push(Buffer.alloc(1024)); + return gzipSync(Buffer.concat(chunks), { level: 9 }); +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const out = process.argv[2]; + if (!out || out.startsWith("--")) + throw new Error("Usage: pack-starter-assets.mjs [--write-manifest]"); + const file = resolve(out); + const archive = await packStarterAssets(); + const record = { + file, + bytes: archive.length, + sha256: createHash("sha256").update(archive).digest("hex"), + }; + await mkdir(dirname(file), { recursive: true }); + await writeFile(file, archive); + if (process.argv.includes("--write-manifest")) { + const { version } = JSON.parse(await readFile(join(root, "package.json"), "utf8")); + const url = `https://github.com/ThreeNativeHQ/threenative-engine/releases/download/terrain-starter-assets-v${version}/terrain-starter-assets-${version}.tar.gz`; + await writeFile( + join(root, "starter-assets.json"), + `${JSON.stringify({ version, url, sha256: record.sha256, bytes: record.bytes }, null, 2)}\n`, + ); + await writeFile( + join(root, "starter-credits.json"), + await readFile(join(root, "starter-assets/credits.json")), + ); + } + console.log(JSON.stringify(record)); +} diff --git a/packages/terrain/src/core/erosion.ts b/packages/terrain/src/core/erosion.ts index b5ea0e8dfc..3485952b69 100644 --- a/packages/terrain/src/core/erosion.ts +++ b/packages/terrain/src/core/erosion.ts @@ -29,6 +29,10 @@ export function thermal( { iterations = 20, talus = 32, rate = 0.22 }: IThermalOptions = {}, observations?: IErosionMaps, ): Float32Array { + if (!Number.isInteger(n) || n < 2) + throw RangeError("Thermal erosion requires grid resolution n >= 2"); + if (!Number.isInteger(iterations) || iterations < 0 || iterations > 200) + throw RangeError("Thermal erosion iterations must be between 0 and 200"); const h = height.slice(); const delta = new Float64Array(h.length); const limit = Math.tan((talus * Math.PI) / 180) * (size / (n - 1)); @@ -37,18 +41,25 @@ export function thermal( for (let z = 0; z < n; z += 1) { for (let x = 0; x < n; x += 1) { const i = z * n + x; + const here = h[i] as number; let best = -1; let diff = limit; - for (const j of [ - x > 0 ? i - 1 : -1, - x < n - 1 ? i + 1 : -1, - z > 0 ? i - n : -1, - z < n - 1 ? i + n : -1, - ]) { - if (j >= 0 && (h[i] as number) - (h[j] as number) > diff) { - diff = (h[i] as number) - (h[j] as number); - best = j; - } + // The same four neighbours in the same order, without the per-cell array. + if (x > 0 && here - (h[i - 1] as number) > diff) { + diff = here - (h[i - 1] as number); + best = i - 1; + } + if (x < n - 1 && here - (h[i + 1] as number) > diff) { + diff = here - (h[i + 1] as number); + best = i + 1; + } + if (z > 0 && here - (h[i - n] as number) > diff) { + diff = here - (h[i - n] as number); + best = i - n; + } + if (z < n - 1 && here - (h[i + n] as number) > diff) { + diff = here - (h[i + n] as number); + best = i + n; } if (best >= 0) { const transfer = (diff - limit) * rate; @@ -64,30 +75,41 @@ export function thermal( return h; } -interface IBrushCell { - dx: number; - dz: number; - weight: number; +interface IBrush { + readonly dx: Int32Array; + readonly dz: Int32Array; + readonly weight: Float64Array; + /** Sum of every normalised weight: the total for a brush fully inside the field. */ + readonly full: number; + /** Largest offset on either axis, so a brush this far from an edge is fully inside. */ + readonly radius: number; } -const brushes = new Map(); +const brushes = new Map(); /** Erosion brush: normalised weights over a disc, so a droplet digs a dimple and not a single-cell pit. */ -function brush(radius: number): IBrushCell[] { +function brush(radius: number): IBrush { let cells = brushes.get(radius); if (!cells) { - cells = []; + const dx: number[] = []; + const dz: number[] = []; + const raw: number[] = []; let total = 0; - for (let dz = -radius; dz <= radius; dz += 1) { - for (let dx = -radius; dx <= radius; dx += 1) { - const d = Math.hypot(dx, dz); + for (let z = -radius; z <= radius; z += 1) { + for (let x = -radius; x <= radius; x += 1) { + const d = Math.hypot(x, z); if (d > radius) continue; const weight = 1 - d / (radius + 1); - cells.push({ dx, dz, weight }); + dx.push(x); + dz.push(z); + raw.push(weight); total += weight; } } - for (const c of cells) c.weight /= total; + const weight = Float64Array.from(raw, (w) => w / total); + let full = 0; + for (let k = 0; k < weight.length; k += 1) full += weight[k] as number; + cells = { dx: Int32Array.from(dx), dz: Int32Array.from(dz), full, radius, weight }; brushes.set(radius, cells); } return cells; @@ -101,15 +123,21 @@ interface IHydraulicProbe { iz: number; } +/** The largest supported grid resolution (`validation.ts` RESOLUTIONS): bounds the automatic default. */ +const MAX_AUTOMATIC_DROPLETS = 1025 * 1025; +/** The explicit recipe limit on `droplets` (`validation.ts`), so a direct call cannot exceed it. */ +const MAX_EXPLICIT_DROPLETS = 200_000; + /** Hydraulic erosion: seeded droplets carry sediment downhill, depositing where they slow. */ export function hydraulic( height: Float32Array, n: number, size: number, - { - // Roughly one droplet per cell. Incision saturates near here: more droplets keep cutting the - // same drainage lines over, which deepens nothing and spends the bake time. - droplets = n * n, + options: IHydraulicOptions = {}, + observations?: IErosionMaps, +): Float32Array { + const { + droplets: requestedDroplets, // A droplet has to be able to cross the world, so its step budget scales with the grid: a // fixed 40 barely leaves a 257 world while a 65 world would walk off it in a few steps. maxSteps = Math.round(Math.min(64, Math.max(24, n / 4))), @@ -123,13 +151,42 @@ export function hydraulic( evaporation = 0.025, seed = 1, brushRadius = 3, - }: IHydraulicOptions = {}, - observations?: IErosionMaps, -): Float32Array { + } = options; + if (!Number.isInteger(n) || n < 2) + throw RangeError("Hydraulic erosion requires grid resolution n >= 2"); + // Roughly one droplet per cell. Incision saturates near here: more droplets keep cutting the + // same drainage lines over, which deepens nothing and spends the bake time. A caller that + // supplies a count is held to the recipe limit; the automatic default is bounded by the largest + // supported grid, so leaving it out cannot request unbounded work. + const droplets = + requestedDroplets === undefined ? Math.min(n * n, MAX_AUTOMATIC_DROPLETS) : requestedDroplets; + if (!Number.isSafeInteger(droplets) || droplets < 0) + throw RangeError("Hydraulic erosion droplets must be a non-negative integer"); + if (requestedDroplets !== undefined && droplets > MAX_EXPLICIT_DROPLETS) + throw RangeError(`Hydraulic erosion droplets must not exceed ${String(MAX_EXPLICIT_DROPLETS)}`); + if (!Number.isInteger(maxSteps) || maxSteps < 1 || maxSteps > 128) + throw RangeError("Hydraulic erosion maxSteps must be between 1 and 128"); const h = Float64Array.from(height); const rnd = random(seed); const cell = size / (n - 1); - const cells = brush(brushRadius); + const minSlope = 0.005 * cell; + const oneMinusInertia = 1 - inertia; + const oneMinusEvaporation = 1 - evaporation; + const brushCells = brush(brushRadius); + const brushDx = brushCells.dx; + const brushDz = brushCells.dz; + const brushWeight = brushCells.weight; + const brushCount = brushDx.length; + const brushFull = brushCells.full; + const brushEdge = brushCells.radius; + // The flat index offset `dz * n + dx` per brush cell for this grid. The interior loop adds it to + // the centre index instead of multiplying two coordinates for every cell; integer-only, exact. + const brushOffset = new Int32Array(brushCount); + for (let k = 0; k < brushCount; k += 1) + brushOffset[k] = (brushDz[k] as number) * n + (brushDx[k] as number); + const obsFlow = observations?.flow; + const obsSediment = observations?.sediment; + const obsDeposition = observations?.deposition; /** Adds `amount` over the brush around one cell, renormalised where the field ends. */ const spread = ( x: number, @@ -137,58 +194,111 @@ export function hydraulic( amount: number, floor = Number.NEGATIVE_INFINITY, ): number => { + if (amount === 0) return 0; + // A brush fully inside the field sums every weight; only a brush at the edge needs the + // partial total, which the first pass computes in the same order as `full`. + const interior = x >= brushEdge && x < n - brushEdge && z >= brushEdge && z < n - brushEdge; + // Interior brushes cover the field bulk: no edge total scan, no bounds branch and no per-cell + // `z * n + x` multiply. Same brush order, same weights and same arithmetic as the shared tail. + if (interior) { + const total = brushFull; + if (total <= 0) return 0; + const base = z * n + x; + if (amount < 0) { + let moved = 0; + for (let k = 0; k < brushCount; k += 1) { + const i = base + (brushOffset[k] as number); + const share = (amount * (brushWeight[k] as number)) / total; + const avail = (h[i] as number) - floor; + const change = avail <= 0 ? -0 : -share < avail ? share : -avail; + h[i] = (h[i] as number) + change; + moved += change; + } + return moved; + } + if (obsDeposition !== undefined) { + for (let k = 0; k < brushCount; k += 1) { + const i = base + (brushOffset[k] as number); + const share = (amount * (brushWeight[k] as number)) / total; + h[i] = (h[i] as number) + share; + obsDeposition[i] = (obsDeposition[i] as number) + share; + } + } else { + for (let k = 0; k < brushCount; k += 1) { + const i = base + (brushOffset[k] as number); + const share = (amount * (brushWeight[k] as number)) / total; + h[i] = (h[i] as number) + share; + } + } + return amount; + } let total = 0; - for (const c of cells) { - const ix = x + c.dx; - const iz = z + c.dz; - if (ix >= 0 && ix < n && iz >= 0 && iz < n) total += c.weight; + for (let k = 0; k < brushCount; k += 1) { + const ix = x + (brushDx[k] as number); + const iz = z + (brushDz[k] as number); + if (ix >= 0 && ix < n && iz >= 0 && iz < n) total += brushWeight[k] as number; } if (total <= 0) return 0; - let moved = 0; - for (const c of cells) { - const ix = x + c.dx; - const iz = z + c.dz; - if (ix < 0 || ix >= n || iz < 0 || iz >= n) continue; - const i = iz * n + ix; - const share = (amount * c.weight) / total; - // A brush cell below the downstream bed has no soil the droplet can pick up. Taking it - // anyway digs pits deeper than the channel and leaves unsupported pillars between paths. - const change = amount < 0 ? -Math.min(-share, Math.max(0, (h[i] as number) - floor)) : share; - h[i] = (h[i] as number) + change; - moved += change; - if (observations && amount > 0) - observations.deposition[i] = - (observations.deposition[i] as number) + (amount * c.weight) / total; + if (amount < 0) { + let moved = 0; + for (let k = 0; k < brushCount; k += 1) { + const ix = x + (brushDx[k] as number); + const iz = z + (brushDz[k] as number); + if (ix < 0 || ix >= n || iz < 0 || iz >= n) continue; + const i = iz * n + ix; + const share = (amount * (brushWeight[k] as number)) / total; + const avail = (h[i] as number) - floor; + const change = avail <= 0 ? -0 : -share < avail ? share : -avail; + h[i] = (h[i] as number) + change; + moved += change; + } + return moved; } - return moved; + if (obsDeposition !== undefined) { + for (let k = 0; k < brushCount; k += 1) { + const ix = x + (brushDx[k] as number); + const iz = z + (brushDz[k] as number); + if (ix < 0 || ix >= n || iz < 0 || iz >= n) continue; + const i = iz * n + ix; + const share = (amount * (brushWeight[k] as number)) / total; + h[i] = (h[i] as number) + share; + obsDeposition[i] = (obsDeposition[i] as number) + share; + } + } else { + for (let k = 0; k < brushCount; k += 1) { + const ix = x + (brushDx[k] as number); + const iz = z + (brushDz[k] as number); + if (ix < 0 || ix >= n || iz < 0 || iz >= n) continue; + const i = iz * n + ix; + const share = (amount * (brushWeight[k] as number)) / total; + h[i] = (h[i] as number) + share; + } + } + return amount; }; - const get = (x: number, z: number): IHydraulicProbe => { + // Two reusable probes: `get` fills one instead of allocating a fresh object twice per step. + const probeOld: IHydraulicProbe = { value: 0, dx: 0, dz: 0, ix: 0, iz: 0 }; + const probeNext: IHydraulicProbe = { value: 0, dx: 0, dz: 0, ix: 0, iz: 0 }; + const get = (x: number, z: number, out: IHydraulicProbe): IHydraulicProbe => { const ix = Math.min(n - 2, Math.floor(x)); const iz = Math.min(n - 2, Math.floor(z)); const tx = x - ix; const tz = z - iz; const i = iz * n + ix; - return { - value: lerp( - lerp(h[i] as number, h[i + 1] as number, tx), - lerp(h[i + n] as number, h[i + n + 1] as number, tx), - tz, - ), - dx: - lerp( - (h[i + 1] as number) - (h[i] as number), - (h[i + n + 1] as number) - (h[i + n] as number), - tz, - ) / cell, - dz: - lerp( - (h[i + n] as number) - (h[i] as number), - (h[i + n + 1] as number) - (h[i + 1] as number), - tx, - ) / cell, - ix, - iz, - }; + const h00 = h[i] as number; + const h10 = h[i + 1] as number; + const h01 = h[i + n] as number; + const h11 = h[i + n + 1] as number; + const g10 = h10 - h00; + const g01 = h01 - h00; + const v0 = h00 + g10 * tx; + const v1 = h01 + (h11 - h01) * tx; + out.value = v0 + (v1 - v0) * tz; + out.dx = (g10 + (h11 - h01 - g10) * tz) / cell; + out.dz = (g01 + (h11 - h10 - g01) * tx) / cell; + out.ix = ix; + out.iz = iz; + return out; }; for (let k = 0; k < droplets; k += 1) { let x = rnd() * (n - 1 - 0.001); @@ -199,24 +309,34 @@ export function hydraulic( let speed = 1; let sediment = 0; for (let step = 0; step < maxSteps; step += 1) { - const old = get(x, z); - if (observations) { + const old = get(x, z, probeOld); + if (obsFlow !== undefined && obsSediment !== undefined) { const tx = x - old.ix; const tz = z - old.iz; const i = old.iz * n + old.ix; - for (let oz = 0; oz < 2; oz++) { - for (let ox = 0; ox < 2; ox++) { - const index = i + oz * n + ox; - const weight = (ox ? tx : 1 - tx) * (oz ? tz : 1 - tz); - observations.flow[index] = (observations.flow[index] as number) + water * weight; - observations.sediment[index] = - (observations.sediment[index] as number) + sediment * weight; - } - } + const w00 = (1 - tx) * (1 - tz); + const w10 = tx * (1 - tz); + const w01 = (1 - tx) * tz; + const w11 = tx * tz; + obsFlow[i] = (obsFlow[i] as number) + water * w00; + obsSediment[i] = (obsSediment[i] as number) + sediment * w00; + obsFlow[i + 1] = (obsFlow[i + 1] as number) + water * w10; + obsSediment[i + 1] = (obsSediment[i + 1] as number) + sediment * w10; + obsFlow[i + n] = (obsFlow[i + n] as number) + water * w01; + obsSediment[i + n] = (obsSediment[i + n] as number) + sediment * w01; + obsFlow[i + n + 1] = (obsFlow[i + n + 1] as number) + water * w11; + obsSediment[i + n + 1] = (obsSediment[i + n + 1] as number) + sediment * w11; } - dx = dx * inertia - old.dx * (1 - inertia); - dz = dz * inertia - old.dz * (1 - inertia); - let length = Math.hypot(dx, dz); + dx = dx * inertia - old.dx * oneMinusInertia; + dz = dz * inertia - old.dz * oneMinusInertia; + // Scale before squaring, retaining hypot's two-component arithmetic without its varargs path. + const largest = Math.max(Math.abs(dx), Math.abs(dz)); + const scaledX = Math.abs(dx) / largest; + const scaledZ = Math.abs(dz) / largest; + let length = + largest === 0 || largest === Number.POSITIVE_INFINITY + ? largest + : Math.sqrt(scaledX * scaledX + scaledZ * scaledZ) * largest; if (length < 1e-9) { const angle = rnd() * Math.PI * 2; dx = Math.cos(angle); @@ -231,19 +351,22 @@ export function hydraulic( sediment = 0; break; } - const next = get(nx, nz); + const next = get(nx, nz, probeNext); const dh = next.value - old.value; - const cap = Math.max(-dh, 0.005 * cell) * speed * water * capacity; + const slope = -dh; + const cap = (slope > minSlope ? slope : minSlope) * speed * water * capacity; if (dh > 0 || sediment > cap) { - const amount = dh > 0 ? Math.min(dh, sediment) : (sediment - cap) * deposition; + const amount = dh > 0 ? (dh < sediment ? dh : sediment) : (sediment - cap) * deposition; spread(old.ix, old.iz, amount); sediment -= amount; } else { - const amount = Math.max(0, Math.min((cap - sediment) * erosion, -dh)); + const bite = (cap - sediment) * erosion; + const amount = bite < slope ? bite : slope; sediment -= spread(old.ix, old.iz, -amount, next.value); } - speed = Math.sqrt(Math.max(0.01, speed * speed - dh * 3)); - water *= 1 - evaporation; + const sq = speed * speed - dh * 3; + speed = Math.sqrt(sq > 0.01 ? sq : 0.01); + water *= oneMinusEvaporation; x = nx; z = nz; if (water < 0.02) break; @@ -251,7 +374,7 @@ export function hydraulic( // A live droplet at the integration cutoff is still carrying its load. Dumping that entire // load here builds artificial sediment pillars; only evaporation settles the remainder. if (sediment > 0 && water < 0.02) { - const end = get(clamp(x, 0, n - 1.001), clamp(z, 0, n - 1.001)); + const end = get(clamp(x, 0, n - 1.001), clamp(z, 0, n - 1.001), probeOld); spread(end.ix, end.iz, sediment); } } diff --git a/packages/terrain/src/core/export.ts b/packages/terrain/src/core/export.ts index 9bb91637a2..c7106cd6dc 100644 --- a/packages/terrain/src/core/export.ts +++ b/packages/terrain/src/core/export.ts @@ -101,10 +101,20 @@ const json = (name: string, data: unknown): IExportFile => ({ * @constraint authoring data only; the game owns appearance, physics and rendering * @example const terrain = new Terrain({ resolution: 17 }); const file = await makeExport(terrain.evaluate(), terrain.toJSON(), "project"); */ -export async function makeExport( +export function makeExport( + state: ITerrainState | null, + recipe: ITerrainDocument, + kind: "project", +): Promise; +export function makeExport( state: ITerrainState, recipe: ITerrainDocument, kind: ExportKind, +): Promise; +export async function makeExport( + state: ITerrainState | null, + recipe: ITerrainDocument, + kind: ExportKind, ): Promise { const result = (name: string, type: string, bytes: Uint8Array): IExportArchive => ({ name, @@ -113,6 +123,7 @@ export async function makeExport( }); if (kind === "project") return result("terrain-recipe.json", "application/json", json("", recipe).bytes); + if (!state) throw new Error("Wait for initial build"); if (kind === "png") return result("terrain-height16.png", "image/png", await encodeHeightPNG(state)); if (kind === "glb") return result("terrain.glb", "model/gltf-binary", encodeGLB(bakeMesh(state))); diff --git a/packages/terrain/src/core/io.ts b/packages/terrain/src/core/io.ts index e6fbaaba27..d2b28d59b6 100644 --- a/packages/terrain/src/core/io.ts +++ b/packages/terrain/src/core/io.ts @@ -270,7 +270,9 @@ export async function decodeHeightPNG( const text = decoder.decode(data); if (text.startsWith("StrataHeightRange\0")) { try { - metadata = JSON.parse(text.slice(18)); + const parsed = JSON.parse(text.slice(18)); + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw Error(); + metadata = parsed; } catch { throw Error("Invalid PNG height range metadata"); } @@ -403,7 +405,7 @@ export function encodeGLB(input: IBakedMesh | readonly IBakedMesh[]) { } const binary = join(chunks); const doc = { - asset: { version: "2.0", generator: "Strata Terrain 0.1.0" }, + asset: { version: "2.0", generator: "@threenative/terrain 0.1.0" }, scene: 0, scenes: [{ nodes: meshes.map((_, i) => i) }], nodes: meshes.map((m, i) => ({ mesh: i, name: m.name ?? `Terrain ${i}` })), diff --git a/packages/terrain/src/core/operations.ts b/packages/terrain/src/core/operations.ts index 33e4fcb167..b518752115 100644 --- a/packages/terrain/src/core/operations.ts +++ b/packages/terrain/src/core/operations.ts @@ -833,7 +833,7 @@ export function finalizeState(s: ITerrainState): ITerrainState { accepted.push({ id: `${rule.id}:candidate:${seed}:${attempt}`, layer: rule.id, - asset: rule.asset ?? "pine", + asset: rule.asset, position: [x, h + (rule.offsetY ?? 0), z], rotation: yaw, scale: itemScale, @@ -845,7 +845,7 @@ export function finalizeState(s: ITerrainState): ITerrainState { bucket.push(index); buckets.set(key, bucket); } - s.instances.push(...accepted); + for (const instance of accepted) s.instances.push(instance); if (accepted.length < count) s.diagnostics.push( `Scatter '${rule.id}': placed ${accepted.length}/${count}; masks or spacing exhausted the attempt budget.`, diff --git a/packages/terrain/src/core/terrain.ts b/packages/terrain/src/core/terrain.ts index d9527d533b..e586c8492c 100644 --- a/packages/terrain/src/core/terrain.ts +++ b/packages/terrain/src/core/terrain.ts @@ -63,8 +63,8 @@ export class TerrainEvaluator { validateDocument(doc); validateConfig({ ...doc.config, resolution }); const keys: string[] = []; - let key = JSON.stringify({ ...doc.config, resolution }); - for (const layer of doc.layers) { + let key = this.limit > 0 ? JSON.stringify({ ...doc.config, resolution }) : ""; + for (const layer of this.limit > 0 ? doc.layers : []) { key += `|${JSON.stringify(layer)}`; keys.push(key); } @@ -433,7 +433,7 @@ export class Terrain { biome(options: OperationOptions<"biome"> = {}): this { return this._operation("biome", options); } - scatter(options: OperationOptions<"scatter"> = {}): this { + scatter(options: OperationOptions<"scatter">): this { return this._operation("scatter", options); } clear(options: OperationOptions<"clear">): this { diff --git a/packages/terrain/src/core/types.ts b/packages/terrain/src/core/types.ts index 6fb9eb708c..58b44478fc 100644 --- a/packages/terrain/src/core/types.ts +++ b/packages/terrain/src/core/types.ts @@ -218,7 +218,7 @@ export interface IBiomeParams extends IBrushParams { } export interface IScatterParams extends IOperationBase { - readonly asset?: string; + readonly asset: string; readonly count?: number; /** Guaranteed within one rule, never across rules. */ readonly minDistance?: number; diff --git a/packages/terrain/src/core/validation.ts b/packages/terrain/src/core/validation.ts index b06af473b0..0bb314cbb5 100644 --- a/packages/terrain/src/core/validation.ts +++ b/packages/terrain/src/core/validation.ts @@ -133,7 +133,10 @@ function fail(message: string): never { /** Rejects anything a recipe cannot round-trip: non-finite numbers, views, cycles, unsafe keys. */ export function finiteTree(value: unknown, path = "value", depth = 0): void { if (depth > 32) fail(`${path}: nesting is too deep`); - if (typeof value === "number" && !Number.isFinite(value)) fail(`${path} must be finite`); + if (typeof value === "number") { + if (!Number.isFinite(value)) fail(`${path} must be finite`); + return; + } if ( value === undefined || typeof value === "function" || @@ -143,9 +146,15 @@ export function finiteTree(value: unknown, path = "value", depth = 0): void { fail(`${path} is not JSON-serializable`); if (value && typeof value === "object") { if (ArrayBuffer.isView(value)) fail(`${path}: use a plain array in recipes`); - for (const [key, entry] of Object.entries(value)) { - if (["__proto__", "prototype", "constructor"].includes(key)) + for (const key of Object.keys(value)) { + if (key === "__proto__" || key === "prototype" || key === "constructor") fail(`${path}: unsafe object key`); + const entry = (value as Record)[key]; + if (typeof entry === "number") { + if (depth + 1 > 32) fail(`${path}.${key}: nesting is too deep`); + if (!Number.isFinite(entry)) fail(`${path}.${key} must be finite`); + continue; + } finiteTree(entry, `${path}.${key}`, depth + 1); } } @@ -513,6 +522,8 @@ export function validateLayer(layer: Layer): void { } if (params.name !== undefined) identifier(params.name, "name"); if (params.asset !== undefined) identifier(params.asset, "asset"); + if (layer.type === "scatter" && typeof params.asset !== "string") + fail("scatter requires an asset identifier"); if (["heightmap", "paste"].includes(layer.type) && !params.data) fail(`${layer.type} requires heightmap data`); const data = params.data as diff --git a/packages/terrain/src/core/world-package.ts b/packages/terrain/src/core/world-package.ts index 6d11449816..bc0078ea07 100644 --- a/packages/terrain/src/core/world-package.ts +++ b/packages/terrain/src/core/world-package.ts @@ -89,6 +89,7 @@ const SCALE_TOLERANCE = 1e-3; const SLOTS = MATERIAL_IDS.map((id, channel) => ({ channel, component: COMPONENTS[channel % COMPONENTS.length] as Component, + componentIndex: channel % COMPONENTS.length, id, plane: Math.floor(channel / COMPONENTS.length), })); @@ -132,22 +133,42 @@ function uniformScale(asset: string, id: string, scale: readonly [number, number return mean; } -function placementRecord(item: IPlacement): number[] { +function writePlacementRecord( + out: Float32Array, + offset: number, + item: IPlacement, + extent: IWorldPackageManifest["extent"], +): void { if (item.transform === undefined && !Number.isFinite(item.rotation)) throw Error(`World package: placement '${item.id}' has a non-finite rotation.`); const { position, quaternion, scale } = poseOf(item); + if ( + !Number.isFinite(position[0]) || + !Number.isFinite(position[1]) || + !Number.isFinite(position[2]) + ) + throw Error( + `World package: placement '${item.id}' has a non-finite position [${position.join(", ")}].`, + ); + if ( + position[0] < extent.minX || + position[0] > extent.minX + extent.sizeX || + position[2] < extent.minZ || + position[2] > extent.minZ + extent.sizeZ + ) + throw Error( + `World package: placement '${item.id}' position [${position[0]}, ${position[2]}] is outside extent bounds [${extent.minX}..${extent.minX + extent.sizeX}, ${extent.minZ}..${extent.minZ + extent.sizeZ}].`, + ); const length = Math.hypot(...quaternion); if (!(length > 0)) throw Error(`World package: placement '${item.id}' has a zero quaternion.`); - return [ - position[0], - position[1], - position[2], - quaternion[0] / length, - quaternion[1] / length, - quaternion[2] / length, - quaternion[3] / length, - uniformScale(item.asset, item.id, scale), - ]; + out[offset] = position[0]; + out[offset + 1] = position[1]; + out[offset + 2] = position[2]; + out[offset + 3] = quaternion[0] / length; + out[offset + 4] = quaternion[1] / length; + out[offset + 5] = quaternion[2] / length; + out[offset + 6] = quaternion[3] / length; + out[offset + 7] = uniformScale(item.asset, item.id, scale); } /** @@ -197,7 +218,7 @@ function placementCells( for (const [asset, items] of [...cell.items.entries()].sort(byId)) { runs.push({ asset, count: items.length, offset: written }); for (const item of items) { - records.set(placementRecord(item), written * RECORD_VALUES); + writePlacementRecord(records, written * RECORD_VALUES, item, extent); written += 1; } } @@ -223,7 +244,12 @@ export function bakeWorldPackage( options: IBakeWorldPackageOptions, ): IBakedWorldPackage { const { size, resolution } = state; - const { assets, cellSize = 64, layers, names } = options; + const cellSize = options.cellSize ?? 64; + if (!Number.isFinite(cellSize) || cellSize <= 0) + throw Error( + `World package: cellSize must be a positive finite number, got ${String(cellSize)}.`, + ); + const { assets, layers, names } = options; const heightmapName = names?.heightmap ?? "heightmap.u16"; const placementsName = names?.placements ?? "placements.bin"; const splatName = names?.splat ?? "splat.rgba"; @@ -299,16 +325,15 @@ function splatBytes(state: ITerrainState, resolution: number): Uint8Array { const bytes = new Uint8Array(planeBytes * PLANES); for (let row = 0; row < resolution; row += 1) { const source = (resolution - 1 - row) * resolution; + const rowOffset = row * resolution * 4; for (let column = 0; column < resolution; column += 1) { const sample = (source + column) * MATERIAL_IDS.length; + const rowCol = rowOffset + column * 4; for (const slot of SLOTS) { const weight = state.splat[sample + slot.channel] as number; - bytes[ - slot.plane * planeBytes + - row * resolution * 4 + - column * 4 + - COMPONENTS.indexOf(slot.component) - ] = Math.round(Math.min(1, Math.max(0, weight)) * 255); + bytes[slot.plane * planeBytes + rowCol + slot.componentIndex] = Math.round( + (weight <= 0 ? 0 : weight >= 1 ? 1 : weight) * 255, + ); } } } diff --git a/packages/terrain/src/editor/app.js b/packages/terrain/src/editor/app.js deleted file mode 100644 index 769f16d0c3..0000000000 --- a/packages/terrain/src/editor/app.js +++ /dev/null @@ -1,1481 +0,0 @@ -import { - MATERIAL_IDS, - Mask, - Terrain, - applyPlacementOverrides, - bakeTerrain, - decodeHeightPNG, - decodeRAW16, - makeExport, -} from "../index.js"; -import { fillIcons, icon } from "./icons.js"; -import { PRESETS, createPreset } from "./presets.js"; -import { inspectSpatial } from "./spatialInspector.js"; -export function mountRecoveredEditor({ - initial, - providedView, - commit, - getSnapshot, - cameraOperation, - environmentOperation, - assetOperation, - subscribe, - onEvaluated, - materialColours, -}) { - const materialColors = materialColours; - const abort = new AbortController(); - let disposed = false; - const $ = (id) => document.getElementById(id); - const escapeHTML = (s) => - String(s).replace( - /[&<>"']/g, - (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c], - ); - const tools = [ - ["sculpt", "Sculpt", "Raise the ground. Hold Shift to lower."], - ["smooth", "Smooth", "Soften spikes and blend transitions."], - ["flatten", "Flatten", "Make a building pad at a chosen elevation."], - ["ramp", "Ramp", "Click two endpoints to connect their elevations."], - ["stamp", "Stamp", "Place a transformed procedural terrain feature."], - ["erode", "Erode", "Run a local thermal or hydraulic erosion pass."], - ["paint", "Paint", "Paint a material or a named biome mask."], - ["scatter", "Scatter", "Place deterministic assets inside the brush."], - ["spline", "Spline", "Click control points. Enter finishes the road or river."], - ["water", "Water", "Flood a connected basin from the clicked point."], - [ - "select", - "Select", - "Select a prop or recipe landform. Drag its handles or enter precise values.", - ], - ]; - const options = { - flatten: { height: 24 }, - stamp: { shape: "mountain", mirrorX: false }, - erode: { method: "thermal" }, - paint: { material: "dirt", mode: "material", biome: "forest" }, - scatter: { asset: "pine", count: 180, erase: false }, - spline: { kind: "road", width: 12, depth: 4 }, - water: { kind: "lake", level: 5 }, - ramp: { width: 22 }, - }; - const terrain = Terrain.fromJSON(initial.document.recipe); - let state = null; - let renderedRecipe = null; - const view = providedView; - let selected = terrain.layers[0]?.id; - let active = "sculpt"; - let preset = "alpine"; - let navigation = false; - let points = []; - let stroke = null; - let busy = false; - let sequence = 0; - let workerBusy = false; - let evaluationRequests = 0; - let pending = null; - let worker = null; - let exportWorker = null; - let exportReject = null; - let timer = null; - let toastTimer = null; - const localSaved = false; - fillIcons(); - function toast(message) { - clearTimeout(toastTimer); - $("toast").textContent = message; - $("toast").classList.remove("hidden"); - toastTimer = setTimeout(() => $("toast").classList.add("hidden"), 4300); - } - function attempt(fn) { - try { - return fn(); - } catch (e) { - toast(e.message); - return undefined; - } - } - function setBusy(value, text = "Building terrain") { - busy = value; - $("busy-status").classList.toggle("hidden", !value); - $("busy-text").textContent = text; - } - function workerURL() { - return new URL("./worker.js", import.meta.url); - } - function newWorker() { - const w = new Worker(workerURL(), { type: "module" }); - w.onmessage = async ({ data }) => { - if (data.type === "progress") { - if (data.id === sequence) - $("busy-text").textContent = - `Evaluating ${data.progress.index + 1}/${data.progress.total}`; - return; - } - // Imported models load asynchronously; the preview waits for them rather than failing a - // placement that names an asset still on its way. The worker stays busy meanwhile. - if (data.id === sequence && data.type === "evaluated") - await view.assetsReady?.().catch(() => {}); - workerBusy = false; - if (data.id === sequence) { - if (data.type === "error") { - toast(data.error); - $("save-status").textContent = "Build failed · last preview retained"; - } else if (data.type === "evaluated") { - const ready = attempt(() => { - data.state = view.update(data.state) ?? data.state; - return true; - }); - if (!ready) { - $("save-status").textContent = "Preview failed · last preview retained"; - } else { - state = data.state; - renderedRecipe = JSON.stringify(terrain.toJSON()); - renderedRevision = revision; - onEvaluated?.({ revision, state, ms: data.ms }); - $("mesh-stats").textContent = - `${(2 * (state.resolution - 1) ** 2).toLocaleString()} tris · ${state.instances.length.toLocaleString()} instances`; - $("eval-time").textContent = `${Math.round(data.ms)} ms · CPU worker`; - $("diagnostic-count").textContent = state.diagnostics.length; - $("diagnostic-list").textContent = state.diagnostics.join("\n") || "No diagnostics."; - $("save-status").textContent = - `Rendered ${renderedRevision.slice(0, 8)} · saved on disk`; - } - } - } - if (pending) dispatch(); - else setBusy(false); - }; - w.onerror = (e) => { - e.preventDefault(); - workerBusy = false; - pending = null; - setBusy(false); - toast(`Worker failed: ${e.message}. Serve the source over HTTP or use the standalone HTML.`); - }; - return w; - } - function dispatch() { - if (workerBusy || !pending) return; - const job = pending; - pending = null; - try { - worker ??= newWorker(); - workerBusy = true; - worker.postMessage(job); - evaluationRequests++; - } catch (e) { - workerBusy = false; - setBusy(false); - toast(e.message); - } - } - function build() { - if (disposed) return; - sequence++; - pending = { - id: sequence, - type: "evaluate", - recipe: terrain.toJSON(), - resolution: Number($("preview-resolution").value), - }; - setBusy(true); - clearTimeout(timer); - timer = setTimeout(dispatch, 35); - } - function cancelBuild() { - clearTimeout(timer); - pending = null; - sequence++; - worker?.terminate(); - worker = null; - workerBusy = false; - exportWorker?.terminate(); - exportWorker = null; - exportReject?.(new Error("Export cancelled")); - exportReject = null; - for (const b of document.querySelectorAll("[data-export]")) { - b.disabled = false; - } - setBusy(false); - $("save-status").textContent = "Build cancelled · preview unchanged"; - toast("Build cancelled. Rebuild to evaluate the current stack."); - } - function updateHistory() { - $("undo-btn").disabled = !terrain.canUndo; - $("redo-btn").disabled = !terrain.canRedo; - } - function renderLayers() { - const layers = terrain.layers; - if (!layers.some((l) => l.id === selected)) selected = layers.at(-1)?.id; - $("layer-count").textContent = layers.length; - const list = $("layer-list"); - list.replaceChildren(); - layers.forEach((l, i) => { - const row = document.createElement("div"); - row.className = `layer-row${l.id === selected ? " selected" : ""}${l.enabled === false ? " disabled" : ""}`; - row.draggable = true; - row.dataset.id = l.id; - const index = document.createElement("span"); - index.className = "layer-index"; - index.textContent = String(i + 1).padStart(2, "0"); - const glyph = document.createElement("span"); - glyph.className = "layer-icon"; - glyph.innerHTML = icon( - l.type === "materials" ? "paint" : ["river", "road"].includes(l.type) ? "spline" : l.type, - ); - const label = document.createElement("div"); - label.className = "layer-label"; - const name = document.createElement("strong"); - const type = document.createElement("small"); - name.textContent = l.name ?? l.id; - type.textContent = l.type; - label.append(name, type); - const eye = document.createElement("button"); - eye.className = "layer-eye"; - eye.innerHTML = icon(l.enabled === false ? "eye-off" : "eye"); - eye.title = l.enabled === false ? "Enable layer" : "Disable layer"; - eye.setAttribute("aria-label", `${eye.title}: ${l.name ?? l.id}`); - eye.onclick = (e) => { - e.stopPropagation(); - terrain.toggle(l.id, l.enabled === false); - }; - row.append(index, glyph, label, eye); - row.onclick = () => { - selected = l.id; - renderLayers(); - if (active === "select") view.selectLayer?.(l.id); - }; - row.ondragstart = (e) => e.dataTransfer.setData("text/plain", l.id); - row.ondragover = (e) => e.preventDefault(); - row.ondrop = (e) => { - e.preventDefault(); - const id = e.dataTransfer.getData("text/plain"); - attempt(() => terrain.move(id, i)); - }; - list.append(row); - }); - renderInspector(); - updateHistory(); - } - function renderInspector() { - const layer = selected ? terrain.layer(selected) : null; - for (const id of [ - "selected-name", - "selected-opacity", - "selected-json", - "apply-layer", - "layer-up", - "layer-down", - "layer-duplicate", - "layer-delete", - ]) - $(id).disabled = !layer; - $("selected-type").textContent = layer ? layer.type.toUpperCase() : "LAYER PROPERTIES"; - $("selected-name").value = layer?.name ?? layer?.id ?? ""; - $("selected-opacity").value = layer?.opacity ?? 1; - $("opacity-value").textContent = `${Math.round((layer?.opacity ?? 1) * 100)}%`; - $("selected-json").value = layer - ? JSON.stringify( - { params: layer.params, ...(layer.mask ? { mask: layer.mask } : {}) }, - null, - 2, - ) - : ""; - $("layer-error").textContent = ""; - } - // Imported models load asynchronously. When one the recipe places arrives, changes or goes, the - // preview rebuilds once it is ready; a document that names no placed asset costs no rebuild. - let assetSignature = JSON.stringify(initial.document.assets ?? []); - function syncAssets(document_) { - const next = JSON.stringify(document_.assets ?? []); - if (next === assetSignature) return; - const before = new Map( - JSON.parse(assetSignature).map((entry) => [entry.id, JSON.stringify(entry)]), - ); - const after = new Map( - (document_.assets ?? []).map((entry) => [entry.id, JSON.stringify(entry)]), - ); - const changed = new Set( - [...before.keys(), ...after.keys()].filter((id) => before.get(id) !== after.get(id)), - ); - assetSignature = next; - const used = () => terrain.toJSON().layers.some((layer) => changed.has(layer.params?.asset)); - Promise.resolve(view.assetsReady?.()) - .catch(() => {}) - .then(() => { - if (disposed) return; - renderOptions(); - if (used()) build(); - }); - } - let revision = initial.revision; - let renderedRevision = null; - let saving = false; - let loading = false; - function useSnapshot(snapshot) { - if (snapshot.diagnostic) { - toast(snapshot.diagnostic); - $("save-status").textContent = "Invalid disk save · valid preview retained"; - return; - } - if (snapshot.revision === revision) return; - const recipeChanged = - JSON.stringify(terrain.toJSON()) !== JSON.stringify(snapshot.document.recipe); - const ready = attempt(() => { - view.setDocument(snapshot.document, snapshot.revision); - return true; - }); - if (!ready) { - $("save-status").textContent = "Preview failed · last preview retained"; - return; - } - revision = snapshot.revision; - renderCameras(snapshot.document); - renderEnvironment(); - renderAssets(); - syncAssets(snapshot.document); - if (!recipeChanged) { - const matchesPreview = renderedRecipe === JSON.stringify(snapshot.document.recipe); - if (state && matchesPreview) - state = applyPlacementOverrides(state, snapshot.document.placementOverrides ?? {}); - if (matchesPreview && !busy) { - renderedRevision = revision; - $("save-status").textContent = `Applied ${revision.slice(0, 8)} · saved on disk`; - } else { - $("save-status").textContent = busy - ? `Requested ${revision.slice(0, 8)} · building` - : "Saved recipe not rendered · last preview retained"; - } - return; - } - loading = true; - terrain.loadJSON(snapshot.document.recipe); - loading = false; - renderLayers(); - build(); - } - async function changed() { - renderLayers(); - if (loading) return; - if (saving) { - loading = true; - terrain.loadJSON(saved.document.recipe); - loading = false; - toast("Wait for the current save"); - return; - } - saving = true; - document.body.dataset.saving = "true"; - try { - const next = await commit({ - baseRevision: revision, - document: { ...saved.document, recipe: terrain.toJSON() }, - }); - saved = next; - revision = next.revision; - view.setDocument(next.document, next.revision); - $("save-status").textContent = `Requested ${revision.slice(0, 8)} · building`; - build(); - } catch (error) { - toast(error.message); - const next = await getSnapshot(); - saved = next; - revision = next.revision; - view.setDocument(next.document, next.revision); - loading = true; - terrain.loadJSON(next.document.recipe); - loading = false; - renderLayers(); - build(); - } finally { - saving = false; - delete document.body.dataset.saving; - } - } - let saved = initial; - subscribe((snapshot) => { - if (!saving) { - saved = snapshot; - useSnapshot(snapshot); - } - }); - terrain.subscribe(changed); - function brush() { - return { - radius: Number($("brush-radius").value), - strength: Number($("brush-strength").value), - falloff: Number($("brush-falloff").value), - shape: $("brush-shape").value, - rotation: Number($("brush-rotation").value), - spacing: Number($("brush-spacing").value), - jitter: Number($("brush-jitter").value), - }; - } - function optionSelect(label, key, choices, value) { - return ``; - } - function optionNumber(label, key, value, step = 1) { - return ``; - } - function renderOptions() { - const o = options[active] ?? {}; - let html = ""; - if (active === "flatten") - html = `${optionNumber("Target elevation (m)", "height", o.height)}

Use the terrain Y coordinate as a guide.

`; - if (active === "stamp") - html = `${optionSelect("Stamp shape", "shape", ["mountain", "crater", "ridge", "valley", "mesa", "dune"], o.shape)}

Strength controls amplitude. Rotate in Brush dynamics. JSON also supports anisotropic scale and mirrored heightmaps.

`; - if (active === "erode") - html = `${optionSelect("Simulation", "method", ["thermal", "hydraulic"], o.method)}

Evaluates on the current heightfield, then blends inside the brush footprint.

`; - if (active === "paint") { - html = optionSelect("Paint target", "mode", ["material", "biome"], o.mode); - if (o.mode === "biome") - html += ``; - else - html += `
${MATERIAL_IDS.map((id, i) => ``).join("")}
`; - } - if (active === "scatter") { - const assets = view.propAssets?.() ?? ["pine", "boulder", "grass"]; - html = `${ - optionSelect("Asset ID", "asset", assets, assets.includes(o.asset) ? o.asset : assets[0]) + - optionNumber("Requested instances", "count", o.count) - }

The palette is this project's own assets; register another through the view.

`; - } - if (active === "spline") - html = `${ - optionSelect("Spline type", "kind", ["road", "river"], o.kind) + - optionNumber("Width (m)", "width", o.width) + - (o.kind === "river" ? optionNumber("River depth (m)", "depth", o.depth) : "") - }

Control points retain their elevations. Edit the layer JSON for precise profiles.

`; - if (active === "water") - html = `${ - optionSelect("Water body", "kind", ["lake", "ocean"], o.kind) + - optionNumber("Water level (m)", "level", o.level, 0.5) - }

Lake seed must be below the water level. Oceans flood from map boundaries.

`; - if (active === "ramp") - html = `${optionNumber("Ramp width (m)", "width", o.width)}

First click: start. Second click: end. The shoulder blends into the terrain.

`; - $("tool-options").innerHTML = html; - } - function selectTool(id) { - stroke = null; - active = id; - document.body.dataset.terrainTool = id; - navigation = false; - view.setNavigation(false); - view.setSelection?.(id === "select"); - view.setBrush(null); - $("navigate-btn").classList.remove("active"); - points = []; - showPoints(); - const i = tools.findIndex((t) => t[0] === id); - $("brush-title").textContent = - id === "select" ? "Select a prop or landform" : `${tools[i][1]} terrain`; - $("brush-desc").textContent = tools[i][2]; - $("tool-key").textContent = id === "select" ? "Q" : (i + 1) % 10; - for (const b of document.querySelectorAll(".tool-button")) { - b.classList.toggle("active", b.dataset.tool === id); - } - renderOptions(); - $("canvas-help").innerHTML = - `${icon("mouse")}${escapeHTML(tools[i][2])} · Right-drag navigates`; - } - for (let i = 0; i < tools.length; i++) { - const [id, name] = tools[i]; - const b = document.createElement("button"); - b.className = "tool-button"; - b.dataset.tool = id; - b.innerHTML = `${icon(id === "select" ? "mouse" : id)}${name}${id === "select" ? "Q" : (i + 1) % 10}`; - b.onclick = () => selectTool(id); - $("tool-grid").append(b); - } - $("tool-options").onchange = (e) => { - const key = e.target.dataset.option; - if (!key) return; - options[active][key] = - e.target.type === "checkbox" - ? e.target.checked - : e.target.type === "number" - ? Number(e.target.value) - : e.target.value; - if (key === "mode" || key === "kind") renderOptions(); - }; - $("tool-options").onclick = (e) => { - const b = e.target.closest("[data-material]"); - if (b) { - options.paint.material = b.dataset.material; - renderOptions(); - } - }; - for (const [id, out, format] of [ - ["brush-radius", "radius-value", (v) => `${v} m`], - ["brush-strength", "strength-value", (v) => `${Math.round(v * 100)}%`], - ["brush-falloff", "falloff-value", (v) => `${Math.round(v * 100)}%`], - ["brush-rotation", "rotation-value", (v) => `${v}°`], - ["brush-spacing", "spacing-value", (v) => `${Math.round(v * 100)}%`], - ["brush-jitter", "jitter-value", (v) => `${Math.round(v * 100)}%`], - ]) - $(id).oninput = () => { - $(out).textContent = format(Number($(id).value)); - }; - function addStroke(path, lower) { - if (!path.length) return; - const b = brush(); - const at = path[0]; - const mask = Mask.circle(at, b.radius, b.falloff); - const label = `${tools.find((t) => t[0] === active)[1]} stroke`; - terrain.transaction(() => { - if (["sculpt", "smooth", "flatten"].includes(active)) { - const p = { ...b, points: path, label }; - if (active === "sculpt") p.strength = b.strength * 22 * (lower ? -1 : 1); - if (active === "smooth") p.iterations = 4; - if (active === "flatten") p.height = options.flatten.height; - terrain[active](p); - } else if (active === "stamp") - terrain.stamp({ - label: `${options.stamp.shape} stamp`, - at, - radius: b.radius, - amplitude: b.strength * 130, - shape: options.stamp.shape, - rotation: b.rotation, - roughness: 0.18, - falloff: b.falloff, - }); - else if (active === "erode") - terrain.erode({ - label: `${options.erode.method} erosion`, - ...b, - at, - method: options.erode.method, - iterations: 14, - droplets: 1600, - maxSteps: 45, - opacity: b.strength, - strength: 1, - }); - else if (active === "paint") { - const p = { ...b, points: path, label }; - if (options.paint.mode === "biome") - terrain.biome({ ...p, name: options.paint.biome, value: lower ? 0 : 1 }); - else terrain.paint({ ...p, material: options.paint.material }); - } else if (active === "scatter") { - const o = options.scatter; - if (o.erase) - terrain.clear({ label: `Clear ${o.asset}`, target: "scatter", asset: o.asset, mask }); - else - terrain.scatter({ - label: `Scatter ${o.asset}`, - asset: o.asset, - count: o.count, - minDistance: o.asset === "pine" ? 4 : 1.5, - scale: [0.7, 1.35], - maxSlope: o.asset === "boulder" ? 65 : 36, - mask, - }); - } else if (active === "water") { - const o = options.water; - terrain.water({ - label: o.kind === "lake" ? "Lake water" : "Ocean water", - kind: o.kind, - at, - radius: o.kind === "ocean" ? terrain.config.size : b.radius, - level: o.level, - }); - } - }); - selected = terrain.layers.at(-1)?.id; - renderLayers(); - } - function showPoints() { - view.showSpline(points); - $("spline-bar").classList.toggle("hidden", !points.length); - $("spline-count").textContent = - `${points.length} control point${points.length === 1 ? "" : "s"}`; - $("finish-spline").disabled = points.length < 2; - } - function finishSpline() { - if (points.length < 2) return; - attempt(() => { - if (active === "ramp") - terrain.ramp({ - label: "Terrain ramp", - from: points[0], - to: points[1], - width: options.ramp.width, - shoulder: options.ramp.width * 0.5, - }); - else { - const o = options.spline; - const path = points.map((p) => [...p]); - if (o.kind === "river") - for (let i = 1; i < path.length; i++) - path[i][1] = Math.min(path[i][1], path[i - 1][1] - 0.1); - terrain[o.kind]({ - label: o.kind === "river" ? "River spline" : "Road spline", - points: path, - width: o.width, - shoulder: o.width * 0.7, - ...(o.kind === "river" ? { depth: o.depth, enforceDownhill: true } : {}), - }); - } - points = []; - showPoints(); - selected = terrain.layers.at(-1)?.id; - renderLayers(); - }); - } - const host = $("viewport"); - host.addEventListener("pointerdown", (e) => { - if (saving) return; - if (e.target !== host && !e.target.classList.contains("render-canvas")) return; - if (e.button !== 0 || e.altKey || navigation) return; - if (active === "select") return; - const hit = view.pick(e.clientX, e.clientY); - if (!hit) return; - if (active === "spline" || active === "ramp") { - points.push(hit); - showPoints(); - if (active === "ramp" && points.length === 2) finishSpline(); - return; - } - stroke = { pointer: e.pointerId, path: [[hit[0], hit[2]]], lower: e.shiftKey }; - host.setPointerCapture(e.pointerId); - e.preventDefault(); - }); - host.addEventListener("pointermove", (e) => { - const hit = view.pick(e.clientX, e.clientY); - if (!hit) { - view.setBrush(null); - return; - } - $("coordinates").textContent = - `X ${hit[0].toFixed(1)} · Y ${hit[1].toFixed(1)} · Z ${hit[2].toFixed(1)} m`; - const b = brush(); - view.setBrush(navigation || active === "select" ? null : { ...b, at: [hit[0], hit[2]] }); - if (stroke && ["sculpt", "smooth", "flatten", "paint"].includes(active)) { - const last = stroke.path.at(-1); - if ( - Math.hypot(last[0] - hit[0], last[1] - hit[2]) > Math.max(0.3, b.radius * 0.04) && - stroke.path.length < 2048 - ) - stroke.path.push([hit[0], hit[2]]); - } - }); - host.addEventListener("pointerup", (e) => { - if (stroke?.pointer === e.pointerId) { - const current = stroke; - stroke = null; - if (host.hasPointerCapture(e.pointerId)) host.releasePointerCapture(e.pointerId); - attempt(() => addStroke(current.path, current.lower)); - } - }); - host.addEventListener("pointercancel", () => { - stroke = null; - }); - host.addEventListener("pointerleave", () => { - if (!stroke) view.setBrush(null); - }); - $("finish-spline").onclick = finishSpline; - $("cancel-spline").onclick = () => { - points = []; - showPoints(); - }; - $("undo-btn").onclick = () => terrain.undo(); - $("redo-btn").onclick = () => terrain.redo(); - $("selected-opacity").oninput = () => { - $("opacity-value").textContent = `${Math.round(Number($("selected-opacity").value) * 100)}%`; - }; - $("selected-opacity").onchange = () => - attempt(() => terrain.update(selected, { opacity: Number($("selected-opacity").value) })); - $("selected-name").onchange = () => - attempt(() => terrain.update(selected, { name: $("selected-name").value })); - $("apply-layer").onclick = () => { - try { - const patch = JSON.parse($("selected-json").value); - if ( - !patch || - typeof patch !== "object" || - Array.isArray(patch) || - Object.keys(patch).some((k) => !["params", "mask"].includes(k)) - ) - throw Error('Use {"params": {...}, "mask": {...}}'); - const old = terrain.layer(selected); - terrain.add({ - ...old, - params: patch.params ?? old.params, - mask: patch.mask ?? { type: "all" }, - }); - $("layer-error").textContent = ""; - } catch (e) { - $("layer-error").textContent = e.message; - } - }; - $("layer-up").onclick = () => - attempt(() => - terrain.move(selected, Math.max(0, terrain.layers.findIndex((l) => l.id === selected) - 1)), - ); - $("layer-down").onclick = () => - attempt(() => - terrain.move( - selected, - Math.min(terrain.layers.length - 1, terrain.layers.findIndex((l) => l.id === selected) + 1), - ), - ); - $("layer-delete").onclick = () => attempt(() => terrain.remove(selected)); - $("layer-duplicate").onclick = () => - attempt(() => { - const layer = terrain.layer(selected); - let i = 1; - while (terrain.layers.some((l) => l.id === `${layer.id.slice(0, 85)}-copy-${i}`)) i++; - layer.id = `${layer.id.slice(0, 85)}-copy-${i}`; - layer.name = `${(layer.name ?? layer.type).slice(0, 85)} copy`; - terrain.add(layer); - selected = layer.id; - renderLayers(); - }); - for (const b of document.querySelectorAll("[data-panel]")) - b.onclick = () => { - for (const x of document.querySelectorAll("[data-panel]")) { - x.classList.toggle("active", x === b); - } - $("tools-panel").classList.toggle("hidden", b.dataset.panel !== "tools"); - $("generate-panel").classList.toggle("hidden", b.dataset.panel !== "generate"); - }; - for (const p of PRESETS) { - const b = document.createElement("button"); - b.className = `preset-card${p.id === preset ? " active" : ""}`; - b.dataset.preset = p.id; - b.innerHTML = `${icon(p.id === "desert" ? "terrace" : p.id === "island" ? "water" : "mountain")}${p.name}${p.tag}`; - b.onclick = () => { - preset = p.id; - for (const el of document.querySelectorAll("[data-preset]")) { - el.classList.toggle("active", el === b); - } - }; - $("preset-list").append(b); - } - function namePreset(id) { - const p = PRESETS.find((x) => x.id === id); - $("project-name").textContent = p.name; - $("landscape-title").textContent = p.name; - $("landscape-subtitle").textContent = p.subtitle; - $("landscape-tag").textContent = `LANDSCAPE / ${p.tag}`; - } - $("generate-world").onclick = () => - attempt(() => { - const next = createPreset(preset, { - resolution: terrain.config.resolution, - seed: Number($("seed-input").value), - size: Number($("world-size").value), - }); - terrain.loadJSON(next.toJSON()); - namePreset(preset); - selected = terrain.layers[0]?.id; - renderLayers(); - points = []; - showPoints(); - view.frame(); - toast("Landscape regenerated. Undo restores the previous stack."); - }); - const passes = [ - [ - "noise", - "Noise", - () => terrain.noise({ label: "Detail noise", amplitude: 4, scale: 30, warp: 7 }), - ], - [ - "mountain", - "Mountains", - () => - terrain.stamp({ - label: "Mountain mass", - at: [0, -80], - radius: [135, 90], - amplitude: 95, - shape: "mountain", - }), - ], - [ - "ramp", - "Valleys", - () => - terrain.stamp({ - label: "Valley basin", - at: [0, 0], - radius: [100, 150], - amplitude: 35, - shape: "valley", - }), - ], - [ - "erode", - "Erosion", - () => - terrain.erode({ - label: "Hydraulic erosion", - method: "hydraulic", - droplets: 8000, - maxSteps: 45, - }), - ], - [ - "biome", - "Biomes", - () => - terrain.biome({ - label: "Forest biome", - name: "forest", - mask: Mask.and(Mask.height(5, 100, 10), Mask.slope(0, 35, 8)), - }), - ], - [ - "scatter", - "Populate", - () => - terrain.scatter({ - label: "Forest population", - asset: "pine", - count: 800, - minDistance: 5, - mask: Mask.and(Mask.height(5, 100, 10), Mask.slope(0, 35, 8)), - }), - ], - ]; - for (const [ico, label, fn] of passes) { - const b = document.createElement("button"); - b.innerHTML = icon(ico) + label; - b.onclick = () => attempt(fn); - $("pass-grid").append(b); - } - $("navigate-btn").onclick = () => { - navigation = !navigation; - view.setNavigation(navigation); - $("navigate-btn").classList.toggle("active", navigation); - view.setBrush(null); - }; - $("view-angle").onchange = () => view.setView($("view-angle").value); - $("render-mode").onchange = () => view.setMode($("render-mode").value); - $("preview-resolution").onchange = build; - $("frame-btn").onclick = () => view.frame(); - $("rebuild-btn").onclick = build; - $("cancel-build").onclick = cancelBuild; - $("mobile-tools").onclick = () => { - document.body.dataset.mobilePanel = - document.body.dataset.mobilePanel === "tools" ? "" : "tools"; - }; - $("mobile-layers").onclick = () => { - document.body.dataset.mobilePanel = - document.body.dataset.mobilePanel === "layers" ? "" : "layers"; - }; - $("recipe-btn").onclick = () => { - $("recipe-editor").value = JSON.stringify(terrain.toJSON(), null, 2); - $("recipe-error").textContent = ""; - $("recipe-dialog").showModal(); - }; - $("close-recipe").onclick = () => $("recipe-dialog").close(); - $("apply-recipe").onclick = () => { - try { - terrain.loadJSON($("recipe-editor").value); - $("recipe-error").textContent = ""; - toast("Recipe validated and applied."); - } catch (e) { - $("recipe-error").textContent = e.message; - } - }; - $("copy-recipe").onclick = async () => { - try { - await navigator.clipboard.writeText($("recipe-editor").value); - toast("Recipe copied."); - } catch { - const ta = $("recipe-editor"); - ta.focus(); - ta.select(); - toast("Clipboard unavailable. JSON selected; press Ctrl/⌘ C."); - } - }; - function download(output) { - const blob = new Blob([output.bytes], { type: output.type }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = output.name; - a.click(); - setTimeout(() => URL.revokeObjectURL(url), 5000); - } - $("export-btn").onclick = () => { - $("export-error").textContent = ""; - $("export-dialog").showModal(); - }; - $("close-export").onclick = () => $("export-dialog").close(); - async function exportKind(kind) { - $("export-error").textContent = ""; - if (kind === "project") { - download(await makeExport(state, terrain.toJSON(), kind)); - return; - } - // The whole world comes from the live view, because only the view holds the resolved models, - // the grounded transforms and the baked water. The worker export below is terrain-only, and - // presenting that as a world is the thing this card exists to stop. - if (kind === "world") { - if (!view.exportCurrentWorld) throw Error("This view cannot export a world GLB"); - for (const b of document.querySelectorAll("[data-export]")) b.disabled = true; - setBusy(true, "Baking world GLB"); - try { - download(await view.exportCurrentWorld()); - toast("World GLB ready."); - } catch (e) { - $("export-error").textContent = e.message; - } finally { - for (const b of document.querySelectorAll("[data-export]")) b.disabled = false; - if (!workerBusy && !pending) setBusy(false); - } - return; - } - for (const b of document.querySelectorAll("[data-export]")) { - b.disabled = true; - } - setBusy(true, "Baking export"); - try { - const output = await new Promise((resolve, reject) => { - exportReject = reject; - exportWorker = new Worker(workerURL(), { type: "module" }); - exportWorker.onmessage = ({ data }) => { - if (data.type === "exported") resolve(data.output); - else if (data.type === "error") reject(new Error(data.error)); - else if (data.type === "progress") - $("busy-text").textContent = `Baking ${data.progress.index + 1}/${data.progress.total}`; - }; - exportWorker.onerror = (e) => { - e.preventDefault(); - reject(new Error(e.message)); - }; - exportWorker.postMessage({ - type: "export", - id: 1, - recipe: terrain.toJSON(), - resolution: Number($("export-resolution").value), - kind, - }); - }); - download(output); - toast(`Export ready: ${output.name}`); - } catch (e) { - $("export-error").textContent = e.message; - } finally { - exportWorker?.terminate(); - exportWorker = null; - exportReject = null; - for (const b of document.querySelectorAll("[data-export]")) { - b.disabled = false; - } - if (!workerBusy && !pending) setBusy(false); - } - } - for (const b of document.querySelectorAll("[data-export]")) - b.onclick = () => exportKind(b.dataset.export); - $("import-btn").onclick = () => $("import-file").click(); - $("import-file").onchange = async () => { - const file = $("import-file").files[0]; - if (!file) return; - try { - if (file.size > 64 * 1024 * 1024) throw Error("File is larger than the 64 MB import limit"); - if (file.name.toLowerCase().endsWith(".json")) terrain.loadJSON(await file.text()); - else { - const bytes = new Uint8Array(await file.arrayBuffer()); - const min = Number($("import-min").value); - const max = Number($("import-max").value); - let data; - if (file.name.toLowerCase().endsWith(".png")) { - data = await decodeHeightPNG(bytes); - if (!data.hasEmbeddedRange) data = await decodeHeightPNG(bytes, { min, max }); - } else { - const n = Math.sqrt(bytes.length / 2); - if (!Number.isInteger(n)) throw Error("RAW16 import requires a square heightfield"); - data = decodeRAW16(bytes, { width: n, height: n, min, max, littleEndian: true }); - } - terrain.heightmap({ - label: "Imported heightmap", - data: { width: data.width, height: data.height, values: data.values }, - blend: "replace", - }); - } - toast("File imported as an undoable edit."); - $("export-error").textContent = ""; - } catch (e) { - $("export-error").textContent = e.message; - } finally { - $("import-file").value = ""; - } - }; - const keydown = (e) => { - if (saving) return; - if (["INPUT", "TEXTAREA", "SELECT"].includes(e.target.tagName)) return; - if (active === "select" && (e.ctrlKey || e.metaKey || e.key === "Escape")) return; - if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "z") { - e.preventDefault(); - e.shiftKey ? terrain.redo() : terrain.undo(); - return; - } - if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "y") { - e.preventDefault(); - terrain.redo(); - return; - } - if (document.querySelector("dialog[open]")) return; - if (/^[0-9]$/.test(e.key)) selectTool(tools[(Number(e.key) + 9) % 10][0]); - if (e.key.toLowerCase() === "q") selectTool("select"); - if (e.key.toLowerCase() === "v") $("navigate-btn").click(); - if (e.key.toLowerCase() === "f") view.frame(); - if (e.key === "Enter") finishSpline(); - if (e.key === "Escape") { - points = []; - stroke = null; - showPoints(); - } - }; - // Camera bookmarks: the same operations an agent calls, over the same document. - const cameraPanel = document.createElement("div"); - cameraPanel.id = "camera-inspector"; - cameraPanel.className = "inspector"; - cameraPanel.innerHTML = - '
OBSERVATION CAMERAS0
' + - '
' + - '' + - '' + - '
' + - '' + - '
' + - '' + - '' + - '
'; - const style = document.createElement("style"); - style.textContent = - "#camera-inspector{overflow-y:auto;min-height:0}#camera-list{margin-bottom:9px;max-height:120px;overflow-y:auto}" + - "#camera-row{display:flex;gap:6px;align-items:center;width:100%;justify-content:flex-start;margin-bottom:5px;font-size:10px}" + - "#camera-row.active{border-color:var(--accent);color:var(--accent)}#camera-row small{margin-left:auto;opacity:.7}"; - document.head.append(style); - const sidebar = document.querySelector(".right-sidebar"); - if (!sidebar) throw new Error("Camera inspector has no sidebar"); - sidebar.insertBefore(cameraPanel, sidebar.querySelector(".inspector")); - let cameraRows = []; - function selectedCameraId() { - return $("camera-name").value.trim(); - } - function renderCameras(document_) { - // The panel is created after the first snapshot subscription; ignore anything earlier. - if (!$("camera-count")) return; - const cameras = document_.cameras ?? []; - const active = document_.activeCamera ?? null; - $("camera-count").textContent = String(cameras.length); - const list = $("camera-list"); - list.replaceChildren(); - cameraRows = cameras; - for (const camera of cameras) { - const row = document.createElement("button"); - row.id = `camera-row-${camera.id}`; - row.className = `camera-row${camera.id === active ? " active" : ""}`; - row.textContent = camera.name; - const kind = document.createElement("small"); - kind.textContent = - camera.projection === "orthographic" ? "ortho" : `${Math.round(camera.fov)}°`; - row.append(kind); - row.onclick = () => attemptCamera({ op: "activate", id: camera.id }); - list.append(row); - } - const live = cameras.find((camera) => camera.id === active); - // The projection picker follows the live camera, so a save never contradicts what is drawn. - if (live) $("camera-projection").value = live.projection; - $("camera-error").textContent = ""; - } - async function attemptCamera(operation, target) { - try { - if (operation.op === "focus") { - const result = view.cameras?.().focus(target); - $("camera-error").textContent = result?.diagnostic ?? ""; - if (result?.diagnostic) toast(result.diagnostic); - return result; - } - const result = await cameraOperation(operation, revision); - revision = result.revision; - saved = { ...saved, revision: result.revision }; - if (result.fallback) toast("Active camera deleted · back to the editor camera"); - renderCameras({ - ...saved.document, - cameras: result.cameras, - activeCamera: result.activeCamera, - }); - $("camera-error").textContent = ""; - return result; - } catch (error) { - $("camera-error").textContent = error.message; - toast(error.message); - return undefined; - } - } - function focusTarget() { - const raw = $("camera-target").value.trim(); - if (!raw) return undefined; - const numbers = raw.split(/\s+/u).map(Number); - if (numbers.length === 3 && numbers.every((value) => Number.isFinite(value))) - return { kind: "point", at: numbers }; - // One id names a prop, a landmark or a region; the view resolves whichever it knows. - return { - kind: raw.startsWith("region:") ? "region" : "prop", - id: raw.replace(/^(region|landmark|prop):/u, ""), - }; - } - $("camera-save").onclick = () => { - const pose = view.cameras?.().read(); - if (!pose) throw Error("This view has no camera surface"); - const projection = $("camera-projection").value; - attemptCamera({ - op: "create", - camera: { - id: `camera-${Date.now().toString(36)}`, - name: $("camera-name").value.trim() || "Observation", - position: pose.position, - target: pose.target, - up: pose.up, - near: pose.near, - far: pose.far, - projection, - ...(projection === "perspective" - ? { fov: Number($("camera-fov").value) } - : { - extent: Math.max( - 1, - Math.hypot(...pose.position.map((value) => value - pose.target[0])), - ), - zoom: 1, - }), - }, - }); - }; - $("camera-activate").onclick = () => attemptCamera({ op: "activate", id: selectedCameraId() }); - $("camera-delete").onclick = () => attemptCamera({ op: "delete", id: selectedCameraId() }); - $("camera-focus").onclick = () => { - const target = focusTarget(); - if (!target) return toast("Name a prop, landmark, region or x y z point"); - return attemptCamera({ op: "focus" }, { target, margin: Number($("camera-margin").value) }); - }; - // Preview environment: the same get / patch / reset an agent calls, over the same document. - const envFields = [ - ["sun", "azimuth", "Sun azimuth °", "number", "1"], - ["sun", "elevation", "Sun elevation °", "number", "1"], - ["sun", "intensity", "Sun intensity", "number", "0.1"], - ["sun", "colour", "Sun colour", "color"], - ["fill", "intensity", "Sky fill", "number", "0.05"], - ["sky", "colour", "Sky colour", "color"], - ["sky", "image", "Sky image", "asset"], - ["sky", "rotation", "Sky rotation °", "number", "1"], - ["sky", "intensity", "Sky intensity", "number", "0.05"], - ["lighting", "image", "Lighting image", "asset"], - ["lighting", "rotation", "Lighting rotation °", "number", "1"], - ["lighting", "intensity", "Lighting intensity", "number", "0.05"], - ["fog", "density", "Haze density", "number", "0.0001"], - ["fog", "colour", "Haze colour", "color"], - [null, "exposure", "Exposure", "number", "0.05"], - ["ocean", "shallow", "Sea shallow", "color"], - ["ocean", "deep", "Sea deep", "color"], - ]; - const envId = (section, key) => `env-${section ?? "root"}-${key}`; - const environmentPanel = document.createElement("details"); - environmentPanel.id = "environment-inspector"; - environmentPanel.className = "inspector"; - const envInputs = envFields - .map(([section, key, label, type, step]) => - type === "asset" - ? `` - : ``, - ) - .join(""); - environmentPanel.innerHTML = `ENVIRONMENT0${envInputs}
`; - environmentPanel.style.cssText = "overflow-y:auto;min-height:0;max-height:46vh"; - sidebar.insertBefore(environmentPanel, sidebar.querySelector(".inspector")); - const hex = (value) => - typeof value === "string" && /^#[0-9a-f]{6}$/u.test(value) ? value : "#000000"; - function renderEnvironment() { - if (!$("environment-count")) return; - const live = view.environment?.().read() ?? {}; - for (const [section, key, , type] of envFields) { - const value = (section ? live[section] : live)?.[key]; - const input = $(envId(section, key)); - if (type === "asset") { - // Every registered environment or ordinary image, plus the procedural default. - const choices = (saved.document.assets ?? []).filter( - (entry) => entry.kind === "environment" || entry.kind === "image", - ); - input.replaceChildren( - new Option("(procedural)", ""), - ...choices.map((entry) => new Option(entry.id, entry.id)), - ); - input.value = value ?? ""; - continue; - } - if (document.activeElement !== input && value !== undefined) - input.value = type === "color" ? hex(value) : String(Number(value.toFixed(6))); - } - $("environment-count").textContent = String( - Object.keys(saved.document.environment ?? {}).length, - ); - } - // Environment and asset edits share one revision, so they go one at a time: a second edit made - // before the first answers would otherwise be sent against a revision that has already moved. - let lane = Promise.resolve(); - const inLane = (task) => { - lane = lane.then(task, task); - return lane; - }; - function attemptEnvironment(operation) { - return inLane(() => runEnvironment(operation)); - } - async function runEnvironment(operation) { - try { - const result = await environmentOperation(operation, revision); - revision = result.revision; - saved = { - ...saved, - revision: result.revision, - document: { ...saved.document, environment: result.environment }, - }; - view.environment?.().apply(result.environment); - renderEnvironment(); - $("environment-error").textContent = ""; - return result; - } catch (error) { - $("environment-error").textContent = error.message; - toast(error.message); - renderEnvironment(); - return undefined; - } - } - for (const [section, key, , type] of envFields) { - $(envId(section, key)).onchange = (event) => { - const raw = event.target.value; - // An image field's empty choice returns the sky or lighting to its procedural default. - const value = type === "color" ? raw : type === "asset" ? raw || null : Number(raw); - return attemptEnvironment({ - op: "patch", - values: section ? { [section]: { [key]: value } } : { [key]: value }, - }); - }; - } - $("environment-reset").onclick = () => attemptEnvironment({ op: "reset" }); - // Project assets: GLB models enter the palette from a picked or dropped file, and from a local - // path an agent registers; both reach the same operations and the same document. - const assetPanel = document.createElement("details"); - assetPanel.id = "asset-inspector"; - assetPanel.className = "inspector"; - assetPanel.innerHTML = `PROJECT ASSETS0
Drop a .glb or image here
`; - assetPanel.style.cssText = "overflow-y:auto;min-height:0;max-height:40vh"; - sidebar.insertBefore(assetPanel, sidebar.querySelector(".inspector")); - const assetId = (name) => - name - .replace(/\.[^.]+$/u, "") - .toLowerCase() - .replace(/[^a-z0-9_-]+/gu, "-") - .replace(/^-+|-+$/gu, "") - .slice(0, 48) || "asset"; - function renderAssets() { - if (!$("asset-count")) return; - const assets = saved.document.assets ?? []; - $("asset-count").textContent = String(assets.length); - const list = $("asset-list"); - list.replaceChildren(); - for (const entry of assets) { - const row = document.createElement("div"); - row.className = "asset-row"; - row.id = `asset-row-${entry.id}`; - row.style.cssText = - "display:flex;gap:6px;align-items:center;margin-bottom:5px;font-size:10px"; - const label = document.createElement("span"); - const size = entry.bounds - ? entry.bounds.max.map((v, i) => (v - entry.bounds.min[i]).toFixed(2)).join("×") - : `${entry.width ?? "?"}×${entry.height ?? "?"}`; - label.textContent = `${entry.id} · ${entry.kind} · ${size}`; - label.style.flex = "1"; - row.append(label); - if (entry.kind === "model") { - const scale = document.createElement("input"); - scale.type = "number"; - scale.step = "0.01"; - scale.value = String(entry.adjust?.scale ?? 1); - scale.title = "Scale from the file's units to metres"; - scale.style.width = "64px"; - scale.onchange = () => - attemptAsset({ - op: "adjust", - id: entry.id, - adjust: { scale: Number(scale.value), pivot: entry.adjust?.pivot ?? "base" }, - }); - row.append(scale); - } - if (entry.kind === "image") { - const mapped = Object.entries(saved.document.surfaces ?? {}) - .filter(([, mapping]) => mapping.asset === entry.id) - .map(([input]) => input); - if (mapped.length) label.textContent += ` → ${mapped.join(", ")}`; - const inputs = view.surfaceInputs?.() ?? []; - if (inputs.length) { - const choose = document.createElement("select"); - choose.title = "Which of this project's surface inputs this image replaces"; - for (const { input } of inputs) choose.add(new Option(input, input)); - const use = document.createElement("button"); - use.className = "secondary"; - use.textContent = "Use"; - use.onclick = () => attemptAsset({ op: "map", input: choose.value, asset: entry.id }); - row.append(choose, use); - } - } - const remove = document.createElement("button"); - remove.className = "ghost"; - remove.textContent = "Remove"; - remove.onclick = () => attemptAsset({ op: "remove", id: entry.id }); - row.append(remove); - list.append(row); - } - } - function attemptAsset(operation) { - return inLane(() => runAsset(operation)); - } - async function runAsset(operation) { - try { - const result = await assetOperation(operation, revision); - revision = result.revision; - const next = { ...saved.document }; - next.assets = result.assets.length ? result.assets : undefined; - next.surfaces = Object.keys(result.surfaces ?? {}).length ? result.surfaces : undefined; - saved = { ...saved, revision: result.revision, document: next }; - view.setDocument(next, result.revision); - syncAssets(next); - renderAssets(); - renderEnvironment(); - renderOptions(); - $("asset-error").textContent = ""; - return result; - } catch (error) { - $("asset-error").textContent = error.message; - toast(error.message); - return undefined; - } - } - async function importFile(file) { - const bytes = new Uint8Array(await file.arrayBuffer()); - let binary = ""; - for (let i = 0; i < bytes.length; i += 0x8000) - binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000)); - return attemptAsset({ - op: "upload", - id: assetId(file.name), - name: file.name, - data: btoa(binary), - replace: $("asset-replace").checked, - }); - } - $("asset-file").onchange = async (event) => { - const [file] = event.target.files; - event.target.value = ""; - if (file) await importFile(file); - }; - $("asset-drop").ondragover = (event) => event.preventDefault(); - $("asset-drop").ondrop = async (event) => { - event.preventDefault(); - const [file] = event.dataTransfer.files; - if (file) await importFile(file); - }; - window.addEventListener("keydown", keydown, { signal: abort.signal }); - window.strata = { - Terrain, - Mask, - get revision() { - return revision; - }, - get renderedRevision() { - return renderedRevision; - }, - get terrain() { - return terrain; - }, - get state() { - return state; - }, - get workerBusy() { - return workerBusy; - }, - get evaluationRequests() { - return evaluationRequests; - }, - get busy() { - return busy; - }, - get view() { - return view; - }, - rebuild: build, - cancel: cancelBuild, - // The same camera operations the GUI list and the controller endpoint use. - cameras: { - list: () => saved.document.cameras ?? [], - active: () => saved.document.activeCamera ?? null, - read: () => view.cameras?.().read(), - operate: (operation) => attemptCamera(operation), - focus: (target, options) => view.cameras?.().focus({ ...options, target }), - resolve: (target) => view.cameras?.().resolve(target), - measure: (bounds) => view.cameras?.().measure(bounds), - }, - assets: { - list: () => saved.document.assets ?? [], - operate: (operation) => attemptAsset(operation), - }, - // The same environment operations the GUI fields and the controller endpoint use. - environment: { - saved: () => saved.document.environment ?? {}, - read: () => view.environment?.().read(), - operate: (operation) => attemptEnvironment(operation), - }, - // Same read-only dispatch the headless API serves, bound to the revision actually rendered. - inspect: (query) => { - if (!state) throw Error("Wait for initial build"); - return inspectSpatial(state, renderedRevision ?? revision, query); - }, - dispose: () => { - disposed = true; - abort.abort(); - clearTimeout(timer); - clearTimeout(toastTimer); - worker?.terminate(); - exportWorker?.terminate(); - exportReject?.(new Error("Editor disposed")); - }, - export: exportKind, - bake: (opts) => { - if (!state) throw Error("Wait for initial build"); - return bakeTerrain(state, opts); - }, - registerAsset: (id, object) => { - if (!view.registerAsset) throw Error("Three.js renderer is not active"); - return view.registerAsset(id, object); - }, - }; - $("view-angle").value = "perspective"; - $("view-angle").disabled = false; - const preview = $("preview-resolution"); - const resolution = String(terrain.config.resolution); - if (![...preview.options].some((option) => option.value === resolution)) - preview.add(new Option(`${resolution}²`, resolution)); - preview.value = resolution; - $("world-size").value = terrain.config.size; - $("seed-input").value = terrain.config.seed; - $("landscape-title").textContent = "Project terrain"; - $("landscape-subtitle").textContent = `${terrain.config.size} m · shared authoring document`; - $("landscape-tag").textContent = "LANDSCAPE / PROJECT"; - selectTool("sculpt"); - renderLayers(); - renderCameras(initial.document); - view.setDocument(initial.document, initial.revision); - renderEnvironment(); - renderAssets(); - build(); - $("renderer-badge").textContent = view.backend; - return window.strata; -} diff --git a/packages/terrain/src/editor/app.ts b/packages/terrain/src/editor/app.ts new file mode 100644 index 0000000000..fb42acc3dc --- /dev/null +++ b/packages/terrain/src/editor/app.ts @@ -0,0 +1,4 @@ +import { EditorSession, type IRecoveredEditorOptions } from "./app/session.js"; +export function mountRecoveredEditor(options: IRecoveredEditorOptions) { + return new EditorSession(options).api; +} diff --git a/packages/terrain/src/editor/app/api.ts b/packages/terrain/src/editor/app/api.ts new file mode 100644 index 0000000000..1bb00a200f --- /dev/null +++ b/packages/terrain/src/editor/app/api.ts @@ -0,0 +1,128 @@ +import { + MATERIAL_IDS, + Mask, + Terrain, + applyPlacementOverrides, + bakeTerrain, + decodeHeightPNG, + decodeRAW16, + makeExport, +} from "../../index.js"; +import type { IAssetOperation } from "../assets.js"; +import type { ICameraOperation, IFocusBounds, IFocusRequest, IFocusTarget } from "../cameras.js"; +import type { IEnvironmentOperation } from "../environment.js"; +import { fillIcons, icon } from "../icons.js"; +import { PRESETS, createPreset } from "../presets.js"; +import type { ISpatialQuery } from "../spatialInspector.js"; +import { inspectSpatial } from "../spatialInspector.js"; +import { $, errorMessage } from "./dom.js"; +import type { EditorSession } from "./session.js"; + +export function createEditorApi(c: EditorSession) { + return { + Terrain, + Mask, + get revision() { + return c.revision; + }, + get renderedRevision() { + return c.renderedRevision; + }, + get terrain() { + return c.terrain; + }, + get state() { + return c.state; + }, + get workerBusy() { + return c.workerBusy; + }, + get evaluationRequests() { + return c.evaluationRequests; + }, + get busy() { + return c.busy; + }, + get view() { + return c.view; + }, + rebuild: c.build, + cancel: c.cancelBuild, + // The same camera operations the GUI list and the controller endpoint use. + cameras: { + list: () => c.saved.document.cameras ?? [], + active: () => c.saved.document.activeCamera ?? null, + read: () => c.view.cameras?.().read(), + operate: (operation: ICameraOperation) => c.attemptCamera(operation), + focus: (target: IFocusTarget, options: Omit = {}) => + c.view.cameras?.().focus({ ...options, target }), + resolve: (target: { kind: "prop" | "landmark" | "region"; id: string }) => + c.view.cameras?.().resolve(target), + measure: (bounds: IFocusBounds) => c.view.cameras?.().measure(bounds), + }, + assets: { + list: () => c.saved.document.assets ?? [], + operate: (operation: IAssetOperation) => c.attemptAsset(operation), + }, + // The same environment operations the GUI fields and the controller endpoint use. + environment: { + saved: () => c.saved.document.environment ?? {}, + read: () => c.view.environment?.().read(), + operate: (operation: IEnvironmentOperation) => c.attemptEnvironment(operation), + }, + // Same read-only dispatch the headless API serves, bound to the revision actually rendered. + inspect: (query: ISpatialQuery) => { + if (!c.state) throw Error("Wait for initial build"); + return inspectSpatial(c.state, c.renderedRevision ?? c.revision, query); + }, + dispose: () => { + c.disposed = true; + c.abort.abort(); + clearTimeout(c.timer ?? undefined); + clearTimeout(c.toastTimer ?? undefined); + c.worker?.terminate(); + c.exportWorker?.terminate(); + c.exportReject?.(new Error("Editor disposed")); + }, + export: c.exportKind, + bake: (opts?: Parameters[1]) => { + if (!c.state) throw Error("Wait for initial build"); + return bakeTerrain(c.state, opts); + }, + registerAsset: (id: string, object: unknown) => { + if (!c.view.registerAsset) throw Error("Three.js renderer is not active"); + return c.view.registerAsset(id, object); + }, + }; +} +export function installApi(c: EditorSession): void { + window.addEventListener("keydown", c.keydown, { signal: c.abort.signal }); + c.api = createEditorApi(c); + window.strata = c.api; + $("view-angle").value = "perspective"; + $("view-angle").disabled = false; + c.preview = $("preview-resolution"); + c.resolution = String(c.terrain.config.resolution); + if (![...c.preview.options].some((option) => option.value === c.resolution)) + c.preview.add(new Option(`${c.resolution}²`, c.resolution)); + c.preview.value = c.resolution; + $("world-size").value = String(c.terrain.config.size); + $("seed-input").value = String(c.terrain.config.seed); + $("landscape-title").textContent = "Project terrain"; + $("landscape-subtitle").textContent = `${c.terrain.config.size} m · shared authoring document`; + $("landscape-tag").textContent = "LANDSCAPE / PROJECT"; + c.selectTool("sculpt"); + c.renderLayers(); + c.renderCameras(c.initial.document); + c.view.setDocument(c.initial.document, c.initial.revision); + c.renderEnvironment(); + c.renderAssets(); + c.build(); + $("renderer-badge").textContent = c.view.backend; +} + +declare global { + interface Window { + strata: ReturnType; + } +} diff --git a/packages/terrain/src/editor/app/assets.ts b/packages/terrain/src/editor/app/assets.ts new file mode 100644 index 0000000000..3dbf7c9ac6 --- /dev/null +++ b/packages/terrain/src/editor/app/assets.ts @@ -0,0 +1,186 @@ +import { + MATERIAL_IDS, + Mask, + Terrain, + applyPlacementOverrides, + bakeTerrain, + decodeHeightPNG, + decodeRAW16, + makeExport, +} from "../../index.js"; +import type { IAssetOperation, IAssetResult } from "../assets.js"; +import { fillIcons, icon } from "../icons.js"; +import { PRESETS, createPreset } from "../presets.js"; +import { inspectSpatial } from "../spatialInspector.js"; +import { $, element, errorMessage } from "./dom.js"; +import type { EditorSession } from "./session.js"; +export function renderAssets(c: EditorSession): void { + if (!document.getElementById("asset-count")) return; + const assets = c.saved.document.assets ?? []; + $("asset-count").textContent = String(assets.length); + const list = $("asset-list"); + list.replaceChildren(); + for (const entry of assets) { + const row = document.createElement("div"); + row.className = "asset-row"; + row.id = `asset-row-${entry.id}`; + row.style.cssText = "display:flex;gap:6px;align-items:center;margin-bottom:5px;font-size:10px"; + const label = document.createElement("span"); + const bounds = entry.bounds; + const size = bounds + ? ([0, 1, 2] as const) + .map((axis) => (bounds.max[axis] - bounds.min[axis]).toFixed(2)) + .join("×") + : `${entry.width ?? "?"}×${entry.height ?? "?"}`; + label.textContent = `${entry.id} · ${entry.kind} · ${size}`; + label.style.flex = "1"; + row.append(label); + if (entry.kind === "model") { + const scale = document.createElement("input"); + scale.type = "number"; + scale.step = "0.01"; + scale.value = String(entry.adjust?.scale ?? 1); + scale.title = "Scale from the file's units to metres"; + scale.style.width = "64px"; + scale.onchange = () => + c.attemptAsset({ + op: "adjust", + id: entry.id, + adjust: { scale: Number(scale.value), pivot: entry.adjust?.pivot ?? "base" }, + }); + row.append(scale); + } + if (entry.kind === "image") { + const mapped = Object.entries(c.saved.document.surfaces ?? {}) + .filter(([, mapping]) => mapping.asset === entry.id) + .map(([input]) => input); + if (mapped.length) label.textContent += ` → ${mapped.join(", ")}`; + const inputs = c.view.surfaceInputs?.() ?? []; + if (inputs.length) { + const choose = document.createElement("select"); + choose.title = "Which of this project's surface inputs this image replaces"; + for (const { input } of inputs) choose.add(new Option(input, input)); + const use = document.createElement("button"); + use.className = "secondary"; + use.textContent = "Use"; + use.onclick = () => c.attemptAsset({ op: "map", input: choose.value, asset: entry.id }); + row.append(choose, use); + } + } + const remove = document.createElement("button"); + remove.className = "ghost"; + remove.textContent = "Remove"; + remove.onclick = () => c.attemptAsset({ op: "remove", id: entry.id }); + row.append(remove); + list.append(row); + } +} + +export function attemptAsset( + c: EditorSession, + operation: IAssetOperation, +): Promise<(IAssetResult & { revision: string }) | undefined> { + return c.inLane(() => c.runAsset(operation)); +} + +export async function runAsset( + c: EditorSession, + operation: IAssetOperation, +): Promise<(IAssetResult & { revision: string }) | undefined> { + try { + const result = await c.assetOperation(operation, c.revision); + c.revision = result.revision; + const next = { ...c.saved.document }; + next.assets = result.assets.length ? [...result.assets] : undefined; + next.surfaces = Object.keys(result.surfaces ?? {}).length ? result.surfaces : undefined; + c.saved = { ...c.saved, revision: result.revision, document: next }; + c.view.setDocument(next, result.revision); + c.syncAssets(next); + c.renderAssets(); + c.renderEnvironment(); + c.renderOptions(); + $("asset-error").textContent = ""; + return result; + } catch (error) { + $("asset-error").textContent = errorMessage(error); + c.toast(errorMessage(error)); + return undefined; + } +} + +export async function importFile( + c: EditorSession, + file: File, +): Promise<(IAssetResult & { revision: string }) | undefined> { + const bytes = new Uint8Array(await file.arrayBuffer()); + let binary = ""; + for (let i = 0; i < bytes.length; i += 0x8000) + binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000)); + return c.attemptAsset({ + op: "upload", + id: c.assetId(file.name), + name: file.name, + data: btoa(binary), + replace: $("asset-replace").checked, + }); +} +export function installAssets(c: EditorSession): void { + c.assetPanel = document.createElement("details"); + c.assetPanel.id = "asset-inspector"; + c.assetPanel.className = "inspector"; + c.assetPanel.replaceChildren( + element( + "summary", + { class: "inspector-header" }, + element("span", {}, "PROJECT ASSETS"), + element("span", { id: "asset-count", class: "count-tag" }, "0"), + ), + element( + "div", + { + id: "asset-drop", + class: "field-label", + style: "border:1px dashed var(--border);padding:10px;margin-bottom:9px", + }, + "Drop a .glb or image here", + element("input", { + id: "asset-file", + type: "file", + accept: ".glb,.png,.jpg,.jpeg,.webp,.hdr,.exr,model/gltf-binary,image/*", + }), + ), + element( + "label", + { class: "field-label" }, + element( + "span", + {}, + element("input", { id: "asset-replace", type: "checkbox" }), + " Replace an asset with the same name", + ), + ), + element("div", { id: "asset-list" }), + element("div", { id: "asset-error", class: "inline-error" }), + ); + c.assetPanel.style.cssText = "overflow-y:auto;min-height:0;max-height:40vh"; + c.sidebar.insertBefore(c.assetPanel, c.sidebar.querySelector(".inspector")); + c.assetId = (name) => + name + .replace(/\.[^.]+$/u, "") + .toLowerCase() + .replace(/[^a-z0-9_-]+/gu, "-") + .replace(/^-+|-+$/gu, "") + .slice(0, 48) || "asset"; + const fileInput = $("asset-file"); + fileInput.onchange = async () => { + const file = fileInput.files?.[0]; + fileInput.value = ""; + if (file) await c.importFile(file); + }; + $("asset-drop").ondragover = (event) => event.preventDefault(); + $("asset-drop").ondrop = async (event) => { + event.preventDefault(); + const file = event.dataTransfer?.files[0]; + if (file) await c.importFile(file); + }; +} diff --git a/packages/terrain/src/editor/app/cameras.ts b/packages/terrain/src/editor/app/cameras.ts new file mode 100644 index 0000000000..5d5664a694 --- /dev/null +++ b/packages/terrain/src/editor/app/cameras.ts @@ -0,0 +1,200 @@ +import { + MATERIAL_IDS, + Mask, + Terrain, + applyPlacementOverrides, + bakeTerrain, + decodeHeightPNG, + decodeRAW16, + makeExport, +} from "../../index.js"; +import type { + ICameraOperation, + ICameraResult, + IFocusOutcome, + IFocusRequest, + IFocusTarget, +} from "../cameras.js"; +import { fillIcons, icon } from "../icons.js"; +import { PRESETS, createPreset } from "../presets.js"; +import type { IAuthoringDocument } from "../server.js"; +import { inspectSpatial } from "../spatialInspector.js"; +import { $, element, errorMessage, field } from "./dom.js"; +import type { EditorSession } from "./session.js"; +export function selectedCameraId(c: EditorSession): string { + return $("camera-name").value.trim(); +} + +export function renderCameras(c: EditorSession, document_: IAuthoringDocument): void { + // The panel is created after the first snapshot subscription; ignore anything earlier. + if (!document.getElementById("camera-count")) return; + const cameras = document_.cameras ?? []; + const active = document_.activeCamera ?? null; + $("camera-count").textContent = String(cameras.length); + const list = $("camera-list"); + list.replaceChildren(); + + for (const camera of cameras) { + const row = document.createElement("button"); + row.id = `camera-row-${camera.id}`; + row.className = `camera-row${camera.id === active ? " active" : ""}`; + row.textContent = camera.name; + const kind = document.createElement("small"); + kind.textContent = + camera.projection === "orthographic" ? "ortho" : `${Math.round(camera.fov)}°`; + row.append(kind); + row.onclick = () => c.attemptCamera({ op: "activate", id: camera.id }); + list.append(row); + } + const live = cameras.find((camera) => camera.id === active); + // The projection picker follows the live camera, so a save never contradicts what is drawn. + if (live) $("camera-projection").value = live.projection; + $("camera-error").textContent = ""; +} + +export async function attemptCamera( + c: EditorSession, + operation: ICameraOperation | { op: "focus" }, + target?: Omit, +): Promise<(ICameraResult & { revision: string }) | IFocusOutcome | undefined> { + try { + if (operation.op === "focus") { + if (!target) throw Error("Camera focus requires a target"); + const result = c.view.cameras?.().focus(target); + $("camera-error").textContent = result?.diagnostic ?? ""; + if (result?.diagnostic) c.toast(result.diagnostic); + return result; + } + const result = await c.cameraOperation(operation, c.revision); + c.revision = result.revision; + c.saved = { ...c.saved, revision: result.revision }; + if (result.fallback) c.toast("Active camera deleted · back to the editor camera"); + c.renderCameras({ + ...c.saved.document, + cameras: [...result.cameras], + activeCamera: result.activeCamera, + }); + $("camera-error").textContent = ""; + return result; + } catch (error) { + $("camera-error").textContent = errorMessage(error); + c.toast(errorMessage(error)); + return undefined; + } +} + +export function focusTarget(c: EditorSession): IFocusTarget | undefined { + const raw = $("camera-target").value.trim(); + if (!raw) return undefined; + const numbers = raw.split(/\s+/u).map(Number); + if (numbers.length === 3 && numbers.every((value) => Number.isFinite(value))) + return { kind: "point", at: numbers as [number, number, number] }; + // One id names a prop, a landmark or a region; the view resolves whichever it knows. + return { + kind: raw.startsWith("region:") ? "region" : "prop", + id: raw.replace(/^(region|landmark|prop):/u, ""), + }; +} +export function installCameras(c: EditorSession): void { + c.cameraPanel = document.createElement("div"); + c.cameraPanel.id = "camera-inspector"; + c.cameraPanel.className = "inspector"; + c.cameraPanel.replaceChildren( + element( + "div", + { class: "inspector-header" }, + element("span", {}, "OBSERVATION CAMERAS"), + element("span", { id: "camera-count", class: "count-tag" }, "0"), + ), + element("div", { id: "camera-list" }), + field("Name", element("input", { id: "camera-name", placeholder: "Camera name" })), + field( + "Projection", + element( + "select", + { id: "camera-projection" }, + element("option", { value: "perspective" }, "Perspective"), + element("option", { value: "orthographic" }, "Orthographic map"), + ), + ), + element( + "div", + { class: "two-fields" }, + field( + "Fov", + element("input", { id: "camera-fov", type: "number", value: "60", min: "1", max: "170" }), + ), + field( + "Margin", + element("input", { + id: "camera-margin", + type: "number", + value: "1.15", + min: "1", + step: "0.05", + }), + ), + ), + field( + "Focus target", + element("input", { + id: "camera-target", + placeholder: "prop id, landmark id, region id or x y z", + }), + ), + element( + "div", + { class: "two-fields" }, + element("button", { id: "camera-save", class: "secondary" }, "Save current"), + element("button", { id: "camera-activate", class: "secondary" }, "Activate"), + ), + element("button", { id: "camera-focus", class: "secondary full" }, "Focus target"), + element("button", { id: "camera-delete", class: "ghost full" }, "Delete camera"), + element("div", { id: "camera-error", class: "inline-error" }), + ); + c.style = document.createElement("style"); + c.style.textContent = + "#camera-inspector{overflow-y:auto;min-height:0}#camera-list{margin-bottom:9px;max-height:120px;overflow-y:auto}" + + "#camera-row{display:flex;gap:6px;align-items:center;width:100%;justify-content:flex-start;margin-bottom:5px;font-size:10px}" + + "#camera-row.active{border-color:var(--accent);color:var(--accent)}#camera-row small{margin-left:auto;opacity:.7}"; + document.head.append(c.style); + const sidebar = document.querySelector(".right-sidebar"); + if (!sidebar) throw new Error("Camera inspector has no sidebar"); + c.sidebar = sidebar; + c.sidebar.insertBefore(c.cameraPanel, c.sidebar.querySelector(".inspector")); + $("camera-save").onclick = () => { + const pose = c.view.cameras?.().read(); + if (!pose) throw Error("This view has no camera surface"); + const projection = $("camera-projection").value; + c.attemptCamera({ + op: "create", + camera: { + id: `camera-${Date.now().toString(36)}`, + name: $("camera-name").value.trim() || "Observation", + position: pose.position, + target: pose.target, + up: pose.up, + near: pose.near, + far: pose.far, + projection, + ...(projection === "perspective" + ? { fov: Number($("camera-fov").value) } + : { + extent: Math.max( + 1, + Math.hypot(...pose.position.map((value) => value - pose.target[0])), + ), + zoom: 1, + }), + }, + }); + }; + $("camera-activate").onclick = () => + c.attemptCamera({ op: "activate", id: c.selectedCameraId() }); + $("camera-delete").onclick = () => c.attemptCamera({ op: "delete", id: c.selectedCameraId() }); + $("camera-focus").onclick = () => { + const target = c.focusTarget(); + if (!target) return c.toast("Name a prop, landmark, region or x y z point"); + return c.attemptCamera({ op: "focus" }, { target, margin: Number($("camera-margin").value) }); + }; +} diff --git a/packages/terrain/src/editor/app/dom.ts b/packages/terrain/src/editor/app/dom.ts new file mode 100644 index 0000000000..5be6a8ffa3 --- /dev/null +++ b/packages/terrain/src/editor/app/dom.ts @@ -0,0 +1,22 @@ +/** Required recovered controls fail by ID rather than silently dropping an edit. */ +export function $(id: string): T { + const element = document.getElementById(id); + if (!element) throw new Error(`Missing editor control: ${id}`); + return element as T; +} +export function element( + tag: K, + attributes: Record = {}, + ...children: (Node | string)[] +): HTMLElementTagNameMap[K] { + const node = document.createElement(tag); + for (const [key, value] of Object.entries(attributes)) node.setAttribute(key, value); + node.append(...children); + return node; +} +export function field(label: string, input: HTMLElement) { + return element("label", { class: "field-label" }, label, input); +} +export function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/packages/terrain/src/editor/app/environment.ts b/packages/terrain/src/editor/app/environment.ts new file mode 100644 index 0000000000..df93008639 --- /dev/null +++ b/packages/terrain/src/editor/app/environment.ts @@ -0,0 +1,141 @@ +import { + MATERIAL_IDS, + Mask, + Terrain, + applyPlacementOverrides, + bakeTerrain, + decodeHeightPNG, + decodeRAW16, + makeExport, +} from "../../index.js"; +import type { IEnvironment, IEnvironmentOperation, IEnvironmentResult } from "../environment.js"; +import { fillIcons, icon } from "../icons.js"; +import { PRESETS, createPreset } from "../presets.js"; +import { inspectSpatial } from "../spatialInspector.js"; +import { $, element, errorMessage, field } from "./dom.js"; +import type { EditorSession } from "./session.js"; +export function renderEnvironment(c: EditorSession): void { + if (!document.getElementById("environment-count")) return; + const live = c.view.environment?.().read() ?? {}; + for (const [section, key, , type] of c.envFields) { + const fields = section ? live[section] : live; + const value = + fields && typeof fields === "object" ? (fields as Record)[key] : undefined; + const input = $(c.envId(section, key)); + if (type === "asset") { + // Every registered environment or ordinary image, plus the procedural default. + const choices = (c.saved.document.assets ?? []).filter( + (entry) => entry.kind === "environment" || entry.kind === "image", + ); + input.replaceChildren( + new Option("(procedural)", ""), + ...choices.map((entry) => new Option(entry.id, entry.id)), + ); + input.value = typeof value === "string" ? value : ""; + continue; + } + if (document.activeElement !== input && value !== undefined) { + if (type === "color") input.value = c.hex(value); + else if (typeof value === "number") input.value = String(Number(value.toFixed(6))); + } + } + $("environment-count").textContent = String( + Object.keys(c.saved.document.environment ?? {}).length, + ); +} + +export function attemptEnvironment( + c: EditorSession, + operation: IEnvironmentOperation, +): Promise<(IEnvironmentResult & { revision: string }) | undefined> { + return c.inLane(() => c.runEnvironment(operation)); +} + +export async function runEnvironment( + c: EditorSession, + operation: IEnvironmentOperation, +): Promise<(IEnvironmentResult & { revision: string }) | undefined> { + try { + const result = await c.environmentOperation(operation, c.revision); + c.revision = result.revision; + c.saved = { + ...c.saved, + revision: result.revision, + document: { ...c.saved.document, environment: result.environment }, + }; + c.view.environment?.().apply(result.environment); + c.renderEnvironment(); + $("environment-error").textContent = ""; + return result; + } catch (error) { + $("environment-error").textContent = errorMessage(error); + c.toast(errorMessage(error)); + c.renderEnvironment(); + return undefined; + } +} +export function installEnvironment(c: EditorSession): void { + c.envFields = [ + ["sun", "azimuth", "Sun azimuth °", "number", "1"], + ["sun", "elevation", "Sun elevation °", "number", "1"], + ["sun", "intensity", "Sun intensity", "number", "0.1"], + ["sun", "colour", "Sun colour", "color"], + ["fill", "intensity", "Sky fill", "number", "0.05"], + ["sky", "colour", "Sky colour", "color"], + ["sky", "image", "Sky image", "asset"], + ["sky", "rotation", "Sky rotation °", "number", "1"], + ["sky", "intensity", "Sky intensity", "number", "0.05"], + ["lighting", "image", "Lighting image", "asset"], + ["lighting", "rotation", "Lighting rotation °", "number", "1"], + ["lighting", "intensity", "Lighting intensity", "number", "0.05"], + ["fog", "density", "Haze density", "number", "0.0001"], + ["fog", "colour", "Haze colour", "color"], + [null, "exposure", "Exposure", "number", "0.05"], + ["ocean", "shallow", "Sea shallow", "color"], + ["ocean", "deep", "Sea deep", "color"], + ]; + c.envId = (section, key) => `env-${section ?? "root"}-${key}`; + c.environmentPanel = document.createElement("details"); + c.environmentPanel.id = "environment-inspector"; + c.environmentPanel.className = "inspector"; + c.environmentPanel.replaceChildren( + element( + "summary", + { class: "inspector-header" }, + element("span", {}, "ENVIRONMENT"), + element("span", { id: "environment-count", class: "count-tag" }, "0"), + ), + ...c.envFields.map(([section, key, label, type, step]) => + field( + label, + type === "asset" + ? element( + "select", + { id: c.envId(section, key) }, + element("option", { value: "" }, "(procedural)"), + ) + : element("input", { id: c.envId(section, key), type, ...(step ? { step } : {}) }), + ), + ), + element("button", { id: "environment-reset", class: "ghost full" }, "Reset to project look"), + element("div", { id: "environment-error", class: "inline-error" }), + ); + c.environmentPanel.style.cssText = "overflow-y:auto;min-height:0;max-height:46vh"; + c.sidebar.insertBefore(c.environmentPanel, c.sidebar.querySelector(".inspector")); + c.hex = (value) => + typeof value === "string" && /^#[0-9a-f]{6}$/u.test(value) ? value : "#000000"; + c.lane = Promise.resolve(); + for (const [section, key, , type] of c.envFields) { + const input = $(c.envId(section, key)); + input.onchange = () => { + const raw = input.value; + // An image field's empty choice returns the sky or lighting to its procedural default. + const value = type === "color" ? raw : type === "asset" ? raw || null : Number(raw); + return c.attemptEnvironment({ + op: "patch", + values: section ? { [section]: { [key]: value } } : { [key]: value }, + }); + }; + } + $("environment-reset").onclick = () => c.attemptEnvironment({ op: "reset" }); +} diff --git a/packages/terrain/src/editor/app/evaluation.ts b/packages/terrain/src/editor/app/evaluation.ts new file mode 100644 index 0000000000..1541c94f0a --- /dev/null +++ b/packages/terrain/src/editor/app/evaluation.ts @@ -0,0 +1,474 @@ +import type { ITerrainState } from "../../core/types.js"; +import { + MATERIAL_IDS, + Mask, + Terrain, + applyPlacementOverrides, + bakeTerrain, + decodeHeightPNG, + decodeRAW16, + makeExport, +} from "../../index.js"; +import type { IProjectAsset } from "../assets.js"; +import { fillIcons, icon } from "../icons.js"; +import { PRESETS, createPreset } from "../presets.js"; +import { SaveQueue, externalSnapshotAction } from "../saveQueue.js"; +import type { IAuthoringDocument, IEditorSnapshot } from "../server.js"; +import { inspectSpatial } from "../spatialInspector.js"; +import type { EditorWorkerReply } from "../worker.js"; +import { $, element, errorMessage } from "./dom.js"; +import { AnnouncementQueue } from "./keyboard.js"; +import type { EditorSession } from "./session.js"; +export function toast(c: EditorSession, message: string, blocking = false): void { + c.announce(message, blocking); + clearTimeout(c.toastTimer ?? undefined); + $("toast").textContent = message; + $("toast").classList.remove("hidden"); + c.toastTimer = setTimeout(() => $("toast").classList.add("hidden"), 4300); +} + +export function attempt(c: EditorSession, fn: () => unknown): unknown { + try { + return fn(); + } catch (e) { + c.toast(errorMessage(e)); + return undefined; + } +} + +export function setBusy(c: EditorSession, value: boolean, text = "Building terrain"): void { + c.busy = value; + $("busy-status").classList.toggle("hidden", !value); + $("busy-text").textContent = text; +} + +export function workerURL(c: EditorSession): URL { + return new URL("./worker.js", import.meta.url); +} + +export function newWorker(c: EditorSession): Worker { + const w = new Worker(c.workerURL(), { type: "module" }); + w.onmessage = async ({ data }: MessageEvent) => { + if (c.disposed || c.worker !== w) return; + if (data.type === "progress") { + if (data.id === c.sequence) + $("busy-text").textContent = `Evaluating ${data.progress.index + 1}/${data.progress.total}`; + return; + } + // Imported models load asynchronously; the preview waits for them rather than failing a + // placement that names an asset still on its way. The worker stays busy meanwhile. + if (data.id === c.sequence && data.type === "evaluated") + await c.view.assetsReady?.().catch(() => {}); + if (c.disposed || c.worker !== w) return; + c.workerBusy = false; + if (data.id === c.sequence) { + if (data.type === "error") { + c.toast(data.error); + $("save-status").textContent = "Build failed · last preview retained"; + } else if (data.type === "evaluated") { + const ready = c.attempt(() => { + data.state = c.view.update(data.state) ?? data.state; + return true; + }); + if (!ready) { + $("save-status").textContent = "Preview failed · last preview retained"; + } else { + c.state = data.state; + c.renderedRecipe = JSON.stringify(c.terrain.toJSON()); + c.renderedRevision = c.revision; + c.onEvaluated?.({ revision: c.revision, state: data.state, ms: data.ms }); + $("mesh-stats").textContent = + `${(2 * (data.state.resolution - 1) ** 2).toLocaleString()} tris · ${data.state.instances.length.toLocaleString()} instances`; + $("eval-time").textContent = `${Math.round(data.ms)} ms · CPU worker`; + $("diagnostic-count").textContent = String(data.state.diagnostics.length); + $("diagnostic-list").textContent = data.state.diagnostics.join("\n") || "No diagnostics."; + $("save-status").textContent = c.saveQueue.hasLocalIntent() + ? "Rendered local edits · not saved yet" + : `Rendered ${c.renderedRevision.slice(0, 8)} · saved on disk`; + } + } + } + if (c.pending) c.dispatch(); + else c.setBusy(false); + }; + w.onerror = (e) => { + e.preventDefault(); + if (c.disposed || c.worker !== w) return; + w.terminate(); + c.worker = null; + c.workerBusy = false; + $("save-status").textContent = "Worker failed · last preview retained"; + c.toast(`Worker failed: ${e.message}. The next edit will restart terrain evaluation.`); + if (c.pending) c.dispatch(); + else c.setBusy(false); + }; + return w; +} + +export function dispatch(c: EditorSession): void { + if (c.workerBusy || !c.pending) return; + const job = c.pending; + c.pending = null; + try { + c.worker ??= c.newWorker(); + c.workerBusy = true; + c.worker.postMessage(job); + c.evaluationRequests++; + } catch (e) { + c.worker?.terminate(); + c.worker = null; + c.workerBusy = false; + c.setBusy(false); + c.toast(errorMessage(e)); + } +} + +export function build(c: EditorSession): void { + if (c.disposed) return; + c.sequence++; + c.pending = { + id: c.sequence, + type: "evaluate", + recipe: c.terrain.toJSON(), + resolution: Number($("preview-resolution").value), + }; + c.setBusy(true); + clearTimeout(c.timer ?? undefined); + c.timer = setTimeout(c.dispatch, 35); +} + +export function cancelBuild(c: EditorSession): void { + clearTimeout(c.timer ?? undefined); + c.pending = null; + c.sequence++; + c.worker?.terminate(); + c.worker = null; + c.workerBusy = false; + c.exportWorker?.terminate(); + c.exportWorker = null; + c.exportReject?.(new Error("Export cancelled")); + c.exportReject = null; + for (const b of document.querySelectorAll("[data-export]")) { + b.disabled = false; + } + c.setBusy(false); + $("save-status").textContent = "Build cancelled · preview unchanged"; + c.toast("Build cancelled. Rebuild to evaluate the current stack."); +} + +export function syncAssets(c: EditorSession, document_: IAuthoringDocument): void { + const next = JSON.stringify(document_.assets ?? []); + if (next === c.assetSignature) return; + const before = new Map( + (JSON.parse(c.assetSignature) as IProjectAsset[]).map((entry) => [ + entry.id, + JSON.stringify(entry), + ]), + ); + const after = new Map((document_.assets ?? []).map((entry) => [entry.id, JSON.stringify(entry)])); + const changed = new Set( + [...before.keys(), ...after.keys()].filter((id) => before.get(id) !== after.get(id)), + ); + c.assetSignature = next; + const used = () => + c.terrain + .toJSON() + .layers.some((layer) => "asset" in layer.params && changed.has(layer.params.asset ?? "")); + Promise.resolve(c.view.assetsReady?.()) + .catch(() => {}) + .then(() => { + if (c.disposed) return; + c.renderOptions(); + if (used()) c.build(); + }); +} + +export function useSnapshot(c: EditorSession, snapshot: IEditorSnapshot): void { + if (snapshot.diagnostic) { + c.toast(snapshot.diagnostic); + $("save-status").textContent = "Invalid disk save · valid preview retained"; + return; + } + if (snapshot.revision === c.revision) return; + const recipeChanged = + JSON.stringify(c.terrain.toJSON()) !== JSON.stringify(snapshot.document.recipe); + const ready = c.attempt(() => { + c.view.setDocument(snapshot.document, snapshot.revision); + return true; + }); + if (!ready) { + $("save-status").textContent = "Preview failed · last preview retained"; + return; + } + c.revision = snapshot.revision; + c.renderCameras(snapshot.document); + c.renderEnvironment(); + c.renderAssets(); + c.syncAssets(snapshot.document); + if (!recipeChanged) { + const matchesPreview = c.renderedRecipe === JSON.stringify(snapshot.document.recipe); + if (c.state && matchesPreview) + c.state = applyPlacementOverrides(c.state, snapshot.document.placementOverrides ?? {}); + if (matchesPreview && !c.busy) { + c.renderedRevision = c.revision; + $("save-status").textContent = `Applied ${c.revision.slice(0, 8)} · saved on disk`; + } else { + $("save-status").textContent = c.busy + ? `Requested ${c.revision.slice(0, 8)} · building` + : "Saved recipe not rendered · last preview retained"; + } + return; + } + c.loading = true; + c.terrain.loadJSON(snapshot.document.recipe); + c.loading = false; + c.renderLayers(); + c.build(); +} + +export function renderUnsent(c: EditorSession): void { + const panel = $("not-saved"); + const list = $("not-saved-list"); + const signature = JSON.stringify(c.saveQueue.unsent); + if (signature === c.unsentSignature) return; + c.unsentSignature = signature; + const hadFocus = list.contains(document.activeElement); + list.replaceChildren(); + for (const edit of c.saveQueue.unsent) { + const retry = element( + "button", + { id: `retry-${edit.id}`, type: "button", "data-save-action": "retry" }, + "Retry", + ); + const discard = element( + "button", + { id: `discard-${edit.id}`, type: "button", "data-save-action": "discard" }, + "Discard", + ); + retry.onclick = () => c.saveQueue.retry(edit); + discard.onclick = () => { + c.saveQueue.discard(edit); + c.toast("Not-saved edit discarded"); + }; + list.append(element("li", {}, element("p", {}, edit.label), retry, discard)); + } + panel.hidden = c.saveQueue.unsent.length === 0; + if (hadFocus) { + $("save-status").tabIndex = -1; + $("save-status").focus(); + } +} + +export function installSaveQueue(c: EditorSession): void { + const panel = element( + "section", + { id: "not-saved", "aria-label": "Not saved", class: "panel-scroll" }, + element("h2", {}, "Not saved"), + element("ul", { id: "not-saved-list" }), + ); + panel.hidden = true; + panel.style.cssText = + "position:fixed;bottom:24px;right:24px;z-index:60;max-width:420px;max-height:40vh;background:var(--panel);border:1px solid var(--accent)"; + document.body.append(panel); + c.unsentSignature = ""; + c.saveQueue = new SaveQueue({ + base: c.saved, + commit: async (transaction) => c.commit(transaction), + getSnapshot: async () => c.getSnapshot(), + onSaved: (snapshot, local) => { + if (c.disposed) return; + c.saved = snapshot; + c.revision = snapshot.revision; + c.view.setDocument(local, c.revision); + if (JSON.stringify(c.terrain.toJSON()) !== JSON.stringify(local.recipe)) { + c.loading = true; + try { + c.terrain.loadJSON(local.recipe); + } finally { + c.loading = false; + } + c.renderLayers(); + } + c.renderCameras(local); + c.renderEnvironment(); + c.renderAssets(); + c.syncAssets(local); + c.build(); + }, + onNotify: (message) => { + if (!c.disposed) c.toast(`Edit not saved: ${message}`, true); + }, + onChange: () => { + if (c.disposed) return; + const local = c.saveQueue.localDocument; + if (JSON.stringify(c.terrain.toJSON()) !== JSON.stringify(local.recipe)) { + c.loading = true; + try { + c.terrain.loadJSON(local.recipe); + } finally { + c.loading = false; + } + c.renderLayers(); + c.build(); + } + c.view.setDocument(local, c.revision); + renderUnsent(c); + c.saving = c.saveQueue.hasPending(); + if (c.saving) document.body.dataset.saving = "true"; + else delete document.body.dataset.saving; + $("save-status").textContent = c.saveQueue.unsent.length + ? `${c.saveQueue.unsent.length} edit(s) not saved` + : c.saving + ? "Saving edits · local work retained" + : "Saved on disk · preview rebuilding"; + }, + }); +} + +export async function changed(c: EditorSession): Promise { + c.renderLayers(); + if (c.loading || c.disposed) return; + const next = { ...c.saved.document, recipe: c.terrain.toJSON() }; + if (!c.saveQueue.hasLocalIntent() && c.saveQueue.revision !== c.saved.revision) + c.saveQueue.adopt(c.saved); + c.saveQueue.record(next); + c.view.setDocument(c.saveQueue.localDocument, c.revision); + c.build(); + await c.saveQueue.flush(); +} +export function installEvaluation(c: EditorSession) { + c.abort = new AbortController(); + c.disposed = false; + const live = document.createElement("div"); + live.id = "editor-announcements"; + live.setAttribute("role", "status"); + live.setAttribute("aria-live", "polite"); + live.setAttribute("aria-atomic", "true"); + live.style.cssText = + "position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap"; + document.body.append(live); + const queue = new AnnouncementQueue(); + let announcementTimer: ReturnType | undefined; + const deliver = () => { + const next = queue.shift(); + if (!next) { + announcementTimer = undefined; + return; + } + live.textContent = ""; + live.setAttribute("aria-live", next.blocking ? "assertive" : "polite"); + announcementTimer = setTimeout(() => { + live.textContent = next.message; + announcementTimer = setTimeout(deliver, 1000); + }, 50); + }; + c.announce = (message, blocking = false) => { + queue.push(message, blocking); + if (announcementTimer === undefined) deliver(); + }; + const errors = new MutationObserver((records) => { + const changed = new Set(); + for (const record of records) { + const node = record.target instanceof Element ? record.target : record.target.parentElement; + const error = node?.closest('[id$="-error"], #placement-status'); + if (error) changed.add(error); + } + for (const error of changed) if (error.textContent) c.announce(error.textContent); + // A modal makes outside content inert; keep the one live region inside the active dialog. + const parent = document.querySelector("dialog[open]") ?? document.body; + if (live.parentElement !== parent) parent.append(live); + }); + errors.observe(document.body, { + subtree: true, + childList: true, + characterData: true, + attributes: true, + attributeFilter: ["open"], + }); + c.abort.signal.addEventListener( + "abort", + () => { + errors.disconnect(); + clearTimeout(announcementTimer); + live.remove(); + }, + { once: true }, + ); + $("toast").removeAttribute("role"); + const focusStyle = document.createElement("style"); + focusStyle.textContent = + ":focus-visible{outline:2px solid var(--accent)!important;outline-offset:3px}"; + document.head.append(focusStyle); + c.abort.signal.addEventListener("abort", () => focusStyle.remove(), { once: true }); + c.tools = [ + ["sculpt", "Sculpt", "Raise the ground. Hold Shift to lower."], + ["smooth", "Smooth", "Soften spikes and blend transitions."], + ["flatten", "Flatten", "Make a building pad at a chosen elevation."], + ["ramp", "Ramp", "Click two endpoints to connect their elevations."], + ["stamp", "Stamp", "Place a transformed procedural terrain feature."], + ["erode", "Erode", "Run a local thermal or hydraulic erosion pass."], + ["paint", "Paint", "Paint a material or a named biome mask."], + ["scatter", "Scatter", "Place deterministic assets inside the brush."], + [ + "spline", + "Spline", + "Click control points, or use viewport arrows and Space. Ctrl+Enter finishes the road or river.", + ], + ["water", "Water", "Flood a connected basin from the clicked point."], + [ + "select", + "Select", + "Select a prop or recipe landform. Drag its handles or enter precise values.", + ], + ]; + c.options = { + flatten: { height: 24 }, + stamp: { shape: "mountain", mirrorX: false }, + erode: { method: "thermal" }, + paint: { material: "dirt", mode: "material", biome: "forest" }, + scatter: { asset: "pine", count: 180, erase: false }, + spline: { kind: "road", width: 12, depth: 4 }, + water: { kind: "lake", level: 5 }, + ramp: { width: 22 }, + }; + c.terrain = Terrain.fromJSON(c.initial.document.recipe); + c.state = null; + c.renderedRecipe = null; + c.view = c.providedView; + c.selected = c.terrain.layers[0]?.id; + c.active = "sculpt"; + c.preset = "alpine"; + c.navigation = false; + c.points = []; + c.stroke = null; + c.busy = false; + c.sequence = 0; + c.workerBusy = false; + c.evaluationRequests = 0; + c.pending = null; + c.worker = null; + c.exportWorker = null; + c.exportReject = null; + c.timer = null; + c.toastTimer = null; + fillIcons(); + c.assetSignature = JSON.stringify(c.initial.document.assets ?? []); + c.revision = c.initial.revision; + c.renderedRevision = null; + c.saving = false; + c.loading = false; + c.saved = c.initial; + installSaveQueue(c); + c.subscribe((snapshot) => { + const action = externalSnapshotAction(c.saveQueue, snapshot); + if (action === "defer") return; + if (action === "ignore") { + c.useSnapshot(snapshot); + return; + } + c.saveQueue.adopt(snapshot); + c.saved = snapshot; + c.useSnapshot(snapshot); + }); + c.terrain.subscribe(c.changed); +} diff --git a/packages/terrain/src/editor/app/exports.ts b/packages/terrain/src/editor/app/exports.ts new file mode 100644 index 0000000000..52df8d48ff --- /dev/null +++ b/packages/terrain/src/editor/app/exports.ts @@ -0,0 +1,169 @@ +import type { ExportKind, IExportArchive } from "../../core/types.js"; +import { + MATERIAL_IDS, + Mask, + Terrain, + applyPlacementOverrides, + bakeTerrain, + decodeHeightPNG, + decodeRAW16, + makeExport, +} from "../../index.js"; +import { fillIcons, icon } from "../icons.js"; +import { PRESETS, createPreset } from "../presets.js"; +import { inspectSpatial } from "../spatialInspector.js"; +import type { EditorWorkerReply } from "../worker.js"; +import { $, errorMessage } from "./dom.js"; +import type { EditorSession } from "./session.js"; +export function download( + c: EditorSession, + output: { name: string; bytes: Uint8Array; type?: string; mime?: string }, +): void { + const blob = new Blob([new Uint8Array(output.bytes)], { type: output.type ?? output.mime }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = output.name; + a.click(); + setTimeout(() => URL.revokeObjectURL(url), 5000); +} + +export async function exportKind(c: EditorSession, kind: ExportKind | "world"): Promise { + $("export-error").textContent = ""; + if (kind === "project") { + c.download(await makeExport(c.state, c.terrain.toJSON(), kind)); + return; + } + // The whole world comes from the live view, because only the view holds the resolved models, + // the grounded transforms and the baked water. The worker export below is terrain-only, and + // presenting that as a world is the thing this card exists to stop. + if (kind === "world") { + if (!c.view.exportCurrentWorld) throw Error("This view cannot export a world GLB"); + for (const b of document.querySelectorAll("[data-export]")) + b.disabled = true; + c.setBusy(true, "Baking world GLB"); + try { + c.download(await c.view.exportCurrentWorld()); + c.toast("World GLB ready."); + } catch (e) { + $("export-error").textContent = errorMessage(e); + } finally { + for (const b of document.querySelectorAll("[data-export]")) + b.disabled = false; + if (!c.workerBusy && !c.pending) c.setBusy(false); + } + return; + } + for (const b of document.querySelectorAll("[data-export]")) { + b.disabled = true; + } + c.setBusy(true, "Baking export"); + try { + const output = await new Promise((resolve, reject) => { + c.exportReject = reject; + c.exportWorker = new Worker(c.workerURL(), { type: "module" }); + c.exportWorker.onmessage = ({ data }: MessageEvent) => { + if (data.type === "exported") resolve(data.output); + else if (data.type === "error") reject(new Error(data.error)); + else if (data.type === "progress") + $("busy-text").textContent = `Baking ${data.progress.index + 1}/${data.progress.total}`; + }; + c.exportWorker.onerror = (e) => { + e.preventDefault(); + reject(new Error(e.message)); + }; + c.exportWorker.postMessage({ + type: "export", + id: 1, + recipe: c.terrain.toJSON(), + resolution: Number($("export-resolution").value), + kind, + }); + }); + c.download(output); + c.toast(`Export ready: ${output.name}`); + } catch (e) { + $("export-error").textContent = errorMessage(e); + } finally { + c.exportWorker?.terminate(); + c.exportWorker = null; + c.exportReject = null; + for (const b of document.querySelectorAll("[data-export]")) { + b.disabled = false; + } + if (!c.workerBusy && !c.pending) c.setBusy(false); + } +} +export function installExports(c: EditorSession): void { + $("recipe-btn").onclick = () => { + $("recipe-editor").value = JSON.stringify(c.terrain.toJSON(), null, 2); + $("recipe-error").textContent = ""; + $("recipe-dialog").showModal(); + }; + $("close-recipe").onclick = () => $("recipe-dialog").close(); + $("apply-recipe").onclick = () => { + try { + c.terrain.loadJSON($("recipe-editor").value); + $("recipe-error").textContent = ""; + c.toast("Recipe validated and applied."); + } catch (e) { + $("recipe-error").textContent = errorMessage(e); + } + }; + $("copy-recipe").onclick = async () => { + try { + await navigator.clipboard.writeText($("recipe-editor").value); + c.toast("Recipe copied."); + } catch { + const ta = $("recipe-editor"); + ta.focus(); + ta.select(); + c.toast("Clipboard unavailable. JSON selected; press Ctrl/⌘ C."); + } + }; + $("export-btn").onclick = () => { + $("export-error").textContent = ""; + $("export-dialog").showModal(); + }; + $("close-export").onclick = () => $("export-dialog").close(); + for (const b of document.querySelectorAll("[data-export]")) + b.onclick = () => { + const kind = b.dataset.export; + if (!kind) throw new Error("Missing export kind"); + return c.exportKind(kind as ExportKind | "world"); + }; + $("import-btn").onclick = () => $("import-file").click(); + $("import-file").onchange = async () => { + const file = $("import-file").files?.[0]; + if (!file) return; + try { + if (file.size > 64 * 1024 * 1024) throw Error("File is larger than the 64 MB import limit"); + if (file.name.toLowerCase().endsWith(".json")) c.terrain.loadJSON(await file.text()); + else { + const bytes = new Uint8Array(await file.arrayBuffer()); + const min = Number($("import-min").value); + const max = Number($("import-max").value); + let data: Awaited> | ReturnType; + if (file.name.toLowerCase().endsWith(".png")) { + const png = await decodeHeightPNG(bytes); + data = png.hasEmbeddedRange ? png : await decodeHeightPNG(bytes, { min, max }); + } else { + const n = Math.sqrt(bytes.length / 2); + if (!Number.isInteger(n)) throw Error("RAW16 import requires a square heightfield"); + data = decodeRAW16(bytes, { width: n, height: n, min, max, littleEndian: true }); + } + c.terrain.heightmap({ + label: "Imported heightmap", + data: { width: data.width, height: data.height, values: data.values }, + blend: "replace", + }); + } + c.toast("File imported as an undoable edit."); + $("export-error").textContent = ""; + } catch (e) { + $("export-error").textContent = errorMessage(e); + } finally { + $("import-file").value = ""; + } + }; +} diff --git a/packages/terrain/src/editor/app/keyboard.ts b/packages/terrain/src/editor/app/keyboard.ts new file mode 100644 index 0000000000..ee5b4c33d7 --- /dev/null +++ b/packages/terrain/src/editor/app/keyboard.ts @@ -0,0 +1,41 @@ +/** Native controls own their keys; only the viewport turns activation into a terrain edit. */ +export function editorKeyAction( + event: { + key: string; + ctrlKey?: boolean; + metaKey?: boolean; + shiftKey?: boolean; + }, + tag = "", + viewport = false, + splinePending = false, +): string | undefined { + const key = event.key.toLowerCase(); + if (key === "escape") return "cancel"; + if (["INPUT", "TEXTAREA", "SELECT", "BUTTON", "SUMMARY"].includes(tag)) return; + if (event.ctrlKey || event.metaKey) { + if (key === "z") return event.shiftKey ? "redo" : "undo"; + if (key === "y") return "redo"; + return; + } + if (/^[0-9]$/.test(key)) return `tool:${(Number(key) + 9) % 10}`; + if (key === "q") return "select"; + if (key === "v") return "navigate"; + if (key === "f") return "frame"; + if (viewport && ["arrowup", "arrowdown", "arrowleft", "arrowright"].includes(key)) return key; + // Clicks then Enter is how the recovered GUI finishes a spline; Space still adds a point. + if (viewport && key === "enter" && splinePending) return "finish"; + if (viewport && (key === "enter" || key === " ")) return "apply"; + if (key === "enter") return "finish"; +} + +/** FIFO delivery keeps simultaneous errors and repeated messages observable. */ +export class AnnouncementQueue { + private readonly messages: { message: string; blocking: boolean }[] = []; + push(message: string, blocking = false): void { + if (message.trim()) this.messages.push({ message, blocking }); + } + shift(): { message: string; blocking: boolean } | undefined { + return this.messages.shift(); + } +} diff --git a/packages/terrain/src/editor/app/layers.ts b/packages/terrain/src/editor/app/layers.ts new file mode 100644 index 0000000000..86e6e44b42 --- /dev/null +++ b/packages/terrain/src/editor/app/layers.ts @@ -0,0 +1,172 @@ +import { + MATERIAL_IDS, + Mask, + Terrain, + applyPlacementOverrides, + bakeTerrain, + decodeHeightPNG, + decodeRAW16, + makeExport, +} from "../../index.js"; +import { fillIcons, icon } from "../icons.js"; +import { PRESETS, createPreset } from "../presets.js"; +import { inspectSpatial } from "../spatialInspector.js"; +import { $, errorMessage } from "./dom.js"; +import type { EditorSession } from "./session.js"; +export function updateHistory(c: EditorSession): void { + $("undo-btn").disabled = !c.terrain.canUndo; + $("redo-btn").disabled = !c.terrain.canRedo; +} + +export function renderLayers(c: EditorSession): void { + const layers = c.terrain.layers; + if (!layers.some((l) => l.id === c.selected)) c.selected = layers.at(-1)?.id; + $("layer-count").textContent = String(layers.length); + const list = $("layer-list"); + const focusedId = document.activeElement?.closest(".layer-row")?.dataset.id; + list.replaceChildren(); + layers.forEach((l, i) => { + const row = document.createElement("div"); + row.className = `layer-row${l.id === c.selected ? " selected" : ""}${l.enabled === false ? " disabled" : ""}`; + row.draggable = true; + row.tabIndex = 0; + row.setAttribute("role", "button"); + row.setAttribute("aria-label", `Select layer ${l.id}`); + row.onkeydown = (event) => { + if (event.target === row && (event.key === "Enter" || event.key === " ")) { + event.preventDefault(); + event.stopPropagation(); + row.click(); + } + }; + row.dataset.id = l.id; + const index = document.createElement("span"); + index.className = "layer-index"; + index.textContent = String(i + 1).padStart(2, "0"); + const glyph = document.createElement("span"); + glyph.className = "layer-icon"; + glyph.replaceChildren( + icon( + l.type === "materials" ? "paint" : ["river", "road"].includes(l.type) ? "spline" : l.type, + ), + ); + const label = document.createElement("div"); + label.className = "layer-label"; + const name = document.createElement("strong"); + const type = document.createElement("small"); + name.textContent = l.name ?? l.id; + type.textContent = l.type; + label.append(name, type); + const eye = document.createElement("button"); + eye.className = "layer-eye"; + eye.replaceChildren(icon(l.enabled === false ? "eye-off" : "eye")); + eye.title = l.enabled === false ? "Enable layer" : "Disable layer"; + eye.setAttribute("aria-label", `${eye.title}: ${l.name ?? l.id}`); + eye.onclick = (e) => { + e.stopPropagation(); + c.terrain.toggle(l.id, l.enabled === false); + }; + row.append(index, glyph, label, eye); + row.onclick = () => { + c.selected = l.id; + c.renderLayers(); + if (c.active === "select") c.view.selectLayer?.(l.id); + }; + row.ondragstart = (e) => e.dataTransfer?.setData("text/plain", l.id); + row.ondragover = (e) => e.preventDefault(); + row.ondrop = (e) => { + e.preventDefault(); + const id = e.dataTransfer?.getData("text/plain"); + if (id) c.attempt(() => c.terrain.move(id, i)); + }; + list.append(row); + if (focusedId === l.id) row.focus(); + }); + c.renderInspector(); + c.updateHistory(); +} + +export function renderInspector(c: EditorSession): void { + const layer = c.selected ? c.terrain.layer(c.selected ?? "") : null; + for (const id of [ + "selected-name", + "selected-opacity", + "selected-json", + "apply-layer", + "layer-up", + "layer-down", + "layer-duplicate", + "layer-delete", + ]) + $(id).disabled = !layer; + $("selected-type").textContent = layer ? layer.type.toUpperCase() : "LAYER PROPERTIES"; + $("selected-name").value = layer?.name ?? layer?.id ?? ""; + $("selected-opacity").value = String(layer?.opacity ?? 1); + $("opacity-value").textContent = `${Math.round((layer?.opacity ?? 1) * 100)}%`; + $("selected-json").value = layer + ? JSON.stringify({ params: layer.params, ...(layer.mask ? { mask: layer.mask } : {}) }, null, 2) + : ""; + $("layer-error").textContent = ""; +} +export function installLayers(c: EditorSession): void { + $("undo-btn").onclick = () => c.terrain.undo(); + $("redo-btn").onclick = () => c.terrain.redo(); + $("selected-opacity").oninput = () => { + $("opacity-value").textContent = `${Math.round(Number($("selected-opacity").value) * 100)}%`; + }; + $("selected-opacity").onchange = () => + c.attempt(() => + c.terrain.update(c.selected ?? "", { opacity: Number($("selected-opacity").value) }), + ); + $("selected-name").onchange = () => + c.attempt(() => c.terrain.update(c.selected ?? "", { name: $("selected-name").value })); + $("apply-layer").onclick = () => { + try { + const patch = JSON.parse($("selected-json").value); + if ( + !patch || + typeof patch !== "object" || + Array.isArray(patch) || + Object.keys(patch).some((k) => !["params", "mask"].includes(k)) + ) + throw Error('Use {"params": {...}, "mask": {...}}'); + const old = c.terrain.layer(c.selected ?? ""); + c.terrain.add({ + ...old, + params: patch.params ?? old.params, + mask: patch.mask ?? { type: "all" }, + }); + $("layer-error").textContent = ""; + } catch (e) { + $("layer-error").textContent = errorMessage(e); + } + }; + $("layer-up").onclick = () => + c.attempt(() => + c.terrain.move( + c.selected ?? "", + Math.max(0, c.terrain.layers.findIndex((l) => l.id === c.selected) - 1), + ), + ); + $("layer-down").onclick = () => + c.attempt(() => + c.terrain.move( + c.selected ?? "", + Math.min( + c.terrain.layers.length - 1, + c.terrain.layers.findIndex((l) => l.id === c.selected) + 1, + ), + ), + ); + $("layer-delete").onclick = () => c.attempt(() => c.terrain.remove(c.selected ?? "")); + $("layer-duplicate").onclick = () => + c.attempt(() => { + const layer = c.terrain.layer(c.selected ?? ""); + let i = 1; + while (c.terrain.layers.some((l) => l.id === `${layer.id.slice(0, 85)}-copy-${i}`)) i++; + const id = `${layer.id.slice(0, 85)}-copy-${i}`; + c.terrain.add({ ...layer, id, name: `${(layer.name ?? layer.type).slice(0, 85)} copy` }); + c.selected = id; + c.renderLayers(); + }); +} diff --git a/packages/terrain/src/editor/app/session.ts b/packages/terrain/src/editor/app/session.ts new file mode 100644 index 0000000000..b0fd1a5540 --- /dev/null +++ b/packages/terrain/src/editor/app/session.ts @@ -0,0 +1,208 @@ +import type { ITerrainDocument, ITerrainState, Layer } from "../../core/types.js"; +import type { Terrain } from "../../index.js"; +import type { IFocusRequest } from "../cameras.js"; +import type { IEnvironment } from "../environment.js"; +import type { IEditorView, TerrainEditorController } from "../index.js"; +import type { SaveQueue } from "../saveQueue.js"; +import type { IEditorSnapshot } from "../server.js"; + +import type { createEditorApi } from "./api.js"; +export interface IRecoveredEditorOptions { + initial: IEditorSnapshot; + providedView: IEditorView; + materialColours: readonly (readonly [number, number, number])[]; + commit: TerrainEditorController["commit"]; + getSnapshot: TerrainEditorController["snapshot"]; + cameraOperation: TerrainEditorController["camera"]; + environmentOperation: TerrainEditorController["environment"]; + assetOperation: TerrainEditorController["asset"]; + subscribe(listener: (snapshot: IEditorSnapshot) => void): void; + onEvaluated?(event: { revision: string; state: ITerrainState; ms: number }): void; +} +export interface IEvaluationRequest { + id: number; + type: "evaluate" | "export"; + recipe: ITerrainDocument; + resolution: number; + kind?: string; +} +export interface IToolOptions { + flatten: { height: number }; + stamp: { shape: "mountain" | "crater" | "ridge" | "valley" | "mesa" | "dune"; mirrorX: boolean }; + erode: { method: "thermal" | "hydraulic" }; + paint: { + material: "grass" | "dirt" | "rock" | "snow" | "sand" | "mud" | "road" | "moss"; + mode: string; + biome: string; + }; + scatter: { asset: string; count: number; erase: boolean }; + spline: { kind: "road" | "river"; width: number; depth: number }; + water: { kind: "lake" | "ocean"; level: number }; + ramp: { width: number }; +} +import { installApi } from "./api.js"; +import { attemptAsset, importFile, installAssets, renderAssets, runAsset } from "./assets.js"; +import { + attemptCamera, + focusTarget, + installCameras, + renderCameras, + selectedCameraId, +} from "./cameras.js"; +import { + attemptEnvironment, + installEnvironment, + renderEnvironment, + runEnvironment, +} from "./environment.js"; +import { + attempt, + build, + cancelBuild, + changed, + dispatch, + installEvaluation, + newWorker, + setBusy, + syncAssets, + toast, + useSnapshot, + workerURL, +} from "./evaluation.js"; +import { download, exportKind, installExports } from "./exports.js"; +import { installLayers, renderInspector, renderLayers, updateHistory } from "./layers.js"; +import { addStroke, finishSpline, installStrokes, showPoints } from "./strokes.js"; +import { installToolbar, namePreset } from "./toolbar.js"; +import { + brush, + installTools, + optionNumber, + optionSelect, + renderOptions, + selectTool, +} from "./tools.js"; +export class EditorSession { + abort!: AbortController; + disposed!: boolean; + tools!: [string, string, string][]; + options!: IToolOptions; + terrain!: Terrain; + state!: ITerrainState | null; + renderedRecipe!: string | null; + view!: IEditorView; + selected!: string | undefined; + active!: string; + preset!: string; + navigation!: boolean; + points!: [number, number, number][]; + stroke!: { pointer: number; path: [number, number][]; lower: boolean } | null; + busy!: boolean; + sequence!: number; + workerBusy!: boolean; + evaluationRequests!: number; + pending!: IEvaluationRequest | null; + worker!: Worker | null; + exportWorker!: Worker | null; + exportReject!: ((reason: Error) => void) | null; + timer!: ReturnType | null; + announce!: (message: string, blocking?: boolean) => void; + toastTimer!: ReturnType | null; + assetSignature!: string; + revision!: string; + renderedRevision!: string | null; + saveQueue!: SaveQueue; + unsentSignature!: string; + saving!: boolean; + loading!: boolean; + saved!: IEditorSnapshot; + host!: HTMLElement; + passes!: [string, string, () => unknown][]; + keydown!: (e: KeyboardEvent) => void; + cameraPanel!: HTMLDivElement; + style!: HTMLStyleElement; + sidebar!: HTMLElement; + envFields!: [keyof IEnvironment | null, string, string, string, string?][]; + envId!: (section: string | null, key: string) => string; + environmentPanel!: HTMLDetailsElement; + + hex!: (value: unknown) => string; + lane!: Promise; + inLane(task: () => Promise): Promise { + const result = this.lane.then(task, task); + this.lane = result; + return result; + } + assetPanel!: HTMLDetailsElement; + assetId!: (name: string) => string; + preview!: HTMLSelectElement; + resolution!: string; + api!: ReturnType; + updateHistory = updateHistory.bind(null, this); + renderLayers = renderLayers.bind(null, this); + renderInspector = renderInspector.bind(null, this); + brush = brush.bind(null, this); + optionSelect = optionSelect.bind(null, this); + optionNumber = optionNumber.bind(null, this); + renderOptions = renderOptions.bind(null, this); + selectTool = selectTool.bind(null, this); + addStroke = addStroke.bind(null, this); + showPoints = showPoints.bind(null, this); + finishSpline = finishSpline.bind(null, this); + namePreset = namePreset.bind(null, this); + download = download.bind(null, this); + exportKind = exportKind.bind(null, this); + selectedCameraId = selectedCameraId.bind(null, this); + renderCameras = renderCameras.bind(null, this); + attemptCamera = attemptCamera.bind(null, this); + focusTarget = focusTarget.bind(null, this); + renderEnvironment = renderEnvironment.bind(null, this); + attemptEnvironment = attemptEnvironment.bind(null, this); + runEnvironment = runEnvironment.bind(null, this); + renderAssets = renderAssets.bind(null, this); + attemptAsset = attemptAsset.bind(null, this); + runAsset = runAsset.bind(null, this); + importFile = importFile.bind(null, this); + toast = toast.bind(null, this); + attempt = attempt.bind(null, this); + setBusy = setBusy.bind(null, this); + workerURL = workerURL.bind(null, this); + newWorker = newWorker.bind(null, this); + dispatch = dispatch.bind(null, this); + build = build.bind(null, this); + cancelBuild = cancelBuild.bind(null, this); + syncAssets = syncAssets.bind(null, this); + useSnapshot = useSnapshot.bind(null, this); + changed = changed.bind(null, this); + initial: IRecoveredEditorOptions["initial"]; + providedView: IRecoveredEditorOptions["providedView"]; + materialColours: IRecoveredEditorOptions["materialColours"]; + commit: IRecoveredEditorOptions["commit"]; + getSnapshot: IRecoveredEditorOptions["getSnapshot"]; + cameraOperation: IRecoveredEditorOptions["cameraOperation"]; + environmentOperation: IRecoveredEditorOptions["environmentOperation"]; + assetOperation: IRecoveredEditorOptions["assetOperation"]; + subscribe: IRecoveredEditorOptions["subscribe"]; + onEvaluated: IRecoveredEditorOptions["onEvaluated"]; + constructor(options: IRecoveredEditorOptions) { + this.initial = options.initial; + this.providedView = options.providedView; + this.materialColours = options.materialColours; + this.commit = options.commit; + this.getSnapshot = options.getSnapshot; + this.cameraOperation = options.cameraOperation; + this.environmentOperation = options.environmentOperation; + this.assetOperation = options.assetOperation; + this.subscribe = options.subscribe; + this.onEvaluated = options.onEvaluated; + installEvaluation(this); + installTools(this); + installStrokes(this); + installLayers(this); + installToolbar(this); + installExports(this); + installCameras(this); + installEnvironment(this); + installAssets(this); + installApi(this); + } +} diff --git a/packages/terrain/src/editor/app/strokes.ts b/packages/terrain/src/editor/app/strokes.ts new file mode 100644 index 0000000000..1bf9a42342 --- /dev/null +++ b/packages/terrain/src/editor/app/strokes.ts @@ -0,0 +1,267 @@ +import { + MATERIAL_IDS, + Mask, + Terrain, + applyPlacementOverrides, + bakeTerrain, + decodeHeightPNG, + decodeRAW16, + makeExport, +} from "../../index.js"; +import { fillIcons, icon } from "../icons.js"; +import { PRESETS, createPreset } from "../presets.js"; +import { inspectSpatial } from "../spatialInspector.js"; +import { $, errorMessage } from "./dom.js"; +import { editorKeyAction } from "./keyboard.js"; +import type { EditorSession } from "./session.js"; +export function addStroke(c: EditorSession, path: [number, number][], lower: boolean): void { + if (!path[0]) return; + const b = c.brush(); + const at = path[0]; + const mask = Mask.circle(at, b.radius, b.falloff); + const tool = c.tools.find((t) => t[0] === c.active); + if (!tool) throw new Error(`Unknown editor tool: ${c.active}`); + const label = `${tool[1]} stroke`; + c.terrain.transaction(() => { + if (["sculpt", "smooth", "flatten"].includes(c.active)) { + const p = { ...b, points: path, label }; + if (c.active === "sculpt") p.strength = b.strength * 22 * (lower ? -1 : 1); + if (c.active === "smooth") c.terrain.smooth({ ...p, iterations: 4 }); + if (c.active === "flatten") c.terrain.flatten({ ...p, height: c.options.flatten.height }); + if (c.active === "sculpt") c.terrain.sculpt(p); + } else if (c.active === "stamp") + c.terrain.stamp({ + label: `${c.options.stamp.shape} stamp`, + at, + radius: b.radius, + amplitude: b.strength * 130, + shape: c.options.stamp.shape, + rotation: b.rotation, + roughness: 0.18, + falloff: b.falloff, + }); + else if (c.active === "erode") + c.terrain.erode({ + label: `${c.options.erode.method} erosion`, + ...b, + at, + method: c.options.erode.method, + iterations: 14, + droplets: 1600, + maxSteps: 45, + opacity: b.strength, + strength: 1, + }); + else if (c.active === "paint") { + const p = { ...b, points: path, label }; + if (c.options.paint.mode === "biome") + c.terrain.biome({ ...p, name: c.options.paint.biome, value: lower ? 0 : 1 }); + else c.terrain.paint({ ...p, material: c.options.paint.material }); + } else if (c.active === "scatter") { + const o = c.options.scatter; + if (o.erase) + c.terrain.clear({ label: `Clear ${o.asset}`, target: "scatter", asset: o.asset, mask }); + else + c.terrain.scatter({ + label: `Scatter ${o.asset}`, + asset: o.asset, + count: o.count, + minDistance: o.asset === "pine" ? 4 : 1.5, + scale: [0.7, 1.35], + maxSlope: o.asset === "boulder" ? 65 : 36, + mask, + }); + } else if (c.active === "water") { + const o = c.options.water; + c.terrain.water({ + label: o.kind === "lake" ? "Lake water" : "Ocean water", + kind: o.kind, + at, + radius: o.kind === "ocean" ? c.terrain.config.size : b.radius, + level: o.level, + }); + } + }); + c.selected = c.terrain.layers.at(-1)?.id; + c.renderLayers(); +} + +export function showPoints(c: EditorSession): void { + c.view.showSpline(c.points); + $("spline-bar").classList.toggle("hidden", !c.points.length); + $("spline-count").textContent = + `${c.points.length} control point${c.points.length === 1 ? "" : "s"}`; + $("finish-spline").disabled = c.points.length < 2; +} + +export function finishSpline(c: EditorSession): void { + const [from, to] = c.points; + if (!from || !to) return; + c.attempt(() => { + if (c.active === "ramp") + c.terrain.ramp({ + label: "Terrain ramp", + from, + to, + width: c.options.ramp.width, + shoulder: c.options.ramp.width * 0.5, + }); + else { + const o = c.options.spline; + const path: [number, number, number][] = c.points.map((p) => [...p]); + if (o.kind === "river") + for (let i = 1; i < path.length; i++) { + const current = path[i]; + const previous = path[i - 1]; + if (current && previous) current[1] = Math.min(current[1], previous[1] - 0.1); + } + c.terrain[o.kind]({ + label: o.kind === "river" ? "River spline" : "Road spline", + points: path, + width: o.width, + shoulder: o.width * 0.7, + ...(o.kind === "river" ? { depth: o.depth, enforceDownhill: true } : {}), + }); + } + c.points = []; + c.showPoints(); + c.selected = c.terrain.layers.at(-1)?.id; + c.renderLayers(); + }); +} +export function installStrokes(c: EditorSession): void { + c.host = $("viewport"); + c.host.addEventListener("pointerdown", (e) => { + if ( + e.target !== c.host && + (!(e.target instanceof Element) || !e.target.classList.contains("render-canvas")) + ) + return; + c.host.focus(); + if (e.button !== 0 || e.altKey || c.navigation) return; + if (c.active === "select") return; + const hit = c.view.pick(e.clientX, e.clientY); + if (!hit) return; + if (c.active === "spline" || c.active === "ramp") { + c.points.push(hit); + c.showPoints(); + if (c.active === "ramp" && c.points.length === 2) c.finishSpline(); + return; + } + c.stroke = { pointer: e.pointerId, path: [[hit[0], hit[2]]], lower: e.shiftKey }; + c.host.setPointerCapture(e.pointerId); + e.preventDefault(); + }); + c.host.addEventListener("pointermove", (e) => { + const hit = c.view.pick(e.clientX, e.clientY); + if (!hit) { + c.view.setBrush(null); + return; + } + $("coordinates").textContent = + `X ${hit[0].toFixed(1)} · Y ${hit[1].toFixed(1)} · Z ${hit[2].toFixed(1)} m`; + const b = c.brush(); + c.view.setBrush(c.navigation || c.active === "select" ? null : { ...b, at: [hit[0], hit[2]] }); + if (c.stroke && ["sculpt", "smooth", "flatten", "paint"].includes(c.active)) { + const last = c.stroke.path.at(-1); + if ( + last && + Math.hypot(last[0] - hit[0], last[1] - hit[2]) > Math.max(0.3, b.radius * 0.04) && + c.stroke.path.length < 2048 + ) + c.stroke.path.push([hit[0], hit[2]]); + } + }); + c.host.addEventListener("pointerup", (e) => { + if (c.stroke?.pointer === e.pointerId) { + const current = c.stroke; + c.stroke = null; + if (c.host.hasPointerCapture(e.pointerId)) c.host.releasePointerCapture(e.pointerId); + c.attempt(() => c.addStroke(current.path, current.lower)); + } + }); + c.host.addEventListener("pointercancel", () => { + c.stroke = null; + }); + c.host.addEventListener("pointerleave", () => { + if (!c.stroke) c.view.setBrush(null); + }); + $("finish-spline").onclick = c.finishSpline; + $("cancel-spline").onclick = () => { + c.points = []; + c.showPoints(); + }; + c.host.tabIndex = 0; + c.host.setAttribute( + "aria-label", + "Terrain viewport. Arrows move the brush; Enter or Space applies; Shift lowers; Enter or Ctrl+Enter finishes a spline of two or more points; Escape cancels.", + ); + let cursor: [number, number] | undefined; + c.keydown = (e) => { + const target = e.target instanceof Element ? e.target : undefined; + const action = editorKeyAction( + e, + target?.tagName, + target === c.host, + (c.active === "spline" || c.active === "ramp") && c.points.length >= 2, + ); + if (!action) return; + if (action === "cancel") { + if (c.stroke && c.host.hasPointerCapture(c.stroke.pointer)) + c.host.releasePointerCapture(c.stroke.pointer); + c.stroke = null; + c.points = []; + c.showPoints(); + c.view.setBrush(null); + return; + } + if (document.querySelector("dialog[open]")) return; + if (c.active === "select" && (action === "undo" || action === "redo")) return; + e.preventDefault(); + if (action === "undo") c.terrain.undo(); + else if (action === "redo") c.terrain.redo(); + else if (action.startsWith("tool:")) { + const tool = c.tools[Number(action.slice(5))]; + if (tool) c.selectTool(tool[0]); + } else if (action === "select") c.selectTool("select"); + else if (action === "navigate") $("navigate-btn").click(); + else if (action === "frame") c.view.frame(); + else if (action === "finish") c.finishSpline(); + else if (!c.navigation && c.active !== "select") { + const rect = c.host.getBoundingClientRect(); + cursor ??= [rect.width / 2, rect.height / 2]; + if (action === "arrowleft") cursor[0] -= 20; + if (action === "arrowright") cursor[0] += 20; + if (action === "arrowup") cursor[1] -= 20; + if (action === "arrowdown") cursor[1] += 20; + cursor = [ + Math.max(0, Math.min(rect.width - 1, cursor[0])), + Math.max(0, Math.min(rect.height - 1, cursor[1])), + ]; + const hit = c.view.pick(rect.left + cursor[0], rect.top + cursor[1]); + if (!hit) { + c.view.setBrush(null); + return; + } + c.view.setBrush({ ...c.brush(), at: [hit[0], hit[2]] }); + if (action !== "apply") return; + if (c.active === "spline" || c.active === "ramp") { + c.points.push(hit); + c.showPoints(); + if (c.active === "ramp" && c.points.length === 2) c.finishSpline(); + } else c.attempt(() => c.addStroke([[hit[0], hit[2]]], e.shiftKey)); + } + }; + c.host.addEventListener( + "keydown", + (event) => { + // Ctrl/Enter commits a keyboard-authored spline without stealing native button activation. + if (event.ctrlKey && event.key === "Enter" && c.points.length >= 2) { + event.preventDefault(); + event.stopPropagation(); + c.finishSpline(); + } + }, + { signal: c.abort.signal }, + ); +} diff --git a/packages/terrain/src/editor/app/toolbar.ts b/packages/terrain/src/editor/app/toolbar.ts new file mode 100644 index 0000000000..c64a75a20f --- /dev/null +++ b/packages/terrain/src/editor/app/toolbar.ts @@ -0,0 +1,159 @@ +import { + MATERIAL_IDS, + Mask, + Terrain, + applyPlacementOverrides, + bakeTerrain, + decodeHeightPNG, + decodeRAW16, + makeExport, +} from "../../index.js"; +import { fillIcons, icon } from "../icons.js"; +import { PRESETS, createPreset } from "../presets.js"; +import { inspectSpatial } from "../spatialInspector.js"; +import { $, element } from "./dom.js"; +import type { EditorSession } from "./session.js"; +export function namePreset(c: EditorSession, id: string): void { + const p = PRESETS.find((x) => x.id === id); + if (!p) throw new Error(`Unknown preset: ${id}`); + $("project-name").textContent = p.name; + $("landscape-title").textContent = p.name; + $("landscape-subtitle").textContent = p.subtitle; + $("landscape-tag").textContent = `LANDSCAPE / ${p.tag}`; +} +export function installToolbar(c: EditorSession): void { + for (const b of document.querySelectorAll("[data-panel]")) + b.onclick = () => { + for (const x of document.querySelectorAll("[data-panel]")) { + x.classList.toggle("active", x === b); + } + $("tools-panel").classList.toggle("hidden", b.dataset.panel !== "tools"); + $("generate-panel").classList.toggle("hidden", b.dataset.panel !== "generate"); + }; + for (const p of PRESETS) { + const b = document.createElement("button"); + b.className = `preset-card${p.id === c.preset ? " active" : ""}`; + b.dataset.preset = p.id; + b.append( + element( + "span", + { class: `preset-thumb ${p.id}` }, + icon(p.id === "desert" ? "terrace" : p.id === "island" ? "water" : "mountain"), + ), + element("span", {}, element("strong", {}, p.name), element("small", {}, p.tag)), + ); + b.onclick = () => { + c.preset = p.id; + for (const el of document.querySelectorAll("[data-preset]")) { + el.classList.toggle("active", el === b); + } + }; + $("preset-list").append(b); + } + $("generate-world").onclick = () => + c.attempt(() => { + const next = createPreset(c.preset, { + resolution: c.terrain.config.resolution, + seed: Number($("seed-input").value), + size: Number($("world-size").value), + }); + c.terrain.loadJSON(next.toJSON()); + c.namePreset(c.preset); + c.selected = c.terrain.layers[0]?.id; + c.renderLayers(); + c.points = []; + c.showPoints(); + c.view.frame(); + c.toast("Landscape regenerated. Undo restores the previous stack."); + }); + c.passes = [ + [ + "noise", + "Noise", + () => c.terrain.noise({ label: "Detail noise", amplitude: 4, scale: 30, warp: 7 }), + ], + [ + "mountain", + "Mountains", + () => + c.terrain.stamp({ + label: "Mountain mass", + at: [0, -80], + radius: [135, 90], + amplitude: 95, + shape: "mountain", + }), + ], + [ + "ramp", + "Valleys", + () => + c.terrain.stamp({ + label: "Valley basin", + at: [0, 0], + radius: [100, 150], + amplitude: 35, + shape: "valley", + }), + ], + [ + "erode", + "Erosion", + () => + c.terrain.erode({ + label: "Hydraulic erosion", + method: "hydraulic", + droplets: 8000, + maxSteps: 45, + }), + ], + [ + "biome", + "Biomes", + () => + c.terrain.biome({ + label: "Forest biome", + name: "forest", + mask: Mask.and(Mask.height(5, 100, 10), Mask.slope(0, 35, 8)), + }), + ], + [ + "scatter", + "Populate", + () => + c.terrain.scatter({ + label: "Forest population", + asset: "pine", + count: 800, + minDistance: 5, + mask: Mask.and(Mask.height(5, 100, 10), Mask.slope(0, 35, 8)), + }), + ], + ]; + for (const [ico, label, fn] of c.passes) { + const b = document.createElement("button"); + b.append(icon(ico), label); + b.onclick = () => c.attempt(fn); + $("pass-grid").append(b); + } + $("navigate-btn").onclick = () => { + c.navigation = !c.navigation; + c.view.setNavigation(c.navigation); + $("navigate-btn").classList.toggle("active", c.navigation); + c.view.setBrush(null); + }; + $("view-angle").onchange = () => c.view.setView($("view-angle").value); + $("render-mode").onchange = () => c.view.setMode($("render-mode").value); + $("preview-resolution").onchange = c.build; + $("frame-btn").onclick = () => c.view.frame(); + $("rebuild-btn").onclick = c.build; + $("cancel-build").onclick = c.cancelBuild; + $("mobile-tools").onclick = () => { + document.body.dataset.mobilePanel = + document.body.dataset.mobilePanel === "tools" ? "" : "tools"; + }; + $("mobile-layers").onclick = () => { + document.body.dataset.mobilePanel = + document.body.dataset.mobilePanel === "layers" ? "" : "layers"; + }; +} diff --git a/packages/terrain/src/editor/app/tools.ts b/packages/terrain/src/editor/app/tools.ts new file mode 100644 index 0000000000..f32a2d3014 --- /dev/null +++ b/packages/terrain/src/editor/app/tools.ts @@ -0,0 +1,229 @@ +import { MATERIAL_IDS } from "../../index.js"; +import { icon } from "../icons.js"; +import { $, element, errorMessage, field } from "./dom.js"; +import type { EditorSession } from "./session.js"; +export function brush(c: EditorSession) { + return { + radius: Number($("brush-radius").value), + strength: Number($("brush-strength").value), + falloff: Number($("brush-falloff").value), + shape: $("brush-shape").value as "circle" | "square", + rotation: Number($("brush-rotation").value), + spacing: Number($("brush-spacing").value), + jitter: Number($("brush-jitter").value), + }; +} +export function optionSelect( + _c: EditorSession, + label: string, + key: string, + choices: readonly string[], + value: string, +) { + const input = element("select", { "data-option": key }); + for (const choice of choices) + input.add( + new Option(choice.charAt(0).toUpperCase() + choice.slice(1), choice, false, choice === value), + ); + return field(label, input); +} +export function optionNumber( + _c: EditorSession, + label: string, + key: string, + value: number, + step = 1, +) { + return field( + label, + element("input", { + type: "number", + "data-option": key, + value: String(value), + step: String(step), + }), + ); +} +export function renderOptions(c: EditorSession): void { + const nodes: HTMLElement[] = []; + const note = (text: string) => nodes.push(element("p", { class: "small muted" }, text)); + switch (c.active) { + case "flatten": + nodes.push(c.optionNumber("Target elevation (m)", "height", c.options.flatten.height)); + note("Use the terrain Y coordinate as a guide."); + break; + case "stamp": + nodes.push( + c.optionSelect( + "Stamp shape", + "shape", + ["mountain", "crater", "ridge", "valley", "mesa", "dune"], + c.options.stamp.shape, + ), + ); + note( + "Strength controls amplitude. Rotate in Brush dynamics. JSON also supports anisotropic scale and mirrored heightmaps.", + ); + break; + case "erode": + nodes.push( + c.optionSelect("Simulation", "method", ["thermal", "hydraulic"], c.options.erode.method), + ); + note("Evaluates on the current heightfield, then blends inside the brush footprint."); + break; + case "paint": { + const o = c.options.paint; + nodes.push(c.optionSelect("Paint target", "mode", ["material", "biome"], o.mode)); + if (o.mode === "biome") + nodes.push( + field("Biome name", element("input", { "data-option": "biome", value: o.biome })), + ); + else { + const grid = element("div", { class: "swatch-grid" }); + MATERIAL_IDS.forEach((id, i) => { + const colour = c.materialColours[i]; + if (!colour) throw new Error(`Missing material colour: ${id}`); + const swatch = element( + "button", + { + class: `swatch${o.material === id ? " active" : ""}`, + "data-material": id, + title: id, + }, + element("i", { + style: `background:rgb(${colour.map((value) => Math.round(value * 255)).join(",")})`, + }), + element("span", {}, id), + ); + grid.append(swatch); + }); + nodes.push(grid); + } + break; + } + case "scatter": { + const o = c.options.scatter; + const assets = c.view.propAssets?.() ?? ["pine", "boulder", "grass"]; + nodes.push( + c.optionSelect( + "Asset ID", + "asset", + assets, + assets.includes(o.asset) ? o.asset : (assets[0] ?? ""), + ), + c.optionNumber("Requested instances", "count", o.count), + ); + const erase = element("input", { type: "checkbox", "data-option": "erase" }); + erase.checked = o.erase; + nodes.push(field("", element("span", {}, erase, " Erase this asset inside brush"))); + note("The palette is this project's own assets; register another through the view."); + break; + } + case "spline": { + const o = c.options.spline; + nodes.push( + c.optionSelect("Spline type", "kind", ["road", "river"], o.kind), + c.optionNumber("Width (m)", "width", o.width), + ); + if (o.kind === "river") nodes.push(c.optionNumber("River depth (m)", "depth", o.depth)); + note("Control points retain their elevations. Edit the layer JSON for precise profiles."); + break; + } + case "water": { + const o = c.options.water; + nodes.push( + c.optionSelect("Water body", "kind", ["lake", "ocean"], o.kind), + c.optionNumber("Water level (m)", "level", o.level, 0.5), + ); + note("Lake seed must be below the water level. Oceans flood from map boundaries."); + break; + } + case "ramp": + nodes.push(c.optionNumber("Ramp width (m)", "width", c.options.ramp.width)); + note("First click: start. Second click: end. The shoulder blends into the terrain."); + break; + } + $("tool-options").replaceChildren(...nodes); +} +export function selectTool(c: EditorSession, id: string): void { + const tool = c.tools.find((tool) => tool[0] === id); + if (!tool) throw new Error(`Unknown editor tool: ${id}`); + c.stroke = null; + c.active = id; + document.body.dataset.terrainTool = id; + c.navigation = false; + c.view.setNavigation(false); + c.view.setSelection?.(id === "select"); + c.view.setBrush(null); + $("navigate-btn").classList.remove("active"); + c.points = []; + c.showPoints(); + const i = c.tools.indexOf(tool); + $("brush-title").textContent = + id === "select" ? "Select a prop or landform" : `${tool[1]} terrain`; + $("brush-desc").textContent = tool[2]; + $("tool-key").textContent = id === "select" ? "Q" : String((i + 1) % 10); + for (const button of document.querySelectorAll(".tool-button")) { + button.classList.toggle("active", button.dataset.tool === id); + button.setAttribute("aria-pressed", String(button.dataset.tool === id)); + } + c.renderOptions(); + $("canvas-help").replaceChildren( + icon("mouse"), + element("span", {}, `${tool[2]} `, element("b", {}, "·"), " Right-drag navigates"), + ); +} +export function installTools(c: EditorSession): void { + c.tools.forEach(([id, name], i) => { + const button = element( + "button", + { class: "tool-button", "data-tool": id }, + icon(id === "select" ? "mouse" : id), + element("span", {}, name), + element("small", {}, id === "select" ? "Q" : String((i + 1) % 10)), + ); + button.onclick = () => c.selectTool(id); + $("tool-grid").append(button); + }); + $("tool-options").onchange = (event) => { + const target = event.target; + if (!(target instanceof HTMLInputElement || target instanceof HTMLSelectElement)) return; + const key = target.dataset.option; + if (!key) return; + const options = c.options[c.active as keyof typeof c.options]; + if (!options || !(key in options)) return; + // The controls above define each option's type; the evaluator validates the resulting recipe. + Object.assign(options, { + [key]: + target instanceof HTMLInputElement && target.type === "checkbox" + ? target.checked + : target.type === "number" + ? Number(target.value) + : target.value, + }); + if (key === "mode" || key === "kind") c.renderOptions(); + }; + $("tool-options").onclick = (event) => { + if (!(event.target instanceof Element)) return; + const button = event.target.closest("[data-material]"); + const material = button?.dataset.material; + if (material && MATERIAL_IDS.includes(material as (typeof MATERIAL_IDS)[number])) { + c.options.paint.material = material as (typeof MATERIAL_IDS)[number]; + c.renderOptions(); + } + }; + const fields: [string, string, (value: number) => string][] = [ + ["brush-radius", "radius-value", (v) => `${v} m`], + ["brush-strength", "strength-value", (v) => `${Math.round(v * 100)}%`], + ["brush-falloff", "falloff-value", (v) => `${Math.round(v * 100)}%`], + ["brush-rotation", "rotation-value", (v) => `${v}°`], + ["brush-spacing", "spacing-value", (v) => `${Math.round(v * 100)}%`], + ["brush-jitter", "jitter-value", (v) => `${Math.round(v * 100)}%`], + ]; + for (const [id, out, format] of fields) { + $(id).setAttribute("aria-label", id.replace("brush-", "Brush ")); + $(id).oninput = () => { + $(out).textContent = format(Number($(id).value)); + }; + } +} diff --git a/packages/terrain/src/editor/icons.js b/packages/terrain/src/editor/icons.ts similarity index 70% rename from packages/terrain/src/editor/icons.js rename to packages/terrain/src/editor/icons.ts index 2d4e060edc..38dfb04edb 100644 --- a/packages/terrain/src/editor/icons.js +++ b/packages/terrain/src/editor/icons.ts @@ -1,4 +1,4 @@ -const paths = { +const paths: Record = { sculpt: "M3 19 9 7l4 7 3-5 5 10H3Z M9 3v5M6 5h6", smooth: "M2 15c4 0 3-6 7-6s3 6 7 6 4-4 6-4 M3 20h18", flatten: "M2 17h5l3-8h7l5 8 M9 5h9", @@ -35,9 +35,24 @@ const paths = { clear: "m3 15 9-12 10 8-8 10H9l-6-6Zm5-6 10 8M14 21h8", terrace: "M2 20h5v-5h5v-5h5V5h5", }; -export function icon(name) { - return ``; +export function icon(name: string): SVGSVGElement { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + for (const [key, value] of Object.entries({ + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "1.55", + "stroke-linecap": "round", + "stroke-linejoin": "round", + "aria-hidden": "true", + })) + svg.setAttribute(key, value); + const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + path.setAttribute("d", paths[name] ?? paths.layers ?? ""); + svg.append(path); + return svg; } -export function fillIcons(root = document) { - for (const el of root.querySelectorAll("[data-icon]")) el.innerHTML = icon(el.dataset.icon); +export function fillIcons(root: ParentNode = document): void { + for (const el of root.querySelectorAll("[data-icon]")) + el.replaceChildren(icon(el.dataset.icon ?? "layers")); } diff --git a/packages/terrain/src/editor/index.ts b/packages/terrain/src/editor/index.ts index f57e46299b..56c28a6f30 100644 --- a/packages/terrain/src/editor/index.ts +++ b/packages/terrain/src/editor/index.ts @@ -12,7 +12,7 @@ import type { } from "./cameras.js"; import type { IEnvironment, IEnvironmentOperation, IEnvironmentResult } from "./environment.js"; import type { IAuthoringDocument, IEditorActivation, IEditorSnapshot } from "./server.js"; -import { editorShell } from "./shell.js"; +import { createEditorShell } from "./shell.js"; import type { ISpatialObservation, ISpatialQuery } from "./spatialInspector.js"; export { @@ -250,6 +250,12 @@ export class TerrainEditorController { ): Promise { return this.#request("document", transaction); } + reload(): Promise { + return this.#request("reload", {}); + } + restoreLastValid(): Promise { + return this.#request("restore", {}); + } subscribe(listener: (snapshot: IEditorSnapshot) => void, onError: () => void): () => void { const events = new EventSource(new URL("events", this.baseUrl)); events.onmessage = ({ data }) => { @@ -277,11 +283,7 @@ export async function mountTerrainEditor(options: { materialColours: readonly (readonly [number, number, number])[]; onEvaluated?(event: { revision: string; state: ITerrainState; ms: number }): void; }): Promise<{ controller: TerrainEditorController; dispose(): void }> { - document.body.innerHTML = editorShell; - const lock = document.createElement("style"); - lock.textContent = - 'body[data-saving="true"] button,body[data-saving="true"] input,body[data-saving="true"] select,body[data-saving="true"] textarea{pointer-events:none}'; - document.head.append(lock); + document.body.replaceChildren(createEditorShell()); const host = document.getElementById("viewport"); if (!host) throw new Error("Recovered editor has no viewport"); const controller = new TerrainEditorController(new URL("./", location.href).href); @@ -317,7 +319,17 @@ export async function mountTerrainEditor(options: { unsubscribe(); controls.dispose(); view.dispose(); - lock.remove(); }, }; } + +export { landformPose, transformedLandform } from "./landforms.js"; +export { + pickEditorSurface, + pickEditorPlacement, + editorPointToClient, + editorPlacementCenter, + editorSurfaceHeight, +} from "./picking.js"; + +export { createEditorSelection } from "./selection.js"; diff --git a/examples/strata-terrain-preview/src/render/landforms.ts b/packages/terrain/src/editor/landforms.ts similarity index 70% rename from examples/strata-terrain-preview/src/render/landforms.ts rename to packages/terrain/src/editor/landforms.ts index 8265194ad6..db92da8796 100644 --- a/examples/strata-terrain-preview/src/render/landforms.ts +++ b/packages/terrain/src/editor/landforms.ts @@ -1,12 +1,18 @@ -import type { Layer } from "@threenative/terrain"; import { Quaternion, Vector3 } from "three"; +import type { Layer } from "../core/types.js"; -/** Recipe footprints are heights over X/Z, never independently transformed solids. */ -export function landformPose(layer: Layer, worldSize: number) { +/** + * Recipe footprints are heights over X/Z, never independently transformed solids. + * @requires npm i -D @threenative/terrain + * @situation derive a terrain-editor gizmo pose from a landform layer + * @constraint the caller supplies world and default footprint sizes; unsupported layers return undefined + * @example const pose = landformPose(layer, terrain.size, footprintSize); + */ +export function landformPose(layer: Layer, worldSize: number, defaultSize: number) { if (layer.type !== "stamp" && layer.type !== "paste" && layer.type !== "heightmap") return undefined; const p = layer.params; - const size = p.size ?? (layer.type === "heightmap" ? worldSize : 120); + const size = p.size ?? (layer.type === "heightmap" ? worldSize : defaultSize); const radius = "radius" in p && p.radius !== undefined ? p.radius @@ -26,6 +32,13 @@ export function landformPose(layer: Layer, worldSize: number) { }; } +/** + * Convert a terrain-editor gizmo pose into a heightfield landform footprint. + * @requires npm i -D @threenative/terrain + * @situation apply a terrain-editor gizmo transform to a landform layer + * @constraint finite coordinates and positive scales are required; X/Z rotations that overhang throw + * @example const next = transformedLandform(layer, position, quaternion, scale, surfaceHeight); + */ export function transformedLandform( layer: Layer, position: Vector3, diff --git a/packages/terrain/src/editor/picking.ts b/packages/terrain/src/editor/picking.ts new file mode 100644 index 0000000000..996911646d --- /dev/null +++ b/packages/terrain/src/editor/picking.ts @@ -0,0 +1,126 @@ +import { + type Camera, + type InstancedMesh, + type Intersection, + Matrix4, + type Mesh, + type Object3D, + Quaternion, + Vector2, + Vector3, +} from "three"; +import type { IPlacementOverride } from "../core/types.js"; + +type EditorRaycast = (options: { + screen?: Vector2; + origin?: Vector3; + direction?: Vector3; + targets: readonly Object3D[]; +}) => Intersection | undefined; + +/** + * Convert viewport client coordinates once; the installed picker owns ray intersection. + * @requires npm i -D @threenative/terrain + * @situation pick a terrain-editor surface from viewport client coordinates + * @constraint the caller supplies the raycast implementation and ordered targets + * @example const hit = pickEditorSurface(ctx, [terrainMesh], event.clientX, event.clientY); + */ +export function pickEditorSurface( + ctx: { + renderer: { domElement: { getBoundingClientRect(): { left: number; top: number } } }; + raycast: EditorRaycast; + }, + targets: readonly Object3D[], + clientX: number, + clientY: number, +) { + const box = ctx.renderer.domElement.getBoundingClientRect(); + return ctx.raycast({ + screen: new Vector2(clientX - box.left, clientY - box.top), + targets, + }); +} + +/** + * Query the rendered heightfield, including its triangulation rather than an interpolated bake. + * @requires npm i -D @threenative/terrain + * @situation query terrain-editor surface elevation at world X/Z coordinates + * @constraint the supplied mesh and raycast determine the surface; missing intersections return undefined + * @example const height = editorSurfaceHeight(ctx, terrainMesh, x, z); + */ +export function editorSurfaceHeight( + ctx: { raycast: EditorRaycast }, + mesh: Mesh, + x: number, + z: number, + originHeight?: number, +): number | undefined { + let height = originHeight; + if (height === undefined) { + mesh.geometry.computeBoundingBox(); + const top = mesh.geometry.boundingBox?.max.y; + if (top === undefined) return undefined; + height = top + 1; + } + return ctx.raycast({ + origin: new Vector3(x, height, z), + direction: new Vector3(0, -1, 0), + targets: [mesh], + })?.point.y; +} + +/** + * A foreground terrain hit occludes placements; matching uses the evaluated world position. + * @requires npm i -D @threenative/terrain + * @situation pick a placed instance in the terrain editor from viewport client coordinates + * @constraint the caller supplies the raycast context, terrain mesh, instanced meshes and evaluated placements + * @example const id = pickEditorPlacement(ctx, terrainMesh, meshes, placements, event.clientX, event.clientY); + */ +export function pickEditorPlacement( + ctx: Parameters[0], + terrain: Mesh, + meshes: readonly Object3D[], + placements: Iterable<{ pose: Matrix4; placement: { id: string } }>, + clientX: number, + clientY: number, +): string | undefined { + const hit = pickEditorSurface(ctx, [terrain, ...meshes], clientX, clientY); + if (hit?.instanceId === undefined) return undefined; + const matrix = new Matrix4(); + (hit.object as InstancedMesh).getMatrixAt(hit.instanceId, matrix); + const position = new Vector3().setFromMatrixPosition(matrix.premultiply(hit.object.matrixWorld)); + return [...placements].find( + (instance) => + new Vector3().setFromMatrixPosition(instance.pose).distanceToSquared(position) < 1e-6, + )?.placement.id; +} + +/** + * Project an injected world point to client pixels in the caller's viewport. + * @requires npm i -D @threenative/terrain + * @situation convert a terrain-editor world point to viewport client pixels + * @constraint the point is projected in place; pass a clone to keep the original + * @example const [x, y] = editorPointToClient(point.clone(), camera, canvas.getBoundingClientRect()); + */ +export function editorPointToClient( + point: Vector3, + camera: Camera, + box: { width: number; height: number; left: number; top: number }, +): [number, number] { + point.project(camera); + return [((point.x + 1) * box.width) / 2 + box.left, ((1 - point.y) * box.height) / 2 + box.top]; +} + +/** + * Transform the caller's measured geometry centre into the evaluated placement's world space. + * @requires npm i -D @threenative/terrain + * @situation find the world-space centre of a placed terrain-editor instance + * @constraint the point is multiplied in place; the pose needs finite position, quaternion and scale + * @example const centre = editorPlacementCenter(localCentre.clone(), placement); + */ +export function editorPlacementCenter(point: Vector3, pose: IPlacementOverride): Vector3 { + return point + .multiply(new Vector3().fromArray(pose.scale)) + .applyQuaternion(new Quaternion().fromArray(pose.quaternion)) + .add(new Vector3().fromArray(pose.position)); +} diff --git a/packages/terrain/src/editor/presets.js b/packages/terrain/src/editor/presets.ts similarity index 97% rename from packages/terrain/src/editor/presets.js rename to packages/terrain/src/editor/presets.ts index 75005cad3d..d7ec700c03 100644 --- a/packages/terrain/src/editor/presets.js +++ b/packages/terrain/src/editor/presets.ts @@ -28,7 +28,7 @@ export const PRESETS = [ export function createPreset(id = "alpine", { resolution = 257, seed = 73, size = 512 } = {}) { const t = new Terrain({ size, resolution, seed }); const q = size / 512; - const pt = (x, y, z) => [x * q, y * q, z * q]; + const pt = (x: number, y: number, z: number): [number, number, number] => [x * q, y * q, z * q]; if (id === "blank") return t; t.transaction(() => { if (id === "alpine") { @@ -41,7 +41,7 @@ export function createPreset(id = "alpine", { resolution = 257, seed = 73, size warp: 35 * q, octaves: 5, }); - const peaks = [ + const peaks: [number, number, number, number, number][] = [ [-137, -105, 135, 155, 133], [-10, -150, 136, 130, 156], [110, -115, 135, 147, 137], @@ -271,7 +271,7 @@ export function createPreset(id = "alpine", { resolution = 257, seed = 73, size [1, -140, -110], [2, 83, -86], [3, 150, 140], - ]) + ] as const) t.stamp({ id: `mesa-${i}`, label: `Mesa ${i}`, diff --git a/packages/terrain/src/editor/saveQueue.ts b/packages/terrain/src/editor/saveQueue.ts new file mode 100644 index 0000000000..78302b03c4 --- /dev/null +++ b/packages/terrain/src/editor/saveQueue.ts @@ -0,0 +1,393 @@ +import { type PatchCommand, Terrain } from "../core/terrain.js"; + +import type { ITerrainDocument } from "../core/types.js"; +import type { IAuthoringDocument, IEditorSnapshot } from "./server.js"; + +export type ISaveRecipe = ITerrainDocument; +export type ISaveDocument = IAuthoringDocument; +export type ISaveSnapshot = IEditorSnapshot; +export interface ISaveLayer { + id: string; + enabled?: boolean; + params?: object; +} +export type SaveLayer = ISaveLayer; +/** + * One local intent in the ordered journal: the change from the document shown just before it + * (`baseline`) to the document shown just after (`target`), plus the layer commands when a + * plain layer diff expresses it. A refused intent keeps its exact change so it can be retried. + */ +export interface IUnsentEdit { + id: string; + label: string; + error: string; + commands: PatchCommand[]; + document: ISaveDocument | null; +} +interface ISaveIntent extends IUnsentEdit { + baseline: ISaveDocument; + target: ISaveDocument; + wholeDocument: boolean; + state: "queued" | "inflight" | "rejected"; +} +export type ISaveTransaction = + | { baseRevision: string; commands: readonly PatchCommand[]; document?: never } + | { baseRevision: string; document: ISaveDocument; commands?: never }; +/** What an external snapshot (reload, restore, SSE) does to the local view. */ +export type ExternalSnapshotAction = "ignore" | "defer" | "adopt" | "apply"; + +/** + * The net layer commands between two recipes. A layer only present in the next recipe is a new + * `upsert`; one changed in place keeps every field through an `upsert`; a reorder emits one `move` + * per layer so the server sees the exact stack order. + * + * The order comparison uses the disk order the upserts and removes will produce — base layers + * that survive, then brand-new layers appended in next order — so a layer inserted before an + * existing one still gets its moves and is not silently appended to the end. + */ +export function diffLayers(baseLayers: SaveLayer[], nextLayers: SaveLayer[]): PatchCommand[] { + const baseMap = new Map((baseLayers ?? []).map((layer) => [layer.id, layer])); + const nextMap = new Map((nextLayers ?? []).map((layer) => [layer.id, layer])); + const commands: PatchCommand[] = []; + for (const id of baseMap.keys()) if (!nextMap.has(id)) commands.push({ op: "remove", id }); + for (const layer of nextLayers ?? []) { + const before = baseMap.get(layer.id); + if (!before) { + commands.push({ op: "upsert", layer: layer as never }); + } else if (JSON.stringify(before) !== JSON.stringify(layer)) { + const onlyToggle = + before.enabled !== layer.enabled && + JSON.stringify({ ...before, enabled: layer.enabled }) === JSON.stringify(layer); + commands.push( + onlyToggle + ? { op: "toggle", id: layer.id, enabled: layer.enabled } + : { op: "upsert", layer: layer as never }, + ); + } + } + const projected = [ + ...(baseLayers ?? []).filter((layer) => nextMap.has(layer.id)).map((layer) => layer.id), + ...(nextLayers ?? []).filter((layer) => !baseMap.has(layer.id)).map((layer) => layer.id), + ]; + if (JSON.stringify(projected) !== JSON.stringify((nextLayers ?? []).map((layer) => layer.id))) + for (const [index, layer] of (nextLayers ?? []).entries()) + commands.push({ op: "move", id: layer.id, index }); + return commands; +} + +function recipeOf(document: ISaveDocument): ISaveRecipe { + return document.recipe; +} +function layersOf(document: ISaveDocument): SaveLayer[] { + return recipeOf(document).layers ?? []; +} +function withoutRecipe(document: ISaveDocument): Record { + const { recipe: _recipe, ...rest } = document; + return rest; +} +/** Every recipe field a patch command cannot express: config, assets and anything a game adds. */ +function withoutLayers(document: ISaveDocument): Record { + const { layers: _layers, ...rest } = recipeOf(document); + return rest; +} +function isPlainObject(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} +function same(left: unknown, right: unknown): boolean { + return JSON.stringify(left) === JSON.stringify(right); +} +function isConflict(error: unknown): boolean { + const status = (error as { status?: number } | undefined)?.status; + const message = error instanceof Error ? error.message : String(error); + return status === 409 || /(^|\D)409(\D|$)|Stale base revision/u.test(message); +} +function applyToRecipe(recipe: ISaveRecipe, commands: readonly PatchCommand[]): ISaveRecipe { + if (commands.length === 0) return structuredClone(recipe); + const terrain = Terrain.fromJSON(recipe as never); + terrain.applyPatch(commands as never); + return terrain.toJSON(); +} + +/** + * Three-way merge of plain JSON. Objects merge key by key; arrays and primitives are leaves, so a + * field the local user changed wins, and a field only the disk changed keeps the disk value. This + * is what lets a whole-document save rebase onto a fresh revision without dropping an unrelated + * external edit. + */ +function threeWayMerge(base: unknown, local: unknown, remote: unknown): unknown { + if (isPlainObject(base) || isPlainObject(local) || isPlainObject(remote)) { + const b = isPlainObject(base) ? base : {}; + const l = isPlainObject(local) ? local : {}; + const r = isPlainObject(remote) ? remote : {}; + const merged: Record = {}; + for (const key of new Set([...Object.keys(b), ...Object.keys(l), ...Object.keys(r)])) + merged[key] = threeWayMerge(b[key], l[key], r[key]); + return merged; + } + return same(local, base) ? remote : local; +} + +/** + * Merge a whole-document intent (baseline -> target) onto the fresh disk document. Unchanged + * fields keep the fresh value; changed fields take the local value; the layer intent replays as + * commands so an external layer change survives alongside the local one. + */ +function mergeWholeDocument( + baseline: ISaveDocument, + target: ISaveDocument, + fresh: ISaveDocument, +): ISaveDocument { + const merged = threeWayMerge(baseline, target, fresh) as ISaveDocument; + const commands = diffLayers(layersOf(baseline), layersOf(target)); + if (commands.length) { + const terrain = Terrain.fromJSON(recipeOf(fresh) as never); + terrain.applyPatch(commands as never); + merged.recipe = { ...merged.recipe, layers: terrain.layers as never }; + } + return merged; +} + +/** + * Decide what an external snapshot does to the editor. A diagnostic snapshot is ignored here (the + * caller shows the banner). Any local intent — queued, in flight or refused — defers the snapshot + * so a reload, restore or SSE event can never roll local work back; an unchanged revision only + * needs the queue to adopt it, a newer one also reloads the preview. + */ +export function externalSnapshotAction( + queue: SaveQueue, + snapshot: ISaveSnapshot, +): ExternalSnapshotAction { + if (snapshot.diagnostic) return "ignore"; + if (queue.hasLocalIntent()) return "defer"; + return snapshot.revision === queue.revision ? "adopt" : "apply"; +} + +/** + * Client-side ordered intent journal for the project terrain document. + * + * The queue owns one journal of local intents and one confirmed disk snapshot. Each `record` diffs + * the new observation against the last displayed document and appends one intent; the displayed + * document is always the confirmed snapshot replayed with every intent still in the journal. A + * commit acknowledges exactly one intent, so a second stroke, a refusal or a discard never drops a + * sibling: discarding a refused edit removes only that intent and replays the rest. A 409 rebases + * the intent on the fresh revision and retries once (a command batch replays its layer commands, a + * whole-document batch merges only the fields it changed). A refusal stays visible for retry or + * discard. A metadata lane hands back its own committed document through `adopt`. + * + * @example const queue = new SaveQueue({ base, commit, getSnapshot }); queue.record(nextDocument); + */ +export class SaveQueue { + #commit: (transaction: ISaveTransaction) => Promise; + #getSnapshot: () => Promise; + #onSaved: ((snapshot: ISaveSnapshot, local: ISaveDocument) => void) | undefined; + #onNotify: ((message: string) => void) | undefined; + #onChange: (() => void) | undefined; + #confirmed: ISaveSnapshot; + #journal: ISaveIntent[] = []; + #saving = false; + #flushing: Promise = Promise.resolve(); + + constructor(options: { + base: ISaveSnapshot; + commit: (transaction: ISaveTransaction) => Promise; + getSnapshot: () => Promise; + onSaved?: (snapshot: ISaveSnapshot, local: ISaveDocument) => void; + onNotify?: (message: string) => void; + onChange?: () => void; + }) { + this.#confirmed = options.base; + this.#commit = options.commit; + this.#getSnapshot = options.getSnapshot; + this.#onSaved = options.onSaved; + this.#onNotify = options.onNotify; + this.#onChange = options.onChange; + } + + get base(): ISaveSnapshot { + return this.#confirmed; + } + get revision(): string { + return this.#confirmed.revision; + } + /** The local document as the queue sees it: the confirmed snapshot with every intent replayed. */ + get localDocument(): ISaveDocument { + return this.#replay(); + } + /** Refused intents, kept visible with retry and discard. */ + get unsent(): readonly IUnsentEdit[] { + return this.#journal.filter((intent) => intent.state === "rejected"); + } + get saving(): boolean { + return this.#saving; + } + hasPending(): boolean { + return this.#journal.some((intent) => intent.state === "queued" || intent.state === "inflight"); + } + /** True while any local intent exists — queued, in flight or refused — or a save is running. */ + hasLocalIntent(): boolean { + return this.#saving || this.#journal.length > 0; + } + + /** + * Record one local observation. The intent diffs the new document against the displayed local + * document: the confirmed snapshot replayed with the journal. It never diffs against the disk, so + * a second edit before the first save answers is queued rather than reverted. A remote change + * adopted since the last edit belongs to the baseline, so it never becomes a local intent. + */ + record(document: ISaveDocument): void { + const baseline = this.localDocument; + if (same(baseline, document)) return; + const whole = + !same(withoutLayers(baseline), withoutLayers(document)) || + !same(withoutRecipe(baseline), withoutRecipe(document)); + const commands = whole ? [] : diffLayers(layersOf(baseline), layersOf(document)); + if (!whole && commands.length === 0) return; + this.#journal.push({ + id: `intent-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`, + label: `${whole ? 1 : commands.length} edit(s)`, + error: "", + commands, + document: whole ? structuredClone(document) : null, + baseline: structuredClone(baseline), + target: structuredClone(document), + wholeDocument: whole, + state: "queued", + }); + this.#onChange?.(); + void this.flush(); + } + + /** + * Adopt a snapshot the queue did not request (SSE, reload, restore or a metadata lane commit). + * The base revision always follows disk; the displayed document stays the confirmed snapshot + * replayed with any local intent, so nothing in flight is dropped. + */ + adopt(snapshot: ISaveSnapshot): void { + this.#confirmed = snapshot; + this.#onChange?.(); + } + + /** Re-queue a refused intent in place, so retry keeps its order in the journal. */ + retry(edit: IUnsentEdit): void { + const intent = this.#intent(edit.id); + if (!intent) return; + intent.state = "queued"; + intent.error = ""; + this.#onChange?.(); + void this.flush(); + } + + /** Drop one refused intent and replay the rest, so a later save never resends it. */ + discard(edit: IUnsentEdit): void { + this.#journal = this.#journal.filter((intent) => intent.id !== edit.id); + this.#onChange?.(); + void this.flush(); + } + + async flush(): Promise { + if (this.#saving) return this.#flushing; + if (!this.hasPending()) return; + this.#saving = true; + this.#flushing = this.#run(); + return this.#flushing; + } + + #intent(id: string): ISaveIntent | undefined { + return this.#journal.find((intent) => intent.id === id); + } + + /** The confirmed snapshot with every journal intent replayed in order. */ + #replay(): ISaveDocument { + let document = structuredClone(this.#confirmed.document); + for (const intent of this.#journal) { + try { + document = this.#apply(document, intent); + } catch (error) { + // A rejected sibling can make a dependent intent un-replayable: keep it visible instead + // of silently dropping it, so the user can retry or discard it. + if (intent.state !== "rejected") this.#reject(intent, error); + } + } + return document; + } + + #apply(document: ISaveDocument, intent: ISaveIntent): ISaveDocument { + if (intent.wholeDocument) return mergeWholeDocument(intent.baseline, intent.target, document); + const recipe = applyToRecipe(recipeOf(document), intent.commands); + return { ...document, recipe }; + } + + #reject(intent: ISaveIntent, error: unknown): void { + intent.state = "rejected"; + intent.error = error instanceof Error ? error.message : String(error); + intent.label = `${intent.wholeDocument ? 1 : intent.commands.length} edit(s) rejected: ${intent.error}`; + this.#onNotify?.(intent.error); + } + + #transaction(intent: ISaveIntent, snapshot: ISaveSnapshot): ISaveTransaction { + if (intent.wholeDocument) + return { + baseRevision: snapshot.revision, + document: mergeWholeDocument(intent.baseline, intent.target, snapshot.document), + }; + return { baseRevision: snapshot.revision, commands: intent.commands }; + } + + #rebase(intent: ISaveIntent, snapshot: ISaveSnapshot): ISaveTransaction { + if (intent.wholeDocument) + return { + baseRevision: snapshot.revision, + document: mergeWholeDocument(intent.baseline, intent.target, snapshot.document), + }; + const recipe = applyToRecipe(recipeOf(snapshot.document), intent.commands); + return { + baseRevision: snapshot.revision, + commands: diffLayers(layersOf(snapshot.document), recipe.layers ?? []), + }; + } + + async #run(): Promise { + try { + for (;;) { + const intent = this.#journal.find((entry) => entry.state === "queued"); + if (!intent) break; + intent.state = "inflight"; + this.#onChange?.(); + let transaction: ISaveTransaction; + try { + transaction = this.#transaction(intent, this.#confirmed); + } catch (error) { + this.#reject(intent, error); + this.#onChange?.(); + continue; + } + for (let attempt = 1; attempt <= 2; attempt++) { + try { + const next = await this.#commit(transaction); + this.#confirmed = next; + this.#journal = this.#journal.filter((entry) => entry.id !== intent.id); + this.#onSaved?.(next, this.localDocument); + this.#onChange?.(); + break; + } catch (error) { + if (isConflict(error) && attempt === 1) { + try { + const fresh = await this.#getSnapshot(); + this.#confirmed = fresh; + transaction = this.#rebase(intent, fresh); + continue; + } catch {} + } + this.#reject(intent, error); + this.#onChange?.(); + break; + } + } + } + } finally { + this.#saving = false; + this.#onChange?.(); + } + if (this.hasPending()) void this.flush(); + } +} diff --git a/packages/terrain/src/editor/selection.ts b/packages/terrain/src/editor/selection.ts new file mode 100644 index 0000000000..d13ddac5a7 --- /dev/null +++ b/packages/terrain/src/editor/selection.ts @@ -0,0 +1,472 @@ +import { Euler, type EventListener, type Object3D, Quaternion, Vector3 } from "three"; +import type { + TransformControls, + TransformControlsEventMap, +} from "three/addons/controls/TransformControls.js"; +import { validatePlacementOverrides } from "../core/placements.js"; +import type { IPlacementOverride } from "../core/types.js"; +import type { Layer } from "../core/types.js"; +import type { TerrainEditorController } from "./index.js"; +import { landformPose, transformedLandform } from "./landforms.js"; +import type { IEditorSnapshot } from "./server.js"; + +/** + * Gesture and transaction mechanics; the caller owns the controls, proxy and all rendering. + * @requires npm i -D @threenative/terrain + * @situation drive terrain-editor selection, gizmo drags, undo and saved transactions + * @constraint the caller supplies the TransformControls, proxy, controller and every drawing callback + * @example const selection = createEditorSelection({ gizmo, proxy, controller, grounding, surface, defaultFootprintSize, refreshPose, drawFootprint, restorePlacement, previewPlacement, setSaving, selectedChanged, setNavigation, message }); + */ +export function createEditorSelection({ + gizmo, + proxy, + controller, + grounding, + surface, + defaultFootprintSize, + refreshPose, + drawFootprint, + restorePlacement, + previewPlacement, + setSaving, + selectedChanged, + setNavigation, + message, +}: { + gizmo: TransformControls; + proxy: Object3D; + controller: Pick; + grounding(): boolean; + surface(x: number, z: number): number | undefined; + defaultFootprintSize: number; + refreshPose(): void; + drawFootprint(layer: Layer): void; + restorePlacement(id: string, value: IPlacementOverride | undefined): void; + previewPlacement(id: string, value: IPlacementOverride): void; + setSaving(value: boolean): void; + selectedChanged(id: string | undefined, isLayer: boolean): void; + setNavigation(value: boolean): void; + message(text: string): void; +}) { + let snapshot: IEditorSnapshot | undefined; + let selected: string | undefined; + let selectedLayer: string | undefined; + let anchor = 0; + let layerDraft: Layer | undefined; + let layerHistory: { before: Layer; after: Layer } | undefined; + let active = false; + let drag: + | { id: string; base: IEditorSnapshot; grounding: boolean; layer?: Layer; anchor: number } + | undefined; + let cancelled = false; + let saving = false; + let draft: { id: string; transform: IPlacementOverride } | undefined; + let history: { id: string; before?: IPlacementOverride; after?: IPlacementOverride } | undefined; + function applyPose(layer?: Layer, placement?: IPlacementOverride) { + const landform = layer + ? landformPose(layer, snapshot?.document.recipe.config.size ?? 0, defaultFootprintSize) + : undefined; + gizmo.showX = !landform || gizmo.getMode() !== "rotate"; + gizmo.showZ = !landform || gizmo.getMode() !== "rotate"; + if (landform) { + anchor = (surface(landform.position[0], landform.position[2]) ?? 0) - landform.position[1]; + proxy.position.fromArray(landform.position); + proxy.position.y += anchor; + proxy.quaternion.copy(landform.quaternion); + proxy.scale.fromArray(landform.scale); + } else if (placement) { + proxy.position.fromArray(placement.position); + proxy.quaternion.fromArray(placement.quaternion); + proxy.scale.fromArray(placement.scale); + } else { + gizmo.detach(); + return; + } + proxy.updateMatrixWorld(true); + if (active) gizmo.attach(proxy); + const angles = new Euler().setFromQuaternion(proxy.quaternion, "XYZ"); + return landform + ? [ + ...landform.position, + 0, + (-2 * Math.atan2(landform.quaternion.y, landform.quaternion.w) * 180) / Math.PI, + 0, + ...landform.scale, + ] + : [ + ...proxy.position.toArray(), + ...[angles.x, angles.y, angles.z].map((v) => (v * 180) / Math.PI), + ...proxy.scale.toArray(), + ]; + } + const listeners: (() => void)[] = []; + function listen( + type: K, + listener: EventListener, + ) { + gizmo.addEventListener(type, listener); + listeners.push(() => gizmo.removeEventListener(type, listener)); + } + function restore(): void { + if (selectedLayer) { + refreshPose(); + return; + } + if (selected) restorePlacement(selected, snapshot?.document.placementOverrides?.[selected]); + refreshPose(); + } + function preview(id: string, transform: IPlacementOverride): void { + const value = validatePlacementOverrides({ [id]: transform })[id]; + if (!value) throw new Error("Selected placement is unavailable"); + previewPlacement(id, value); + draft = { id, transform: value }; + refreshPose(); + } + async function save( + id: string, + value: IPlacementOverride | undefined, + base = snapshot, + remember = true, + ): Promise { + if (saving || !base) { + message("Wait for the current transform save."); + return; + } + saving = true; + setSaving(true); + gizmo.enabled = false; + const overrides = { ...base.document.placementOverrides }; + if (value) overrides[id] = value; + else delete overrides[id]; + try { + const next = await controller.commit({ + baseRevision: base.revision, + document: { ...base.document, placementOverrides: overrides }, + }); + if (remember) { + history = { id, before: base.document.placementOverrides?.[id], after: value }; + layerHistory = undefined; + } + if (!snapshot || snapshot.revision === base.revision || snapshot.revision === next.revision) + snapshot = next; + draft = undefined; + restore(); + message(`Saved ${next.revision.slice(0, 8)} · one transform transaction`); + } catch (error) { + message( + `${error instanceof Error ? error.message : String(error)} · draft retained; apply again to rebase explicitly.`, + ); + try { + snapshot = await controller.snapshot(); + restore(); + } catch { + message("Connection interrupted · transform draft retained."); + } + } finally { + saving = false; + setSaving(false); + gizmo.enabled = active; + } + } + async function saveLayer(layer: Layer, base = snapshot, remember = true): Promise { + if (saving || !base) return; + saving = true; + setSaving(true); + gizmo.enabled = false; + try { + const before = base.document.recipe.layers.find((l) => l.id === layer.id); + const next = await controller.commit({ + baseRevision: base.revision, + commands: [{ op: "upsert", layer }], + }); + if (remember && before) { + layerHistory = { before, after: layer }; + history = undefined; + } + if (!snapshot || snapshot.revision === base.revision || snapshot.revision === next.revision) + snapshot = next; + layerDraft = undefined; + message(`Saved ${next.revision.slice(0, 8)} · one landform transaction`); + } catch (error) { + message(`${String(error)} · landform draft retained; apply again to rebase explicitly.`); + snapshot = await controller.snapshot(); + } finally { + saving = false; + setSaving(false); + gizmo.enabled = active; + refreshPose(); + } + } + function select(id: string | undefined, isLayer = false): void { + if (gizmo.dragging || saving) return; + if (selected !== id) { + restore(); + draft = undefined; + } + selectedLayer = isLayer ? id : undefined; + selected = selectedLayer ? undefined : id; + layerDraft = undefined; + selectedChanged(id, isLayer); + refreshPose(); + } + async function undo(): Promise { + if (saving) return; + if (selectedLayer && layerHistory) { + const entry = layerHistory; + const current = await controller.snapshot(); + const layer = current.document.recipe.layers.find((l) => l.id === entry.after.id); + const keys = ["at", "radius", "size", "rotation", "scale", "offset"]; + const after = entry.after.params as Record; + const before = entry.before.params as Record; + const params = { ...layer?.params } as Record; + if ( + !layer || + layer.type !== entry.after.type || + keys.some((key) => JSON.stringify(params[key]) !== JSON.stringify(after[key])) + ) { + message("Undo conflict: another actor changed this landform; their edit is retained."); + return; + } + for (const key of keys) { + if (before[key] === undefined) delete params[key]; + else params[key] = before[key]; + } + await saveLayer({ ...layer, params } as Layer, current, false); + if (!layerDraft) layerHistory = undefined; + return; + } + if (!history) return; + const entry = history; + const current = await controller.snapshot(); + if ( + JSON.stringify(current.document.placementOverrides?.[entry.id]) !== + JSON.stringify(entry.after) + ) { + message("Undo conflict: another actor changed this placement; their edit is retained."); + return; + } + await save(entry.id, entry.before, current, false); + if (!draft) history = undefined; + } + function applyFields(values: number[], grounded: boolean): void { + if (selectedLayer && snapshot) { + const layer = snapshot.document.recipe.layers.find((l) => l.id === selectedLayer); + if (!layer) return; + try { + const [ + x = Number.NaN, + y = Number.NaN, + z = Number.NaN, + , + ry = Number.NaN, + , + sx = Number.NaN, + sy = Number.NaN, + sz = Number.NaN, + ] = values; + const position = new Vector3(x, y + anchor, z); + const rotation = new Quaternion().setFromAxisAngle( + new Vector3(0, 1, 0), + (-ry * Math.PI) / 180, + ); + const next = layerDraft + ? ({ + ...layer, + params: { + ...layer.params, + ...Object.fromEntries( + Object.entries(layerDraft.params).filter(([key]) => + ["at", "radius", "size", "rotation", "scale", "offset"].includes(key), + ), + ), + }, + } as Layer) + : transformedLandform(layer, position, rotation, new Vector3(sx, sy, sz), anchor); + layerDraft = next; + void saveLayer(next); + } catch (error) { + message(String(error)); + } + return; + } + if (!selected) return; + try { + const position = values.slice(0, 3) as IPlacementOverride["position"]; + const rotation = values.slice(3, 6).map((v) => (v * Math.PI) / 180); + const quaternion = new Quaternion() + .setFromEuler(new Euler(rotation[0], rotation[1], rotation[2], "XYZ")) + .toArray(); + const scale = values.slice(6, 9) as IPlacementOverride["scale"]; + const value = + draft?.id === selected + ? draft.transform + : { position, quaternion, scale, grounding: grounded }; + preview(selected, value); + void save(selected, value); + } catch (error) { + message(String(error)); + } + } + listen("dragging-changed", ({ value }) => { + setNavigation(!value); + }); + listen("mouseDown", () => { + const id = selectedLayer ?? selected; + if (!id || !snapshot || saving) { + cancelled = true; + gizmo.pointerUp(null); + return; + } + if (selectedLayer && gizmo.getMode() === "rotate" && gizmo.axis !== "Y") { + cancelled = true; + gizmo.pointerUp(null); + message("Only Y rotation is supported: a heightfield cannot overhang."); + return; + } + drag = { + id, + base: snapshot, + layer: snapshot.document.recipe.layers.find((l) => l.id === selectedLayer), + anchor, + grounding: grounding() && !(gizmo.getMode() === "translate" && gizmo.axis?.includes("Y")), + }; + cancelled = false; + }); + listen("objectChange", () => { + if (!drag || cancelled) return; + try { + if (drag.layer) { + layerDraft = transformedLandform( + drag.layer, + proxy.position, + proxy.quaternion, + proxy.scale, + drag.anchor, + ); + drawFootprint(layerDraft); + return; + } + preview(drag.id, { + position: proxy.position.toArray(), + quaternion: proxy.quaternion.clone().normalize().toArray(), + scale: proxy.scale.toArray(), + grounding: drag.grounding, + }); + } catch (error) { + message(String(error)); + cancelled = true; + restore(); + } + }); + listen("mouseUp", () => { + const gesture = drag; + drag = undefined; + if (!cancelled && gesture?.layer && layerDraft) { + void saveLayer(layerDraft, gesture.base); + return; + } + if (cancelled || !gesture || !draft) { + restore(); + return; + } + void save(gesture.id, draft.transform, gesture.base); + }); + return { + get snapshot() { + return snapshot; + }, + get selected() { + return selected; + }, + get selectedLayer() { + return selectedLayer; + }, + get anchor() { + return anchor; + }, + get layerDraft() { + return layerDraft; + }, + get active() { + return active; + }, + get drag() { + return drag; + }, + get saving() { + return saving; + }, + get draft() { + return draft; + }, + applyPose, + save, + select, + undo, + applyFields, + async reassign(id: string): Promise { + if (!selected || !snapshot || saving) return; + const current = snapshot; + const value = current.document.placementOverrides?.[id]; + if (!value || current.document.placementOverrides?.[selected]) { + message("Choose a placement without an existing override."); + return; + } + const overrides = { ...current.document.placementOverrides, [selected]: value }; + delete overrides[id]; + try { + await controller.commit({ + baseRevision: current.revision, + document: { ...current.document, placementOverrides: overrides }, + }); + } catch (error) { + message(String(error)); + } + }, + clearDrafts(): void { + draft = undefined; + layerDraft = undefined; + }, + cancel(): void { + cancelled = true; + draft = undefined; + layerDraft = undefined; + gizmo.reset(); + restore(); + message("Drag cancelled · no transaction"); + }, + canPick(event: { button: number; altKey: boolean }): boolean { + return active && event.button === 0 && !event.altKey && !gizmo.axis && !gizmo.dragging; + }, + dispose(): void { + for (const remove of listeners) remove(); + }, + selectLayer(id: string): void { + const layer = snapshot?.document.recipe.layers.find((entry) => entry.id === id); + if ( + active && + layer && + landformPose(layer, snapshot?.document.recipe.config.size ?? 0, defaultFootprintSize) + ) + select(id, true); + }, + setActive(enabled: boolean): void { + active = enabled; + gizmo.enabled = enabled; + if (!enabled) { + cancelled = true; + draft = undefined; + layerDraft = undefined; + gizmo.pointerUp(null); + gizmo.reset(); + restore(); + gizmo.detach(); + setNavigation(true); + } else refreshPose(); + }, + sync(next: IEditorSnapshot, matchesPreview: boolean): void { + snapshot = next; + if (matchesPreview && !drag && !saving) restore(); + }, + }; +} diff --git a/packages/terrain/src/editor/server.ts b/packages/terrain/src/editor/server.ts index f259a17da4..e233afe9d7 100644 --- a/packages/terrain/src/editor/server.ts +++ b/packages/terrain/src/editor/server.ts @@ -7,6 +7,7 @@ import { validatePlacementOverrides } from "../core/placements.js"; import { type PatchCommand, Terrain, TerrainEvaluator } from "../core/terrain.js"; import type { ITerrainDocument } from "../core/types.js"; import type { IPlacementOverride } from "../core/types.js"; +import { loadCooker } from "../mcp/cook.js"; import { AssetStore } from "./assetStore.js"; import { type IAssetLimits, @@ -41,6 +42,7 @@ import { const PREFIX = "/terrain-editor/"; const MAX_BYTES = 64 * 1024 * 1024; +const MAX_SMALL_BYTES = 1024 * 1024; const MAX_REFERENCES = 16; export interface IAuthoringDocument { @@ -150,12 +152,26 @@ class EditorError extends Error { export class TerrainEditorDocument { readonly path: string; #snapshot: IEditorSnapshot; + #lastValidDocument: IAuthoringDocument | null = null; #listeners = new Set<(snapshot: IEditorSnapshot) => void>(); constructor(path: string) { if (!isAbsolute(path)) throw new Error("Document path must be absolute"); this.path = resolve(path); - const document = this.#read(); - this.#snapshot = { document, revision: revision(document), diagnostic: null }; + try { + const document = this.#read(); + this.#lastValidDocument = document; + this.#snapshot = { document, revision: revision(document), diagnostic: null }; + } catch (error) { + const fallback: IAuthoringDocument = { + version: 1, + recipe: new Terrain().toJSON(), + }; + this.#snapshot = { + document: fallback, + revision: revision(fallback), + diagnostic: error instanceof Error ? error.message : String(error), + }; + } } #read(): IAuthoringDocument { const info = lstatSync(this.path); @@ -177,6 +193,7 @@ export class TerrainEditorDocument { try { const document = this.#read(); const nextRevision = revision(document); + this.#lastValidDocument = document; if (nextRevision === this.#snapshot.revision && this.#snapshot.diagnostic === null) return; this.#snapshot = { document, revision: nextRevision, diagnostic: null }; } catch (error) { @@ -184,6 +201,33 @@ export class TerrainEditorDocument { } this.#emit(); } + restoreLastValid(): IEditorSnapshot { + // Never overwrite a malformed original with a synthetic fallback: without a real valid save + // there is nothing to restore, so the caller must fix the file and reload instead. + if (!this.#lastValidDocument) + throw new EditorError(409, "No valid save to restore; the document has never been valid"); + const target = this.#lastValidDocument; + const temporary = `${this.path}.${randomUUID()}.tmp`; + try { + writeFileSync(temporary, `${JSON.stringify(target, null, 2)}\n`, { flag: "wx", mode: 0o600 }); + renameSync(temporary, this.path); + } catch (error) { + try { + unlinkSync(temporary); + } catch (cleanupError) { + if ((cleanupError as NodeJS.ErrnoException).code !== "ENOENT") + throw new AggregateError( + [error, cleanupError], + "Document save and temporary-file cleanup failed", + ); + } + throw error; + } + this.#lastValidDocument = target; + this.#snapshot = { document: target, revision: revision(target), diagnostic: null }; + this.#emit(); + return this.snapshot(); + } commit(input: unknown): IEditorSnapshot { this.refresh(); if (this.#snapshot.diagnostic) @@ -230,25 +274,35 @@ export class TerrainEditorDocument { throw error; } this.#snapshot = { document: next, revision: nextRevision, diagnostic: null }; + this.#lastValidDocument = next; this.#emit(); return this.snapshot(); } } -async function body(request: IncomingMessage): Promise { - if (request.headers["content-type"]?.split(";")[0] !== "application/json") +async function body( + request: IncomingMessage, + maxBytes = MAX_SMALL_BYTES, + parse = true, +): Promise { + if (Number(request.headers["content-length"]) > maxBytes) { + request.resume(); + throw new EditorError(413, `Request exceeds ${maxBytes / (1024 * 1024)} MiB`); + } + if (parse && request.headers["content-type"]?.split(";")[0] !== "application/json") throw new EditorError(415, "Expected application/json"); - if (Number(request.headers["content-length"]) > MAX_BYTES) - throw new EditorError(413, "Request exceeds 64 MiB"); const chunks: Buffer[] = []; let bytes = 0; - for await (const chunk of request) { + for await (const chunk of request.iterator({ destroyOnReturn: false })) { const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); bytes += buffer.length; - if (bytes > MAX_BYTES) throw new EditorError(413, "Request exceeds 64 MiB"); - chunks.push(buffer); + if (bytes > maxBytes) { + request.resume(); + throw new EditorError(413, `Request exceeds ${maxBytes / (1024 * 1024)} MiB`); + } + if (parse) chunks.push(buffer); } - return JSON.parse(Buffer.concat(chunks).toString("utf8")); + return parse ? JSON.parse(Buffer.concat(chunks).toString("utf8")) : undefined; } function json(response: ServerResponse, status: number, value: unknown): void { response.writeHead(status, { @@ -386,6 +440,44 @@ export function terrainEditor(options: { json(response, 200, document.snapshot()); return; } + if (request.method === "POST" && path === `${PREFIX}api/export`) { + const input = (await body(request, MAX_BYTES)) as { + baseRevision?: unknown; + preparedWorld?: Record; + }; + const snapshot = document.snapshot(); + if (snapshot.diagnostic) + throw new EditorError(409, "Cannot export an invalid disk save"); + if (!input || input.baseRevision !== snapshot.revision) + throw new EditorError(409, "Stale export revision"); + if ( + !input.preparedWorld || + typeof input.preparedWorld !== "object" || + Array.isArray(input.preparedWorld) + ) + throw new Error("Expected preparedWorld glTF JSON"); + const cooker = await loadCooker(vite.config.root); + const cooked = await cooker.cookTerrainWorld({ preparedWorld: input.preparedWorld }); + if (document.snapshot().revision !== snapshot.revision) + throw new EditorError(409, "World changed while exporting"); + response.writeHead(200, { + "content-type": "model/gltf-binary", + "cache-control": "no-store", + }); + response.end(cooked.worldGlb); + return; + } + if (request.method === "POST" && path === `${PREFIX}api/reload`) { + await body(request, MAX_SMALL_BYTES, false); + document.refresh(); + json(response, 200, document.snapshot()); + return; + } + if (request.method === "POST" && path === `${PREFIX}api/restore`) { + await body(request, MAX_SMALL_BYTES, false); + json(response, 200, document.restoreLastValid()); + return; + } if (request.method === "GET" && path === `${PREFIX}api/events`) { response.writeHead(200, { "content-type": "text/event-stream", @@ -398,7 +490,7 @@ export function terrainEditor(options: { return; } if (request.method === "POST" && path === `${PREFIX}api/document`) { - json(response, 200, document.commit(await body(request))); + json(response, 200, document.commit(await body(request, MAX_BYTES))); return; } if (request.method === "POST" && path === `${PREFIX}api/cameras`) { @@ -455,7 +547,7 @@ export function terrainEditor(options: { return; } if (request.method === "POST" && path === `${PREFIX}api/assets`) { - const transaction = await body(request); + const transaction = await body(request, MAX_BYTES); if ( !transaction || typeof transaction !== "object" || @@ -631,9 +723,10 @@ export function terrainEditor(options: { } throw new EditorError(405, "Unsupported terrain editor route/method"); } catch (error) { - json(response, error instanceof EditorError ? error.status : 400, { + const status = error instanceof EditorError ? error.status : 400; + json(response, status, { + code: status, error: error instanceof Error ? error.message : String(error), - snapshot: document.snapshot(), }); } })(); diff --git a/packages/terrain/src/editor/shell-dialogs.ts b/packages/terrain/src/editor/shell-dialogs.ts new file mode 100644 index 0000000000..43372922b4 --- /dev/null +++ b/packages/terrain/src/editor/shell-dialogs.ts @@ -0,0 +1,181 @@ +import { shellElement } from "./shell.js"; +export function appendShellDialogs(parent: Node): void { + const n1 = shellElement(parent, "dialog", { id: "recipe-dialog", class: "recipe-dialog" }); + const n2 = shellElement(n1, "div", { class: "dialog-header" }); + const n3 = shellElement(n2, "div"); + const n4 = shellElement(n3, "span", { class: "eyebrow" }); + n4.appendChild(document.createTextNode("AI-FIRST AUTHORING")); + const n5 = shellElement(n3, "h2"); + n5.appendChild(document.createTextNode("The recipe is the source.")); + const n6 = shellElement(n3, "p"); + n6.appendChild( + document.createTextNode("Edit structured data, not a mesh. No JavaScript is executed here."), + ); + const n7 = shellElement(n2, "button", { + id: "close-recipe", + class: "icon-button dialog-close", + "aria-label": "Close recipe", + }); + n7.appendChild(document.createTextNode("×")); + const n8 = shellElement(n1, "div", { class: "recipe-layout" }); + const n9 = shellElement(n8, "div"); + const n10 = shellElement(n9, "div", { class: "json-label" }); + const n11 = shellElement(n10, "span"); + n11.appendChild(document.createTextNode("PROJECT RECIPE")); + const n12 = shellElement(n10, "span"); + n12.appendChild(document.createTextNode("schema version 1")); + shellElement(n9, "textarea", { + id: "recipe-editor", + spellcheck: "false", + "aria-label": "Project recipe JSON", + }); + shellElement(n9, "div", { id: "recipe-error", class: "inline-error" }); + const n15 = shellElement(n8, "aside", { class: "api-note" }); + const n16 = shellElement(n15, "span", { class: "eyebrow" }); + n16.appendChild(document.createTextNode("CODE API")); + const n17 = shellElement(n15, "h3"); + n17.appendChild(document.createTextNode("Give your AI")); + shellElement(n17, "br"); + n17.appendChild(document.createTextNode("better tools.")); + const n19 = shellElement(n15, "p"); + n19.appendChild( + document.createTextNode( + "The headless engine runs in Node, a worker, or your game. Every action has a named operation.", + ), + ); + const n20 = shellElement(n15, "pre"); + const n21 = shellElement(n20, "code"); + n21.appendChild( + document.createTextNode( + 'terrain.stamp({\n id: "north-peak",\n at: [80, -100],\n radius: [90, 60],\n amplitude: 80\n});\n\nterrain.update("river-main", {\n params: { width: 24 }\n});\n\nterrain.undo();\nterrain.toJSON();', + ), + ); + const n22 = shellElement(n15, "div", { class: "api-footnote" }); + n22.appendChild(document.createTextNode("In DevTools: ")); + const n23 = shellElement(n22, "code"); + n23.appendChild(document.createTextNode("strata.terrain")); + shellElement(n22, "br"); + n22.appendChild(document.createTextNode("On disk: ")); + const n25 = shellElement(n22, "code"); + n25.appendChild(document.createTextNode("src/index.js")); + shellElement(n22, "br"); + n22.appendChild(document.createTextNode("Agent instructions: ")); + const n27 = shellElement(n22, "code"); + n27.appendChild(document.createTextNode("docs/AGENT_GUIDE.md")); + const n28 = shellElement(n1, "div", { class: "dialog-footer" }); + const n29 = shellElement(n28, "button", { id: "copy-recipe", class: "ghost" }); + shellElement(n29, "span", { "data-icon": "copy" }); + n29.appendChild(document.createTextNode("Copy JSON")); + const n31 = shellElement(n28, "span", { class: "muted small" }); + n31.appendChild( + document.createTextNode("Validation failures leave the current project untouched."), + ); + const n32 = shellElement(n28, "button", { id: "apply-recipe", class: "primary" }); + n32.appendChild(document.createTextNode("Validate & apply")); + const n33 = shellElement(parent, "dialog", { id: "export-dialog", class: "export-dialog" }); + const n34 = shellElement(n33, "div", { class: "dialog-header" }); + const n35 = shellElement(n34, "div"); + const n36 = shellElement(n35, "span", { class: "eyebrow" }); + n36.appendChild(document.createTextNode("PORTABLE BY DESIGN")); + const n37 = shellElement(n35, "h2"); + n37.appendChild(document.createTextNode("Take your terrain anywhere.")); + const n38 = shellElement(n35, "p"); + n38.appendChild(document.createTextNode("Keep the recipe. Bake the data your engine needs.")); + const n39 = shellElement(n34, "button", { + id: "close-export", + class: "icon-button dialog-close", + "aria-label": "Close export", + }); + n39.appendChild(document.createTextNode("×")); + const n40 = shellElement(n33, "div", { class: "export-settings" }); + const n41 = shellElement(n40, "label", { class: "field-label" }); + n41.appendChild(document.createTextNode("Bake resolution")); + const n42 = shellElement(n41, "select", { id: "export-resolution" }); + const n43 = shellElement(n42, "option", { value: "129" }); + n43.appendChild(document.createTextNode("129 × 129")); + const n44 = shellElement(n42, "option", { value: "257", selected: "" }); + n44.appendChild(document.createTextNode("257 × 257")); + const n45 = shellElement(n42, "option", { value: "513" }); + n45.appendChild(document.createTextNode("513 × 513")); + const n46 = shellElement(n42, "option", { value: "1025" }); + n46.appendChild(document.createTextNode("1025 × 1025")); + const n47 = shellElement(n40, "p"); + n47.appendChild(document.createTextNode("Preview and export resolutions are independent.")); + shellElement(n47, "br"); + n47.appendChild(document.createTextNode("High-resolution bakes run in a separate worker.")); + const n49 = shellElement(n33, "div", { class: "export-grid" }); + const n50 = shellElement(n49, "button", { class: "export-card", "data-export": "project" }); + shellElement(n50, "span", { "data-icon": "code" }); + const n52 = shellElement(n50, "strong"); + n52.appendChild(document.createTextNode("Project recipe")); + const n53 = shellElement(n50, "small"); + n53.appendChild(document.createTextNode("JSON · layers, masks, seeds")); + const n54 = shellElement(n49, "button", { class: "export-card", "data-export": "png" }); + shellElement(n54, "span", { "data-icon": "image" }); + const n56 = shellElement(n54, "strong"); + n56.appendChild(document.createTextNode("Heightmap")); + const n57 = shellElement(n54, "small"); + n57.appendChild(document.createTextNode("16-bit grayscale PNG")); + const n58 = shellElement(n49, "button", { class: "export-card", "data-export": "raw" }); + shellElement(n58, "span", { "data-icon": "grid" }); + const n60 = shellElement(n58, "strong"); + n60.appendChild(document.createTextNode("RAW heightfield")); + const n61 = shellElement(n58, "small"); + n61.appendChild(document.createTextNode("RAW16 + scale metadata ZIP")); + const n62 = shellElement(n49, "button", { class: "export-card", "data-export": "splat" }); + shellElement(n62, "span", { "data-icon": "paint" }); + const n64 = shellElement(n62, "strong"); + n64.appendChild(document.createTextNode("Splatmaps")); + const n65 = shellElement(n62, "small"); + n65.appendChild(document.createTextNode("8 channels · 2 RGBA PNGs")); + const n66 = shellElement(n49, "button", { class: "export-card", "data-export": "glb" }); + shellElement(n66, "span", { "data-icon": "cube" }); + const n68 = shellElement(n66, "strong"); + n68.appendChild(document.createTextNode("Terrain mesh")); + const n69 = shellElement(n66, "small"); + n69.appendChild(document.createTextNode("GLB · baked vertex colors")); + const n70 = shellElement(n49, "button", { + class: "export-card featured", + "data-export": "world", + }); + shellElement(n70, "span", { "data-icon": "cube" }); + const n72 = shellElement(n70, "strong"); + n72.appendChild(document.createTextNode("World GLB")); + const n73 = shellElement(n70, "small"); + n73.appendChild(document.createTextNode("GLB · terrain, props, water, PBR maps")); + const n74 = shellElement(n49, "button", { class: "export-card", "data-export": "runtime" }); + shellElement(n74, "span", { "data-icon": "layers" }); + const n76 = shellElement(n74, "strong"); + n76.appendChild(document.createTextNode("Runtime bundle")); + const n77 = shellElement(n74, "small"); + n77.appendChild(document.createTextNode("Chunk LODs · collision · placements")); + const n78 = shellElement(n33, "div", { class: "import-area" }); + const n79 = shellElement(n78, "div"); + const n80 = shellElement(n79, "strong"); + n80.appendChild(document.createTextNode("Bring your own terrain")); + const n81 = shellElement(n79, "p"); + n81.appendChild(document.createTextNode("Project JSON, grayscale PNG8/16, or square RAW16 LE.")); + const n82 = shellElement(n78, "button", { id: "import-btn", class: "secondary" }); + shellElement(n82, "span", { "data-icon": "import" }); + n82.appendChild(document.createTextNode("Import file")); + shellElement(n78, "input", { + id: "import-file", + class: "hidden", + type: "file", + accept: ".json,.png,.raw,.r16", + }); + const n85 = shellElement(n78, "div", { class: "import-ranges" }); + const n86 = shellElement(n85, "label"); + n86.appendChild(document.createTextNode("External map minimum (m)")); + shellElement(n86, "input", { id: "import-min", type: "number", value: "0" }); + const n88 = shellElement(n85, "label"); + n88.appendChild(document.createTextNode("External map maximum (m)")); + shellElement(n88, "input", { id: "import-max", type: "number", value: "180" }); + shellElement(n33, "div", { id: "export-error", class: "inline-error" }); + const n91 = shellElement(n33, "div", { class: "export-note" }); + n91.appendChild( + document.createTextNode( + "World GLB carries the terrain, the placed models with their final transforms, this project’s PBR maps and its baked water; lighting, sky, fog and live water stay the receiving game’s. The terrain-only cards export no props, and the runtime bundle includes deterministic placements separately. No navmesh or EXR export.", + ), + ); +} diff --git a/packages/terrain/src/editor/shell-layout.ts b/packages/terrain/src/editor/shell-layout.ts new file mode 100644 index 0000000000..133fac046a --- /dev/null +++ b/packages/terrain/src/editor/shell-layout.ts @@ -0,0 +1,443 @@ +import { shellElement } from "./shell.js"; +export function appendShellLayout(parent: Node): void { + const n1 = shellElement(parent, "header", { class: "app-header" }); + n1.appendChild(document.createTextNode("\n ")); + const n2 = shellElement(n1, "div", { class: "brand-wrap" }); + const n3 = shellElement(n2, "svg", { + class: "brand-mark", + viewBox: "0 0 36 36", + fill: "none", + "aria-hidden": "true", + }); + shellElement(n3, "path", { + d: "M3 27 14 8l7 11 5-7 8 15M3 32h31M8 27l7-11 6 11M21 27l5-7 4 7", + stroke: "currentColor", + "stroke-width": "1.6", + "stroke-linejoin": "round", + }); + const n5 = shellElement(n2, "div"); + const n6 = shellElement(n5, "strong", { id: "brand" }); + n6.appendChild(document.createTextNode("STRATA")); + const n7 = shellElement(n5, "span"); + n7.appendChild(document.createTextNode("TERRAIN STUDIO")); + n1.appendChild(document.createTextNode("\n ")); + shellElement(n1, "div", { class: "header-divider" }); + const n9 = shellElement(n1, "div", { class: "project-heading" }); + const n10 = shellElement(n9, "span", { id: "project-name" }); + n10.appendChild(document.createTextNode("Alpine watershed")); + const n11 = shellElement(n9, "small"); + shellElement(n11, "i", { class: "status-dot" }); + const n13 = shellElement(n11, "span", { id: "save-status" }); + n13.appendChild(document.createTextNode("Local workspace")); + n1.appendChild(document.createTextNode("\n ")); + const n14 = shellElement(n1, "div", { class: "header-actions" }); + const n15 = shellElement(n14, "button", { + class: "mobile-only", + id: "mobile-tools", + title: "Tools", + }); + n15.appendChild(document.createTextNode("Tools")); + const n16 = shellElement(n14, "button", { + class: "mobile-only", + id: "mobile-layers", + title: "Layers", + }); + n16.appendChild(document.createTextNode("Layers")); + const n17 = shellElement(n14, "div", { class: "history-buttons" }); + const n18 = shellElement(n17, "button", { + id: "undo-btn", + class: "icon-button", + title: "Undo (Ctrl/⌘ Z)", + "aria-label": "Undo", + }); + shellElement(n18, "span", { "data-icon": "undo" }); + const n20 = shellElement(n17, "button", { + id: "redo-btn", + class: "icon-button", + title: "Redo (Ctrl/⌘ Shift Z)", + "aria-label": "Redo", + }); + shellElement(n20, "span", { "data-icon": "redo" }); + const n22 = shellElement(n14, "button", { id: "recipe-btn", class: "ghost" }); + shellElement(n22, "span", { "data-icon": "code" }); + const n24 = shellElement(n22, "span"); + n24.appendChild(document.createTextNode("Recipe & API")); + const n25 = shellElement(n14, "button", { id: "export-btn", class: "primary" }); + shellElement(n25, "span", { "data-icon": "export" }); + n25.appendChild(document.createTextNode("Export terrain")); + n1.appendChild(document.createTextNode("\n")); + const n27 = shellElement(parent, "div", { class: "workspace" }); + n27.appendChild(document.createTextNode("\n ")); + const n28 = shellElement(n27, "aside", { class: "sidebar left-sidebar" }); + n28.appendChild(document.createTextNode("\n ")); + const n29 = shellElement(n28, "div", { class: "panel-heading" }); + const n30 = shellElement(n29, "span"); + n30.appendChild(document.createTextNode("AUTHORING")); + const n31 = shellElement(n29, "span", { class: "tiny-tag" }); + n31.appendChild(document.createTextNode("NON-DESTRUCTIVE")); + n28.appendChild(document.createTextNode("\n ")); + const n32 = shellElement(n28, "div", { class: "panel-tabs" }); + const n33 = shellElement(n32, "button", { class: "active", "data-panel": "tools" }); + n33.appendChild(document.createTextNode("Tools")); + const n34 = shellElement(n32, "button", { "data-panel": "generate" }); + n34.appendChild(document.createTextNode("Generate")); + n28.appendChild(document.createTextNode("\n ")); + const n35 = shellElement(n28, "div", { id: "tools-panel", class: "panel-scroll" }); + n35.appendChild(document.createTextNode("\n ")); + shellElement(n35, "div", { id: "tool-grid", class: "tool-grid" }); + n35.appendChild(document.createTextNode("\n ")); + const n37 = shellElement(n35, "section", { class: "brush-section" }); + const n38 = shellElement(n37, "div", { class: "section-title" }); + const n39 = shellElement(n38, "span", { id: "brush-title" }); + n39.appendChild(document.createTextNode("Sculpt terrain")); + const n40 = shellElement(n38, "span", { class: "keycap", id: "tool-key" }); + n40.appendChild(document.createTextNode("1")); + const n41 = shellElement(n37, "p", { id: "brush-desc", class: "muted tool-description" }); + n41.appendChild(document.createTextNode("Raise the ground. Hold Shift to lower.")); + n37.appendChild(document.createTextNode("\n ")); + const n42 = shellElement(n37, "label", { class: "slider-label" }); + n42.appendChild(document.createTextNode("Size ")); + const n43 = shellElement(n42, "output", { id: "radius-value" }); + n43.appendChild(document.createTextNode("36 m")); + shellElement(n37, "input", { + id: "brush-radius", + type: "range", + min: "4", + max: "150", + value: "36", + step: "1", + "aria-label": "Brush radius", + }); + n37.appendChild(document.createTextNode("\n ")); + const n45 = shellElement(n37, "label", { class: "slider-label" }); + n45.appendChild(document.createTextNode("Strength ")); + const n46 = shellElement(n45, "output", { id: "strength-value" }); + n46.appendChild(document.createTextNode("35%")); + shellElement(n37, "input", { + id: "brush-strength", + type: "range", + min: "0.01", + max: "1", + value: "0.35", + step: "0.01", + "aria-label": "Brush strength", + }); + n37.appendChild(document.createTextNode("\n ")); + const n48 = shellElement(n37, "label", { class: "slider-label" }); + n48.appendChild(document.createTextNode("Falloff ")); + const n49 = shellElement(n48, "output", { id: "falloff-value" }); + n49.appendChild(document.createTextNode("65%")); + shellElement(n37, "input", { + id: "brush-falloff", + type: "range", + min: "0", + max: "1", + value: "0.65", + step: "0.01", + "aria-label": "Brush falloff", + }); + n37.appendChild(document.createTextNode("\n ")); + shellElement(n37, "div", { id: "tool-options", class: "tool-options" }); + n37.appendChild(document.createTextNode("\n ")); + const n52 = shellElement(n37, "details", { class: "advanced" }); + const n53 = shellElement(n52, "summary"); + n53.appendChild(document.createTextNode("Brush dynamics")); + const n54 = shellElement(n53, "span"); + n54.appendChild(document.createTextNode("+")); + const n55 = shellElement(n52, "label", { class: "field-label" }); + n55.appendChild(document.createTextNode("Shape")); + const n56 = shellElement(n55, "select", { id: "brush-shape" }); + const n57 = shellElement(n56, "option", { value: "circle" }); + n57.appendChild(document.createTextNode("Circle")); + const n58 = shellElement(n56, "option", { value: "square" }); + n58.appendChild(document.createTextNode("Square")); + const n59 = shellElement(n52, "label", { class: "slider-label" }); + n59.appendChild(document.createTextNode("Rotation ")); + const n60 = shellElement(n59, "output", { id: "rotation-value" }); + n60.appendChild(document.createTextNode("0°")); + shellElement(n52, "input", { + id: "brush-rotation", + type: "range", + min: "0", + max: "360", + value: "0", + }); + const n62 = shellElement(n52, "label", { class: "slider-label" }); + n62.appendChild(document.createTextNode("Spacing ")); + const n63 = shellElement(n62, "output", { id: "spacing-value" }); + n63.appendChild(document.createTextNode("15%")); + shellElement(n52, "input", { + id: "brush-spacing", + type: "range", + min: "0.02", + max: "1", + step: "0.01", + value: "0.15", + }); + const n65 = shellElement(n52, "label", { class: "slider-label" }); + n65.appendChild(document.createTextNode("Jitter ")); + const n66 = shellElement(n65, "output", { id: "jitter-value" }); + n66.appendChild(document.createTextNode("0%")); + shellElement(n52, "input", { + id: "brush-jitter", + type: "range", + min: "0", + max: "1", + step: "0.01", + value: "0", + }); + n37.appendChild(document.createTextNode("\n ")); + n35.appendChild(document.createTextNode("\n ")); + const n68 = shellElement(n35, "div", { class: "tip-card" }); + shellElement(n68, "span", { "data-icon": "layers" }); + const n70 = shellElement(n68, "p"); + n70.appendChild(document.createTextNode("Every stroke is a layer.")); + shellElement(n70, "br"); + const n72 = shellElement(n70, "strong"); + n72.appendChild(document.createTextNode("Nothing is permanent.")); + n35.appendChild(document.createTextNode("\n ")); + n28.appendChild(document.createTextNode("\n ")); + const n73 = shellElement(n28, "div", { id: "generate-panel", class: "panel-scroll hidden" }); + const n74 = shellElement(n73, "div", { class: "section-title" }); + n74.appendChild(document.createTextNode("START FROM A LANDSCAPE")); + shellElement(n73, "div", { id: "preset-list", class: "preset-list" }); + const n76 = shellElement(n73, "div", { class: "two-fields" }); + const n77 = shellElement(n76, "label", { class: "field-label" }); + n77.appendChild(document.createTextNode("Seed")); + shellElement(n77, "input", { + id: "seed-input", + type: "number", + value: "73", + min: "0", + max: "4294967295", + }); + const n79 = shellElement(n76, "label", { class: "field-label" }); + n79.appendChild(document.createTextNode("World size")); + const n80 = shellElement(n79, "select", { id: "world-size" }); + const n81 = shellElement(n80, "option"); + n81.appendChild(document.createTextNode("256")); + const n82 = shellElement(n80, "option", { selected: "" }); + n82.appendChild(document.createTextNode("512")); + const n83 = shellElement(n80, "option"); + n83.appendChild(document.createTextNode("1024")); + const n84 = shellElement(n80, "option"); + n84.appendChild(document.createTextNode("2048")); + const n85 = shellElement(n73, "button", { id: "generate-world", class: "primary full" }); + shellElement(n85, "span", { "data-icon": "refresh" }); + n85.appendChild(document.createTextNode("Generate landscape")); + const n87 = shellElement(n73, "p", { class: "muted small" }); + n87.appendChild( + document.createTextNode( + "Replaces the stack as one undoable action. All dimensions are in metres.", + ), + ); + const n88 = shellElement(n73, "div", { class: "section-title pass-title" }); + n88.appendChild(document.createTextNode("ADD A PROCEDURAL PASS")); + shellElement(n73, "div", { id: "pass-grid", class: "pass-grid" }); + n28.appendChild(document.createTextNode("\n ")); + const n90 = shellElement(n28, "div", { class: "sidebar-bottom" }); + shellElement(n90, "i", { class: "status-dot" }); + const n92 = shellElement(n90, "span"); + n92.appendChild(document.createTextNode("Deterministic · local · open source")); + n28.appendChild(document.createTextNode("\n ")); + n27.appendChild(document.createTextNode("\n ")); + const n93 = shellElement(n27, "main", { class: "stage" }); + n93.appendChild(document.createTextNode("\n ")); + const n94 = shellElement(n93, "div", { class: "viewport-toolbar" }); + const n95 = shellElement(n94, "div", { class: "view-settings" }); + const n96 = shellElement(n95, "button", { + class: "icon-button", + id: "navigate-btn", + "aria-label": "Toggle navigation tool", + title: "Navigate / edit (V)", + }); + shellElement(n96, "span", { "data-icon": "cursor" }); + const n98 = shellElement(n95, "select", { id: "view-angle", "aria-label": "Camera view" }); + const n99 = shellElement(n98, "option", { value: "perspective" }); + n99.appendChild(document.createTextNode("Perspective")); + const n100 = shellElement(n98, "option", { value: "top" }); + n100.appendChild(document.createTextNode("Top view")); + const n101 = shellElement(n95, "select", { id: "render-mode", "aria-label": "Render mode" }); + const n102 = shellElement(n101, "option", { value: "lit" }); + n102.appendChild(document.createTextNode("Lit")); + const n103 = shellElement(n101, "option", { value: "height" }); + n103.appendChild(document.createTextNode("Height")); + const n104 = shellElement(n101, "option", { value: "slope" }); + n104.appendChild(document.createTextNode("Slope")); + const n105 = shellElement(n101, "option", { value: "materials" }); + n105.appendChild(document.createTextNode("Material IDs")); + const n106 = shellElement(n101, "option", { value: "wire" }); + n106.appendChild(document.createTextNode("Wireframe")); + const n107 = shellElement(n94, "div", { class: "view-settings" }); + const n108 = shellElement(n107, "span", { class: "toolbar-label" }); + n108.appendChild(document.createTextNode("PREVIEW")); + const n109 = shellElement(n107, "select", { + id: "preview-resolution", + "aria-label": "Preview resolution", + }); + const n110 = shellElement(n109, "option", { value: "65" }); + n110.appendChild(document.createTextNode("65²")); + const n111 = shellElement(n109, "option", { value: "129" }); + n111.appendChild(document.createTextNode("129²")); + const n112 = shellElement(n109, "option", { value: "257", selected: "" }); + n112.appendChild(document.createTextNode("257²")); + const n113 = shellElement(n109, "option", { value: "513" }); + n113.appendChild(document.createTextNode("513²")); + const n114 = shellElement(n107, "button", { + class: "icon-button", + id: "frame-btn", + title: "Frame terrain (F)", + "aria-label": "Frame terrain", + }); + shellElement(n114, "span", { "data-icon": "frame" }); + const n116 = shellElement(n107, "button", { + class: "icon-button", + id: "rebuild-btn", + title: "Rebuild preview", + "aria-label": "Rebuild preview", + }); + shellElement(n116, "span", { "data-icon": "refresh" }); + n93.appendChild(document.createTextNode("\n ")); + const n118 = shellElement(n93, "div", { id: "viewport", class: "viewport" }); + const n119 = shellElement(n118, "div", { class: "viewport-caption" }); + const n120 = shellElement(n119, "span", { id: "landscape-tag", class: "eyebrow" }); + n120.appendChild(document.createTextNode("LANDSCAPE 01 / ALPINE")); + const n121 = shellElement(n119, "h1", { id: "landscape-title" }); + n121.appendChild(document.createTextNode("Alpine watershed")); + const n122 = shellElement(n119, "p", { id: "landscape-subtitle" }); + n122.appendChild(document.createTextNode("Granite ridges · river valley · conifer forest")); + const n123 = shellElement(n118, "div", { class: "viewport-badges" }); + const n124 = shellElement(n123, "span", { id: "renderer-badge", class: "render-badge" }); + n124.appendChild(document.createTextNode("Loading Three.js…")); + const n125 = shellElement(n123, "span", { id: "busy-status", class: "busy-badge hidden" }); + shellElement(n125, "span", { class: "spinner" }); + const n127 = shellElement(n125, "span", { id: "busy-text" }); + n127.appendChild(document.createTextNode("Building terrain")); + const n128 = shellElement(n125, "button", { id: "cancel-build", title: "Cancel build" }); + n128.appendChild(document.createTextNode("×")); + const n129 = shellElement(n118, "div", { class: "north-indicator" }); + const n130 = shellElement(n129, "span"); + n130.appendChild(document.createTextNode("N")); + const n131 = shellElement(n129, "svg", { viewBox: "0 0 26 38" }); + shellElement(n131, "path", { + d: "M13 2 22 30 13 25 4 30Z", + fill: "none", + stroke: "currentColor", + }); + shellElement(n131, "path", { d: "m13 2 0 23-9 5Z", fill: "currentColor", opacity: ".4" }); + const n134 = shellElement(n118, "div", { id: "spline-bar", class: "spline-bar hidden" }); + const n135 = shellElement(n134, "span", { id: "spline-count" }); + n135.appendChild(document.createTextNode("0 control points")); + const n136 = shellElement(n134, "button", { id: "finish-spline", class: "primary" }); + n136.appendChild(document.createTextNode("Finish spline ↵")); + const n137 = shellElement(n134, "button", { id: "cancel-spline", class: "ghost" }); + n137.appendChild(document.createTextNode("Cancel")); + const n138 = shellElement(n118, "div", { class: "canvas-help", id: "canvas-help" }); + shellElement(n138, "span", { "data-icon": "mouse" }); + const n140 = shellElement(n138, "span"); + n140.appendChild(document.createTextNode("Drag to sculpt ")); + const n141 = shellElement(n140, "b"); + n141.appendChild(document.createTextNode("·")); + n140.appendChild(document.createTextNode(" Shift lowers ")); + const n142 = shellElement(n140, "b"); + n142.appendChild(document.createTextNode("·")); + n140.appendChild(document.createTextNode(" Right-drag orbits")); + shellElement(n118, "div", { id: "toast", role: "status", class: "toast hidden" }); + n93.appendChild(document.createTextNode("\n ")); + const n144 = shellElement(n93, "div", { class: "viewport-footer" }); + const n145 = shellElement(n144, "span", { id: "coordinates" }); + n145.appendChild(document.createTextNode("X —   Y —   Z —")); + const n146 = shellElement(n144, "span", { id: "mesh-stats" }); + n146.appendChild(document.createTextNode("Preparing terrain…")); + const n147 = shellElement(n144, "span", { id: "eval-time" }); + n147.appendChild(document.createTextNode("CPU worker")); + n93.appendChild(document.createTextNode("\n ")); + n27.appendChild(document.createTextNode("\n ")); + const n148 = shellElement(n27, "aside", { class: "sidebar right-sidebar" }); + const n149 = shellElement(n148, "div", { class: "panel-heading" }); + const n150 = shellElement(n149, "span"); + n150.appendChild(document.createTextNode("OPERATION STACK")); + const n151 = shellElement(n149, "span", { id: "layer-count", class: "count-tag" }); + n151.appendChild(document.createTextNode("0")); + const n152 = shellElement(n148, "div", { class: "stack-explainer" }); + n152.appendChild(document.createTextNode("Evaluated from top to bottom")); + shellElement(n152, "span", { "data-icon": "arrow-down" }); + shellElement(n148, "div", { id: "layer-list", class: "layer-list" }); + const n155 = shellElement(n148, "div", { class: "inspector" }); + const n156 = shellElement(n155, "div", { class: "inspector-header" }); + const n157 = shellElement(n156, "span", { id: "selected-type" }); + n157.appendChild(document.createTextNode("LAYER PROPERTIES")); + const n158 = shellElement(n156, "div"); + const n159 = shellElement(n158, "button", { + id: "layer-up", + class: "icon-button small-icon", + title: "Move earlier", + "aria-label": "Move layer up", + }); + shellElement(n159, "span", { "data-icon": "arrow-up" }); + const n161 = shellElement(n158, "button", { + id: "layer-down", + class: "icon-button small-icon", + title: "Move later", + "aria-label": "Move layer down", + }); + shellElement(n161, "span", { "data-icon": "arrow-down" }); + const n163 = shellElement(n158, "button", { + id: "layer-duplicate", + class: "icon-button small-icon", + title: "Duplicate layer", + "aria-label": "Duplicate layer", + }); + shellElement(n163, "span", { "data-icon": "copy" }); + const n165 = shellElement(n158, "button", { + id: "layer-delete", + class: "icon-button small-icon danger", + title: "Delete layer", + "aria-label": "Delete layer", + }); + shellElement(n165, "span", { "data-icon": "trash" }); + shellElement(n155, "input", { + id: "selected-name", + class: "layer-name-input", + "aria-label": "Layer name", + placeholder: "Select a layer", + }); + const n168 = shellElement(n155, "label", { class: "slider-label" }); + n168.appendChild(document.createTextNode("Opacity ")); + const n169 = shellElement(n168, "output", { id: "opacity-value" }); + n169.appendChild(document.createTextNode("100%")); + shellElement(n155, "input", { + id: "selected-opacity", + type: "range", + min: "0", + max: "1", + step: "0.01", + value: "1", + "aria-label": "Layer opacity", + }); + const n171 = shellElement(n155, "div", { class: "json-label" }); + const n172 = shellElement(n171, "span"); + n172.appendChild(document.createTextNode("PARAMETERS + MASK")); + const n173 = shellElement(n171, "span"); + n173.appendChild(document.createTextNode("JSON")); + shellElement(n155, "textarea", { + id: "selected-json", + spellcheck: "false", + "aria-label": "Layer parameters", + }); + shellElement(n155, "div", { id: "layer-error", class: "inline-error" }); + const n176 = shellElement(n155, "button", { id: "apply-layer", class: "secondary full" }); + n176.appendChild(document.createTextNode("Apply changes")); + const n177 = shellElement(n148, "details", { class: "diagnostics" }); + const n178 = shellElement(n177, "summary"); + n178.appendChild(document.createTextNode("Build diagnostics ")); + const n179 = shellElement(n178, "span", { id: "diagnostic-count" }); + n179.appendChild(document.createTextNode("0")); + const n180 = shellElement(n177, "div", { id: "diagnostic-list" }); + n180.appendChild(document.createTextNode("No diagnostics.")); + const n181 = shellElement(n148, "div", { class: "sidebar-bottom" }); + shellElement(n181, "span", { "data-icon": "code" }); + const n183 = shellElement(n181, "span"); + n183.appendChild(document.createTextNode("Same operations. UI or code.")); + n27.appendChild(document.createTextNode("\n")); +} diff --git a/packages/terrain/src/editor/shell.ts b/packages/terrain/src/editor/shell.ts index a59af722b4..1c35998df5 100644 --- a/packages/terrain/src/editor/shell.ts +++ b/packages/terrain/src/editor/shell.ts @@ -1,2 +1,28 @@ -export const editorShell = - '\n
\n
STRATATERRAIN STUDIO
\n
Alpine watershedLocal workspace
\n
\n
\n
\n \n
\n
PREVIEW
\n
LANDSCAPE 01 / ALPINE

Alpine watershed

Granite ridges \u00b7 river valley \u00b7 conifer forest

Loading Three.js\u2026
N
Drag to sculpt \u00b7 Shift lowers \u00b7 Right-drag orbits
\n \n
\n \n
\n
AI-FIRST AUTHORING

The recipe is the source.

Edit structured data, not a mesh. No JavaScript is executed here.

PROJECT RECIPEschema version 1
\n
PORTABLE BY DESIGN

Take your terrain anywhere.

Keep the recipe. Bake the data your engine needs.

Preview and export resolutions are independent.
High-resolution bakes run in a separate worker.

Bring your own terrain

Project JSON, grayscale PNG8/16, or square RAW16 LE.

World GLB carries the terrain, the placed models with their final transforms, this project\u2019s PBR maps and its baked water; lighting, sky, fog and live water stay the receiving game\u2019s. The terrain-only cards export no props, and the runtime bundle includes deterministic placements separately. No navmesh or EXR export.
\n'; +import { appendShellDialogs } from "./shell-dialogs.js"; +import { appendShellLayout } from "./shell-layout.js"; +export function shellElement( + parent: Node, + tag: string, + attributes: Record = {}, +): Element { + const element = + tag === "svg" || tag === "path" + ? document.createElementNS("http://www.w3.org/2000/svg", tag) + : document.createElement(tag); + for (const [name, value] of Object.entries(attributes)) element.setAttribute(name, value); + // Range values must be applied after step, or the browser rounds fractional defaults. + if (attributes.value !== undefined) element.setAttribute("value", attributes.value); + parent.appendChild(element); + return element; +} + +export function createEditorShell(): DocumentFragment { + const fragment = document.createDocumentFragment(); + const style = document.createElement("style"); + style.textContent = + ":root{--bg:#111612;--panel:#171d18;--panel-2:#1c231d;--border:#2b342c;--muted:#839085;--text:#e1e7df;--accent:#c2e9a2;--accent-dim:#273724;--danger:#e9a698;--mono:'SFMono-Regular',Consolas,'Liberation Mono',monospace}*{box-sizing:border-box}html,body{margin:0;height:100%;overflow:hidden}body{background:var(--bg);color:var(--text);font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;font-size:12px;-webkit-font-smoothing:antialiased}button,input,select,textarea{font:inherit;color:inherit}button{display:inline-flex;align-items:center;justify-content:center;gap:7px;cursor:pointer;border:1px solid var(--border);background:var(--panel-2);border-radius:5px;padding:8px 11px;transition:background .13s,color .13s,border-color .13s;white-space:nowrap}button:hover{background:#2b362c;border-color:#50614b}button:disabled{opacity:.35;cursor:default}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:1px solid var(--accent);outline-offset:3px}button svg,[data-icon] svg{width:16px;height:16px;display:block;flex-shrink:0}input,select,textarea{background:#111812;border:1px solid var(--border);border-radius:4px;padding:7px 8px;min-width:0}select{cursor:pointer;max-width:100%;outline:none}input[type=number]{width:100%}input[type=range]{appearance:none;-webkit-appearance:none;width:100%;height:3px;padding:0;border:none;background:#364333;border-radius:4px;display:block;margin:10px 0 22px;accent-color:var(--accent)}input[type=range]::-webkit-slider-thumb{appearance:none;width:10px;height:10px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 3px #182019}input[type=range]::-moz-range-thumb{width:10px;height:10px;border:0;border-radius:50%;background:var(--accent)}textarea{resize:vertical;font-family:var(--mono);font-size:11px;line-height:1.6;tab-size:2;white-space:pre;overflow:auto}p{line-height:1.6}h1,h2,h3,p{margin:0}small{font-size:10px}svg{stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}button.primary{background:var(--accent);color:#14200f;border-color:var(--accent);font-weight:650}button.primary:hover{background:#d4f5b7}button.secondary{background:#293527;border-color:#42513b;color:#d4e7c9}button.ghost{background:transparent;border-color:var(--border)}button.icon-button{width:31px;height:30px;padding:0;background:transparent;border-color:transparent}button.icon-button:hover,button.icon-button.active{background:#2b3927;color:var(--accent)}.small-icon{width:25px!important;height:25px!important}.muted{color:var(--muted)}.small{font-size:10px}.hidden{display:none!important}.full{width:100%}.eyebrow{font-size:9px;letter-spacing:1.8px;font-weight:600;color:var(--accent)}.status-dot{display:inline-block;width:5px;height:5px;background:#a9cf85;border-radius:50%;flex:none}.app-header{height:65px;padding:0 22px;display:flex;align-items:center;gap:22px;border-bottom:1px solid var(--border);background:#151b16}.brand-wrap{display:flex;align-items:center;gap:10px;width:208px;flex-shrink:0}.brand-mark{width:34px;height:36px;color:var(--accent)}.brand-wrap strong{font-size:19px;font-weight:650;letter-spacing:4px;line-height:1.25;display:block}.brand-wrap span{font-size:8px;letter-spacing:2.1px;color:#8c9b88;display:block;margin-top:3px}.header-divider{height:26px;width:1px;background:var(--border)}.project-heading{display:flex;flex-direction:column;gap:5px}.project-heading>span{font-size:12px;font-weight:550}.project-heading small{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:9px}.header-actions{margin-left:auto;display:flex;align-items:center;gap:10px}.history-buttons{display:flex;gap:0;padding-right:12px;border-right:1px solid var(--border);margin-right:2px}.workspace{height:calc(100% - 65px);display:grid;grid-template-columns:254px minmax(260px,1fr) 292px}.sidebar{display:flex;flex-direction:column;min-height:0;background:var(--panel)}.left-sidebar{border-right:1px solid var(--border)}.right-sidebar{border-left:1px solid var(--border)}.panel-heading{height:48px;min-height:48px;padding:0 17px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);font-size:9px;font-weight:650;letter-spacing:1.4px}.tiny-tag{font-size:7px;letter-spacing:.8px;color:#8da27f}.count-tag{padding:3px 6px;border:1px solid #354332;border-radius:4px;font:9px var(--mono);color:var(--accent)}.panel-tabs{display:flex;padding:0 15px;border-bottom:1px solid var(--border);flex:none}.panel-tabs button{width:50%;border:0;border-radius:0;background:transparent;padding:12px 8px;color:var(--muted);font-size:11px}.panel-tabs .active{color:var(--accent);border-bottom:2px solid var(--accent)}.panel-scroll{padding:16px;overflow-y:auto;flex:1}.tool-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:21px}.tool-button{justify-content:flex-start;gap:10px;border-color:#2b352b;padding:11px 12px;font-size:10px;background:#1c231d;color:#aebbaa}.tool-button.active{background:var(--accent-dim);border-color:#6f9158;color:#d8edc8}.tool-button svg{width:16px;height:16px;color:#8e9e86}.tool-button.active svg{color:var(--accent)}.section-title{display:flex;align-items:center;justify-content:space-between;font-size:11px;font-weight:600;margin:0 0 9px}.keycap{border:1px solid #394633;border-radius:3px;color:#8a9b7f;font:9px var(--mono);padding:2px 5px}.tool-description{font-size:10px;margin:0 0 20px;min-height:30px}.slider-label{font-size:10px;color:#b1bcae;display:flex;align-items:center;justify-content:space-between}.slider-label output{font:10px var(--mono);color:#d7e7c8}.field-label{display:flex;flex-direction:column;gap:7px;font-size:10px;color:#a9b5a3;margin-bottom:12px}.field-label input,.field-label select{width:100%;color:var(--text)}.tool-options{border-top:1px solid var(--border);padding-top:14px;margin:0 0 6px}.segmented{display:flex;gap:5px;margin-bottom:12px}.segmented button{flex:1;padding:7px 4px;font-size:9px}.segmented button.active{color:var(--accent);border-color:#789362;background:#273624}.stamp-grid,.swatch-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:5px;margin-bottom:14px}.stamp-option{flex-direction:column;padding:9px 4px;font-size:8px;gap:6px;color:var(--muted)}.stamp-option svg{width:36px;height:18px}.stamp-option.active{border-color:#75905f;color:var(--accent);background:#243520}.swatch{width:100%;height:24px;border-radius:3px}.advanced{border-top:1px solid var(--border);margin-top:16px;padding-top:11px}.advanced summary{font-size:10px;cursor:pointer;color:#a7b49e;list-style:none;display:flex;justify-content:space-between;margin-bottom:14px}.advanced summary span{color:#6d7c65}.tip-card{margin-top:18px;padding:13px 11px;background:#1b241b;display:flex;gap:10px;border:1px solid #2a3727;border-radius:5px;color:#a4b197}.tip-card [data-icon]{padding-top:3px;color:#829e6c}.tip-card p{font-size:9px;line-height:1.7}.tip-card strong{font-weight:500;color:#c7d9ba}.sidebar-bottom{min-height:31px;display:flex;align-items:center;justify-content:center;gap:7px;border-top:1px solid var(--border);font-size:8px;color:#7c8d74;letter-spacing:.1px;background:#141b14}.sidebar-bottom svg{width:13px;height:13px}.preset-list{display:flex;flex-direction:column;gap:7px;margin:15px 0}.preset-card{position:relative;overflow:hidden;display:flex;align-items:flex-start;flex-direction:column;gap:5px;padding:15px 12px;text-align:left;background:linear-gradient(125deg,#2b3a29,#172017);white-space:normal;min-height:74px}.preset-card[data-preset=island]{background:linear-gradient(125deg,#274644,#192c26)}.preset-card[data-preset=desert]{background:linear-gradient(125deg,#5a4632,#2a261d)}.preset-card[data-preset=blank]{background:linear-gradient(125deg,#293028,#1a2119)}.preset-card.active{border-color:#99b979}.preset-card strong{font-size:11px;z-index:1}.preset-card small{font-size:8px;color:#9daf92;z-index:1;max-width:155px;line-height:1.5}.preset-card:after{content:'';position:absolute;right:-14px;bottom:-18px;transform:rotate(45deg);width:90px;height:90px;border:1px solid #b0d3a02b;box-shadow:0 0 0 12px #b0d3a00b,0 0 0 25px #b0d3a009}.two-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}.pass-title{font-size:8px;letter-spacing:1.1px;color:#a6b49c;margin-top:25px}.pass-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px}.pass-grid button{font-size:9px;padding:10px 7px;justify-content:flex-start}.pass-grid svg{width:13px}.stage{display:flex;flex-direction:column;min-width:0;min-height:0;background:#141c17}.viewport-toolbar{height:43px;flex:none;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);background:#171e18;padding:0 12px;gap:8px}.view-settings{display:flex;align-items:center;gap:5px}.view-settings select{border-color:transparent;background:transparent;font-size:10px;padding:5px 2px;color:#b0bbaa}.toolbar-label{font-size:7px;color:#73836b;letter-spacing:1.3px;margin-left:4px}.viewport{position:relative;min-height:0;flex:1;overflow:hidden;isolation:isolate;background:radial-gradient(ellipse at 50% 35%,#27332b 0,#111a15 80%)}.viewport canvas.render-canvas{position:absolute;inset:0;width:100%;height:100%;display:block;touch-action:none;z-index:0;outline:none}.viewport-caption{position:absolute;left:25px;top:25px;z-index:2;pointer-events:none;text-shadow:0 2px 12px #0008}.viewport-caption h1{font-size:25px;font-weight:450;letter-spacing:-.7px;margin-top:8px;color:#e0eadd}.viewport-caption p{font-size:9px;color:#9ead9a;margin-top:6px;letter-spacing:.2px}.viewport-badges{position:absolute;right:17px;top:19px;display:flex;flex-direction:column;align-items:flex-end;gap:7px;z-index:3}.render-badge{font-size:8px;font-family:var(--mono);color:#afbc9e;background:#151e16c9;border:1px solid #3b4a347a;padding:6px 8px;border-radius:4px;max-width:210px;text-align:right}.busy-badge{display:flex;gap:7px;align-items:center;border-radius:4px;background:#1c281b;border:1px solid #546b43;color:var(--accent);font-size:9px;padding:5px 7px}.busy-badge button{border:0;background:none;padding:0 2px;font-size:16px;line-height:1}.spinner{width:9px;height:9px;border:1px solid #6a825b;border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.north-indicator{position:absolute;right:24px;top:102px;display:flex;flex-direction:column;align-items:center;gap:6px;color:#a9bea1;z-index:2;pointer-events:none}.north-indicator span{font:9px var(--mono)}.north-indicator svg{width:21px;height:34px}.canvas-help{position:absolute;bottom:23px;left:50%;transform:translateX(-50%);white-space:nowrap;display:flex;gap:8px;align-items:center;background:#151e16cf;color:#92a187;padding:8px 12px;border:1px solid #4352387a;border-radius:6px;font-size:9px;z-index:3;pointer-events:none;backdrop-filter:blur(9px)}.canvas-help svg{width:13px;height:15px}.canvas-help b{font-weight:400;color:#617058;margin:0 7px}.viewport-footer{height:29px;min-height:29px;display:flex;gap:8px;align-items:center;justify-content:space-between;padding:0 13px;border-top:1px solid var(--border);background:#151c16;color:#88967e;font:8px var(--mono)}.viewport-footer #mesh-stats{color:#abb9a1}.stack-explainer{height:32px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 15px;font-size:8px;color:#7e8d76;border-bottom:1px solid #273125}.stack-explainer svg{width:12px;height:12px}.layer-list{flex:1;min-height:100px;overflow-y:auto;padding:6px}.layer-row{display:flex;gap:7px;align-items:center;padding:8px 7px;min-height:33px;border:1px solid transparent;border-radius:4px;cursor:pointer;font-size:10px;color:#b0bea7;user-select:none}.layer-row:hover{background:#212d20}.layer-row.selected{background:#2a3824;border-color:#4b613d;color:#e4eedb}.layer-row.disabled{opacity:.45}.layer-row .layer-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}.layer-row .layer-icon{color:#8aa574}.layer-row .layer-icon svg{width:13px;height:13px}.layer-eye{background:none!important;border:0!important;padding:0!important;min-width:16px;width:16px;height:17px;color:#839a73}.layer-eye svg{width:12px!important;height:12px!important}.layer-row .layer-index{font:8px var(--mono);color:#65745b;width:17px;flex:none}.layer-row.drag-over{border-top-color:var(--accent)}.inspector{flex:none;border-top:1px solid var(--border);padding:12px 13px 13px;background:#182018}.inspector-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;font-size:8px;font-weight:600;letter-spacing:1px;color:#a0b091}.inspector-header>div{display:flex;gap:1px}.danger:hover{color:var(--danger)!important}.layer-name-input{width:100%;font-size:11px;background:#141b14;margin-bottom:14px}.inspector input[type=range]{margin-bottom:13px}.json-label{display:flex;justify-content:space-between;color:#7d9072;font-size:7px;letter-spacing:1px;margin:10px 0 7px}.json-label span:last-child{font-family:var(--mono);color:#6c805f;letter-spacing:0}.inspector textarea{width:100%;height:133px;min-height:75px;max-height:300px;font-size:9px;color:#b2c4a3;background:#111810;resize:vertical;border-color:#2c3a26}.inspector .full{padding:7px;font-size:10px;margin-top:6px}.inline-error{font:10px/1.5 var(--mono);color:var(--danger);white-space:pre-wrap;margin-top:5px;max-height:80px;overflow:auto}.inline-error:empty{display:none}.diagnostics{border-top:1px solid var(--border);font-size:9px;color:#85977a;flex:none}.diagnostics summary{padding:10px 15px;cursor:pointer}.diagnostics summary span{float:right;color:#a9c08e}.diagnostics>div{padding:0 15px 12px;font-size:9px;line-height:1.5;max-height:100px;overflow:auto}.spline-bar{position:absolute;bottom:74px;left:50%;transform:translateX(-50%);display:flex;gap:8px;align-items:center;border:1px solid #506d3c;border-radius:6px;background:#172314ee;padding:9px;z-index:5;font-size:10px;white-space:nowrap}.spline-bar>span{padding:0 6px}.spline-bar button{font-size:10px}.toast{position:absolute;left:50%;bottom:76px;transform:translateX(-50%);background:#253720f5;border:1px solid #627f48;border-radius:5px;padding:11px 16px;max-width:85%;font-size:11px;color:#d4e8c4;z-index:10;box-shadow:0 5px 30px #0005;text-align:center}dialog{background:#171e18;color:var(--text);border:1px solid #3c4b34;border-radius:12px;padding:25px;box-shadow:0 25px 130px #000a;max-width:calc(100vw - 30px);max-height:calc(100vh - 35px);overflow:auto}dialog::backdrop{background:#070d09aa;backdrop-filter:blur(6px)}.dialog-header{display:flex;justify-content:space-between;gap:20px;margin-bottom:24px}.dialog-header h2{font-size:27px;font-weight:450;letter-spacing:-.9px;margin-top:7px}.dialog-header p{font-size:11px;color:#8da080;margin-top:8px}.dialog-close{font-size:25px;font-weight:300;line-height:1}.recipe-dialog{width:1060px}.recipe-layout{display:grid;grid-template-columns:minmax(0,1fr) 268px;gap:25px}.recipe-editor-wrap{min-width:0}#recipe-editor{width:100%;height:min(60vh,590px);font-size:11px;line-height:1.75;color:#c2d5b0;background:#10180f;resize:none;padding:15px}.api-note{border-left:1px solid #35432d;padding:20px 0 0 24px;color:#a7b89a}.api-note h3{font-size:25px;line-height:1.2;font-weight:420;letter-spacing:-.6px;color:#dbe9cf;margin:12px 0 15px}.api-note p{font-size:10px;line-height:1.8}.api-note pre{font:10px/1.65 var(--mono);color:#bddd9d;padding:15px 0;margin:7px 0}.api-footnote{font:9px/2 var(--mono);color:#798f6a;border-top:1px solid #2f3d28;padding-top:16px}.api-footnote code{color:#b2c59e}.dialog-footer{display:flex;align-items:center;gap:15px;margin-top:18px}.dialog-footer .primary{margin-left:auto}.dialog-footer .muted{max-width:320px;font-size:9px;line-height:1.5}.export-dialog{width:720px}.export-settings{display:flex;align-items:center;gap:25px;padding:14px 17px;background:#1e291c;border:1px solid #33462a;border-radius:6px;margin-bottom:18px}.export-settings .field-label{margin:0;min-width:154px}.export-settings p{color:#94a986;font-size:10px;line-height:1.8}.export-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.export-card{display:flex;align-items:flex-start;flex-direction:column;gap:10px;padding:17px 13px;white-space:normal;text-align:left;background:#1c261a;border-color:#34452c;border-radius:7px;min-height:112px}.export-card svg{width:20px;height:20px;color:#a6c98b}.export-card strong{font-size:11px;font-weight:550}.export-card small{color:#859d75;font-size:9px;line-height:1.5}.export-card.featured{border-color:#7f9f64;background:#25371e}.import-area{margin-top:22px;border-top:1px solid #33412c;padding-top:18px;display:flex;justify-content:space-between;flex-wrap:wrap;gap:14px;align-items:center}.import-area strong{font-weight:500;font-size:11px}.import-area p{font-size:9px;color:#8ca27b;margin-top:5px}.import-ranges{display:grid;grid-template-columns:1fr 1fr;gap:12px;width:100%}.import-ranges label{display:flex;align-items:center;gap:7px;color:#8fa580;font-size:9px}.import-ranges input{width:70px;font-size:10px}.export-note{margin-top:18px;font-size:9px;line-height:1.7;color:#7b8f6e;border-top:1px solid #2c3926;padding-top:12px}.mobile-only{display:none}::-webkit-scrollbar{width:5px;height:5px}::-webkit-scrollbar-thumb{background:#3a4931;border-radius:5px}::-webkit-scrollbar-track{background:transparent}@media(max-height:850px) and (min-width:961px){.tool-button{padding:8px 10px}.panel-scroll{padding:12px}.tool-grid{margin-bottom:16px}.tip-card{display:none}.inspector textarea{height:100px}.advanced{margin-top:8px}.tool-description{min-height:0;margin-bottom:15px}.tool-options{padding-top:10px}}@media(max-width:1150px){.workspace{grid-template-columns:225px minmax(260px,1fr) 258px}.app-header{gap:14px;padding:0 16px}.brand-wrap{width:191px}.header-actions{gap:7px}.viewport-caption h1{font-size:22px}.viewport-caption p{max-width:230px}.render-badge{max-width:140px;font-size:7px}.viewport-badges{top:90px;left:24px;right:auto;align-items:flex-start}.north-indicator{top:25px}.viewport-footer{font-size:7px}.canvas-help{font-size:8px;max-width:95%;padding:8px}.canvas-help b{margin:0 3px}.tool-button{font-size:9px;gap:8px}.tiny-tag{font-size:6px}}@media(max-width:960px){.workspace{grid-template-columns:1fr}.sidebar{position:absolute;z-index:20;top:65px;bottom:0;box-shadow:0 10px 40px #0009;display:none;width:280px}.left-sidebar{left:0}.right-sidebar{right:0}.mobile-only{display:inline-flex;font-size:9px;padding:7px}.project-heading,.header-divider,.history-buttons{display:none}.brand-wrap{width:auto;margin-right:auto}.header-actions .ghost span:last-child{display:none}.header-actions .primary{font-size:9px;padding:8px}.brand-wrap strong{font-size:15px;letter-spacing:3px}.brand-wrap span{font-size:6px}.brand-mark{width:29px}.app-header{padding:0 12px;gap:6px}.header-actions{gap:4px}.header-actions .ghost{padding:8px}.recipe-layout{grid-template-columns:1fr}.api-note{display:none}.dialog-footer .muted{display:none}.dialog-header h2{font-size:22px}.export-grid{grid-template-columns:1fr 1fr}.export-settings p{display:none}.import-ranges label{flex-direction:column;align-items:flex-start}.canvas-help{font-size:8px}.viewport-caption p{max-width:260px}.viewport-badges{top:101px}.viewport-caption{left:19px}.toolbar-label{display:none}.view-settings select{font-size:9px}.view-settings{gap:1px}body[data-mobile-panel=tools] .left-sidebar,body[data-mobile-panel=layers] .right-sidebar{display:flex}.viewport-footer{font-size:7px;gap:5px}.export-dialog{padding:18px}.dialog-footer{gap:8px}.dialog-footer button{font-size:10px}}\n/* Compact, readable layer labels and tool-specific selectors. */\n.layer-row .layer-label{min-width:0;display:flex;flex-direction:column;gap:2px}.layer-label strong{font-size:10px;overflow:hidden;text-overflow:ellipsis;font-weight:500}.layer-label small{font-size:8px;color:#7c9270}.layer-row{min-height:39px;padding-top:6px;padding-bottom:6px}.swatch{height:43px;flex-direction:column;gap:4px;font-size:8px;padding:5px 3px}.swatch i{display:block;width:25px;height:10px;border-radius:2px}.swatch.active{border-color:var(--accent);background:#2a3922}.preset-card{flex-direction:row;align-items:center;gap:12px}.preset-card>span:last-child{display:flex;flex-direction:column;gap:4px}.preset-thumb svg{width:30px;height:30px;color:#bdd1a6}.tool-options input[type=checkbox]{vertical-align:middle;accent-color:var(--accent)}.canvas-help>svg{width:14px;height:14px;flex-shrink:0}\n"; + fragment.append(style); + appendShellLayout(fragment); + appendShellDialogs(fragment); + return fragment; +} diff --git a/packages/terrain/src/editor/worker.js b/packages/terrain/src/editor/worker.js deleted file mode 100644 index 13fb8121ee..0000000000 --- a/packages/terrain/src/editor/worker.js +++ /dev/null @@ -1,23 +0,0 @@ -import { TerrainEvaluator, makeExport, validateDocument } from "../index.js"; -const evaluator = new TerrainEvaluator({ cacheMB: 48 }); -self.onmessage = async ({ data }) => { - const { id, type, recipe, resolution, kind } = data; - try { - validateDocument(recipe); - const start = performance.now(); - const state = evaluator.evaluate(recipe, { - resolution, - onProgress: (p) => self.postMessage({ id, type: "progress", progress: p }), - }); - if (type === "export") { - const output = await makeExport(state, recipe, kind); - self.postMessage({ id, type: "exported", output }, [output.bytes.buffer]); - } else self.postMessage({ id, type: "evaluated", state, ms: performance.now() - start }); - } catch (error) { - self.postMessage({ - id, - type: "error", - error: error instanceof Error ? error.message : String(error), - }); - } -}; diff --git a/packages/terrain/src/editor/worker.ts b/packages/terrain/src/editor/worker.ts new file mode 100644 index 0000000000..b3050467ff --- /dev/null +++ b/packages/terrain/src/editor/worker.ts @@ -0,0 +1,55 @@ +import { TerrainEvaluator, bakeMesh, makeExport, validateDocument } from "../index.js"; +import type { ExportKind, IExportArchive, ITerrainDocument, ITerrainState } from "../index.js"; + +export interface IWorkerRequest { + id: number; + type: "export" | "evaluate"; + recipe: ITerrainDocument; + resolution: number; + kind?: ExportKind; +} +export type EditorWorkerReply = + | { id: number; type: "progress"; progress: { index: number; total: number; id: string } } + | { id: number; type: "error"; error: string } + | { id: number; type: "evaluated"; state: ITerrainState; normal: Float32Array; ms: number } + | { id: number; type: "exported"; output: IExportArchive }; +interface IEditorWorkerScope { + onmessage: ((event: MessageEvent) => Promise) | null; + postMessage(message: EditorWorkerReply, transfer?: Transferable[]): void; +} +const worker = globalThis as typeof globalThis & IEditorWorkerScope; +const evaluator = new TerrainEvaluator({ cacheMB: 48 }); +worker.onmessage = async ({ data }) => { + const { id, type, recipe, resolution, kind } = data; + try { + validateDocument(recipe); + const start = performance.now(); + const state = evaluator.evaluate(recipe, { + resolution, + onProgress: (p) => worker.postMessage({ id, type: "progress", progress: p }), + }); + if (type === "export") { + if (!kind) throw new Error("Missing export kind"); + const output = await makeExport(state, recipe, kind); + if (!(output.bytes.buffer instanceof ArrayBuffer)) + throw Error("Export buffer is not transferable"); + worker.postMessage({ id, type: "exported", output }, [output.bytes.buffer]); + } else { + const normal = bakeMesh(state).normals; + const transfer = [state.height, normal, state.splat].map(({ buffer }) => { + if (!(buffer instanceof ArrayBuffer)) throw Error("Terrain buffer is not transferable"); + return buffer; + }); + worker.postMessage( + { id, type: "evaluated", state, normal, ms: performance.now() - start }, + transfer, + ); + } + } catch (error) { + worker.postMessage({ + id, + type: "error", + error: error instanceof Error ? error.message : String(error), + }); + } +}; diff --git a/packages/terrain/src/export.ts b/packages/terrain/src/export.ts index 89b826324d..d53f4705ac 100644 --- a/packages/terrain/src/export.ts +++ b/packages/terrain/src/export.ts @@ -201,15 +201,17 @@ function staticModel(source: Object3D, name: string, terrain = false): Object3D } /** - * Export a committed evaluated world using ordinary glTF 2.0 nodes and embedded PBR images. + * Prepare the game's validated, frozen portable scene; the server cook owns GLB encoding. * @requires npm i -D @threenative/terrain - * @situation export terrain, resolved models and final manual placement transforms as a portable GLB - * @situation export the whole terrain world as one glb for another three.js project - * @constraint browser authoring (FileReader/canvas); caller supplies all appearance and coherent baked water; root and /three remain headless - * @example const output = await exportWorldGLB({ revision, snapshotTime: 0, state, terrain, assets, transforms }); - * @override actual static models, final matrices and baked PBR maps are supplied by the game + * @situation export the terrain as a glb for another three.js project via the headless cook + * @situation prepare the terrain editor's resolved world for headless portable GLB cooking + * @constraint browser authoring FileReader/canvas; all appearance and coherent water come from the game + * @example const prepared = await prepareWorldGLTF(input); */ -export async function exportWorldGLB(input: IWorldGLBInput): Promise { +export async function prepareWorldGLTF(input: IWorldGLBInput): Promise<{ + json: Record; + report: IWorldGLBExport["report"]; +}> { const { state } = input; if (!/^[a-f0-9]{64}$/.test(input.revision) || !Number.isFinite(input.snapshotTime)) throw new Error("World export requires a content revision and finite snapshot time"); @@ -323,7 +325,7 @@ export async function exportWorldGLB(input: IWorldGLBInput): Promise(); const materials = new Map(); @@ -372,7 +374,7 @@ export async function exportWorldGLB(input: IWorldGLBInput): Promise { if (!(object instanceof Mesh)) return; @@ -384,8 +386,8 @@ export async function exportWorldGLB(input: IWorldGLBInput): Promise resource.uri) || - json.extensionsRequired?.some((name) => !name.startsWith("KHR_")) - ) - throw new Error("World GLB contains an external resource or nonportable required extension"); - return { - name: "world.glb", - mime: "model/gltf-binary", - bytes: new Uint8Array(binary), - report: { - revision: input.revision, - resolution: n, - snapshotTime: input.snapshotTime, - placementIds: [...ids], - waterIds: [...waterIds], - receivingGameSupplies: [ - "lighting", - "sky/environment", - "fog", - "exposure", - "post-processing", - "live water/wind", - ], - }, + const report: IWorldGLBExport["report"] = { + revision: input.revision, + resolution: n, + snapshotTime: input.snapshotTime, + placementIds: [...ids], + waterIds: [...waterIds], + receivingGameSupplies: [ + "lighting", + "sky/environment", + "fog", + "exposure", + "post-processing", + "live water/wind", + ], }; + if (json instanceof ArrayBuffer) throw new Error("Expected prepared glTF JSON"); + return { json, report }; } diff --git a/packages/terrain/src/kit-assets.ts b/packages/terrain/src/kit-assets.ts new file mode 100644 index 0000000000..b2cea17a1b --- /dev/null +++ b/packages/terrain/src/kit-assets.ts @@ -0,0 +1,145 @@ +import { createHash } from "node:crypto"; +import { lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises"; +import { homedir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { gunzipSync } from "node:zlib"; + +function fail(reason: string): never { + throw new Error(`TN_TERRAIN_ASSETS_${reason}`); +} + +async function isDirectory(path: string) { + return ( + ( + await lstat(path).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return undefined; + throw error; + }) + )?.isDirectory() === true + ); +} + +// Accept only the pinned packer's ustar subset; never links, devices or extended headers. +async function extract(archive: Buffer, target: string) { + const tar = gunzipSync(archive, { maxOutputLength: 128 * 1024 * 1024 }); + const seen = new Set(); + for (let at = 0; at + 512 <= tar.length; ) { + const header = tar.subarray(at, at + 512); + if (header.every((byte) => byte === 0)) { + if ( + tar.length < at + 1024 || + !tar.subarray(at).every((byte) => byte === 0) || + seen.size === 0 + ) + fail("END"); + return; + } + const text = (from: number, to: number) => + header.subarray(from, to).toString("utf8").split("\0")[0] ?? ""; + const octal = (from: number, to: number) => { + const value = text(from, to).trim(); + if (!/^[0-7]+$/u.test(value)) fail("HEADER"); + return Number.parseInt(value, 8); + }; + const checksum = header.reduce( + (sum, byte, index) => sum + (index >= 148 && index < 156 ? 32 : byte), + 0, + ); + if (text(257, 263) !== "ustar" || text(263, 265) !== "00" || octal(148, 156) !== checksum) + fail("HEADER"); + const prefix = text(345, 500); + const name = `${prefix ? `${prefix}/` : ""}${text(0, 100)}`.replace(/\/$/u, ""); + if (!name.startsWith("starter-assets/") && name !== "starter-assets") fail("PATH"); + if ( + name.includes("\\") || + name + .split("/") + .some((part) => part === ".." || part === "." || part === "" || part.includes(":")) + ) + fail("PATH"); + if (seen.has(name)) fail("DUPLICATE"); + seen.add(name); + const size = octal(124, 136); + const type = header[156]; + if (type !== 48 && type !== 53) fail("TYPE"); + if ( + !Number.isSafeInteger(size) || + size < 0 || + (type === 53 && size !== 0) || + (name === "starter-assets" && type !== 53) + ) + fail("SIZE"); + const next = at + 512 + Math.ceil(size / 512) * 512; + if (next > tar.length) fail("TRUNCATED"); + const path = join(target, ...name.split("/").slice(1)); + if (type === 53) await mkdir(path, { recursive: true, mode: 0o755 }); + else { + await mkdir(dirname(path), { recursive: true, mode: 0o755 }); + await writeFile(path, tar.subarray(at + 512, at + 512 + size), { flag: "wx", mode: 0o644 }); + } + at = next; + } + fail("END"); +} + +/** + * Resolve shared starter assets locally, or download the package's pinned archive on demand. + * @situation resolve terrain kit models and textures for offline baking + * @constraint Node authoring only; verifies archive size and SHA256 before atomic extraction + * @requires npm i -D @threenative/terrain + * @example const assets = await starterAssetsDir(); + */ +export async function starterAssetsDir(): Promise { + const root = dirname(fileURLToPath(import.meta.resolve("@threenative/terrain/package.json"))); + const local = join(root, "starter-assets"); + if (await isDirectory(local)) return local; + const manifest = JSON.parse(await readFile(join(root, "starter-assets.json"), "utf8")) as { + version: string; + url: string; + sha256: string; + bytes: number; + }; + if ( + !/^[a-f0-9]{64}$/u.test(manifest.sha256) || + !Number.isSafeInteger(manifest.bytes) || + manifest.bytes <= 0 || + manifest.bytes > 128 * 1024 * 1024 + ) + fail("MANIFEST"); + const cache = join( + process.env.XDG_CACHE_HOME || join(homedir(), ".cache"), + "threenative", + "terrain-starter-assets", + manifest.sha256, + ); + if (await isDirectory(cache)) return cache; + const url = new URL(process.env.THREENATIVE_TERRAIN_ASSETS_URL || manifest.url); + let archive: Buffer; + if (url.protocol === "file:") archive = await readFile(url); + else { + if (url.protocol !== "https:" && url.protocol !== "http:") fail("URL"); + const response = await fetch(url); + if (!response.ok) fail(`HTTP: ${response.status} ${url.href}`); + archive = Buffer.from(await response.arrayBuffer()); + } + if (archive.length !== manifest.bytes) fail("SIZE"); + if (createHash("sha256").update(archive).digest("hex") !== manifest.sha256) fail("SHA256"); + await mkdir(dirname(cache), { recursive: true }); + const tmp = await mkdtemp(`${cache}.tmp-`); + try { + await extract(archive, tmp); + try { + await rename(tmp, cache); + } catch (error) { + if ( + !(await isDirectory(cache)) || + !["EEXIST", "ENOTEMPTY"].includes((error as NodeJS.ErrnoException).code ?? "") + ) + throw error; + } + } finally { + await rm(tmp, { recursive: true, force: true }); + } + return cache; +} diff --git a/packages/terrain/src/kit.ts b/packages/terrain/src/kit.ts new file mode 100644 index 0000000000..7d4a829d35 --- /dev/null +++ b/packages/terrain/src/kit.ts @@ -0,0 +1,242 @@ +// Offline kit authoring; recipes, assets and sky choices remain in the copied game source. +import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { starterAssetsDir } from "./kit-assets.js"; +export { starterAssetsDir } from "./kit-assets.js"; +import type { ITerrainState } from "./core/types.js"; +import type { IWorldPackageAsset, IWorldPackageManifest } from "./core/world-package.js"; +import { + TerrainEvaluator, + applyPlacementOverrides, + bakeWorldPackage, + decodeRAW16, +} from "./index.js"; + +type KitAsset = { + readonly near: string; + readonly bounds: IWorldPackageAsset["bounds"]; + readonly maxDistance?: number; +} & ( + | { readonly mid?: undefined; readonly midDistance?: undefined } + | { readonly mid: string; readonly midDistance: number } +); + +interface IKitCookInput { + readonly state: ITerrainState; + readonly species: Readonly< + Record< + string, + { + glb: string; + bounds: IWorldPackageAsset["bounds"]; + lods: { distance: number; glb: string }[]; + maxDistance?: number; + } + > + >; + readonly materials: { + texturesDir: string; + layers: { + id: string; + diff: string; + nrm: string; + diffMimeType: "image/jpeg"; + nrmMimeType: "image/jpeg"; + }[]; + }; + readonly out: string; + readonly extraLayers: Readonly>; +} + +export interface IBakeKitOptions { + readonly sky: string; + /** Supplied by the kit; terrain does not depend on the optional asset cook. */ + readonly cookTerrainWorld?: (input: IKitCookInput) => Promise<{ + manifest: IWorldPackageManifest; + placementCounts: Readonly>; + worldGlb: Uint8Array; + worldPackage?: ReturnType; + }>; +} + +/** `--key value` after the script; `--assets` and `--out` default as noted below. */ +function flag(name: string, fallback: string) { + const at = process.argv.indexOf(`--${name}`); + if (at === -1) return fallback; + const value = process.argv[at + 1]; + if (value === undefined || value === "" || value.startsWith("--")) { + throw new Error(`bake: --${name} needs a nonempty directory value`); + } + return resolve(value); +} + +/** + * Bake a copied kit, optionally using the caller's installed world cook. + * @situation bake a copied terrain starter kit into its offline world package + * @constraint Node authoring only; recipes, asset tables and sky paths stay in the game + * @requires npm i -D @threenative/terrain + * @example await bakeKit(import.meta.url, { sky: "hdri/game-sky.hdr" }); + */ +export async function bakeKit( + url: string, + { sky, cookTerrainWorld }: IBakeKitOptions, +): Promise { + const here = dirname(fileURLToPath(url)); + const assetsDir = process.argv.includes("--assets") + ? flag("assets", "") + : await starterAssetsDir(); + const outDir = flag("out", join(here, "world")); + + const recipe = JSON.parse(await readFile(join(here, "recipe.json"), "utf8")); + // shortcut: starter heights use a 0.1 m lattice; revise decoding if their precision changes. + for (const layer of recipe.recipe.layers) { + const data = layer.params?.data; + if (data?.raw16 === undefined) continue; + const raw = await readFile(join(here, data.raw16)); + data.values = Array.from(decodeRAW16(raw, data).values, (value) => Math.round(value * 10) / 10); + } + const assetTable = JSON.parse(await readFile(join(here, "assets.json"), "utf8")) as Record< + string, + KitAsset + >; + const surface = JSON.parse(await readFile(join(here, "surface.json"), "utf8")); + + const state = applyPlacementOverrides( + new TerrainEvaluator({ cacheMB: 0 }).evaluate(recipe.recipe), + recipe.placementOverrides ?? {}, + ); + + const assets = Object.fromEntries( + Object.entries(assetTable).map(([id, spec]) => [ + id, + { + bounds: spec.bounds, + glb: `models/${spec.near.split("/").at(-1)}`, + ...(spec.mid + ? { lods: [{ distance: spec.midDistance, glb: `models/${spec.mid.split("/").at(-1)}` }] } + : {}), + maxDistance: spec.maxDistance, + }, + ]), + ); + + const started = performance.now(); + const cooked = cookTerrainWorld + ? await cookTerrainWorld({ + state, + species: Object.fromEntries( + Object.entries(assetTable).map(([id, spec]) => [ + id, + { + glb: join(assetsDir, spec.near), + bounds: spec.bounds, + lods: spec.mid + ? [{ distance: spec.midDistance ?? 40, glb: join(assetsDir, spec.mid) }] + : [], + maxDistance: spec.maxDistance, + }, + ]), + ), + materials: { + texturesDir: assetsDir, + layers: [surface.base, ...surface.layers].map((layer) => ({ + id: layer.id, + ...surface.sources[layer.id], + diffMimeType: "image/jpeg" as const, + nrmMimeType: "image/jpeg" as const, + })), + }, + out: outDir, + extraLayers: { table: "terrain-table.json" }, + }) + : undefined; + const baked = + cooked?.worldPackage ?? + bakeWorldPackage(state, { + assets: cooked?.manifest.assets ?? assets, + ...(cooked ? {} : { layers: { table: "terrain-table.json" } }), + }); + const manifest = cooked?.manifest ?? baked.manifest; + const { splat, files } = baked; + const written = new Map(); + + async function write(relative: string, bytes: string | Uint8Array) { + await mkdir(join(outDir, dirname(relative)), { recursive: true }); + await writeFile(join(outDir, relative), bytes); + written.set(relative, bytes.length); + } + + async function copy(from: string, to: string) { + await mkdir(join(outDir, dirname(to)), { recursive: true }); + await copyFile(join(assetsDir, from), join(outDir, to)); + written.set(to, (await readFile(join(outDir, to))).byteLength); + } + + if (!cooked) await write("world.json", `${JSON.stringify(manifest, null, 2)}\n`); + // The lake and river the generator evaluated, so the game draws the water the bake shaped. + const round = (value: number) => Math.round(value * 1_000) / 1_000; + await write( + "water.json", + `${JSON.stringify( + { + lakes: state.waters.map((lake) => ({ + id: lake.id, + at: lake.at, + radius: lake.radius, + level: lake.level, + })), + rivers: state.rivers.map((river) => ({ + id: river.id, + width: round(river.width), + points: river.points.map((point) => point.map(round)), + })), + }, + null, + 2, + )}\n`, + ); + if (!cooked) { + for (const [name, bytes] of Object.entries(files)) await write(name, bytes); + for (const spec of Object.values(assetTable)) { + await copy(spec.near, `models/${spec.near.split("/").at(-1)}`); + if (spec.mid) await copy(spec.mid, `models/${spec.mid.split("/").at(-1)}`); + } + } + // The sky the props are lit by (CC0 Poly Haven Kloofendal); world.ts gives it to their materials. + await copy(sky, "sky.hdr"); + // The cook copies the layer maps itself when it runs. + if (!cooked) + for (const layer of [surface.base, ...surface.layers]) { + const source = surface.sources[layer.id]; + await copy(source.diff, `${surface.textures}/${layer.id}_diff.jpg`); + await copy(source.nrm, `${surface.textures}/${layer.id}_nrm.jpg`); + } + // `sources` is the bake's own lookup: the runtime reads only base/layers/splat/textures. + await write( + "terrain-table.json", + `${JSON.stringify({ ...surface, splat, textures: "textures" }, null, 2)}\n`, + ); + + if (cooked) { + console.log( + `${Object.entries(cooked.placementCounts) + .map(([asset, count]) => `${asset} ${String(count)}`) + .join( + ", ", + )} placements; world.glb ${String(cooked.worldGlb.length)} B; ${Math.round(performance.now() - started)} ms`, + ); + } else { + const perAsset: Record = {}; + for (const cell of manifest.cells) + for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; + const total = [...written.values()].reduce((sum, bytes) => sum + bytes, 0); + console.log( + `${Object.entries(perAsset) + .map(([asset, count]) => `${asset} ${String(count)}`) + .join(", ")} placements; ${[...written] + .map(([name, bytes]) => `${name} ${String(bytes)} B`) + .join(", ")}; ${String(total)} B total; ${Math.round(performance.now() - started)} ms`, + ); + } +} diff --git a/packages/terrain/src/mcp/child.ts b/packages/terrain/src/mcp/child.ts new file mode 100644 index 0000000000..7702f1f287 --- /dev/null +++ b/packages/terrain/src/mcp/child.ts @@ -0,0 +1,88 @@ +import { spawn } from "node:child_process"; + +export interface IIsolatedRun { + command: string; + args: readonly string[]; + /** Positive milliseconds. The child's whole process group is killed when this elapses. */ + timeoutMs: number; + signal?: AbortSignal; +} + +export interface IIsolatedResult { + stdout: string; + stderr: string; +} + +/** Thrown when the child was killed by the timeout or the caller, so its output may be partial. */ +export class IsolatedKillError extends Error {} + +/** + * Runs one child process in its own process group. Resolves only on exit code 0. A timeout or a + * cancel kills the whole group, grandchildren included, and rejects once the child has closed. + * A signal that is already aborted rejects before anything is spawned. + */ +export function runIsolated({ + command, + args, + timeoutMs, + signal, +}: IIsolatedRun): Promise { + if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { + return Promise.reject(new RangeError("runIsolated: timeoutMs must be a positive number.")); + } + if (signal?.aborted) { + return Promise.reject( + new IsolatedKillError("runIsolated: child was cancelled before it started."), + ); + } + return new Promise((resolve, reject) => { + // ponytail: POSIX process groups; on Windows only the direct child is killed (taskkill /T if needed). + const child = spawn(command, [...args], { + detached: process.platform !== "win32", + stdio: ["ignore", "pipe", "pipe"], + }); + let stdout = ""; + let stderr = ""; + let killed: Error | undefined; + let settled = false; + const finish = (error?: Error, result?: IIsolatedResult) => { + if (settled) return; + settled = true; + clearTimeout(timer); + signal?.removeEventListener("abort", onAbort); + if (error !== undefined) reject(error); + else resolve(result as IIsolatedResult); + }; + const stop = (reason: string) => { + killed ??= new IsolatedKillError(reason); + try { + if (child.pid !== undefined && process.platform !== "win32") + process.kill(-child.pid, "SIGKILL"); + else child.kill("SIGKILL"); + } catch { + // The group already exited; `close` below settles the run. + } + }; + const timer = setTimeout( + () => stop(`runIsolated: child timed out after ${timeoutMs} ms and was killed.`), + timeoutMs, + ); + const onAbort = () => stop("runIsolated: child was cancelled and was killed."); + signal?.addEventListener("abort", onAbort, { once: true }); + + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (chunk: string) => { + stdout += chunk; + }); + child.stderr.on("data", (chunk: string) => { + stderr += chunk; + }); + child.on("error", (error) => finish(error)); + child.on("close", (code) => { + if (killed !== undefined) finish(killed); + else if (code === 0) finish(undefined, { stdout, stderr }); + else finish(new Error(`runIsolated: child exited with code ${code}: ${stderr.trim()}`)); + }); + }); +} diff --git a/packages/terrain/src/mcp/cook-child.ts b/packages/terrain/src/mcp/cook-child.ts new file mode 100644 index 0000000000..b610b6ae05 --- /dev/null +++ b/packages/terrain/src/mcp/cook-child.ts @@ -0,0 +1,11 @@ +import { cookKit } from "./cook.js"; +import type { ICookRequest } from "./cook.js"; + +// The parent validated every field; the child still refuses a request that is not a plain object. +const request: unknown = JSON.parse(process.argv[2] ?? "null"); +if (typeof request !== "object" || request === null) { + throw new Error("cook-child: the request must be a JSON object."); +} + +// The parent reads this single JSON line as the tool result. +console.log(await cookKit(request as ICookRequest)); diff --git a/packages/terrain/src/mcp/cook.ts b/packages/terrain/src/mcp/cook.ts new file mode 100644 index 0000000000..fa591958fb --- /dev/null +++ b/packages/terrain/src/mcp/cook.ts @@ -0,0 +1,344 @@ +import { existsSync, readFileSync } from "node:fs"; +import { copyFile, mkdir, readFile, realpath, rm, writeFile } from "node:fs/promises"; +import { dirname, join, relative, resolve, sep } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { decodeRAW16 } from "../core/io.js"; +import { applyPlacementOverrides } from "../core/placements.js"; +import { Terrain } from "../core/terrain.js"; +import { bakeWorldPackage } from "../core/world-package.js"; +import { starterAssetsDir } from "../kit-assets.js"; +import { measureGlb } from "./glb-stats.js"; +import { TERRAIN_KITS, resolveAssetSource } from "./tools.js"; + +/** One cook request, passed as the child's only argument. The parent validates every field first. */ +export interface ICookRequest { + kit: string; + assetSource?: string; + out: string; + /** The game project root: `@threenative/assets` is resolved from here, never bundled. */ + projectRoot: string; + sizeMeters?: number; + seed?: number; +} + +export interface IBounds { + min: [number, number, number]; + max: [number, number, number]; +} + +interface IAssetSpec { + near: string; + mid?: string; + midDistance?: number; + maxDistance?: number; + bounds: unknown; +} + +interface ISurface { + base: { id: string }; + layers: { id: string }[]; + sources: Record; + textures: string; +} + +/** The slice of `@threenative/assets` this cook calls; the module is loaded from the game project. */ +interface ICooker { + cookTerrainWorld(options: { preparedWorld: Record }): Promise<{ + worldGlb: Uint8Array; + }>; + cookTerrainWorld(options: Record): Promise<{ + worldGlb: Uint8Array; + manifest: { + assets: unknown; + extent: { minX: number; minZ: number; sizeX: number; sizeZ: number }; + terrain: { + columns: number; + heightmap: string; + heightMax: number; + heightMin: number; + rows: number; + spacing: number; + }; + }; + placementCounts: unknown; + }>; +} + +const MISSING_ASSETS = + "terrain_generate needs @threenative/assets in the game project. Run: npm i -D @threenative/assets"; + +function isTriple(value: unknown): value is [number, number, number] { + return ( + Array.isArray(value) && + value.length === 3 && + value.every((entry) => typeof entry === "number" && Number.isFinite(entry)) + ); +} + +/** Narrows a species `bounds` field; a missing or malformed box fails the cook instead of passing through. */ +export function parseBounds(value: unknown, species: string): IBounds { + const box = typeof value === "object" && value !== null ? (value as Record) : {}; + const { min, max } = box; + if ( + !isTriple(min) || + !isTriple(max) || + min.some((entry, axis) => entry > (max[axis] as number)) + ) { + throw new Error( + `cook: species '${species}' bounds must be { min: [x,y,z], max: [x,y,z] } with min <= max.`, + ); + } + return { min, max }; +} + +/** The package entry file under `projectRoot/node_modules`, found by walking upward like Node does. */ +function installedEntry(projectRoot: string, name: string): string | undefined { + let directory = resolve(projectRoot); + while (true) { + const manifestFile = join(directory, "node_modules", name, "package.json"); + if (existsSync(manifestFile)) { + const manifest = JSON.parse(readFileSync(manifestFile, "utf8")) as { + exports?: { "."?: { import?: string } | string }; + main?: string; + }; + const root = manifest.exports?.["."]; + const relative = (typeof root === "string" ? root : root?.import) ?? manifest.main; + return relative === undefined ? undefined : resolve(dirname(manifestFile), relative); + } + const parent = dirname(directory); + if (parent === directory) return undefined; + directory = parent; + } +} + +/** Loads `@threenative/assets` from the game project; its absence is an actionable error, not a stack. */ +export async function loadCooker(projectRoot: string): Promise { + const entry = installedEntry(projectRoot, "@threenative/assets"); + if (entry === undefined || !existsSync(entry)) throw new Error(MISSING_ASSETS); + const loaded = (await import(pathToFileURL(entry).href)) as Partial; + if (typeof loaded.cookTerrainWorld !== "function") { + throw new Error(`${MISSING_ASSETS} (the installed version has no cookTerrainWorld).`); + } + return loaded as ICooker; +} + +const round = (value: number) => Math.round(value * 1_000) / 1_000; + +/** Cooks one starter kit into `request.out` and returns the one-line summary the parent relays. */ +export async function cookKit(request: ICookRequest): Promise { + if (!(TERRAIN_KITS as readonly string[]).includes(request.kit)) { + throw new Error(`cook: unknown kit '${request.kit}'.`); + } + const assetSource = request.assetSource ?? (await resolveAssetSource(request.projectRoot)); + const logicalOut = relative(assetSource, request.out); + if (logicalOut === "" || logicalOut === ".." || logicalOut.startsWith(`..${sep}`)) { + throw new Error( + "terrain_generate: out must be under assets.source so the build can list it in the manifest.", + ); + } + const cooker = await loadCooker(request.projectRoot); + // Kit source ships in the package; binary assets resolve locally or from the pinned release. + const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); + const kitDir = join(packageRoot, "starter", request.kit); + const assetsDir = await starterAssetsDir(); + + // Fail closed before any evaluation: a non-recursive mkdir claims `out` atomically, so two cooks cannot share it. + await mkdir(dirname(request.out), { recursive: true }); + try { + await mkdir(request.out); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "EEXIST") { + throw new Error(`cook: out '${request.out}' already exists.`); + } + throw error; + } + // This cook owns `out` from here on, so a failure leaves nothing partial behind. + try { + return await cookInto({ ...request, assetSource }, cooker, kitDir, assetsDir); + } catch (error) { + await rm(request.out, { recursive: true, force: true }); + throw error; + } +} + +async function cookInto( + request: ICookRequest, + cooker: ICooker, + kitDir: string, + assetsDir: string, +): Promise { + const started = performance.now(); + const recipe = JSON.parse(await readFile(join(kitDir, "recipe.json"), "utf8")); + if (request.sizeMeters !== undefined) recipe.recipe.size = request.sizeMeters; + if (request.seed !== undefined) recipe.recipe.seed = request.seed; + // shortcut: starter heights use a 0.1 m lattice; revise decoding if their precision changes. + for (const layer of recipe.recipe.layers) { + const data = layer.params?.data; + if (data?.raw16 === undefined) continue; + const raw = await readFile(join(kitDir, data.raw16)); + data.values = Array.from( + decodeRAW16(raw, data).values, + (value: number) => Math.round(value * 10) / 10, + ); + } + const assetTable = JSON.parse(await readFile(join(kitDir, "assets.json"), "utf8")) as Record< + string, + IAssetSpec + >; + const surface = JSON.parse(await readFile(join(kitDir, "surface.json"), "utf8")) as ISurface; + + const state = applyPlacementOverrides( + Terrain.fromJSON(recipe.recipe).evaluate(), + recipe.placementOverrides ?? {}, + ); + + const species = Object.fromEntries( + Object.entries(assetTable).map(([id, spec]) => [ + id, + { + glb: join(assetsDir, spec.near), + bounds: parseBounds(spec.bounds, id), + lods: spec.mid + ? [{ distance: spec.midDistance ?? 40, glb: join(assetsDir, spec.mid) }] + : [], + maxDistance: spec.maxDistance, + }, + ]), + ); + + const cooked = await cooker.cookTerrainWorld({ + state, + species, + materials: { + texturesDir: assetsDir, + layers: [surface.base, ...surface.layers].map((layer) => { + const source = surface.sources[layer.id]; + if (source === undefined) throw new Error(`cook: no source for layer '${layer.id}'.`); + return { id: layer.id, ...source, diffMimeType: "image/jpeg", nrmMimeType: "image/jpeg" }; + }), + }, + out: request.out, + extraLayers: { table: "terrain-table.json" }, + }); + + await writeFile( + join(request.out, "water.json"), + `${JSON.stringify( + { + lakes: state.waters.map((lake) => ({ + id: lake.id, + at: lake.at, + radius: lake.radius, + level: lake.level, + })), + rivers: state.rivers.map((river) => ({ + id: river.id, + width: round(river.width), + points: river.points.map((point) => point.map(round)), + })), + }, + null, + 2, + )}\n`, + ); + + const { splat, manifest, files } = bakeWorldPackage(state, { + assets: cooked.manifest.assets as Parameters[1]["assets"], + }); + await writeFile( + join(request.out, "terrain-table.json"), + `${JSON.stringify({ ...surface, splat, textures: "textures" }, null, 2)}\n`, + ); + await copyFile( + join(assetsDir, "hdri", "kloofendal_48d_partly_cloudy_1k.hdr"), + join(request.out, "sky.hdr"), + ); + + // The editable recipe, with the heightmap inlined, is what `terrain_patch` takes back. + await writeFile(join(request.out, "recipe.json"), JSON.stringify(recipe.recipe)); + + const shown = relative(await realpath(request.projectRoot), request.out) + .split(sep) + .join("/"); + // The same source-relative key resolves through the dev and production asset manifests. + const url = relative(request.assetSource as string, request.out) + .split(sep) + .join("/"); + const worldUrl = `${url}/world.json`; + const ring = + Math.ceil(Math.max(manifest.extent.sizeX, manifest.extent.sizeZ) / manifest.cellSize / 2) + 1; + const instances = manifest.cells.reduce( + (count, cell) => count + cell.runs.reduce((sum, run) => sum + run.count, 0), + 0, + ); + const placementBytes = files[manifest.placements]; + if (placementBytes === undefined) + throw new Error("cook: world package has no placement records."); + const budgets = { + residentCells: Math.max(1, manifest.cells.length), + instances: Math.max(1, instances), + bytes: Math.max(1, placementBytes.byteLength), + }; + const heightmapUrl = `${url}/${cooked.manifest.terrain.heightmap}`; + // The GLB carries no collider, so the snippet also builds the ground; aliases dodge stock names. + const physical = JSON.stringify({ + extent: cooked.manifest.extent, + terrain: cooked.manifest.terrain, + }); + return JSON.stringify({ + recipe: request.kit, + out: shown, + worldGlb: `${shown}/world.glb`, + recipeJson: `${shown}/recipe.json`, + worldPackage: `${shown}/world.json`, + ...measureGlb(cooked.worldGlb), + placementCounts: cooked.placementCounts, + worldGlbBytes: cooked.worldGlb.length, + ms: Math.round(performance.now() - started), + load: [ + 'import { WorldCells as TerrainWorldCells, Heightfield as TerrainHeightfield, heightSamplerFromHeightmap as sampleTerrainHeight, loadWorldHeightmap as loadTerrainHeightmap, loadTerrainSplat, type IWorldCellsLoadOptions as TerrainWorldOptions } from "@threenative/core/world";', + 'import { CollisionShape3D as TerrainCollisionShape, RigidBody3D as TerrainRigidBody } from "@threenative/physics";', + 'import { Object3D as TerrainAnchor } from "three";', + `const terrainWorld = ${physical};`, + `const [terrainHeightmapUrl] = await ctx.assets.resolve(${JSON.stringify(heightmapUrl)});`, + 'if (terrainHeightmapUrl === undefined) throw new Error("terrain: the world heightmap is not served.");', + "const terrainHeights = await loadTerrainHeightmap(terrainHeightmapUrl);", + "const terrainField = TerrainHeightfield.fromSampler({", + " columns: terrainWorld.terrain.columns,", + " depth: terrainWorld.extent.sizeZ,", + " origin: { x: terrainWorld.extent.minX + terrainWorld.extent.sizeX / 2, z: terrainWorld.extent.minZ + terrainWorld.extent.sizeZ / 2 },", + " rows: terrainWorld.terrain.rows,", + " sampleHeight: sampleTerrainHeight(terrainWorld.terrain, terrainWorld.extent, terrainHeights),", + " width: terrainWorld.extent.sizeX,", + "});", + "const terrainAnchor = new TerrainAnchor();", + "terrainAnchor.position.set(terrainWorld.extent.minX + terrainWorld.extent.sizeX / 2, 0, terrainWorld.extent.minZ + terrainWorld.extent.sizeZ / 2);", + "const terrainBody = new TerrainRigidBody({", + ' entity: "terrain",', + " object: terrainAnchor,", + " physics: ctx.physics,", + " shape: TerrainCollisionShape.heightfield(terrainField.rows, terrainField.columns, terrainField.toColliderHeights(), { x: terrainWorld.extent.sizeX, y: 1, z: terrainWorld.extent.sizeZ }),", + ' type: "fixed",', + "});", + "let world: TerrainWorldCells | undefined;", + "let terrainSurface: Awaited> | undefined;", + "// The anchor owns the streamed world, caller-supplied surface and full-world collider.", + 'terrainAnchor.addEventListener("removed", () => { world?.dispose(); terrainSurface?.dispose(); terrainBody.dispose(); });', + "ctx.add(terrainAnchor);", + "let terrainFollow = terrainAnchor;", + 'let terrainInvalidate: NonNullable["invalidate"];', + "try {", + ` terrainSurface = await loadTerrainSplat({ assets: ctx.assets, renderer: ctx.renderer, url: ${JSON.stringify(worldUrl)} });`, + " world = await TerrainWorldCells.load({", + ` assets: ctx.assets, url: ${JSON.stringify(worldUrl)}, surface: terrainSurface,`, + " follow: { get position() { return terrainFollow.position; } },", + ` ring: ${ring}, budgets: ${JSON.stringify(budgets)}, terrain: { streamRadius: ${ring + 1} },`, + " shadows: { cast: true, receive: true, invalidate: (region) => terrainInvalidate?.(region) },", + "});", + " ctx.add(world);", + "} catch (error) { terrainAnchor.removeFromParent(); throw error; }", + 'const loadCells = (follow: TerrainAnchor, invalidate?: NonNullable["invalidate"]) => { terrainFollow = follow; terrainInvalidate = invalidate; };', + "// Call loadCells(player.mesh) after creating the player; optionally forward streamed shadow invalidation.", + ].join("\n"), + }); +} diff --git a/packages/terrain/src/mcp/glb-stats.ts b/packages/terrain/src/mcp/glb-stats.ts new file mode 100644 index 0000000000..e814d0a455 --- /dev/null +++ b/packages/terrain/src/mcp/glb-stats.ts @@ -0,0 +1,90 @@ +const GLB_MAGIC = 0x46546c67; +const JSON_CHUNK = 0x4e4f534a; +const INSTANCING = "EXT_mesh_gpu_instancing"; +// Primitive modes that draw triangles: TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN. +const TRIANGLE_MODES = new Set([4, 5, 6]); + +interface IGltfPrimitive { + attributes: Record; + indices?: number; + mode?: number; +} + +interface IGltfNode { + children?: number[]; + mesh?: number; + extensions?: Record } | undefined>; +} + +interface IGltfJson { + accessors?: { count: number }[]; + meshes?: { primitives: IGltfPrimitive[] }[]; + nodes?: IGltfNode[]; + scene?: number; + scenes?: { nodes?: number[] }[]; +} + +/** What the default scene of a GLB puts on screen, counted from the file itself. */ +export interface IGlbStats { + /** Primitives drawn, an instanced primitive counting once however many instances it has. */ + draws: number; + /** Instances declared by `EXT_mesh_gpu_instancing` nodes. */ + instances: number; + /** Triangles of the default scene with every instance counted in full, before LOD and culling. */ + triangles: number; +} + +function jsonChunk(bytes: Uint8Array): IGltfJson { + const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + if (bytes.byteLength < 20 || view.getUint32(0, true) !== GLB_MAGIC) { + throw new Error("glb-stats: not a GLB, the glTF binary header is missing."); + } + const length = view.getUint32(12, true); + if (view.getUint32(16, true) !== JSON_CHUNK || 20 + length > bytes.byteLength) { + throw new Error("glb-stats: the first GLB chunk is not a complete JSON chunk."); + } + return JSON.parse(new TextDecoder().decode(bytes.subarray(20, 20 + length))) as IGltfJson; +} + +/** + * Counts the default scene of a GLB. Only the JSON chunk is read: every count lives in an accessor + * header, so no buffer is decoded. Alternate scenes (the LOD levels a cook writes beside the + * default one) are not counted, because a loader draws one scene. + */ +export function measureGlb(bytes: Uint8Array): IGlbStats { + const gltf = jsonChunk(bytes); + const nodes = gltf.nodes ?? []; + const accessorCount = (index: number | undefined): number => { + const count = index === undefined ? undefined : gltf.accessors?.[index]?.count; + if (count === undefined) throw new Error(`glb-stats: accessor ${index} is missing.`); + return count; + }; + const stats: IGlbStats = { draws: 0, instances: 0, triangles: 0 }; + const seen = new Set(); + const visit = (index: number): void => { + const node = nodes[index]; + if (node === undefined) throw new Error(`glb-stats: node ${index} is missing.`); + if (seen.has(index)) return; + seen.add(index); + if (node.mesh !== undefined) { + const mesh = gltf.meshes?.[node.mesh]; + if (mesh === undefined) throw new Error(`glb-stats: mesh ${node.mesh} is missing.`); + const attributes = node.extensions?.[INSTANCING]?.attributes; + const copies = attributes === undefined ? 1 : accessorCount(Object.values(attributes)[0]); + if (attributes !== undefined) stats.instances += copies; + for (const primitive of mesh.primitives) { + stats.draws += 1; + if (!TRIANGLE_MODES.has(primitive.mode ?? 4)) continue; + const vertices = accessorCount(primitive.indices ?? primitive.attributes.POSITION); + stats.triangles += + (primitive.mode === 5 || primitive.mode === 6 + ? Math.max(vertices - 2, 0) + : Math.floor(vertices / 3)) * copies; + } + } + for (const child of node.children ?? []) visit(child); + }; + const scene = gltf.scenes?.[gltf.scene ?? 0]; + for (const root of scene?.nodes ?? (gltf.scenes === undefined ? nodes.keys() : [])) visit(root); + return stats; +} diff --git a/packages/terrain/src/mcp/server.ts b/packages/terrain/src/mcp/server.ts new file mode 100644 index 0000000000..14c15ea037 --- /dev/null +++ b/packages/terrain/src/mcp/server.ts @@ -0,0 +1,127 @@ +#!/usr/bin/env node +import { realpathSync } from "node:fs"; +import { rm } from "node:fs/promises"; +import { join, relative, resolve } from "node:path"; +import { createInterface } from "node:readline"; +import { fileURLToPath } from "node:url"; +import { IsolatedKillError, runIsolated } from "./child.js"; +import { + TERRAIN_TOOLS, + parseGenerateInput, + parsePatchInput, + patchRecipe, + resolveAssetSource, + resolveOutput, +} from "./tools.js"; + +const SERVER_NAME = "threenative-terrain"; +const SERVER_VERSION = "0.1.0"; +// The cook must finish inside the 60 s cold budget (PRD-584 AC-2). The child is killed past it. +const COOK_TIMEOUT_MS = 60_000; +const COOK_CHILD = fileURLToPath(new URL("./cook-child.js", import.meta.url)); + +interface IRpcRequest { + id?: number | string | null; + method?: string; + params?: Record; +} + +function reply(id: IRpcRequest["id"], result: unknown): string { + return JSON.stringify({ id, jsonrpc: "2.0", result }); +} + +function fail(id: IRpcRequest["id"], code: number, message: string): string { + return JSON.stringify({ id, jsonrpc: "2.0", error: { code, message } }); +} + +function toolResult(text: string, isError = false) { + return { content: [{ type: "text", text }], isError }; +} + +/** Runs one tool. A rejected argument or a failed cook becomes a tool error result, not a crash. */ +async function callTool(name: string, args: unknown): Promise> { + try { + if (name === "terrain_generate") { + const input = parseGenerateInput(args); + const projectRoot = process.cwd(); + const assetSource = await resolveAssetSource(projectRoot); + const out = await resolveOutput( + projectRoot, + input.out ?? relative(projectRoot, join(assetSource, "worlds", input.kit)), + ); + const child = await runIsolated({ + command: process.execPath, + // Inherits the parent's loader flags, so the same TypeScript runtime starts the child. + args: [ + ...process.execArgv, + COOK_CHILD, + JSON.stringify({ ...input, out, projectRoot, assetSource }), + ], + timeoutMs: COOK_TIMEOUT_MS, + }).catch(async (error: unknown) => { + // A killed cook cannot clean up after itself; `resolveOutput` proved `out` was new, so it is ours. + if (error instanceof IsolatedKillError) await rm(out, { recursive: true, force: true }); + throw error; + }); + // The cook library also prints progress lines; the child's summary is always its last line. + return toolResult(child.stdout.trim().split("\n").at(-1) ?? ""); + } + if (name === "terrain_patch") { + const { recipe, commands } = parsePatchInput(args); + return toolResult(JSON.stringify(patchRecipe(recipe, commands))); + } + throw new TypeError(`Unknown tool '${name}'.`); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + // A child's uncaught error arrives with a stack; the `Error: ...` line is what the agent can act on. + return toolResult(message.match(/^\w*Error: .*$/m)?.[0] ?? message, true); + } +} + +/** Answers one JSON-RPC line. Notifications (no id) return `undefined`. */ +export async function handleLine(line: string): Promise { + let request: IRpcRequest; + try { + request = JSON.parse(line) as IRpcRequest; + } catch { + return fail(null, -32700, "Parse error."); + } + const { id, method, params } = request; + if (method === undefined) return fail(id ?? null, -32600, "Invalid request: no method."); + if (id === undefined) return undefined; + if (method === "initialize") { + return reply(id, { + protocolVersion: + typeof params?.protocolVersion === "string" ? params.protocolVersion : "2025-06-18", + capabilities: { tools: {} }, + serverInfo: { name: SERVER_NAME, version: SERVER_VERSION }, + }); + } + if (method === "tools/list") return reply(id, { tools: TERRAIN_TOOLS }); + if (method === "tools/call") { + const name = params?.name; + if (typeof name !== "string") return fail(id, -32602, "tools/call needs a tool name."); + return reply(id, await callTool(name, params?.arguments ?? {})); + } + return fail(id, -32601, `Method not found: ${method}.`); +} + +/** Serves stdio until stdin closes. Each request is answered as soon as it finishes. */ +export function runServer(): void { + const lines = createInterface({ input: process.stdin, crlfDelay: Number.POSITIVE_INFINITY }); + lines.on("line", (line) => { + if (line.trim() === "") return; + handleLine(line).then((response) => { + if (response !== undefined) process.stdout.write(`${response}\n`); + }); + }); +} + +// Started by the `bin`, or by the core shim, which sets argv[1] to this file before importing it. +const entry = process.argv[1]; +if ( + entry !== undefined && + realpathSync(resolve(entry)) === realpathSync(fileURLToPath(import.meta.url)) +) { + runServer(); +} diff --git a/packages/terrain/src/mcp/tools.ts b/packages/terrain/src/mcp/tools.ts new file mode 100644 index 0000000000..59316583ff --- /dev/null +++ b/packages/terrain/src/mcp/tools.ts @@ -0,0 +1,306 @@ +import { lstat, readdir, realpath } from "node:fs/promises"; +import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; +import { OPERATION_TYPES } from "../core/operations.js"; +import { Terrain } from "../core/terrain.js"; +import type { LayerPatch, PatchCommand } from "../core/terrain.js"; +import type { ITerrainDocument, Layer } from "../core/types.js"; + +/** The starter kits, one folder each under the package's `starter/`. */ +export const TERRAIN_KITS = ["forest", "coastal", "alpine", "desert", "tundra"] as const; + +export type TerrainKit = (typeof TERRAIN_KITS)[number]; + +export interface IGenerateInput { + kit: TerrainKit; + sizeMeters?: number; + seed?: number; + out?: string; +} + +const PATCH_OPS: readonly string[] = ["upsert", "update", "remove", "move", "toggle"]; + +export const TERRAIN_TOOLS = [ + { + name: "terrain_generate", + description: + "Choose from a request or explicit kit and cook one editable starter terrain kit (forest, coastal, alpine, desert or tundra) into a world.glb. Returns the kit as recipe, the world.glb, recipe.json and world.json paths, triangles, instances and draws measured from the produced GLB's default scene, and a ctx.assets.model load snippet. triangles counts every instance of the LOD0 default scene in full, before distance LOD and culling, instances sums the EXT_mesh_gpu_instancing instances, and draws counts primitives across instanced and non-instanced meshes, an instanced primitive counting once.", + inputSchema: { + type: "object", + properties: { + kit: { type: "string", enum: [...TERRAIN_KITS] }, + request: { type: "string", minLength: 1 }, + sizeMeters: { type: "number", exclusiveMinimum: 0 }, + seed: { type: "integer" }, + out: { type: "string", description: "Relative directory that must not exist yet." }, + }, + anyOf: [{ required: ["request"] }, { required: ["kit"] }], + additionalProperties: false, + }, + }, + { + name: "terrain_patch", + description: + "Apply editor commands (upsert, update, remove, move, toggle) to a terrain recipe and return the edited recipe.", + inputSchema: { + type: "object", + properties: { + recipe: { type: "object" }, + commands: { type: "array", items: { type: "object" } }, + }, + required: ["recipe", "commands"], + additionalProperties: false, + }, + }, +] as const; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +/** Rejects any field the tool does not declare, so a typo never falls back to a default. */ +function closedRecord( + input: unknown, + tool: string, + fields: readonly string[], +): Record { + if (!isRecord(input)) throw new TypeError(`${tool}: arguments must be an object.`); + for (const key of Object.keys(input)) { + if (!fields.includes(key)) throw new TypeError(`${tool}: unknown field '${key}'.`); + } + return input; +} + +function relativeDirectory(out: unknown): string { + if ( + typeof out !== "string" || + out.trim() === "" || + isAbsolute(out) || + out.split(/[\\/]/).includes("..") + ) { + throw new TypeError("terrain_generate: out must be a relative directory inside the project."); + } + return out; +} + +const GENERATE_FIELDS = ["request", "kit", "sizeMeters", "seed", "out"]; + +export function parseGenerateInput(input: unknown): IGenerateInput { + const args = closedRecord(input, "terrain_generate", GENERATE_FIELDS); + const { request, sizeMeters, seed, out } = args; + if (request !== undefined && (typeof request !== "string" || request.trim() === "")) { + throw new TypeError("terrain_generate: request must be a non-empty string."); + } + const words = typeof request === "string" ? request.toLowerCase().split(/[^a-z]+/u) : []; + const synonyms = [ + ["forest", "pine", "pines", "woods", "woodland"], + ["coastal", "beach", "coast", "sea", "ocean"], + ["alpine", "mountain", "mountains", "snowy", "peaks"], + ["desert", "dune", "dunes", "sand"], + ["tundra", "arctic", "ice"], + ]; + const kit = + args.kit !== undefined + ? args.kit + : TERRAIN_KITS[synonyms.findIndex((names) => names.some((name) => words.includes(name)))]; + if (typeof kit !== "string" || !(TERRAIN_KITS as readonly string[]).includes(kit)) { + throw new TypeError(`terrain_generate: kit must be one of ${TERRAIN_KITS.join(", ")}.`); + } + const parsed: IGenerateInput = { kit: kit as TerrainKit }; + if (sizeMeters !== undefined) { + if (typeof sizeMeters !== "number" || !Number.isFinite(sizeMeters) || sizeMeters <= 0) { + throw new TypeError("terrain_generate: sizeMeters must be a positive finite number."); + } + parsed.sizeMeters = sizeMeters; + } + if (seed !== undefined) { + if (typeof seed !== "number" || !Number.isSafeInteger(seed)) { + throw new TypeError("terrain_generate: seed must be a safe integer."); + } + parsed.seed = seed; + } + if (out !== undefined) parsed.out = relativeDirectory(out); + return parsed; +} + +/** + * Resolves `out` under `root` and fails closed unless the real path stays inside the real root. + * The deepest existing ancestor is resolved through `realpath`, so a symlinked `out` or a symlinked + * ancestor that leaves the root is rejected, as is a dangling link. Returns the verified real path. + */ +export async function resolveOutput(root: string, out: string): Promise { + const realRoot = await realpath(root); + let existing = resolve(realRoot, out); + const tail: string[] = []; + for (;;) { + try { + existing = await realpath(existing); + break; + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + // ENOENT on a path that lstat finds is a dangling symlink, which a mkdir would follow. + if ( + await lstat(existing).then( + () => true, + () => false, + ) + ) { + throw new Error(`terrain_generate: out '${out}' passes through a dangling symlink.`); + } + tail.unshift(basename(existing)); + existing = dirname(existing); + } + } + const real = join(existing, ...tail); + const inside = relative(realRoot, real); + if (inside === "" || inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside)) { + throw new Error(`terrain_generate: out '${out}' resolves outside the project.`); + } + await assertOutputFree(real); + return real; +} + +/** Fails closed when the output directory already exists, so a cook never replaces files. */ +export async function assertOutputFree(dir: string): Promise { + const entries = await readdir(dir).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return undefined; + throw error; + }); + if (entries !== undefined) { + throw new Error(`terrain_generate: out '${dir}' already exists; choose a new directory.`); + } +} + +function isTerrainDocument(input: unknown): input is ITerrainDocument { + Terrain.fromJSON(JSON.stringify(input)); + return true; +} + +function isLayer(input: unknown): input is Layer { + isTerrainDocument({ version: 1, config: new Terrain().config, layers: [input] }); + return true; +} + +function isLayerPatch( + patch: Record, +): patch is Record & LayerPatch { + if (patch.mask !== undefined && patch.mask !== null) { + isLayer({ id: "validation", type: "noise", params: {}, mask: patch.mask }); + } + return ( + (patch.type === undefined || OPERATION_TYPES.some((type) => type === patch.type)) && + (patch.name === undefined || typeof patch.name === "string") && + (patch.enabled === undefined || typeof patch.enabled === "boolean") && + (patch.opacity === undefined || + (typeof patch.opacity === "number" && Number.isFinite(patch.opacity))) && + (patch.params === undefined || isRecord(patch.params)) + ); +} + +function parseCommand(command: unknown, index: number): PatchCommand { + if (!isRecord(command) || !PATCH_OPS.includes(command.op as string)) { + throw new TypeError( + `terrain_patch: command ${index} op must be one of ${PATCH_OPS.join(", ")}.`, + ); + } + if (command.op === "upsert") { + if (!isRecord(command.layer)) { + throw new TypeError(`terrain_patch: command ${index} needs a layer object.`); + } + if (!isLayer(command.layer)) { + throw new TypeError(`terrain_patch: command ${index} needs a valid layer.`); + } + return { ...command, op: "upsert", layer: command.layer }; + } + if (typeof command.id !== "string") { + throw new TypeError(`terrain_patch: command ${index} needs a layer id.`); + } + const id = command.id; + switch (command.op) { + case "update": { + if (!isRecord(command.patch) || !isLayerPatch(command.patch)) { + throw new TypeError(`terrain_patch: command ${index} needs a valid patch object.`); + } + return { ...command, op: "update", id, patch: command.patch }; + } + case "move": + if (typeof command.index !== "number" || !Number.isInteger(command.index)) { + throw new TypeError(`terrain_patch: command ${index} needs an integer index.`); + } + return { ...command, op: "move", id, index: command.index }; + case "toggle": + if (command.enabled !== undefined && typeof command.enabled !== "boolean") { + throw new TypeError(`terrain_patch: command ${index} enabled must be a boolean.`); + } + return { ...command, op: "toggle", id }; + case "remove": + return { ...command, op: "remove", id }; + default: + throw new TypeError(`terrain_patch: command ${index} has an unknown op.`); + } +} + +export function parsePatchInput(input: unknown): { + recipe: ITerrainDocument; + commands: PatchCommand[]; +} { + const args = closedRecord(input, "terrain_patch", ["recipe", "commands"]); + if (!isRecord(args.recipe)) { + throw new TypeError("terrain_patch: recipe must be a terrain document object."); + } + if (!Array.isArray(args.commands)) { + throw new TypeError("terrain_patch: commands must be an array."); + } + if (!isTerrainDocument(args.recipe)) { + throw new TypeError("terrain_patch: recipe must be a valid terrain document."); + } + const commands = args.commands.map(parseCommand); + patchRecipe(args.recipe, commands); + return { recipe: args.recipe, commands }; +} + +/** Applies commands in one editor transaction and returns the edited recipe document. */ +export function patchRecipe( + recipe: ITerrainDocument, + commands: readonly PatchCommand[], +): ITerrainDocument { + return Terrain.fromJSON(recipe).applyPatch(commands).toJSON() as ITerrainDocument; +} + +/** Read the same TypeScript config the game builds, using its installed Vite loader. */ +export async function resolveAssetSource(projectRoot: string): Promise { + const configFile = join(projectRoot, "threenative.config.ts"); + const exists = await lstat(configFile).then( + () => true, + (error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return false; + throw error; + }, + ); + let source: unknown = "assets"; + if (exists) { + const { loadConfigFromFile } = await import("vite"); + const loaded = await loadConfigFromFile( + { command: "build", mode: "production" }, + configFile, + projectRoot, + ); + const assets: unknown = (loaded?.config as Record | undefined)?.assets; + if (assets !== undefined) { + if (!isRecord(assets)) throw new TypeError("terrain_generate: assets must be an object."); + source = assets.source === undefined ? "assets" : assets.source; + } + } + if (typeof source !== "string" || source.trim() === "") { + throw new TypeError("terrain_generate: assets.source must be a non-empty path."); + } + const configuredRoot = resolve(projectRoot, source); + const sourceRoot = await realpath(configuredRoot).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return configuredRoot; + throw error; + }); + const inside = relative(projectRoot, sourceRoot); + if (inside === "" || inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside)) { + throw new TypeError("terrain_generate: assets.source must be inside the project."); + } + return sourceRoot; +} diff --git a/packages/terrain/src/starter.ts b/packages/terrain/src/starter.ts new file mode 100644 index 0000000000..a2d8b05912 --- /dev/null +++ b/packages/terrain/src/starter.ts @@ -0,0 +1,375 @@ +import type { IComputeDriven, ICtx } from "@threenative/core"; +import { + Heightfield, + type IWorldCellsLoadOptions, + type IWorldPackage, + WorldCells, + heightSamplerFromHeightmap, + loadWorldHeightmap, +} from "@threenative/core/world"; +import { CollisionShape3D, type IPhysicsContext, RigidBody3D } from "@threenative/physics"; +import { + type Color, + type Material, + type Mesh, + type MeshStandardMaterial, + Object3D, + type Texture, + Vector3, +} from "three"; +import { HDRLoader } from "three/addons/loaders/HDRLoader.js"; + +export type StarterContext = ICtx, IPhysicsContext>; +export type StarterColliders = Readonly< + Record +>; +export interface IStarterWorld { + readonly world: WorldCells; + readonly ground: RigidBody3D; + readonly colliders: PropColliders; + readonly field: Heightfield; + readonly placements: Float32Array; + readonly extent: IWorldPackage["extent"]; +} +export interface IStarterWorldSpec { + readonly name: string; + readonly url: string; + readonly colliders: StarterColliders; + readonly colliderReach: number; + readonly loadSurface: (ctx: StarterContext, url: string) => Promise; + readonly lighting: { + readonly sky: string; + readonly lods: boolean; + readonly dress: ( + mesh: Mesh, + sky: Texture, + id: string, + originals: ReadonlyMap, + ) => void; + }; + readonly streaming: Pick< + IWorldCellsLoadOptions, + "ring" | "impostors" | "gpuScene" | "shadows" | "budgets" | "terrain" + >; + readonly water?: ( + ctx: StarterContext, + file: unknown, + field: Heightfield, + manifest: IWorldPackage, + heightmap: Uint16Array, + ) => Extra; +} + +/** Load caller-authored starter data with one scene lifetime; all appearance comes from spec. + * @situation attach a baked starter world with game-owned surfaces, prop dressing and water + * @constraint Runtime subpath; the game supplies every appearance and collision choice + * @requires npm i -D @threenative/terrain + * @example await addStarterWorld(ctx, follow, spec); + */ +export async function addStarterWorld( + ctx: ICtx, IPhysicsContext>, + follow: Object3D, + spec: IStarterWorldSpec, + url = spec.url, + whileCurrent: () => boolean = () => true, +): Promise { + const assertCurrent = () => { + if (whileCurrent()) return; + const error = new Error(`${spec.name} world: its scene is no longer current.`); + error.name = "AbortError"; + throw error; + }; + let surface: Material | undefined; + let releaseLighting: (() => void) | undefined; + let world: WorldCells | undefined; + let ground: RigidBody3D | undefined; + let colliders: PropColliders | undefined; + let extra: Extra | undefined; + let released = false; + const dispose = () => { + if (released) return; + released = true; + colliders?.detach(); + colliders?.removeFromParent(); + ground?.dispose(); + ground?.object?.removeFromParent(); + extra?.dispose(); + world?.dispose(); + surface?.dispose(); + releaseLighting?.(); + }; + try { + assertCurrent(); + const manifest = (await ( + await fetchAsset(ctx, url, assertCurrent, spec.name) + ).json()) as IWorldPackage; + assertCurrent(); + const base = url.slice(0, url.lastIndexOf("/") + 1); + const records = await ( + await fetchAsset(ctx, base + manifest.placements, assertCurrent, spec.name) + ).arrayBuffer(); + assertCurrent(); + const [heightmapUrl] = await ctx.assets.resolve(base + manifest.terrain.heightmap); + assertCurrent(); + if (heightmapUrl === undefined) + throw new Error(`${spec.name} world: its heightmap is not served.`); + const heightmap = await loadWorldHeightmap(heightmapUrl); + assertCurrent(); + surface = await spec.loadSurface(ctx, url); + assertCurrent(); + releaseLighting = await lightProps(ctx, url, manifest, assertCurrent, spec); + assertCurrent(); + const options: IWorldCellsLoadOptions = { + url, + assets: ctx.assets, + data: { manifest, placements: records, heightmap }, + surface, + follow, + ...spec.streaming, + }; + world = await WorldCells.load(options); + assertCurrent(); + const field = starterField(manifest, heightmap); + ground = groundCollider(ctx, manifest, field); + if (spec.water) { + const file = await ( + await fetchAsset(ctx, `${base}water.json`, assertCurrent, spec.name) + ).json(); + assertCurrent(); + extra = spec.water(ctx, file, field, manifest, heightmap); + assertCurrent(); + } + const props = new Float32Array(records); + colliders = new PropColliders( + ctx, + follow, + manifest, + props, + spec.colliders, + spec.colliderReach, + spec.name, + ); + ctx.add(world); + if (ground.object !== undefined) ctx.add(ground.object); + ctx.add(colliders); + ctx.entities.add(`${spec.name.toLowerCase()}-world.${world.uuid}`, { dispose }); + return { world, ground, colliders, field, placements: props, extent: manifest.extent, extra }; + } catch (error) { + dispose(); + throw error; + } +} + +async function lightProps( + ctx: ICtx, IPhysicsContext>, + url: string, + manifest: IWorldPackage, + assertCurrent: () => void, + spec: IStarterWorldSpec<{ dispose(): void }>, +): Promise<() => void> { + const base = url.slice(0, url.lastIndexOf("/") + 1); + const [skyUrl] = await ctx.assets.resolve(`${base}${spec.lighting.sky}`); + assertCurrent(); + if (skyUrl === undefined) + throw new Error(`${spec.name} world: '${spec.lighting.sky}' is not served.`); + const sky = await new HDRLoader().loadAsync(skyUrl); + const originals = new Map< + MeshStandardMaterial, + { color: Color; envMap: Texture | null; intensity: number } + >(); + const dispose = () => { + for (const [material, original] of originals) { + if (material.envMap !== sky) continue; + material.envMap = original.envMap; + material.envMapIntensity = original.intensity; + material.color.copy(original.color); + } + sky.dispose(); + }; + try { + assertCurrent(); + // Every LOD of an asset, not only its nearest: a far boulder left undressed shows its raw scan. + const models = Object.entries(manifest.assets).flatMap(([id, asset]) => + [asset.glb, ...(spec.lighting.lods ? (asset.lods ?? []).map((lod) => lod.glb) : [])].map( + (glb) => ({ id, glb }), + ), + ); + for (const { id, glb } of models) { + const model = await ctx.assets.model<{ scene: Object3D }>(base + glb); + assertCurrent(); + model.scene.traverse((object) => { + const mesh = object as Mesh; + for (const material of [mesh.material ?? []].flat() as MeshStandardMaterial[]) + if (!originals.has(material)) + originals.set(material, { + color: material.color.clone(), + envMap: material.envMap, + intensity: material.envMapIntensity, + }); + spec.lighting.dress(mesh, sky, id, originals); + }); + } + return dispose; + } catch (error) { + dispose(); + throw error; + } +} + +function starterField(manifest: IWorldPackage, heightmap: Uint16Array): Heightfield { + const { extent, terrain } = manifest; + return Heightfield.fromSampler({ + columns: terrain.columns, + rows: terrain.rows, + width: extent.sizeX, + depth: extent.sizeZ, + origin: { x: extent.minX + extent.sizeX / 2, z: extent.minZ + extent.sizeZ / 2 }, + sampleHeight: heightSamplerFromHeightmap(terrain, extent, heightmap), + }); +} + +function groundCollider( + ctx: ICtx, IPhysicsContext>, + manifest: IWorldPackage, + field: Heightfield, +): RigidBody3D { + const { extent } = manifest; + const anchor = new Object3D(); + anchor.position.set(extent.minX + extent.sizeX / 2, 0, extent.minZ + extent.sizeZ / 2); + return new RigidBody3D({ + object: anchor, + physics: ctx.physics, + type: "fixed", + entity: "terrain", + shape: CollisionShape3D.heightfield(field.rows, field.columns, field.toColliderHeights(), { + x: extent.sizeX, + y: 1, + z: extent.sizeZ, + }), + }); +} + +/** Stream caller-specified fixed collision shapes around a moving follow object. + * @situation attach nearby prop collision bodies from baked placement records + * @constraint Shapes and reach are supplied by the game; detach releases every body + * @requires npm i -D @threenative/terrain + * @example new PropColliders(ctx, follow, manifest, records, shapes, reach, name); + */ +export class PropColliders extends Object3D implements IComputeDriven { + readonly warmupNodes: readonly unknown[] = []; + readonly processCadence = "render" as const; + readonly #live = new Map(); + readonly #runs: { asset: string; offset: number; count: number }[] = []; + readonly #at = { x: Number.NaN, z: Number.NaN }; + readonly #followPosition = new Vector3(); + #released = false; + + constructor( + private readonly ctx: ICtx, IPhysicsContext>, + private readonly follow: Object3D, + manifest: IWorldPackage, + private readonly records: Float32Array, + private readonly shapes: StarterColliders, + private readonly reach: number, + name: string, + ) { + super(); + this.name = `${name.toLowerCase()}-prop-colliders`; + for (const cell of manifest.cells) + for (const run of cell.runs) if (this.shapes[run.asset]) this.#runs.push(run); + try { + this.#rebuild(); + } catch (error) { + this.detach(); + throw error; + } + } + + /** How many prop bodies exist right now. */ + get active(): number { + return this.#live.size; + } + + get released(): boolean { + return this.#released; + } + + attachRenderer(): void {} + + process(): void { + if (this.#released) return; + const p = this.follow.getWorldPosition(this.#followPosition); + if (Math.hypot(p.x - this.#at.x, p.z - this.#at.z) > 4 || Number.isNaN(this.#at.x)) + this.#rebuild(); + } + + detach(): void { + for (const body of this.#live.values()) body.dispose(); + this.#live.clear(); + this.#released = true; + } + + #rebuild(): void { + const { x: fx, z: fz } = this.follow.getWorldPosition(this.#followPosition); + this.#at.x = fx; + this.#at.z = fz; + const keep = new Set(); + for (const run of this.#runs) { + const shape = this.shapes[run.asset]; + if (!shape) continue; + for (let i = run.offset; i < run.offset + run.count; i++) { + const x = this.records[i * 8] as number; + const z = this.records[i * 8 + 2] as number; + if (Math.hypot(x - fx, z - fz) > this.reach) continue; + keep.add(i); + if (this.#live.has(i)) continue; + const { lift, collider } = colliderAt(shape, this.records[i * 8 + 7] as number); + this.#live.set( + i, + new RigidBody3D({ + physics: this.ctx.physics, + type: "fixed", + entity: `${run.asset}.${String(i)}`, + position: { x, y: (this.records[i * 8 + 1] as number) + lift, z }, + shape: collider, + }), + ); + } + } + for (const [i, body] of this.#live) + if (!keep.has(i)) { + body.dispose(); + this.#live.delete(i); + } + } +} + +/** One table row at one placement's scale; a capsule stands on the origin, a sphere sits on it. */ +function colliderAt( + shape: NonNullable, + scale: number, +): { lift: number; collider: CollisionShape3D } { + if ("sphere" in shape) + return { lift: 0, collider: CollisionShape3D.sphere(shape.sphere * scale) }; + const [halfHeight, radius] = shape.capsule; + return { + lift: (halfHeight + radius) * scale, + collider: CollisionShape3D.capsule(halfHeight * scale, radius * scale), + }; +} + +async function fetchAsset( + ctx: ICtx, IPhysicsContext>, + path: string, + assertCurrent: () => void, + name: string, +): Promise { + const candidates = await ctx.assets.resolve(path); + assertCurrent(); + for (const candidate of candidates) { + const response = await fetch(candidate); + assertCurrent(); + if (response.ok) return response; + } + throw new Error(`${name} world: '${path}' is not served.`); +} diff --git a/packages/terrain/starter-assets.json b/packages/terrain/starter-assets.json new file mode 100644 index 0000000000..c32cba5349 --- /dev/null +++ b/packages/terrain/starter-assets.json @@ -0,0 +1,6 @@ +{ + "version": "0.1.0", + "url": "https://github.com/ThreeNativeHQ/threenative-engine/releases/download/terrain-starter-assets-v0.1.0/terrain-starter-assets-0.1.0.tar.gz", + "sha256": "3062b90cd7f84e6ff14f6a96de64ad406eeba30c55c95cc045b6219edf71f5b0", + "bytes": 31598616 +} diff --git a/packages/terrain/starter-assets/credits.json b/packages/terrain/starter-assets/credits.json index 67716e65d3..b550e17e68 100644 --- a/packages/terrain/starter-assets/credits.json +++ b/packages/terrain/starter-assets/credits.json @@ -88,14 +88,6 @@ "metresPerTile": null, "note": "The lichen-crusted weathered boulder set. Seam 11.5 against an interior 12.1: tileable. Distinct from the existing mossy_rock set, which is moss rather than lichen, so a boulder bed does not read as one surface twice." }, - "forest_leaves_02": { - "asset": "Forest Leaves 02", - "author": "Rob Tuytel (All)", - "license": "CC0", - "groundLayer": null, - "metresPerTile": null, - "note": "Leaf litter and forest floor for the ground under trees. Seam 19.7 against an interior 17.2: tileable. metresPerTile is deliberately null: the tiling is the terrain render layer's choice, not this folder's." - }, "river_small_rocks": { "asset": "River Small Rocks", "author": "Rob Tuytel (Photography, Processing), Rico Cilliers (Minor adjustment)", @@ -111,14 +103,6 @@ "groundLayer": null, "metresPerTile": null, "note": "Alpha-cut card atlas only (diffuse, alpha, OpenGL normal at 512 px). The upstream plant meshes are not shipped: cut to the starter budget they kept one blade or frond of each plant, so the game builds its own cards on this atlas." - }, - "grass_medium_02": { - "asset": "Grass Medium 02", - "author": "Rob Tuytel (photography), Rico Cilliers (modeling)", - "license": "CC0", - "groundLayer": null, - "metresPerTile": null, - "note": "Alpha-cut card atlas only (diffuse, alpha, OpenGL normal at 512 px). The upstream plant meshes are not shipped: cut to the starter budget they kept one blade or frond of each plant, so the game builds its own cards on this atlas." } }, "files": [ @@ -650,54 +634,6 @@ "sampledByTemperateGround": false, "sha256": "cccadfc7b806a15bd396cc433175c80fdb386009674726a8a17e48ba50ce50cb" }, - { - "set": "forest_leaves_02", - "asset": "Forest Leaves 02", - "author": "Rob Tuytel (All)", - "license": "CC0", - "map": "diff", - "file": "forest_leaves_02/forest_leaves_02_diff_512.jpg", - "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/forest_leaves_02/forest_leaves_02_diffuse_1k.jpg", - "bytes": 131897, - "dimensions": "512x512", - "colourSpace": "srgb", - "metresPerTile": null, - "groundLayer": null, - "sampledByTemperateGround": false, - "sha256": "3041025c2bb5c57530d2e611618d8f8bcf75f54cd1a092f323fdc87520f6e2d0" - }, - { - "set": "forest_leaves_02", - "asset": "Forest Leaves 02", - "author": "Rob Tuytel (All)", - "license": "CC0", - "map": "nor_gl", - "file": "forest_leaves_02/forest_leaves_02_nor_gl_512.jpg", - "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/forest_leaves_02/forest_leaves_02_nor_gl_1k.jpg", - "bytes": 155946, - "dimensions": "512x512", - "colourSpace": "linear", - "metresPerTile": null, - "groundLayer": null, - "sampledByTemperateGround": false, - "sha256": "248afab4b44e078501a2363856e679909560a70587c1314a1c17f3f8063218ba" - }, - { - "set": "forest_leaves_02", - "asset": "Forest Leaves 02", - "author": "Rob Tuytel (All)", - "license": "CC0", - "map": "arm", - "file": "forest_leaves_02/forest_leaves_02_arm_512.jpg", - "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/forest_leaves_02/forest_leaves_02_arm_1k.jpg", - "bytes": 105808, - "dimensions": "512x512", - "colourSpace": "linear", - "metresPerTile": null, - "groundLayer": null, - "sampledByTemperateGround": false, - "sha256": "415b28f50e5bca6ef188dee4c9a66a4e2e6668bb909c310e0cb596f746f297cd" - }, { "set": "river_small_rocks", "asset": "River Small Rocks", @@ -782,42 +718,6 @@ "dimensions": [512, 512], "sha256": "72d64f5a9632cf5cc26969b8e60b1a5da5432bbde562b87a6fccef29949e28a8" }, - { - "set": "grass_medium_02", - "asset": "Grass Medium 02", - "author": "Rob Tuytel (photography), Rico Cilliers (modeling)", - "license": "CC0", - "map": "alpha", - "file": "grass_medium_02/grass_medium_02_alpha_512.png", - "url": "https://polyhaven.com/a/grass_medium_02", - "bytes": 24268, - "dimensions": [512, 512], - "sha256": "4f94afff57290dae13e02ea16742ec2fa446f7c596f71eb44da6e92f5b2e17ad" - }, - { - "set": "grass_medium_02", - "asset": "Grass Medium 02", - "author": "Rob Tuytel (photography), Rico Cilliers (modeling)", - "license": "CC0", - "map": "diff", - "file": "grass_medium_02/grass_medium_02_diff_512.jpg", - "url": "https://polyhaven.com/a/grass_medium_02", - "bytes": 26341, - "dimensions": [512, 512], - "sha256": "0df2de1764006ceebe633be49dcfaadab6b27b78c7c21f869b1c93f029b92a74" - }, - { - "set": "grass_medium_02", - "asset": "Grass Medium 02", - "author": "Rob Tuytel (photography), Rico Cilliers (modeling)", - "license": "CC0", - "map": "nor_gl", - "file": "grass_medium_02/grass_medium_02_nor_gl_512.jpg", - "url": "https://polyhaven.com/a/grass_medium_02", - "bytes": 16575, - "dimensions": [512, 512], - "sha256": "bc6c9eaf576fd396347c5ea5b3931907cb9ace0943f0159c20d4824c4b2fe0d0" - }, { "set": "kloofendal_48d_partly_cloudy", "asset": "Kloofendal 48d Partly Cloudy", diff --git a/packages/terrain/starter-assets/forest_leaves_02/forest_leaves_02_arm_512.jpg b/packages/terrain/starter-assets/forest_leaves_02/forest_leaves_02_arm_512.jpg deleted file mode 100644 index 4446b82535..0000000000 Binary files a/packages/terrain/starter-assets/forest_leaves_02/forest_leaves_02_arm_512.jpg and /dev/null differ diff --git a/packages/terrain/starter-assets/forest_leaves_02/forest_leaves_02_diff_512.jpg b/packages/terrain/starter-assets/forest_leaves_02/forest_leaves_02_diff_512.jpg deleted file mode 100644 index 8dab701268..0000000000 Binary files a/packages/terrain/starter-assets/forest_leaves_02/forest_leaves_02_diff_512.jpg and /dev/null differ diff --git a/packages/terrain/starter-assets/forest_leaves_02/forest_leaves_02_nor_gl_512.jpg b/packages/terrain/starter-assets/forest_leaves_02/forest_leaves_02_nor_gl_512.jpg deleted file mode 100644 index 9a8ebd282a..0000000000 Binary files a/packages/terrain/starter-assets/forest_leaves_02/forest_leaves_02_nor_gl_512.jpg and /dev/null differ diff --git a/packages/terrain/starter-assets/grass_medium_02/grass_medium_02_alpha_512.png b/packages/terrain/starter-assets/grass_medium_02/grass_medium_02_alpha_512.png deleted file mode 100644 index 21c35f26dc..0000000000 Binary files a/packages/terrain/starter-assets/grass_medium_02/grass_medium_02_alpha_512.png and /dev/null differ diff --git a/packages/terrain/starter-assets/grass_medium_02/grass_medium_02_diff_512.jpg b/packages/terrain/starter-assets/grass_medium_02/grass_medium_02_diff_512.jpg deleted file mode 100644 index 116f7bbcbc..0000000000 Binary files a/packages/terrain/starter-assets/grass_medium_02/grass_medium_02_diff_512.jpg and /dev/null differ diff --git a/packages/terrain/starter-assets/grass_medium_02/grass_medium_02_nor_gl_512.jpg b/packages/terrain/starter-assets/grass_medium_02/grass_medium_02_nor_gl_512.jpg deleted file mode 100644 index 5166e6c637..0000000000 Binary files a/packages/terrain/starter-assets/grass_medium_02/grass_medium_02_nor_gl_512.jpg and /dev/null differ diff --git a/packages/terrain/starter-credits.json b/packages/terrain/starter-credits.json new file mode 100644 index 0000000000..b550e17e68 --- /dev/null +++ b/packages/terrain/starter-credits.json @@ -0,0 +1,922 @@ +{ + "version": 1, + "purpose": "Prepared CC0 PBR surface maps for the five starter environments, Temperate first.", + "source": "Poly Haven", + "sourceUrl": "https://polyhaven.com", + "license": "CC0 1.0 Universal (public domain dedication)", + "licenseUrl": "https://polyhaven.com/license", + "redistribution": "permitted; attribution is not required and is recorded anyway", + "downloaded": "2026-10-01", + "resolution": "1K (1024x1024) JPEG, the upstream files byte for byte", + "preprocessing": "none. Every file is the upstream 1K JPG, verified byte-identical by sha256, at a dimension that is a multiple of 4 and at most 2048 so the same set can be recooked to BC7.", + "servedTo": "examples/strata-terrain-preview (vite publicDir), loaded through ctx.assets.texture()", + "cookedBytes": 23855553, + "cookedMiB": 22.75, + "cookedBudgetMiB": 25, + "sampledTexturesInGroundMaterial": 14, + "sampledTextureLimitPerStage": 16, + "sets": { + "leafy_grass": { + "asset": "Leafy Grass", + "author": "Charlotte Baglioni", + "license": "CC0", + "groundLayer": "grass", + "metresPerTile": 2.6, + "note": null + }, + "forest_ground_04": { + "asset": "Forest Ground 04", + "author": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "groundLayer": "dirt", + "metresPerTile": 3.4, + "note": null + }, + "cliff_side": { + "asset": "Cliff Side", + "author": "James Ray Cock, Jenelle van Heerden, Dario Barresi", + "license": "CC0", + "groundLayer": "rock", + "metresPerTile": 9.0, + "note": "The PRD shortlist names Sandstone Cracks for dry rock. Cliff Side is the same CC0 source's layered, sediment-banded rock face and is what the Temperate cliffs use; Sandstone Cracks stays unallocated for the Desert/Canyon starter." + }, + "mossy_rock": { + "asset": "Mossy Rock", + "author": "Poly Haven / Dario Barresi", + "license": "CC0", + "groundLayer": "moss", + "metresPerTile": 3.6, + "note": "Substituted for the shortlist's Rock Moss Set 02: that set's 1K diffuse is an atlas of seven rocks over black unused cells with starburst edges, not a tileable ground map. Mossy Rock (CC0, Poly Haven) is tileable, mossy and stony." + }, + "sand_01": { + "asset": "Sand 01", + "author": "Rob Tuytel", + "license": "CC0", + "groundLayer": "sand", + "metresPerTile": 3.2, + "note": "The 1K diffuse and GL normal map are seam-blended for tiling: each cross-fades its edges with a half-shifted copy. Diffuse left-right seam ratio 1.51 to 0.95; top-bottom 1.43 to 1.13. Only the edges changed; the source is Poly Haven, CC0." + }, + "snow_02": { + "asset": "Snow 02", + "author": "Rob Tuytel", + "license": "CC0", + "groundLayer": "snow", + "metresPerTile": 12.0, + "note": null + }, + "bark_brown_02": { + "asset": "Bark Brown 02", + "author": "Rob Tuytel", + "license": "CC0", + "groundLayer": null, + "metresPerTile": 1.6, + "note": "Spruce trunk bark. The shortlist names it for procedural tree bark; the Temperate starter uses it on the tapered spruce trunk, tiled 1.6 m up a 14 m trunk, and nowhere else." + }, + "rock_boulder_dry": { + "asset": "Rock Boulder Dry", + "author": "Dimitrios Savva (Photography), Rico Cilliers (Processing)", + "license": "CC0", + "groundLayer": null, + "metresPerTile": null, + "note": "The dry grey granite-ish boulder set. Its 1K diffuse is tileable stone, not an atlas: the seam column differs from the last by 5.7 grey levels against an interior neighbour difference of 7.5, so it wraps as well as the map averages." + }, + "lichen_rock": { + "asset": "Lichen Rock", + "author": "Rico Cilliers (All)", + "license": "CC0", + "groundLayer": null, + "metresPerTile": null, + "note": "The lichen-crusted weathered boulder set. Seam 11.5 against an interior 12.1: tileable. Distinct from the existing mossy_rock set, which is moss rather than lichen, so a boulder bed does not read as one surface twice." + }, + "river_small_rocks": { + "asset": "River Small Rocks", + "author": "Rob Tuytel (Photography, Processing), Rico Cilliers (Minor adjustment)", + "license": "CC0", + "groundLayer": null, + "metresPerTile": null, + "note": "The pebble set for river banks. Seam 12.6 against an interior 11.2: tileable. metresPerTile is deliberately null, as above." + }, + "fern_02": { + "asset": "Fern 02", + "author": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "groundLayer": null, + "metresPerTile": null, + "note": "Alpha-cut card atlas only (diffuse, alpha, OpenGL normal at 512 px). The upstream plant meshes are not shipped: cut to the starter budget they kept one blade or frond of each plant, so the game builds its own cards on this atlas." + } + }, + "files": [ + { + "set": "bark_brown_02", + "asset": "Bark Brown 02", + "author": "Rob Tuytel", + "license": "CC0", + "map": "diff", + "file": "bark_brown_02/bark_brown_02_diff_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/bark_brown_02/bark_brown_02_diff_1k.jpg", + "bytes": 659592, + "dimensions": "1024x1024", + "colourSpace": "srgb", + "metresPerTile": 1.6, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "920fa0bed0c9d78c1d530e99795113afd532d7db746de440f4a85d7c83ed0f1a" + }, + { + "set": "bark_brown_02", + "asset": "Bark Brown 02", + "author": "Rob Tuytel", + "license": "CC0", + "map": "nor_gl", + "file": "bark_brown_02/bark_brown_02_nor_gl_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/bark_brown_02/bark_brown_02_nor_gl_1k.jpg", + "bytes": 1015623, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 1.6, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "0d5e691e8ad8bcd093a3587887fda2a6b4a948b5708a4f256f7919584c6eb857" + }, + { + "set": "bark_brown_02", + "asset": "Bark Brown 02", + "author": "Rob Tuytel", + "license": "CC0", + "map": "rough", + "file": "bark_brown_02/bark_brown_02_rough_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/bark_brown_02/bark_brown_02_rough_1k.jpg", + "bytes": 337946, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 1.6, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "68125592de36e15bae7aa6db85c4d135aeb7e5653199ba226483a70f6d69837d" + }, + { + "set": "cliff_side", + "asset": "Cliff Side", + "author": "James Ray Cock, Jenelle van Heerden, Dario Barresi", + "license": "CC0", + "map": "ao", + "file": "cliff_side/cliff_side_ao_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/cliff_side/cliff_side_ao_1k.jpg", + "bytes": 412993, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 9.0, + "groundLayer": "rock", + "sampledByTemperateGround": true, + "sha256": "726a57a786c5130a8bcb7c78d81657b68652dd4ee424a7e4683225cfdbb00dfc" + }, + { + "set": "cliff_side", + "asset": "Cliff Side", + "author": "James Ray Cock, Jenelle van Heerden, Dario Barresi", + "license": "CC0", + "map": "diff", + "file": "cliff_side/cliff_side_diff_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/cliff_side/cliff_side_diff_1k.jpg", + "bytes": 859756, + "dimensions": "1024x1024", + "colourSpace": "srgb", + "metresPerTile": 9.0, + "groundLayer": "rock", + "sampledByTemperateGround": true, + "sha256": "5c6858541108fc34e5726ecf4d8d942d6301155c489e7341619260f2909570c5" + }, + { + "set": "cliff_side", + "asset": "Cliff Side", + "author": "James Ray Cock, Jenelle van Heerden, Dario Barresi", + "license": "CC0", + "map": "nor_gl", + "file": "cliff_side/cliff_side_nor_gl_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/cliff_side/cliff_side_nor_gl_1k.jpg", + "bytes": 1007837, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 9.0, + "groundLayer": "rock", + "sampledByTemperateGround": true, + "sha256": "28583860810a7d6d8ffddeaaff463408fc580338fd5a92c67b058207e75375a3" + }, + { + "set": "cliff_side", + "asset": "Cliff Side", + "author": "James Ray Cock, Jenelle van Heerden, Dario Barresi", + "license": "CC0", + "map": "rough", + "file": "cliff_side/cliff_side_rough_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/cliff_side/cliff_side_rough_1k.jpg", + "bytes": 422579, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 9.0, + "groundLayer": "rock", + "sampledByTemperateGround": true, + "sha256": "9d62079c5fc52d1a0dc659d2ad7f807e489dea18b5cb1a130bcea276fdc9f550" + }, + { + "set": "forest_ground_04", + "asset": "Forest Ground 04", + "author": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "map": "ao", + "file": "forest_ground_04/forest_ground_04_ao_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/forest_ground_04/forest_ground_04_ao_1k.jpg", + "bytes": 831864, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 3.4, + "groundLayer": "dirt", + "sampledByTemperateGround": false, + "sha256": "d948c7578a122fdb592d9fda5de01746a9f567e0f2981bc7f89d3358422d0ad1" + }, + { + "set": "forest_ground_04", + "asset": "Forest Ground 04", + "author": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "map": "diff", + "file": "forest_ground_04/forest_ground_04_diff_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/forest_ground_04/forest_ground_04_diff_1k.jpg", + "bytes": 1113899, + "dimensions": "1024x1024", + "colourSpace": "srgb", + "metresPerTile": 3.4, + "groundLayer": "dirt", + "sampledByTemperateGround": true, + "sha256": "50a4dda30875020c64385261aa7e00e2155b592302fa6794165f58fae73cbd2c" + }, + { + "set": "forest_ground_04", + "asset": "Forest Ground 04", + "author": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "map": "nor_gl", + "file": "forest_ground_04/forest_ground_04_nor_gl_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/forest_ground_04/forest_ground_04_nor_gl_1k.jpg", + "bytes": 1326812, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 3.4, + "groundLayer": "dirt", + "sampledByTemperateGround": true, + "sha256": "9c3a0200e02ce40edffbbf9f5f623ef08ea9b1b7812b556182ec978f88887151" + }, + { + "set": "forest_ground_04", + "asset": "Forest Ground 04", + "author": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "map": "rough", + "file": "forest_ground_04/forest_ground_04_rough_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/forest_ground_04/forest_ground_04_rough_1k.jpg", + "bytes": 627507, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 3.4, + "groundLayer": "dirt", + "sampledByTemperateGround": false, + "sha256": "89d4d50395eb92824a64132c4bc8a5a09009071e0b325ab66dc3104c7a3090ed" + }, + { + "set": "leafy_grass", + "asset": "Leafy Grass", + "author": "Charlotte Baglioni", + "license": "CC0", + "map": "ao", + "file": "leafy_grass/leafy_grass_ao_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/leafy_grass/leafy_grass_ao_1k.jpg", + "bytes": 1056302, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 2.6, + "groundLayer": "grass", + "sampledByTemperateGround": true, + "sha256": "0a53eeb9a89531b5d2ceb42f5d86f320396612844dc376c8f0d6d93d6e14cefa" + }, + { + "set": "leafy_grass", + "asset": "Leafy Grass", + "author": "Charlotte Baglioni", + "license": "CC0", + "map": "diff", + "file": "leafy_grass/leafy_grass_diff_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/leafy_grass/leafy_grass_diff_1k.jpg", + "bytes": 1195394, + "dimensions": "1024x1024", + "colourSpace": "srgb", + "metresPerTile": 2.6, + "groundLayer": "grass", + "sampledByTemperateGround": true, + "sha256": "cfa40bc9d9417d1852db8753a8d5917f110c40101179f63543c382e39bc05e4a" + }, + { + "set": "leafy_grass", + "asset": "Leafy Grass", + "author": "Charlotte Baglioni", + "license": "CC0", + "map": "nor_gl", + "file": "leafy_grass/leafy_grass_nor_gl_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/leafy_grass/leafy_grass_nor_gl_1k.jpg", + "bytes": 1466049, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 2.6, + "groundLayer": "grass", + "sampledByTemperateGround": true, + "sha256": "832328216adc0a7e1f70a31d5ee48c9ab7f2152d83816122736cf42ac4b2ebd6" + }, + { + "set": "leafy_grass", + "asset": "Leafy Grass", + "author": "Charlotte Baglioni", + "license": "CC0", + "map": "rough", + "file": "leafy_grass/leafy_grass_rough_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/leafy_grass/leafy_grass_rough_1k.jpg", + "bytes": 511385, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 2.6, + "groundLayer": "grass", + "sampledByTemperateGround": true, + "sha256": "0c2a1d91aacb8e54a496051b59854e51e456fe858abeade778e308359ef16463" + }, + { + "set": "mossy_rock", + "asset": "Mossy Rock", + "author": "Poly Haven / Dario Barresi", + "license": "CC0", + "map": "ao", + "file": "mossy_rock/mossy_rock_ao_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/mossy_rock/mossy_rock_ao_1k.jpg", + "bytes": 533103, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 3.6, + "groundLayer": "moss", + "sampledByTemperateGround": false, + "sha256": "00856838c04dfc778dd830d6e92d0f77effb8286c32d8068f2cf48a02a31cdaa" + }, + { + "set": "mossy_rock", + "asset": "Mossy Rock", + "author": "Poly Haven / Dario Barresi", + "license": "CC0", + "map": "diff", + "file": "mossy_rock/mossy_rock_diff_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/mossy_rock/mossy_rock_diff_1k.jpg", + "bytes": 1124438, + "dimensions": "1024x1024", + "colourSpace": "srgb", + "metresPerTile": 3.6, + "groundLayer": "moss", + "sampledByTemperateGround": true, + "sha256": "4dbf37f1b0465c9c8914213163923be159716f5b97fb6946437b6c5e143ef540" + }, + { + "set": "mossy_rock", + "asset": "Mossy Rock", + "author": "Poly Haven / Dario Barresi", + "license": "CC0", + "map": "nor_gl", + "file": "mossy_rock/mossy_rock_nor_gl_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/mossy_rock/mossy_rock_nor_gl_1k.jpg", + "bytes": 995727, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 3.6, + "groundLayer": "moss", + "sampledByTemperateGround": true, + "sha256": "232cdd6a9fdfd01bb8bb1f8f604c53dbca79fa0d3d7995edf85c9945757aa8df" + }, + { + "set": "mossy_rock", + "asset": "Mossy Rock", + "author": "Poly Haven / Dario Barresi", + "license": "CC0", + "map": "rough", + "file": "mossy_rock/mossy_rock_rough_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/mossy_rock/mossy_rock_rough_1k.jpg", + "bytes": 780918, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 3.6, + "groundLayer": "moss", + "sampledByTemperateGround": false, + "sha256": "0a18bdeb08444b730170b0477f13940ce6cd4d82d06137df661f96d1e2a6a66e" + }, + { + "set": "sand_01", + "asset": "Sand 01", + "author": "Rob Tuytel", + "license": "CC0", + "map": "ao", + "file": "sand_01/sand_01_ao_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/sand_01/sand_01_ao_1k.jpg", + "bytes": 298726, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 3.2, + "groundLayer": "sand", + "sampledByTemperateGround": false, + "sha256": "be5fed35a2b259088682241ce4cf1eb0ee5d36b820f520e3f5e7f353bd1f627a" + }, + { + "set": "sand_01", + "asset": "Sand 01", + "author": "Rob Tuytel", + "license": "CC0", + "map": "diff", + "file": "sand_01/sand_01_diff_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/sand_01/sand_01_diff_1k.jpg", + "bytes": 485088, + "dimensions": "1024x1024", + "colourSpace": "srgb", + "metresPerTile": 3.2, + "groundLayer": "sand", + "sampledByTemperateGround": true, + "sha256": "a73b90eaa6d675ef57e7d48a8cf23e1f412e353ab2e29cf73930b8ecd4e76875" + }, + { + "set": "sand_01", + "asset": "Sand 01", + "author": "Rob Tuytel", + "license": "CC0", + "map": "nor_gl", + "file": "sand_01/sand_01_nor_gl_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/sand_01/sand_01_nor_gl_1k.jpg", + "bytes": 877821, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 3.2, + "groundLayer": "sand", + "sampledByTemperateGround": false, + "sha256": "73984aecdc35b1ffbc322d64b7f8d8165b06970d11f37dd47010609fcb532704" + }, + { + "set": "sand_01", + "asset": "Sand 01", + "author": "Rob Tuytel", + "license": "CC0", + "map": "rough", + "file": "sand_01/sand_01_rough_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/sand_01/sand_01_rough_1k.jpg", + "bytes": 189667, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 3.2, + "groundLayer": "sand", + "sampledByTemperateGround": false, + "sha256": "ccb179b12c56a9032d60e4f4e2da690b3612a58e1784eb14576372690cdcd1cb" + }, + { + "set": "snow_02", + "asset": "Snow 02", + "author": "Rob Tuytel", + "license": "CC0", + "map": "ao", + "file": "snow_02/snow_02_ao_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/snow_02/snow_02_ao_1k.jpg", + "bytes": 289832, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 12.0, + "groundLayer": "snow", + "sampledByTemperateGround": false, + "sha256": "1be6441229c6db11ca39068e46eb4275e33d9c99f8e15d7a5b53d95c47e10ff6" + }, + { + "set": "snow_02", + "asset": "Snow 02", + "author": "Rob Tuytel", + "license": "CC0", + "map": "diff", + "file": "snow_02/snow_02_diff_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/snow_02/snow_02_diff_1k.jpg", + "bytes": 325497, + "dimensions": "1024x1024", + "colourSpace": "srgb", + "metresPerTile": 12.0, + "groundLayer": "snow", + "sampledByTemperateGround": true, + "sha256": "523a4e69c90b96d787dd69b897f29a2b3761a017024405c60aa630d1e8e9009a" + }, + { + "set": "snow_02", + "asset": "Snow 02", + "author": "Rob Tuytel", + "license": "CC0", + "map": "nor_gl", + "file": "snow_02/snow_02_nor_gl_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/snow_02/snow_02_nor_gl_1k.jpg", + "bytes": 1081858, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 12.0, + "groundLayer": "snow", + "sampledByTemperateGround": false, + "sha256": "9495ec680616b8340e2cbecbd151bff84c690cfad4209e90cc98d2d0ba81f810" + }, + { + "set": "snow_02", + "asset": "Snow 02", + "author": "Rob Tuytel", + "license": "CC0", + "map": "rough", + "file": "snow_02/snow_02_rough_1k.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/snow_02/snow_02_rough_1k.jpg", + "bytes": 146089, + "dimensions": "1024x1024", + "colourSpace": "linear", + "metresPerTile": 12.0, + "groundLayer": "snow", + "sampledByTemperateGround": false, + "sha256": "b6dea8039ac2a5beaed6fd41834fb55540cc76b492edd1d48ba2da2f30f1cb75" + }, + { + "set": "rock_boulder_dry", + "asset": "Rock Boulder Dry", + "author": "Dimitrios Savva (Photography), Rico Cilliers (Processing)", + "license": "CC0", + "map": "diff", + "file": "rock_boulder_dry/rock_boulder_dry_diff_512.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/rock_boulder_dry/rock_boulder_dry_diff_1k.jpg", + "bytes": 87707, + "dimensions": "512x512", + "colourSpace": "srgb", + "metresPerTile": null, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "dde5d4cf88dce2a3bcde0490c509f0a5f375c4aa002aa08f96afa6e0fb4f6c7b" + }, + { + "set": "rock_boulder_dry", + "asset": "Rock Boulder Dry", + "author": "Dimitrios Savva (Photography), Rico Cilliers (Processing)", + "license": "CC0", + "map": "nor_gl", + "file": "rock_boulder_dry/rock_boulder_dry_nor_gl_512.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/rock_boulder_dry/rock_boulder_dry_nor_gl_1k.jpg", + "bytes": 60524, + "dimensions": "512x512", + "colourSpace": "linear", + "metresPerTile": null, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "5039f885b242fdb9f4b15b1cde8d09dca53ec50d37544ffb72154505ba2fc136" + }, + { + "set": "rock_boulder_dry", + "asset": "Rock Boulder Dry", + "author": "Dimitrios Savva (Photography), Rico Cilliers (Processing)", + "license": "CC0", + "map": "arm", + "file": "rock_boulder_dry/rock_boulder_dry_arm_512.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/rock_boulder_dry/rock_boulder_dry_arm_1k.jpg", + "bytes": 60580, + "dimensions": "512x512", + "colourSpace": "linear", + "metresPerTile": null, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "6104415f2825ae7d0d072e8cd37b968324bb88b936665fbb73a71bda82a9a2a3" + }, + { + "set": "lichen_rock", + "asset": "Lichen Rock", + "author": "Rico Cilliers (All)", + "license": "CC0", + "map": "diff", + "file": "lichen_rock/lichen_rock_diff_512.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/lichen_rock/lichen_rock_diff_1k.jpg", + "bytes": 120954, + "dimensions": "512x512", + "colourSpace": "srgb", + "metresPerTile": null, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "fa16d72ea9a04d71e0a4e4b2c65ba50b59fe0f4dfb31838055a9eaa7b98f0244" + }, + { + "set": "lichen_rock", + "asset": "Lichen Rock", + "author": "Rico Cilliers (All)", + "license": "CC0", + "map": "nor_gl", + "file": "lichen_rock/lichen_rock_nor_gl_512.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/lichen_rock/lichen_rock_nor_gl_1k.jpg", + "bytes": 125051, + "dimensions": "512x512", + "colourSpace": "linear", + "metresPerTile": null, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "f6f0bb9f6546251851395582b9703df4ce9e0116d56fdd0cacc3b05a3aef3ee6" + }, + { + "set": "lichen_rock", + "asset": "Lichen Rock", + "author": "Rico Cilliers (All)", + "license": "CC0", + "map": "arm", + "file": "lichen_rock/lichen_rock_arm_512.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/lichen_rock/lichen_rock_arm_1k.jpg", + "bytes": 64088, + "dimensions": "512x512", + "colourSpace": "linear", + "metresPerTile": null, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "cccadfc7b806a15bd396cc433175c80fdb386009674726a8a17e48ba50ce50cb" + }, + { + "set": "river_small_rocks", + "asset": "River Small Rocks", + "author": "Rob Tuytel (Photography, Processing), Rico Cilliers (Minor adjustment)", + "license": "CC0", + "map": "diff", + "file": "river_small_rocks/river_small_rocks_diff_512.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/river_small_rocks/river_small_rocks_diff_1k.jpg", + "bytes": 104165, + "dimensions": "512x512", + "colourSpace": "srgb", + "metresPerTile": null, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "b4dc4bd921c9d22b2dcb33984527032e897ca98a09bf63c13caff6787485c3c0" + }, + { + "set": "river_small_rocks", + "asset": "River Small Rocks", + "author": "Rob Tuytel (Photography, Processing), Rico Cilliers (Minor adjustment)", + "license": "CC0", + "map": "nor_gl", + "file": "river_small_rocks/river_small_rocks_nor_gl_512.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/river_small_rocks/river_small_rocks_nor_gl_1k.jpg", + "bytes": 153457, + "dimensions": "512x512", + "colourSpace": "linear", + "metresPerTile": null, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "5acd5993c4ce8cc32778ca57f210c538a676020971240306a2ab2438158e5d89" + }, + { + "set": "river_small_rocks", + "asset": "River Small Rocks", + "author": "Rob Tuytel (Photography, Processing), Rico Cilliers (Minor adjustment)", + "license": "CC0", + "map": "arm", + "file": "river_small_rocks/river_small_rocks_arm_512.jpg", + "url": "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/river_small_rocks/river_small_rocks_arm_1k.jpg", + "bytes": 89842, + "dimensions": "512x512", + "colourSpace": "linear", + "metresPerTile": null, + "groundLayer": null, + "sampledByTemperateGround": false, + "sha256": "5d413aee3dc1e416e451788d9e671d6aa8e2436569156eb3f9f55620d7f670f9" + }, + { + "set": "fern_02", + "asset": "Fern 02", + "author": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "map": "alpha", + "file": "fern_02/fern_02_alpha_512.png", + "url": "https://polyhaven.com/a/fern_02", + "bytes": 41735, + "dimensions": [512, 512], + "sha256": "d3422b26b0f41a0be9d5e7d7878765da674ff173f4c5a0c47795364c8b954bb0" + }, + { + "set": "fern_02", + "asset": "Fern 02", + "author": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "map": "diff", + "file": "fern_02/fern_02_diff_512.jpg", + "url": "https://polyhaven.com/a/fern_02", + "bytes": 33569, + "dimensions": [512, 512], + "sha256": "a6047726852171297213cb8063c1607856b0c414c579ff9d60a54262b925f78a" + }, + { + "set": "fern_02", + "asset": "Fern 02", + "author": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "map": "nor_gl", + "file": "fern_02/fern_02_nor_gl_512.jpg", + "url": "https://polyhaven.com/a/fern_02", + "bytes": 35095, + "dimensions": [512, 512], + "sha256": "72d64f5a9632cf5cc26969b8e60b1a5da5432bbde562b87a6fccef29949e28a8" + }, + { + "set": "kloofendal_48d_partly_cloudy", + "asset": "Kloofendal 48d Partly Cloudy", + "author": "Greg Zaal", + "license": "CC0", + "map": "hdri", + "file": "hdri/kloofendal_48d_partly_cloudy_1k.hdr", + "url": "https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/kloofendal_48d_partly_cloudy_1k.hdr", + "bytes": 1637206, + "dimensions": "1024x512", + "colourSpace": "linear", + "sha256": "5477b7dbb2aea4a6947cb36b96339c119f90bf36a03b039af3254b5e6a22896d" + } + ], + "generated": { + "needle-atlas.png": { + "what": "A 2x2 RGBA atlas: three spruce branch cards (needles drawn as bundles of side shoots at several angles, lighter tips, dark twig) and one poppy petal, on a hard alpha edge", + "generator": "examples/strata-terrain-preview/scripts/make-needle-atlas.mjs", + "seed": 466468, + "command": "node scripts/make-needle-atlas.mjs", + "bytes": 321934, + "dimensions": "1024x1024 (4 cells of 512x512)", + "preprocessing": "none. Drawn from a seeded PRNG and deflated as PNG by the script itself: no image library, no upload, byte-identical on every run.", + "sha256": "d2f1fd6eb3f87083a1c72a07add0c485f477b7b5e64e1d9db0108298c75eae50", + "why": "A photograph of foliage does not give a card with the twig down the middle and needles fanning off both sides, which is what makes an alpha-cutout branch read as a branch from every angle." + }, + "needle-surface.png": { + "what": "The same four cells as relief: RG a tangent-space normal cooked from the albedo's own per-needle ridge field, B the ambient occlusion the canopy drops on itself", + "generator": "examples/strata-terrain-preview/scripts/make-needle-atlas.mjs", + "seed": 466468, + "command": "node scripts/make-needle-atlas.mjs", + "bytes": 1021932, + "dimensions": "1024x1024 (4 cells of 512x512)", + "preprocessing": "none. Cooked from the coverage and ridge the same run recorded while drawing the albedo, and deflated as PNG by the script itself: no image library, byte-identical on every run.", + "sha256": "45d236f6b61f745753fd86b06b0c27d451ae118c9f0164ce44dda51f297ecbf5", + "why": "An alpha-tested quad has no thickness, so a crown built from cards had exactly one flat normal per card and read as stacked flat quads. A normal per needle plus the spray's own occlusion is what makes a card a mass of needles rather than a silhouette." + } + }, + "prepared": { + "why": "The starter's own art folder is the only place a prepared prop can live: it is what the terrain world's Vite publicDir serves and what ctx.assets loads by path, so the ground maps and the props come from one place with one credits file.", + "generator": "examples/strata-terrain-preview/scripts/prep-trees.py", + "command": "~/.local/bin/blender -b --factory-startup --python scripts/prep-trees.py -- --source / --out packages/terrain/starter-assets/ --species --variant --near --mid ", + "license": "CC0 1.0 Universal (public domain dedication), unchanged from the source assets", + "rawDownloads": "never committed; the upstream .gltf and .bin stay in a scratch directory outside this repository", + "preprocessing": [ + "split by material, then join to one solid part and one needle part per prop", + "wood: collapse decimation, then a cylindrical unwrap so the game's own bark map can be sampled on it", + "wood: loose branch shells dropped whole, biggest first, to reach the budget", + "needles: whole alpha cards dropped, evenly over the crown's own volume, then the survivors enlarged about their own centres", + "colours and extra UV sets stripped; cameras, lights and armatures removed", + "exported with export_apply=True, and every count read back out of the written GLB's JSON chunk" + ], + "assets": [ + { + "asset": "Fir Tree 01", + "authors": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "url": "https://polyhaven.com/a/fir_tree_01", + "note": "Poly Haven's own authored LOD2 of tree b (fir_tree_01_b_LOD2, 54,927 triangles, full crown), exported by examples/strata-terrain-preview/scripts/prep-polyhaven-fir.py from the 1k .blend; the forest starter kit's fir." + }, + { + "asset": "Rock Moss Set 01", + "authors": "Rob Tuytel, Rico Cilliers", + "license": "CC0", + "url": "https://polyhaven.com/a/rock_moss_set_01", + "note": "rock01 and rock04, both levels. Shaded by the starter's own mossy_rock set, triplanar." + }, + { + "asset": "Boulder 01", + "authors": "Rob Tuytel", + "license": "CC0", + "url": "https://polyhaven.com/a/boulder_01", + "note": "A second source, so a meadow's stones are not one rock copied." + }, + { + "asset": "Boulder 01", + "authors": "Poly Haven", + "license": "CC0", + "url": "https://polyhaven.com/a/boulder_01", + "note": "Authored LOD1 (33,060 tris) and LOD2 (16,530 tris) with base, normal and roughness maps, exported by examples/strata-terrain-preview/scripts/prep-polyhaven-rock.py; the forest starter kit's boulder." + } + ], + "firedCoverage": { + "verdict": "superseded 2026-10-05", + "why": "The card-dropping prep kept 433 of 437,376 needle cards (0.3% of the silhouette). The .blend ships authored LODs whose LOD2 keeps the whole crown; that file replaced fir-b/c near/mid." + }, + "budgets": { + "treesNear": 6000, + "treesMid": 1500, + "rockNear": 2500, + "rockMid": 600, + "measuredTreeNear": 5998, + "measuredTreeMid": 1497, + "measuredRockNear": "1924-2235", + "measuredRockMid": "469-540" + }, + "files": [ + { + "bytes": 407309, + "dimensions": "1024x1024", + "file": "fir_tree_01/fir_tree_01_twig_arm_1k.jpg", + "sha256": "d66c81fc073ee7ea97a76b6da2399769f16944e50ef0e007f09c95619f597c79" + }, + { + "bytes": 371741, + "dimensions": "1024x1024", + "file": "fir_tree_01/fir_tree_01_twig_diff_1k.jpg", + "sha256": "8c0833a10e4fd44498d848cf9e659fe250a3813984cbd679f42430eb98c61fb8" + }, + { + "bytes": 472604, + "dimensions": "1024x1024", + "file": "fir_tree_01/fir_tree_01_twig_nor_gl_1k.jpg", + "sha256": "d470dc39b1d02bbeba06feb0ad818680ef3c24623b4db597270301a5fb8c9369" + }, + { + "bytes": 31940, + "dimensions": null, + "file": "rocks/boulder-mid.glb", + "sha256": "92295a63e906931a045be398b4463c9aaaf304b48836c21e3eb9a450807b1548" + }, + { + "bytes": 130016, + "dimensions": null, + "file": "rocks/boulder-near.glb", + "sha256": "89d5d00a475de8102ec585a0bd2384855875034f7988f176e16bd0b2a30c7fd7" + }, + { + "bytes": 995, + "dimensions": null, + "file": "rocks/boulder-prep.json", + "sha256": "b01e94e9bdae69058bc6bf4c0b84fd6c25d1369b961449243b465855c4228d3e" + }, + { + "bytes": 14636, + "dimensions": null, + "file": "rocks/rock01-mid.glb", + "sha256": "4c289382d0c05d2c1bfd36a3c997c8240fe580f76dd0a1d2fe1b3c3af7e3e169" + }, + { + "bytes": 48972, + "dimensions": null, + "file": "rocks/rock01-near.glb", + "sha256": "7e78a21d296b4235b4adf3d834f7926b09b928237722691150407647a236eab6" + }, + { + "bytes": 1015, + "dimensions": null, + "file": "rocks/rock01-prep.json", + "sha256": "5e7627e1721cf16b347d5128574834340a7088ad66623b7879f9041abda107ae" + }, + { + "bytes": 13544, + "dimensions": null, + "file": "rocks/rock04-mid.glb", + "sha256": "4d85f38a080e179e6abddf759b3b023ec5a4becf733259b0067213bb141a35bc" + }, + { + "bytes": 46644, + "dimensions": null, + "file": "rocks/rock04-near.glb", + "sha256": "c36532454cb6f6f713d4402227741d823f2f3f98113c6cd9b6bc81c0e0a9de9d" + }, + { + "bytes": 1013, + "dimensions": null, + "file": "rocks/rock04-prep.json", + "sha256": "71b79c26df145ef05a3c58fac045964d185b19aa38d7e483d5a87ae488896fc7" + }, + { + "bytes": 11324, + "dimensions": null, + "file": "rocks/rock13-mid.glb", + "sha256": "d98074cc7944de0c428502264c2d35b01f6a8b87cd10715132a6a7e0847d8e48" + }, + { + "bytes": 5153060, + "dimensions": null, + "file": "fir_tree_01/fir-b-lod2.glb", + "sha256": "a7e1f126ba7c09aa160f82e200689312334204928c9d822a1eaf13ed9d259dc8" + }, + { + "bytes": 2464264, + "dimensions": null, + "file": "boulder_01/boulder-near.glb", + "sha256": "4a86aadf75dd86bf13d32556da6acd2c1d53e2508094fe55a0903f2c44624a59" + }, + { + "bytes": 2071256, + "dimensions": null, + "file": "boulder_01/boulder-mid.glb", + "sha256": "4d4a1026047d229d6fbe992d061b4b4e9ef5f837e64239f18e0664a77aed8d84" + }, + { + "bytes": 3971572, + "dimensions": null, + "file": "fir_tree_01/fir-c-lod2.glb", + "sha256": "9dba8a8a30f204a2c60f45bd059676cdabbe7287180338c38c1ce54d8530bd7b" + } + ] + } +} diff --git a/packages/terrain/starter/alpine/bake.mjs b/packages/terrain/starter/alpine/bake.mjs index 35a3a508ef..70097ec850 100644 --- a/packages/terrain/starter/alpine/bake.mjs +++ b/packages/terrain/starter/alpine/bake.mjs @@ -1,118 +1,6 @@ -// Bakes this folder's recipe into a self-contained world package. Run it once from your game -// project, then serve the folder it writes: -// node src/terrain/alpine/bake.mjs [--assets ] [--out ] -// The engine's `WorldCells` streams `world/world.json` and `loadTerrainSplat` textures its ground; -// the game never evaluates terrain at runtime. -import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { Terrain, applyPlacementOverrides, bakeWorldPackage } from "@threenative/terrain"; +// Run with [--assets ] [--out ]; defaults to installed assets and ./world. +import { bakeKit } from "@threenative/terrain/kit"; -const here = dirname(fileURLToPath(import.meta.url)); - -/** `--key value` after the script; `--assets` and `--out` default as noted below. */ -function flag(name, fallback) { - const at = process.argv.indexOf(`--${name}`); - return at === -1 ? fallback : resolve(process.argv[at + 1] ?? ""); -} - -// The installed kit ships its models and textures beside the package manifest, so a copied kit -// finds them without asking the game for a path. `--assets` overrides that for a local checkout. -const installed = dirname(fileURLToPath(import.meta.resolve("@threenative/terrain/package.json"))); -const assetsDir = flag("assets", join(installed, "starter-assets")); -const outDir = flag("out", join(here, "world")); - -const recipe = JSON.parse(await readFile(join(here, "recipe.json"), "utf8")); -const assetTable = JSON.parse(await readFile(join(here, "assets.json"), "utf8")); -const surface = JSON.parse(await readFile(join(here, "surface.json"), "utf8")); - -const state = applyPlacementOverrides( - Terrain.fromJSON(recipe.recipe).evaluate(), - recipe.placementOverrides ?? {}, -); - -const assets = Object.fromEntries( - Object.entries(assetTable).map(([id, spec]) => [ - id, - { - bounds: spec.bounds, - glb: `models/${spec.near.split("/").at(-1)}`, - ...(spec.mid - ? { lods: [{ distance: spec.midDistance, glb: `models/${spec.mid.split("/").at(-1)}` }] } - : {}), - maxDistance: spec.maxDistance, - }, - ]), -); - -const started = performance.now(); -const { manifest, splat, files } = bakeWorldPackage(state, { - assets, - layers: { table: "terrain-table.json" }, +await bakeKit(import.meta.url, { + sky: "hdri/kloofendal_48d_partly_cloudy_1k.hdr", }); -const written = new Map(); - -async function write(relative, bytes) { - await mkdir(join(outDir, dirname(relative)), { recursive: true }); - await writeFile(join(outDir, relative), bytes); - written.set(relative, bytes.length ?? bytes.byteLength); -} - -async function copy(from, to) { - await mkdir(join(outDir, dirname(to)), { recursive: true }); - await copyFile(join(assetsDir, from), join(outDir, to)); - written.set(to, (await readFile(join(outDir, to))).byteLength); -} - -await write("world.json", `${JSON.stringify(manifest, null, 2)}\n`); -// The lake and river the generator evaluated, so the game draws the water the bake shaped. -const round = (value) => Math.round(value * 1_000) / 1_000; -await write( - "water.json", - `${JSON.stringify( - { - lakes: state.waters.map((lake) => ({ - id: lake.id, - at: lake.at, - radius: lake.radius, - level: lake.level, - })), - rivers: state.rivers.map((river) => ({ - id: river.id, - width: round(river.width), - points: river.points.map((point) => point.map(round)), - })), - }, - null, - 2, - )}\n`, -); -for (const [name, bytes] of Object.entries(files)) await write(name, bytes); -for (const spec of Object.values(assetTable)) { - await copy(spec.near, `models/${spec.near.split("/").at(-1)}`); - if (spec.mid) await copy(spec.mid, `models/${spec.mid.split("/").at(-1)}`); -} -// The sky the props are lit by (CC0 Poly Haven Kloofendal); world.ts gives it to their materials. -await copy("hdri/kloofendal_48d_partly_cloudy_1k.hdr", "sky.hdr"); -for (const layer of [surface.base, ...surface.layers]) { - const source = surface.sources[layer.id]; - await copy(source.diff, `${surface.textures}/${layer.id}_diff.jpg`); - await copy(source.nrm, `${surface.textures}/${layer.id}_nrm.jpg`); -} -// `sources` is the bake's own lookup: the runtime reads only base/layers/splat/textures. -await write( - "terrain-table.json", - `${JSON.stringify({ ...surface, splat, textures: "textures" }, null, 2)}\n`, -); - -const perAsset = {}; -for (const cell of manifest.cells) - for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; -const total = [...written.values()].reduce((sum, bytes) => sum + bytes, 0); -console.log( - `${Object.entries(perAsset) - .map(([asset, count]) => `${asset} ${String(count)}`) - .join(", ")} placements; ${[...written] - .map(([name, bytes]) => `${name} ${String(bytes)} B`) - .join(", ")}; ${String(total)} B total; ${Math.round(performance.now() - started)} ms`, -); diff --git a/packages/terrain/starter/alpine/heightmap.r16 b/packages/terrain/starter/alpine/heightmap.r16 new file mode 100644 index 0000000000..283d2d1ecd Binary files /dev/null and b/packages/terrain/starter/alpine/heightmap.r16 differ diff --git a/packages/terrain/starter/alpine/recipe.json b/packages/terrain/starter/alpine/recipe.json index 4fa657ab65..c5e47a0d57 100644 --- a/packages/terrain/starter/alpine/recipe.json +++ b/packages/terrain/starter/alpine/recipe.json @@ -17,66057 +17,9 @@ "data": { "width": 257, "height": 257, - "values": [ - 448.2, - 447.3, - 440.3, - 416.4, - 387, - 376, - 369.5, - 359.1, - 348.8, - 342.4, - 338.7, - 336.8, - 334.5, - 331.4, - 328, - 324.5, - 321.2, - 317.8, - 314.9, - 312.6, - 310.7, - 308.5, - 306.8, - 305.8, - 305, - 303.6, - 301.8, - 299.6, - 296.1, - 293.7, - 290.7, - 286.7, - 283, - 281.7, - 281, - 279.3, - 271.7, - 264.9, - 257.1, - 252.4, - 249.7, - 247.5, - 245.2, - 241.5, - 237.3, - 233.1, - 227, - 223.4, - 220.2, - 213, - 206.9, - 201.9, - 194.8, - 189.5, - 185.7, - 181.8, - 177.6, - 173, - 168.3, - 164.7, - 161.5, - 158.2, - 154.7, - 150.9, - 146.9, - 142.3, - 138.6, - 137.3, - 136.1, - 134.9, - 133.8, - 133, - 132.1, - 131.1, - 130, - 128.8, - 128.3, - 128, - 127.2, - 126.4, - 125.6, - 124.7, - 123.8, - 122.8, - 121.9, - 120.9, - 119.9, - 119, - 118, - 117.2, - 116.4, - 115.5, - 114.8, - 113.8, - 112.8, - 112, - 111.1, - 110.3, - 109.5, - 108.8, - 108.1, - 107.3, - 106.8, - 106, - 105.4, - 104.5, - 103.5, - 102.6, - 101.7, - 100.8, - 99.9, - 99, - 98, - 97, - 96.1, - 95.3, - 94.3, - 93.2, - 92.5, - 91.9, - 91.3, - 90.6, - 89.9, - 89, - 88.3, - 87.7, - 87.9, - 87.9, - 87.2, - 85.7, - 84.2, - 83.6, - 83, - 82.1, - 81.3, - 80.4, - 79.8, - 79, - 78.4, - 77.4, - 76.4, - 75.7, - 74.9, - 73.9, - 73.1, - 72.7, - 72.5, - 71.5, - 70.2, - 69.3, - 68.6, - 67.9, - 67.4, - 66.6, - 65.6, - 64.8, - 64.5, - 64.7, - 64.4, - 63.5, - 62.8, - 62.3, - 61.7, - 61.2, - 60.7, - 59.9, - 59, - 58.6, - 57.9, - 56.7, - 55.4, - 53.9, - 52.4, - 50.9, - 49.4, - 47.8, - 46.4, - 45, - 43.3, - 41.8, - 40.3, - 39.2, - 38.2, - 37.2, - 35.9, - 34.7, - 33.4, - 32.2, - 30, - 25.1, - 19.1, - 15.5, - 13.9, - 12.4, - 11.1, - 11.2, - 11.7, - 11.9, - 12, - 12, - 11.8, - 11.7, - 11.5, - 11.3, - 11, - 10.9, - 10.5, - 10.2, - 10.2, - 10.5, - 11.1, - 12.1, - 12.7, - 12.8, - 12.7, - 12.2, - 11.7, - 11.4, - 11.3, - 11.2, - 11, - 10.6, - 10.2, - 9.5, - 9.2, - 8.8, - 8.3, - 7.9, - 7.8, - 7.5, - 7.3, - 7.3, - 7.3, - 7.1, - 6.7, - 6.4, - 6.2, - 6, - 5.6, - 5.3, - 5, - 4.6, - 4.5, - 4.3, - 4.2, - 4.2, - 4, - 3.7, - 3.4, - 3.2, - 3, - 2.7, - 2.3, - 2.1, - 2.1, - 1.9, - 1.2, - 449.7, - 447.1, - 438.7, - 423.4, - 391.3, - 377.9, - 371.8, - 362.4, - 352, - 344.1, - 338.1, - 335.4, - 332.3, - 328.9, - 325.4, - 322, - 318.6, - 315.2, - 311.9, - 308.9, - 307.1, - 305.9, - 304.9, - 304.1, - 303.3, - 302.2, - 301.2, - 299.2, - 295.6, - 293.5, - 291.6, - 287.8, - 283.2, - 281.1, - 280.2, - 279, - 275.2, - 266.8, - 259.4, - 254.1, - 250.6, - 248.2, - 245.5, - 242, - 237.8, - 232.5, - 226.8, - 224.2, - 221.9, - 215.6, - 208.2, - 202.5, - 195.4, - 190.5, - 186.9, - 183.1, - 178.9, - 174.6, - 169.9, - 165.7, - 162.8, - 159.4, - 155.8, - 151.8, - 147.1, - 141.6, - 139.1, - 138.1, - 137, - 135.9, - 134.8, - 133.8, - 132.8, - 131.8, - 130.7, - 129.7, - 129.3, - 128.7, - 127.8, - 126.9, - 126, - 125, - 124.1, - 123, - 122, - 121, - 120, - 119.1, - 118.2, - 117.5, - 116.6, - 115.7, - 114.8, - 113.9, - 113, - 112.2, - 111.4, - 110.6, - 109.8, - 109, - 108.5, - 107.6, - 107, - 106.4, - 105.6, - 104.7, - 103.7, - 102.8, - 101.8, - 100.8, - 100.1, - 99.2, - 98.2, - 97.3, - 96.4, - 95.6, - 94.6, - 93.6, - 93.1, - 92.3, - 91.5, - 90.6, - 90.2, - 89.5, - 89.2, - 88.4, - 87.6, - 87.2, - 86.5, - 85.5, - 84.5, - 83.7, - 82.9, - 82, - 81.1, - 80.4, - 79.8, - 79.1, - 78.6, - 77.6, - 76.4, - 75.4, - 74.8, - 74, - 73.3, - 72.7, - 72.2, - 71.3, - 70.1, - 69.2, - 68.7, - 67.9, - 67.2, - 66.7, - 66, - 65.1, - 64.9, - 64.9, - 64.3, - 63.3, - 62.7, - 62.3, - 61.8, - 61.3, - 60.7, - 59.9, - 59, - 58.5, - 57.8, - 56.6, - 55.2, - 53.5, - 52.2, - 50.8, - 49, - 47.4, - 45.9, - 44.4, - 42.8, - 41.3, - 39.8, - 38.7, - 37.5, - 36.4, - 35.1, - 33.9, - 32.8, - 31.4, - 28.3, - 23.2, - 18.4, - 16.5, - 14.8, - 13.2, - 12.1, - 11.9, - 12, - 12.1, - 12.1, - 12.1, - 11.9, - 11.8, - 11.7, - 11.4, - 10.7, - 10.6, - 10.3, - 10.3, - 11.1, - 11.7, - 12.3, - 12.7, - 12.7, - 12.7, - 12.8, - 12.5, - 11.8, - 11.8, - 11.8, - 11.7, - 11.4, - 10.9, - 10.2, - 9.6, - 9.5, - 9.4, - 9.1, - 8.7, - 8.5, - 8.2, - 7.9, - 7.7, - 7.7, - 7.6, - 7.2, - 6.9, - 6.7, - 6.4, - 6.2, - 6.1, - 5.8, - 5.3, - 4.9, - 4.6, - 4.6, - 4.5, - 4.5, - 4.3, - 4, - 3.6, - 3.6, - 3.4, - 3.1, - 2.8, - 2.6, - 2.4, - 1.7, - 449.9, - 444.6, - 437.3, - 417.9, - 390.2, - 381, - 373.7, - 364.9, - 354.5, - 346.1, - 338.9, - 333.8, - 330.1, - 326.5, - 322.9, - 319.5, - 316.1, - 312.6, - 309.3, - 306.8, - 305.6, - 304.6, - 303.8, - 302.9, - 302, - 301, - 300, - 298.5, - 295.1, - 293.2, - 291.4, - 288.2, - 284.4, - 280.6, - 279.4, - 278.2, - 275.9, - 271.4, - 262.9, - 256.5, - 252.3, - 248.9, - 245.7, - 242.9, - 238.7, - 232.1, - 227.9, - 225.5, - 222.8, - 217.5, - 209.2, - 202, - 194.8, - 191.1, - 187.8, - 184.2, - 180.2, - 176.1, - 171.6, - 167, - 163.7, - 160.4, - 156.5, - 150.6, - 144.4, - 141.4, - 139.8, - 138.9, - 137.9, - 136.7, - 135.6, - 134.5, - 133.5, - 132.5, - 131.4, - 130.5, - 129.8, - 129, - 128.1, - 127.1, - 126.1, - 125.1, - 124.2, - 123.2, - 122.1, - 121.1, - 120.2, - 119.3, - 118.3, - 117.6, - 116.6, - 115.7, - 114.8, - 113.8, - 113, - 112.4, - 111.7, - 110.9, - 110.1, - 109.3, - 108.8, - 108, - 107.2, - 106.6, - 105.7, - 104.7, - 103.8, - 102.9, - 101.9, - 100.9, - 100.1, - 99.3, - 98.3, - 97.3, - 96.5, - 95.6, - 94.9, - 94.2, - 93.7, - 93, - 92.1, - 90.8, - 90.4, - 90.2, - 89.8, - 89.2, - 88.2, - 87.6, - 86.9, - 85.9, - 84.9, - 83.9, - 83.2, - 82.5, - 81.3, - 80.7, - 80, - 79.3, - 78.7, - 77.7, - 76.5, - 75.4, - 74.9, - 74.2, - 73.5, - 72.6, - 72.1, - 71.5, - 70.5, - 69.5, - 68.7, - 68, - 67.1, - 66.6, - 66.1, - 65.3, - 65.1, - 64.8, - 64.2, - 63.4, - 62.8, - 62.4, - 61.9, - 61.3, - 60.7, - 59.9, - 59, - 58.3, - 57.5, - 56.3, - 54.9, - 53.1, - 51.7, - 50.3, - 48.7, - 47.1, - 45.6, - 43.9, - 42.4, - 40.9, - 39.5, - 38.3, - 36.9, - 35.7, - 34.7, - 33.4, - 31.9, - 29.2, - 25.5, - 21.8, - 18.9, - 17.3, - 15.7, - 13.9, - 12.5, - 12.1, - 12.2, - 12.3, - 12.3, - 12.2, - 12.1, - 11.9, - 11.7, - 11, - 10.8, - 10.8, - 10.8, - 11.2, - 12.1, - 12.8, - 13, - 12.8, - 12.7, - 12.9, - 13.2, - 13.2, - 12.8, - 12.5, - 12.4, - 12, - 11.6, - 11, - 10.2, - 9.7, - 9.8, - 9.7, - 9.6, - 9.5, - 9.2, - 9, - 8.6, - 8.1, - 7.9, - 7.6, - 7.4, - 7.2, - 7.1, - 6.9, - 6.8, - 6.7, - 6.3, - 6, - 5.8, - 5.3, - 5.1, - 4.9, - 4.8, - 4.8, - 4.4, - 4.1, - 4.1, - 4, - 3.8, - 3.5, - 3.1, - 3, - 2.5, - 448.4, - 443.6, - 438.2, - 415.9, - 390.9, - 382.8, - 374.6, - 365.8, - 356, - 347.7, - 340.5, - 334.1, - 329.6, - 325.5, - 321.4, - 317.5, - 313.8, - 310.2, - 307.3, - 306, - 304.8, - 303.7, - 302.6, - 301.6, - 300.5, - 299.4, - 298.1, - 296.4, - 294, - 292.6, - 291.1, - 288.5, - 284.6, - 280.3, - 278.3, - 277.2, - 275.3, - 273.2, - 267.2, - 258.7, - 253.4, - 249.7, - 247, - 244.3, - 241, - 235.9, - 230.4, - 225.6, - 222.3, - 218.4, - 209.7, - 200.8, - 195, - 191.7, - 188.6, - 185.3, - 181.5, - 177.5, - 173.2, - 168.5, - 164.5, - 161.1, - 156.4, - 149.1, - 144.4, - 142.2, - 140.5, - 139.4, - 138.5, - 137.4, - 136.2, - 135.1, - 134.2, - 133.2, - 132, - 130.9, - 130, - 128.9, - 127.7, - 126.7, - 125.8, - 124.9, - 124.1, - 123.2, - 122.3, - 121.3, - 120.4, - 119.5, - 118.5, - 117.6, - 116.6, - 115.8, - 114.9, - 113.9, - 113.1, - 112.6, - 111.9, - 111.1, - 110.4, - 109.5, - 108.9, - 108.1, - 107.5, - 106.7, - 105.8, - 104.8, - 103.8, - 102.8, - 101.9, - 100.9, - 100, - 99.1, - 98.1, - 97.1, - 96.4, - 95.7, - 95.1, - 94.4, - 93.9, - 93.3, - 92.6, - 91.7, - 91.2, - 90.8, - 90.3, - 89.7, - 88.9, - 88.2, - 87.4, - 86.2, - 85.2, - 84.2, - 83.5, - 82.9, - 81.9, - 81, - 80.1, - 79.4, - 78.8, - 77.9, - 76.9, - 75.9, - 75.2, - 74.5, - 73.6, - 72.7, - 72.2, - 71.7, - 70.9, - 69.9, - 68.8, - 68, - 67.2, - 66.6, - 66.2, - 65.6, - 65.1, - 64.6, - 64, - 63.5, - 63.1, - 62.5, - 61.9, - 61.2, - 60.6, - 60, - 59.2, - 58.2, - 57, - 56, - 55, - 52.9, - 51.3, - 49.9, - 48.6, - 47.2, - 45.6, - 43.9, - 42.3, - 40.5, - 39.2, - 37.9, - 36.5, - 35.2, - 34, - 32.9, - 30.9, - 26.8, - 23.9, - 21.4, - 19.7, - 18, - 16.3, - 14.5, - 12.7, - 12.1, - 12.4, - 12.5, - 12.4, - 12.4, - 12.2, - 12.1, - 11.5, - 11, - 11.5, - 11.9, - 12, - 12.5, - 13.1, - 13.3, - 13.5, - 13.5, - 13.4, - 13.4, - 13.5, - 13.7, - 13.5, - 12.9, - 12.5, - 12, - 11.4, - 10.9, - 10.3, - 10, - 10.1, - 10.1, - 9.9, - 9.7, - 9.7, - 9.4, - 9, - 8.6, - 8.2, - 7.8, - 7.6, - 7.5, - 7.5, - 7.3, - 7.3, - 7.3, - 7, - 6.7, - 6.5, - 6.3, - 5.9, - 5.5, - 5.2, - 5.2, - 5, - 4.7, - 4.6, - 4.5, - 4.2, - 3.8, - 3.7, - 3.9, - 3.4, - 448.4, - 444.3, - 435.6, - 415.1, - 393.2, - 384.1, - 375.9, - 365.5, - 355.9, - 349, - 342.2, - 335.5, - 330.4, - 325.9, - 321.4, - 316.9, - 312.5, - 308.3, - 306.4, - 305.3, - 304.1, - 302.9, - 301.8, - 300.5, - 299.1, - 297.6, - 296, - 294.2, - 292.7, - 291.5, - 290.2, - 287.5, - 283.8, - 280.1, - 277.5, - 276.3, - 274.8, - 273.2, - 269.7, - 263.1, - 254.6, - 251, - 248.7, - 245.7, - 242.7, - 238.4, - 233.5, - 227, - 222.2, - 217.1, - 207.8, - 201.7, - 196.7, - 192.9, - 189.6, - 186.3, - 182.7, - 178.9, - 174.8, - 170.3, - 165.9, - 162.1, - 156.3, - 150.6, - 146.8, - 144.7, - 142.8, - 140.2, - 139.1, - 138.1, - 137, - 136, - 134.9, - 133.7, - 132.5, - 131.3, - 130.1, - 128.9, - 127.7, - 126.5, - 125.3, - 124.3, - 123.4, - 122.6, - 122, - 121.2, - 120.5, - 119.7, - 118.7, - 117.7, - 116.6, - 115.8, - 114.9, - 114, - 113.3, - 112.7, - 111.9, - 111.3, - 110.7, - 109.8, - 109, - 108.3, - 107.6, - 106.8, - 105.8, - 104.8, - 103.7, - 102.7, - 101.8, - 100.7, - 99.7, - 98.9, - 97.9, - 97.1, - 96.5, - 96, - 95.4, - 94.8, - 94.3, - 93.4, - 92.8, - 92.3, - 91.4, - 91, - 90.6, - 90, - 89.3, - 88.7, - 87.9, - 86.7, - 85.5, - 84.6, - 84, - 83.2, - 82.3, - 81.3, - 80.2, - 79.5, - 79, - 78.3, - 77.9, - 76.7, - 75.5, - 74.6, - 73.3, - 72.7, - 72.3, - 71.9, - 71.2, - 70.4, - 69.4, - 68.5, - 67.3, - 66.6, - 66.2, - 65.7, - 65.1, - 64.4, - 63.8, - 63.5, - 63, - 62.5, - 61.9, - 61, - 60.5, - 59.9, - 59.1, - 57.8, - 56.2, - 55.1, - 54.3, - 52.5, - 51, - 49.7, - 48.5, - 47.3, - 45.6, - 43.7, - 42.1, - 40.3, - 39, - 37.5, - 36.2, - 34.6, - 33.2, - 32.1, - 30.3, - 26, - 23.4, - 21.8, - 20.2, - 18.4, - 16.8, - 15, - 12.9, - 11.6, - 12.3, - 12.7, - 12.6, - 12.5, - 12.3, - 12.2, - 11.8, - 11.1, - 11.5, - 12.3, - 12.9, - 13.3, - 13.6, - 13.8, - 14, - 14.1, - 14, - 13.7, - 13.6, - 13.4, - 13.1, - 12.6, - 12.3, - 11.9, - 11.2, - 10.7, - 10.6, - 10.7, - 10.6, - 10.5, - 10.4, - 10.2, - 10, - 9.8, - 9.3, - 9, - 8.7, - 8.4, - 8.3, - 8.1, - 8, - 7.8, - 7.6, - 7.6, - 7.6, - 7.4, - 7, - 6.8, - 6.6, - 6.3, - 5.9, - 5.8, - 5.6, - 5.2, - 5.1, - 5.1, - 4.7, - 4.4, - 4.4, - 4.6, - 4.1, - 449.4, - 444.5, - 433.3, - 413.2, - 393.3, - 385.3, - 377.2, - 367.3, - 357.9, - 350.9, - 344.1, - 336.6, - 329.9, - 323.4, - 318.9, - 314.5, - 310.1, - 307.1, - 305.9, - 304.7, - 303.5, - 302.2, - 301, - 299.5, - 297.8, - 296, - 294.4, - 292.8, - 291, - 289.5, - 287.8, - 285.7, - 283.4, - 280.1, - 277.1, - 275.4, - 274.2, - 273, - 270.7, - 266.9, - 259.2, - 253.4, - 250.4, - 247, - 243.8, - 239.6, - 234.6, - 228.5, - 223.6, - 216.1, - 208.6, - 202.5, - 197.7, - 194.2, - 191, - 187.4, - 183.7, - 180.1, - 176.3, - 172.1, - 167.7, - 163.4, - 158.2, - 152.9, - 149.7, - 147.6, - 145.3, - 141.9, - 140.1, - 139.1, - 137.8, - 136.6, - 135.5, - 134.4, - 133.3, - 132, - 130.9, - 129.7, - 128.4, - 127.1, - 125.7, - 124.4, - 123.3, - 122.3, - 121.5, - 120.8, - 120.1, - 119.6, - 118.8, - 117.8, - 116.8, - 115.8, - 115, - 114.3, - 113.6, - 112.8, - 112, - 111.3, - 110.7, - 109.9, - 109, - 108.2, - 107.3, - 106.4, - 105.5, - 104.5, - 103.5, - 102.6, - 101.7, - 100.6, - 99.5, - 98.8, - 97.8, - 97.2, - 96.7, - 96.2, - 95.6, - 95.3, - 94.8, - 94, - 93.1, - 92.4, - 91.5, - 91, - 90.7, - 90.1, - 89.5, - 88.9, - 88.2, - 87.1, - 86.1, - 85.3, - 84.4, - 83.5, - 82.6, - 81.7, - 80.6, - 79.8, - 79.3, - 78.9, - 78.7, - 77.3, - 76, - 74.8, - 73.5, - 72.8, - 72.2, - 71.8, - 71.2, - 70.6, - 70.2, - 69.4, - 67.9, - 66.8, - 66.3, - 65.8, - 65.2, - 64.7, - 64.2, - 63.4, - 62.9, - 62.4, - 61.7, - 60.9, - 60.4, - 59.9, - 59.1, - 57.6, - 55.8, - 54.4, - 53.6, - 52.3, - 50.9, - 49.7, - 48.3, - 47, - 45.4, - 43.7, - 41.9, - 40.2, - 38.8, - 37.2, - 35.7, - 34.1, - 32.5, - 31.2, - 29.7, - 25.9, - 23.7, - 22, - 20.3, - 18.6, - 16.7, - 15, - 12.7, - 12.1, - 12.5, - 12.8, - 12.6, - 12.5, - 12.4, - 12.2, - 12, - 11.8, - 12.2, - 13, - 13.6, - 13.6, - 13.8, - 14.4, - 14.5, - 14.3, - 13.9, - 13.7, - 13.4, - 12.8, - 12.4, - 12.2, - 12, - 11.7, - 11.1, - 10.7, - 10.8, - 11, - 11, - 10.8, - 10.8, - 10.6, - 10.4, - 10.3, - 9.9, - 9.4, - 9, - 8.9, - 8.9, - 8.8, - 8.6, - 8.4, - 8, - 7.8, - 7.8, - 7.9, - 7.7, - 7.4, - 7, - 6.9, - 6.7, - 6.4, - 6.2, - 5.9, - 5.7, - 5.6, - 5.5, - 5.1, - 4.9, - 5.3, - 5, - 451.5, - 445.8, - 433.3, - 417.3, - 395.2, - 387, - 378, - 368.9, - 359.9, - 353.5, - 346.5, - 337.6, - 328.4, - 320, - 314.9, - 311.1, - 308.2, - 306.7, - 305.5, - 304.4, - 303.2, - 301.9, - 300.5, - 298.9, - 297.2, - 295.3, - 293.7, - 291.5, - 289, - 286.9, - 285.5, - 284.4, - 282.9, - 280.2, - 277, - 274.7, - 273.4, - 272.4, - 270.9, - 267.9, - 262.6, - 257.1, - 252.7, - 248.5, - 244.5, - 240.1, - 235.4, - 229.9, - 222.6, - 214.2, - 208.9, - 204, - 199.2, - 195.5, - 192.1, - 188.6, - 185.1, - 181.5, - 177.8, - 173.7, - 169.1, - 164.8, - 160.3, - 155.5, - 152.4, - 149.4, - 147, - 144, - 141.2, - 139.9, - 138.6, - 137.5, - 136.4, - 135.6, - 134.5, - 132.9, - 131.7, - 130.5, - 129.1, - 127.6, - 126.3, - 124.9, - 123.9, - 122.9, - 121.9, - 120.9, - 119.9, - 119.1, - 118.5, - 117.8, - 116.9, - 116, - 115.2, - 114.5, - 113.8, - 113, - 112.2, - 111.3, - 110.5, - 109.8, - 109, - 108, - 106.9, - 106.1, - 105.1, - 104.1, - 103.1, - 102.3, - 101.5, - 100.5, - 99.5, - 98.8, - 98.1, - 97.5, - 96.9, - 96.4, - 95.9, - 95.6, - 95, - 94.5, - 94, - 93.2, - 92.5, - 91.6, - 90.8, - 90.3, - 89.8, - 89.1, - 88.6, - 88, - 86.6, - 85.8, - 84.9, - 83.7, - 82.9, - 82.1, - 81.2, - 80.2, - 79.5, - 78.9, - 78.4, - 77.3, - 76.4, - 75.3, - 74, - 72.9, - 72.2, - 71.6, - 70.8, - 70.3, - 69.9, - 69.2, - 68.2, - 67.2, - 66.4, - 65.8, - 65.3, - 65, - 64.6, - 63.7, - 63, - 62.4, - 61.6, - 61, - 60.5, - 59.8, - 58.9, - 58.1, - 56.7, - 55.2, - 53.3, - 52.2, - 51.1, - 49.7, - 48.1, - 46.7, - 45.1, - 43.3, - 41.6, - 39.9, - 38.5, - 36.8, - 35.2, - 33.8, - 31.7, - 30.3, - 29, - 25.8, - 24.1, - 22.2, - 20.2, - 18.5, - 16.5, - 14.5, - 12.6, - 12.4, - 12.8, - 12.9, - 12.7, - 12.6, - 12.4, - 12.3, - 12.3, - 12.9, - 13.5, - 13.9, - 14, - 14.2, - 14.5, - 14.9, - 14.6, - 14.2, - 13.8, - 13.4, - 12.8, - 12.1, - 12, - 11.9, - 11.7, - 11.4, - 11.1, - 11, - 11.2, - 11.3, - 11.3, - 11.2, - 11, - 10.8, - 10.6, - 10.6, - 10.4, - 9.9, - 9.4, - 9.3, - 9.3, - 9.2, - 9, - 9, - 8.7, - 8.4, - 8.3, - 8.4, - 8.2, - 8, - 7.6, - 7.3, - 7.2, - 7, - 6.8, - 6.6, - 6.4, - 6.3, - 6.3, - 5.9, - 5.9, - 6.3, - 6.1, - 453.4, - 448.2, - 437.4, - 423.2, - 398.8, - 388.1, - 377.7, - 369.3, - 361.5, - 356.1, - 348.3, - 338.5, - 328, - 317.5, - 312.3, - 309.5, - 307.9, - 306.5, - 305.2, - 304, - 302.9, - 301.7, - 300.5, - 299, - 297.3, - 295.1, - 292.8, - 290.3, - 287.9, - 285.8, - 284.6, - 283, - 281.5, - 279.1, - 276.9, - 274.2, - 272.7, - 271.6, - 270.4, - 268.2, - 263.9, - 259, - 254.6, - 249.8, - 245.3, - 240.8, - 234.7, - 226.5, - 219.7, - 214.7, - 210.5, - 206.2, - 201.5, - 197.2, - 193.2, - 189.7, - 186.6, - 183.1, - 179.5, - 175.3, - 170.6, - 166.2, - 161.5, - 157.2, - 153.5, - 150.1, - 147.1, - 144.5, - 142, - 140.6, - 139.7, - 138.7, - 137.9, - 137, - 135.7, - 134.1, - 132.5, - 131.2, - 129.7, - 128.3, - 127, - 125.8, - 124.7, - 123.6, - 122.5, - 121.5, - 120.4, - 119.4, - 118.3, - 117.5, - 116.8, - 116.1, - 115.3, - 114.4, - 113.7, - 113.1, - 112.4, - 111.4, - 110.4, - 109.7, - 108.8, - 107.8, - 106.7, - 105.8, - 104.7, - 103.7, - 102.9, - 102.1, - 101.3, - 100.5, - 99.7, - 99.1, - 98.5, - 97.8, - 97.2, - 96.6, - 96.3, - 96, - 95.2, - 94.8, - 94.3, - 93.6, - 92.9, - 91.7, - 90.8, - 90.5, - 90, - 89.4, - 88.9, - 88.5, - 86.9, - 86.2, - 85.3, - 84.1, - 83.2, - 82.4, - 81.7, - 80.7, - 79.7, - 78.7, - 78, - 77.1, - 76.5, - 75.3, - 74.2, - 73.5, - 72.5, - 71.5, - 70.6, - 70.1, - 69.6, - 69, - 68.2, - 67.3, - 66.6, - 65.9, - 65.2, - 64.9, - 64.2, - 63.6, - 63.3, - 62.7, - 61.7, - 61, - 60.4, - 59.4, - 58.5, - 58, - 56.7, - 55.4, - 53.3, - 51.9, - 50.9, - 49.6, - 48.1, - 46.5, - 44.7, - 43, - 41.5, - 39.7, - 38.2, - 36.5, - 35, - 33.5, - 31.1, - 29.4, - 28, - 25.4, - 23.9, - 22.1, - 20.3, - 18.4, - 16.4, - 14.7, - 12.7, - 11.8, - 12.6, - 13, - 12.8, - 12.7, - 12.8, - 12.9, - 13.4, - 14, - 14.4, - 14.7, - 14.7, - 14.8, - 14.8, - 14.5, - 14.1, - 13.8, - 13.4, - 12.7, - 12, - 11.8, - 12, - 11.8, - 11.5, - 11.2, - 11.1, - 11.3, - 11.4, - 11.5, - 11.5, - 11.6, - 11.5, - 11.4, - 11.1, - 10.9, - 10.8, - 10.5, - 10.2, - 10, - 9.8, - 9.5, - 9.7, - 9.4, - 9.2, - 9, - 8.8, - 8.8, - 8.9, - 8.6, - 8.3, - 8, - 7.7, - 7.5, - 7.5, - 7.3, - 7, - 6.9, - 7, - 6.8, - 6.9, - 7.1, - 6.8, - 455.9, - 450.7, - 442.5, - 423.8, - 397.7, - 387.9, - 377.3, - 369.4, - 363.4, - 359, - 350.1, - 338.8, - 328.2, - 316, - 311.5, - 309.3, - 307.7, - 306.3, - 304.9, - 303.7, - 302.4, - 301.2, - 300.2, - 299, - 297.1, - 294.7, - 291.6, - 289.6, - 287.6, - 285.3, - 284.1, - 282.5, - 280.2, - 278.2, - 275.7, - 273.5, - 272.2, - 271.1, - 269.7, - 267.3, - 264.3, - 260.5, - 255.8, - 250.9, - 246.2, - 240, - 230.9, - 224.8, - 221, - 217, - 212.6, - 208.3, - 203.7, - 199.3, - 194.9, - 191, - 188, - 184.5, - 181.1, - 177.3, - 173, - 167.4, - 162.7, - 158.8, - 154.6, - 150.5, - 146.9, - 144.4, - 143, - 141.8, - 140.8, - 139.9, - 139.5, - 138.4, - 137, - 135.5, - 133.4, - 131.7, - 130.4, - 129.2, - 127.9, - 126.7, - 125.5, - 124.3, - 123.2, - 122.1, - 121, - 119.8, - 118.7, - 117.6, - 116.7, - 116, - 115.1, - 114.3, - 113.6, - 112.8, - 112.1, - 111.1, - 110.1, - 109.4, - 108.5, - 107.4, - 106.3, - 105.3, - 104.3, - 103.4, - 102.6, - 102, - 101.2, - 100.5, - 99.9, - 99.2, - 98.7, - 98, - 97.5, - 96.9, - 96.4, - 96.2, - 95.4, - 94.9, - 94.2, - 93.6, - 92.8, - 91.8, - 91.2, - 90.8, - 90.5, - 89.8, - 88.8, - 87.9, - 87.3, - 86.7, - 85.5, - 84.5, - 83.7, - 82.9, - 82.2, - 81.4, - 80.1, - 78.9, - 77.9, - 77.2, - 77, - 75.9, - 74.2, - 73.7, - 72.7, - 71.6, - 70.6, - 70.1, - 69.9, - 70.3, - 69.5, - 67.7, - 66.8, - 66.3, - 65.4, - 64.8, - 64.3, - 63.7, - 63.1, - 62.4, - 61.3, - 60.8, - 60.2, - 59.3, - 58.5, - 57.5, - 56.4, - 54.7, - 53.3, - 51.9, - 50.7, - 49.5, - 48, - 46.2, - 44.7, - 43.2, - 41.9, - 40.1, - 37.8, - 36.3, - 34.9, - 33.5, - 31.9, - 29.7, - 27.3, - 25.2, - 23.7, - 22.2, - 20.5, - 18.3, - 16.5, - 14.5, - 12.7, - 12.2, - 13.1, - 13.1, - 13.1, - 13.4, - 13.6, - 13.9, - 14.5, - 14.7, - 15.1, - 15.3, - 15.1, - 14.8, - 14.3, - 13.9, - 13.7, - 13.4, - 12.7, - 12.1, - 11.7, - 11.7, - 11.8, - 11.7, - 11.4, - 11.2, - 11.1, - 11.4, - 11.5, - 11.6, - 11.6, - 11.7, - 11.8, - 11.7, - 11.6, - 11.4, - 11.2, - 11.1, - 10.9, - 10.6, - 10.4, - 10.3, - 10, - 9.7, - 9.7, - 9.7, - 9.4, - 9.2, - 9.3, - 9.3, - 9, - 8.6, - 8.1, - 7.9, - 8, - 7.9, - 7.6, - 7.5, - 7.7, - 7.5, - 7.7, - 8, - 7.4, - 458.4, - 454.5, - 446.8, - 428.6, - 399.1, - 387.4, - 377.8, - 371.5, - 366.3, - 361.5, - 352.8, - 338.6, - 327.4, - 315.4, - 311.7, - 309.5, - 307.6, - 306.1, - 304.7, - 303.4, - 302, - 300.7, - 299.6, - 298.4, - 296.5, - 293.6, - 290.8, - 289.3, - 287.4, - 285, - 283.5, - 281.8, - 279.5, - 277.2, - 274.9, - 272.9, - 271.7, - 270.5, - 269.1, - 267.1, - 265.1, - 261.7, - 256.5, - 251.9, - 246.7, - 238, - 230.6, - 225.9, - 222.3, - 218.8, - 214.6, - 210, - 205.6, - 201.3, - 196.9, - 192.5, - 189.1, - 185.8, - 182.3, - 178.9, - 175.2, - 170.2, - 164.4, - 160.4, - 156.2, - 151.8, - 147.5, - 145.3, - 144.3, - 143.1, - 141.9, - 141.4, - 141.1, - 139.9, - 137.8, - 135.7, - 134, - 132.5, - 131.2, - 130, - 128.8, - 127.5, - 126.3, - 125.1, - 123.8, - 122.6, - 121.4, - 120.2, - 119.1, - 118, - 116.9, - 115.9, - 115.1, - 114.3, - 113.4, - 112.5, - 111.7, - 110.8, - 109.8, - 109, - 108, - 107, - 106, - 105, - 104.1, - 103.3, - 102.6, - 101.9, - 101.3, - 100.7, - 100.2, - 99.5, - 98.9, - 98.1, - 97.6, - 97.3, - 96.7, - 96.6, - 95.9, - 95.3, - 94.5, - 93.5, - 92.7, - 92.1, - 91.6, - 91.3, - 90.8, - 90.1, - 89.2, - 88.6, - 88.1, - 87.4, - 86.2, - 85.1, - 84.2, - 83.4, - 82.6, - 81.9, - 80.5, - 79.4, - 78.4, - 77.4, - 76.6, - 75.8, - 74.4, - 73.7, - 72.8, - 71.9, - 71, - 70.5, - 71, - 72, - 71.4, - 68.2, - 66.7, - 66.5, - 65.6, - 65, - 64.4, - 63.8, - 63, - 62.2, - 61, - 60.4, - 60, - 59.3, - 58.3, - 57.1, - 55.9, - 54.5, - 53.2, - 52, - 50.7, - 49.4, - 47.8, - 46.2, - 44.7, - 43.5, - 42.2, - 40.7, - 38.2, - 35.8, - 34.6, - 33.4, - 32.1, - 30.5, - 27.8, - 25, - 23.5, - 22, - 20.3, - 18, - 16, - 14.1, - 13.2, - 13.4, - 13.5, - 13.5, - 13.6, - 14, - 14.5, - 14.9, - 15.2, - 15.4, - 15.7, - 15.7, - 15.2, - 14.4, - 13.9, - 13.7, - 13.3, - 12.8, - 12.3, - 11.9, - 11.6, - 11.5, - 11.7, - 11.6, - 11.2, - 10.9, - 11, - 11.3, - 11.6, - 11.7, - 11.6, - 11.6, - 11.6, - 11.7, - 11.7, - 11.5, - 11.3, - 11.3, - 11.2, - 10.9, - 10.8, - 10.8, - 10.5, - 10.2, - 10.2, - 10.2, - 10.1, - 9.7, - 9.6, - 9.6, - 9.6, - 9.2, - 8.5, - 8.5, - 8.6, - 8.5, - 8.5, - 8.4, - 8.3, - 8.3, - 8.6, - 9, - 8.3, - 461.3, - 458.2, - 450.3, - 428.9, - 400.9, - 387.7, - 378.9, - 374.5, - 369.3, - 364.3, - 355.5, - 339.3, - 325.5, - 315.5, - 311.9, - 309.7, - 307.9, - 306.2, - 304.7, - 303.3, - 301.8, - 300.3, - 299, - 297.6, - 295.8, - 293.3, - 290.7, - 288.9, - 286.9, - 284.7, - 282.8, - 281.2, - 279.7, - 277.2, - 274.9, - 273, - 271.6, - 270.1, - 268.7, - 267.1, - 265.3, - 262.2, - 257.2, - 252.1, - 245.6, - 237.8, - 232.3, - 227.4, - 223.7, - 220, - 215.8, - 211.1, - 206.5, - 202.7, - 198.9, - 194.2, - 190.5, - 187.1, - 183.5, - 180.2, - 176.6, - 172.6, - 166.9, - 161.9, - 157.8, - 152.6, - 148.5, - 146.6, - 145.4, - 144.2, - 143.2, - 143.3, - 142.9, - 141.3, - 139, - 136.5, - 134.7, - 133.4, - 132.1, - 130.8, - 129.6, - 128.3, - 127, - 125.7, - 124.2, - 123, - 121.8, - 120.7, - 119.7, - 118.5, - 117.4, - 116.3, - 115.3, - 114.4, - 113.4, - 112.6, - 111.7, - 110.8, - 109.9, - 109, - 108, - 107, - 106.1, - 105.2, - 104.4, - 103.7, - 102.9, - 102.2, - 101.5, - 100.8, - 100.3, - 99.8, - 99.2, - 98.5, - 98, - 97.8, - 97.2, - 96.6, - 96.3, - 95.8, - 95.1, - 93.8, - 92.9, - 92.4, - 91.9, - 91.4, - 91, - 90.3, - 89.6, - 89.3, - 88.6, - 88, - 86.7, - 85.9, - 84.8, - 84, - 83.2, - 82.3, - 80.7, - 79.6, - 78.9, - 77.6, - 76.2, - 75.4, - 74.7, - 74, - 73.2, - 72.3, - 71.2, - 70.7, - 71.2, - 72, - 71.4, - 69.1, - 67.2, - 66.8, - 65.9, - 65, - 64.2, - 63.5, - 62.6, - 61.9, - 61, - 60.5, - 59.9, - 59.1, - 58.2, - 56.9, - 55.8, - 54.5, - 53.1, - 51.9, - 50.7, - 49.3, - 47.8, - 46.3, - 44.8, - 43.5, - 42.2, - 40.8, - 39.1, - 36.4, - 34.3, - 33.1, - 31.7, - 30.2, - 28, - 24.5, - 23, - 21.4, - 19.8, - 17.5, - 15.3, - 14.1, - 13.9, - 13.8, - 13.7, - 13.9, - 14.6, - 15, - 15.4, - 15.6, - 15.6, - 16, - 16, - 15.7, - 14.6, - 13.9, - 13.7, - 13.1, - 12.6, - 12.2, - 11.9, - 11.7, - 11.5, - 11.3, - 11.3, - 11.2, - 11, - 10.9, - 11, - 11.4, - 11.6, - 11.8, - 11.8, - 11.7, - 11.7, - 11.6, - 11.6, - 11.6, - 11.5, - 11.4, - 11.3, - 11.2, - 11.1, - 11.1, - 11.1, - 10.9, - 10.7, - 10.6, - 10.6, - 10.4, - 10.1, - 10, - 10, - 9.7, - 9.1, - 9.2, - 9.3, - 9.3, - 9.1, - 9.3, - 9.2, - 9, - 9.3, - 9.8, - 9, - 463.6, - 461.5, - 457.5, - 440, - 410.3, - 389.1, - 381.1, - 376.9, - 371.9, - 365.8, - 355.1, - 338.4, - 323.7, - 316.8, - 312.3, - 309.9, - 308.1, - 306.4, - 304.8, - 303.3, - 301.8, - 300.2, - 298.9, - 297.4, - 295.8, - 293.6, - 290.7, - 288.7, - 286.5, - 284.1, - 282.1, - 280.8, - 279.7, - 276.9, - 274.8, - 273.1, - 271.7, - 270.4, - 269.1, - 267.8, - 265.7, - 262.6, - 257.7, - 249, - 243.1, - 238.8, - 233.2, - 228.5, - 225, - 221.4, - 217.2, - 212.4, - 207.7, - 203.5, - 199.6, - 195.1, - 191.9, - 188.6, - 184.9, - 181.4, - 177.8, - 174, - 169.4, - 163.6, - 158.9, - 153.7, - 150.5, - 148, - 146.5, - 145.4, - 145, - 145.5, - 144.7, - 142.9, - 140.5, - 138, - 135.8, - 134.2, - 132.9, - 131.6, - 130.3, - 129, - 127.6, - 126.2, - 124.8, - 123.6, - 122.5, - 121.4, - 120.2, - 119.1, - 117.9, - 116.9, - 115.9, - 114.9, - 113.9, - 113.1, - 112.2, - 111.3, - 110.3, - 109.3, - 108.3, - 107.4, - 106.5, - 105.6, - 104.9, - 104.1, - 103.3, - 102.6, - 101.9, - 101.2, - 100.7, - 100.1, - 99.5, - 99, - 98.5, - 98.3, - 97.7, - 96.9, - 96.4, - 95.9, - 95, - 93.9, - 93.3, - 92.8, - 92.1, - 91.5, - 91, - 90.2, - 89.6, - 89.1, - 88.3, - 87.7, - 86.8, - 86.1, - 85.3, - 84.2, - 83.1, - 82.1, - 80.5, - 79.9, - 79.5, - 77.9, - 76.5, - 75.6, - 74.8, - 74, - 73.3, - 72.4, - 71.5, - 71, - 71.2, - 71.7, - 71.3, - 69.8, - 67.9, - 67.5, - 66.8, - 65, - 63.9, - 62.9, - 62.1, - 61.5, - 60.9, - 60.3, - 59.7, - 59, - 58.2, - 57, - 55.8, - 54.5, - 53.3, - 52.1, - 50.7, - 49.1, - 47.9, - 46.5, - 44.9, - 43.6, - 42.1, - 40.6, - 39.1, - 37.1, - 34.3, - 32.8, - 31.5, - 30.1, - 27.6, - 24.1, - 22, - 20.4, - 18.9, - 17, - 14.8, - 14.2, - 14.2, - 14.1, - 14, - 14.5, - 15.6, - 15.9, - 15.9, - 16, - 16.1, - 16.2, - 15.8, - 15, - 14.1, - 13.7, - 13.3, - 12.6, - 12.3, - 11.9, - 11.6, - 11.6, - 11.4, - 11.1, - 10.9, - 11, - 10.9, - 11, - 11.2, - 11.5, - 11.6, - 11.9, - 12, - 12, - 12, - 11.9, - 11.7, - 11.6, - 11.5, - 11.4, - 11.3, - 11.3, - 11.2, - 11.3, - 11.3, - 11.3, - 11.1, - 11, - 10.9, - 10.8, - 10.7, - 10.5, - 10.5, - 10.3, - 9.9, - 9.7, - 9.9, - 10, - 9.6, - 9.8, - 10, - 9.9, - 10, - 10.1, - 9.3, - 465.7, - 464.3, - 461.7, - 450.1, - 425, - 392.4, - 382.6, - 377.6, - 372.3, - 365.8, - 356.3, - 344.3, - 334.9, - 322.9, - 312.7, - 310, - 308.3, - 306.6, - 304.9, - 303.4, - 301.9, - 300.4, - 298.9, - 297.6, - 296.1, - 293.9, - 290.7, - 288.4, - 286.7, - 284.2, - 281.9, - 280.4, - 279.2, - 276.6, - 274.8, - 273.3, - 272, - 270.7, - 269.5, - 268.2, - 266.2, - 263.4, - 257.9, - 248.8, - 244.2, - 238.7, - 233.5, - 229.4, - 225.7, - 222.7, - 218.9, - 214.3, - 209.4, - 204.7, - 200.4, - 196.5, - 193.4, - 190.2, - 186.4, - 182.6, - 178.8, - 175, - 171.1, - 165.2, - 159.2, - 155.4, - 152.7, - 149.5, - 147.9, - 147.1, - 147.8, - 147.7, - 146.4, - 144.5, - 142.3, - 139.7, - 137, - 135.1, - 133.7, - 132.4, - 131, - 129.7, - 128.3, - 126.9, - 125.6, - 124.4, - 123.2, - 121.9, - 120.7, - 119.6, - 118.4, - 117.4, - 116.5, - 115.6, - 114.6, - 113.7, - 112.8, - 111.8, - 110.8, - 109.8, - 108.8, - 107.8, - 106.9, - 106.1, - 105.3, - 104.5, - 103.7, - 103, - 102.3, - 101.5, - 100.9, - 100.3, - 99.8, - 99.3, - 98.9, - 98.5, - 98, - 97.2, - 96.6, - 95.8, - 95.2, - 94.1, - 93.5, - 93, - 92.5, - 91.9, - 91.4, - 90.6, - 89.8, - 89.3, - 88.4, - 87.5, - 86.7, - 86.1, - 85.5, - 84.2, - 82.9, - 82, - 81, - 80.2, - 79.6, - 78.1, - 76.4, - 75.7, - 74.7, - 73.6, - 72.9, - 72.2, - 71.4, - 70.9, - 70.9, - 71.2, - 71, - 69.6, - 67.6, - 67.3, - 66.3, - 64.4, - 63.3, - 62.4, - 61.7, - 61.2, - 60.4, - 60.1, - 59.6, - 59, - 58, - 56.9, - 55.8, - 54.7, - 53.5, - 52.4, - 51.2, - 49.1, - 48, - 46.8, - 45.3, - 43.9, - 42.1, - 40.4, - 38.8, - 37.2, - 34.3, - 32.6, - 31.2, - 29.6, - 27.7, - 24.4, - 21.2, - 19.5, - 18.1, - 16.6, - 14.8, - 14.7, - 14.5, - 14.4, - 14.7, - 15.5, - 16.1, - 16.1, - 16.1, - 16.3, - 16.3, - 16.1, - 15.3, - 14.4, - 13.9, - 13.5, - 13, - 12.4, - 11.9, - 11.7, - 11.4, - 11.4, - 11.2, - 10.9, - 10.6, - 10.7, - 10.9, - 11.2, - 11.3, - 11.4, - 11.6, - 11.8, - 11.9, - 12, - 12.1, - 12.1, - 11.9, - 11.8, - 11.6, - 11.6, - 11.6, - 11.5, - 11.5, - 11.6, - 11.7, - 11.5, - 11.4, - 11.4, - 11.3, - 11.2, - 11.2, - 10.9, - 10.9, - 10.9, - 10.6, - 10.3, - 10.4, - 10.5, - 10.2, - 10.2, - 10.6, - 10.6, - 10.5, - 10.4, - 9.7, - 467.6, - 466.6, - 465.5, - 455.9, - 427.4, - 395.7, - 383.6, - 377, - 370.6, - 363.1, - 355.7, - 351.4, - 345, - 330.8, - 316.2, - 310.9, - 308.9, - 307.2, - 305.2, - 303.5, - 302.1, - 300.5, - 299, - 297.7, - 296.3, - 294.1, - 290.8, - 288.4, - 286.7, - 284.4, - 281.8, - 280.3, - 279.1, - 277, - 275.2, - 273.7, - 272, - 270.7, - 269.5, - 268.1, - 265.5, - 262.3, - 256.4, - 249.2, - 243.4, - 239.1, - 235.2, - 231, - 226.9, - 223.5, - 220.4, - 216.4, - 211.4, - 206.5, - 202.3, - 198.7, - 195.1, - 191.7, - 187.7, - 184, - 179.9, - 175.9, - 172.4, - 166.4, - 160.6, - 157.1, - 154.3, - 151, - 149.4, - 149.4, - 150.5, - 149.6, - 147.9, - 146.1, - 144.1, - 141.5, - 138.6, - 136.1, - 134.5, - 133.2, - 131.7, - 130.5, - 129.1, - 127.7, - 126.4, - 125.1, - 123.8, - 122.5, - 121.3, - 120.2, - 119.1, - 118.2, - 117.3, - 116.3, - 115.3, - 114.4, - 113.4, - 112.3, - 111.2, - 110.2, - 109.2, - 108.3, - 107.5, - 106.7, - 105.8, - 105, - 104.2, - 103.4, - 102.7, - 101.9, - 101.1, - 100.5, - 100.1, - 99.8, - 99.7, - 99.2, - 98.4, - 97.5, - 96.6, - 95.8, - 95.5, - 94.4, - 93.7, - 93.3, - 92.9, - 92.4, - 91.8, - 91.1, - 90.1, - 89.6, - 88.7, - 87.8, - 86.9, - 86.1, - 85.2, - 84.2, - 83.2, - 82, - 80.9, - 80.2, - 79.6, - 78.2, - 76.7, - 75.9, - 74.8, - 73.7, - 72.9, - 72.1, - 71.5, - 70.9, - 70.5, - 70.2, - 69.7, - 68.3, - 67.3, - 66.3, - 65.2, - 64, - 62.9, - 62.1, - 61.5, - 60.8, - 60.1, - 59.8, - 59.2, - 58.7, - 57.8, - 56.9, - 55.9, - 54.9, - 53.8, - 52.7, - 51.4, - 49.4, - 48.4, - 47.3, - 45.9, - 44.6, - 42.9, - 40.9, - 38.9, - 37, - 34.3, - 32.6, - 31.3, - 29.4, - 27.2, - 24.2, - 20.5, - 18.6, - 16.9, - 15.6, - 15.2, - 15, - 14.8, - 15, - 15.8, - 16.4, - 16.3, - 16.4, - 16.6, - 16.7, - 16.4, - 15.8, - 14.9, - 14.3, - 13.8, - 13.4, - 12.9, - 12, - 11.7, - 11.5, - 11.4, - 11.3, - 11.1, - 10.7, - 10.7, - 10.8, - 11, - 11.2, - 11.4, - 11.5, - 11.7, - 11.8, - 11.9, - 11.8, - 11.9, - 12, - 12, - 12, - 11.9, - 11.8, - 11.8, - 11.8, - 11.8, - 11.9, - 11.9, - 11.9, - 11.8, - 11.6, - 11.7, - 11.7, - 11.5, - 11.2, - 11, - 11, - 11.1, - 11, - 11, - 10.9, - 10.7, - 10.8, - 11.1, - 10.9, - 10.9, - 10.7, - 9.8, - 469, - 468.5, - 468, - 456.9, - 427.1, - 398, - 384.3, - 376.6, - 368.9, - 360.3, - 353.1, - 349, - 341.7, - 329, - 318.8, - 313, - 309.5, - 307.6, - 305.5, - 303.8, - 302.3, - 300.8, - 299.3, - 298, - 296.7, - 295, - 291.9, - 288.6, - 286.8, - 284.4, - 281.9, - 280.4, - 279.2, - 277.4, - 275.6, - 273.8, - 271.9, - 270.6, - 269.2, - 267.1, - 263.7, - 259.8, - 254.5, - 248.4, - 244.2, - 240.7, - 236.8, - 232.8, - 228.7, - 224.7, - 221.7, - 218.2, - 213.7, - 209.1, - 204.8, - 201.1, - 197.3, - 193.2, - 188.5, - 185, - 181.2, - 176.9, - 173.2, - 167.5, - 162.5, - 158.5, - 154.9, - 152, - 151.1, - 151.9, - 152.7, - 151.3, - 149.7, - 147.7, - 145.1, - 142.8, - 140.3, - 137.5, - 135.5, - 134, - 132.5, - 131.2, - 129.8, - 128.3, - 127.1, - 126, - 124.7, - 123.2, - 122, - 120.9, - 119.9, - 118.9, - 118, - 116.9, - 115.9, - 115, - 114, - 112.9, - 111.9, - 110.8, - 109.8, - 109, - 108.1, - 107.2, - 106.4, - 105.5, - 104.6, - 103.9, - 103.1, - 102.2, - 101.4, - 100.8, - 100.3, - 99.9, - 99.9, - 99.5, - 98.8, - 98.1, - 97.1, - 95.9, - 95.3, - 94.7, - 94, - 93.6, - 93, - 92.5, - 92.2, - 92, - 90.9, - 89.4, - 88.6, - 87.9, - 87.1, - 86.2, - 85.2, - 84.1, - 83.2, - 82.1, - 81, - 80.1, - 79.4, - 78.3, - 77.2, - 76.1, - 74.7, - 73.6, - 72.6, - 71.9, - 71.6, - 70.9, - 70.1, - 69.3, - 68.8, - 68, - 67.1, - 65.7, - 64.7, - 63.9, - 63, - 62, - 61.3, - 60.5, - 59.8, - 59.6, - 59, - 58.4, - 57.5, - 56.7, - 55.9, - 55, - 54.1, - 53, - 51.6, - 50.1, - 49, - 47.8, - 46.6, - 45.5, - 44.4, - 42.4, - 39.9, - 38, - 34.8, - 32.5, - 31.3, - 29.8, - 27.2, - 22.9, - 19.9, - 17.8, - 16, - 15.6, - 15.5, - 15.3, - 15.2, - 15.9, - 16.6, - 16.7, - 16.6, - 17, - 17.2, - 17, - 16.3, - 15.4, - 14.5, - 14, - 13.6, - 13.1, - 12.4, - 11.8, - 11.6, - 11.5, - 11.4, - 11.3, - 11, - 10.7, - 10.8, - 10.9, - 11.1, - 11.3, - 11.5, - 11.7, - 11.7, - 11.8, - 11.8, - 11.8, - 11.9, - 11.9, - 12, - 12.1, - 12.1, - 12.1, - 12.1, - 12.1, - 12, - 11.9, - 12, - 12.1, - 12.1, - 12, - 12.1, - 12.1, - 11.8, - 11.5, - 11.3, - 11.3, - 11.4, - 11.5, - 11.4, - 11.3, - 11.3, - 11.4, - 11.6, - 11.5, - 11.6, - 11.1, - 9.8, - 471.5, - 471.2, - 470.5, - 460.9, - 430.9, - 401.4, - 388.1, - 378.3, - 368.6, - 361.2, - 354, - 346.3, - 338.2, - 327.6, - 320.6, - 315.5, - 310.6, - 308, - 306, - 304.3, - 302.7, - 301.2, - 299.8, - 298.5, - 297.1, - 295.5, - 292.2, - 288.7, - 287.1, - 284.9, - 282.4, - 280.8, - 279.6, - 278, - 276.3, - 274.3, - 272.4, - 270.9, - 269.2, - 267.4, - 265, - 261.2, - 255.8, - 249.4, - 245.8, - 242.3, - 238.4, - 234.7, - 230.7, - 226.6, - 223.2, - 219.7, - 215.5, - 211, - 206.7, - 202.8, - 198.8, - 194, - 189.7, - 186.2, - 182.6, - 178, - 173.6, - 169, - 164.6, - 159.3, - 155.1, - 153.3, - 153.3, - 154.3, - 154.3, - 153.2, - 151.9, - 149.7, - 146.6, - 143.8, - 141.4, - 138.8, - 136.3, - 134.8, - 133.2, - 131.8, - 130.5, - 129.1, - 127.9, - 127.1, - 125.8, - 124.1, - 122.9, - 121.7, - 120.6, - 119.6, - 118.7, - 117.5, - 116.5, - 115.6, - 114.6, - 113.6, - 112.5, - 111.5, - 110.6, - 109.6, - 108.7, - 107.8, - 106.9, - 106, - 105.1, - 104.3, - 103.5, - 102.7, - 101.9, - 101.2, - 100.6, - 100, - 99.8, - 99.5, - 99, - 98.4, - 97.6, - 96.6, - 95.7, - 94.8, - 94.1, - 93.4, - 93, - 92.6, - 92.3, - 92.1, - 91.1, - 89.4, - 88.7, - 88, - 87.2, - 86.1, - 85.2, - 84, - 83, - 82, - 81, - 80.3, - 79.5, - 78.3, - 77.5, - 76.4, - 74.5, - 72.9, - 71.8, - 71.1, - 70.8, - 70.3, - 69.6, - 69.1, - 68.6, - 67.5, - 66.1, - 65, - 64.3, - 63.8, - 62.9, - 62, - 61.2, - 60.3, - 59.5, - 59.1, - 58.6, - 57.9, - 57.2, - 56.6, - 56, - 55.3, - 54.4, - 53.3, - 52, - 51, - 50, - 48.6, - 47.1, - 46.1, - 45.4, - 44.1, - 41.1, - 38.5, - 36, - 33.2, - 30.9, - 29.5, - 26.6, - 21.2, - 19.1, - 17.4, - 15.5, - 15.5, - 15.9, - 15.6, - 15.8, - 16.8, - 16.9, - 16.9, - 16.9, - 17.4, - 17.5, - 16.8, - 16, - 14.8, - 14.2, - 13.8, - 13.3, - 12.7, - 12, - 11.7, - 11.5, - 11.5, - 11.3, - 11, - 10.8, - 10.7, - 11.1, - 11.2, - 11.3, - 11.5, - 11.6, - 11.6, - 11.6, - 11.7, - 11.8, - 11.9, - 11.9, - 12, - 12.1, - 12.1, - 12, - 12.2, - 12.3, - 12.4, - 12.3, - 12.2, - 12.1, - 12.2, - 12.2, - 12.2, - 12.2, - 12.2, - 11.9, - 11.7, - 11.7, - 11.9, - 11.8, - 11.9, - 11.9, - 11.7, - 11.8, - 12, - 12.1, - 12.1, - 12.1, - 11.5, - 9.6, - 474.4, - 473.6, - 472.5, - 465.8, - 439.3, - 410.4, - 395.6, - 383.2, - 371, - 362.9, - 356.3, - 346.1, - 336.1, - 328.1, - 322.5, - 317.6, - 312.8, - 308.9, - 306.7, - 304.8, - 303.1, - 301.6, - 300.3, - 299.1, - 297.8, - 296.1, - 293.2, - 289.6, - 288, - 286.2, - 283.9, - 281.9, - 280.6, - 279.2, - 277.4, - 275.4, - 273.3, - 271.8, - 270.1, - 268.5, - 266.9, - 264.3, - 259.3, - 252.1, - 247.6, - 243.9, - 240.2, - 236.7, - 232.7, - 228.6, - 225, - 221, - 216.9, - 212.5, - 208.1, - 203.5, - 199.2, - 195, - 191.4, - 187.4, - 183, - 178.7, - 174.6, - 170.4, - 165.1, - 158.8, - 155.8, - 155, - 155.5, - 156, - 156, - 155.5, - 153.9, - 151.6, - 148.4, - 145.2, - 142.3, - 139.3, - 137, - 135.6, - 134.1, - 132.6, - 131.3, - 130, - 128.8, - 127.6, - 126.4, - 124.9, - 123.7, - 122.5, - 121.4, - 120.4, - 119.4, - 118.2, - 117.2, - 116.3, - 115.3, - 114.2, - 113.1, - 112.2, - 111.3, - 110.3, - 109.3, - 108.4, - 107.4, - 106.5, - 105.7, - 104.8, - 103.9, - 103.1, - 102.4, - 101.7, - 101, - 100.4, - 100, - 99.6, - 98.9, - 98.3, - 97.5, - 97, - 96.3, - 95.1, - 94.3, - 93.5, - 93.2, - 93, - 92.4, - 91.8, - 90.5, - 89.6, - 89, - 88.2, - 87.3, - 86.3, - 85.4, - 84.4, - 83.1, - 81.9, - 81, - 80.3, - 79.2, - 78.2, - 77.8, - 76.9, - 74.8, - 72.7, - 71.5, - 70.7, - 70.1, - 69.7, - 69.3, - 69, - 68.5, - 66.8, - 65.5, - 64.8, - 64.1, - 63.5, - 62.9, - 62, - 61.3, - 60.5, - 59.3, - 58.5, - 58.1, - 57.6, - 57.2, - 56.8, - 56.1, - 55.4, - 54.5, - 53.6, - 52.6, - 51.6, - 50.9, - 50, - 48.2, - 46.5, - 45.7, - 44.2, - 41.3, - 37.6, - 36, - 34.2, - 31, - 28.5, - 24.5, - 20, - 18.4, - 17, - 15.7, - 16.1, - 16.2, - 16, - 16.5, - 17.3, - 17.2, - 17.7, - 18.1, - 17.9, - 17.3, - 16.6, - 15.5, - 14.6, - 14, - 13.5, - 13, - 12.3, - 12, - 11.8, - 11.5, - 11.4, - 11.2, - 10.9, - 10.9, - 11, - 11.3, - 11.4, - 11.4, - 11.5, - 11.6, - 11.6, - 11.6, - 11.7, - 11.9, - 11.9, - 12, - 12.1, - 12.2, - 12.3, - 12.1, - 12.2, - 12.3, - 12.6, - 12.7, - 12.6, - 12.4, - 12.3, - 12.3, - 12.4, - 12.5, - 12.4, - 12.3, - 12.2, - 12.2, - 12.3, - 12.4, - 12.3, - 12.4, - 12.2, - 12.3, - 12.5, - 12.5, - 12.6, - 12.5, - 11.5, - 9.5, - 476.5, - 475.5, - 474.3, - 471.7, - 453.9, - 432.7, - 417.7, - 400.5, - 383.6, - 368.8, - 359.8, - 347.9, - 334.4, - 327.8, - 323.2, - 318.2, - 313.2, - 309.4, - 307.2, - 305.3, - 303.5, - 302.2, - 301, - 299.8, - 298.6, - 297.2, - 294.8, - 291.4, - 289.6, - 288.3, - 286.3, - 283.9, - 282, - 280.6, - 278.9, - 276.8, - 274.7, - 272.9, - 271.2, - 269.9, - 268.7, - 266.6, - 263.2, - 255.4, - 249.3, - 245.5, - 242.1, - 238.6, - 234.7, - 230.6, - 226.7, - 222.7, - 218.6, - 214.2, - 209.5, - 204.9, - 200.5, - 196.5, - 192.2, - 187.6, - 183.8, - 180, - 176, - 170.6, - 163.6, - 159.1, - 157.3, - 156.9, - 157.3, - 157.4, - 158.3, - 158.1, - 155.6, - 153.1, - 150, - 146.5, - 142.6, - 139.7, - 138.2, - 137, - 135.4, - 134, - 132.5, - 131, - 129.7, - 128.4, - 127, - 125.8, - 124.6, - 123.4, - 122.3, - 121.2, - 120.1, - 119.1, - 118.1, - 117, - 115.9, - 114.8, - 113.9, - 112.8, - 111.8, - 110.8, - 109.8, - 108.9, - 108, - 107, - 106.1, - 105.2, - 104.3, - 103.4, - 102.7, - 102.1, - 101.5, - 101, - 100.3, - 99.6, - 98.7, - 98.1, - 97.7, - 97.3, - 96.7, - 95.4, - 94.5, - 93.9, - 93.6, - 93.4, - 92.7, - 91.9, - 91, - 90, - 89.1, - 88.3, - 87.4, - 86.6, - 85.6, - 84.5, - 83.4, - 82.1, - 81.1, - 80.1, - 78.9, - 78, - 77.5, - 76.8, - 75.4, - 73.7, - 72.6, - 71.3, - 70.5, - 69.8, - 69.1, - 68.8, - 68.7, - 67.6, - 65.7, - 64.5, - 64, - 63.4, - 62.8, - 62.1, - 61.3, - 60.5, - 59.4, - 58.6, - 57.9, - 57.4, - 57.2, - 56.6, - 55.7, - 55.1, - 54.4, - 53.5, - 52.9, - 51.7, - 50.9, - 50.2, - 49.3, - 47.4, - 45.7, - 43.8, - 39.5, - 35.8, - 35.3, - 34.5, - 31.6, - 26.7, - 22.2, - 19.5, - 18.1, - 16.8, - 16.5, - 16.8, - 16.6, - 16.5, - 17.4, - 17.8, - 17.8, - 18.3, - 18.6, - 18, - 17.2, - 16.1, - 15.2, - 14.4, - 13.8, - 13.4, - 12.9, - 12.3, - 12.3, - 12.1, - 11.5, - 11.4, - 11.3, - 11.2, - 11.3, - 11.2, - 11.2, - 11.6, - 11.7, - 11.7, - 11.6, - 11.6, - 11.7, - 11.7, - 11.9, - 12, - 12, - 12.1, - 12.2, - 12.3, - 12.3, - 12.3, - 12.4, - 12.6, - 12.7, - 12.9, - 12.8, - 12.6, - 12.5, - 12.5, - 12.6, - 12.7, - 12.8, - 12.6, - 12.5, - 12.4, - 12.6, - 12.7, - 12.8, - 12.7, - 12.8, - 13, - 13, - 13, - 12.4, - 11.1, - 9.4, - 478.2, - 477.3, - 476.5, - 475.9, - 471.8, - 460.1, - 442.6, - 422.1, - 400.1, - 380.3, - 367.1, - 352.8, - 335.6, - 327.5, - 323.1, - 318.5, - 313.7, - 309.9, - 307.8, - 306.3, - 304.4, - 303, - 301.8, - 300.6, - 299.3, - 298.1, - 296.5, - 294.3, - 292.6, - 291.6, - 289.7, - 286.8, - 284.2, - 282.5, - 280.8, - 278.6, - 276.9, - 274.8, - 272.5, - 271, - 269.9, - 268.2, - 265.3, - 257.9, - 251.1, - 247, - 243.9, - 240.6, - 236.8, - 232.7, - 228.7, - 224.5, - 220.5, - 216.1, - 211.2, - 206.3, - 201.7, - 197.5, - 192.7, - 188.8, - 185.5, - 181.6, - 176.8, - 168.2, - 162.8, - 160.3, - 159.2, - 159.1, - 159.3, - 160, - 160.8, - 159.5, - 157.1, - 154.4, - 151.2, - 146.9, - 143.4, - 141.3, - 140.6, - 139.3, - 137.3, - 135.5, - 134, - 132.4, - 130.9, - 129.4, - 128, - 126.6, - 125.4, - 124.2, - 123.1, - 122, - 120.9, - 119.8, - 118.7, - 117.6, - 116.6, - 115.5, - 114.4, - 113.3, - 112.4, - 111.4, - 110.4, - 109.4, - 108.5, - 107.6, - 106.6, - 105.7, - 104.8, - 103.9, - 103.1, - 102.6, - 102.1, - 101.5, - 100.6, - 99.7, - 98.8, - 98.3, - 97.8, - 97.4, - 96.8, - 95.8, - 94.7, - 94.3, - 94, - 93.5, - 92.7, - 92, - 91.4, - 90.3, - 89.2, - 88.4, - 87.6, - 86.8, - 85.7, - 84.6, - 83.7, - 82.6, - 81.6, - 80.1, - 78.8, - 78.1, - 77.2, - 76.2, - 75.3, - 74.2, - 73.4, - 72, - 70.7, - 69.9, - 69, - 68.3, - 67.6, - 66.8, - 65.6, - 64.3, - 63.8, - 63.1, - 62.6, - 61.9, - 61.1, - 60.3, - 59.4, - 58.5, - 57.7, - 57.1, - 56.7, - 56, - 55.3, - 54.6, - 53.9, - 53.1, - 52.1, - 51.1, - 50.5, - 49.8, - 49, - 47.4, - 45.3, - 43, - 37.6, - 34.7, - 34.2, - 33.5, - 29.8, - 24.7, - 21.3, - 19.4, - 18, - 16.7, - 16.3, - 17.1, - 17, - 17.4, - 18.2, - 18.4, - 18.8, - 18.9, - 18.7, - 17.9, - 17, - 15.7, - 15, - 14.2, - 13.7, - 13.2, - 12.8, - 12.4, - 12, - 11.8, - 11.5, - 11.5, - 11.4, - 11.4, - 11.5, - 11.4, - 11.4, - 11.7, - 11.7, - 11.7, - 11.7, - 11.6, - 11.8, - 12, - 12, - 12.1, - 12.1, - 12.2, - 12.4, - 12.4, - 12.5, - 12.5, - 12.5, - 12.6, - 12.8, - 12.9, - 12.9, - 12.8, - 12.8, - 12.8, - 13, - 13.2, - 13.1, - 12.9, - 12.7, - 12.7, - 12.8, - 13, - 13.1, - 13.1, - 13.3, - 13.4, - 13.5, - 13.4, - 12.3, - 10.8, - 9.4, - 480.2, - 479.7, - 479, - 478.3, - 478, - 475.5, - 462.2, - 438.9, - 415.2, - 392.1, - 375.3, - 360.8, - 345, - 328.8, - 322.5, - 318.2, - 314.2, - 310.7, - 308.9, - 307.5, - 305.9, - 304.1, - 302.7, - 301.5, - 300.3, - 299.2, - 298.2, - 297.2, - 296.2, - 295.6, - 294, - 290.1, - 286.7, - 284.8, - 283.1, - 280.9, - 279.2, - 276.8, - 274.3, - 272, - 270.4, - 268.9, - 266.4, - 261.2, - 254.7, - 250.2, - 245.6, - 242.3, - 238.7, - 234.9, - 231, - 226.6, - 222.4, - 218, - 212.9, - 206.2, - 200.9, - 196.7, - 193.2, - 189.9, - 186.6, - 182.3, - 175.3, - 166.8, - 163.4, - 161.9, - 161.1, - 161.1, - 162.1, - 163.8, - 163.1, - 160.9, - 158.6, - 155.9, - 151.9, - 147.6, - 145.1, - 144, - 143.4, - 141.6, - 139.4, - 137.2, - 135.5, - 134.1, - 132.4, - 130.6, - 129, - 127.6, - 126.2, - 125, - 123.9, - 122.8, - 121.7, - 120.5, - 119.4, - 118.3, - 117.3, - 116.1, - 115, - 113.9, - 113, - 112.1, - 111, - 110, - 109, - 108, - 107.1, - 106.1, - 105.2, - 104.5, - 103.9, - 103.3, - 102.7, - 102.1, - 101, - 100, - 99.2, - 98.6, - 98.2, - 97.7, - 97, - 96.2, - 95.2, - 94.6, - 94.3, - 93.7, - 92.8, - 92.1, - 91.7, - 90.7, - 89.3, - 88.6, - 87.8, - 87, - 85.8, - 84.7, - 84, - 83.7, - 82.9, - 80.9, - 79.2, - 78.6, - 77.6, - 76.5, - 75.6, - 74.3, - 73.4, - 71.9, - 70.6, - 69.8, - 68.9, - 67.9, - 66.9, - 66.2, - 65.2, - 64.2, - 63.3, - 62.5, - 61.9, - 61.3, - 60.8, - 60, - 59.4, - 58.4, - 57.5, - 56.9, - 56.2, - 55.6, - 54.8, - 54, - 53.2, - 52.3, - 51.4, - 50.7, - 50, - 49.3, - 48.3, - 46, - 44.6, - 41.8, - 37.2, - 34.3, - 33, - 31.8, - 27.2, - 22.9, - 20.7, - 19.3, - 17.9, - 16.8, - 16.8, - 17.4, - 17.6, - 18.2, - 18.7, - 19, - 19.4, - 19.2, - 18.6, - 17.6, - 16.6, - 15.6, - 14.8, - 14.2, - 13.6, - 13.1, - 12.8, - 12.4, - 11.8, - 11.6, - 11.5, - 11.6, - 11.5, - 11.4, - 11.5, - 11.6, - 11.8, - 11.9, - 12, - 12, - 12, - 11.9, - 11.9, - 12.1, - 12.2, - 12.3, - 12.3, - 12.3, - 12.5, - 12.5, - 12.6, - 12.7, - 12.7, - 12.7, - 12.8, - 13, - 13.1, - 13, - 13.1, - 13.2, - 13.3, - 13.5, - 13.5, - 13.3, - 13.1, - 13, - 13.2, - 13.4, - 13.5, - 13.7, - 14.1, - 14.3, - 14.2, - 13.6, - 12.3, - 11, - 9.6, - 482.5, - 481.8, - 480.8, - 480.2, - 479.9, - 479.1, - 473.1, - 451.9, - 428.4, - 404.2, - 383.1, - 368.2, - 353.5, - 332.4, - 321.9, - 317.8, - 314.4, - 311.9, - 310.4, - 309, - 307.5, - 305.6, - 303.6, - 302.7, - 301.7, - 300.6, - 299.6, - 298.9, - 298.3, - 297.6, - 296.1, - 293.1, - 289.5, - 287.1, - 284.9, - 282.4, - 280.5, - 278.2, - 275.8, - 273, - 271.2, - 269.7, - 267.3, - 264.3, - 260.6, - 255.9, - 248.5, - 243.8, - 240.5, - 236.9, - 232.9, - 228.8, - 224.7, - 220.1, - 213.2, - 205.7, - 200.7, - 195.4, - 191.6, - 189.1, - 186, - 179.3, - 171.4, - 167.2, - 164.9, - 163.7, - 163.1, - 164, - 165.6, - 166, - 164.6, - 162.4, - 160.1, - 157.1, - 152.9, - 150.1, - 148, - 147.4, - 145.9, - 143.8, - 141.6, - 139.2, - 136.9, - 135.4, - 133.9, - 132, - 130.1, - 128.6, - 127.2, - 125.9, - 124.8, - 123.7, - 122.5, - 121.3, - 120.3, - 119.2, - 118, - 116.8, - 115.6, - 114.6, - 113.7, - 112.7, - 111.6, - 110.5, - 109.5, - 108.4, - 107.5, - 106.6, - 105.6, - 105, - 104.6, - 103.9, - 103.2, - 102.6, - 101.5, - 100.6, - 99.8, - 99.3, - 98.8, - 98.2, - 97.2, - 96.5, - 95.8, - 95.1, - 94.5, - 93.7, - 93, - 92.3, - 91.7, - 90.6, - 89.4, - 88.6, - 87.8, - 87, - 86, - 84.8, - 84.2, - 83.6, - 82.8, - 81.3, - 79.9, - 79.2, - 78.6, - 78, - 76.7, - 74.4, - 73.2, - 72, - 70.7, - 69.7, - 68.8, - 67.8, - 66.8, - 65.9, - 64.9, - 64, - 63, - 62.2, - 61.4, - 60.8, - 60.4, - 59.6, - 58.9, - 58.1, - 57.2, - 56.5, - 55.8, - 55.2, - 54.4, - 53.4, - 52.5, - 51.6, - 50.9, - 50.2, - 49.6, - 48.8, - 47.3, - 45, - 43.5, - 40.6, - 38.2, - 35.3, - 32.2, - 29.3, - 24.9, - 22.3, - 20.3, - 19.3, - 18.6, - 17.9, - 18, - 18, - 18.3, - 18.8, - 19.3, - 19.8, - 19.9, - 19.5, - 18.6, - 17.4, - 16.4, - 15.6, - 14.8, - 14.2, - 13.6, - 13.2, - 12.7, - 12.3, - 11.9, - 11.7, - 11.7, - 11.6, - 11.6, - 11.6, - 11.7, - 11.8, - 12.1, - 12, - 12.1, - 12.2, - 12.3, - 12.3, - 12.2, - 12.3, - 12.3, - 12.4, - 12.5, - 12.4, - 12.5, - 12.6, - 12.7, - 12.8, - 12.8, - 12.7, - 12.9, - 13.3, - 13.4, - 13.3, - 13.3, - 13.4, - 13.4, - 13.6, - 13.7, - 13.6, - 13.5, - 13.3, - 13.4, - 13.6, - 13.9, - 14.1, - 14.7, - 15, - 14.7, - 13.9, - 12.6, - 11.1, - 9.7, - 484.2, - 483.6, - 482.6, - 481.9, - 481.5, - 481, - 479.1, - 464.7, - 441.5, - 416, - 390.4, - 371.9, - 356.9, - 334.1, - 321.8, - 318.5, - 315.5, - 312.8, - 311.4, - 310.1, - 308.4, - 306.5, - 304.9, - 303.8, - 303, - 301.9, - 301.1, - 300.5, - 299.9, - 299, - 297.2, - 295, - 292, - 289.2, - 286.4, - 283.8, - 281.5, - 279.6, - 277.3, - 274.5, - 272.5, - 271, - 268.9, - 265.6, - 262.6, - 259.2, - 254, - 246.6, - 242.1, - 238.5, - 234.6, - 230.6, - 226.6, - 221.4, - 212.2, - 205.2, - 200, - 194.6, - 190.4, - 187.4, - 183.1, - 176.2, - 171.7, - 168.3, - 166.4, - 165.5, - 166.3, - 168.1, - 168.2, - 167.4, - 165.7, - 164, - 161.4, - 158.2, - 155, - 152.6, - 151.6, - 150.3, - 148.3, - 146, - 143.7, - 141.4, - 138.9, - 136.7, - 135, - 133.2, - 131.7, - 130.1, - 128.3, - 126.8, - 125.5, - 124.4, - 123.2, - 122.1, - 121.1, - 120, - 118.8, - 117.6, - 116.3, - 115.2, - 114.3, - 113.3, - 112.2, - 111.1, - 110, - 108.9, - 108, - 107, - 106.2, - 105.6, - 105.1, - 104.3, - 103.5, - 103, - 102, - 101.2, - 100.5, - 100.1, - 99.3, - 98.5, - 97.7, - 96.7, - 95.9, - 95.5, - 94.7, - 94, - 93.2, - 92.5, - 91.8, - 90.5, - 89.6, - 88.5, - 87.7, - 87, - 86.2, - 85.2, - 84.3, - 83.2, - 82.1, - 81, - 80, - 79.2, - 78.5, - 78.2, - 76.9, - 74.6, - 73.1, - 72.2, - 71.1, - 69.9, - 68.7, - 67.4, - 66.4, - 65.4, - 64.5, - 63.6, - 62.6, - 61.8, - 61.2, - 60.6, - 60, - 59.1, - 58.3, - 57.6, - 56.9, - 56.1, - 55.3, - 54.6, - 53.7, - 52.8, - 51.9, - 51.2, - 50.4, - 49.6, - 49.1, - 48.3, - 46.4, - 44.5, - 42.2, - 39.7, - 38.2, - 35.7, - 31.4, - 27.5, - 24.2, - 22.3, - 20.3, - 19.8, - 19.5, - 19.1, - 18.9, - 18.9, - 19.2, - 19.8, - 20.4, - 20.6, - 20.1, - 19.6, - 18.4, - 17.2, - 16.4, - 15.6, - 14.8, - 14.1, - 13.7, - 13.2, - 12.7, - 12.2, - 12.1, - 11.9, - 11.8, - 11.7, - 11.8, - 11.8, - 11.9, - 11.9, - 12, - 12.1, - 12.1, - 12.2, - 12.4, - 12.5, - 12.6, - 12.6, - 12.5, - 12.6, - 12.7, - 12.6, - 12.6, - 12.6, - 12.7, - 12.8, - 12.9, - 12.9, - 13, - 13.3, - 13.5, - 13.5, - 13.5, - 13.6, - 13.5, - 13.6, - 13.9, - 13.9, - 13.7, - 13.6, - 13.5, - 13.8, - 14.1, - 14.3, - 15, - 15.5, - 15.3, - 14.3, - 13, - 11.4, - 9.9, - 486, - 485.2, - 484.1, - 483.3, - 482.9, - 482.9, - 483, - 475.4, - 451.6, - 423.9, - 396.4, - 374, - 359.1, - 337.7, - 324.4, - 320, - 317.2, - 314.4, - 312.2, - 310.9, - 309.2, - 307.6, - 306.1, - 305, - 304.2, - 303.3, - 302.5, - 301.6, - 301, - 300.1, - 298.3, - 295.9, - 293.2, - 290.3, - 287.4, - 284.8, - 282.4, - 280.7, - 278.7, - 276.4, - 273.7, - 272, - 270, - 266.4, - 263.4, - 260.5, - 256.7, - 249.1, - 243.5, - 240, - 236.1, - 232, - 227, - 219, - 210.3, - 203.7, - 198.7, - 193.9, - 190, - 186.8, - 182.1, - 176.5, - 172.3, - 169.4, - 168.1, - 168.1, - 170.5, - 170.8, - 169.8, - 168.4, - 167, - 165.6, - 163.3, - 160.8, - 157.6, - 156.1, - 155.3, - 153.3, - 150.8, - 148.3, - 145.9, - 143.4, - 141, - 138.5, - 136.4, - 134.7, - 133.7, - 131.9, - 129.7, - 127.9, - 126.4, - 125.3, - 124, - 122.9, - 121.9, - 120.7, - 119.5, - 118.2, - 117, - 115.9, - 114.9, - 113.9, - 112.7, - 111.6, - 110.5, - 109.4, - 108.4, - 107.5, - 106.7, - 106.2, - 105.6, - 104.7, - 103.8, - 103.2, - 102.5, - 101.7, - 101.2, - 100.9, - 100.1, - 98.7, - 97.7, - 96.8, - 96.3, - 96.1, - 94.9, - 94, - 93.3, - 92.5, - 91.7, - 90.7, - 89.6, - 88.4, - 87.6, - 87, - 86.3, - 85.4, - 84.4, - 83.2, - 81.9, - 80.7, - 79.9, - 78.8, - 77.7, - 77, - 76, - 75.2, - 73.7, - 72.3, - 71.3, - 69.9, - 68.6, - 67.2, - 66.2, - 65, - 64.1, - 63.2, - 62.2, - 61.6, - 61, - 60.4, - 59.7, - 58.8, - 58, - 57.2, - 56.5, - 55.6, - 54.7, - 53.9, - 53, - 52.1, - 51.3, - 50.6, - 49.9, - 49.1, - 48.5, - 47.9, - 46.5, - 44, - 40.7, - 38.7, - 37.3, - 34.6, - 30, - 26.4, - 24.3, - 22.3, - 20.4, - 20.1, - 19.9, - 19.5, - 19.4, - 19.7, - 20.1, - 20.7, - 21.3, - 21.1, - 20.3, - 19.5, - 18.2, - 17.1, - 16.3, - 15.5, - 14.6, - 14.2, - 13.8, - 13.3, - 12.7, - 12.4, - 12.2, - 12.1, - 12, - 11.9, - 11.9, - 12, - 12.1, - 12.1, - 12.2, - 12.2, - 12.2, - 12.3, - 12.6, - 12.7, - 12.9, - 12.9, - 12.8, - 12.8, - 12.9, - 12.9, - 12.9, - 12.7, - 12.7, - 12.8, - 12.9, - 13, - 13.2, - 13.4, - 13.6, - 13.6, - 13.6, - 13.7, - 13.7, - 13.8, - 14, - 14.1, - 14.1, - 13.9, - 13.8, - 14, - 14.3, - 14.7, - 15.3, - 16, - 15.7, - 14.7, - 13.4, - 11.9, - 10.3, - 487.8, - 486.8, - 485.6, - 484.7, - 484.4, - 485, - 485.2, - 481.4, - 459.8, - 431.4, - 403, - 376.7, - 361, - 345, - 329.4, - 322.3, - 318.9, - 316.4, - 313.8, - 312, - 310.7, - 309.2, - 307.5, - 306.4, - 305.5, - 304.5, - 303.7, - 302.7, - 301.9, - 300.8, - 298.9, - 296.8, - 294.2, - 291.4, - 288.2, - 285.2, - 283.4, - 281.8, - 280, - 278, - 274.5, - 271.8, - 269.5, - 266.1, - 263.7, - 261, - 256.2, - 248.9, - 244.3, - 241.1, - 237.5, - 233.2, - 225.3, - 217.2, - 210.4, - 203.8, - 199.5, - 193.9, - 189.4, - 186.8, - 182, - 177.4, - 173.8, - 171, - 170.3, - 171.8, - 173.5, - 172.8, - 171.5, - 169.9, - 168.2, - 166.6, - 164.9, - 163, - 161.3, - 160.3, - 158.4, - 155.9, - 153.3, - 150.6, - 148, - 145.4, - 143.1, - 140.7, - 138.4, - 136.9, - 136, - 134, - 131.8, - 129.7, - 127.8, - 126.2, - 125, - 123.9, - 122.7, - 121.4, - 120.1, - 118.9, - 117.7, - 116.6, - 115.5, - 114.4, - 113.2, - 112.2, - 111, - 109.8, - 108.8, - 107.9, - 107.3, - 106.7, - 106.1, - 105.2, - 104.3, - 103.7, - 103, - 102.2, - 101.7, - 101.4, - 100.6, - 99.1, - 98, - 97.1, - 96.7, - 96.5, - 95.3, - 94.4, - 93.4, - 92.5, - 91.8, - 90.9, - 89.7, - 88.5, - 87.7, - 87, - 86.2, - 85.1, - 84.1, - 83, - 81.8, - 80.8, - 79.8, - 78.5, - 77.3, - 76.3, - 75.6, - 75, - 73.9, - 72.5, - 71.6, - 70.4, - 68.9, - 66.9, - 65.9, - 64.7, - 63.7, - 62.8, - 62.1, - 61.4, - 60.8, - 60.2, - 59.3, - 58.4, - 57.6, - 56.9, - 56, - 55.2, - 54.2, - 53.3, - 52.5, - 51.5, - 50.6, - 50, - 49.4, - 48.9, - 48.2, - 47.4, - 46.4, - 43.3, - 39.4, - 37.9, - 36.1, - 33, - 29, - 26.3, - 24.3, - 22.2, - 20.7, - 20.6, - 20.3, - 19.9, - 19.9, - 20.2, - 20.6, - 21.3, - 21.9, - 21.2, - 20.5, - 19.4, - 18.4, - 17.3, - 16.2, - 15.4, - 14.8, - 14.3, - 13.7, - 13.2, - 12.8, - 12.5, - 12.4, - 12.5, - 12.3, - 12.2, - 12.2, - 12.3, - 12.3, - 12.3, - 12.4, - 12.5, - 12.5, - 12.6, - 12.9, - 13, - 13.1, - 13.1, - 12.9, - 13, - 13.1, - 13.1, - 13.1, - 13, - 12.9, - 13, - 13.1, - 13.2, - 13.3, - 13.4, - 13.6, - 13.7, - 13.7, - 13.8, - 13.9, - 14, - 14.1, - 14.3, - 14.4, - 14.2, - 14.1, - 14.2, - 14.6, - 15.2, - 15.5, - 16.3, - 16.1, - 15.1, - 13.7, - 12.2, - 10.7, - 489.6, - 488.6, - 487.4, - 486.5, - 486.7, - 487.1, - 487.5, - 487, - 472.5, - 442.7, - 410.6, - 379.4, - 362.1, - 350.4, - 337.1, - 326.6, - 322.1, - 318.4, - 315.7, - 313.3, - 311.8, - 310.4, - 308.6, - 307.5, - 306.5, - 305.6, - 304.6, - 303.4, - 302.4, - 301.2, - 299.4, - 297.2, - 295.2, - 292.4, - 289.2, - 286, - 284.3, - 282.9, - 281.3, - 279.4, - 275.6, - 271.2, - 268.3, - 266.1, - 264, - 260.2, - 254.3, - 249.2, - 245.3, - 242.1, - 238.6, - 232.1, - 224.3, - 218.1, - 211, - 205.9, - 201.4, - 194.4, - 188.9, - 186.5, - 182.8, - 178.8, - 175.2, - 173.2, - 174.3, - 175.5, - 176.2, - 174.9, - 173.2, - 171.6, - 169.4, - 167.5, - 166.3, - 164.9, - 164.2, - 163.3, - 161.2, - 158.4, - 155.5, - 152.8, - 150.2, - 147.6, - 145.2, - 142.8, - 140.4, - 139.3, - 138.1, - 136, - 134, - 131.8, - 129.6, - 127.5, - 126, - 124.7, - 123.5, - 122.2, - 120.9, - 119.7, - 118.5, - 117.3, - 116.1, - 114.9, - 113.8, - 112.7, - 111.5, - 110.3, - 109.2, - 108.4, - 107.9, - 107.2, - 106.6, - 105.8, - 104.9, - 104.1, - 103.4, - 102.6, - 102, - 101.4, - 100.4, - 99.3, - 98.5, - 97.5, - 96.9, - 96.3, - 95.5, - 94.9, - 93.7, - 92.6, - 91.7, - 90.9, - 89.7, - 88.5, - 87.6, - 86.7, - 85.9, - 84.9, - 84.1, - 83.1, - 82, - 80.9, - 79.8, - 78.8, - 78.1, - 76.8, - 75.9, - 75.1, - 74.1, - 72.8, - 71.7, - 70.7, - 69.2, - 66.3, - 65, - 64.1, - 63.3, - 62.5, - 62, - 61.4, - 60.6, - 59.7, - 58.7, - 58, - 57.2, - 56.4, - 55.6, - 54.7, - 53.7, - 52.9, - 52.2, - 51.1, - 50, - 49.5, - 49, - 48.4, - 47.7, - 47, - 45.5, - 41.2, - 38.5, - 37.2, - 34.7, - 31.5, - 29.1, - 26.8, - 24.3, - 22.3, - 21.2, - 21, - 20.8, - 20.6, - 20.6, - 21.1, - 21.7, - 22.4, - 22.3, - 21.4, - 20.5, - 19.3, - 18.4, - 17.5, - 16.1, - 15.3, - 14.9, - 14.4, - 13.9, - 13.4, - 13, - 12.9, - 13.1, - 13.4, - 12.8, - 12.6, - 12.5, - 12.5, - 12.6, - 12.6, - 12.7, - 12.9, - 12.7, - 12.8, - 13.1, - 13.1, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.3, - 13.4, - 13.3, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.5, - 13.7, - 13.9, - 13.8, - 13.9, - 14, - 14.1, - 14.3, - 14.4, - 14.5, - 14.4, - 14, - 14.2, - 14.7, - 15.4, - 15.7, - 16.5, - 16.5, - 15.4, - 14.1, - 12.6, - 11, - 491.6, - 490.6, - 489.5, - 488.9, - 488.9, - 489, - 490, - 490.2, - 480.7, - 449.1, - 415.2, - 382.1, - 362.9, - 353.8, - 345, - 335.3, - 327.2, - 322.1, - 318.1, - 314.7, - 312.7, - 311.2, - 309.6, - 308.4, - 307.4, - 306.5, - 305.2, - 303.7, - 302.6, - 301.2, - 299.5, - 297.4, - 295.6, - 293, - 289.6, - 286.9, - 285, - 283.6, - 282.2, - 280.5, - 276.4, - 270.9, - 268.6, - 266.7, - 263.6, - 258.6, - 254.2, - 250.3, - 246.6, - 243.2, - 238.3, - 230.6, - 225.2, - 218.7, - 212.8, - 208.6, - 202.7, - 194.2, - 188.8, - 187, - 183.9, - 179.6, - 177, - 177.4, - 178.6, - 179.2, - 178.8, - 176.7, - 174.6, - 172.9, - 170.5, - 168.8, - 167.7, - 167.2, - 167.4, - 166.6, - 164, - 160.8, - 158, - 155.3, - 152.7, - 150, - 147.5, - 144.8, - 142.6, - 141, - 140, - 138.1, - 136, - 133.9, - 131.7, - 129.2, - 127.1, - 125.6, - 124.4, - 123.1, - 121.8, - 120.5, - 119.2, - 118, - 116.8, - 115.6, - 114.4, - 113.2, - 112.1, - 111, - 109.9, - 109.1, - 108.5, - 107.8, - 106.9, - 106.1, - 105.3, - 104.5, - 103.8, - 103, - 102.1, - 101.4, - 100.4, - 99.5, - 98.7, - 97.9, - 97.2, - 96.4, - 95.9, - 95.5, - 93.9, - 92.6, - 91.7, - 90.8, - 89.9, - 88.7, - 87.8, - 86.8, - 85.8, - 84.8, - 84, - 83.2, - 82.1, - 81, - 79.9, - 78.9, - 78.2, - 77, - 76.1, - 75.1, - 74.1, - 73, - 71.8, - 70.6, - 68.9, - 66.2, - 64.2, - 63.5, - 62.9, - 62.2, - 61.7, - 61.1, - 60.3, - 59.3, - 58.3, - 57.6, - 56.8, - 55.9, - 54.9, - 54, - 53.1, - 52.4, - 51.7, - 50.6, - 49.5, - 48.9, - 48.5, - 47.7, - 47.1, - 46.3, - 43.6, - 39.8, - 38, - 36.4, - 33.6, - 31.3, - 29.8, - 27.5, - 25, - 23, - 21.6, - 21.3, - 21.3, - 21.4, - 21.8, - 22.4, - 23.1, - 23.2, - 22.5, - 21.5, - 20.4, - 19.1, - 18.2, - 17.3, - 15.9, - 15.4, - 15.1, - 14.6, - 14.1, - 13.7, - 13.5, - 13.7, - 14.2, - 13.9, - 13.1, - 12.9, - 12.9, - 12.8, - 12.8, - 12.9, - 13, - 13.2, - 13.1, - 13.1, - 13.2, - 13.3, - 13.3, - 13.4, - 13.4, - 13.5, - 13.6, - 13.5, - 13.6, - 13.6, - 13.5, - 13.5, - 13.4, - 13.4, - 13.5, - 13.7, - 13.8, - 14, - 14, - 14.1, - 14.1, - 14.2, - 14.3, - 14.3, - 14.5, - 14.6, - 14.3, - 14.3, - 14.8, - 15.5, - 15.7, - 16.4, - 16.8, - 15.8, - 14.4, - 13, - 11.5, - 493.7, - 492.6, - 491.5, - 491, - 491, - 491.4, - 492, - 491.9, - 483.3, - 451.9, - 418, - 385.2, - 364.7, - 356.5, - 349.1, - 341.5, - 332.8, - 326.4, - 321.4, - 317.2, - 314.1, - 312.6, - 310.8, - 309.2, - 308.2, - 307.5, - 306.2, - 304.2, - 302.5, - 300.8, - 299.1, - 297.5, - 295.3, - 293, - 290.3, - 288, - 285.7, - 284.4, - 283, - 280.9, - 275.8, - 271.3, - 269.3, - 266.7, - 262.2, - 258.2, - 255.2, - 251.8, - 248.1, - 244.3, - 237.3, - 231.2, - 226, - 219.5, - 215.1, - 210.7, - 202.7, - 193.2, - 188.9, - 187.2, - 183.9, - 180.2, - 179.7, - 181.1, - 182.3, - 182.4, - 180.9, - 178.5, - 176.3, - 173.9, - 171.7, - 170.3, - 169.8, - 170.9, - 172.1, - 170.1, - 166.9, - 163.5, - 160.5, - 157.7, - 155.3, - 152.6, - 149.6, - 146.9, - 145, - 143.1, - 141.7, - 140, - 137.8, - 135.8, - 133.8, - 131.5, - 128.8, - 126.6, - 125.3, - 124, - 122.6, - 121.3, - 120, - 118.7, - 117.5, - 116.3, - 115.2, - 114, - 112.8, - 111.7, - 110.6, - 109.7, - 108.9, - 108.1, - 107.2, - 106.2, - 105.4, - 104.7, - 104.1, - 103.5, - 102.5, - 101.5, - 100.7, - 100, - 99.1, - 98.2, - 97.3, - 96.6, - 96.1, - 95.5, - 93.8, - 92.5, - 91.6, - 90.7, - 89.8, - 88.8, - 88, - 87, - 85.9, - 84.9, - 84, - 83.2, - 82, - 81, - 80.1, - 79.1, - 78.2, - 77, - 76.2, - 75.4, - 74.4, - 73.2, - 72.1, - 70.8, - 69, - 66.5, - 64.3, - 63.4, - 62.7, - 62.1, - 61.5, - 60.9, - 60.1, - 58.9, - 58.1, - 57.1, - 56.3, - 55.4, - 54.4, - 53.4, - 52.6, - 52, - 51.2, - 50.1, - 49.2, - 48.5, - 47.9, - 47.3, - 46.6, - 44.9, - 41.5, - 39.3, - 37.6, - 35.8, - 33.6, - 32, - 29.7, - 27.9, - 25.8, - 23.8, - 22.6, - 22, - 22, - 22.3, - 22.8, - 23.6, - 23.9, - 23.4, - 22.5, - 21.3, - 20.1, - 19.2, - 18.3, - 17.1, - 16, - 15.5, - 15.1, - 14.6, - 14.3, - 14, - 14.1, - 14.7, - 14.4, - 13.6, - 13.2, - 13.2, - 13.1, - 13.1, - 13.1, - 13.1, - 13.2, - 13.5, - 13.4, - 13.4, - 13.5, - 13.6, - 13.6, - 13.6, - 13.7, - 13.7, - 13.8, - 13.8, - 13.9, - 14, - 13.9, - 13.8, - 13.6, - 13.6, - 13.7, - 13.9, - 14, - 14.1, - 14.1, - 14.2, - 14.2, - 14.2, - 14.1, - 14.2, - 14.5, - 14.7, - 14.6, - 14.5, - 14.7, - 15.5, - 15.7, - 16.2, - 16.7, - 16, - 14.7, - 13.3, - 11.9, - 495.5, - 494.3, - 493.4, - 493.1, - 493.3, - 493.6, - 493.7, - 493.8, - 486.5, - 457, - 425.6, - 395.8, - 370.2, - 357.7, - 350.6, - 343.4, - 335.7, - 330.7, - 324.7, - 321.2, - 316.9, - 313.8, - 312.3, - 310.3, - 309.3, - 308.5, - 307.2, - 305.1, - 302.7, - 300.5, - 298.8, - 297, - 294.9, - 293.1, - 291, - 289, - 286.6, - 285.3, - 283.9, - 281.2, - 276.1, - 272, - 270, - 266.5, - 262.7, - 259.6, - 256.6, - 253.2, - 249.4, - 244.2, - 237.5, - 232.6, - 226.7, - 221.4, - 217.5, - 211.4, - 200, - 191.6, - 188.8, - 187.1, - 184.6, - 182.3, - 183.3, - 184.7, - 185.6, - 185, - 182.6, - 180.4, - 178.2, - 175.6, - 173.2, - 172.2, - 172.7, - 177.4, - 176.5, - 173.4, - 169.8, - 166.5, - 163.3, - 160.3, - 157.7, - 154.7, - 151.3, - 149.1, - 147.2, - 145.1, - 143.6, - 141.9, - 139.6, - 137.6, - 135.6, - 133.3, - 130.9, - 127.8, - 126.2, - 124.8, - 123.5, - 122.2, - 120.9, - 119.6, - 118.4, - 117.1, - 116, - 114.8, - 113.5, - 112.3, - 111.1, - 110.1, - 109.2, - 108.4, - 107.4, - 106.6, - 105.8, - 105.1, - 104.4, - 103.8, - 102.8, - 101.6, - 100.9, - 100.2, - 99.3, - 98.4, - 97.5, - 96.8, - 96.1, - 95.2, - 93.9, - 92.6, - 91.8, - 90.9, - 89.8, - 88.9, - 88.1, - 87.1, - 86.1, - 85.1, - 84.1, - 83.1, - 82, - 81.1, - 80.2, - 79.2, - 78.3, - 77.1, - 76.3, - 75.4, - 74.5, - 73.2, - 72, - 70.9, - 69.2, - 67.3, - 65, - 63.7, - 62.9, - 62.1, - 61.5, - 60.7, - 59.9, - 58.5, - 57.5, - 56.6, - 55.8, - 54.7, - 53.7, - 52.9, - 52.2, - 51.5, - 50.7, - 49.7, - 48.8, - 48.1, - 47.4, - 46.6, - 45.2, - 43, - 40.9, - 39.2, - 37.3, - 35.6, - 34.1, - 32.5, - 30, - 28.2, - 26.5, - 24.6, - 23.5, - 23, - 22.8, - 23.2, - 24.3, - 24.6, - 24.4, - 23.6, - 22.5, - 21.3, - 20.3, - 19.3, - 18.3, - 17, - 16.3, - 15.7, - 15.2, - 14.8, - 14.5, - 14.3, - 14.3, - 14.5, - 13.9, - 13.5, - 13.4, - 13.4, - 13.3, - 13.4, - 13.4, - 13.4, - 13.6, - 13.7, - 13.8, - 13.8, - 13.9, - 14, - 13.9, - 13.9, - 13.9, - 14, - 14.1, - 14.2, - 14.4, - 14.3, - 14.2, - 14.1, - 13.9, - 14, - 14.1, - 14, - 14.3, - 14.3, - 14.2, - 14.3, - 14.3, - 14.3, - 14.3, - 14.3, - 14.4, - 14.6, - 14.8, - 14.8, - 14.6, - 15.2, - 15.7, - 16.1, - 16.4, - 16.1, - 15.1, - 13.7, - 12.4, - 496.8, - 496, - 495.7, - 495.5, - 495.4, - 495.3, - 495.5, - 495.4, - 489.1, - 463, - 434.4, - 405.8, - 376.8, - 359.6, - 352, - 345.1, - 338.2, - 334.2, - 328.3, - 323.7, - 320.1, - 315.3, - 313.6, - 312.1, - 311.2, - 310.2, - 308, - 305.6, - 303, - 300.6, - 298.4, - 296.6, - 295, - 293.5, - 291.8, - 290.1, - 288.2, - 286.3, - 284.7, - 282.7, - 277.8, - 272.9, - 270.7, - 267.6, - 264.1, - 260.9, - 257.8, - 253.9, - 248.8, - 243.4, - 238.7, - 234, - 228.1, - 223.6, - 219.4, - 211.6, - 200.9, - 191.9, - 189.1, - 187.5, - 186.2, - 185.2, - 187.3, - 188.9, - 189, - 187.3, - 184.5, - 182.3, - 179.9, - 176.7, - 174.9, - 174.6, - 178.5, - 182.2, - 180.2, - 177.1, - 173.1, - 169.5, - 166.4, - 163.2, - 159.9, - 155.8, - 153.1, - 151.4, - 149.3, - 147.1, - 145.6, - 143.9, - 141.3, - 139.2, - 137.2, - 135, - 132.5, - 129.1, - 127.1, - 125.7, - 124.4, - 123.1, - 121.8, - 120.6, - 119.3, - 118, - 116.8, - 115.6, - 114.3, - 113, - 111.7, - 110.6, - 109.7, - 108.9, - 108, - 107.1, - 106.2, - 105.4, - 104.6, - 103.7, - 103, - 102.1, - 101.3, - 100.5, - 99.6, - 98.8, - 97.8, - 96.8, - 95.9, - 95, - 94.1, - 93, - 92.1, - 91.1, - 90, - 89, - 88.3, - 87.4, - 86.5, - 85.3, - 84.3, - 83.2, - 82.1, - 80.9, - 80, - 79.1, - 78.1, - 77.2, - 76.3, - 75.3, - 74.4, - 73.2, - 71.8, - 70.5, - 69.2, - 67.4, - 65.1, - 63.9, - 63, - 62.1, - 61.3, - 60.2, - 59.3, - 58.1, - 57.1, - 56.3, - 55.3, - 54.2, - 53.3, - 52.5, - 51.6, - 50.8, - 50, - 49.1, - 48.3, - 47.6, - 46.9, - 45.5, - 43.5, - 42.1, - 40.7, - 39.2, - 37.2, - 35.5, - 34, - 32.6, - 30.9, - 29, - 27.1, - 25.3, - 24.5, - 24, - 23.8, - 24.4, - 25.5, - 25.6, - 25, - 23.9, - 22.7, - 21.6, - 20.4, - 19.3, - 18.3, - 17.1, - 16.5, - 16.1, - 15.6, - 15.2, - 14.9, - 14.7, - 14.4, - 14.2, - 14, - 13.8, - 13.8, - 13.7, - 13.6, - 13.7, - 13.7, - 13.7, - 13.9, - 14, - 14, - 14, - 14.1, - 14.3, - 14.3, - 14.3, - 14.3, - 14.4, - 14.4, - 14.5, - 14.6, - 14.6, - 14.5, - 14.4, - 14.3, - 14.4, - 14.4, - 14.3, - 14.5, - 14.6, - 14.4, - 14.3, - 14.4, - 14.4, - 14.5, - 14.5, - 14.5, - 14.5, - 14.7, - 14.9, - 14.5, - 14.6, - 15.3, - 15.8, - 16, - 16, - 15.3, - 14.3, - 13, - 498.5, - 498.1, - 498, - 497.6, - 497.2, - 497.2, - 497.7, - 497.7, - 494.4, - 472.1, - 443, - 413.1, - 383.2, - 362.1, - 353.5, - 347.4, - 341.4, - 336.1, - 329.9, - 324.4, - 322.6, - 319.1, - 315.6, - 314.1, - 313, - 311.3, - 308.7, - 306.1, - 302.8, - 300.5, - 298.1, - 296.6, - 295.4, - 294.1, - 292.6, - 291.4, - 290.4, - 288.5, - 285.8, - 283.4, - 278.9, - 274.1, - 271.3, - 268.4, - 265, - 261.8, - 258, - 253.7, - 249.1, - 244.5, - 240.3, - 235.8, - 230, - 225.5, - 220.8, - 213.6, - 203.8, - 193.3, - 190.1, - 188.7, - 187.6, - 188.8, - 192.1, - 192.3, - 191.4, - 189.2, - 186.9, - 184.6, - 181.1, - 178.3, - 177.4, - 179.6, - 185.9, - 185.9, - 183.6, - 180.5, - 176.7, - 172.9, - 169.5, - 165.9, - 161.3, - 157.7, - 155.9, - 153.7, - 151.5, - 149.3, - 147.8, - 145.9, - 143.1, - 140.8, - 138.9, - 136.7, - 133.8, - 130.4, - 128.1, - 126.8, - 125.4, - 124.1, - 122.8, - 121.5, - 120.2, - 118.9, - 117.6, - 116.3, - 115.1, - 113.8, - 112.5, - 111.2, - 110.3, - 109.5, - 108.5, - 107.6, - 106.6, - 105.7, - 104.7, - 103.9, - 103.2, - 102.5, - 101.7, - 100.9, - 99.9, - 99.1, - 98.1, - 97.1, - 96.1, - 95.2, - 94.3, - 93.2, - 92.3, - 91.2, - 90, - 88.9, - 88.3, - 87.4, - 86.5, - 85.4, - 84.4, - 83.4, - 82.1, - 80.9, - 79.9, - 78.9, - 77.9, - 77.1, - 76.4, - 75.5, - 74.3, - 73.2, - 71.8, - 70.4, - 69.2, - 67, - 64.7, - 63.7, - 62.7, - 61.9, - 60.9, - 59.8, - 58.8, - 57.9, - 56.8, - 56, - 54.9, - 53.9, - 53, - 52, - 50.9, - 50.1, - 49.4, - 48.6, - 47.9, - 47.2, - 46.3, - 44.6, - 42.9, - 41.7, - 40.3, - 38.8, - 37, - 35.5, - 34, - 32.4, - 30.7, - 29.1, - 27.3, - 25.9, - 25.3, - 24.8, - 24.7, - 25.6, - 26.3, - 26.1, - 25.2, - 24.2, - 22.8, - 21.6, - 20.3, - 19.2, - 18.3, - 17.2, - 16.7, - 16.5, - 16.1, - 15.6, - 15.3, - 15.1, - 14.8, - 14.6, - 14.3, - 14.2, - 14.1, - 14, - 13.9, - 14, - 14, - 14.1, - 14.2, - 14.3, - 14.3, - 14.3, - 14.4, - 14.5, - 14.5, - 14.6, - 14.7, - 14.7, - 14.7, - 14.8, - 14.8, - 14.8, - 14.8, - 14.8, - 14.6, - 14.6, - 14.6, - 14.6, - 14.8, - 14.9, - 14.6, - 14.4, - 14.4, - 14.3, - 14.2, - 14.5, - 14.5, - 14.5, - 14.5, - 14.7, - 14.7, - 14.4, - 14.7, - 15.1, - 15.2, - 15.4, - 15.3, - 14.7, - 13.4, - 500.6, - 500.3, - 499.9, - 499.2, - 499, - 499, - 499.7, - 500, - 498.4, - 481.8, - 453.3, - 423.4, - 392.1, - 365.4, - 354.8, - 350.5, - 344.8, - 337.6, - 331.1, - 325.8, - 324, - 322.4, - 318.3, - 315.3, - 313.3, - 311.3, - 309.1, - 306.3, - 302.7, - 300.4, - 298.6, - 297.2, - 296, - 294.8, - 293.6, - 292.7, - 292, - 290.8, - 287.8, - 284.3, - 280.2, - 276.6, - 272.4, - 269.2, - 265.7, - 262.5, - 258.5, - 254.7, - 250.7, - 246.4, - 242, - 237.1, - 231.7, - 227, - 220.9, - 215.3, - 207.9, - 195.8, - 191.8, - 189.9, - 190.2, - 193.9, - 195.6, - 194.7, - 193.2, - 191.2, - 188.9, - 185.9, - 182.3, - 180.5, - 181, - 187.1, - 189.9, - 188.6, - 186.9, - 184.4, - 180.7, - 176.6, - 172.7, - 168.4, - 163.3, - 161.1, - 159.2, - 156.5, - 154, - 151.5, - 149.5, - 147.4, - 144.7, - 142.6, - 140.5, - 138.1, - 134.9, - 132, - 129.5, - 127.8, - 126.5, - 125.2, - 123.8, - 122.5, - 121.2, - 119.9, - 118.5, - 117.2, - 115.9, - 114.5, - 113.1, - 111.9, - 110.9, - 110.1, - 109, - 107.9, - 106.9, - 106.1, - 105.2, - 104.4, - 103.7, - 103, - 102.1, - 101.3, - 100.3, - 99.3, - 98.4, - 97.4, - 96.5, - 95.8, - 94.7, - 93.4, - 92.4, - 91.1, - 90.2, - 89, - 88.1, - 87.3, - 86.3, - 85.4, - 84.2, - 83.2, - 82, - 80.9, - 79.8, - 78.7, - 77.6, - 76.8, - 76.1, - 75.3, - 73.9, - 72.8, - 71.7, - 69.9, - 68.2, - 66.2, - 64.6, - 63.5, - 62.8, - 61.9, - 60.8, - 59.8, - 58.8, - 57.9, - 56.8, - 55.9, - 54.7, - 53.6, - 52.8, - 51.8, - 50.7, - 49.8, - 48.9, - 48.4, - 47.7, - 46.8, - 45.7, - 44.2, - 42.9, - 41.5, - 40, - 38.4, - 36.6, - 35.1, - 33.8, - 32.1, - 30.4, - 28.9, - 27.4, - 26.3, - 26, - 26, - 26.5, - 26.9, - 27.1, - 26.5, - 25.6, - 24.5, - 22.9, - 21.6, - 20.2, - 19.1, - 18.1, - 17.3, - 17, - 16.8, - 16.5, - 16, - 15.7, - 15.5, - 15.2, - 15, - 14.7, - 14.5, - 14.4, - 14.4, - 14.3, - 14.2, - 14.3, - 14.4, - 14.6, - 14.7, - 14.8, - 14.8, - 14.9, - 14.9, - 15, - 15.1, - 15.1, - 15, - 15, - 15, - 15.1, - 15.1, - 15.2, - 15.2, - 15.1, - 15, - 14.9, - 14.9, - 15.3, - 15.3, - 14.7, - 14.5, - 14.5, - 14.4, - 14.3, - 14.5, - 14.4, - 14.3, - 14.3, - 14.5, - 14.8, - 14.5, - 14.1, - 14.6, - 14.7, - 14.5, - 14.4, - 14.3, - 13.6, - 502.3, - 501.8, - 501.3, - 500.8, - 500.9, - 501, - 501.1, - 501.4, - 500.9, - 495.2, - 470.8, - 437.9, - 403.8, - 372.5, - 357.8, - 353.3, - 346.5, - 340, - 335.8, - 332, - 326.1, - 323.9, - 320.9, - 317.7, - 314.5, - 311.4, - 308.7, - 305.7, - 302.7, - 300.6, - 299.2, - 298, - 296.8, - 295.6, - 294.8, - 294.2, - 293.3, - 291.9, - 289.5, - 286.5, - 283.4, - 280.3, - 275.3, - 270.2, - 266.3, - 262.9, - 259.4, - 256.1, - 251.7, - 247.7, - 243.8, - 238.8, - 233.2, - 227.9, - 222.3, - 217.7, - 211, - 197.9, - 193.6, - 192.1, - 195.2, - 197.9, - 197.6, - 196.4, - 195.1, - 193.2, - 189.6, - 186.3, - 184.3, - 184.5, - 190, - 195.7, - 194.4, - 192.4, - 190.6, - 188.3, - 185.1, - 181, - 176.2, - 170.4, - 166.5, - 164.6, - 162.1, - 159.2, - 156.4, - 154, - 151.5, - 149.1, - 146.7, - 144.6, - 141.9, - 138.9, - 136, - 133.6, - 130.7, - 128.6, - 127.5, - 126.2, - 124.9, - 123.6, - 122.2, - 120.9, - 119.5, - 118.1, - 116.7, - 115.3, - 113.7, - 112.4, - 111.3, - 110.3, - 109.3, - 108.4, - 107.4, - 106.5, - 105.8, - 105, - 104.2, - 103.4, - 102.5, - 101.7, - 100.8, - 99.8, - 98.8, - 97.7, - 97, - 96.6, - 95.5, - 93.8, - 92.5, - 91.5, - 90.4, - 88.9, - 88, - 87.1, - 86.3, - 85.5, - 84.3, - 83.1, - 82, - 80.8, - 79.7, - 78.7, - 77.7, - 76.9, - 75.6, - 74.5, - 73.5, - 72.2, - 71, - 69.3, - 67.5, - 65.8, - 64.7, - 63.8, - 63.2, - 62.3, - 60.9, - 59.9, - 58.9, - 57.9, - 56.9, - 55.9, - 54.7, - 53.6, - 52.7, - 51.6, - 50.9, - 49.9, - 49, - 48.2, - 47.4, - 46.3, - 45.2, - 44, - 42.5, - 41.3, - 39.9, - 37.9, - 35.9, - 34.1, - 32.6, - 31.5, - 30.3, - 29.2, - 27.8, - 27, - 27.3, - 28.3, - 28.7, - 28.3, - 27.6, - 26.7, - 25.8, - 24.6, - 23.1, - 21.6, - 20.2, - 19.1, - 18.1, - 17.6, - 17.3, - 17.1, - 16.7, - 16.3, - 16.1, - 15.9, - 15.6, - 15.3, - 15.1, - 15, - 14.9, - 14.7, - 14.7, - 14.7, - 14.7, - 14.8, - 15, - 15.1, - 15.2, - 15.3, - 15.3, - 15.2, - 15.4, - 15.5, - 15.6, - 15.5, - 15.4, - 15.5, - 15.5, - 15.4, - 15.5, - 15.6, - 15.5, - 15.5, - 15.3, - 15.1, - 15.3, - 15.4, - 15, - 14.7, - 14.7, - 14.7, - 14.6, - 14.5, - 14.4, - 14.3, - 14.3, - 14.5, - 14.8, - 14.8, - 14.1, - 14, - 14, - 13.8, - 13.6, - 13.5, - 13.3, - 503.8, - 503.2, - 502.7, - 502.6, - 502.7, - 502.7, - 502.6, - 502.6, - 502.5, - 501.5, - 488.4, - 458.4, - 426.2, - 398.6, - 375, - 359.5, - 349, - 345.5, - 344.8, - 341, - 333, - 326.8, - 323.9, - 321, - 317.3, - 312.8, - 309, - 305.7, - 303.1, - 301.2, - 299.9, - 298.7, - 297.6, - 296.6, - 296, - 295.6, - 294.7, - 293.1, - 291.3, - 289, - 286.7, - 284.1, - 278.4, - 271.1, - 266.7, - 263, - 260.1, - 256.9, - 252.4, - 248.8, - 245.1, - 240.5, - 234.6, - 229.3, - 224.7, - 220.1, - 213.6, - 200.7, - 195.6, - 195.5, - 199.1, - 200.1, - 199.1, - 198.3, - 197.8, - 196.7, - 192.7, - 189.1, - 188.6, - 191.7, - 200.3, - 200.5, - 198, - 196, - 194.3, - 191.8, - 189, - 185.8, - 179.9, - 173.8, - 171.3, - 168.4, - 165.4, - 162.3, - 159.2, - 156.5, - 154.1, - 151.6, - 149, - 146.4, - 143.1, - 140.1, - 137.5, - 135.4, - 132.9, - 129.8, - 128.4, - 127.2, - 125.9, - 124.6, - 123.3, - 121.9, - 120.4, - 119, - 117.5, - 116, - 114.4, - 113, - 111.6, - 110.4, - 109.5, - 108.7, - 107.9, - 107.1, - 106.3, - 105.6, - 104.7, - 103.9, - 102.9, - 102.1, - 101.1, - 99.9, - 98.9, - 98.1, - 97.3, - 96.7, - 95.6, - 94, - 92.7, - 91.7, - 90.4, - 89.1, - 88, - 87, - 86.2, - 85.4, - 84.3, - 83, - 81.9, - 80.5, - 79.5, - 78.6, - 77.9, - 77.1, - 75.8, - 74.4, - 73.4, - 72.1, - 70.4, - 69, - 67.7, - 66.2, - 65, - 64.3, - 63.6, - 62.7, - 61.2, - 60, - 59.1, - 58, - 56.9, - 55.6, - 54.6, - 53.9, - 52.8, - 51.7, - 51.4, - 50.5, - 49.5, - 48.1, - 47.1, - 45.8, - 44.7, - 43.7, - 42.4, - 41.2, - 39.4, - 37.1, - 35.2, - 34.2, - 33, - 31.9, - 30.9, - 30, - 28.7, - 28.1, - 28.9, - 29.9, - 29.8, - 29.2, - 28.1, - 27, - 25.9, - 24.6, - 23.1, - 21.6, - 20.4, - 19.4, - 18.5, - 18, - 17.7, - 17.5, - 17.2, - 16.9, - 16.6, - 16.3, - 16, - 15.7, - 15.5, - 15.4, - 15.3, - 15.1, - 15.1, - 15.1, - 15.1, - 15.2, - 15.4, - 15.5, - 15.6, - 15.7, - 15.7, - 15.6, - 15.7, - 15.8, - 15.9, - 15.9, - 15.9, - 15.9, - 15.9, - 15.9, - 15.9, - 15.9, - 15.8, - 15.8, - 15.6, - 15.5, - 15.3, - 15.5, - 15.4, - 15.1, - 14.9, - 15, - 14.9, - 14.7, - 14.6, - 14.5, - 14.5, - 14.5, - 14.9, - 15, - 14.1, - 13.3, - 13.3, - 13.3, - 13.1, - 12.9, - 13, - 505.7, - 504.7, - 504.4, - 504.3, - 504.4, - 504.6, - 504.5, - 504.2, - 504, - 503.8, - 502, - 485.1, - 458.7, - 433.6, - 408.3, - 383.6, - 361.5, - 355.2, - 355.9, - 349.4, - 340.8, - 333.1, - 328.5, - 325.1, - 320.1, - 314.8, - 310, - 306.4, - 304.1, - 302.2, - 300.8, - 299.6, - 298.5, - 297.7, - 297.1, - 296.8, - 296, - 294.6, - 292.6, - 290.6, - 288.7, - 286.4, - 280.4, - 272.6, - 267.4, - 264.4, - 260.8, - 257.6, - 253.3, - 249.7, - 246, - 241.8, - 236.5, - 231.7, - 227.4, - 222.9, - 217.2, - 205.6, - 199.1, - 199.4, - 201.8, - 202, - 201.3, - 201.7, - 201.1, - 199.9, - 197.3, - 193.5, - 193.2, - 201, - 204.8, - 203.7, - 201.3, - 199.3, - 198, - 195.5, - 192.6, - 189.7, - 185, - 178.7, - 175.6, - 172.3, - 168.9, - 165.6, - 162.3, - 159.4, - 156.7, - 154.2, - 151.5, - 148.4, - 145.1, - 141.8, - 138.7, - 136.9, - 135.1, - 131.2, - 129.4, - 128.3, - 127, - 125.7, - 124.2, - 122.8, - 121.3, - 119.8, - 118.2, - 116.6, - 115.1, - 113.5, - 112, - 110.7, - 109.6, - 109, - 108.4, - 107.7, - 107.1, - 106.5, - 105.6, - 104.4, - 103.5, - 102.4, - 101.3, - 100.1, - 99.1, - 98.3, - 97.4, - 96.6, - 95.4, - 94, - 92.7, - 91.7, - 90.5, - 89.3, - 88.3, - 87.3, - 86.3, - 85.4, - 84.4, - 83.3, - 82, - 80.6, - 79.5, - 78.7, - 77.7, - 76.7, - 75.7, - 74.3, - 73.4, - 72.2, - 70.3, - 68.9, - 67.7, - 66.6, - 65.3, - 64.5, - 63.8, - 62.8, - 61.7, - 60.3, - 59.3, - 57.9, - 56.4, - 55.3, - 54.4, - 53.3, - 52.2, - 51.3, - 51.5, - 50.7, - 49.5, - 47.7, - 46.7, - 45.9, - 44.6, - 43.8, - 42.6, - 41, - 38.8, - 36.9, - 35.5, - 34.6, - 33.4, - 32.3, - 31.5, - 30.8, - 30, - 29.7, - 30.7, - 30.7, - 30.2, - 29.4, - 28.3, - 27.3, - 26, - 24.6, - 23.2, - 21.8, - 20.5, - 19.5, - 18.9, - 18.4, - 18.1, - 17.9, - 17.6, - 17.4, - 17.1, - 16.8, - 16.5, - 16.3, - 16, - 15.8, - 15.7, - 15.6, - 15.5, - 15.4, - 15.5, - 15.7, - 15.9, - 15.9, - 16, - 16.1, - 16.2, - 16.1, - 16.2, - 16.2, - 16.2, - 16.3, - 16.3, - 16.3, - 16.2, - 16.2, - 16.2, - 16.2, - 16, - 16.1, - 16.1, - 15.9, - 15.7, - 15.7, - 15.6, - 15.5, - 15.2, - 15.2, - 15.2, - 15, - 14.9, - 14.7, - 14.6, - 14.6, - 14.9, - 15, - 14.4, - 13.4, - 13, - 13.1, - 13.1, - 13, - 13.3, - 507.3, - 506.4, - 506.1, - 506.2, - 506.3, - 506.3, - 506.5, - 506.2, - 505.7, - 505.7, - 505.6, - 503.5, - 491.7, - 468.2, - 441.7, - 413.4, - 385.6, - 368.5, - 363.1, - 357.8, - 348.7, - 339.8, - 335.2, - 331.1, - 323.9, - 317.8, - 312.7, - 308, - 305.2, - 303.4, - 302, - 300.7, - 299.6, - 298.8, - 298.2, - 297.8, - 297.1, - 295.9, - 293.5, - 291.8, - 289.8, - 286.7, - 281.5, - 274.7, - 269, - 265.5, - 261.8, - 258.8, - 254.5, - 250.9, - 247.2, - 243.6, - 239.4, - 235.4, - 230.6, - 225.9, - 220.2, - 209.7, - 204.7, - 203.7, - 204.2, - 204.3, - 204.6, - 204.7, - 203.9, - 202.6, - 199.8, - 196, - 200.7, - 207.7, - 207.9, - 206.8, - 204.8, - 203, - 201, - 198.4, - 195.8, - 193.1, - 189.3, - 184.5, - 180.7, - 176.7, - 172.8, - 169.2, - 165.6, - 162.5, - 159.5, - 156.7, - 153.7, - 150.4, - 147.4, - 142.6, - 139.2, - 137.7, - 135.5, - 132, - 130.1, - 129.4, - 128.1, - 126.7, - 125.2, - 123.6, - 122.1, - 120.5, - 118.8, - 117.2, - 115.6, - 114, - 112.4, - 111, - 109.7, - 109.2, - 108.9, - 108.4, - 107.9, - 107.3, - 106.4, - 105, - 104, - 102.8, - 101.6, - 100.5, - 99.3, - 98.4, - 97.4, - 96.5, - 95.5, - 94, - 92.8, - 91.8, - 90.7, - 89.7, - 88.6, - 87.5, - 86.4, - 85.5, - 84.5, - 83.6, - 82.6, - 81, - 79.9, - 78.8, - 77.5, - 76.4, - 75.3, - 73.9, - 72.8, - 71.7, - 70.1, - 68.9, - 67.8, - 66.7, - 65.9, - 65, - 64.1, - 62.8, - 61.7, - 60.6, - 59.7, - 58.2, - 56.3, - 55.2, - 54.5, - 53.5, - 52.5, - 51.2, - 51.1, - 50.6, - 49.4, - 47.7, - 46.6, - 46, - 44.6, - 43.5, - 42.2, - 40.5, - 38.9, - 37.7, - 36.4, - 34.8, - 33.6, - 32.6, - 31.8, - 31.3, - 31.1, - 31.3, - 31.5, - 31.3, - 30.4, - 29.5, - 28.5, - 27.2, - 25.9, - 24.5, - 23, - 21.7, - 20.5, - 19.7, - 19.3, - 18.9, - 18.6, - 18.4, - 18.1, - 17.8, - 17.5, - 17.2, - 17, - 16.7, - 16.5, - 16.3, - 16.1, - 16, - 16, - 15.9, - 16.1, - 16.2, - 16.3, - 16.3, - 16.4, - 16.5, - 16.6, - 16.6, - 16.6, - 16.6, - 16.6, - 16.6, - 16.7, - 16.7, - 16.6, - 16.5, - 16.5, - 16.4, - 16.3, - 16.4, - 16.4, - 16.3, - 16, - 16, - 15.9, - 15.7, - 15.6, - 15.5, - 15.4, - 15.4, - 15.3, - 14.9, - 14.8, - 14.8, - 14.8, - 14.9, - 14.8, - 14.1, - 13.3, - 13.1, - 13.3, - 13.5, - 13.4, - 508.7, - 508.1, - 507.9, - 508, - 508, - 508.5, - 508.7, - 508.2, - 507.5, - 507.2, - 507.1, - 506.9, - 505.9, - 495.1, - 467.9, - 439.2, - 414.3, - 398.3, - 383.8, - 367.8, - 355.5, - 345.4, - 339.2, - 335, - 328.2, - 320.5, - 315.7, - 310.7, - 306.9, - 304.9, - 303.4, - 302.3, - 301.3, - 300.4, - 299.5, - 298.9, - 298.1, - 296.9, - 295, - 293.1, - 290.6, - 287.5, - 283.4, - 277.6, - 270.6, - 266.1, - 263.3, - 260, - 255.8, - 251.8, - 248.3, - 245.2, - 241.9, - 238.6, - 234.3, - 229.2, - 223.8, - 215.1, - 208.2, - 206.1, - 206.4, - 207.6, - 207.7, - 206.9, - 206.5, - 205.4, - 202.4, - 201.7, - 210, - 211.8, - 211, - 209.8, - 207.9, - 205.7, - 203.3, - 200.9, - 198.5, - 196.1, - 193, - 189.5, - 185.5, - 181.2, - 177.1, - 173.1, - 169.3, - 165.6, - 162.4, - 159.2, - 155.6, - 152.6, - 149.3, - 143.2, - 140.5, - 139.7, - 137.4, - 133.8, - 130.9, - 130.3, - 129.1, - 127.6, - 126, - 124.4, - 122.8, - 121.1, - 119.5, - 117.8, - 116.2, - 114.5, - 112.6, - 111.3, - 110.2, - 109.7, - 109.4, - 108.9, - 108.3, - 107.7, - 106.6, - 105.5, - 104.5, - 103.3, - 102, - 100.9, - 99.8, - 98.5, - 97.3, - 96.3, - 95.2, - 94, - 93, - 91.9, - 90.7, - 89.8, - 88.9, - 88.1, - 86.8, - 85.5, - 84.5, - 83.6, - 82.4, - 81.2, - 80.2, - 79, - 77.6, - 76.2, - 75, - 73.6, - 72.2, - 71.2, - 70.1, - 69.1, - 68, - 67, - 66.3, - 65.4, - 64.1, - 62.7, - 61.5, - 60.5, - 59.9, - 59, - 57.1, - 55.1, - 54.4, - 53.8, - 52.9, - 51.5, - 51.1, - 50.4, - 49.6, - 48.5, - 47.2, - 45.8, - 44.1, - 42.8, - 41.5, - 40.1, - 38.7, - 37.8, - 36.7, - 35.3, - 34, - 33, - 32.3, - 32.1, - 32.1, - 32.3, - 32.2, - 31.5, - 30.6, - 29.7, - 28.6, - 27.2, - 25.8, - 24.5, - 22.9, - 21.6, - 20.6, - 20.1, - 19.9, - 19.4, - 19.1, - 18.9, - 18.6, - 18.4, - 18, - 17.7, - 17.4, - 17.2, - 17, - 16.8, - 16.7, - 16.6, - 16.5, - 16.5, - 16.6, - 16.7, - 16.8, - 16.8, - 17, - 17, - 17, - 17.1, - 17.1, - 17.1, - 17.1, - 17.1, - 17.1, - 17.1, - 17, - 16.9, - 16.8, - 16.7, - 16.7, - 16.7, - 16.7, - 16.7, - 16.4, - 16.2, - 16.3, - 16.2, - 16, - 15.8, - 15.8, - 15.8, - 15.7, - 15.3, - 14.9, - 14.8, - 14.8, - 14.7, - 14.8, - 14.5, - 14, - 13.6, - 13.5, - 13.5, - 13.5, - 509.9, - 509.6, - 509.6, - 509.6, - 510, - 510.3, - 510.5, - 510.1, - 509.2, - 508.6, - 508.4, - 508.3, - 508.2, - 506.3, - 491.4, - 465.8, - 445.1, - 429.1, - 408.3, - 381.1, - 358.6, - 348.2, - 340.2, - 335.9, - 329.6, - 323.3, - 319.4, - 315.1, - 312.2, - 309.4, - 306.7, - 304.9, - 303.2, - 302.3, - 300.9, - 299.9, - 298.7, - 297.5, - 296.2, - 294.5, - 292.2, - 288.7, - 284.6, - 279.1, - 272.4, - 267.7, - 265, - 261.6, - 257.7, - 253.3, - 249.6, - 247, - 244.1, - 241.1, - 237.5, - 232.4, - 227.2, - 221.3, - 212.7, - 208.6, - 210.1, - 211.2, - 210.2, - 209.4, - 209.5, - 208.6, - 206.8, - 210.3, - 215.2, - 214.5, - 213.4, - 212, - 209.8, - 207.7, - 205.5, - 203, - 200.5, - 198.1, - 195.8, - 193.9, - 190.2, - 185.7, - 181.5, - 177.5, - 173.5, - 169.4, - 165.4, - 161.6, - 157.6, - 155, - 150.9, - 145.1, - 143.9, - 144.3, - 142.2, - 136.4, - 132.2, - 131, - 129.5, - 128, - 126.9, - 125.2, - 123.5, - 121.8, - 120.1, - 118.4, - 116.6, - 114.9, - 113, - 111.6, - 110.8, - 110.2, - 109.7, - 109, - 108.4, - 107.8, - 107, - 105.9, - 104.7, - 103.5, - 102.3, - 101.3, - 100.2, - 98.8, - 97.7, - 96.4, - 95.3, - 94.3, - 93.1, - 92, - 91, - 90, - 89.1, - 88.4, - 86.9, - 85.7, - 84.7, - 83.6, - 82.4, - 81.4, - 80.2, - 79.2, - 77.8, - 76.4, - 75.2, - 74.1, - 72.5, - 71.1, - 70.3, - 69.2, - 68.2, - 67.5, - 66.4, - 65.4, - 64.2, - 62.6, - 61.5, - 60.5, - 59.6, - 58.8, - 57.7, - 55.7, - 54.6, - 54, - 53.3, - 52.1, - 51.5, - 50.8, - 50.1, - 49.5, - 48.8, - 46.9, - 43.4, - 42, - 41.4, - 40.1, - 38.5, - 37.5, - 36.6, - 35.8, - 34.7, - 33.7, - 33, - 33, - 33, - 33, - 32.7, - 31.7, - 30.7, - 29.9, - 28.8, - 27.2, - 25.7, - 24.2, - 22.7, - 21.5, - 20.8, - 20.5, - 20.3, - 19.9, - 19.6, - 19.3, - 19.1, - 18.8, - 18.5, - 18.1, - 17.9, - 17.7, - 17.5, - 17.4, - 17.3, - 17.2, - 17.1, - 17.2, - 17.2, - 17.2, - 17.3, - 17.5, - 17.6, - 17.5, - 17.6, - 17.6, - 17.6, - 17.6, - 17.7, - 17.6, - 17.6, - 17.5, - 17.5, - 17.4, - 17.3, - 17.3, - 17.2, - 17.1, - 17, - 17, - 16.8, - 16.6, - 16.7, - 16.6, - 16.4, - 16.3, - 16.1, - 16, - 16, - 15.6, - 15.3, - 15, - 14.9, - 14.9, - 14.9, - 14.7, - 14.3, - 14.1, - 13.9, - 13.8, - 13.8, - 511.1, - 510.9, - 511.1, - 511.4, - 511.5, - 511.8, - 512, - 511.8, - 510.9, - 510.4, - 510.2, - 509.9, - 509.7, - 509.6, - 507.4, - 493.9, - 476.1, - 454.5, - 427.4, - 395.3, - 364.6, - 349, - 340.7, - 335.7, - 330, - 326.4, - 323, - 319.5, - 317.4, - 315.4, - 311.9, - 308.8, - 305.8, - 303.8, - 301.9, - 300.6, - 299.3, - 298, - 296.6, - 295.3, - 293.7, - 290, - 285, - 280.7, - 275.6, - 270, - 266.7, - 263.3, - 259.4, - 254.9, - 250.9, - 248.4, - 245.9, - 243.2, - 239.7, - 234.6, - 230, - 225.7, - 218.6, - 213, - 214.1, - 214.9, - 213.5, - 213, - 213, - 211.5, - 211.8, - 217.2, - 218.1, - 216.8, - 215.3, - 213.7, - 211.5, - 209.2, - 207, - 204.6, - 202.3, - 199.9, - 198.8, - 197.5, - 194.6, - 190.1, - 185.8, - 182, - 178, - 173.7, - 168.5, - 163.3, - 159.5, - 156.9, - 153.7, - 150.3, - 148.5, - 148, - 145.3, - 139.9, - 134.4, - 131.2, - 128.5, - 127.4, - 127.6, - 126.1, - 124.4, - 122.4, - 120.6, - 118.8, - 117.1, - 115.4, - 113.7, - 112.3, - 111.4, - 110.8, - 110.1, - 109.4, - 108.7, - 108, - 107.2, - 106.2, - 105.1, - 104, - 102.6, - 101.5, - 100.4, - 99.1, - 98, - 96.7, - 95.7, - 94.5, - 93.3, - 92.2, - 91.2, - 90.2, - 89.2, - 88.3, - 86.9, - 85.9, - 84.9, - 83.7, - 82.5, - 81.4, - 80.3, - 79.2, - 77.9, - 76.6, - 75.4, - 74, - 72.5, - 71.3, - 70.4, - 69.2, - 68.3, - 67.3, - 66, - 65.1, - 64.2, - 62.8, - 61.5, - 60.3, - 59.2, - 58.5, - 57.5, - 56.2, - 55.1, - 54.5, - 54, - 52.5, - 51.9, - 51.2, - 50.7, - 50.3, - 49.8, - 48.3, - 43.8, - 41.5, - 41, - 40.3, - 38.8, - 37.5, - 37, - 36.4, - 35.4, - 34.9, - 34.3, - 34, - 33.7, - 33.3, - 32.8, - 31.9, - 30.8, - 29.9, - 28.7, - 27.3, - 25.6, - 24, - 22.6, - 21.6, - 21.2, - 20.9, - 20.6, - 20.4, - 20.1, - 19.8, - 19.5, - 19.2, - 18.9, - 18.6, - 18.4, - 18.2, - 18.1, - 17.9, - 17.8, - 17.7, - 17.7, - 17.7, - 17.7, - 17.8, - 17.9, - 18.1, - 18.2, - 18.1, - 18.2, - 18.1, - 18, - 18.1, - 18.2, - 18.2, - 18.1, - 18.1, - 18, - 18, - 17.9, - 17.8, - 17.6, - 17.5, - 17.4, - 17.5, - 17.4, - 17.2, - 17.1, - 16.9, - 16.8, - 16.7, - 16.4, - 16.3, - 16.2, - 16.1, - 15.8, - 15.3, - 15.1, - 15.2, - 15.1, - 15, - 14.6, - 14.4, - 14.4, - 14.2, - 14.1, - 512.8, - 512.3, - 512.6, - 512.6, - 512.8, - 512.9, - 513, - 512.8, - 512.6, - 512.5, - 512.2, - 511.6, - 511.4, - 511.4, - 511.3, - 510.1, - 499.7, - 474.4, - 442.7, - 410, - 376.9, - 351.2, - 339.7, - 334.3, - 330, - 327.4, - 323.9, - 320.7, - 318.6, - 317, - 315.2, - 311.9, - 308.5, - 305.5, - 302.6, - 300.7, - 299.5, - 298.5, - 297.1, - 295.6, - 294.2, - 290.7, - 285.4, - 282.2, - 278.6, - 273.6, - 268.3, - 265, - 261.3, - 256.7, - 252.4, - 249.6, - 247.2, - 244.1, - 239.5, - 234.9, - 232.1, - 229, - 224.4, - 218.1, - 217.4, - 217.4, - 217, - 217.4, - 216.5, - 214.6, - 218.6, - 221.8, - 220.5, - 218.9, - 217.3, - 215.4, - 213.2, - 210.8, - 208.4, - 206, - 204, - 202.5, - 202.1, - 200.6, - 198.2, - 194.7, - 190.1, - 186.1, - 182.3, - 177.7, - 170.5, - 165.1, - 161.7, - 158.9, - 156.6, - 153.9, - 152.2, - 150.4, - 147.9, - 143.7, - 138.1, - 132.8, - 129.3, - 128.6, - 128.4, - 126.9, - 125.1, - 123.2, - 121.2, - 119.3, - 117.6, - 115.8, - 114.2, - 113, - 112, - 111.3, - 110.6, - 110, - 109.2, - 108.3, - 107.4, - 106.3, - 105.2, - 104.2, - 102.9, - 101.8, - 100.7, - 99.4, - 98.2, - 97.1, - 95.9, - 94.7, - 93.5, - 92.1, - 91, - 90.2, - 89.4, - 88.4, - 87, - 86, - 84.9, - 83.8, - 82.6, - 81.5, - 80.4, - 79.2, - 77.7, - 76.6, - 75.2, - 73.8, - 72.5, - 71.4, - 70.3, - 69.2, - 68.2, - 67.2, - 65.9, - 65.1, - 64.2, - 63, - 61.7, - 60.2, - 59.3, - 58.7, - 57.4, - 56.4, - 55.4, - 54.8, - 54.3, - 53.2, - 52.3, - 51.7, - 51.3, - 51, - 50.5, - 49.5, - 46.1, - 42.4, - 41, - 40.2, - 39.3, - 38.2, - 37.7, - 37.1, - 36, - 35.6, - 35.3, - 34.7, - 34.3, - 33.7, - 32.9, - 31.9, - 30.7, - 29.7, - 28.4, - 27, - 25.4, - 23.8, - 22.5, - 22, - 21.6, - 21.4, - 21.1, - 20.9, - 20.6, - 20.3, - 20, - 19.7, - 19.4, - 19.2, - 18.9, - 18.7, - 18.6, - 18.5, - 18.4, - 18.4, - 18.3, - 18.3, - 18.3, - 18.4, - 18.5, - 18.4, - 18.5, - 18.6, - 18.6, - 18.6, - 18.5, - 18.6, - 18.6, - 18.5, - 18.5, - 18.5, - 18.4, - 18.4, - 18.3, - 18.2, - 18, - 17.9, - 17.9, - 17.9, - 17.9, - 17.7, - 17.4, - 17.2, - 17, - 16.8, - 16.6, - 16.5, - 16.4, - 16.4, - 16.2, - 15.8, - 15.6, - 15.5, - 15.3, - 15, - 14.6, - 14.5, - 14.7, - 14.6, - 14.4, - 514.9, - 514.2, - 514, - 514.1, - 514.5, - 514.6, - 514.4, - 514.3, - 514.5, - 514.5, - 514.3, - 513.8, - 513.3, - 513, - 513, - 513, - 511.1, - 491.8, - 461, - 430.2, - 394.8, - 359.3, - 343.4, - 337.1, - 331.7, - 327.5, - 324.1, - 321.2, - 319, - 317, - 315, - 312.2, - 309.4, - 306.5, - 303.4, - 300.8, - 299.4, - 298.6, - 297.3, - 295.4, - 293.2, - 289.8, - 286.1, - 283.5, - 280.4, - 276.1, - 269.6, - 265, - 262.5, - 258.4, - 254.2, - 251.2, - 247.9, - 242.8, - 239.1, - 236.5, - 234.4, - 232.1, - 227.8, - 221.1, - 220, - 220.6, - 221.2, - 221.1, - 219.6, - 218.7, - 224, - 224.8, - 222.7, - 220.9, - 219.3, - 217.7, - 215.3, - 212.5, - 210, - 207.6, - 206.4, - 206.5, - 205.3, - 203.3, - 201.1, - 198.3, - 194.6, - 190.2, - 186.1, - 179.9, - 172.3, - 168, - 163.9, - 161.8, - 161, - 158.6, - 156.6, - 154.3, - 151.7, - 147.3, - 141.6, - 134.7, - 131.6, - 131.6, - 130.2, - 127.8, - 125.9, - 123.9, - 121.9, - 119.9, - 118.1, - 116.3, - 114.6, - 113.5, - 112.5, - 111.6, - 110.9, - 110.3, - 109.5, - 108.5, - 107.4, - 106.4, - 105.4, - 104.2, - 103, - 102.1, - 101, - 99.6, - 98.6, - 97.5, - 96.2, - 94.9, - 93.7, - 92.4, - 91.3, - 90.4, - 89.5, - 88.5, - 87.1, - 86, - 84.8, - 83.8, - 82.7, - 81.6, - 80.5, - 79.2, - 77.7, - 76.3, - 75.1, - 73.8, - 72.6, - 71.5, - 70.3, - 69.3, - 68.3, - 67.3, - 66.1, - 65.1, - 64.2, - 63.1, - 61.8, - 60.4, - 59.6, - 58.6, - 57.6, - 56.6, - 55.8, - 55.1, - 54.4, - 53.8, - 53, - 52.3, - 51.8, - 51.5, - 50.8, - 50.1, - 48.2, - 44.1, - 42.2, - 40.8, - 39.7, - 39, - 38.3, - 38, - 37.2, - 36.4, - 35.9, - 35.2, - 34.6, - 33.8, - 32.8, - 31.8, - 30.6, - 29.4, - 28, - 26.6, - 25.1, - 23.5, - 22.8, - 22.5, - 22.2, - 21.9, - 21.6, - 21.4, - 21.1, - 20.8, - 20.5, - 20.2, - 20, - 19.7, - 19.5, - 19.4, - 19.2, - 19.1, - 19.1, - 19, - 18.9, - 18.9, - 18.9, - 19, - 19.1, - 19.1, - 19.1, - 19.2, - 19.1, - 19.1, - 19.1, - 19.1, - 19.1, - 19, - 19, - 19, - 18.9, - 18.8, - 18.8, - 18.6, - 18.4, - 18.4, - 18.3, - 18.2, - 18.2, - 18, - 17.7, - 17.5, - 17.4, - 17.3, - 17.2, - 16.9, - 16.7, - 16.4, - 16.3, - 16.3, - 16.1, - 15.8, - 15.6, - 15.2, - 15, - 15, - 15, - 14.9, - 14.6, - 516.6, - 515.8, - 515.6, - 516.1, - 516.3, - 516.4, - 516.5, - 516.4, - 516.5, - 516.5, - 516.2, - 515.9, - 515.5, - 515.3, - 514.9, - 514.8, - 514.7, - 509.3, - 487.4, - 455.4, - 419.3, - 391, - 370.6, - 351.3, - 338.8, - 332.1, - 326.1, - 320.7, - 318.7, - 317, - 314.6, - 312.2, - 309.5, - 306.8, - 304.1, - 301.1, - 299.3, - 298.4, - 297.1, - 294.9, - 292.2, - 289.7, - 287.1, - 284.8, - 282.2, - 277.8, - 270.1, - 265.1, - 263.3, - 260.2, - 256.2, - 253.3, - 249.3, - 243.4, - 240.8, - 238.7, - 236.8, - 234.8, - 231.1, - 225.1, - 223.1, - 224.4, - 223.9, - 222.7, - 221.6, - 222.6, - 227.2, - 227, - 224.8, - 222.9, - 221.1, - 219.5, - 217.6, - 214.4, - 211.7, - 210.1, - 210.7, - 210.4, - 208.7, - 206.8, - 204.8, - 202.1, - 198.6, - 194.6, - 190, - 181.3, - 174.4, - 171.3, - 167.1, - 166, - 164.8, - 162.7, - 160.6, - 157.9, - 154.6, - 150.6, - 145.3, - 138.1, - 134.2, - 134.8, - 134, - 130.3, - 126.8, - 124.6, - 122.6, - 120.5, - 118.6, - 116.7, - 115, - 113.7, - 112.8, - 111.8, - 111.1, - 110.3, - 109.5, - 108.6, - 107.5, - 106.5, - 105.6, - 104.3, - 103.2, - 102.1, - 101.1, - 99.8, - 99, - 97.7, - 96.3, - 95.1, - 94, - 92.8, - 91.7, - 90.7, - 89.7, - 88.6, - 87.3, - 86.1, - 84.8, - 83.8, - 82.8, - 81.7, - 80.7, - 79.3, - 77.9, - 76.4, - 75.3, - 73.9, - 72.7, - 71.5, - 70.3, - 69.2, - 68.3, - 67.2, - 66.1, - 65, - 64.3, - 63.3, - 62.1, - 61.1, - 60, - 58.9, - 57.8, - 56.8, - 56, - 55.3, - 54.6, - 54, - 53.4, - 52.8, - 52.3, - 51.7, - 51, - 50.4, - 48.1, - 44.6, - 43.2, - 41.9, - 40.6, - 40, - 39.2, - 38.5, - 37.7, - 36.9, - 36.3, - 35.6, - 34.7, - 33.7, - 32.7, - 31.6, - 30.5, - 29.2, - 27.7, - 26.3, - 24.9, - 23.9, - 23.4, - 23.1, - 22.7, - 22.4, - 22.1, - 21.9, - 21.6, - 21.3, - 21.1, - 20.8, - 20.5, - 20.3, - 20.1, - 19.9, - 19.8, - 19.8, - 19.7, - 19.6, - 19.5, - 19.6, - 19.6, - 19.6, - 19.5, - 19.5, - 19.7, - 19.7, - 19.7, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.5, - 19.5, - 19.4, - 19.2, - 19.1, - 19, - 18.9, - 18.7, - 18.6, - 18.5, - 18.4, - 18.1, - 17.9, - 17.8, - 17.7, - 17.5, - 17.1, - 16.8, - 16.5, - 16.5, - 16.5, - 16.4, - 16.2, - 15.9, - 15.7, - 15.4, - 15.2, - 15.1, - 15, - 14.8, - 517.8, - 517.3, - 517.3, - 517.9, - 518.2, - 518.3, - 518.5, - 518.8, - 518.7, - 518.3, - 517.7, - 517.6, - 517.3, - 517.2, - 516.7, - 516.6, - 516.4, - 515.7, - 510.2, - 487.3, - 458.6, - 436.3, - 413.3, - 388.7, - 367.3, - 350.2, - 338.6, - 324.3, - 318.2, - 316.7, - 314.8, - 313.2, - 311.1, - 308.4, - 305.2, - 301.7, - 299.1, - 297.8, - 296.5, - 294.3, - 291.8, - 289.7, - 287.6, - 285.7, - 283.1, - 278.9, - 273.2, - 266.8, - 263.9, - 262, - 259.5, - 256.7, - 252, - 245.8, - 243, - 240.8, - 238.9, - 236.7, - 233.1, - 229.4, - 226.4, - 226.7, - 225.5, - 224.1, - 223.9, - 227.1, - 230.2, - 229, - 226.7, - 224.6, - 222.5, - 220.8, - 219.4, - 217.2, - 214.8, - 215.3, - 216.4, - 214.5, - 212.7, - 211.6, - 209.2, - 206.2, - 203.1, - 199, - 193.5, - 183, - 177.1, - 174.5, - 171.3, - 169.9, - 168.5, - 166.3, - 163.9, - 161, - 157.3, - 153.9, - 149.1, - 142.2, - 137.6, - 139.1, - 138.9, - 135.1, - 130.2, - 125.8, - 123.1, - 121.1, - 119.1, - 117.1, - 115.3, - 113.9, - 112.9, - 111.9, - 111.1, - 110.3, - 109.4, - 108.6, - 107.6, - 106.7, - 105.7, - 104.5, - 103.4, - 102.3, - 101, - 100, - 99.2, - 97.8, - 96.4, - 95.2, - 94.1, - 93.1, - 92, - 91, - 89.8, - 88.5, - 87.2, - 86.1, - 85, - 84, - 82.9, - 81.7, - 80.7, - 79.3, - 77.9, - 76.6, - 75.4, - 73.9, - 72.7, - 71.6, - 70.3, - 69.1, - 68.1, - 67, - 66.1, - 65.3, - 64.5, - 63.5, - 62.5, - 61.6, - 60.4, - 58.9, - 57.8, - 57.1, - 56, - 55.5, - 54.9, - 54.4, - 53.7, - 53.3, - 52.4, - 51.8, - 51, - 50.3, - 47.7, - 45.3, - 44.1, - 43, - 42.2, - 41.5, - 40.3, - 38.9, - 38, - 37.3, - 36.7, - 35.9, - 34.9, - 33.8, - 32.8, - 31.6, - 30.3, - 29, - 27.5, - 26, - 24.8, - 24.2, - 23.9, - 23.6, - 23.2, - 23, - 22.7, - 22.4, - 22.1, - 21.8, - 21.6, - 21.3, - 21.1, - 20.9, - 20.7, - 20.5, - 20.4, - 20.4, - 20.3, - 20.2, - 20.1, - 20.2, - 20.1, - 20.1, - 20.1, - 20.1, - 20.2, - 20.3, - 20.2, - 20.1, - 20.1, - 20.1, - 20.1, - 20.1, - 20.1, - 20.1, - 19.9, - 19.8, - 19.8, - 19.7, - 19.6, - 19.5, - 19, - 18.9, - 18.8, - 18.7, - 18.5, - 18.3, - 18.2, - 18, - 17.7, - 17.3, - 17, - 16.8, - 16.8, - 16.6, - 16.6, - 16.5, - 16.3, - 16, - 15.7, - 15.4, - 15.3, - 15.3, - 15.3, - 519.5, - 519.1, - 519.2, - 519.6, - 520, - 520.2, - 520.6, - 521.2, - 520.2, - 519.6, - 519.1, - 519, - 519, - 518.8, - 518.3, - 518, - 517.7, - 517.4, - 517.5, - 513.6, - 500.3, - 479.3, - 456.3, - 437.3, - 417.1, - 383, - 353.9, - 336.4, - 321.8, - 316.3, - 314.3, - 312.9, - 311.5, - 308.9, - 305.9, - 302.4, - 298.9, - 297, - 295.6, - 293.8, - 292.1, - 290.4, - 288.7, - 286.8, - 284.6, - 281, - 275.9, - 270.5, - 265.3, - 263.1, - 261.1, - 259, - 253.6, - 247.6, - 245, - 242.8, - 240.3, - 237.1, - 234.2, - 231.9, - 229.5, - 228.5, - 227.1, - 226, - 228, - 231.6, - 232.5, - 230.7, - 228.5, - 226.3, - 224.1, - 222.1, - 220.8, - 219.3, - 219.9, - 223.3, - 222.7, - 219.4, - 217.1, - 215.4, - 213, - 210.1, - 206.9, - 203.6, - 195.2, - 183.8, - 179.7, - 177.5, - 174.9, - 173.3, - 172.1, - 170.5, - 167.8, - 164.1, - 160.7, - 156.2, - 150.7, - 145.1, - 142, - 143.5, - 142.4, - 137.8, - 133.4, - 129.1, - 124.4, - 121.2, - 119.5, - 117.5, - 115.5, - 114, - 113, - 111.8, - 111, - 110.4, - 109.6, - 108.6, - 107.7, - 106.8, - 105.8, - 104.6, - 103.5, - 102.4, - 101, - 100, - 99.2, - 97.7, - 96.3, - 95.4, - 94.3, - 93.3, - 91.9, - 91.1, - 89.9, - 88.4, - 87.3, - 86.2, - 85, - 84, - 82.9, - 81.6, - 80.5, - 79.3, - 78.1, - 76.8, - 75.4, - 74, - 72.8, - 71.7, - 70.5, - 69.2, - 68, - 66.9, - 66.2, - 65.5, - 64.7, - 63.8, - 62.7, - 62, - 60.9, - 59.6, - 58.6, - 57.6, - 56.5, - 56.1, - 55.4, - 54.6, - 54, - 53.6, - 52.6, - 51.9, - 51.2, - 50, - 47.5, - 45.9, - 44.8, - 43.6, - 43, - 42.2, - 41.1, - 39.7, - 38.5, - 37.8, - 37.1, - 36.3, - 35.2, - 34.1, - 32.9, - 31.5, - 30.1, - 28.8, - 27.3, - 25.8, - 25, - 24.6, - 24.3, - 24, - 23.7, - 23.5, - 23.2, - 22.9, - 22.6, - 22.4, - 22.1, - 21.9, - 21.6, - 21.4, - 21.2, - 21.1, - 21, - 21, - 20.9, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.7, - 20.7, - 20.6, - 20.7, - 20.7, - 20.7, - 20.6, - 20.5, - 20.3, - 20.2, - 20.1, - 19.9, - 19.7, - 19.3, - 19.3, - 19.3, - 19, - 18.8, - 18.7, - 18.5, - 18.4, - 18.1, - 17.7, - 17.4, - 17.3, - 17.1, - 16.9, - 16.8, - 16.8, - 16.6, - 16.3, - 16, - 15.8, - 15.7, - 15.9, - 15.8, - 521.5, - 521.1, - 520.9, - 521, - 521.2, - 521.5, - 522.4, - 522.6, - 521.7, - 521.1, - 520.6, - 520.5, - 520.7, - 520.8, - 519.9, - 519.2, - 519.1, - 519.1, - 519.3, - 519.6, - 518.8, - 511.8, - 499.9, - 487.7, - 457.5, - 415.4, - 374.2, - 347.6, - 330.3, - 317.9, - 314, - 312.3, - 310.6, - 308.4, - 306, - 303, - 299.4, - 296.7, - 295.3, - 294, - 292.8, - 291.4, - 289.6, - 287.9, - 286.2, - 283.2, - 278.1, - 273.9, - 268.7, - 265, - 262.6, - 260.2, - 255.3, - 249.8, - 247.1, - 244.6, - 241.2, - 238.4, - 236.4, - 233.7, - 231.5, - 230.3, - 229.7, - 229.8, - 233, - 235.5, - 234.4, - 232.5, - 230.2, - 228, - 225.8, - 223.7, - 222.2, - 221.8, - 227, - 228.4, - 226, - 222.8, - 220.5, - 218.4, - 216, - 213.5, - 210.2, - 205.6, - 194.1, - 185.5, - 182.3, - 180, - 177.8, - 175.9, - 174.8, - 173.8, - 170.8, - 167, - 163.7, - 157.3, - 151.3, - 147.6, - 145.9, - 145.9, - 143.7, - 139.5, - 136, - 132.3, - 124.9, - 121.2, - 119.9, - 117.8, - 115.8, - 114, - 112.8, - 111.7, - 110.9, - 110.3, - 109.7, - 108.9, - 107.8, - 106.7, - 105.6, - 104.6, - 103.4, - 102.3, - 101.2, - 100.2, - 99.1, - 97.6, - 96.3, - 95.6, - 94.5, - 93.5, - 92, - 91, - 89.7, - 88.4, - 87.5, - 86.4, - 84.9, - 83.9, - 82.7, - 81.6, - 80.6, - 79.3, - 78, - 76.7, - 75.4, - 74.2, - 73, - 71.9, - 70.6, - 69.4, - 68.3, - 67.3, - 66.5, - 65.6, - 64.8, - 63.9, - 63, - 62.2, - 61.2, - 60.1, - 58.8, - 57.8, - 57, - 56.4, - 55.5, - 54.8, - 54.3, - 53.7, - 52.8, - 51.9, - 50.8, - 49.2, - 47.4, - 46.2, - 45, - 43.8, - 42.7, - 42.1, - 41.6, - 40.7, - 39.5, - 38.6, - 37.7, - 36.6, - 35.4, - 34.4, - 32.9, - 31.4, - 29.9, - 28.7, - 27, - 25.8, - 25.3, - 25, - 24.8, - 24.5, - 24.3, - 24, - 23.7, - 23.4, - 23.2, - 23, - 22.7, - 22.4, - 22.2, - 22, - 21.9, - 21.7, - 21.7, - 21.7, - 21.6, - 21.5, - 21.5, - 21.5, - 21.6, - 21.6, - 21.5, - 21.5, - 21.5, - 21.5, - 21.4, - 21.4, - 21.3, - 21.4, - 21.4, - 21.4, - 21.4, - 21.2, - 21.1, - 21, - 20.7, - 20.5, - 20.2, - 19.9, - 19.7, - 19.6, - 19.6, - 19.4, - 19.2, - 19, - 18.9, - 18.8, - 18.5, - 18.2, - 17.9, - 17.7, - 17.5, - 17.3, - 17.1, - 17, - 16.9, - 16.7, - 16.5, - 16.3, - 16.1, - 16, - 16, - 523.5, - 523.1, - 522.6, - 522.3, - 522.4, - 522.9, - 523.8, - 524, - 523.1, - 522.9, - 522.6, - 522.1, - 522.3, - 522.2, - 521.3, - 520.8, - 520.8, - 520.9, - 521.4, - 521.3, - 520.9, - 520.6, - 519.7, - 514.9, - 483.6, - 440, - 398.1, - 362.2, - 340.2, - 326.7, - 317.2, - 312.7, - 310.3, - 308.3, - 306.4, - 303.7, - 300.1, - 297, - 295.3, - 294.2, - 293.1, - 291.9, - 290.1, - 288.6, - 287.3, - 284.9, - 280.1, - 275.8, - 272.3, - 267.9, - 264.6, - 261.5, - 258.4, - 255, - 249.5, - 245.4, - 242.1, - 240.2, - 238.6, - 236.3, - 234.2, - 233.2, - 232.7, - 234, - 237.3, - 237.8, - 236.4, - 234.3, - 232, - 229.7, - 227.5, - 225.3, - 223.8, - 227, - 233.3, - 231.7, - 228.8, - 226.1, - 223.4, - 221, - 218.7, - 216.4, - 213.5, - 207.7, - 194.7, - 188.1, - 185.2, - 182.6, - 180.6, - 178.8, - 177.6, - 176, - 172.5, - 169.5, - 165.8, - 158.3, - 153.1, - 149, - 148.6, - 147.6, - 145.3, - 142.5, - 138.8, - 132.8, - 125.4, - 121.9, - 120.1, - 118.1, - 116.1, - 114.2, - 112.7, - 111.6, - 110.9, - 110.3, - 109.7, - 108.9, - 107.9, - 106.7, - 105.6, - 104.6, - 103.4, - 102.2, - 101.2, - 100.2, - 98.9, - 97.6, - 96.2, - 95.4, - 94.5, - 93.5, - 92.2, - 90.9, - 89.8, - 88.5, - 87.4, - 86.1, - 84.8, - 83.7, - 82.5, - 81.6, - 80.6, - 79.4, - 77.9, - 76.6, - 75.3, - 74, - 72.9, - 71.9, - 70.8, - 69.6, - 68.7, - 67.7, - 66.8, - 65.8, - 64.9, - 64.2, - 63.3, - 62.3, - 61.3, - 59.8, - 58.6, - 57.8, - 57, - 56.3, - 55.4, - 54.9, - 54.5, - 53.6, - 52.6, - 51.7, - 50.5, - 49.3, - 48.1, - 46.7, - 45, - 44, - 43.3, - 42.8, - 42.3, - 41.3, - 40.2, - 39.2, - 38.2, - 36.8, - 35.5, - 34.4, - 32.8, - 31.3, - 29.6, - 28.2, - 26.9, - 26.2, - 25.9, - 25.5, - 25.3, - 25.1, - 24.8, - 24.6, - 24.3, - 24, - 23.8, - 23.6, - 23.3, - 23, - 22.8, - 22.6, - 22.5, - 22.4, - 22.4, - 22.3, - 22.3, - 22.2, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.1, - 22, - 21.9, - 22, - 22.1, - 22.1, - 22, - 21.8, - 21.6, - 21.5, - 21.2, - 20.9, - 20.6, - 20.3, - 20.2, - 20, - 19.9, - 19.8, - 19.7, - 19.4, - 19.1, - 19, - 19, - 18.6, - 18.4, - 18.2, - 17.9, - 17.7, - 17.3, - 17.1, - 17, - 17, - 16.9, - 16.7, - 16.5, - 16.3, - 16.2, - 525.2, - 525, - 524.7, - 524.2, - 524.4, - 524.7, - 525, - 525.2, - 524.5, - 524.3, - 524.2, - 523.8, - 523.7, - 523.5, - 523.1, - 523.1, - 523.3, - 523.6, - 524, - 523.5, - 522.5, - 521.9, - 521.2, - 520.2, - 501.5, - 463.4, - 424, - 386.5, - 353.7, - 334.3, - 324.1, - 316, - 310.8, - 308.6, - 306.8, - 303.7, - 301, - 298.4, - 295.9, - 294.4, - 293.4, - 292.2, - 290.9, - 289.6, - 288.6, - 286.9, - 282.4, - 277.4, - 274.2, - 270.9, - 267.1, - 263.6, - 260.9, - 257.9, - 251.6, - 246.9, - 244.2, - 242.1, - 240.4, - 238.7, - 237.1, - 236.2, - 236.7, - 239.9, - 241.3, - 239.9, - 238.3, - 236.3, - 233.8, - 231.3, - 229.1, - 226.9, - 227, - 235, - 238.3, - 235.5, - 232.9, - 230.3, - 226.9, - 224, - 221.5, - 219.2, - 217, - 211.1, - 197.5, - 191.3, - 188.6, - 185.7, - 183.5, - 181.9, - 180.4, - 178.2, - 175, - 172.2, - 166.3, - 159.8, - 154.5, - 151, - 152.9, - 152.3, - 149.3, - 145.3, - 140.1, - 133.3, - 128.1, - 123.6, - 120.3, - 118.4, - 116.4, - 114.5, - 112.8, - 111.6, - 110.8, - 110, - 109.6, - 109, - 108.3, - 107, - 105.7, - 104.6, - 103.5, - 102.3, - 101.2, - 100.2, - 98.9, - 97.5, - 96.3, - 95.3, - 94.3, - 93.2, - 92, - 90.8, - 90, - 88.6, - 87.3, - 86, - 84.9, - 83.7, - 82.6, - 81.5, - 80.5, - 79.4, - 78, - 76.6, - 75.3, - 74, - 72.8, - 71.8, - 70.8, - 69.8, - 68.9, - 68, - 67, - 66.2, - 65.3, - 64.5, - 63.6, - 62.4, - 61, - 59.3, - 58.3, - 57.6, - 57.3, - 56.6, - 55.6, - 55, - 54.4, - 53.5, - 52.8, - 52.3, - 51, - 49.5, - 48.6, - 47.3, - 45.7, - 45, - 44.3, - 43.9, - 43.3, - 41.7, - 40.4, - 39.3, - 38, - 36.8, - 35.5, - 34.1, - 32.6, - 31.2, - 29.3, - 27.9, - 27.1, - 26.8, - 26.4, - 26.1, - 25.9, - 25.6, - 25.4, - 25.1, - 24.9, - 24.6, - 24.4, - 24.2, - 23.9, - 23.7, - 23.4, - 23.2, - 23.1, - 23, - 23, - 22.9, - 22.9, - 22.9, - 22.9, - 22.9, - 23, - 22.9, - 23, - 23.1, - 23.1, - 22.8, - 22.7, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 22.6, - 22.3, - 22.1, - 22, - 21.7, - 21.4, - 21.1, - 20.8, - 20.7, - 20.5, - 20.3, - 20.3, - 20.2, - 19.8, - 19.5, - 19.4, - 19.4, - 19.2, - 19, - 18.7, - 18.3, - 18, - 17.6, - 17.4, - 17.3, - 17.3, - 17.3, - 17, - 16.7, - 16.6, - 16.4, - 526.9, - 526.8, - 526.5, - 526.4, - 526.5, - 526.4, - 526.4, - 526.4, - 525.9, - 525.5, - 525.4, - 525.3, - 525.2, - 525.4, - 525.1, - 525.2, - 525.4, - 525.4, - 525.3, - 524.8, - 523.9, - 523.3, - 523.1, - 523, - 517.6, - 488.4, - 450.7, - 415.8, - 376.9, - 342.7, - 327.9, - 320.3, - 312.8, - 309.3, - 307.2, - 304.6, - 302.6, - 300.5, - 297.6, - 295.4, - 293.9, - 292.6, - 291.4, - 290.3, - 289.3, - 287.9, - 283.7, - 279.2, - 276.1, - 272.6, - 269.5, - 266.4, - 262.8, - 257.7, - 252.9, - 249.1, - 246.7, - 244.2, - 242.1, - 240, - 238.7, - 239.7, - 242.2, - 245.8, - 245.9, - 242.9, - 240.7, - 238.1, - 235.4, - 233.1, - 230.7, - 228.7, - 232.5, - 240.5, - 242, - 239.2, - 236.8, - 234.3, - 230.6, - 227.3, - 224.5, - 222, - 219.7, - 214.6, - 204.2, - 196.4, - 192.1, - 188.9, - 187, - 185.9, - 183.4, - 181, - 178.2, - 174.5, - 167.1, - 162, - 156.1, - 154.6, - 157, - 156.1, - 152.4, - 147.4, - 142.2, - 137.1, - 132.5, - 127.5, - 122.7, - 119.1, - 116.5, - 114.7, - 113, - 111.5, - 110.3, - 109.6, - 109.2, - 108.9, - 108.3, - 107, - 105.8, - 104.7, - 103.7, - 102.6, - 101.3, - 100.1, - 98.9, - 97.6, - 96.4, - 95.3, - 94.3, - 93.2, - 91.9, - 90.7, - 90, - 88.8, - 87.3, - 86, - 84.9, - 83.9, - 82.7, - 81.6, - 80.6, - 79.5, - 78.1, - 76.7, - 75.4, - 74, - 72.8, - 71.7, - 70.7, - 69.7, - 68.8, - 68, - 67.1, - 66.3, - 65.5, - 64.6, - 63.8, - 62.9, - 61.5, - 60, - 58.9, - 57.8, - 57.3, - 56.6, - 55.7, - 55, - 54.1, - 53.7, - 53.5, - 52.7, - 51.3, - 50.1, - 49.4, - 48.3, - 47.3, - 46.4, - 45.3, - 44.7, - 43.7, - 41.7, - 40.5, - 39.3, - 37.9, - 36.6, - 35.4, - 33.8, - 32.2, - 30.8, - 29.2, - 28.2, - 27.7, - 27.3, - 27, - 26.7, - 26.4, - 26.2, - 25.9, - 25.6, - 25.4, - 25.2, - 25, - 24.8, - 24.5, - 24.2, - 24, - 23.9, - 23.7, - 23.6, - 23.6, - 23.6, - 23.5, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.8, - 23.8, - 23.6, - 23.5, - 23.3, - 23.3, - 23.3, - 23.3, - 23.3, - 23.1, - 22.9, - 22.7, - 22.5, - 22.2, - 21.9, - 21.6, - 21.4, - 21.2, - 20.9, - 20.8, - 20.7, - 20.6, - 20.2, - 20, - 20, - 19.9, - 19.7, - 19.4, - 18.9, - 18.5, - 18.3, - 18, - 17.8, - 17.6, - 17.7, - 17.6, - 17.3, - 16.9, - 16.9, - 16.9, - 528.6, - 528.4, - 528.4, - 528.4, - 528.4, - 528.3, - 528, - 527.8, - 527.6, - 527.2, - 527.1, - 527, - 527.2, - 526.9, - 527, - 527.2, - 527.1, - 526.8, - 526.4, - 525.9, - 525.4, - 525.6, - 525.6, - 525.1, - 524.5, - 511.5, - 481, - 444.9, - 403.3, - 358.4, - 330.5, - 322.2, - 315.5, - 310.7, - 307.9, - 306.1, - 304.3, - 302.5, - 300.3, - 297.8, - 295.5, - 293.7, - 292, - 290.9, - 290, - 288.7, - 285.8, - 281.5, - 278.1, - 274.5, - 271.2, - 268.4, - 264.9, - 259.1, - 255.1, - 251.2, - 248.8, - 245.8, - 243.4, - 241.9, - 241, - 243.1, - 246.8, - 250.3, - 250.8, - 246.6, - 243.2, - 240.9, - 238.1, - 235.4, - 232.5, - 232, - 238.6, - 244, - 244.1, - 242.3, - 240.4, - 237.9, - 234.6, - 230.9, - 227.7, - 224.9, - 221.9, - 217.6, - 209.4, - 203.7, - 198.1, - 192.6, - 191.6, - 189.4, - 186.2, - 183.9, - 181.3, - 175.5, - 169, - 163.4, - 158.8, - 159, - 159.6, - 158.2, - 155.3, - 150.5, - 146.4, - 143, - 138.6, - 133.1, - 127.4, - 121.7, - 117.3, - 114.9, - 113.3, - 111.7, - 110.1, - 109.1, - 108.8, - 108.2, - 107.5, - 106.7, - 105.8, - 104.7, - 103.8, - 102.6, - 101.4, - 100.1, - 99, - 97.9, - 96.6, - 95.4, - 94.3, - 93.1, - 91.9, - 90.9, - 90, - 88.8, - 87.5, - 86.1, - 85, - 83.8, - 82.6, - 81.6, - 80.4, - 79.3, - 78.1, - 76.8, - 75.4, - 74, - 72.9, - 71.9, - 70.7, - 69.5, - 68.5, - 67.7, - 66.9, - 66.1, - 65.3, - 64.3, - 63.6, - 62.9, - 61.7, - 60.2, - 58.8, - 57.9, - 57.1, - 56.2, - 55.4, - 54.8, - 54, - 53.7, - 53, - 51.9, - 51.3, - 50.8, - 50.2, - 49.3, - 48.3, - 47, - 45.7, - 44.6, - 43.3, - 41.7, - 40.5, - 39.1, - 37.7, - 36.4, - 35.1, - 33.4, - 31.7, - 30.2, - 29.2, - 28.6, - 28.1, - 27.9, - 27.6, - 27.3, - 27, - 26.7, - 26.5, - 26.2, - 26, - 25.8, - 25.6, - 25.3, - 25.1, - 24.8, - 24.6, - 24.4, - 24.3, - 24.2, - 24.2, - 24.2, - 24.3, - 24.3, - 24.4, - 24.4, - 24.4, - 24.4, - 24.4, - 24.4, - 24.4, - 24.4, - 24.2, - 24, - 24, - 24, - 23.8, - 23.7, - 23.6, - 23.4, - 23.1, - 23, - 22.7, - 22.4, - 22.1, - 22, - 21.8, - 21.5, - 21.4, - 21.2, - 21, - 20.8, - 20.8, - 20.6, - 20.3, - 20.1, - 19.7, - 19.3, - 18.9, - 18.7, - 18.5, - 18.2, - 18, - 18, - 17.9, - 17.7, - 17.3, - 17.1, - 17.1, - 530.1, - 530.2, - 530.3, - 530.1, - 530, - 530, - 530, - 530.1, - 529.3, - 528.7, - 528.7, - 529, - 529.6, - 529.1, - 528.6, - 528.8, - 528.6, - 528.1, - 527.7, - 527.5, - 527.7, - 528, - 527.6, - 527.4, - 527.2, - 525.7, - 510.3, - 471.6, - 425.3, - 376.1, - 335.5, - 323.9, - 318.3, - 312.8, - 310.1, - 308.2, - 306.7, - 305.6, - 304.2, - 301.6, - 298.6, - 295.6, - 293.5, - 291.7, - 290.5, - 289.4, - 287.8, - 284.1, - 279.9, - 276.9, - 273.2, - 270, - 266.8, - 260.3, - 255.9, - 252.8, - 250.6, - 247.4, - 245.6, - 244.4, - 243.6, - 245.5, - 249.7, - 254.6, - 255, - 250.7, - 246.5, - 244.1, - 241.8, - 238.9, - 236.3, - 238.2, - 247.3, - 247.4, - 245.6, - 244.5, - 243.2, - 241.1, - 238.3, - 234.9, - 231.3, - 228.5, - 225, - 219.3, - 212.5, - 207.8, - 203, - 197.5, - 196.9, - 193.9, - 190, - 186.6, - 182.8, - 176.3, - 170.9, - 164.7, - 162.2, - 163.3, - 162.2, - 160.5, - 158.1, - 153.9, - 150.6, - 147.8, - 144.5, - 140, - 133.4, - 127.1, - 121.3, - 116.4, - 113.7, - 111.9, - 110.3, - 108.9, - 108.2, - 107.9, - 107.4, - 106.6, - 105.6, - 104.7, - 103.7, - 102.6, - 101.4, - 100.2, - 99, - 97.9, - 96.7, - 95.5, - 94.3, - 93.2, - 92.1, - 91, - 89.9, - 88.7, - 87.5, - 86.2, - 85, - 83.8, - 82.6, - 81.5, - 80.3, - 79.1, - 77.8, - 76.5, - 75.1, - 73.8, - 72.8, - 71.9, - 70.7, - 69.5, - 68.4, - 67.4, - 66.6, - 65.9, - 65, - 63.9, - 63, - 62.1, - 61.2, - 59.9, - 58.6, - 57.7, - 56.8, - 56.1, - 55.5, - 54.9, - 54.1, - 53.6, - 52.9, - 52.2, - 51.7, - 51.4, - 51.1, - 50.3, - 49, - 47.4, - 45.7, - 44.5, - 43, - 41.6, - 40.3, - 38.9, - 37.4, - 36, - 34.5, - 32.8, - 31.2, - 30.1, - 29.5, - 29, - 28.7, - 28.4, - 28.1, - 27.9, - 27.6, - 27.3, - 27.1, - 26.8, - 26.6, - 26.4, - 26.2, - 26, - 25.8, - 25.5, - 25.2, - 25, - 24.9, - 24.9, - 24.8, - 24.9, - 24.9, - 25, - 25, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25.2, - 25.2, - 25, - 24.8, - 24.8, - 24.6, - 24.4, - 24.3, - 24.1, - 23.8, - 23.5, - 23.4, - 23.2, - 23, - 22.7, - 22.5, - 22.3, - 22, - 21.9, - 21.7, - 21.6, - 21.5, - 21.4, - 21.1, - 20.8, - 20.4, - 20.1, - 19.7, - 19.5, - 19.2, - 18.9, - 18.6, - 18.5, - 18.4, - 18.3, - 18.2, - 17.7, - 17.2, - 17.2, - 531.4, - 531.8, - 532.1, - 531.9, - 531.8, - 531.8, - 531.9, - 532.2, - 531.4, - 530.4, - 530.7, - 531.4, - 531.7, - 531.6, - 530.7, - 530.1, - 529.9, - 529.4, - 529.4, - 529.7, - 529.5, - 529.5, - 529.7, - 530.1, - 530.3, - 529.6, - 524.8, - 490.6, - 441, - 389.4, - 342.8, - 324.7, - 320.2, - 316.6, - 314, - 311.4, - 310.2, - 311.6, - 311, - 308, - 304.7, - 301.1, - 297.9, - 294.1, - 291.9, - 290, - 288.4, - 285.8, - 281.9, - 279.4, - 276.3, - 272.6, - 267.6, - 260.7, - 256.3, - 253.7, - 251.8, - 250.8, - 250.2, - 248.8, - 247.3, - 247.6, - 254.2, - 260.1, - 258.4, - 254.3, - 250.4, - 247.5, - 245.5, - 242.5, - 240.2, - 245.1, - 253.7, - 250.4, - 247.8, - 247.1, - 245.4, - 243.8, - 241.5, - 238.7, - 235.4, - 232.3, - 228.8, - 221.9, - 216.1, - 211.8, - 206.5, - 202.4, - 201.6, - 198.7, - 194.7, - 190.3, - 184.1, - 177.5, - 171.8, - 166.7, - 166, - 166.7, - 164.7, - 162.8, - 160.5, - 157.8, - 155.1, - 152.1, - 149.1, - 145.6, - 141, - 134.5, - 127.7, - 121.4, - 115.8, - 112.6, - 110.6, - 109.1, - 108, - 107.8, - 107.5, - 106.8, - 105.7, - 104.7, - 103.7, - 102.6, - 101.5, - 100.3, - 99.2, - 98, - 96.8, - 95.7, - 94.5, - 93.3, - 92, - 90.9, - 89.8, - 88.6, - 87.3, - 86, - 84.7, - 83.6, - 82.5, - 81.5, - 80.4, - 79.1, - 77.7, - 76.3, - 74.8, - 73.5, - 72.5, - 71.5, - 70.6, - 69.7, - 68.4, - 67.1, - 66.3, - 65.6, - 64.9, - 63.8, - 62.9, - 61.8, - 60.9, - 59.9, - 58.6, - 57.5, - 56.8, - 56.2, - 55.8, - 55.2, - 54.5, - 54.1, - 53.5, - 53, - 52.5, - 52.2, - 52.1, - 51.3, - 49.9, - 47.5, - 45.6, - 44.5, - 42.9, - 41.3, - 40, - 38.6, - 37, - 35.5, - 34, - 32.3, - 31.2, - 30.5, - 30, - 29.5, - 29.2, - 29, - 28.7, - 28.5, - 28.2, - 28, - 27.7, - 27.4, - 27.2, - 27, - 26.8, - 26.6, - 26.3, - 26.1, - 25.9, - 25.7, - 25.6, - 25.5, - 25.5, - 25.5, - 25.6, - 25.6, - 25.7, - 25.8, - 25.9, - 25.9, - 25.8, - 25.8, - 25.9, - 25.8, - 25.8, - 25.6, - 25.4, - 25.2, - 25, - 24.8, - 24.6, - 24.2, - 24, - 23.9, - 23.7, - 23.4, - 23.2, - 23, - 22.8, - 22.5, - 22.3, - 22.3, - 22.3, - 22.2, - 22.1, - 21.8, - 21.4, - 21, - 20.5, - 20.2, - 19.9, - 19.5, - 19.4, - 19.2, - 19, - 19, - 18.8, - 18.6, - 18, - 17.2, - 17.3, - 532.6, - 533, - 533.4, - 533.5, - 533.6, - 533.6, - 533.3, - 533.4, - 532.8, - 532.3, - 533, - 533.5, - 533.7, - 533.6, - 532.8, - 531.7, - 531.3, - 531.1, - 531.6, - 531.9, - 531.3, - 531.2, - 531.5, - 531.7, - 531.7, - 531.3, - 529.7, - 505.2, - 458, - 407.9, - 357.7, - 330.4, - 325.7, - 323.1, - 321.7, - 321.4, - 319.6, - 319, - 318.3, - 316.1, - 313.5, - 310.8, - 306.7, - 301.2, - 296.5, - 292.7, - 289.5, - 286.3, - 283.6, - 281.4, - 278.9, - 275.6, - 269.2, - 262.3, - 257.5, - 254.7, - 253.5, - 255, - 256.8, - 257.2, - 256.5, - 255.6, - 261.1, - 264, - 261.4, - 257.9, - 253.9, - 250.6, - 248.5, - 246.2, - 246.7, - 253.5, - 257.8, - 255.2, - 253.2, - 251.2, - 248.5, - 246.2, - 244.1, - 241.9, - 239, - 236, - 230.7, - 223.7, - 220, - 215.6, - 210.2, - 207.7, - 205.7, - 202.5, - 199.1, - 194.7, - 188.1, - 179.2, - 172.3, - 168.6, - 169, - 169.6, - 167.9, - 165.8, - 164.1, - 162.1, - 159.7, - 156.8, - 153.6, - 150.4, - 147.3, - 143, - 135.6, - 128.6, - 121.6, - 116, - 111.6, - 109.3, - 108.1, - 107.8, - 107.4, - 106.9, - 105.8, - 104.6, - 103.6, - 102.6, - 101.4, - 100.4, - 99.4, - 98.1, - 96.9, - 95.8, - 94.6, - 93.3, - 92.1, - 90.9, - 89.7, - 88.3, - 87, - 85.9, - 84.6, - 83.4, - 82.4, - 81.4, - 80.4, - 79.1, - 77.7, - 76.3, - 74.6, - 73.2, - 72.1, - 71.2, - 70.4, - 69.4, - 68.2, - 67.1, - 66.1, - 65.2, - 64.6, - 63.9, - 63, - 61.8, - 60.9, - 59.9, - 58.6, - 57.5, - 56.8, - 56.6, - 56.4, - 56.2, - 55.7, - 55.1, - 54.4, - 54, - 53.3, - 52.9, - 52.7, - 51.8, - 50.3, - 47.9, - 45.6, - 44.4, - 42.8, - 41.1, - 39.8, - 38.3, - 36.5, - 35.1, - 33.6, - 32.3, - 31.6, - 31.1, - 30.5, - 30.1, - 29.8, - 29.6, - 29.3, - 29, - 28.8, - 28.6, - 28.3, - 28, - 27.8, - 27.6, - 27.4, - 27.2, - 27, - 26.7, - 26.5, - 26.4, - 26.2, - 26.2, - 26.2, - 26.2, - 26.3, - 26.4, - 26.4, - 26.6, - 26.7, - 26.6, - 26.5, - 26.5, - 26.5, - 26.4, - 26.4, - 26.3, - 26.1, - 26, - 25.6, - 25.3, - 25.1, - 24.8, - 24.5, - 24.4, - 24.1, - 23.9, - 23.7, - 23.5, - 23.2, - 23, - 22.8, - 22.8, - 22.8, - 22.9, - 22.7, - 22.3, - 21.9, - 21.3, - 20.8, - 20.6, - 20.3, - 20.1, - 19.8, - 19.7, - 19.7, - 19.5, - 19.2, - 18.8, - 18.3, - 17.5, - 17.3, - 534, - 534.5, - 534.8, - 535, - 534.9, - 535.1, - 534.9, - 534.6, - 534.1, - 534.2, - 534.3, - 535, - 535.6, - 535.2, - 534.7, - 533.6, - 532.8, - 533.3, - 534.4, - 534.7, - 534.1, - 533.7, - 533.7, - 533.5, - 533.4, - 533.3, - 533, - 524.2, - 486.1, - 436.7, - 394.2, - 368.4, - 349.9, - 338.7, - 340.6, - 342.4, - 335.4, - 329.5, - 326.9, - 325.7, - 323.6, - 319.8, - 315.6, - 309, - 302.2, - 297.2, - 292.7, - 288.1, - 285.4, - 283.3, - 281.3, - 278.5, - 273.1, - 265.8, - 259.5, - 257.2, - 257.7, - 260, - 262.8, - 264.5, - 265.1, - 263.9, - 266.9, - 267.4, - 264.5, - 261.4, - 257.5, - 253.8, - 251.3, - 251, - 256, - 261.6, - 261.2, - 258.6, - 257, - 255.5, - 252.4, - 249.4, - 246.8, - 244.6, - 242.2, - 239.1, - 231.7, - 226.2, - 223.5, - 219, - 213.4, - 213.2, - 211, - 206.5, - 202.8, - 199.3, - 193.3, - 180.7, - 172.7, - 170.2, - 171.2, - 172.3, - 171.6, - 170, - 168, - 166.2, - 164, - 161.4, - 158.2, - 154.9, - 151.9, - 148.5, - 143.4, - 136.8, - 130, - 122.8, - 115.7, - 110, - 108.3, - 107.7, - 107.3, - 106.8, - 105.7, - 104.6, - 103.7, - 102.6, - 101.4, - 100.4, - 99.3, - 98.1, - 96.8, - 95.7, - 94.6, - 93.3, - 92, - 90.8, - 89.5, - 88.1, - 86.8, - 85.7, - 84.6, - 83.4, - 82.4, - 81.4, - 80.3, - 79, - 77.6, - 76.3, - 74.7, - 73.2, - 72.1, - 71.1, - 70.3, - 69.5, - 68.2, - 67.1, - 66.1, - 65.1, - 64.1, - 63.2, - 62.4, - 61.7, - 61.1, - 60.1, - 58.8, - 57.8, - 57.3, - 57.2, - 57.1, - 57, - 56.5, - 55.7, - 54.9, - 54.3, - 53.5, - 53, - 52.5, - 51.4, - 49.9, - 47.8, - 46, - 44.4, - 42.6, - 41, - 39.5, - 38, - 36.3, - 34.8, - 33.5, - 32.7, - 32.1, - 31.6, - 31.1, - 30.7, - 30.4, - 30.1, - 29.9, - 29.6, - 29.4, - 29.2, - 28.9, - 28.6, - 28.5, - 28.3, - 28, - 27.8, - 27.6, - 27.4, - 27.2, - 27.1, - 26.9, - 26.9, - 27, - 27, - 27, - 27.1, - 27.2, - 27.4, - 27.4, - 27.3, - 27.1, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.6, - 26.4, - 26.1, - 25.8, - 25.6, - 25.4, - 25.1, - 25, - 24.8, - 24.5, - 24.3, - 24.1, - 23.9, - 23.7, - 23.6, - 23.5, - 23.4, - 23.3, - 23, - 22.7, - 22.4, - 21.8, - 21.5, - 21.1, - 20.8, - 20.6, - 20.3, - 20.3, - 20, - 19.7, - 19.5, - 19.1, - 18.6, - 18, - 17.6, - 535.3, - 535.6, - 536, - 536.3, - 536.2, - 536.4, - 536.3, - 536.3, - 536.2, - 536.3, - 535.5, - 536.5, - 537.7, - 537.1, - 536.3, - 535.2, - 534.8, - 535.8, - 536.5, - 536.9, - 536.7, - 536.7, - 536.6, - 536.7, - 536.7, - 535.3, - 534.7, - 533.6, - 518.9, - 480.7, - 448.3, - 427.2, - 404.5, - 382.9, - 372.2, - 374.1, - 364.1, - 350.1, - 338.2, - 334.4, - 332.4, - 327.5, - 322.6, - 317, - 309.1, - 301.8, - 296.2, - 291.7, - 288.1, - 285.4, - 283.6, - 281.2, - 276.6, - 268.2, - 262.2, - 264.8, - 266.2, - 266.8, - 268.9, - 268.9, - 269, - 270.5, - 273, - 271.8, - 267.7, - 264.7, - 260.7, - 257.1, - 255.1, - 259.1, - 265.4, - 266.4, - 263.8, - 261.7, - 260.5, - 258.7, - 256.1, - 252.9, - 250, - 247.5, - 245.3, - 241.3, - 233.4, - 228.9, - 226, - 220.1, - 217.5, - 218.3, - 216.7, - 212, - 206.3, - 203, - 195.2, - 180.1, - 174.1, - 172.4, - 173.4, - 174.6, - 174.6, - 174, - 172.1, - 170.1, - 168, - 165.6, - 162.7, - 159.4, - 156.4, - 153.1, - 148.7, - 143.1, - 137.1, - 130, - 122.8, - 114.2, - 109, - 107.5, - 107, - 106.5, - 105.6, - 104.6, - 103.6, - 102.5, - 101.4, - 100.3, - 99.2, - 98, - 96.7, - 95.5, - 94.4, - 93.1, - 91.9, - 90.5, - 89.3, - 88.1, - 86.7, - 85.5, - 84.5, - 83.5, - 82.4, - 81.4, - 80.4, - 79.2, - 77.9, - 76.8, - 75.5, - 73.7, - 72.2, - 71.1, - 70.4, - 69.7, - 68.4, - 67.3, - 66.3, - 65.3, - 64.1, - 63, - 62.4, - 61.8, - 61.3, - 60.4, - 59.5, - 58.8, - 58.4, - 58.1, - 57.8, - 57.4, - 56.7, - 55.8, - 54.9, - 54.1, - 53.2, - 52.2, - 51.3, - 50.1, - 48.8, - 47.5, - 46.2, - 44.4, - 42.5, - 40.8, - 39.3, - 37.9, - 36.3, - 34.7, - 33.7, - 33.1, - 32.6, - 32.1, - 31.6, - 31.2, - 31, - 30.8, - 30.5, - 30.3, - 30, - 29.8, - 29.5, - 29.3, - 29.1, - 28.9, - 28.7, - 28.5, - 28.3, - 28, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.7, - 27.7, - 27.7, - 27.7, - 27.9, - 27.9, - 27.8, - 27.8, - 27.8, - 27.7, - 27.6, - 27.6, - 27.4, - 27.1, - 26.9, - 26.6, - 26.4, - 26.1, - 25.9, - 25.8, - 25.6, - 25.4, - 25.1, - 24.9, - 24.6, - 24.4, - 24.4, - 24.6, - 24.3, - 24.1, - 23.7, - 23.4, - 23.1, - 22.8, - 22.5, - 22.3, - 21.8, - 21.4, - 21, - 20.9, - 20.9, - 20.5, - 20.2, - 20, - 19.4, - 18.9, - 18.5, - 18, - 536.6, - 536.8, - 537.1, - 537.3, - 537.4, - 537.5, - 537.8, - 538, - 538.1, - 538.1, - 538.4, - 539, - 539.1, - 538.5, - 537.5, - 536.9, - 537.6, - 538.2, - 538.9, - 539.3, - 538.9, - 539.2, - 539.6, - 540, - 539.5, - 537.6, - 536.3, - 536, - 534.2, - 522, - 503.9, - 485, - 458.2, - 435.3, - 418.7, - 400.8, - 381.6, - 366.4, - 351.4, - 345.1, - 341.4, - 336.6, - 329.9, - 324.2, - 317, - 307.2, - 299.5, - 297.1, - 295.6, - 290.8, - 286, - 283.1, - 279.7, - 272.3, - 267.6, - 269.3, - 270.9, - 272.7, - 274.2, - 273.5, - 273.8, - 277.6, - 278.8, - 275.9, - 270.7, - 267.2, - 263.8, - 261.5, - 262.5, - 267.2, - 270.2, - 268.5, - 265.8, - 264.3, - 262.8, - 260.7, - 257.9, - 255.6, - 253.4, - 250.7, - 248.3, - 243.4, - 235.3, - 231.7, - 227.9, - 222.3, - 222.3, - 221.1, - 219.5, - 215.8, - 210.3, - 206, - 197.2, - 182, - 177, - 175.8, - 176.4, - 177, - 177.8, - 177.8, - 176.3, - 174.2, - 172, - 169.5, - 166.9, - 163.8, - 160.9, - 157.7, - 153.4, - 148.7, - 143.5, - 137.5, - 131.3, - 122.5, - 113, - 108, - 106.9, - 106.1, - 105.3, - 104.3, - 103.2, - 102.2, - 101.1, - 100, - 99, - 97.8, - 96.6, - 95.4, - 94.2, - 93, - 91.8, - 90.4, - 89.1, - 87.9, - 86.6, - 85.6, - 84.6, - 83.4, - 82.4, - 81.2, - 80.3, - 79.3, - 78.2, - 77, - 75.5, - 73.7, - 72.3, - 71.2, - 70.6, - 70, - 68.7, - 67.7, - 66.6, - 65.6, - 64.2, - 63, - 62.5, - 62.2, - 61.7, - 60.9, - 60.2, - 59.6, - 59.2, - 58.7, - 58.1, - 57.3, - 56.3, - 55.4, - 54.4, - 53.4, - 52.5, - 51.5, - 50.3, - 49.3, - 48.2, - 47.3, - 46.3, - 44.6, - 42.3, - 40.6, - 39, - 37.5, - 36.1, - 34.9, - 34.2, - 33.7, - 33.1, - 32.7, - 32.2, - 31.8, - 31.6, - 31.4, - 31.1, - 30.9, - 30.7, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.4, - 29.2, - 28.9, - 28.7, - 28.5, - 28.3, - 28.2, - 28.2, - 28.3, - 28.3, - 28.4, - 28.4, - 28.4, - 28.5, - 28.4, - 28.4, - 28.4, - 28.3, - 28.3, - 28.1, - 28, - 27.9, - 27.8, - 27.5, - 27.3, - 27, - 26.7, - 26.5, - 26.2, - 26.2, - 26, - 25.8, - 25.5, - 25.3, - 25.1, - 25.3, - 25.4, - 24.9, - 24.7, - 24.4, - 24, - 23.6, - 23.3, - 23.1, - 22.8, - 22.3, - 22, - 21.5, - 21.3, - 21, - 21, - 21, - 20.8, - 20, - 19.4, - 18.9, - 18.5, - 538.1, - 538.3, - 538.6, - 538.8, - 538.6, - 538.5, - 539, - 539.4, - 539.6, - 539.6, - 540.2, - 541.1, - 540.5, - 539.8, - 539.1, - 539.4, - 540.8, - 541, - 542.1, - 542.6, - 541.2, - 541.7, - 542, - 541.9, - 541.2, - 539.7, - 538.6, - 537.9, - 537.2, - 536.9, - 534.6, - 528.1, - 507.1, - 485.5, - 463, - 432, - 396.3, - 377.4, - 365.3, - 357.8, - 351.3, - 344.6, - 337.8, - 332.1, - 324.6, - 315.7, - 309.5, - 306.8, - 302.5, - 297.4, - 290.5, - 285, - 281.3, - 276.2, - 271.1, - 270.8, - 272, - 274.4, - 277.5, - 280.1, - 280.3, - 281.5, - 283.2, - 281, - 275.8, - 271.2, - 269.4, - 270.2, - 272, - 273.5, - 272.5, - 270.4, - 267.9, - 266.6, - 264.8, - 262.8, - 260.2, - 258, - 255.8, - 253.6, - 251.1, - 245.5, - 237.6, - 234.5, - 231.4, - 228, - 226.5, - 224.1, - 221.6, - 218, - 212.7, - 205.8, - 197.3, - 189, - 182.9, - 179.2, - 179.9, - 180, - 181, - 181.3, - 180.3, - 178.4, - 176.2, - 173.6, - 171, - 168.2, - 165.1, - 162.1, - 158.1, - 154.2, - 149.6, - 144.4, - 138.3, - 132, - 123.1, - 113.7, - 108.1, - 106.2, - 105.3, - 104.4, - 103.1, - 101.7, - 100.7, - 99.6, - 98.5, - 97.5, - 96.3, - 95.1, - 94, - 92.7, - 91.6, - 90.3, - 89, - 87.7, - 86.6, - 85.6, - 84.6, - 83.4, - 82.3, - 81.2, - 80.2, - 79.4, - 78.3, - 77.1, - 75.2, - 73.8, - 72.7, - 71.6, - 70.8, - 69.8, - 68.9, - 68, - 66.9, - 65.7, - 64.4, - 63.2, - 62.7, - 62.4, - 62.1, - 61.5, - 60.8, - 60, - 59.4, - 58.7, - 57.9, - 56.9, - 55.8, - 54.8, - 53.6, - 52.6, - 51.7, - 50.7, - 49.6, - 48.5, - 47.5, - 46.5, - 45.3, - 43.7, - 41.9, - 40.3, - 38.7, - 37.2, - 36.1, - 35.3, - 34.8, - 34.2, - 33.7, - 33.2, - 32.8, - 32.4, - 32.2, - 32, - 31.8, - 31.6, - 31.4, - 31.1, - 30.9, - 30.7, - 30.5, - 30.3, - 30.1, - 29.9, - 29.7, - 29.5, - 29.2, - 29, - 28.9, - 28.9, - 29, - 29, - 29, - 29.1, - 29.1, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.7, - 28.5, - 28.4, - 28.3, - 28.1, - 27.9, - 27.7, - 27.4, - 27.2, - 26.9, - 26.8, - 26.7, - 26.5, - 26.2, - 25.9, - 25.8, - 26.1, - 26.1, - 25.6, - 25.4, - 25.2, - 24.7, - 24, - 23.8, - 23.7, - 23.3, - 22.9, - 22.6, - 22.3, - 21.9, - 21.5, - 21.6, - 21.6, - 21.4, - 20.7, - 19.9, - 19.3, - 18.9, - 541, - 540.8, - 541, - 540.9, - 540.7, - 540.3, - 540.1, - 540.5, - 540.7, - 540.9, - 542, - 542.4, - 541.7, - 541.1, - 541.1, - 542, - 543.9, - 544.4, - 544.7, - 544.6, - 543.4, - 543.6, - 543.6, - 543.4, - 543.2, - 542, - 540.7, - 540.2, - 539.9, - 539.2, - 538.5, - 538.1, - 535.7, - 524.6, - 498.1, - 458.5, - 416.3, - 386.1, - 375.7, - 367.5, - 360.6, - 354.3, - 345.9, - 338.2, - 330.1, - 322.9, - 318.1, - 315, - 309.7, - 303.3, - 297.7, - 290.1, - 284, - 280.3, - 276.2, - 273.5, - 273.7, - 276.1, - 281.8, - 286.5, - 286.4, - 285.8, - 286.1, - 283.8, - 281.3, - 278.4, - 277.1, - 277.8, - 277.3, - 275.6, - 273.7, - 271.6, - 269.7, - 268.2, - 266.5, - 264.6, - 262.7, - 260.3, - 258, - 255.9, - 253.5, - 247.7, - 240.1, - 237.2, - 234.6, - 232.6, - 230.7, - 228.4, - 224.6, - 220.1, - 212.4, - 203.9, - 197, - 195.6, - 191.5, - 184.7, - 183.5, - 183.3, - 184.4, - 184.8, - 184.4, - 182.1, - 180.2, - 177.9, - 175.3, - 172.4, - 169.5, - 166.6, - 162.7, - 158.8, - 155.2, - 150.4, - 144.5, - 139, - 132.9, - 124.4, - 114.7, - 107.7, - 105.9, - 105.1, - 104, - 102, - 100.3, - 99.1, - 97.9, - 96.9, - 96, - 94.9, - 93.6, - 92.4, - 91.2, - 89.9, - 88.8, - 87.7, - 86.6, - 85.5, - 84.5, - 83.3, - 82.3, - 81.2, - 80.2, - 79.3, - 78.4, - 77.2, - 75.3, - 74, - 73.2, - 72.4, - 71.2, - 69.9, - 69.1, - 68.2, - 67.2, - 66, - 64.6, - 63.7, - 63.3, - 62.9, - 62.6, - 62, - 61.2, - 60.2, - 59.3, - 58.4, - 57.4, - 56.4, - 55.3, - 54.3, - 53.3, - 52.3, - 51.2, - 50.1, - 49, - 48, - 47.1, - 45.8, - 44.5, - 43, - 41.5, - 40, - 38.5, - 37.3, - 36.5, - 35.9, - 35.3, - 34.8, - 34.3, - 33.8, - 33.5, - 33.2, - 32.9, - 32.7, - 32.4, - 32.2, - 32, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 29.9, - 29.7, - 29.7, - 29.7, - 29.7, - 29.7, - 29.7, - 29.8, - 29.8, - 29.7, - 29.7, - 29.6, - 29.6, - 29.4, - 29.3, - 29.2, - 29, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.1, - 27.9, - 27.7, - 27.5, - 27.4, - 27.2, - 26.9, - 26.6, - 26.5, - 26.8, - 26.9, - 26.4, - 26.2, - 26, - 25.6, - 24.6, - 24.3, - 24.5, - 24, - 23.5, - 23.3, - 22.9, - 22.5, - 22.3, - 22.4, - 22.2, - 22, - 21.4, - 20.7, - 20.1, - 19.5, - 543.7, - 543.2, - 543.3, - 543.1, - 542.9, - 542.5, - 541.8, - 541.9, - 542.4, - 543.5, - 544.4, - 544.3, - 543.5, - 543.1, - 543.7, - 544.4, - 545.5, - 546.2, - 546.4, - 545.9, - 544.9, - 545, - 544.9, - 545.1, - 545.5, - 544.7, - 543.5, - 543.3, - 542.4, - 541.3, - 540.7, - 540.6, - 539.9, - 538, - 523, - 483.8, - 438.7, - 396.8, - 381.4, - 374.8, - 368.3, - 362.4, - 356, - 346.2, - 335.9, - 329.2, - 324.8, - 320.9, - 316.1, - 309.3, - 303.4, - 296.6, - 288.9, - 283.8, - 281.1, - 278.5, - 277.4, - 280, - 286.2, - 289.2, - 289.7, - 288.8, - 287.3, - 285, - 283.1, - 281.9, - 280.7, - 279.6, - 278.7, - 277, - 274.5, - 272.6, - 270.8, - 269.2, - 267.8, - 266.4, - 264.6, - 262.3, - 259.9, - 257.7, - 254.9, - 251, - 244.1, - 239.7, - 237.3, - 235.6, - 233.5, - 231.1, - 227.4, - 221.8, - 211.7, - 205.8, - 202.1, - 201.8, - 199.5, - 192.2, - 188.1, - 187.7, - 188.9, - 189.9, - 188.5, - 185.7, - 184.2, - 181.9, - 179.4, - 176.4, - 173.5, - 170.6, - 167.3, - 163.4, - 160.2, - 155.8, - 150.5, - 145.3, - 139.8, - 133.2, - 125.2, - 113.8, - 106.9, - 105.7, - 104.8, - 103.4, - 101, - 99.2, - 97.4, - 96.3, - 95.3, - 94.3, - 93.2, - 92, - 90.8, - 89.6, - 88.5, - 87.6, - 86.5, - 85.3, - 84.3, - 83.1, - 82.1, - 81.1, - 80, - 79.1, - 78.3, - 77.2, - 75.7, - 74.4, - 73.6, - 73, - 72, - 70.2, - 69.2, - 68.3, - 67.5, - 66.2, - 65.1, - 64.5, - 63.8, - 63.3, - 62.7, - 62, - 61.1, - 60.1, - 59.1, - 57.9, - 57, - 56, - 54.9, - 54.2, - 53.4, - 52.4, - 51.2, - 49.9, - 48.5, - 47.4, - 46.4, - 45.3, - 43.9, - 42.5, - 41, - 39.7, - 38.5, - 37.7, - 37.1, - 36.5, - 36, - 35.5, - 35, - 34.5, - 34.2, - 33.9, - 33.6, - 33.3, - 33.1, - 32.9, - 32.7, - 32.5, - 32.3, - 32.1, - 31.9, - 31.7, - 31.5, - 31.3, - 31.1, - 30.9, - 30.7, - 30.5, - 30.4, - 30.4, - 30.4, - 30.4, - 30.5, - 30.6, - 30.6, - 30.5, - 30.4, - 30.4, - 30.3, - 30.1, - 30, - 29.7, - 29.5, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 28.8, - 28.6, - 28.4, - 28.1, - 27.9, - 27.8, - 27.6, - 27.3, - 27.3, - 27.5, - 27.5, - 27.3, - 27, - 26.8, - 26.1, - 25.3, - 25, - 25.1, - 24.7, - 24.3, - 23.8, - 23.5, - 23.2, - 22.9, - 22.9, - 22.8, - 22.6, - 22.1, - 21.4, - 20.9, - 20.5, - 546.2, - 546.1, - 546.1, - 545.1, - 544.6, - 544.2, - 543.9, - 544, - 544.5, - 545.3, - 545.8, - 546.2, - 546, - 545.9, - 546, - 546.7, - 547.6, - 547.8, - 547.6, - 547.2, - 546.4, - 546.3, - 546.4, - 546.4, - 546.5, - 546.2, - 545.8, - 545.6, - 544.5, - 543.2, - 542.8, - 542.7, - 541.9, - 540.7, - 536.5, - 511.1, - 466.3, - 421.1, - 389.2, - 378.2, - 371.6, - 366, - 363.5, - 356.2, - 344.3, - 335.2, - 329.6, - 324.9, - 320.7, - 315.4, - 308.1, - 298.7, - 291.3, - 286.8, - 284.7, - 283.5, - 284, - 286.6, - 289.4, - 291, - 291, - 289.6, - 288.1, - 286.2, - 284.1, - 282.9, - 281.9, - 280.6, - 279.5, - 278.2, - 276.3, - 274.1, - 272.3, - 270.3, - 269.2, - 268.1, - 266.3, - 264.2, - 261.8, - 259, - 256.3, - 253.7, - 249.7, - 243, - 239.6, - 237.8, - 235.7, - 233.1, - 229, - 222.7, - 215.3, - 211.3, - 209.1, - 208.2, - 206.1, - 201.6, - 196.5, - 194.1, - 195.5, - 194.6, - 191.7, - 189.8, - 189.4, - 186.6, - 183.1, - 179.6, - 176.8, - 174.3, - 171.4, - 168, - 164.5, - 160.6, - 156.3, - 151.2, - 145.9, - 140.2, - 133.9, - 125.1, - 112.9, - 106.5, - 105.4, - 104.5, - 102.6, - 100.1, - 97.8, - 96, - 94.7, - 93.7, - 92.7, - 91.6, - 90.5, - 89.4, - 88.3, - 87.3, - 86.3, - 85.1, - 84, - 83, - 82, - 80.9, - 79.8, - 79, - 78.2, - 77.2, - 75.9, - 74.8, - 73.6, - 72.7, - 71.9, - 70.3, - 69.2, - 68.3, - 67.7, - 66.5, - 65.5, - 64.7, - 63.9, - 63.1, - 62.5, - 61.9, - 60.9, - 59.9, - 58.7, - 57.5, - 56.6, - 55.7, - 54.8, - 54.2, - 53.4, - 52.4, - 51.1, - 49.6, - 48, - 46.7, - 45.5, - 44.3, - 43.2, - 41.8, - 40.6, - 39.6, - 38.9, - 38.2, - 37.6, - 37.2, - 36.7, - 36.2, - 35.7, - 35.3, - 34.9, - 34.6, - 34.3, - 34, - 33.8, - 33.6, - 33.4, - 33.2, - 33, - 32.8, - 32.6, - 32.4, - 32.2, - 32, - 31.9, - 31.6, - 31.4, - 31.2, - 31.1, - 31.1, - 31.1, - 31.2, - 31.2, - 31.3, - 31.3, - 31.2, - 31.1, - 31.1, - 30.9, - 30.7, - 30.5, - 30.4, - 30.2, - 30.1, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.3, - 29.1, - 28.8, - 28.6, - 28.5, - 28.3, - 28, - 28.1, - 28.2, - 28.2, - 28, - 27.9, - 27.3, - 26.7, - 26.3, - 26, - 25.8, - 25.5, - 25.1, - 24.6, - 24.2, - 23.9, - 23.5, - 23.3, - 23.2, - 23.1, - 22.7, - 22.1, - 21.8, - 21.6, - 548.7, - 549, - 549.5, - 547.8, - 546.2, - 545.9, - 546.3, - 546.2, - 546.3, - 546.9, - 547.7, - 548.1, - 547.6, - 547.5, - 547.8, - 548.3, - 549.3, - 549.5, - 549, - 548.2, - 547.6, - 547.8, - 547.9, - 547.6, - 547.7, - 547.7, - 547.6, - 547.3, - 546.1, - 545.2, - 544.9, - 544.7, - 543.9, - 543, - 542.7, - 533.5, - 497.1, - 457.7, - 419.4, - 392, - 375.7, - 367.9, - 365.4, - 361.2, - 352.3, - 343, - 334.3, - 327.1, - 322.5, - 318.1, - 310.7, - 299.6, - 293.9, - 291.6, - 291.6, - 292.1, - 291.5, - 291, - 293, - 296.4, - 294.1, - 291.2, - 289.2, - 287.3, - 285.4, - 283.9, - 282.7, - 281.6, - 280.5, - 279.4, - 278.1, - 276.2, - 274, - 271.8, - 270.7, - 269.8, - 268.2, - 266.1, - 263.5, - 260.7, - 258.2, - 256, - 251.5, - 245.1, - 241.6, - 239.5, - 237.2, - 234.4, - 228.9, - 224.1, - 221.9, - 218.7, - 215.9, - 213.1, - 210.2, - 206.2, - 201.9, - 199.6, - 201, - 199.1, - 194.5, - 194.5, - 195.4, - 192.4, - 188.1, - 183.8, - 180.2, - 177.6, - 175, - 171.8, - 168.6, - 164.9, - 161.8, - 157.1, - 151.6, - 146.2, - 140.5, - 133.4, - 124, - 112.3, - 106.3, - 105.2, - 103.9, - 101.6, - 99.4, - 97.2, - 95, - 93.3, - 92, - 90.8, - 89.7, - 88.9, - 87.9, - 86.8, - 85.8, - 84.9, - 83.9, - 82.9, - 81.8, - 80.6, - 79.6, - 78.9, - 78.1, - 77.2, - 76.2, - 75, - 73.5, - 72.4, - 71.4, - 70.1, - 69.1, - 68.4, - 67.7, - 66.5, - 65.5, - 64.6, - 63.8, - 63.1, - 62.5, - 61.6, - 60.5, - 59.3, - 58.1, - 57.3, - 56.8, - 56.2, - 55.3, - 54.5, - 53.6, - 52.2, - 50.6, - 49.1, - 47.6, - 46.1, - 44.8, - 43.6, - 42.5, - 41.5, - 40.6, - 40, - 39.4, - 38.8, - 38.3, - 37.9, - 37.4, - 36.9, - 36.5, - 36.1, - 35.7, - 35.4, - 35, - 34.7, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.5, - 33.3, - 33.2, - 33, - 32.8, - 32.6, - 32.3, - 32.1, - 31.9, - 31.8, - 31.8, - 31.8, - 31.9, - 32, - 32, - 32, - 32, - 31.9, - 31.7, - 31.5, - 31.2, - 31.1, - 30.9, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.3, - 30.2, - 30, - 29.8, - 29.5, - 29.3, - 29.2, - 29, - 28.8, - 28.8, - 29, - 28.9, - 28.8, - 28.6, - 28, - 27.5, - 27.1, - 26.8, - 26.5, - 26.3, - 25.9, - 25.3, - 25, - 24.6, - 24.2, - 24, - 23.7, - 23.6, - 23.3, - 22.8, - 22.7, - 22.7, - 551.2, - 551.3, - 551.3, - 550.6, - 548.9, - 548.4, - 548.2, - 548, - 548.3, - 549, - 549.7, - 549.8, - 549.5, - 549.5, - 549.5, - 549.9, - 550.7, - 550.9, - 550.9, - 550.1, - 549.6, - 549.5, - 549.5, - 549.7, - 550, - 549.7, - 549.1, - 548.9, - 548.5, - 547.6, - 547, - 546.6, - 545.9, - 545.8, - 545.8, - 544.6, - 531.3, - 498.7, - 462.5, - 430.4, - 400.2, - 377.7, - 367.5, - 362.8, - 356.1, - 349.2, - 341.2, - 330.7, - 323.2, - 318, - 312, - 304.8, - 300.9, - 301.2, - 302.9, - 302.2, - 298.7, - 296.2, - 300, - 302.3, - 298.5, - 293.6, - 290.5, - 288.7, - 286.7, - 285.2, - 283.7, - 282.5, - 281.6, - 280.5, - 279.3, - 277.9, - 275.6, - 273.3, - 272.4, - 271, - 269.7, - 267.7, - 265.2, - 262.7, - 260.1, - 257.5, - 251.4, - 246.6, - 243.5, - 241, - 238.5, - 233.9, - 228.6, - 226.6, - 225.5, - 223, - 220.2, - 217.5, - 213.9, - 208.6, - 203.8, - 204.2, - 206.4, - 204.5, - 200, - 199.1, - 199.8, - 197.5, - 193.8, - 189.6, - 185.4, - 181.8, - 178.5, - 175.4, - 172.1, - 168.3, - 165.8, - 162.7, - 157.5, - 151.7, - 146.2, - 140.3, - 132.6, - 123.8, - 112.3, - 106.1, - 104.9, - 103.3, - 101, - 99.1, - 96.9, - 94.4, - 92, - 90.2, - 88.9, - 88.1, - 87.1, - 85.9, - 85, - 84.4, - 83.6, - 82.6, - 81.5, - 80.5, - 79.6, - 78.8, - 78.1, - 77.2, - 76.2, - 75.1, - 73.6, - 72.4, - 71.4, - 70.3, - 69.4, - 68.5, - 67.5, - 66.2, - 65.1, - 64.2, - 63.5, - 62.9, - 62.2, - 61.4, - 60.1, - 59.2, - 58.4, - 57.7, - 57.1, - 56.3, - 55.3, - 54.4, - 53.5, - 52.2, - 50.5, - 48.7, - 47.2, - 45.7, - 44.4, - 43.2, - 42.5, - 41.9, - 41.2, - 40.6, - 40.1, - 39.5, - 39, - 38.6, - 38.2, - 37.7, - 37.3, - 36.8, - 36.5, - 36.1, - 35.8, - 35.5, - 35.2, - 35, - 34.9, - 34.6, - 34.4, - 34.2, - 34.1, - 33.9, - 33.8, - 33.6, - 33.3, - 33.1, - 32.9, - 32.7, - 32.5, - 32.5, - 32.6, - 32.7, - 32.8, - 32.8, - 32.8, - 32.7, - 32.6, - 32.5, - 32.2, - 31.8, - 31.6, - 31.4, - 31.3, - 31.2, - 31, - 30.8, - 30.8, - 30.9, - 30.9, - 30.6, - 30.4, - 30.2, - 30.1, - 30, - 29.8, - 29.7, - 29.7, - 29.8, - 29.7, - 29.7, - 29.3, - 28.7, - 28.3, - 27.9, - 27.7, - 27.3, - 27, - 26.7, - 26.2, - 25.8, - 25.3, - 25, - 24.8, - 24.5, - 24.4, - 24.1, - 23.5, - 23.6, - 23.6, - 552.6, - 552.6, - 552.4, - 552.5, - 552, - 551.2, - 550.5, - 550.2, - 550.4, - 551.2, - 551.3, - 551.1, - 551.1, - 551.1, - 551.1, - 551.1, - 551.6, - 552.4, - 553.2, - 552.7, - 552, - 551.9, - 551.5, - 551.9, - 552.1, - 551.6, - 551.1, - 551.3, - 551.5, - 550.6, - 549.2, - 548.5, - 548.5, - 549.1, - 548.3, - 547.4, - 545.7, - 533.8, - 503.2, - 469.6, - 436.7, - 406.3, - 376.5, - 366.3, - 362.4, - 352, - 343.8, - 333.9, - 324.4, - 317.6, - 313.2, - 310.9, - 309.8, - 310.3, - 309.3, - 307.5, - 305.3, - 304.6, - 305.5, - 304.5, - 300.2, - 295.1, - 291.5, - 289.9, - 288, - 286.3, - 284.8, - 283.6, - 282.7, - 281.5, - 280.4, - 279, - 277, - 274.8, - 273.3, - 272, - 270.9, - 269.4, - 267.2, - 264.5, - 261.9, - 259.4, - 252.8, - 248.3, - 245.3, - 242.1, - 238, - 232.4, - 229.7, - 229.3, - 228, - 226.1, - 224, - 221.2, - 216.5, - 208.1, - 205.7, - 209.5, - 211.4, - 209.9, - 206.2, - 203.8, - 204.5, - 203.9, - 200.3, - 195.8, - 191.3, - 186.4, - 182.5, - 178.9, - 175.7, - 172.2, - 169.2, - 166.6, - 163.1, - 157.5, - 151.5, - 145.9, - 139.5, - 132, - 122.9, - 112.2, - 106.2, - 104.6, - 102.6, - 100.7, - 98.9, - 96.6, - 93.9, - 90.8, - 88.9, - 87.6, - 86.3, - 85, - 84.1, - 83.7, - 83.1, - 82.2, - 81.2, - 80.4, - 79.7, - 78.9, - 78.1, - 77.3, - 76.1, - 74.8, - 73.6, - 72.6, - 71.6, - 70.5, - 69.5, - 68.4, - 67.2, - 65.9, - 64.8, - 64.2, - 63.7, - 63, - 62.3, - 61.5, - 60.3, - 59.3, - 58.6, - 57.7, - 56.7, - 55.7, - 54.6, - 53.8, - 53, - 51.9, - 50.2, - 48, - 46.7, - 45.4, - 44.3, - 43.6, - 43, - 42.5, - 41.9, - 41.3, - 40.8, - 40.3, - 39.8, - 39.4, - 39, - 38.5, - 38.1, - 37.6, - 37.2, - 36.9, - 36.6, - 36.2, - 36, - 35.8, - 35.6, - 35.3, - 35.1, - 35, - 34.8, - 34.6, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.5, - 33.3, - 33.2, - 33.3, - 33.4, - 33.5, - 33.5, - 33.5, - 33.4, - 33.3, - 33.1, - 32.9, - 32.5, - 32.3, - 32, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.5, - 31.4, - 31.3, - 31.2, - 31, - 30.9, - 30.9, - 30.7, - 30.6, - 30.6, - 30.6, - 30.6, - 30.5, - 30.3, - 29.8, - 29.2, - 28.9, - 28.5, - 28.1, - 27.8, - 27.4, - 27, - 26.6, - 26.2, - 25.8, - 25.6, - 25.3, - 25.1, - 24.6, - 24, - 24, - 24, - 553.8, - 553.6, - 553.7, - 554.3, - 554.3, - 553.7, - 553, - 552.7, - 552.6, - 552.9, - 552.6, - 552.2, - 552.3, - 552.6, - 553, - 553.2, - 553.1, - 553.7, - 554.3, - 554.4, - 553.8, - 553.8, - 553.6, - 553.5, - 553.3, - 552.8, - 552.8, - 553.2, - 553.4, - 552.7, - 551.5, - 551.3, - 551.5, - 551.4, - 550.8, - 550, - 549, - 547.2, - 533.9, - 503, - 473.7, - 440.6, - 399.3, - 381.1, - 381.6, - 367.5, - 348.3, - 335.8, - 325.7, - 317.8, - 315.6, - 315.9, - 315.4, - 314.9, - 313.6, - 311.4, - 310.8, - 310.1, - 309.1, - 307.3, - 304.5, - 298.6, - 292.9, - 290.5, - 288.9, - 287.1, - 285.7, - 284.7, - 283.8, - 282.4, - 281.3, - 280, - 278.2, - 276.3, - 274.2, - 272.8, - 272, - 271, - 269.3, - 266.7, - 263.9, - 260.5, - 254.3, - 249.8, - 246.6, - 242.2, - 236, - 232.1, - 231.7, - 231.9, - 230.7, - 229, - 227.3, - 225.1, - 217.7, - 208.6, - 210.8, - 215.8, - 216.2, - 214.1, - 210.7, - 208.4, - 209.8, - 209.8, - 207, - 202.7, - 197.9, - 191.1, - 187, - 183, - 179.2, - 176.1, - 173.3, - 170.7, - 167.8, - 163.5, - 158, - 151.9, - 145.7, - 139.1, - 132.1, - 123.3, - 112.1, - 105.9, - 104.2, - 102.2, - 100.4, - 98.8, - 96.6, - 93.2, - 89.9, - 87.7, - 86.3, - 84.9, - 83.9, - 83.1, - 82.4, - 81.7, - 80.9, - 80.3, - 79.6, - 78.9, - 78.2, - 77.3, - 76.2, - 74.9, - 73.7, - 72.7, - 71.7, - 70.6, - 69.3, - 67.9, - 66.7, - 65.9, - 65.2, - 64.6, - 64.1, - 63.5, - 62.6, - 61.7, - 60.5, - 59.4, - 58.3, - 57.3, - 56.1, - 54.9, - 53.7, - 52.7, - 51.9, - 51, - 49.5, - 47.5, - 46.4, - 45.4, - 44.7, - 44.2, - 43.6, - 43.1, - 42.6, - 42, - 41.5, - 41, - 40.5, - 40.1, - 39.7, - 39.3, - 38.9, - 38.4, - 38, - 37.7, - 37.3, - 37.1, - 36.8, - 36.6, - 36.4, - 36.2, - 35.9, - 35.7, - 35.5, - 35.4, - 35.2, - 35.1, - 34.9, - 34.7, - 34.4, - 34.3, - 34.1, - 34, - 34.1, - 34.2, - 34.2, - 34.2, - 34.1, - 34, - 33.8, - 33.6, - 33.4, - 33.1, - 32.9, - 32.6, - 32.4, - 32.3, - 32.3, - 32.3, - 32.2, - 32.2, - 32.2, - 32.1, - 32, - 31.8, - 31.7, - 31.7, - 31.6, - 31.6, - 31.5, - 31.5, - 31.5, - 31.5, - 31.3, - 30.8, - 30.3, - 29.8, - 29.4, - 29, - 28.6, - 28.1, - 27.6, - 27.2, - 26.8, - 26.5, - 26.3, - 26, - 25.7, - 25.2, - 24.5, - 24.2, - 24, - 555.1, - 555.1, - 555.8, - 555.9, - 555.5, - 555.1, - 555, - 555.2, - 554.8, - 554.8, - 554.2, - 554.1, - 553.9, - 554.1, - 554.6, - 555, - 555.2, - 555.3, - 555.3, - 555.2, - 555, - 555.1, - 555.4, - 555.1, - 554.8, - 554.5, - 554.3, - 554.1, - 554.2, - 554.1, - 553.8, - 553.7, - 553.5, - 552.9, - 552.2, - 551.7, - 551.4, - 550.6, - 548.5, - 536.2, - 514, - 476.4, - 441.6, - 418.7, - 407.6, - 392.2, - 361.3, - 338.4, - 326.9, - 321.7, - 321.8, - 322.1, - 320.5, - 318.8, - 317, - 315.6, - 316, - 314.9, - 313.2, - 311.5, - 309.7, - 303.6, - 294.3, - 290.8, - 289.4, - 288.1, - 286.6, - 285.5, - 284.5, - 283.2, - 282, - 280.8, - 279.4, - 277.5, - 275.6, - 273.9, - 273, - 272.2, - 271.1, - 268.9, - 265.7, - 260.6, - 255.7, - 251.5, - 247.4, - 240.9, - 235.4, - 233.6, - 234.6, - 234.1, - 233, - 231.8, - 230.4, - 228.1, - 220.3, - 214.1, - 217.1, - 220.5, - 220.6, - 218, - 215.4, - 213.5, - 214.4, - 214.1, - 211.7, - 207.8, - 203.4, - 196.8, - 191.9, - 188.7, - 184.3, - 180.2, - 177.1, - 174.6, - 172.1, - 168.9, - 164.5, - 160.2, - 153.9, - 146.6, - 140, - 132.5, - 122.9, - 111.2, - 105.8, - 103.9, - 101.8, - 100.4, - 98.4, - 96.1, - 93, - 89.8, - 87.1, - 85.4, - 84.2, - 83.1, - 82, - 81.3, - 80.8, - 80.3, - 79.9, - 79.1, - 78.3, - 77.5, - 76.6, - 75.3, - 74.2, - 73.1, - 71.9, - 70.5, - 69.1, - 67.6, - 66.7, - 66.3, - 65.8, - 65.1, - 64.4, - 63.7, - 62.7, - 61.6, - 60.3, - 59, - 57.9, - 56.8, - 55.6, - 54.3, - 53.1, - 52.1, - 51.2, - 50.2, - 48.8, - 47.6, - 46.6, - 46, - 45.5, - 44.9, - 44.4, - 43.8, - 43.3, - 42.7, - 42.2, - 41.7, - 41.3, - 40.9, - 40.5, - 40.1, - 39.7, - 39.3, - 38.9, - 38.5, - 38.1, - 37.9, - 37.7, - 37.5, - 37.2, - 37, - 36.7, - 36.5, - 36.3, - 36.2, - 36, - 35.8, - 35.7, - 35.5, - 35.3, - 35.1, - 34.9, - 34.9, - 34.9, - 34.9, - 34.9, - 34.9, - 34.9, - 34.7, - 34.4, - 34.1, - 33.9, - 33.7, - 33.5, - 33.2, - 33.1, - 33.1, - 33.1, - 33.1, - 33.1, - 33.1, - 33.1, - 33, - 32.9, - 32.8, - 32.6, - 32.5, - 32.7, - 32.6, - 32.4, - 32.4, - 32.3, - 32.2, - 32, - 31.6, - 31.2, - 30.7, - 30.3, - 29.9, - 29.4, - 28.9, - 28.3, - 27.8, - 27.5, - 27.2, - 26.9, - 26.6, - 26.3, - 26, - 25.3, - 24.6, - 23.9, - 556.5, - 556.7, - 557, - 557, - 556.5, - 556.4, - 556.5, - 556.7, - 556.5, - 556.3, - 556.2, - 556.6, - 556.6, - 556.3, - 556.7, - 556.9, - 557.3, - 557.7, - 557.6, - 557.1, - 556.9, - 556.3, - 556.4, - 556.8, - 557, - 557.6, - 557.9, - 556.6, - 556.2, - 556.3, - 555.6, - 555.1, - 555.1, - 554.6, - 553.8, - 553.4, - 553.3, - 553.3, - 552.4, - 550.1, - 544.3, - 516.2, - 488.5, - 462.7, - 436.6, - 413.2, - 383.4, - 348.5, - 332.1, - 333.4, - 332.5, - 331, - 326.9, - 322.8, - 321, - 319.4, - 320.2, - 319.1, - 316.9, - 315.9, - 313.6, - 306.4, - 297.9, - 292.4, - 290, - 288.8, - 287.5, - 286.3, - 285.2, - 284, - 282.6, - 281.2, - 279.9, - 278.5, - 276.8, - 275.1, - 274, - 273.3, - 272.2, - 270.4, - 266.9, - 261.6, - 257.4, - 253, - 246.6, - 239.9, - 237.1, - 237, - 237.2, - 236.4, - 235.3, - 234.4, - 233.1, - 231.9, - 227.6, - 222.3, - 223.3, - 224.8, - 224.2, - 222.3, - 220.6, - 218.5, - 219.1, - 219.3, - 216.7, - 212.7, - 208.5, - 203.7, - 196.9, - 194.2, - 191.7, - 188, - 182.5, - 178.4, - 175.8, - 173.1, - 169.6, - 165.4, - 161.3, - 155.6, - 148, - 140.6, - 132.6, - 122.4, - 112.7, - 106.2, - 103.5, - 101.6, - 99.8, - 97.8, - 95.7, - 93, - 90.2, - 87.1, - 84.9, - 83.5, - 82.3, - 81.3, - 80.7, - 80.2, - 79.9, - 79.4, - 78.7, - 77.8, - 76.9, - 75.8, - 74.6, - 73.2, - 71.9, - 70.4, - 69, - 67.9, - 67, - 66.4, - 65.7, - 64.8, - 64.1, - 63.3, - 62.4, - 61.1, - 59.7, - 58.3, - 57.1, - 56, - 54.9, - 53.7, - 52.6, - 51.7, - 50.8, - 49.7, - 48.7, - 48, - 47.4, - 46.8, - 46.2, - 45.7, - 45.1, - 44.6, - 44, - 43.5, - 43, - 42.5, - 42.2, - 41.8, - 41.4, - 41, - 40.6, - 40.1, - 39.8, - 39.4, - 39, - 38.8, - 38.5, - 38.3, - 38.1, - 37.8, - 37.5, - 37.3, - 37.1, - 37, - 36.8, - 36.6, - 36.4, - 36.3, - 36, - 35.8, - 35.7, - 35.6, - 35.6, - 35.6, - 35.6, - 35.5, - 35.5, - 35.3, - 35, - 34.7, - 34.5, - 34.3, - 34.2, - 34, - 33.9, - 33.9, - 33.9, - 33.9, - 34, - 34, - 33.9, - 33.9, - 33.8, - 33.7, - 33.5, - 33.4, - 33.6, - 33.5, - 33.4, - 33.3, - 33.2, - 33, - 32.8, - 32.5, - 32, - 31.6, - 31.3, - 30.8, - 30.4, - 29.8, - 29.2, - 28.7, - 28.4, - 28.1, - 27.7, - 27.3, - 27.1, - 27, - 26.5, - 25.4, - 24.4, - 557.8, - 557.9, - 557.9, - 557.8, - 557.6, - 557.5, - 557.7, - 557.9, - 557.9, - 557.9, - 558.2, - 558.5, - 558.6, - 558.7, - 559, - 559, - 559.1, - 559.6, - 559.7, - 559.5, - 559.2, - 558.7, - 558.5, - 558.7, - 559.3, - 560.2, - 560.8, - 560.2, - 559.4, - 558.9, - 558.1, - 557.6, - 557.4, - 556.6, - 556.1, - 555.8, - 555.7, - 555.8, - 555.8, - 554.5, - 554.5, - 542.5, - 514.8, - 485.6, - 455.8, - 426, - 399.4, - 373.6, - 360.3, - 358.9, - 351.5, - 346.5, - 338.3, - 330.9, - 328.5, - 324.9, - 324.3, - 323.4, - 322.4, - 321.1, - 318.6, - 313.8, - 305.6, - 298.3, - 291.7, - 289.4, - 288.2, - 287.1, - 285.9, - 284.6, - 283.3, - 281.7, - 280.4, - 279.1, - 277.7, - 276.2, - 275, - 274, - 272.8, - 270.9, - 267.4, - 263.4, - 258.9, - 253.3, - 246, - 240.5, - 239.1, - 240.1, - 240.1, - 238.9, - 237.8, - 236.8, - 235.5, - 234.2, - 232.4, - 230, - 230, - 230.2, - 228.3, - 226.7, - 226.1, - 224.2, - 224.6, - 223.9, - 221.7, - 218.5, - 213.8, - 209.4, - 203.7, - 198.7, - 196.5, - 193.9, - 189.6, - 183.9, - 179.7, - 176.9, - 174.1, - 169.8, - 165.6, - 161.9, - 156.3, - 148.4, - 141.4, - 132.8, - 123.5, - 114.5, - 105.6, - 103, - 101.7, - 100, - 98, - 94.8, - 92.5, - 89.8, - 87, - 84.6, - 82.8, - 81.6, - 80.6, - 79.9, - 79.4, - 79, - 78.6, - 77.9, - 77, - 76, - 74.7, - 73.1, - 71.7, - 70.3, - 69, - 68, - 66.8, - 65.8, - 64.9, - 64, - 63.3, - 62.6, - 61.7, - 60.4, - 59.1, - 57.7, - 56.5, - 55.3, - 54.4, - 53.4, - 52.4, - 51.6, - 50.8, - 49.9, - 49.2, - 48.6, - 48.1, - 47.5, - 47, - 46.4, - 45.9, - 45.4, - 44.8, - 44.3, - 43.8, - 43.4, - 43, - 42.7, - 42.3, - 41.9, - 41.4, - 41, - 40.7, - 40.3, - 39.9, - 39.7, - 39.4, - 39.2, - 39, - 38.7, - 38.4, - 38.2, - 38, - 37.8, - 37.6, - 37.4, - 37.3, - 37.1, - 36.9, - 36.7, - 36.5, - 36.4, - 36.3, - 36.3, - 36.1, - 36, - 35.9, - 35.7, - 35.6, - 35.4, - 35.2, - 35.1, - 35, - 34.8, - 34.7, - 34.7, - 34.8, - 34.8, - 34.8, - 34.8, - 34.8, - 34.8, - 34.8, - 34.6, - 34.4, - 34.4, - 34.4, - 34.4, - 34.3, - 34.1, - 34, - 34, - 33.9, - 33.5, - 32.9, - 32.6, - 32.3, - 31.8, - 31.4, - 30.9, - 30.4, - 29.9, - 29.5, - 29.1, - 28.7, - 28.4, - 28.2, - 27.9, - 27.3, - 26.2, - 25.4, - 559.2, - 559.2, - 559.2, - 559, - 558.8, - 558.7, - 558.9, - 559.2, - 559.2, - 559.3, - 559.8, - 559.9, - 560, - 560.2, - 560.4, - 560.7, - 560.9, - 561.3, - 561.4, - 561.4, - 561.1, - 560.8, - 560.5, - 560.5, - 561.3, - 561.8, - 562.3, - 562.2, - 561.9, - 561.3, - 560.4, - 560.5, - 560.2, - 559.1, - 558.5, - 558.1, - 557.9, - 557.9, - 557.7, - 557.2, - 557.3, - 553.5, - 532.7, - 504.8, - 473.9, - 443.1, - 418.8, - 403.5, - 390, - 379, - 367.4, - 359.5, - 352.6, - 346.4, - 339, - 334.4, - 331, - 328.8, - 329, - 326.7, - 322.5, - 318.4, - 311.3, - 305.1, - 296.6, - 290.2, - 288.8, - 287.7, - 286.5, - 285.2, - 283.9, - 282.5, - 280.9, - 279.7, - 278.5, - 276.9, - 275.8, - 274.5, - 273.2, - 271.4, - 268.7, - 264.9, - 260.3, - 253, - 247.2, - 242.8, - 242.6, - 243.7, - 242.8, - 241.2, - 240.1, - 239.3, - 238.2, - 236.8, - 234.9, - 233.4, - 234.9, - 235.6, - 233.2, - 231.9, - 231.3, - 230.7, - 230.1, - 228.1, - 225.9, - 223.9, - 220.9, - 216.7, - 212.5, - 204.8, - 200.5, - 198.2, - 194.8, - 190.5, - 184.7, - 180.1, - 177.3, - 174.5, - 170.4, - 165.7, - 161.8, - 155.1, - 148.7, - 141.8, - 133.7, - 126, - 115.1, - 106, - 103.4, - 102, - 100.4, - 97.6, - 94, - 91.6, - 89.2, - 86.5, - 83.9, - 82.1, - 80.7, - 79.6, - 78.9, - 78.2, - 77.8, - 77.2, - 76.4, - 75.4, - 74, - 72.7, - 71.4, - 70.1, - 68.6, - 67.4, - 66.2, - 65.1, - 64.1, - 63.3, - 62.6, - 61.7, - 60.9, - 59.8, - 58.6, - 57.4, - 56.3, - 55.2, - 54.3, - 53.6, - 52.8, - 52.1, - 51.3, - 50.6, - 50, - 49.4, - 48.9, - 48.3, - 47.8, - 47.2, - 46.7, - 46.2, - 45.7, - 45.2, - 44.7, - 44.3, - 43.9, - 43.6, - 43.2, - 42.8, - 42.3, - 42, - 41.5, - 41.2, - 40.9, - 40.6, - 40.3, - 40.1, - 39.9, - 39.6, - 39.4, - 39.1, - 38.9, - 38.7, - 38.5, - 38.3, - 38.1, - 37.9, - 37.7, - 37.5, - 37.3, - 37.2, - 37, - 36.9, - 36.8, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36, - 35.9, - 35.8, - 35.6, - 35.6, - 35.6, - 35.6, - 35.6, - 35.6, - 35.7, - 35.7, - 35.7, - 35.6, - 35.4, - 35.3, - 35.5, - 35.4, - 35.2, - 35.2, - 34.9, - 34.7, - 34.8, - 34.8, - 34.2, - 33.8, - 33.5, - 33.2, - 32.9, - 32.5, - 32, - 31.5, - 31, - 30.5, - 30.2, - 29.9, - 29.6, - 29.3, - 28.7, - 27.9, - 27.1, - 26.6, - 560.4, - 560.5, - 560.4, - 560.2, - 560.2, - 560.2, - 560.3, - 560.5, - 560.5, - 560.6, - 561.1, - 561.3, - 561.4, - 561.6, - 561.7, - 561.9, - 562.2, - 562.7, - 562.8, - 563, - 562.8, - 562.7, - 562.5, - 562.7, - 563.4, - 563.8, - 563.9, - 563.8, - 563.6, - 563.2, - 562.6, - 562.5, - 562.2, - 561.3, - 560.6, - 560.2, - 560.2, - 560.9, - 561.3, - 560.2, - 559.2, - 559, - 550.4, - 524.6, - 494.5, - 464.9, - 438.8, - 424.7, - 410.5, - 397.7, - 385, - 372.6, - 364.4, - 359.6, - 352.7, - 345.2, - 340.4, - 337.9, - 335.5, - 330.7, - 325.4, - 320.6, - 313.9, - 308.4, - 300.8, - 291.2, - 289.4, - 288.5, - 287.2, - 285.8, - 284.8, - 283.3, - 281.5, - 280.3, - 279.1, - 277.6, - 276.4, - 275.2, - 273.8, - 272.1, - 269.7, - 266, - 261.5, - 254.2, - 248.8, - 246.3, - 246.9, - 246.7, - 245.4, - 243.8, - 242.6, - 241.9, - 240.9, - 239.3, - 237.5, - 236.3, - 237.7, - 238.7, - 238.8, - 237.8, - 236.4, - 236.1, - 236.8, - 232.3, - 229.5, - 227.5, - 225.9, - 224, - 221.1, - 214.7, - 206.6, - 201.8, - 198.6, - 194.9, - 190.3, - 184.9, - 180.5, - 177.4, - 175.3, - 171.5, - 166.6, - 161.5, - 155.7, - 149.1, - 141.9, - 134.8, - 127.6, - 117.9, - 108.4, - 103.6, - 101.7, - 99.7, - 96.4, - 93.2, - 90.8, - 88.4, - 85.9, - 83.4, - 81.3, - 79.9, - 78.8, - 77.8, - 77, - 76.5, - 75.5, - 74.4, - 73.1, - 71.9, - 70.8, - 69.5, - 68.1, - 67, - 66, - 64.8, - 63.8, - 63, - 62.1, - 61.2, - 60.3, - 59.4, - 58.3, - 57.4, - 56.5, - 55.6, - 54.9, - 54.2, - 53.5, - 52.8, - 52.1, - 51.5, - 50.8, - 50.3, - 49.7, - 49.1, - 48.6, - 48.1, - 47.6, - 47.1, - 46.6, - 46.1, - 45.6, - 45.2, - 44.9, - 44.5, - 44.1, - 43.8, - 43.3, - 42.9, - 42.5, - 42.1, - 41.9, - 41.6, - 41.3, - 41, - 40.8, - 40.5, - 40.3, - 40.1, - 39.8, - 39.6, - 39.4, - 39.1, - 39, - 38.8, - 38.5, - 38.3, - 38.2, - 38, - 37.9, - 37.7, - 37.5, - 37.4, - 37.2, - 37.2, - 37.1, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.4, - 36.4, - 36.4, - 36.5, - 36.5, - 36.5, - 36.6, - 36.5, - 36.3, - 36.1, - 36.1, - 36.4, - 36.4, - 36.2, - 36, - 35.6, - 35.5, - 35.7, - 35.6, - 35, - 34.7, - 34.4, - 34.1, - 33.9, - 33.5, - 33, - 32.5, - 32.1, - 31.7, - 31.3, - 31, - 30.8, - 30.4, - 29.5, - 28.6, - 27.9, - 27.9, - 561.8, - 562, - 561.8, - 561.7, - 561.7, - 561.8, - 562, - 562, - 562.1, - 562.3, - 562.7, - 562.7, - 562.9, - 563.1, - 563.1, - 563.2, - 563.5, - 563.9, - 564, - 564.1, - 564.2, - 564.8, - 564.8, - 564.7, - 564.8, - 565.3, - 565.5, - 565.5, - 565.3, - 565.1, - 564.9, - 564.2, - 563.6, - 563.2, - 562.7, - 562.5, - 562.9, - 563.6, - 563.7, - 563.2, - 562.3, - 562, - 560.2, - 544.5, - 515.9, - 487.5, - 461.1, - 442.8, - 431.4, - 417.2, - 402.9, - 390.8, - 376.1, - 368.9, - 362.9, - 356.2, - 350.1, - 346.5, - 342.8, - 332.9, - 325.7, - 321.4, - 317.2, - 312.4, - 306.6, - 294.3, - 290.1, - 289.1, - 287.9, - 286.7, - 285.7, - 283.9, - 282.1, - 280.8, - 279.5, - 278.3, - 277.3, - 276.2, - 274.9, - 273.1, - 269.8, - 266.4, - 262.2, - 256.7, - 252, - 250.6, - 250.3, - 249.1, - 247.9, - 246.7, - 245.3, - 244, - 243, - 241.7, - 240.2, - 239.5, - 240.5, - 241.9, - 243.6, - 242.5, - 240.9, - 240.7, - 243, - 238.9, - 233.6, - 231.2, - 229.4, - 228.2, - 227.2, - 224.7, - 219.4, - 209, - 202.1, - 198.5, - 194.8, - 190.5, - 186, - 181.9, - 181.1, - 178.5, - 172.5, - 167.2, - 162.2, - 156.5, - 149.5, - 142.4, - 135.5, - 128.2, - 120, - 110.5, - 103.5, - 101, - 98.5, - 95.8, - 93.1, - 90.5, - 88, - 85.4, - 83, - 80.9, - 79.4, - 78.1, - 77, - 76, - 75, - 73.8, - 72.4, - 71.2, - 70, - 68.9, - 67.9, - 67, - 66.1, - 64.9, - 64.1, - 63.1, - 62.2, - 61.3, - 60.4, - 59.5, - 58.6, - 57.8, - 57, - 56.3, - 55.6, - 54.9, - 54.2, - 53.6, - 52.9, - 52.2, - 51.6, - 51, - 50.5, - 50, - 49.5, - 49, - 48.5, - 48, - 47.5, - 47, - 46.6, - 46.2, - 45.8, - 45.5, - 45.1, - 44.8, - 44.4, - 44, - 43.5, - 43.2, - 42.9, - 42.6, - 42.2, - 42, - 41.7, - 41.4, - 41.2, - 41, - 40.8, - 40.6, - 40.3, - 40.1, - 39.8, - 39.6, - 39.4, - 39.2, - 39, - 38.9, - 38.7, - 38.5, - 38.3, - 38.1, - 38.1, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37.2, - 37.2, - 37.3, - 37.3, - 37.4, - 37.4, - 37.3, - 37.1, - 37, - 37.1, - 37.4, - 37.4, - 37.1, - 36.7, - 36.4, - 36.3, - 36.5, - 36.6, - 36, - 35.6, - 35.5, - 35.2, - 34.9, - 34.6, - 34.1, - 33.5, - 33.2, - 32.9, - 32.5, - 32.1, - 31.8, - 31.3, - 30.2, - 29.3, - 28.8, - 30.1, - 563.2, - 563.4, - 563.2, - 562.9, - 563, - 563.2, - 563.5, - 563.6, - 563.6, - 564, - 564.3, - 564.3, - 564.4, - 564.5, - 564.6, - 564.7, - 565.4, - 565.3, - 565.2, - 565.7, - 566.2, - 566.5, - 566.6, - 566.6, - 566.3, - 566.5, - 566.7, - 566.8, - 566.7, - 566.7, - 566.4, - 565.9, - 565.3, - 564.8, - 564.5, - 564.9, - 565.2, - 565.2, - 565, - 564.9, - 564.7, - 564.2, - 563.3, - 558.9, - 537.2, - 509, - 483, - 463.8, - 449.4, - 436.1, - 422.3, - 407.8, - 395.3, - 387.3, - 378.8, - 367.7, - 360.2, - 356.9, - 354.4, - 344.5, - 336, - 329.9, - 324.8, - 319.5, - 313.1, - 298.7, - 290.8, - 289.5, - 288.3, - 287.2, - 286.3, - 284.4, - 283, - 281.7, - 280.4, - 279, - 278.2, - 277.4, - 276, - 273.7, - 269.9, - 266.6, - 263, - 259.1, - 255.8, - 254.6, - 253.3, - 251.4, - 250, - 249, - 247.7, - 246.2, - 244.9, - 243.6, - 242.2, - 242.3, - 243.9, - 247.5, - 249.2, - 247.5, - 244.5, - 244.7, - 247.1, - 247.4, - 244, - 243.2, - 240.1, - 236.2, - 236.9, - 235.9, - 230.3, - 221.2, - 207.2, - 202.4, - 198.8, - 195.1, - 191.6, - 189.7, - 190.7, - 187.2, - 180.6, - 173.1, - 167.9, - 163, - 157.4, - 150.8, - 143.3, - 136, - 128.9, - 121.2, - 112, - 104, - 100.1, - 97.8, - 95.4, - 92.7, - 90.5, - 88.1, - 85.1, - 82.5, - 80.4, - 78.8, - 77.5, - 76.2, - 75, - 73.7, - 72.4, - 71.4, - 70.3, - 69.2, - 68.3, - 67.4, - 66.4, - 65.5, - 64.7, - 63.7, - 62.8, - 61.9, - 61, - 60.1, - 59.3, - 58.5, - 57.7, - 57, - 56.3, - 55.6, - 55, - 54.3, - 53.7, - 53, - 52.4, - 51.9, - 51.4, - 50.9, - 50.3, - 49.8, - 49.4, - 48.9, - 48.4, - 48, - 47.5, - 47.2, - 46.8, - 46.5, - 46.1, - 45.8, - 45.4, - 45, - 44.5, - 44.2, - 43.9, - 43.5, - 43.2, - 42.9, - 42.6, - 42.4, - 42.2, - 42, - 41.8, - 41.6, - 41.3, - 41, - 40.7, - 40.4, - 40.2, - 40, - 39.9, - 39.8, - 39.6, - 39.4, - 39.2, - 39, - 38.9, - 38.7, - 38.6, - 38.5, - 38.4, - 38.3, - 38.3, - 38.1, - 38.1, - 38.1, - 38.1, - 38.1, - 38.2, - 38.2, - 38.2, - 38.1, - 37.9, - 37.9, - 38.1, - 38.5, - 38.4, - 38, - 37.6, - 37.2, - 37, - 37.5, - 37.6, - 37, - 36.7, - 36.6, - 36.4, - 36.1, - 35.8, - 35.2, - 34.6, - 34.2, - 34.1, - 33.7, - 33.3, - 32.7, - 31.9, - 30.9, - 30.3, - 30.1, - 32.6, - 564.6, - 564.5, - 564.3, - 564, - 564.1, - 564.4, - 564.8, - 565, - 565.1, - 565.6, - 565.7, - 565.7, - 565.7, - 565.9, - 566, - 566.6, - 567.4, - 567.3, - 567.3, - 568.1, - 568.3, - 568.5, - 568.5, - 568.2, - 567.7, - 567.5, - 567.9, - 568, - 568.1, - 568.1, - 567.9, - 567.5, - 567.1, - 566.8, - 566.8, - 567.1, - 567.1, - 567, - 566.8, - 566.6, - 566.3, - 566.1, - 565.2, - 564.2, - 552.5, - 528.5, - 508, - 490.5, - 472.7, - 456.1, - 442.4, - 425.7, - 410.5, - 401.1, - 393.7, - 384.1, - 373.1, - 369.3, - 367.4, - 359.4, - 351.3, - 343.1, - 333.9, - 327.2, - 320, - 308.5, - 293, - 290, - 288.7, - 287.7, - 286.6, - 285.3, - 284.4, - 282.9, - 281.3, - 279.9, - 279.1, - 278.2, - 276.7, - 273.5, - 270, - 266.9, - 263.6, - 261.2, - 259.3, - 257.5, - 256, - 253.7, - 251.8, - 251, - 250, - 248.4, - 246.6, - 245.2, - 245, - 246.6, - 250.7, - 253.2, - 252.7, - 250.9, - 247.9, - 248.3, - 250.8, - 252.4, - 253.5, - 255.4, - 253.8, - 249.9, - 248, - 245.9, - 242.5, - 233.8, - 214, - 206, - 202.9, - 199.4, - 195.9, - 196.5, - 199.9, - 195.2, - 187.8, - 180.5, - 173.2, - 168.6, - 164.4, - 158.8, - 151.4, - 143.5, - 136.7, - 128.9, - 122.1, - 113.7, - 105.4, - 101.3, - 98.3, - 95.3, - 92.9, - 91, - 88.4, - 85.3, - 82.2, - 79.8, - 78.2, - 76.9, - 75.6, - 74.3, - 73, - 72, - 71, - 69.9, - 68.9, - 68, - 67.1, - 66.1, - 65.3, - 64.4, - 63.5, - 62.6, - 61.7, - 60.9, - 60.1, - 59.3, - 58.5, - 57.7, - 57.1, - 56.4, - 55.8, - 55.1, - 54.5, - 53.8, - 53.3, - 52.8, - 52.3, - 51.7, - 51.2, - 50.7, - 50.3, - 49.8, - 49.4, - 49, - 48.6, - 48.2, - 47.8, - 47.5, - 47.1, - 46.8, - 46.4, - 45.9, - 45.5, - 45.1, - 44.8, - 44.5, - 44.2, - 43.9, - 43.5, - 43.3, - 43.1, - 42.9, - 42.8, - 42.6, - 42.3, - 41.9, - 41.6, - 41.3, - 41.1, - 40.9, - 40.8, - 40.7, - 40.5, - 40.3, - 40, - 39.8, - 39.6, - 39.5, - 39.4, - 39.3, - 39.3, - 39.2, - 39.2, - 39, - 38.9, - 38.9, - 38.9, - 39, - 39.1, - 39.1, - 39.1, - 39, - 38.9, - 38.9, - 39, - 39.3, - 39.1, - 38.9, - 38.4, - 38, - 38.1, - 38.6, - 38.5, - 38, - 37.7, - 37.5, - 37.3, - 37.1, - 36.8, - 36.2, - 35.7, - 35.4, - 35.2, - 34.7, - 34.1, - 33.2, - 32.3, - 31.5, - 31.2, - 32, - 35.2, - 566.1, - 565.9, - 566, - 566.3, - 566.1, - 565.8, - 565.9, - 566, - 566.6, - 567, - 567, - 567.1, - 567.1, - 567.3, - 567.8, - 568.4, - 568.7, - 569, - 569.2, - 569.6, - 569.7, - 570, - 570.1, - 569.8, - 569.1, - 568.8, - 569.2, - 569.2, - 569.5, - 569.5, - 569.2, - 568.8, - 568.6, - 568.6, - 568.4, - 568.5, - 568.6, - 568.6, - 568.6, - 568.3, - 568, - 567.8, - 567.2, - 566.9, - 564.7, - 552, - 534.5, - 515.4, - 496.7, - 480.5, - 464.2, - 445, - 426.2, - 412.8, - 405.9, - 399.8, - 389.9, - 384.1, - 381, - 372.2, - 363.9, - 356.6, - 348.3, - 339.8, - 331.5, - 320.6, - 304.7, - 292.6, - 289.2, - 288.1, - 287, - 285.9, - 285.1, - 283.6, - 281.6, - 280.3, - 279.4, - 278.6, - 277.1, - 274.5, - 271.1, - 267.8, - 265.1, - 263.2, - 261.6, - 260, - 258.3, - 255.8, - 254.1, - 253.2, - 252.3, - 250.4, - 248.4, - 248.4, - 250, - 252.9, - 255.7, - 256.1, - 255.1, - 253.5, - 251.4, - 251.8, - 254.6, - 255.8, - 257.4, - 259.6, - 258.4, - 255.9, - 253, - 250.3, - 248, - 243.8, - 228.4, - 214, - 207.7, - 204.1, - 201.1, - 203.9, - 206.6, - 203.2, - 196.5, - 189.6, - 181.3, - 173.8, - 169.2, - 164.9, - 158.9, - 149.9, - 144.1, - 136.9, - 128.9, - 122.7, - 116.2, - 108.2, - 102.8, - 99, - 95.7, - 93.4, - 90.9, - 88.5, - 85.6, - 82, - 78.9, - 77.8, - 76.5, - 75.2, - 73.9, - 72.7, - 71.6, - 70.6, - 69.6, - 68.7, - 67.9, - 66.9, - 66, - 65.1, - 64.3, - 63.3, - 62.4, - 61.7, - 60.9, - 60.1, - 59.3, - 58.6, - 57.9, - 57.2, - 56.6, - 55.9, - 55.3, - 54.7, - 54.2, - 53.7, - 53.2, - 52.6, - 52.1, - 51.7, - 51.3, - 50.8, - 50.4, - 50, - 49.6, - 49.2, - 48.8, - 48.4, - 48.1, - 47.8, - 47.4, - 46.9, - 46.5, - 46.2, - 45.8, - 45.5, - 45.2, - 44.9, - 44.6, - 44.3, - 44.1, - 43.9, - 43.7, - 43.5, - 43.3, - 42.9, - 42.5, - 42.2, - 42, - 41.8, - 41.7, - 41.5, - 41.3, - 41.1, - 40.9, - 40.6, - 40.4, - 40.3, - 40.2, - 40.1, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.8, - 39.8, - 39.9, - 40, - 40.1, - 40, - 39.9, - 39.8, - 39.9, - 40.1, - 40.1, - 40, - 39.8, - 39.2, - 38.9, - 39.2, - 39.7, - 39.6, - 39.1, - 38.8, - 38.4, - 38.1, - 37.9, - 37.8, - 37.3, - 36.9, - 36.5, - 36, - 35.4, - 34.6, - 33.7, - 32.9, - 32.5, - 32.6, - 34.1, - 37.2, - 567.4, - 567.4, - 567.6, - 568, - 567.8, - 567.7, - 567.6, - 567.7, - 568, - 568.1, - 568.4, - 568.4, - 568.5, - 569, - 569.5, - 570, - 570.2, - 570.4, - 570.5, - 570.7, - 571, - 571.4, - 571.6, - 571.3, - 570.9, - 570.6, - 570.5, - 570.6, - 570.9, - 570.9, - 570.6, - 570.3, - 570.1, - 570.1, - 569.8, - 569.9, - 570, - 570, - 569.9, - 569.9, - 569.9, - 569.5, - 568.8, - 568.9, - 569, - 566.9, - 556.1, - 536.8, - 519.1, - 504.6, - 490.9, - 471.7, - 452.2, - 432.4, - 415.6, - 408.1, - 402.6, - 396.4, - 391, - 384.5, - 376.5, - 368.2, - 361.1, - 355.4, - 348.6, - 333.3, - 321.4, - 306.6, - 292.4, - 288.6, - 287.5, - 286.2, - 285, - 283.5, - 281.9, - 280.5, - 279.4, - 278.7, - 277.3, - 275.4, - 272.2, - 269.2, - 267.4, - 265.8, - 263.8, - 262.1, - 260.5, - 258.3, - 256.8, - 255.6, - 254.9, - 253.4, - 251.9, - 252.6, - 254.3, - 256.2, - 257.8, - 258.4, - 257.4, - 255.6, - 254.2, - 255.8, - 258.2, - 259.2, - 260.6, - 262.8, - 262.3, - 260.7, - 257.6, - 254.3, - 251.9, - 249.8, - 244.6, - 233, - 221.1, - 212.3, - 210.9, - 214, - 211.7, - 208.1, - 204.2, - 199.9, - 193, - 182.8, - 174.5, - 169.8, - 165.3, - 158.8, - 150.6, - 143.8, - 136.6, - 129.2, - 122.5, - 116.4, - 109.5, - 104.1, - 99.9, - 96.6, - 93.9, - 91.1, - 88.7, - 85.7, - 81.3, - 78.5, - 77.1, - 76, - 74.8, - 73.6, - 72.4, - 71.3, - 70.4, - 69.5, - 68.7, - 67.7, - 66.8, - 65.9, - 65.1, - 64.2, - 63.4, - 62.6, - 61.8, - 60.9, - 60.2, - 59.5, - 58.8, - 58.1, - 57.5, - 56.8, - 56.2, - 55.6, - 55.1, - 54.6, - 54.1, - 53.5, - 53.1, - 52.7, - 52.2, - 51.8, - 51.4, - 51, - 50.5, - 50.1, - 49.8, - 49.4, - 49.1, - 48.8, - 48.4, - 48, - 47.6, - 47.2, - 46.9, - 46.6, - 46.3, - 45.9, - 45.6, - 45.4, - 45.1, - 44.8, - 44.6, - 44.4, - 44.2, - 43.9, - 43.5, - 43.2, - 43, - 42.8, - 42.6, - 42.4, - 42.2, - 41.9, - 41.7, - 41.4, - 41.3, - 41.1, - 41, - 41, - 41, - 40.9, - 40.8, - 40.8, - 40.7, - 40.6, - 40.6, - 40.7, - 40.9, - 41, - 41, - 40.9, - 40.8, - 40.8, - 41, - 41, - 40.9, - 40.6, - 40, - 40, - 40.2, - 40.8, - 40.7, - 40.2, - 40, - 39.6, - 39.3, - 38.9, - 38.7, - 38.5, - 38.1, - 37.5, - 36.8, - 36, - 35.1, - 34.3, - 34, - 34, - 34.3, - 36.5, - 39.4, - 569.2, - 568.9, - 569, - 569.2, - 569, - 569.2, - 569.5, - 569.7, - 569.7, - 569.8, - 570.1, - 569.9, - 570.1, - 570.4, - 570.9, - 571.4, - 571.7, - 571.9, - 571.9, - 571.9, - 572.3, - 572.7, - 572.7, - 572.7, - 572.8, - 572.5, - 571.9, - 571.8, - 572.2, - 572.3, - 572.1, - 571.7, - 571.5, - 571.5, - 571.6, - 571.6, - 571.4, - 571.3, - 571.4, - 571.4, - 571.3, - 571, - 570.6, - 570.5, - 570.4, - 569.9, - 568, - 557.2, - 542.2, - 529, - 517.5, - 499.1, - 480.4, - 458.5, - 434.5, - 415.4, - 408.1, - 403, - 398.1, - 393.1, - 387.1, - 379.6, - 371.8, - 365.4, - 359.9, - 351.3, - 338.1, - 322.9, - 306.6, - 293, - 288.1, - 286.5, - 285.2, - 283.7, - 282.2, - 280.8, - 279.5, - 278.6, - 277.3, - 275.2, - 272.4, - 271.1, - 269.8, - 268.4, - 266.5, - 264.7, - 262.9, - 260.9, - 259.1, - 257.9, - 257.5, - 256.7, - 255.4, - 255.3, - 257.3, - 259, - 260.6, - 261.2, - 259.9, - 258, - 257.2, - 260.2, - 261.9, - 262.6, - 264.1, - 266.3, - 266, - 264.9, - 262.1, - 258.4, - 256, - 253.7, - 251.2, - 247.6, - 241.6, - 234.9, - 226.4, - 220, - 215.2, - 211.7, - 208, - 204.5, - 200.9, - 195.7, - 186.9, - 176.7, - 170.5, - 165.9, - 159.5, - 151.4, - 143.8, - 137.1, - 128.9, - 122.3, - 116.9, - 111.1, - 105.5, - 101.1, - 97.9, - 95.1, - 92.3, - 89.3, - 85.8, - 81.7, - 78.6, - 76.9, - 75.7, - 74.4, - 73.1, - 72, - 71.2, - 70.3, - 69.4, - 68.5, - 67.6, - 66.7, - 65.9, - 65.2, - 64.3, - 63.5, - 62.7, - 61.9, - 61.2, - 60.5, - 59.7, - 59.1, - 58.5, - 57.8, - 57.2, - 56.6, - 56, - 55.5, - 55, - 54.5, - 54.1, - 53.7, - 53.3, - 52.8, - 52.4, - 52, - 51.5, - 51.1, - 50.8, - 50.4, - 50.1, - 49.7, - 49.3, - 49, - 48.6, - 48.3, - 47.9, - 47.6, - 47.2, - 46.9, - 46.7, - 46.4, - 46, - 45.7, - 45.5, - 45.3, - 45.1, - 44.8, - 44.5, - 44.2, - 43.9, - 43.7, - 43.5, - 43.2, - 43, - 42.8, - 42.6, - 42.3, - 42.1, - 42, - 41.9, - 41.9, - 41.9, - 41.8, - 41.7, - 41.7, - 41.6, - 41.5, - 41.5, - 41.6, - 41.8, - 41.9, - 41.9, - 41.8, - 41.7, - 41.7, - 41.9, - 41.9, - 41.7, - 41.3, - 40.9, - 41, - 41.3, - 41.9, - 41.7, - 41.3, - 41.1, - 40.7, - 40.4, - 40, - 39.7, - 39.4, - 39, - 38.3, - 37.5, - 36.7, - 35.9, - 35.5, - 35.5, - 35.6, - 36.2, - 38.8, - 41.7, - 571.6, - 570.9, - 570.6, - 570.8, - 570.6, - 570.9, - 571.2, - 571.3, - 571.5, - 571.8, - 571.8, - 571.5, - 571.8, - 572.1, - 572.2, - 572.6, - 573.1, - 573.4, - 573.5, - 573.8, - 574.2, - 574.7, - 574.5, - 574.5, - 574.4, - 574.1, - 573.6, - 573.3, - 573.4, - 573.4, - 573.4, - 573.2, - 573.1, - 573.2, - 573.3, - 573, - 572.8, - 572.8, - 572.8, - 572.8, - 572.6, - 572.3, - 571.9, - 571.7, - 571.7, - 571.6, - 571.5, - 570.4, - 564.1, - 553.8, - 544.3, - 528.4, - 508.7, - 485.1, - 460.3, - 434.5, - 414.8, - 409.2, - 404.5, - 400, - 395.2, - 389.4, - 382.3, - 375.2, - 369, - 363.4, - 355.9, - 341, - 323.6, - 309, - 293, - 287.5, - 285.9, - 284.5, - 282.9, - 281.5, - 280.1, - 278.9, - 277.6, - 275.8, - 274.2, - 273.1, - 272, - 270.6, - 268.8, - 267.2, - 265.2, - 263.2, - 261.4, - 260.4, - 260.4, - 260, - 258.8, - 258, - 259.9, - 261.6, - 263, - 263.7, - 262, - 260.6, - 261.5, - 264.2, - 265.2, - 265.8, - 267.6, - 269.7, - 269.2, - 268.2, - 266.1, - 262.3, - 259.7, - 257.4, - 254.9, - 252.2, - 248.9, - 243.9, - 235.4, - 226.3, - 220.6, - 215.4, - 211.6, - 207.9, - 204.5, - 200.9, - 197.2, - 190, - 179, - 171.1, - 166.1, - 159.8, - 152.3, - 144.8, - 137.2, - 128.9, - 122.6, - 117.2, - 112, - 107, - 102.9, - 99.5, - 96.2, - 92.5, - 89.4, - 86.3, - 82.6, - 78.9, - 76.7, - 75.4, - 74.1, - 73, - 72.1, - 71.2, - 70.3, - 69.3, - 68.5, - 67.5, - 66.7, - 66, - 65.2, - 64.3, - 63.5, - 62.8, - 62.1, - 61.4, - 60.7, - 60, - 59.3, - 58.7, - 58.1, - 57.6, - 57, - 56.5, - 56.1, - 55.6, - 55.1, - 54.7, - 54.3, - 53.8, - 53.4, - 53, - 52.5, - 52.2, - 51.8, - 51.5, - 51.1, - 50.8, - 50.4, - 50.1, - 49.7, - 49.3, - 49, - 48.6, - 48.3, - 48, - 47.7, - 47.3, - 46.9, - 46.6, - 46.4, - 46.3, - 46, - 45.8, - 45.5, - 45.2, - 44.9, - 44.7, - 44.4, - 44.2, - 44, - 43.7, - 43.5, - 43.2, - 43, - 42.9, - 42.8, - 42.8, - 42.8, - 42.7, - 42.6, - 42.6, - 42.6, - 42.5, - 42.5, - 42.5, - 42.7, - 42.8, - 42.8, - 42.8, - 42.7, - 42.7, - 42.8, - 42.8, - 42.7, - 42.2, - 42.1, - 42.2, - 42.4, - 42.9, - 42.8, - 42.4, - 42.1, - 41.6, - 41.5, - 41.2, - 40.7, - 40.2, - 39.7, - 39, - 38.1, - 37.5, - 37.2, - 37.1, - 37.2, - 37.3, - 38.9, - 43.7, - 46.6, - 573.2, - 573.2, - 572.7, - 572.5, - 572.1, - 572.2, - 572.3, - 572.8, - 573.1, - 573.3, - 573.2, - 573.3, - 573.3, - 573.4, - 573.6, - 573.9, - 574.5, - 574.8, - 575.1, - 575.6, - 576.3, - 576.5, - 576.1, - 575.9, - 575.7, - 575.6, - 575.5, - 574.8, - 574.4, - 574.7, - 574.7, - 574.6, - 574.6, - 574.7, - 574.8, - 574.5, - 574.7, - 574.7, - 574.4, - 574, - 573.7, - 573.5, - 573.4, - 573.4, - 573.3, - 573, - 573.2, - 574.1, - 574.8, - 572.6, - 568.3, - 557.2, - 536, - 512.3, - 488.1, - 459.7, - 432.7, - 416.2, - 410.1, - 405.4, - 401.3, - 397.1, - 391.8, - 385.1, - 378.3, - 372, - 366.3, - 358.1, - 341.1, - 323.5, - 310.1, - 294.2, - 287, - 285.8, - 284.4, - 282.5, - 281.1, - 279.6, - 278.4, - 277.3, - 276.4, - 275.2, - 274.2, - 272.7, - 270.8, - 269.3, - 267.4, - 265.6, - 264, - 263.1, - 262.9, - 262.9, - 262.3, - 261.8, - 262.7, - 263.7, - 264.8, - 265.6, - 265, - 264.3, - 265.6, - 268, - 268.4, - 269.5, - 271.1, - 273.1, - 272.8, - 271.7, - 269.9, - 266.1, - 263.4, - 261.1, - 258.7, - 256.2, - 253.2, - 248.6, - 241, - 230.5, - 225.9, - 221.3, - 215.9, - 211.7, - 208, - 204.4, - 201.1, - 197.6, - 191, - 176.9, - 171.2, - 166.6, - 160.6, - 153.4, - 145.5, - 137.8, - 129.7, - 123.2, - 118.1, - 113.3, - 108.8, - 104.9, - 100.9, - 96.6, - 92.8, - 89.9, - 86.6, - 83, - 79.4, - 76.4, - 75.1, - 74, - 73, - 72.1, - 71.1, - 70.2, - 69.3, - 68.4, - 67.6, - 66.9, - 66.1, - 65.2, - 64.5, - 63.8, - 63, - 62.3, - 61.6, - 60.9, - 60.3, - 59.7, - 59.1, - 58.5, - 58, - 57.5, - 57.1, - 56.6, - 56.1, - 55.7, - 55.3, - 54.8, - 54.4, - 54, - 53.6, - 53.2, - 52.9, - 52.5, - 52.1, - 51.8, - 51.5, - 51.1, - 50.8, - 50.4, - 50.1, - 49.7, - 49.4, - 49, - 48.7, - 48.3, - 47.9, - 47.6, - 47.4, - 47.2, - 47, - 46.7, - 46.5, - 46.2, - 45.9, - 45.7, - 45.4, - 45.1, - 44.9, - 44.7, - 44.4, - 44.2, - 44, - 43.9, - 43.8, - 43.8, - 43.7, - 43.7, - 43.6, - 43.5, - 43.4, - 43.4, - 43.4, - 43.5, - 43.6, - 43.8, - 43.8, - 43.7, - 43.6, - 43.8, - 43.7, - 43.8, - 43.8, - 43.4, - 43.3, - 43.4, - 43.5, - 44, - 44.1, - 43.8, - 43.3, - 42.9, - 42.6, - 42.3, - 41.9, - 41.2, - 40.5, - 39.8, - 39.3, - 38.9, - 38.7, - 38.7, - 38.9, - 39, - 43.6, - 49.2, - 51.7, - 575.1, - 575.2, - 575.1, - 573.7, - 573.2, - 573.4, - 573.8, - 574.4, - 574.8, - 574.9, - 574.5, - 574.7, - 574.8, - 574.8, - 575, - 575.3, - 575.9, - 576.1, - 576.5, - 577, - 577.5, - 577.6, - 577.4, - 577.4, - 577.7, - 578.3, - 579.2, - 578.6, - 576.7, - 576.4, - 575.9, - 576, - 576, - 576.2, - 576.4, - 576.6, - 576.5, - 576, - 575.6, - 575.3, - 574.8, - 574.6, - 574.9, - 575, - 575.1, - 575.1, - 575.5, - 576.3, - 576.8, - 576.6, - 576.4, - 574.6, - 562.6, - 541.3, - 516.6, - 493, - 470.7, - 441.7, - 418.3, - 410.5, - 406.3, - 402.4, - 398.3, - 393.5, - 387.6, - 381.5, - 375.5, - 368.7, - 358.9, - 341.2, - 324, - 311.2, - 293.7, - 287.3, - 286.2, - 284, - 282.1, - 280.8, - 279.9, - 279, - 278, - 277.1, - 276.1, - 275, - 273.2, - 271.4, - 269.8, - 268.3, - 266.7, - 265.7, - 265.1, - 265, - 264.4, - 264.2, - 264.6, - 265.6, - 266.6, - 267.3, - 266.8, - 267.2, - 269.7, - 271.8, - 271.6, - 273, - 274.4, - 276.3, - 276.1, - 275, - 273.6, - 270, - 267.1, - 265, - 262.7, - 260.2, - 257.3, - 253.4, - 245.1, - 233.7, - 229.6, - 226.5, - 222.5, - 217.3, - 212.5, - 208.4, - 204.6, - 201.3, - 197.4, - 187.5, - 178.2, - 173.6, - 167.6, - 161.8, - 154.5, - 147, - 139.1, - 130.4, - 123.9, - 118.9, - 114.2, - 110.3, - 105.8, - 101.4, - 97.1, - 93.1, - 89.7, - 86.5, - 83.4, - 79.7, - 76.3, - 74.9, - 73.9, - 73, - 72, - 71, - 70.1, - 69.3, - 68.5, - 67.8, - 67, - 66.2, - 65.5, - 64.7, - 64, - 63.3, - 62.6, - 61.9, - 61.2, - 60.6, - 60, - 59.5, - 59, - 58.5, - 58.1, - 57.6, - 57.1, - 56.6, - 56.2, - 55.8, - 55.4, - 55, - 54.6, - 54.3, - 53.9, - 53.5, - 53.1, - 52.7, - 52.4, - 52.1, - 51.8, - 51.4, - 51.1, - 50.7, - 50.4, - 50.1, - 49.7, - 49.3, - 48.9, - 48.6, - 48.4, - 48.3, - 48, - 47.8, - 47.5, - 47.2, - 46.9, - 46.7, - 46.4, - 46.2, - 45.9, - 45.7, - 45.4, - 45.2, - 45, - 44.9, - 44.8, - 44.7, - 44.7, - 44.7, - 44.6, - 44.5, - 44.4, - 44.3, - 44.3, - 44.4, - 44.5, - 44.7, - 44.8, - 44.7, - 44.6, - 44.7, - 44.8, - 44.8, - 44.9, - 44.5, - 44.5, - 44.6, - 44.6, - 45, - 45.3, - 45.1, - 44.6, - 44.2, - 43.7, - 43.4, - 42.8, - 42.2, - 41.5, - 41.1, - 40.8, - 40.5, - 40.2, - 40.4, - 40.5, - 41.6, - 48.8, - 53.2, - 54.3, - 577.1, - 577.9, - 578.5, - 577.6, - 576.8, - 575.7, - 575.2, - 575.3, - 575.8, - 576.1, - 576, - 575.9, - 576, - 576.1, - 576.3, - 576.8, - 577.3, - 577.5, - 577.9, - 578.2, - 578.5, - 578.9, - 579.7, - 580.7, - 581.1, - 581.7, - 582.5, - 581.8, - 579.6, - 577.7, - 577.1, - 577.4, - 577.5, - 577.7, - 577.9, - 577.9, - 577.5, - 577, - 576.7, - 576.5, - 576.3, - 576.3, - 576.5, - 576.7, - 577.1, - 577.4, - 577.6, - 577.8, - 578, - 578.1, - 578.3, - 578.8, - 578.2, - 570.9, - 555.4, - 536.3, - 514.8, - 481.3, - 449.1, - 426.2, - 413.3, - 407.8, - 403.6, - 399.2, - 394.9, - 390.2, - 385.3, - 378.6, - 370.9, - 359.6, - 341.2, - 324.5, - 308.3, - 292.2, - 288.2, - 286, - 283.5, - 282.1, - 281.2, - 280.6, - 279.9, - 278.9, - 278, - 277.2, - 275.5, - 273.6, - 272.3, - 271, - 269.4, - 268.2, - 267.8, - 267.1, - 266.6, - 266.4, - 266.6, - 267.6, - 268.5, - 268.8, - 268.7, - 270.7, - 273.7, - 275.2, - 274.8, - 276.1, - 277.4, - 279.4, - 279.1, - 278, - 276.9, - 275.1, - 271.4, - 268.5, - 266.2, - 263.9, - 261.3, - 257.5, - 245.5, - 236.4, - 233.4, - 230.3, - 227.1, - 223.2, - 219, - 214.5, - 209.1, - 205, - 202.8, - 199.4, - 193.1, - 186.8, - 176.6, - 169.3, - 162.9, - 155.8, - 146.9, - 138.9, - 131.8, - 125.2, - 119.8, - 115.4, - 111.2, - 106.5, - 101.9, - 97.6, - 93.4, - 89.9, - 86.5, - 83.2, - 78.9, - 76.2, - 75, - 74, - 73, - 72, - 71.1, - 70.3, - 69.5, - 68.7, - 67.9, - 67.1, - 66.4, - 65.7, - 65, - 64.3, - 63.6, - 62.9, - 62.2, - 61.6, - 61, - 60.5, - 60, - 59.6, - 59.1, - 58.7, - 58.2, - 57.7, - 57.2, - 56.9, - 56.5, - 56.1, - 55.7, - 55.3, - 55, - 54.6, - 54.1, - 53.8, - 53.5, - 53.2, - 52.9, - 52.6, - 52.2, - 51.9, - 51.5, - 51.2, - 50.8, - 50.3, - 49.9, - 49.6, - 49.4, - 49.2, - 49, - 48.8, - 48.5, - 48.2, - 48, - 47.7, - 47.4, - 47.2, - 46.9, - 46.7, - 46.4, - 46.2, - 46, - 45.9, - 45.8, - 45.7, - 45.6, - 45.6, - 45.6, - 45.5, - 45.4, - 45.4, - 45.3, - 45.4, - 45.5, - 45.7, - 45.8, - 45.8, - 45.7, - 45.8, - 45.8, - 45.9, - 46, - 45.7, - 45.7, - 45.7, - 45.7, - 45.8, - 46.3, - 46.4, - 45.9, - 45.4, - 44.9, - 44.5, - 43.9, - 43.3, - 42.8, - 42.6, - 42.4, - 41.9, - 41.9, - 42.2, - 42.1, - 44.8, - 53.3, - 56.1, - 56.7, - 579.2, - 579.9, - 580.7, - 581.5, - 582.7, - 581.5, - 578.6, - 577.5, - 577.5, - 577.3, - 577.4, - 577.3, - 577.3, - 577.6, - 578, - 578.2, - 578.4, - 578.8, - 579.6, - 580.6, - 580.9, - 581.4, - 582.4, - 583.1, - 583.5, - 584.3, - 584.5, - 583.4, - 581.2, - 579.5, - 578.8, - 578.6, - 578.9, - 579.1, - 579.1, - 578.8, - 578.4, - 578, - 577.7, - 577.8, - 578.1, - 578.2, - 578.1, - 578.2, - 578.6, - 578.8, - 579.1, - 579.6, - 580.2, - 580.7, - 581.1, - 581.4, - 581.4, - 581.3, - 578.9, - 571.4, - 553, - 522.1, - 493.1, - 466.7, - 442.3, - 422.4, - 413.5, - 405.4, - 400.7, - 396.9, - 393.1, - 387.6, - 382.5, - 374.3, - 360.6, - 343.9, - 326.7, - 312.2, - 296.9, - 289.2, - 286.1, - 283.8, - 282.6, - 282, - 281.4, - 280.6, - 279.8, - 279, - 277.5, - 275.8, - 274.5, - 273.4, - 272.3, - 270.7, - 269.9, - 269.3, - 268.7, - 268.4, - 268.8, - 269.8, - 270.5, - 270.5, - 271.9, - 274.8, - 277.4, - 278.7, - 278, - 279.1, - 280.2, - 282.3, - 282, - 281.1, - 280, - 278.7, - 275.7, - 272.2, - 269.9, - 267.9, - 265.6, - 261.7, - 247.7, - 239.3, - 236.5, - 234.1, - 231.3, - 228.1, - 224.5, - 220.1, - 215.1, - 209.9, - 207.1, - 206.6, - 201.5, - 195.6, - 189.6, - 180.4, - 171, - 163.9, - 156.1, - 146.2, - 139.4, - 132.7, - 126.3, - 120.7, - 116.2, - 112, - 107.3, - 102.4, - 97.9, - 94, - 90.5, - 86.4, - 82.2, - 78.9, - 76.4, - 75.1, - 74.1, - 73.1, - 72.1, - 71.2, - 70.4, - 69.6, - 68.8, - 68, - 67.2, - 66.5, - 65.9, - 65.2, - 64.5, - 63.9, - 63.2, - 62.6, - 62, - 61.5, - 61, - 60.6, - 60.2, - 59.7, - 59.2, - 58.7, - 58.3, - 57.9, - 57.5, - 57.1, - 56.7, - 56.4, - 56, - 55.6, - 55.3, - 55, - 54.6, - 54.3, - 54, - 53.7, - 53.3, - 53, - 52.6, - 52.2, - 51.8, - 51.4, - 50.9, - 50.7, - 50.5, - 50.3, - 50, - 49.8, - 49.5, - 49.3, - 49, - 48.7, - 48.4, - 48.2, - 48, - 47.7, - 47.5, - 47.2, - 47, - 46.9, - 46.8, - 46.7, - 46.6, - 46.6, - 46.5, - 46.5, - 46.4, - 46.3, - 46.3, - 46.4, - 46.5, - 46.6, - 46.8, - 46.8, - 46.8, - 46.9, - 47, - 47.1, - 47.1, - 47, - 47, - 46.9, - 46.9, - 46.9, - 47.3, - 47.6, - 47.2, - 46.6, - 46.1, - 45.6, - 45.1, - 44.6, - 44.4, - 44.2, - 43.8, - 43.4, - 43.8, - 44, - 43.8, - 46.5, - 55, - 58.9, - 59.5, - 581.3, - 582.1, - 583.7, - 584.8, - 586, - 586.1, - 584.2, - 582.9, - 582.1, - 580.1, - 579, - 579.3, - 579.4, - 580.1, - 580.5, - 579.9, - 580.1, - 580.9, - 582.3, - 583.2, - 583.2, - 583.7, - 584.5, - 584.8, - 585, - 585.5, - 585.4, - 584.3, - 583.1, - 581.8, - 581.1, - 580.5, - 580.6, - 580.4, - 580.4, - 580.7, - 580.8, - 581.1, - 580.5, - 579.8, - 579.8, - 580, - 580, - 580.2, - 580.3, - 580.6, - 581.4, - 582.3, - 583, - 583, - 583.7, - 584.1, - 583.9, - 583.6, - 582.2, - 577.5, - 571.4, - 559.1, - 537.3, - 512, - 484, - 459.7, - 442.8, - 424.9, - 411.1, - 403.7, - 399.4, - 395.5, - 391.6, - 387.2, - 377.8, - 363.6, - 346.2, - 329.5, - 315.5, - 300.7, - 291.5, - 287.5, - 284.7, - 283.4, - 282.7, - 281.9, - 281.4, - 280.6, - 279.4, - 278, - 276.9, - 275.8, - 274.9, - 273.4, - 271.8, - 271.1, - 270.6, - 270.6, - 271.4, - 272.5, - 272.7, - 273, - 275.3, - 278.2, - 280.5, - 281.6, - 281.3, - 282.2, - 283.5, - 285.4, - 285.2, - 284.2, - 283.1, - 281.9, - 279.7, - 276, - 273.9, - 271.9, - 270, - 266.7, - 252.7, - 242.3, - 239.5, - 237.2, - 234.6, - 231.8, - 229, - 225.6, - 221.1, - 216.4, - 212.4, - 211.5, - 207.6, - 202, - 196.3, - 190.7, - 181.9, - 172.8, - 165.5, - 156.9, - 146.5, - 139.7, - 133.1, - 127.2, - 121.6, - 117.2, - 112.8, - 107.9, - 102.6, - 98, - 94.9, - 91.2, - 86.8, - 82.7, - 79, - 76.2, - 74.9, - 74, - 73.2, - 72.2, - 71.3, - 70.6, - 69.8, - 69, - 68.2, - 67.3, - 66.7, - 66.1, - 65.5, - 64.8, - 64.2, - 63.6, - 63, - 62.6, - 62.1, - 61.6, - 61.2, - 60.8, - 60.3, - 59.8, - 59.3, - 58.9, - 58.5, - 58.1, - 57.7, - 57.3, - 57, - 56.7, - 56.3, - 56, - 55.7, - 55.4, - 55.1, - 54.7, - 54.4, - 54, - 53.7, - 53.3, - 52.9, - 52.4, - 52, - 51.8, - 51.6, - 51.3, - 51.1, - 50.8, - 50.5, - 50.3, - 50, - 49.7, - 49.4, - 49.3, - 49.1, - 48.8, - 48.6, - 48.3, - 48.1, - 48, - 47.8, - 47.7, - 47.7, - 47.6, - 47.6, - 47.5, - 47.4, - 47.3, - 47.3, - 47.4, - 47.5, - 47.6, - 47.8, - 47.8, - 47.8, - 47.9, - 48, - 48.2, - 48.3, - 48.3, - 48.2, - 48.2, - 48.2, - 48, - 48.4, - 48.8, - 48.4, - 47.8, - 47.3, - 46.8, - 46.5, - 46.2, - 46.1, - 45.9, - 45.4, - 46, - 46.7, - 46.2, - 46, - 48.3, - 55.9, - 60.5, - 62.6, - 584.4, - 585.3, - 586.4, - 587.2, - 587.7, - 588.3, - 588.5, - 588.2, - 586.8, - 583.6, - 582, - 581.9, - 582, - 582.8, - 583.4, - 582.7, - 582.8, - 583.3, - 584.2, - 584.7, - 585.1, - 585.8, - 586.2, - 586.3, - 586.6, - 586.8, - 586.4, - 585.7, - 585.1, - 584.4, - 583.6, - 582.9, - 582.7, - 582.2, - 581.9, - 582.7, - 584, - 584.5, - 584, - 582.3, - 582.1, - 582.5, - 582.7, - 582.5, - 582.3, - 582.6, - 583.6, - 584.6, - 584.9, - 585, - 585.8, - 586, - 585.8, - 585, - 582.8, - 578.6, - 574.2, - 571.6, - 566, - 550.3, - 522.9, - 500.9, - 482, - 462, - 445.5, - 426.4, - 411.1, - 402, - 398.6, - 396.5, - 393.7, - 384.4, - 366, - 347.7, - 330.9, - 317.8, - 303.8, - 293.3, - 289.3, - 285.7, - 284.1, - 283.3, - 282.7, - 282, - 281.1, - 280.2, - 279.1, - 278.2, - 277.5, - 276.3, - 274.5, - 273.1, - 272.7, - 273.1, - 274.3, - 275.3, - 275.4, - 275.6, - 277.9, - 281, - 283.3, - 284, - 283.9, - 285, - 286.9, - 288.8, - 288.5, - 287.4, - 286.3, - 284.9, - 283.1, - 279.6, - 277.7, - 275.7, - 273.8, - 271.1, - 259.2, - 246.3, - 242.7, - 240.3, - 237.9, - 235.4, - 233, - 230.8, - 227.4, - 222.8, - 219, - 216.4, - 212.3, - 208, - 203, - 196.9, - 191.2, - 184.2, - 174.9, - 167.3, - 156.6, - 146.9, - 140.5, - 134, - 127.9, - 123.3, - 118.5, - 113.4, - 107.9, - 102.7, - 98.8, - 95.8, - 91.8, - 87.4, - 83, - 79, - 75.9, - 74.6, - 74.2, - 73.3, - 72.5, - 71.6, - 70.9, - 70.2, - 69.4, - 68.4, - 67.7, - 67.2, - 66.5, - 65.9, - 65.2, - 64.7, - 64.1, - 63.7, - 63.2, - 62.7, - 62.3, - 61.8, - 61.4, - 60.9, - 60.4, - 59.9, - 59.5, - 59.1, - 58.7, - 58.4, - 58.1, - 57.7, - 57.4, - 57.1, - 56.7, - 56.4, - 56.1, - 55.8, - 55.5, - 55.1, - 54.7, - 54.4, - 53.9, - 53.4, - 53.1, - 52.9, - 52.6, - 52.4, - 52.1, - 51.9, - 51.6, - 51.3, - 51, - 50.7, - 50.5, - 50.3, - 50.1, - 49.9, - 49.6, - 49.4, - 49.2, - 49.1, - 48.9, - 48.8, - 48.8, - 48.7, - 48.6, - 48.5, - 48.5, - 48.4, - 48.4, - 48.5, - 48.6, - 48.7, - 48.8, - 48.9, - 48.9, - 49, - 49.1, - 49.2, - 49.4, - 49.4, - 49.3, - 49.4, - 49.3, - 49.2, - 49.4, - 50, - 49.6, - 49, - 48.5, - 48.2, - 48.1, - 48, - 47.8, - 47.6, - 47.7, - 48.6, - 49.2, - 48.4, - 49, - 52.2, - 58, - 63.9, - 67.2, - 586.7, - 587.2, - 587.7, - 588.4, - 588.8, - 589.8, - 590.3, - 589.7, - 588.1, - 586, - 584.9, - 584.5, - 584.2, - 584.5, - 584.9, - 584.9, - 584.8, - 584.9, - 585.6, - 586.1, - 586.7, - 587.3, - 587.7, - 588, - 588.2, - 588.1, - 587.5, - 587.2, - 586.7, - 586.3, - 586.1, - 585.1, - 584.4, - 584, - 584.3, - 585.4, - 586, - 586.2, - 586.4, - 585.7, - 584.7, - 584.4, - 584.7, - 584.8, - 584.7, - 584.4, - 585.2, - 585.8, - 586.4, - 587.2, - 587.6, - 587.7, - 587.4, - 585.6, - 581.9, - 578.2, - 574.7, - 572.3, - 570.8, - 568, - 557.5, - 543.1, - 522.1, - 504.5, - 486.7, - 465.4, - 439.2, - 414.8, - 403.1, - 402.4, - 403.6, - 399.8, - 388.1, - 368.8, - 349.4, - 331.7, - 318.4, - 300.5, - 293.5, - 289.9, - 286.1, - 285, - 284.5, - 283.6, - 282.8, - 282.2, - 281.3, - 280.7, - 279.9, - 278.7, - 277.4, - 276.1, - 275.4, - 275.7, - 276.8, - 277.7, - 278, - 278, - 280.1, - 284.1, - 286, - 286.5, - 286.9, - 288.4, - 290.2, - 291.7, - 291.7, - 290.6, - 289.4, - 288, - 286.5, - 283.4, - 281.3, - 279.6, - 277.7, - 275.4, - 267.9, - 253.8, - 246.3, - 243.8, - 241.4, - 238.9, - 236.6, - 234.4, - 231.1, - 227.8, - 224.3, - 220.6, - 216.7, - 212.8, - 208.6, - 202.7, - 197.2, - 192.8, - 186.7, - 178.1, - 168.2, - 156.6, - 148, - 141.6, - 135, - 129.7, - 124.6, - 118.9, - 113.3, - 108, - 103.3, - 99.7, - 96.7, - 92.7, - 88, - 83.5, - 79.6, - 76.5, - 75.3, - 74.5, - 73.7, - 72.8, - 72, - 71.3, - 70.5, - 69.7, - 68.9, - 68.3, - 67.6, - 67, - 66.3, - 65.8, - 65.3, - 64.8, - 64.3, - 63.9, - 63.4, - 62.9, - 62.5, - 62, - 61.5, - 61, - 60.6, - 60.1, - 59.7, - 59.5, - 59.2, - 58.8, - 58.5, - 58.2, - 57.8, - 57.5, - 57.2, - 56.9, - 56.6, - 56.2, - 55.9, - 55.5, - 55, - 54.6, - 54.2, - 53.9, - 53.7, - 53.5, - 53.2, - 52.9, - 52.7, - 52.3, - 52, - 51.8, - 51.5, - 51.3, - 51.2, - 51, - 50.8, - 50.5, - 50.3, - 50.1, - 50, - 50, - 49.9, - 49.8, - 49.7, - 49.6, - 49.6, - 49.6, - 49.6, - 49.6, - 49.7, - 49.8, - 49.9, - 50, - 50, - 50, - 50.2, - 50.3, - 50.5, - 50.6, - 50.5, - 50.6, - 50.7, - 50.5, - 50.6, - 51.1, - 50.8, - 50.3, - 50, - 49.8, - 49.8, - 49.7, - 49.6, - 49.5, - 50.1, - 51.1, - 51.2, - 51, - 53.3, - 58.2, - 63.1, - 68.7, - 72.4, - 587.6, - 588.1, - 588.7, - 589.4, - 590.1, - 591.2, - 591.5, - 590.7, - 589.6, - 588.5, - 587.4, - 586.6, - 585.9, - 585.6, - 586.2, - 586.5, - 586.4, - 586.4, - 587.1, - 587.4, - 588.1, - 588.7, - 589.1, - 589.5, - 589.7, - 589.5, - 589, - 589, - 588.3, - 588, - 587.9, - 586.8, - 585.9, - 585.8, - 586.1, - 587.1, - 588.1, - 588.4, - 588.3, - 588.3, - 587.3, - 586.8, - 586.9, - 587, - 586.9, - 586.6, - 586.9, - 587.3, - 588.1, - 588.7, - 589, - 589.1, - 588, - 585.9, - 581.9, - 577.9, - 575.2, - 573.3, - 572.5, - 571.4, - 570.5, - 568.7, - 556.9, - 535.8, - 513.6, - 494, - 469, - 440.9, - 419.9, - 419.8, - 417.6, - 417.5, - 409.6, - 392.4, - 372, - 352.3, - 335.2, - 318.1, - 304.2, - 295.5, - 290.9, - 288.7, - 286.4, - 285.2, - 284.5, - 283.9, - 283.2, - 282.7, - 282, - 281, - 279.9, - 278.7, - 277.7, - 277.9, - 278.8, - 279.7, - 279.9, - 280.8, - 284.2, - 287.3, - 288.6, - 289.3, - 290.2, - 292.2, - 293.4, - 294.4, - 294.7, - 293.9, - 292.7, - 291.1, - 289.9, - 287.2, - 285, - 283.3, - 281.7, - 279.7, - 275.6, - 262.3, - 251.1, - 247.8, - 245.1, - 242.4, - 240.1, - 238.1, - 234.5, - 231.2, - 228.1, - 224.6, - 220.8, - 217.1, - 213.5, - 208.3, - 202.8, - 199, - 194, - 188.5, - 180.2, - 170.1, - 158.6, - 149.6, - 142.7, - 136.3, - 130.5, - 125.4, - 119.8, - 114, - 108.4, - 103.2, - 99.6, - 96.6, - 92.8, - 88.4, - 84.2, - 80.4, - 77.4, - 75.8, - 74.9, - 74.1, - 73.2, - 72.4, - 71.6, - 70.8, - 70, - 69.4, - 68.8, - 68.1, - 67.5, - 66.9, - 66.4, - 65.9, - 65.4, - 64.9, - 64.5, - 64, - 63.5, - 63.1, - 62.6, - 61.8, - 61.5, - 61.2, - 60.8, - 60.5, - 60.3, - 59.9, - 59.6, - 59.3, - 58.9, - 58.6, - 58.3, - 58, - 57.7, - 57.3, - 57, - 56.6, - 56.1, - 55.7, - 55.4, - 55, - 54.8, - 54.6, - 54.4, - 54.1, - 53.8, - 53.4, - 53.1, - 52.8, - 52.6, - 52.4, - 52.3, - 52.1, - 51.9, - 51.6, - 51.4, - 51.2, - 51.1, - 51.1, - 51, - 50.9, - 50.8, - 50.8, - 50.7, - 50.6, - 50.7, - 50.7, - 50.8, - 50.9, - 51, - 51.1, - 51.1, - 51.2, - 51.3, - 51.5, - 51.7, - 51.8, - 51.7, - 51.7, - 52, - 51.9, - 51.9, - 52.1, - 52, - 51.8, - 51.6, - 51.5, - 51.5, - 51.5, - 51.4, - 51.6, - 52.4, - 53.2, - 53.4, - 54.3, - 58.5, - 63.9, - 67.5, - 72.2, - 75.6, - 589.2, - 589.2, - 589.8, - 590.9, - 592, - 592.7, - 592.5, - 591.6, - 591.1, - 590.4, - 588.8, - 588.2, - 587.7, - 586.9, - 587.2, - 587.7, - 587.8, - 587.9, - 588.4, - 588.8, - 589.5, - 590.1, - 590.6, - 591, - 591.2, - 591.2, - 591.1, - 590.6, - 590.3, - 590.1, - 589.4, - 588.6, - 587.8, - 587.8, - 588.1, - 588.5, - 589.6, - 590.4, - 590.3, - 590.4, - 590.2, - 589.9, - 589, - 588.4, - 588.3, - 588.2, - 588.4, - 589.2, - 589.6, - 589.8, - 589.9, - 589.9, - 588.5, - 586.4, - 582.6, - 578.9, - 576.3, - 575, - 574.5, - 573.8, - 573.3, - 573.5, - 571.7, - 558.4, - 540.3, - 521.5, - 497.6, - 477, - 459.7, - 453.4, - 441.1, - 435.5, - 430.1, - 416.3, - 396.7, - 376.8, - 356.3, - 337.3, - 323.4, - 310.4, - 298.6, - 294.4, - 290.8, - 287.9, - 286.4, - 285.9, - 285.2, - 284.5, - 283.8, - 283.1, - 282.2, - 281.3, - 279.9, - 279.5, - 280.5, - 281.4, - 282.3, - 285.4, - 289, - 291.3, - 292, - 292.1, - 293.1, - 295.7, - 296.5, - 296.9, - 297, - 296.7, - 295.6, - 294.1, - 292.7, - 290.5, - 288.6, - 287.1, - 285.5, - 283.6, - 280.8, - 270.5, - 256.8, - 252.3, - 249.3, - 246.3, - 243.6, - 241.6, - 238.5, - 234.9, - 231.9, - 228.7, - 224.9, - 221.3, - 217.5, - 213.4, - 208.2, - 203.8, - 199.5, - 194.8, - 189.8, - 181.8, - 171.8, - 160.7, - 150.2, - 143.8, - 137, - 131.2, - 126.2, - 120.6, - 114.8, - 108.7, - 103.8, - 99.7, - 96, - 92.8, - 88.7, - 84.8, - 81.3, - 78, - 75.8, - 75.2, - 74.4, - 73.6, - 72.8, - 72, - 71.2, - 70.5, - 69.8, - 69.2, - 68.7, - 68.1, - 67.6, - 67.1, - 66.6, - 66.1, - 65.6, - 65.1, - 64.6, - 64.2, - 63.6, - 62.6, - 62.4, - 62.3, - 62, - 61.6, - 61.4, - 61, - 60.7, - 60.4, - 60.1, - 59.7, - 59.3, - 59.1, - 58.8, - 58.4, - 58.1, - 57.6, - 57.2, - 56.8, - 56.5, - 56.1, - 55.9, - 55.7, - 55.5, - 55.2, - 54.9, - 54.6, - 54.2, - 53.9, - 53.7, - 53.5, - 53.4, - 53.2, - 53, - 52.7, - 52.5, - 52.4, - 52.3, - 52.3, - 52.2, - 52.1, - 52, - 52, - 51.9, - 51.8, - 51.8, - 51.9, - 51.9, - 52, - 52.2, - 52.3, - 52.3, - 52.3, - 52.4, - 52.6, - 52.8, - 53, - 52.9, - 52.8, - 53.2, - 53.3, - 53.3, - 53.4, - 53.4, - 53.4, - 53.3, - 53.2, - 53.2, - 53.2, - 53.3, - 53.8, - 54.5, - 55.6, - 56.3, - 57.5, - 63.1, - 69.1, - 72, - 75.3, - 78, - 591.7, - 591.4, - 591.8, - 592.8, - 593.7, - 593.9, - 593.4, - 592.5, - 592, - 591.2, - 589.8, - 590, - 589.7, - 588.9, - 588.7, - 588.9, - 589.2, - 589.4, - 589.7, - 590.1, - 590.7, - 591.4, - 591.9, - 592.3, - 592.5, - 592.7, - 592.6, - 592.3, - 592.2, - 591.5, - 590.8, - 590.2, - 589.5, - 589.7, - 589.8, - 590.3, - 591, - 591.5, - 592, - 592.1, - 592.2, - 592, - 590.9, - 589.9, - 589.7, - 589.8, - 590.4, - 591, - 591, - 591.2, - 591.3, - 591.2, - 589.9, - 587.1, - 583.5, - 580.2, - 578.3, - 577.4, - 577.4, - 576.7, - 575.7, - 575.9, - 576.3, - 574.9, - 567.6, - 548.5, - 526.3, - 505.7, - 488.7, - 476, - 462.2, - 450.6, - 445.1, - 435.5, - 419.8, - 399.3, - 378.7, - 358.6, - 340.8, - 326.3, - 313.3, - 302.4, - 296.8, - 293.6, - 290.1, - 288.4, - 287.5, - 286.5, - 285.8, - 285.1, - 284.5, - 283.8, - 282.4, - 281.5, - 282.6, - 283.9, - 287, - 290.3, - 292.9, - 294.6, - 294.7, - 294.4, - 296.4, - 298.1, - 298.4, - 298.8, - 298.8, - 298.6, - 297.7, - 296.4, - 295.1, - 293.9, - 292.1, - 290.5, - 289, - 287.1, - 284.9, - 279.4, - 267.8, - 257.4, - 253.3, - 250.4, - 247.6, - 245.2, - 242.5, - 239, - 235.9, - 232.6, - 228.9, - 225.5, - 221.7, - 217.7, - 213.2, - 208.7, - 204.5, - 200.2, - 195.7, - 190.3, - 182.6, - 173.4, - 161.7, - 151.8, - 145.2, - 138.9, - 133, - 127.5, - 121.5, - 114.9, - 109.6, - 104.9, - 100.3, - 96.7, - 93, - 89, - 85.5, - 82.2, - 78.8, - 76.2, - 75.3, - 74.9, - 74.1, - 73.2, - 72.4, - 71.7, - 71, - 70.4, - 69.9, - 69.3, - 68.8, - 68.2, - 67.7, - 67.2, - 66.8, - 66.3, - 65.9, - 65.4, - 64.9, - 64.3, - 63.9, - 63.5, - 63.2, - 62.8, - 62.5, - 62.2, - 61.9, - 61.6, - 61.2, - 60.8, - 60.5, - 60.2, - 59.9, - 59.6, - 59.2, - 58.7, - 58.4, - 57.9, - 57.6, - 57.3, - 57, - 56.9, - 56.6, - 56.3, - 56, - 55.7, - 55.3, - 55, - 54.8, - 54.6, - 54.5, - 54.3, - 54.1, - 53.8, - 53.7, - 53.6, - 53.5, - 53.5, - 53.4, - 53.3, - 53.2, - 53.1, - 53.1, - 53, - 53, - 53.1, - 53.1, - 53.2, - 53.4, - 53.5, - 53.5, - 53.5, - 53.5, - 53.8, - 54, - 54.2, - 54.2, - 54, - 54.4, - 54.7, - 54.7, - 54.8, - 54.9, - 55, - 55, - 55, - 55, - 55, - 55.9, - 56.4, - 56.6, - 58, - 58.9, - 60.4, - 67.4, - 74.2, - 77, - 77.9, - 80.3, - 594.4, - 594.1, - 594.1, - 594.4, - 594.8, - 595.1, - 594.9, - 594, - 593, - 592.2, - 591.6, - 591.7, - 591.5, - 591.2, - 590.7, - 590.6, - 590.8, - 591.3, - 591.4, - 591.5, - 591.9, - 592.5, - 593.1, - 593.5, - 594.1, - 594.4, - 594.5, - 593.9, - 593.5, - 592.8, - 592.3, - 592, - 591.8, - 591.6, - 591.6, - 591.8, - 592.2, - 592.7, - 593.3, - 593.6, - 593.6, - 593.2, - 592.4, - 591.8, - 591.6, - 591.7, - 591.9, - 592.1, - 592.1, - 592.6, - 592.8, - 592, - 590.4, - 587.5, - 584.4, - 582, - 580.7, - 579.8, - 579.2, - 578.5, - 577.8, - 578, - 578.7, - 579.9, - 579.7, - 568.9, - 551, - 531.9, - 518, - 506.2, - 480.5, - 463.4, - 455.1, - 449.1, - 439.1, - 422.8, - 401, - 380.9, - 362.3, - 345.2, - 329.3, - 315.5, - 304, - 296.4, - 293.5, - 291.7, - 290.4, - 288.8, - 287.9, - 287.1, - 286.5, - 286.1, - 285.1, - 284.7, - 285.5, - 287.2, - 290.6, - 293.1, - 295.1, - 296, - 296.1, - 296.4, - 298.9, - 299.8, - 300.1, - 300.5, - 300.6, - 300.5, - 299.7, - 298.5, - 297.2, - 296.4, - 295.6, - 294.2, - 292.6, - 290.6, - 288.6, - 287.2, - 281.7, - 266, - 258.2, - 254.6, - 251.9, - 249.5, - 246.9, - 243.7, - 240.3, - 236.6, - 232.8, - 229, - 225.7, - 222.2, - 218, - 213.5, - 209.5, - 205, - 200.8, - 196.2, - 190.8, - 184.8, - 177.5, - 164.3, - 153.2, - 146.5, - 140.6, - 134.8, - 128.9, - 121.9, - 115.2, - 109.8, - 105, - 100.6, - 96.3, - 92.6, - 89.6, - 86.4, - 82.9, - 78.8, - 76.1, - 75.9, - 75.4, - 74.5, - 73.7, - 72.9, - 72.3, - 71.7, - 71, - 70.4, - 69.9, - 69.4, - 68.9, - 68.1, - 67.4, - 67.3, - 67, - 66.6, - 66.1, - 65.6, - 65.2, - 64.7, - 64.4, - 64, - 63.7, - 63.3, - 63.1, - 62.7, - 62.4, - 62.1, - 61.7, - 61.4, - 61.2, - 60.8, - 60.4, - 59.9, - 59.7, - 59.2, - 58.8, - 58.5, - 58.2, - 58, - 57.8, - 57.5, - 57.2, - 56.8, - 56.5, - 56.2, - 55.9, - 55.7, - 55.6, - 55.5, - 55.2, - 55, - 54.9, - 54.8, - 54.7, - 54.7, - 54.6, - 54.5, - 54.4, - 54.3, - 54.3, - 54.3, - 54.2, - 54.3, - 54.4, - 54.5, - 54.6, - 54.7, - 54.8, - 54.8, - 54.8, - 55, - 55.3, - 55.5, - 55.4, - 55.3, - 55.6, - 55.9, - 56.1, - 56.3, - 56.6, - 57.2, - 57.4, - 57.1, - 57, - 57.7, - 59.8, - 60.2, - 59.8, - 60.3, - 61.5, - 65.6, - 71, - 76.7, - 80.1, - 80.9, - 83.4, - 596.7, - 596.4, - 596.4, - 596.4, - 596.6, - 596.6, - 596.3, - 595.3, - 594.3, - 594, - 593.5, - 593.3, - 593.9, - 593.3, - 592.6, - 592.6, - 592.7, - 592.8, - 592.9, - 593.1, - 593.4, - 593.8, - 594.2, - 594.9, - 595.9, - 596.3, - 596.3, - 595.5, - 594.8, - 594.3, - 593.7, - 593.7, - 593.7, - 593.5, - 593.3, - 593.4, - 593.8, - 594.1, - 594.5, - 594.8, - 594.9, - 594.3, - 593.5, - 593.2, - 593.2, - 593.2, - 593.2, - 593.5, - 594, - 594.4, - 594.3, - 592.9, - 591, - 588.2, - 586.1, - 584.6, - 583, - 581.6, - 580.5, - 579.9, - 580.1, - 581, - 582.1, - 582.7, - 582.8, - 581.5, - 573.8, - 558.3, - 550.5, - 541.5, - 508.8, - 485, - 468.5, - 458.9, - 452.2, - 441.5, - 422.7, - 402.9, - 384, - 366.1, - 349.1, - 333, - 318.9, - 302.8, - 296.4, - 295, - 293.9, - 292.5, - 290.9, - 289.3, - 288.5, - 288.2, - 287.7, - 287.7, - 288.1, - 289.4, - 291.6, - 293.4, - 295.1, - 296.4, - 297.2, - 298.3, - 300.2, - 301.2, - 301.8, - 302.3, - 302.6, - 302.3, - 301.8, - 300.6, - 299.4, - 298.6, - 298.2, - 297.5, - 296.2, - 294.4, - 292.4, - 290.7, - 287.5, - 274.9, - 264.5, - 260.3, - 256.9, - 254.1, - 251.4, - 248.5, - 245.2, - 241.7, - 237.3, - 233.1, - 229.4, - 226.1, - 222.3, - 217.7, - 213.6, - 209.4, - 205.3, - 201.6, - 197, - 192.1, - 187, - 178, - 165.8, - 154.1, - 147.7, - 142.1, - 136.3, - 129.9, - 122.5, - 115.6, - 110.1, - 105, - 100.5, - 96.3, - 92.7, - 90, - 86.7, - 83.3, - 79.6, - 77.4, - 76.7, - 75.9, - 75.1, - 74.4, - 73.7, - 73, - 72.3, - 71.7, - 71.2, - 70.7, - 70.2, - 69.1, - 68.2, - 68.2, - 68.1, - 67.7, - 67.3, - 66.8, - 66.3, - 65.9, - 65.6, - 65.2, - 64.9, - 64.5, - 64.2, - 63.9, - 63.6, - 63.3, - 63, - 62.7, - 62.4, - 62, - 61.5, - 61.3, - 61, - 60.5, - 60.1, - 59.9, - 59.6, - 59.3, - 59.1, - 58.8, - 58.4, - 58.1, - 57.8, - 57.5, - 57.2, - 57, - 56.8, - 56.7, - 56.5, - 56.3, - 56.1, - 56, - 56, - 56, - 55.9, - 55.8, - 55.7, - 55.6, - 55.5, - 55.5, - 55.5, - 55.5, - 55.6, - 55.7, - 55.8, - 55.9, - 56, - 56, - 56, - 56.2, - 56.5, - 56.7, - 56.7, - 56.7, - 56.9, - 57.1, - 57.5, - 57.8, - 58.2, - 59, - 59.2, - 59.2, - 59.6, - 61.6, - 63.4, - 63.4, - 63, - 62.7, - 65.5, - 71.6, - 76, - 80.1, - 82.8, - 84, - 85.5, - 598.3, - 598.2, - 598.2, - 598.2, - 598.1, - 597.9, - 597.5, - 596.4, - 595.7, - 595.5, - 595.1, - 595.1, - 596.1, - 596.4, - 595.3, - 594.9, - 594.4, - 594.2, - 594.2, - 594.4, - 594.9, - 595.5, - 595.9, - 596.8, - 597.6, - 597.9, - 597.7, - 597.2, - 596.5, - 595.9, - 595.3, - 595.1, - 595, - 594.9, - 595.1, - 595.1, - 595.3, - 595.8, - 596.4, - 596.2, - 596, - 595.5, - 594.9, - 594.5, - 594.6, - 594.9, - 595, - 595.3, - 596, - 595.9, - 595, - 593.3, - 591.4, - 589.7, - 588.2, - 586.8, - 585.4, - 583.9, - 582.7, - 582.5, - 583.4, - 583.9, - 584.3, - 584.4, - 584.3, - 584.2, - 583, - 578.6, - 574.8, - 563.6, - 537.5, - 514.9, - 490.7, - 473.4, - 463.4, - 455.6, - 442.6, - 423.8, - 405.6, - 384.8, - 367.4, - 351.7, - 335, - 320.9, - 308.1, - 300.7, - 298.5, - 296.8, - 295.3, - 293.5, - 291.2, - 290.1, - 289.8, - 290, - 290.4, - 291.2, - 292.4, - 293.7, - 295.1, - 296.6, - 298, - 299.6, - 301.2, - 302.4, - 303.2, - 303.9, - 304.3, - 304.1, - 303.5, - 302.1, - 301.4, - 300.3, - 300, - 299.6, - 298.8, - 297.3, - 295.4, - 293.4, - 290.2, - 278.3, - 269.3, - 266, - 262.6, - 259.1, - 255.9, - 252.9, - 249.9, - 246.5, - 242.8, - 238.2, - 233.8, - 229.8, - 225.9, - 221.6, - 217.7, - 213.9, - 210.2, - 206.5, - 202.4, - 198.1, - 193.8, - 188.6, - 180.5, - 169.2, - 158, - 149.4, - 143.5, - 137.8, - 131.1, - 123.5, - 116.1, - 110, - 104.4, - 99.7, - 95.7, - 92.3, - 90, - 87.2, - 84, - 80.3, - 78.1, - 77.2, - 76.6, - 75.8, - 75.1, - 74.4, - 73.6, - 73, - 72.4, - 71.9, - 71.4, - 70.7, - 69.9, - 69.6, - 69.4, - 69, - 68.5, - 68.1, - 67.6, - 67.2, - 66.8, - 66.5, - 66.2, - 65.9, - 65.5, - 65.2, - 64.9, - 64.5, - 64.2, - 64, - 63.7, - 63.3, - 62.9, - 62.7, - 62.3, - 61.8, - 61.6, - 61.3, - 61, - 60.7, - 60.4, - 60, - 59.7, - 59.4, - 59.1, - 58.8, - 58.5, - 58.3, - 58.1, - 57.9, - 57.7, - 57.6, - 57.4, - 57.3, - 57.3, - 57.3, - 57.2, - 57.1, - 57, - 56.9, - 56.8, - 56.8, - 56.8, - 56.8, - 56.9, - 57, - 57.1, - 57.2, - 57.3, - 57.3, - 57.3, - 57.4, - 57.7, - 58.1, - 58.2, - 58.2, - 58.2, - 58.4, - 58.8, - 59.2, - 59.5, - 60.2, - 60.9, - 61.5, - 62.7, - 64.9, - 65.7, - 65.4, - 65.2, - 65.4, - 69.7, - 75, - 79.8, - 82.5, - 84.3, - 85.7, - 87, - 600, - 599.8, - 599.6, - 599.6, - 599.4, - 599, - 598.8, - 598, - 597.4, - 597, - 596.9, - 596.8, - 597.2, - 597.8, - 597.2, - 596.5, - 596, - 595.8, - 595.8, - 596.5, - 597, - 597.4, - 597.6, - 598.1, - 598.8, - 599.2, - 599.1, - 598.7, - 598.1, - 597.5, - 597, - 596.7, - 596.5, - 596.4, - 596.3, - 596.3, - 596.8, - 597.5, - 597.9, - 597.6, - 597.2, - 596.9, - 596.3, - 596.1, - 596.3, - 596.5, - 596.7, - 597.1, - 597.4, - 596.8, - 595.7, - 594.3, - 593.1, - 591.7, - 590.2, - 588.8, - 587.5, - 585.9, - 584.9, - 585.3, - 585.6, - 586, - 586, - 585.7, - 586, - 586.1, - 585.6, - 584.3, - 581.1, - 572.8, - 554.1, - 535.6, - 515.8, - 494.7, - 480.3, - 469.3, - 459.8, - 445.4, - 427.1, - 405.6, - 384.1, - 367.6, - 351.1, - 334.3, - 325.2, - 312.7, - 303, - 300.4, - 299.4, - 298.4, - 296.3, - 292.9, - 291.7, - 291.8, - 292.1, - 292.8, - 293.8, - 294.9, - 296.2, - 297.4, - 298.8, - 300.2, - 301.8, - 303.4, - 304.5, - 305.3, - 305.7, - 305.5, - 305, - 304, - 303.5, - 302.4, - 301.8, - 301.7, - 301.1, - 299.6, - 297.9, - 295.9, - 293.4, - 282.2, - 273.2, - 270.4, - 267.9, - 264.6, - 260.8, - 257.3, - 254.3, - 251, - 247.7, - 243.7, - 238.9, - 234.3, - 230.3, - 225.6, - 221.6, - 217.9, - 214.4, - 210.6, - 206.7, - 203, - 199.1, - 194.8, - 190, - 184, - 172.1, - 160.6, - 150.9, - 144.9, - 139.2, - 132.3, - 124.6, - 117.1, - 110.4, - 104.9, - 100, - 96.4, - 93.9, - 90.8, - 87.4, - 83.9, - 80.6, - 78.7, - 77.9, - 77.2, - 76.5, - 75.8, - 75, - 74.3, - 73.7, - 73.2, - 72.7, - 72.2, - 71.6, - 71.1, - 70.7, - 70.2, - 69.7, - 69.3, - 68.8, - 68.4, - 68.1, - 67.8, - 67.5, - 67.1, - 66.8, - 66.5, - 66.1, - 65.8, - 65.5, - 65.3, - 65, - 64.6, - 64.4, - 64.2, - 63.6, - 63.2, - 63, - 62.8, - 62.4, - 62, - 61.7, - 61.4, - 61, - 60.7, - 60.4, - 60.1, - 59.9, - 59.6, - 59.4, - 59.2, - 59, - 58.9, - 58.8, - 58.7, - 58.6, - 58.6, - 58.5, - 58.3, - 58.2, - 58.1, - 58.1, - 58.1, - 58.1, - 58.1, - 58.2, - 58.3, - 58.5, - 58.6, - 58.6, - 58.7, - 58.6, - 58.7, - 59.1, - 59.4, - 59.7, - 59.7, - 59.6, - 59.8, - 60.2, - 60.6, - 60.9, - 61.9, - 63.2, - 64.5, - 65.8, - 66.9, - 67.6, - 67.5, - 67.3, - 67.9, - 72.4, - 76.9, - 81.2, - 83.6, - 84.9, - 86.6, - 88.3, - 601.6, - 601.2, - 601, - 601, - 600.8, - 600.4, - 600.2, - 599.6, - 599.1, - 598.8, - 598.4, - 598.2, - 598.3, - 598.9, - 598.7, - 598.3, - 598, - 597.6, - 597.6, - 598.2, - 598.6, - 598.8, - 599.1, - 599.6, - 600, - 600.3, - 600.5, - 600.2, - 599.6, - 599, - 598.5, - 598.3, - 598, - 597.8, - 598, - 598.3, - 598.7, - 599.2, - 599.3, - 598.9, - 599, - 598.5, - 598.2, - 598.2, - 598.2, - 598.1, - 598.5, - 599, - 598.8, - 597.6, - 596.3, - 595.3, - 594.5, - 593.4, - 592, - 590.4, - 589.3, - 587.9, - 587.5, - 587.6, - 587.8, - 587.8, - 587.4, - 587.7, - 588.2, - 588.1, - 587.6, - 587.1, - 584.8, - 578.4, - 565.5, - 552.8, - 537.3, - 516.4, - 498.6, - 487.7, - 477, - 464.8, - 450.6, - 431.6, - 406.5, - 386.5, - 367.9, - 351.3, - 335.8, - 326.7, - 317.5, - 307.2, - 303.2, - 302.4, - 301.2, - 298.7, - 294.6, - 293.5, - 293.6, - 294.2, - 295.2, - 296.5, - 297.6, - 298.4, - 299.7, - 301.2, - 302.7, - 304.2, - 305.6, - 306.5, - 306.9, - 306.9, - 306.5, - 305.9, - 305.5, - 304.4, - 304, - 304.1, - 303.6, - 302, - 300.2, - 298.1, - 295.7, - 286.5, - 278, - 274.7, - 272.3, - 269.4, - 266.2, - 262.8, - 259.1, - 255.6, - 252.2, - 248.4, - 244, - 239, - 234.6, - 229.8, - 225.3, - 221.6, - 218, - 214.5, - 210.7, - 207, - 203.5, - 200.1, - 196.6, - 192.1, - 184.5, - 174.2, - 163.4, - 152.8, - 146.5, - 140.5, - 133.6, - 126.2, - 117.9, - 110.8, - 106, - 101.4, - 97.9, - 95, - 91.2, - 88, - 84.6, - 81.3, - 79.4, - 78.7, - 78, - 77.3, - 76.5, - 75.8, - 75.1, - 74.5, - 74, - 73.4, - 72.9, - 72.4, - 71.9, - 71.4, - 71, - 70.6, - 70.2, - 69.8, - 69.4, - 69.1, - 68.7, - 68.4, - 68.1, - 67.8, - 67.4, - 67.1, - 66.9, - 66.6, - 66.3, - 66, - 65.8, - 65.5, - 65, - 64.6, - 64.4, - 64.1, - 63.8, - 63.4, - 63.1, - 62.8, - 62.4, - 62.1, - 61.8, - 61.5, - 61.2, - 61, - 60.8, - 60.6, - 60.5, - 60.3, - 60.2, - 60.1, - 60, - 59.9, - 59.8, - 59.6, - 59.5, - 59.4, - 59.4, - 59.4, - 59.4, - 59.4, - 59.5, - 59.6, - 59.8, - 59.9, - 59.9, - 60, - 60, - 60, - 60.3, - 60.7, - 61, - 61.1, - 61.1, - 61.2, - 61.6, - 62.1, - 62.4, - 63.3, - 64.9, - 66.1, - 67.1, - 68.1, - 68.9, - 69.3, - 69.4, - 70.9, - 75.5, - 79.8, - 82.9, - 84.6, - 85.7, - 87.2, - 88.9, - 603.1, - 602.6, - 602.2, - 602.4, - 602.3, - 602, - 601.8, - 601.6, - 600.9, - 600.5, - 600.1, - 600.3, - 600.4, - 600.4, - 600.2, - 600, - 599.7, - 599.2, - 599.2, - 599.4, - 599.6, - 599.9, - 600.4, - 601.2, - 601.6, - 601.5, - 601.7, - 601.3, - 600.8, - 600.5, - 600.3, - 600.2, - 600.2, - 600.4, - 601.3, - 601.5, - 601.1, - 601.2, - 600.9, - 600.7, - 600.6, - 600.4, - 600, - 600, - 600.1, - 600.4, - 600.5, - 600.3, - 600, - 598.8, - 597.3, - 596.3, - 595.3, - 594.3, - 593.1, - 591.8, - 591.1, - 590.4, - 590, - 589.8, - 589.5, - 589.2, - 589.4, - 589.9, - 590.2, - 590.4, - 589.8, - 588.9, - 586.8, - 581, - 572.9, - 566.5, - 553.8, - 537.6, - 516.7, - 500.9, - 490.3, - 480.5, - 470.6, - 454.9, - 432.6, - 413.7, - 394, - 372.2, - 353.6, - 338.3, - 330.8, - 322.6, - 313.5, - 306.5, - 304.5, - 303.5, - 300.8, - 297.2, - 295.9, - 296, - 296.8, - 297.9, - 298.9, - 299.7, - 300.7, - 302.1, - 303.6, - 305.1, - 306.5, - 307.5, - 308, - 308, - 308.2, - 307.9, - 307.5, - 306.6, - 306, - 306.1, - 306, - 305.1, - 302.8, - 300.2, - 297.1, - 290, - 283.3, - 280.2, - 277.7, - 274.4, - 271.1, - 268, - 264.4, - 260.6, - 256.9, - 253, - 248.8, - 244.3, - 239.5, - 234.5, - 229.7, - 225.9, - 221.9, - 218, - 214.6, - 211, - 207.7, - 204.5, - 201.2, - 198, - 193.8, - 187.8, - 180.6, - 171.5, - 159.4, - 148.5, - 141.8, - 134.9, - 127.3, - 117.8, - 111.3, - 106.8, - 102.1, - 98.4, - 94.6, - 91.2, - 88.8, - 86.1, - 82.6, - 80.2, - 79.4, - 78.8, - 78, - 77.3, - 76.6, - 76, - 75.4, - 74.8, - 74.2, - 73.7, - 73.2, - 72.7, - 72.3, - 71.9, - 71.5, - 71.1, - 70.8, - 70.4, - 70.1, - 69.8, - 69.4, - 69, - 68.7, - 68.4, - 68.2, - 67.9, - 67.7, - 67.4, - 67.2, - 66.9, - 66.4, - 66, - 65.7, - 65.5, - 65.2, - 64.9, - 64.6, - 64.2, - 63.8, - 63.4, - 63.1, - 62.8, - 62.6, - 62.4, - 62.2, - 62.1, - 61.9, - 61.8, - 61.6, - 61.6, - 61.5, - 61.4, - 61.2, - 61, - 60.9, - 60.8, - 60.7, - 60.7, - 60.7, - 60.8, - 60.9, - 61, - 61.1, - 61.2, - 61.3, - 61.4, - 61.4, - 61.4, - 61.6, - 62, - 62.3, - 62.4, - 62.4, - 62.7, - 63.1, - 63.6, - 63.8, - 64.3, - 65.6, - 66.8, - 67.9, - 68.9, - 69.9, - 70.9, - 71.4, - 74, - 79, - 82.2, - 83.9, - 85.6, - 87, - 88, - 89.6, - 604.7, - 604.3, - 604.1, - 603.9, - 603.7, - 603.5, - 603.3, - 603.1, - 602.4, - 601.8, - 601.9, - 602.5, - 602.2, - 602, - 601.7, - 601.4, - 601.1, - 601, - 600.9, - 600.9, - 601, - 601.4, - 602.2, - 602.8, - 602.9, - 602.7, - 602.8, - 602.6, - 602.1, - 602.2, - 602.3, - 602.7, - 603.3, - 603.7, - 604.3, - 604.2, - 603.6, - 603.3, - 602.8, - 602.4, - 602.2, - 602, - 601.9, - 602, - 602.1, - 602.1, - 602, - 601.5, - 600.9, - 600, - 598.7, - 597.7, - 596.6, - 595.3, - 594.1, - 593.2, - 592.8, - 592.4, - 592, - 591.5, - 590.8, - 590.8, - 591.2, - 591.5, - 591.8, - 591.7, - 591.3, - 590.1, - 587.1, - 582.8, - 577.7, - 573.1, - 565.8, - 556.6, - 539.5, - 515.8, - 500.8, - 492.5, - 484.8, - 474.4, - 456.7, - 437.8, - 419.6, - 399.7, - 376, - 353.3, - 338.4, - 332.5, - 327.3, - 318.7, - 310.6, - 306.9, - 305.7, - 304.2, - 302.1, - 300, - 299.1, - 299.4, - 300.3, - 301.2, - 302, - 303.3, - 304.7, - 306.1, - 307.5, - 308.6, - 309.2, - 309.4, - 309.6, - 309.8, - 309.4, - 309.1, - 308.4, - 308.5, - 309, - 309.1, - 306.7, - 303.4, - 300.3, - 295.6, - 289.5, - 285.4, - 282.9, - 279.6, - 276, - 272.7, - 269.4, - 265.3, - 261.2, - 257.4, - 253.2, - 249.2, - 244.9, - 239.6, - 234.4, - 230.5, - 226.4, - 222, - 218.3, - 214.8, - 211.2, - 208.3, - 205, - 202, - 199.2, - 195.6, - 190.9, - 185.4, - 179.1, - 166.6, - 151.6, - 143.3, - 137, - 128.6, - 119, - 112.2, - 107.3, - 103.1, - 99.2, - 95.2, - 90.9, - 89.3, - 87.2, - 83.8, - 81.2, - 80, - 79.5, - 78.8, - 78.1, - 77.5, - 76.9, - 76.2, - 75.5, - 75, - 74.6, - 74.1, - 73.6, - 73.2, - 72.8, - 72.5, - 72.1, - 71.7, - 71.5, - 71.2, - 70.8, - 70.4, - 70.1, - 69.8, - 69.5, - 69.3, - 69.1, - 68.9, - 68.6, - 68.3, - 67.9, - 67.4, - 67.2, - 66.9, - 66.6, - 66.4, - 66.1, - 65.7, - 65.3, - 64.9, - 64.5, - 64.2, - 64, - 63.9, - 63.8, - 63.7, - 63.5, - 63.3, - 63.1, - 63, - 63, - 62.8, - 62.7, - 62.5, - 62.3, - 62.1, - 62, - 62, - 62, - 62.1, - 62.2, - 62.4, - 62.5, - 62.6, - 62.8, - 62.8, - 62.9, - 62.9, - 63.1, - 63.5, - 63.8, - 63.9, - 63.9, - 64.2, - 64.7, - 65.1, - 65.2, - 65.6, - 66.4, - 67.3, - 68.5, - 69.7, - 70.8, - 72.1, - 73.3, - 77.4, - 81.8, - 83.3, - 84.3, - 85.8, - 87.9, - 89, - 90.3, - 606.2, - 606.3, - 605.9, - 605.4, - 604.9, - 604.6, - 604.6, - 604.3, - 603.7, - 603.2, - 603.3, - 603.8, - 603.7, - 603.7, - 603.4, - 603.2, - 602.5, - 602.4, - 602.6, - 602.7, - 602.7, - 603.5, - 604.2, - 604.3, - 604.1, - 604.2, - 604.8, - 604.9, - 604, - 603.9, - 604.5, - 605.5, - 605.9, - 606.4, - 606.1, - 605.6, - 605.5, - 605.2, - 604.6, - 604.3, - 604.2, - 603.9, - 603.8, - 604, - 603.8, - 603.6, - 603.4, - 602.7, - 601.9, - 600.6, - 599.6, - 598.6, - 597.6, - 596.4, - 595.3, - 594.6, - 594.4, - 594, - 593.4, - 592.9, - 592.5, - 592.6, - 592.8, - 593.3, - 593.3, - 593.1, - 592.3, - 590.6, - 588.1, - 585.3, - 582.6, - 578.7, - 572.5, - 564.5, - 555.6, - 538.3, - 513.2, - 500.7, - 494.1, - 487.2, - 476.3, - 462.5, - 443.8, - 425.2, - 404.9, - 378.6, - 354.7, - 343.9, - 337.2, - 329.1, - 321.3, - 314.2, - 309.6, - 307.8, - 307.1, - 305.5, - 303.2, - 302.2, - 302, - 302.5, - 303.4, - 304.6, - 306, - 307.3, - 308.7, - 309.9, - 310.6, - 310.8, - 311.1, - 311.3, - 311.2, - 311.3, - 311.1, - 311.5, - 312.6, - 312.8, - 310.5, - 307.2, - 303.9, - 301.3, - 296.9, - 291, - 287.8, - 284.8, - 280.9, - 277.5, - 274.5, - 271.1, - 265.5, - 261.5, - 257.7, - 253.6, - 249.7, - 245.3, - 239.4, - 234.7, - 230.8, - 226.4, - 222.3, - 218.4, - 214.7, - 211.9, - 209, - 206.5, - 204, - 200.4, - 196.5, - 192.8, - 188.9, - 183.9, - 173.1, - 153.4, - 145.1, - 138.6, - 130.5, - 121.5, - 113.8, - 108.3, - 104, - 99.8, - 95.1, - 91.4, - 89.7, - 88.2, - 84.8, - 81.8, - 80.5, - 80.2, - 79.6, - 79, - 78.4, - 77.8, - 77.2, - 76.6, - 76.1, - 75.5, - 75.1, - 74.6, - 74.2, - 73.8, - 73.4, - 73.1, - 72.7, - 72.5, - 72.1, - 71.8, - 71.5, - 71.4, - 71.1, - 70.8, - 70.6, - 70.4, - 70.1, - 69.8, - 69.4, - 69, - 68.8, - 68.5, - 68.2, - 67.9, - 67.7, - 67.3, - 66.9, - 66.4, - 66, - 65.8, - 65.6, - 65.5, - 65.4, - 65.2, - 65.1, - 64.9, - 64.8, - 64.6, - 64.5, - 64.4, - 64.2, - 64, - 63.7, - 63.5, - 63.4, - 63.3, - 63.3, - 63.3, - 63.5, - 63.7, - 63.9, - 64.1, - 64.2, - 64.3, - 64.3, - 64.3, - 64.5, - 64.9, - 65.2, - 65.4, - 65.4, - 65.8, - 66.2, - 66.5, - 66.8, - 67.1, - 67.6, - 68.2, - 69.1, - 70.4, - 71.7, - 73.1, - 74.9, - 79.5, - 82.5, - 83.8, - 84.8, - 86.1, - 88.1, - 90.1, - 91.4, - 607.5, - 607.5, - 607.1, - 606.6, - 606.2, - 605.9, - 605.8, - 605.5, - 605.2, - 604.9, - 604.9, - 605.2, - 605.5, - 605.8, - 605.9, - 605.6, - 604.7, - 604.3, - 604.4, - 604.7, - 604.9, - 605.6, - 605.8, - 605.5, - 605.6, - 606.3, - 607.2, - 607.5, - 606.5, - 606.4, - 607.3, - 607.9, - 608, - 608.2, - 608, - 607.6, - 607.3, - 606.9, - 606.4, - 606.3, - 606.1, - 606.1, - 606, - 605.7, - 605.6, - 605.3, - 604.9, - 604, - 603.3, - 601.8, - 600.4, - 599.3, - 598.4, - 597.7, - 597, - 596.3, - 595.8, - 595.3, - 594.7, - 594.8, - 594.8, - 594.4, - 595.1, - 596.1, - 595.6, - 594.6, - 593.2, - 591.6, - 589.9, - 588.2, - 585.8, - 582.6, - 576.4, - 567.8, - 563.7, - 552.4, - 533.5, - 509.7, - 500.5, - 495.2, - 489, - 481, - 467.6, - 450, - 432.2, - 410.7, - 384.4, - 362.8, - 350.6, - 339.7, - 331.8, - 323.6, - 316.8, - 312.9, - 311, - 309.2, - 308, - 306.6, - 305.3, - 304.3, - 304.9, - 306, - 307.3, - 308.6, - 310, - 311.3, - 312, - 312.2, - 312.5, - 312.6, - 312.7, - 312.9, - 313.4, - 314.4, - 315.6, - 316.2, - 314.8, - 311.3, - 307.9, - 304.6, - 300.9, - 297.4, - 293.8, - 290.1, - 286.2, - 282.4, - 279.3, - 275.6, - 270.8, - 266.1, - 262.2, - 257.9, - 253.4, - 249.9, - 245, - 239.6, - 235.3, - 230.4, - 226.1, - 222.3, - 218.5, - 215.3, - 212.7, - 210, - 207.9, - 204.7, - 201, - 197.4, - 193.9, - 190.2, - 185.1, - 170.4, - 155.8, - 147, - 140.5, - 132.8, - 123.8, - 116.4, - 110, - 104.4, - 100.3, - 96.3, - 92.4, - 90.4, - 88.9, - 86.8, - 84.5, - 82.1, - 81, - 80.6, - 80, - 79.4, - 78.8, - 78.2, - 77.7, - 77.1, - 76.6, - 76.2, - 75.7, - 75.3, - 74.9, - 74.4, - 74.1, - 73.8, - 73.5, - 73.2, - 73, - 72.9, - 72.7, - 72.4, - 72.1, - 72, - 71.7, - 71.4, - 70.9, - 70.5, - 70.3, - 70.1, - 69.9, - 69.6, - 69.2, - 68.8, - 68.4, - 68, - 67.7, - 67.5, - 67.3, - 67.1, - 67, - 66.9, - 66.7, - 66.5, - 66.4, - 66.3, - 66.1, - 66, - 65.8, - 65.6, - 65.2, - 65, - 64.9, - 64.8, - 64.7, - 64.7, - 64.8, - 65, - 65.2, - 65.4, - 65.6, - 65.7, - 65.8, - 65.9, - 66, - 66.2, - 66.5, - 66.8, - 66.9, - 67.2, - 67.6, - 67.9, - 68.3, - 68.6, - 68.9, - 69.4, - 70.1, - 71.2, - 72.5, - 74, - 76.1, - 80.1, - 82.6, - 84, - 85.3, - 87, - 88.9, - 91, - 92.8, - 608.4, - 608.2, - 607.9, - 607.7, - 607.4, - 607, - 607, - 607.2, - 607, - 606.6, - 606.4, - 606.6, - 607, - 607.4, - 608, - 608, - 607.2, - 606.6, - 606.4, - 606.6, - 606.7, - 606.9, - 607, - 607, - 607.9, - 608.8, - 609, - 608.9, - 608.5, - 608.5, - 609.6, - 610.1, - 610.5, - 610.6, - 610.4, - 610.1, - 609, - 608.2, - 608, - 608.2, - 608.3, - 608, - 607.7, - 607.4, - 607.4, - 607, - 606.3, - 605.3, - 604.6, - 603.3, - 601.5, - 600.3, - 599.5, - 599, - 598.6, - 598, - 597.3, - 596.7, - 596.3, - 596.4, - 596.4, - 596.4, - 597.6, - 598.4, - 597.7, - 596.1, - 594.2, - 593, - 591.7, - 590.6, - 588.6, - 585.4, - 580.5, - 573.5, - 571.2, - 560, - 545.1, - 522.9, - 509.6, - 500, - 496.4, - 491.8, - 484.2, - 471.9, - 455.5, - 438.5, - 413.6, - 393.2, - 370.9, - 353.6, - 344.6, - 335.9, - 325.3, - 319.3, - 316.8, - 314.3, - 311.4, - 309.7, - 308.4, - 307.2, - 307.2, - 308, - 308.9, - 310.1, - 311.4, - 312.7, - 313.5, - 313.8, - 314, - 314, - 314.2, - 314.7, - 315.8, - 316.9, - 317.9, - 319.2, - 318.3, - 315.3, - 311.6, - 308.5, - 305.1, - 303.4, - 300.7, - 296.2, - 291.6, - 287.7, - 283.8, - 280.4, - 276.7, - 272.1, - 267.6, - 262.6, - 257.7, - 254.3, - 250.5, - 245.2, - 240.6, - 235.2, - 230.2, - 226.2, - 222.7, - 218.9, - 215.7, - 213, - 210.6, - 208.3, - 205, - 201.5, - 198, - 194.4, - 190.7, - 185.4, - 177.6, - 162.9, - 150.8, - 143.7, - 135.6, - 126.3, - 118.7, - 111.9, - 106.1, - 101.2, - 96.7, - 94.1, - 93.2, - 92, - 89.9, - 86.6, - 83.4, - 82.2, - 81.6, - 81, - 80.5, - 79.9, - 79.3, - 78.7, - 78.2, - 77.7, - 77.3, - 76.9, - 76.4, - 76, - 75.6, - 75.3, - 74.9, - 74.6, - 74.5, - 74.5, - 74.4, - 74.2, - 73.9, - 73.7, - 73.3, - 72.9, - 72.4, - 72.1, - 71.9, - 71.7, - 71.5, - 71.2, - 70.8, - 70.3, - 70, - 69.6, - 69.3, - 69.1, - 69, - 68.8, - 68.7, - 68.6, - 68.4, - 68.2, - 68.1, - 67.9, - 67.7, - 67.6, - 67.5, - 67.2, - 66.9, - 66.7, - 66.6, - 66.5, - 66.4, - 66.3, - 66.4, - 66.5, - 67.5, - 67.5, - 67, - 67.2, - 67.3, - 67.4, - 67.6, - 67.8, - 68, - 68.2, - 68.3, - 68.5, - 68.8, - 69.2, - 69.7, - 70.1, - 70.4, - 70.9, - 71.4, - 72.3, - 73.4, - 74.9, - 77.4, - 80.8, - 83, - 84.5, - 85.7, - 87.5, - 89.9, - 91.9, - 93.9, - 609.5, - 609.5, - 609.8, - 609.4, - 609.1, - 608.7, - 608.5, - 608.7, - 608.7, - 608.2, - 607.8, - 607.7, - 608.1, - 608.7, - 609.7, - 610.2, - 609, - 608, - 608, - 608.2, - 608.2, - 608.4, - 608.9, - 609.1, - 610, - 610.4, - 610.5, - 610.5, - 610.3, - 610.8, - 611.6, - 612.3, - 612.7, - 612.6, - 612.2, - 611.3, - 610.5, - 610.1, - 610.1, - 610.1, - 609.9, - 609.6, - 609.5, - 609, - 608.6, - 608, - 607.3, - 606.4, - 605.5, - 604.2, - 602.8, - 601.7, - 600.7, - 600.2, - 599.9, - 599.3, - 598.8, - 598.4, - 598, - 598, - 598.6, - 599.5, - 599.8, - 599.6, - 598.5, - 596.8, - 595.4, - 594.5, - 593.5, - 592.1, - 590.1, - 586.5, - 583.1, - 579.4, - 576.7, - 567, - 554.1, - 539.1, - 522.1, - 508.5, - 501.4, - 498.4, - 493.9, - 487.4, - 476.7, - 461.8, - 443, - 424.4, - 399.1, - 373.9, - 357.3, - 347, - 334.4, - 326.5, - 322.3, - 320.1, - 317, - 313.8, - 311.2, - 310.1, - 310, - 310.4, - 310.9, - 311.9, - 313, - 314.3, - 315.2, - 315.6, - 315.7, - 315.7, - 316, - 317.1, - 318.5, - 319.1, - 320.1, - 321.1, - 320.9, - 319.5, - 316.5, - 313.1, - 309.6, - 308.7, - 306.6, - 302.6, - 298.2, - 294.1, - 289.6, - 284.9, - 281.4, - 277.9, - 273.4, - 268.7, - 263.4, - 258.9, - 255.2, - 250.9, - 245.7, - 240.4, - 235, - 230.4, - 226.5, - 222.6, - 218.9, - 215.9, - 213.3, - 211.1, - 209, - 205.7, - 202.2, - 198.8, - 194.8, - 190.6, - 187.9, - 182.2, - 173, - 162.9, - 152.2, - 138.8, - 128.3, - 121.3, - 115.3, - 109.3, - 103.2, - 98.5, - 96.4, - 95.4, - 93.9, - 91.4, - 87.3, - 84.6, - 83.2, - 82.5, - 82.1, - 81.5, - 80.8, - 80.2, - 79.8, - 79.3, - 78.9, - 78.4, - 77.9, - 77.5, - 77, - 76.7, - 76.4, - 76.1, - 76.1, - 76.1, - 76.1, - 75.9, - 75.6, - 75.3, - 74.9, - 74.4, - 74, - 73.7, - 73.5, - 73.4, - 73.3, - 72.9, - 72.6, - 72.1, - 71.7, - 71.3, - 71, - 70.9, - 70.8, - 70.6, - 70.5, - 70.4, - 70.2, - 70, - 69.9, - 69.7, - 69.7, - 69.6, - 69.3, - 68.9, - 68.7, - 68.5, - 68.4, - 68.2, - 68.2, - 68.6, - 68.5, - 69.2, - 71.1, - 71.1, - 69.6, - 68.7, - 68.7, - 68.8, - 69, - 69.3, - 69.5, - 69.7, - 69.8, - 69.8, - 70.1, - 70.7, - 71.2, - 71.6, - 71.9, - 72.4, - 73, - 73.6, - 74.6, - 75.9, - 78.3, - 81.7, - 84, - 85.5, - 87, - 88.7, - 90.7, - 93, - 95.1, - 611.2, - 611.6, - 611.9, - 611.3, - 610.8, - 610.5, - 610.3, - 610.1, - 610, - 609.6, - 609.4, - 609.4, - 609.6, - 610.2, - 611.2, - 611.5, - 610.6, - 610, - 609.9, - 609.8, - 610, - 611, - 612, - 611.7, - 611.6, - 612.4, - 612.8, - 612.4, - 612.6, - 613.1, - 613.7, - 614.3, - 614.3, - 614.1, - 614, - 613.5, - 613, - 612.6, - 612.5, - 611.8, - 611.4, - 611.3, - 611.2, - 610.6, - 609.9, - 609, - 608.4, - 607.6, - 606.7, - 605.5, - 604.1, - 603.2, - 602.3, - 601.6, - 601.3, - 600.8, - 600.3, - 600.3, - 600.1, - 600.1, - 601.2, - 601.9, - 601.5, - 600.5, - 599.2, - 597.8, - 596.8, - 596.3, - 595.2, - 593.2, - 591, - 588, - 584.8, - 582.5, - 577.9, - 569.7, - 560.2, - 550.2, - 535.1, - 521.7, - 510.2, - 503.5, - 499.5, - 496, - 490.5, - 481.7, - 467.6, - 451.5, - 430.5, - 404.2, - 379.6, - 360.9, - 345.5, - 335, - 327.5, - 325.9, - 323.4, - 320, - 316.5, - 313.8, - 312.3, - 312, - 312.6, - 313.6, - 314.8, - 315.9, - 317, - 317.7, - 317.8, - 317.8, - 318.2, - 319.3, - 320.6, - 321.2, - 321.9, - 322.6, - 322.9, - 322.6, - 321.2, - 319.2, - 315.7, - 313.3, - 311.2, - 307.9, - 303.8, - 299.6, - 296.1, - 291.1, - 286.2, - 282.5, - 279.1, - 274.7, - 270.2, - 265.1, - 260.1, - 256.1, - 251.3, - 245.9, - 240.2, - 234.9, - 230.5, - 226.3, - 222.1, - 218.6, - 216.1, - 213.8, - 211.8, - 209.4, - 206.2, - 202.7, - 198.7, - 195, - 192.3, - 189.2, - 185.1, - 180.2, - 173.6, - 161.3, - 148.3, - 133.1, - 124.4, - 118.7, - 112.4, - 106.3, - 100.7, - 97.2, - 95.5, - 93.7, - 90.9, - 88, - 86.1, - 84.5, - 83.8, - 83.2, - 82.5, - 81.7, - 81.2, - 80.9, - 80.5, - 80.1, - 79.7, - 79.2, - 78.6, - 78.1, - 77.9, - 77.8, - 77.8, - 77.9, - 77.8, - 77.6, - 77.3, - 77, - 76.6, - 76.1, - 75.8, - 75.5, - 75.3, - 75.2, - 75, - 74.7, - 74.4, - 73.9, - 73.5, - 73.1, - 72.8, - 72.7, - 72.6, - 72.4, - 72.3, - 72.2, - 72.1, - 71.9, - 71.8, - 71.8, - 72.4, - 72.4, - 71.6, - 71, - 71, - 70.7, - 70.3, - 69.9, - 70.7, - 72, - 73.1, - 73.4, - 74.4, - 74.8, - 73, - 71.2, - 70.3, - 70.2, - 70.4, - 70.6, - 70.8, - 71.1, - 71.2, - 71.2, - 71.6, - 72.2, - 72.6, - 73, - 73.4, - 73.9, - 74.5, - 75.1, - 76, - 77, - 79, - 82.5, - 84.6, - 86.6, - 88.2, - 89.9, - 91.7, - 94, - 95.7, - 613.4, - 613.7, - 613.3, - 612.7, - 612.3, - 612, - 611.9, - 611.5, - 611.2, - 611, - 611.2, - 611.4, - 611.7, - 612, - 612.3, - 612.6, - 612.8, - 612.9, - 612.9, - 612.9, - 613.4, - 614.1, - 614.9, - 614.9, - 614.2, - 614.7, - 614.7, - 614.6, - 614.9, - 615.5, - 615.7, - 615.5, - 615.8, - 616.1, - 615.7, - 615.3, - 614.9, - 614.5, - 614.2, - 613.4, - 613.3, - 612.9, - 612.6, - 612, - 611.5, - 610.3, - 609.4, - 608.9, - 608.1, - 607.1, - 606.2, - 605.2, - 604, - 603.1, - 602.7, - 602.3, - 602, - 602, - 602.4, - 602.5, - 603.2, - 603.3, - 602.7, - 601.6, - 600.2, - 599.4, - 598.6, - 597.7, - 596.3, - 593.9, - 591.5, - 589.1, - 586.4, - 584.1, - 576.9, - 569.3, - 564.2, - 556.7, - 545.9, - 534.4, - 521.9, - 512, - 504.7, - 500.6, - 497.2, - 492.4, - 484.8, - 474, - 458, - 440.2, - 413.1, - 385.7, - 364.7, - 349.4, - 336.4, - 332, - 330.2, - 327, - 323.4, - 319.9, - 317, - 315, - 314.7, - 315.4, - 316.5, - 317.6, - 318.7, - 319.7, - 320.1, - 319.9, - 319.9, - 320.6, - 321.8, - 322.6, - 323.2, - 324.1, - 324.5, - 324.3, - 323.3, - 322.6, - 320.5, - 317.6, - 315.1, - 312.4, - 309, - 304.7, - 300.8, - 297.1, - 292.3, - 287.5, - 284, - 280.7, - 276.1, - 271.7, - 266.7, - 261.3, - 256.7, - 251.5, - 246.4, - 240.5, - 235, - 230.8, - 226.7, - 222.3, - 218.8, - 216.3, - 214.3, - 212.2, - 209.4, - 205.9, - 201.9, - 199.6, - 196.8, - 194, - 190.7, - 186.5, - 181.8, - 176.6, - 168.8, - 155.5, - 138.8, - 128.2, - 121.4, - 115.4, - 109.2, - 103.4, - 98.4, - 95.1, - 93.1, - 90.9, - 88.9, - 87.2, - 85.6, - 85, - 84.4, - 83.6, - 83, - 82.7, - 82.3, - 81.8, - 81.4, - 81, - 80.4, - 79.9, - 79.7, - 79.7, - 79.6, - 79.7, - 79.6, - 79.4, - 79.1, - 78.8, - 78.4, - 78, - 77.7, - 77.4, - 77.2, - 77, - 76.8, - 76.5, - 76.1, - 75.7, - 75.3, - 74.9, - 74.7, - 74.5, - 74.4, - 74.3, - 74.2, - 74.1, - 73.9, - 73.8, - 73.7, - 73.8, - 74.2, - 74.4, - 74.1, - 73.6, - 73.7, - 73.1, - 72.5, - 72.8, - 74.4, - 75.7, - 76.9, - 77.9, - 78.9, - 78.1, - 76.2, - 74.2, - 72.6, - 71.6, - 71.8, - 72, - 72.2, - 72.5, - 72.7, - 72.6, - 73, - 73.6, - 73.9, - 74.4, - 74.9, - 75.4, - 76, - 76.6, - 77.4, - 78.3, - 79.9, - 82.8, - 84.9, - 87.4, - 89.4, - 91, - 92.7, - 94.7, - 96.4, - 614.7, - 614.8, - 614.5, - 614.6, - 615.2, - 614.3, - 613.6, - 612.8, - 612.5, - 612.4, - 612.5, - 613.1, - 613.2, - 613.3, - 613.5, - 614, - 614.8, - 614.7, - 615, - 615.9, - 616.7, - 616.8, - 616.8, - 616.8, - 616.7, - 616.6, - 616.3, - 616.3, - 616.6, - 616.9, - 616.7, - 616.7, - 617.3, - 617.5, - 617.1, - 616.6, - 616.5, - 616.3, - 616, - 615.1, - 614.8, - 614.5, - 613.9, - 613.3, - 612.9, - 612, - 610.9, - 610.1, - 609.2, - 608.5, - 607.9, - 606.9, - 605.8, - 604.8, - 604.2, - 604, - 603.8, - 604, - 604.5, - 604.5, - 604.9, - 604.5, - 603.7, - 602.8, - 601.8, - 601.1, - 600.5, - 599.2, - 596.7, - 594.4, - 592.2, - 589.8, - 587.7, - 585.3, - 577.6, - 570.2, - 566.5, - 562.4, - 554, - 544.9, - 534.8, - 522, - 512.1, - 505.9, - 501.4, - 496.9, - 492.6, - 487.4, - 477.4, - 463.7, - 449.5, - 417.9, - 389.9, - 374.4, - 353, - 338.9, - 336.2, - 334.1, - 331.4, - 326.9, - 322.5, - 320, - 318.1, - 317.9, - 318.6, - 319.5, - 320.7, - 321.8, - 322.3, - 322.4, - 322.3, - 322.4, - 323, - 323.8, - 324.5, - 325.5, - 326.2, - 326.4, - 325.6, - 325.2, - 323.6, - 320.7, - 317.8, - 315.4, - 312.7, - 309.9, - 305.8, - 302, - 298.3, - 293.9, - 289.2, - 285.9, - 282.1, - 277.4, - 273.2, - 268.1, - 262.3, - 257.2, - 252.3, - 246.4, - 240.5, - 235.4, - 231.2, - 227.1, - 222.8, - 219.2, - 216.6, - 214.8, - 212, - 209.2, - 206.6, - 204.5, - 201.8, - 198.3, - 195.1, - 191.8, - 187.7, - 183.3, - 178.9, - 172.4, - 163, - 148.5, - 134.8, - 125.4, - 118.4, - 111.8, - 106.1, - 102, - 98.8, - 95.9, - 92.9, - 89.7, - 87.9, - 87, - 86.1, - 85.5, - 85, - 84.6, - 84.1, - 83.6, - 83.2, - 82.8, - 82.2, - 81.8, - 81.6, - 81.6, - 81.5, - 81.5, - 81.5, - 81.3, - 80.9, - 80.5, - 80.2, - 79.8, - 79.5, - 79.3, - 79.1, - 78.9, - 78.7, - 78.4, - 78, - 77.6, - 77.1, - 76.8, - 76.6, - 76.4, - 76.3, - 76.2, - 76.1, - 76, - 75.9, - 75.8, - 75.7, - 75.9, - 76.5, - 77.1, - 77.5, - 76.4, - 75.8, - 75.4, - 75.1, - 75.6, - 77.1, - 79, - 79.9, - 81.3, - 82.4, - 81.7, - 79.8, - 77.4, - 75.4, - 73.4, - 73, - 73.3, - 73.6, - 74, - 74.1, - 74, - 74.4, - 74.9, - 75.2, - 75.7, - 76.3, - 76.9, - 77.5, - 78.2, - 78.9, - 79.8, - 81, - 82.8, - 84.7, - 87.5, - 89.8, - 92.2, - 94.2, - 95.8, - 97.2, - 616, - 616.3, - 616.8, - 617.2, - 617.4, - 617.1, - 616.3, - 615, - 614.2, - 614.1, - 614.3, - 614.5, - 614.7, - 614.9, - 615.6, - 615.8, - 615.8, - 616.3, - 617.4, - 618.8, - 619.8, - 619.5, - 618.7, - 618.5, - 618.4, - 617.9, - 618.1, - 618.5, - 618.6, - 618.7, - 618.7, - 618.8, - 619.4, - 619.3, - 618.7, - 618.3, - 618, - 617.8, - 617.3, - 616.5, - 616, - 615.6, - 615.2, - 614.4, - 613.9, - 613, - 612.1, - 611.7, - 611, - 610.2, - 609.6, - 608.4, - 607, - 606.2, - 605.7, - 605.5, - 605.5, - 605.9, - 606.5, - 606.5, - 606.2, - 605.5, - 604.8, - 604.1, - 603.1, - 602.5, - 602, - 600.4, - 598, - 595.2, - 593.1, - 590.2, - 587.5, - 584.3, - 576.8, - 572, - 568.9, - 565.8, - 560.4, - 555.1, - 546, - 533.8, - 520.9, - 512, - 506.8, - 502.9, - 498.3, - 493.7, - 484.7, - 475.2, - 470.6, - 454.7, - 429, - 402.1, - 372.8, - 348.9, - 341.7, - 340.3, - 338.5, - 335.4, - 330.5, - 326.3, - 323.7, - 322.2, - 321.6, - 321.9, - 322.9, - 323.7, - 324.5, - 324.9, - 325.1, - 324.9, - 324.9, - 325.2, - 325.9, - 326.6, - 327.3, - 327.9, - 328, - 327.7, - 327, - 324.8, - 321.1, - 318.2, - 315.6, - 313.1, - 310.5, - 307, - 303.5, - 300.3, - 295.7, - 291.4, - 287.4, - 283.3, - 279, - 274.4, - 269, - 263.4, - 258, - 252.3, - 246, - 240.8, - 236, - 231.5, - 227.4, - 223.1, - 219.5, - 217.3, - 215.4, - 212.9, - 210.7, - 208.5, - 205.9, - 202.5, - 199.1, - 196.1, - 192.8, - 188.8, - 184.5, - 180.2, - 176.3, - 168.9, - 156.2, - 143.7, - 132.1, - 122.7, - 115.4, - 110.5, - 107, - 103.8, - 99.5, - 95.3, - 92.1, - 91.2, - 89.5, - 87.2, - 86.4, - 86.3, - 85.8, - 85.4, - 85, - 84.5, - 84, - 83.7, - 83.5, - 83.4, - 83.4, - 83.3, - 83.2, - 83.1, - 82.8, - 82.4, - 82.1, - 81.7, - 81.3, - 81.1, - 81, - 80.8, - 80.6, - 80.3, - 79.9, - 79.5, - 79.1, - 78.8, - 78.5, - 78.4, - 78.3, - 78.2, - 78.2, - 78.1, - 78, - 77.9, - 77.9, - 78.4, - 79.8, - 80.9, - 80.9, - 79.6, - 79, - 78.9, - 79.8, - 80.8, - 81.3, - 81.6, - 82, - 83.6, - 84.4, - 84.7, - 83.6, - 80.6, - 77.7, - 75.1, - 74.3, - 74.5, - 74.9, - 75.3, - 75.5, - 75.5, - 75.7, - 76.2, - 76.7, - 77.3, - 77.8, - 78.4, - 79, - 79.7, - 80.4, - 81.2, - 82.2, - 83.4, - 84.8, - 87.7, - 90.8, - 93, - 94.8, - 96.3, - 97.9, - 618, - 618.7, - 619.5, - 619.7, - 619.2, - 618.6, - 618.3, - 617.5, - 616.6, - 616.2, - 616.1, - 616, - 616.6, - 617.2, - 618.5, - 618.5, - 618.7, - 619.5, - 620, - 620.9, - 621.4, - 620.7, - 620.2, - 620.1, - 619.6, - 619.4, - 620.3, - 620.5, - 620.6, - 621, - 621.4, - 621.6, - 621.8, - 620.9, - 620.3, - 620, - 619.9, - 619.9, - 618.8, - 617.8, - 617, - 616.7, - 616.5, - 616, - 615.6, - 614.8, - 614.1, - 613.9, - 613, - 611.9, - 611, - 610.1, - 608.8, - 608.4, - 607.7, - 607.6, - 607.8, - 608, - 608.1, - 608.1, - 607.6, - 606.8, - 606, - 605.5, - 604.7, - 604, - 603, - 601.1, - 598.8, - 596.2, - 594.2, - 590.4, - 586.6, - 584, - 578.7, - 573.8, - 570.9, - 568.6, - 566.2, - 562.9, - 555.9, - 544.8, - 528.4, - 518.5, - 510.8, - 507.8, - 504.3, - 499.5, - 491.8, - 483.7, - 478.8, - 473.3, - 457.3, - 421.1, - 389.3, - 368.1, - 349.3, - 346.6, - 344.9, - 341.8, - 337.3, - 332.9, - 329.9, - 327.4, - 326, - 325.3, - 325.2, - 325.8, - 326.7, - 327.4, - 327.9, - 327.7, - 327.5, - 327.4, - 327.8, - 328.2, - 328.6, - 329.2, - 329.7, - 329.8, - 329.4, - 328.1, - 324.7, - 321.6, - 318.4, - 315.7, - 313.5, - 311, - 307.9, - 304.8, - 301.1, - 296.9, - 292.8, - 288.6, - 285, - 280.8, - 275.8, - 270.6, - 264.6, - 258.6, - 252.2, - 246.2, - 241.3, - 236.4, - 232, - 227.5, - 223.4, - 220, - 218.3, - 216.2, - 214, - 211.8, - 209.5, - 206.5, - 203.3, - 200.1, - 197, - 193.6, - 189.9, - 185.8, - 182, - 178.3, - 172.8, - 164.8, - 153.8, - 140.5, - 128.9, - 120, - 113.2, - 109.2, - 106.1, - 102.4, - 98.4, - 96.1, - 96.1, - 93.7, - 90.7, - 88.3, - 87.1, - 86.8, - 86.6, - 86.2, - 85.9, - 85.6, - 85.4, - 85.3, - 85.2, - 85.2, - 85, - 84.9, - 84.7, - 84.4, - 84, - 83.7, - 83.3, - 83, - 82.8, - 82.6, - 82.4, - 82.1, - 81.7, - 81.4, - 81.1, - 80.7, - 80.5, - 80.4, - 80.3, - 80.3, - 80.2, - 80.3, - 80.2, - 80.1, - 80.2, - 81.1, - 82.5, - 83.2, - 82.8, - 82.8, - 83.1, - 83, - 84.1, - 85.4, - 86.6, - 86.1, - 85.4, - 86.6, - 86.7, - 86.8, - 85.9, - 83.6, - 80.7, - 77.2, - 75.6, - 75.8, - 76.2, - 76.6, - 77, - 77, - 77, - 77.5, - 78, - 78.7, - 79.3, - 79.9, - 80.4, - 81.1, - 81.8, - 82.6, - 83.5, - 84.6, - 85.9, - 89, - 91.5, - 93.3, - 94.9, - 96.6, - 98.7, - 620.2, - 621.4, - 621.7, - 621.9, - 621.6, - 620.8, - 619.9, - 619, - 618.6, - 618.1, - 618, - 618.2, - 619.1, - 619.9, - 620.6, - 621.2, - 621.4, - 621.8, - 622.2, - 622.3, - 622.2, - 621.7, - 621.7, - 621.5, - 621.4, - 621.7, - 622.3, - 622.3, - 622.4, - 623.1, - 623.6, - 623.8, - 623.5, - 622.6, - 622, - 621.7, - 621.8, - 621.3, - 620.7, - 620, - 619, - 618.2, - 618.2, - 618.3, - 617.8, - 616.8, - 616.2, - 615.4, - 614.4, - 613.7, - 613, - 612.2, - 611.5, - 611.1, - 610.5, - 610.3, - 610.3, - 610.2, - 609.9, - 609.6, - 608.9, - 608, - 607.3, - 606.6, - 605.8, - 605, - 603.5, - 602.1, - 599.7, - 596.1, - 592.4, - 588.5, - 585.6, - 582.6, - 579.1, - 575.6, - 573.2, - 570.9, - 568.7, - 566.5, - 561.5, - 553, - 535.8, - 524.5, - 516.3, - 511.1, - 508.2, - 505.2, - 500.6, - 496, - 488.4, - 478.5, - 447.3, - 414.9, - 404.4, - 389, - 365.5, - 356, - 350.7, - 347.6, - 343.5, - 339.1, - 336, - 333.4, - 331.9, - 330.3, - 328.6, - 328.7, - 329.3, - 330.3, - 331.4, - 331.4, - 330.6, - 330.3, - 330.4, - 330.5, - 330.6, - 330.8, - 331.5, - 332.1, - 331.5, - 330.3, - 328.1, - 325, - 321.2, - 318.4, - 316.3, - 314.2, - 311.7, - 308.7, - 305.4, - 301.6, - 297.4, - 293.4, - 289.8, - 286.6, - 282.4, - 277.6, - 272.1, - 265.6, - 259.3, - 252.8, - 246.4, - 241.1, - 236.6, - 232.1, - 227.6, - 223.6, - 220.9, - 219.3, - 217.2, - 215, - 213, - 210.4, - 207.4, - 204.2, - 201.2, - 198, - 194.6, - 191.5, - 187.7, - 184.1, - 179.8, - 175.4, - 170.3, - 161.6, - 146.9, - 134.4, - 125, - 117.9, - 112.5, - 108.4, - 105.3, - 101.5, - 100, - 99.3, - 97.7, - 95.2, - 91.7, - 88.7, - 87.8, - 87.2, - 87.5, - 87.4, - 87.2, - 87.1, - 87, - 87, - 87, - 86.8, - 86.6, - 86.3, - 86, - 85.7, - 85.3, - 85, - 84.8, - 84.5, - 84.4, - 84.1, - 83.8, - 83.4, - 83, - 82.8, - 82.5, - 82.4, - 82.2, - 82.1, - 81.8, - 81.9, - 81.7, - 82.2, - 82.8, - 84.5, - 85.3, - 85.5, - 85.5, - 85.6, - 85.6, - 86.9, - 88.4, - 88.5, - 89.7, - 89.7, - 88.9, - 88.7, - 88.8, - 88.9, - 87.3, - 84.7, - 82, - 79.2, - 76.9, - 77.1, - 77.6, - 78, - 78.4, - 78.5, - 78.5, - 78.9, - 79.5, - 80.1, - 80.7, - 81.3, - 81.9, - 82.6, - 83.3, - 84, - 84.9, - 85.9, - 87.2, - 89.9, - 92, - 93.7, - 95.3, - 97.3, - 99.5, - 622.2, - 623.2, - 623.2, - 623.2, - 622.9, - 622.6, - 621.7, - 620.5, - 620.3, - 620.2, - 620.3, - 620.5, - 621.2, - 621.8, - 622.1, - 622.6, - 623.2, - 623.9, - 624.3, - 624.2, - 623.9, - 623.7, - 623.9, - 623.9, - 623.9, - 623.7, - 623.9, - 623.9, - 624.5, - 625.2, - 625.5, - 625.5, - 625.2, - 624.5, - 623.9, - 623.5, - 623, - 622.6, - 622.2, - 621.4, - 620.8, - 620.4, - 620.4, - 620.4, - 619.8, - 619.2, - 618.5, - 617.1, - 616.3, - 615.6, - 614.9, - 614.3, - 613.9, - 613.3, - 612.6, - 612.2, - 612, - 611.8, - 611.6, - 611, - 610.1, - 609.3, - 608.4, - 607.3, - 605.4, - 602.6, - 601.2, - 600.6, - 598.1, - 593.4, - 588.6, - 585.3, - 583.1, - 580.3, - 578.7, - 576.4, - 574, - 571.3, - 569.3, - 567.6, - 562.8, - 555.9, - 542.1, - 530.1, - 523, - 516.5, - 511.1, - 508.8, - 506.1, - 502.2, - 498.3, - 488, - 446.3, - 415.6, - 407.9, - 401.4, - 389.5, - 371.9, - 357.4, - 352.2, - 348.7, - 344.9, - 341.9, - 340.1, - 338.6, - 336.6, - 334, - 332.8, - 332.9, - 334.2, - 335.3, - 335.4, - 335.1, - 334.6, - 334, - 333.6, - 333.2, - 333.4, - 334, - 334.4, - 333.9, - 332.9, - 331.7, - 329.3, - 324.6, - 321.2, - 319.1, - 317.1, - 315.1, - 312.8, - 309.8, - 306.4, - 302.4, - 298.2, - 294.4, - 291.3, - 287.5, - 283.3, - 278.5, - 272.6, - 266.1, - 259.7, - 253.1, - 247.1, - 241.7, - 236.8, - 231.8, - 227.6, - 223.8, - 221.6, - 219.9, - 218.3, - 216.3, - 214, - 211.3, - 208.3, - 205.3, - 202.4, - 198.9, - 195.6, - 192.6, - 189.4, - 185.4, - 181.2, - 176.6, - 171.4, - 163.8, - 150, - 139.8, - 132.8, - 125.9, - 117.4, - 111.5, - 107.2, - 104.5, - 104, - 103.3, - 101.5, - 99.7, - 96.8, - 93.7, - 90.1, - 88.8, - 88.2, - 88, - 87.6, - 86.8, - 87.3, - 88.1, - 88.3, - 88.2, - 88.1, - 88, - 87.7, - 87.3, - 87, - 86.8, - 86.6, - 86.4, - 86.1, - 85.9, - 85.5, - 85.1, - 84.5, - 84.3, - 84.3, - 84.4, - 83.9, - 83.6, - 84.5, - 85.3, - 87.9, - 89.2, - 89.7, - 89.1, - 88.9, - 88.9, - 88.6, - 88.8, - 90.5, - 91.1, - 91, - 91.3, - 91.3, - 90.8, - 90.4, - 90.6, - 90.8, - 88.8, - 86.3, - 82.8, - 79.7, - 78.6, - 78.7, - 79.1, - 79.5, - 79.9, - 80.1, - 80.1, - 80.4, - 81, - 81.6, - 82.2, - 82.8, - 83.4, - 84.1, - 84.8, - 85.5, - 86.3, - 87.2, - 88.3, - 90.3, - 92.3, - 94.2, - 96, - 97.8, - 99.9, - 624.7, - 624.7, - 624.4, - 624.3, - 624.2, - 623.9, - 623.3, - 622.7, - 622.6, - 622.1, - 622, - 622.4, - 622.7, - 623.4, - 624, - 624.6, - 625, - 625.4, - 625.5, - 625.6, - 625.6, - 625.5, - 625.8, - 625.9, - 626, - 625.7, - 625.5, - 625.7, - 626.2, - 626.7, - 627.1, - 627, - 626.5, - 625.9, - 625.4, - 625.1, - 624.6, - 624.5, - 624.1, - 623, - 622.4, - 622.7, - 622.8, - 622.4, - 621.8, - 621.2, - 620.3, - 619.1, - 618, - 617.3, - 616.7, - 616.2, - 615.5, - 615.1, - 614.3, - 614, - 613.8, - 613.6, - 613, - 612, - 611.1, - 610.3, - 609, - 607.2, - 604.1, - 598.3, - 596.4, - 595.5, - 592.5, - 588.1, - 583.7, - 581.6, - 580, - 578.8, - 577.8, - 576.7, - 574.2, - 570.8, - 569.3, - 567.7, - 563.5, - 555.9, - 544.3, - 535.1, - 529.3, - 523.8, - 516.1, - 511.4, - 509.5, - 507.3, - 503.1, - 497.4, - 468.6, - 428.4, - 407, - 403.7, - 399.7, - 385.1, - 372.7, - 361.8, - 354.6, - 350.7, - 347.9, - 345.9, - 344.7, - 342.9, - 340.6, - 338.6, - 338.5, - 338.9, - 339.9, - 340.2, - 339.9, - 339.7, - 339, - 337.8, - 336.7, - 336.6, - 337.1, - 337.4, - 336.5, - 335.4, - 334.1, - 332.4, - 327.9, - 324.1, - 321.8, - 319.7, - 317.9, - 315.9, - 313.4, - 310.5, - 307.1, - 303.3, - 299.3, - 296.2, - 292.9, - 288.7, - 284, - 279.1, - 273.2, - 266.7, - 259.9, - 253.3, - 247.8, - 242.3, - 237, - 232.2, - 227.9, - 224.3, - 222.1, - 220.8, - 219.2, - 217.1, - 214.8, - 212.3, - 209.4, - 206.6, - 203.7, - 200.4, - 196.8, - 193.4, - 190.1, - 186.5, - 182, - 176.8, - 172.6, - 166.6, - 160.3, - 154.9, - 143.8, - 132.7, - 123.3, - 114.8, - 110.2, - 109.8, - 109.4, - 107, - 105.8, - 104.3, - 102, - 100, - 97.9, - 95.6, - 93.7, - 92.4, - 90.2, - 88.7, - 89.6, - 89.4, - 88.1, - 88.4, - 89.3, - 89.3, - 88.9, - 88.6, - 88.7, - 88.6, - 88.4, - 88.2, - 87.9, - 87.5, - 87.1, - 86.3, - 87.5, - 87.7, - 87.9, - 88.2, - 89, - 90.3, - 91, - 92.4, - 93.6, - 93.8, - 93.2, - 92.6, - 92.3, - 92.2, - 92.5, - 93.4, - 93.3, - 92.9, - 92.7, - 92.4, - 92.2, - 92.1, - 92.2, - 92, - 90.8, - 88.8, - 85.5, - 82, - 80.7, - 80.5, - 80.6, - 81, - 81.5, - 82, - 81.9, - 81.9, - 82.5, - 83.1, - 83.6, - 84.2, - 84.9, - 85.6, - 86.3, - 86.9, - 87.6, - 88.5, - 89.5, - 90.7, - 92.4, - 94.4, - 96.6, - 98.6, - 100.5, - 626.8, - 626.1, - 625.6, - 625.5, - 625.4, - 625.1, - 624.9, - 624.9, - 624.8, - 624.1, - 624.2, - 624.4, - 624.5, - 625.4, - 626.2, - 626.9, - 627.3, - 627.8, - 627.5, - 626.9, - 626.5, - 626.4, - 627.1, - 627.6, - 627.4, - 627, - 627, - 627.5, - 627.9, - 628.4, - 628.5, - 628.5, - 628.2, - 627.7, - 627.3, - 626.7, - 626.6, - 626.5, - 625.8, - 625, - 624.4, - 624.5, - 624.7, - 624.4, - 623.8, - 623.1, - 621.6, - 620.4, - 619.5, - 618.8, - 618.2, - 617.4, - 617, - 617.1, - 616.5, - 615.8, - 615.5, - 614.9, - 614.2, - 613.2, - 612, - 610.6, - 608.7, - 606.5, - 602.7, - 597.8, - 593.7, - 589.1, - 585.4, - 582.6, - 581.1, - 579.9, - 579.1, - 578.4, - 577.3, - 575.8, - 573, - 569.8, - 568, - 566.5, - 564.2, - 557.1, - 545.9, - 539.2, - 533.3, - 528.6, - 522.1, - 515.7, - 511.2, - 510.1, - 507.9, - 503.5, - 486, - 440.4, - 413.1, - 408.1, - 402.9, - 393.2, - 386, - 379.6, - 371.9, - 359.9, - 354.1, - 351.5, - 349.7, - 347.9, - 346.5, - 344.6, - 343.7, - 343.4, - 344.2, - 344.2, - 344.4, - 344.8, - 343.7, - 341.7, - 340.4, - 340.4, - 341, - 340.3, - 338.9, - 338.1, - 336.7, - 334.6, - 331.4, - 327.5, - 325.2, - 322.5, - 320.6, - 318.5, - 316.4, - 314.1, - 311.2, - 307.7, - 304.2, - 301.3, - 298.7, - 294.5, - 290, - 285.2, - 279.9, - 274.1, - 267.4, - 260.2, - 254, - 248.6, - 243.1, - 237.4, - 232.5, - 228.3, - 224.9, - 223, - 221.6, - 219.8, - 217.8, - 215.8, - 213.1, - 210.5, - 207.7, - 205.1, - 201.9, - 198.2, - 194.6, - 191.1, - 187.5, - 183.1, - 178.6, - 174.4, - 170.6, - 166.6, - 160.4, - 151.6, - 139.6, - 129.3, - 122.6, - 118.7, - 116.8, - 114, - 112.9, - 112.5, - 110.4, - 108.2, - 106.3, - 105, - 104, - 103.4, - 102.1, - 100.6, - 99.1, - 96.5, - 93.2, - 92.1, - 91.2, - 90.1, - 89, - 88.5, - 89.9, - 90.5, - 90.5, - 90.3, - 90, - 89.7, - 89.5, - 89.4, - 90.1, - 90.7, - 90.8, - 91.9, - 93.1, - 94.2, - 94.1, - 94.8, - 96.2, - 96.5, - 95.9, - 95.5, - 95, - 94.8, - 94.9, - 95.3, - 95.3, - 95, - 94.5, - 94.1, - 93.9, - 93.9, - 93.8, - 93.3, - 92.3, - 91.6, - 89.8, - 87, - 84.5, - 82.7, - 82.2, - 82.4, - 82.8, - 83.3, - 83.4, - 83.4, - 83.9, - 84.6, - 85.1, - 85.7, - 86.4, - 87.1, - 87.7, - 88.4, - 89, - 89.8, - 90.8, - 91.8, - 92.9, - 94.5, - 96.8, - 99.4, - 101.1, - 628.5, - 627.7, - 627.1, - 626.8, - 626.5, - 626.7, - 626.7, - 626.3, - 626.1, - 626, - 626.1, - 626.3, - 626.8, - 627.5, - 628, - 628.8, - 629.5, - 630.4, - 630.7, - 629.8, - 628.6, - 628.2, - 629, - 629.2, - 628.9, - 628.7, - 629, - 629.8, - 629.9, - 630, - 630, - 629.8, - 629.7, - 629.5, - 629, - 628.5, - 628.4, - 628.5, - 627.7, - 627, - 626.6, - 626.4, - 626, - 625.7, - 625, - 624.4, - 623.2, - 621.7, - 620.6, - 619.9, - 619.4, - 619.1, - 619, - 618.6, - 618.3, - 617.6, - 616.7, - 615.9, - 615.1, - 613.9, - 612.6, - 610.7, - 608.4, - 605.7, - 601.4, - 597.4, - 592.5, - 586.2, - 582.4, - 581.1, - 580.3, - 579.6, - 578.7, - 577, - 575.5, - 573.5, - 571.6, - 568.7, - 566.7, - 565.1, - 561.8, - 554.6, - 547.2, - 542.7, - 537.1, - 532.1, - 527.6, - 521.1, - 514.9, - 511.4, - 510, - 507, - 499.9, - 475.1, - 447.6, - 430.8, - 423.2, - 410.4, - 396.9, - 390.5, - 386.7, - 381, - 369.9, - 359.1, - 356.2, - 354.1, - 353.5, - 354.1, - 350.7, - 348.7, - 348.5, - 348.5, - 349.2, - 350.1, - 349.7, - 347.4, - 345.7, - 344.5, - 344.2, - 343, - 342.9, - 343.6, - 341.2, - 337.1, - 333.9, - 330.5, - 327.9, - 325.6, - 323.3, - 321.3, - 319.2, - 317.2, - 314.8, - 311.8, - 308.4, - 306.1, - 304.3, - 300.3, - 296, - 291.2, - 285.9, - 280.9, - 274.8, - 267.4, - 260.6, - 254.6, - 248.9, - 243.3, - 237.7, - 232.9, - 228.8, - 225.7, - 224, - 222.2, - 220.3, - 218.5, - 216.3, - 214.1, - 211.6, - 209.2, - 206.3, - 203, - 199.4, - 195.6, - 192.3, - 188.8, - 184.7, - 180.6, - 176.3, - 172, - 168.1, - 163.8, - 156.8, - 146.7, - 139.6, - 133.3, - 130, - 124.4, - 123.3, - 121.3, - 118.4, - 115.3, - 112.8, - 110.9, - 108.9, - 107.7, - 107, - 106.1, - 104.4, - 102, - 99.3, - 97.6, - 95.7, - 93.3, - 91.4, - 90.3, - 91.3, - 92.4, - 92.6, - 92.5, - 92.3, - 92.1, - 91.7, - 91.6, - 92.8, - 94.4, - 94.9, - 95.6, - 96.1, - 96.7, - 97.2, - 97.3, - 98, - 98.3, - 98, - 98, - 97.5, - 96.9, - 96.5, - 96.6, - 96.8, - 97, - 96.7, - 96.7, - 96.4, - 96.1, - 95.8, - 95.1, - 94.1, - 93.7, - 92.9, - 91, - 88.8, - 86.7, - 84.3, - 83.7, - 83.8, - 84.3, - 84.7, - 84.8, - 85.3, - 86.1, - 86.6, - 87.2, - 88, - 88.6, - 89.2, - 89.8, - 90.5, - 91.2, - 92.1, - 93, - 94, - 95.1, - 97, - 99.8, - 101.5, - 630.1, - 629.5, - 629.2, - 629, - 628.4, - 628.6, - 628, - 627.5, - 627.5, - 627.4, - 627.6, - 628, - 628.6, - 629.1, - 629.5, - 630.2, - 631.2, - 632, - 632.4, - 632.1, - 631.2, - 630.5, - 630.7, - 630.6, - 630.3, - 630.4, - 630.7, - 631.3, - 631.6, - 631.6, - 631.5, - 631.3, - 631, - 630.8, - 630.9, - 630.5, - 630.2, - 630.2, - 629.9, - 629.2, - 628.6, - 628, - 627.5, - 627, - 626.4, - 626.1, - 625.4, - 623.9, - 622.6, - 621.8, - 621.5, - 621.2, - 620.6, - 620.2, - 619.7, - 619.1, - 618.1, - 617, - 616, - 614.7, - 613.2, - 611.3, - 608.7, - 605.8, - 601.4, - 597.7, - 591.4, - 585.3, - 581.7, - 580.3, - 579.3, - 578.3, - 577.4, - 574.9, - 572.7, - 570.8, - 569.4, - 566.4, - 564.5, - 562.4, - 558.1, - 554, - 550.6, - 547, - 542, - 536.8, - 531.4, - 525.6, - 519.7, - 514.5, - 511.4, - 509.1, - 506.4, - 501.4, - 485.9, - 459, - 443.3, - 434.2, - 417.6, - 401, - 394.3, - 390.8, - 385.3, - 374.5, - 366.1, - 364.5, - 363.7, - 362, - 358.6, - 357, - 355.6, - 355.6, - 356.5, - 357.6, - 356.8, - 355.5, - 355.3, - 350.7, - 347.5, - 347.3, - 348.9, - 349.9, - 347.1, - 340.6, - 335.1, - 332.2, - 330.2, - 328, - 325.7, - 323.3, - 321.4, - 319.9, - 318, - 315.6, - 312.7, - 311, - 308.7, - 305.6, - 301.8, - 297.3, - 292.1, - 286.7, - 281.5, - 275.4, - 268.1, - 261.3, - 254.9, - 249.1, - 243.3, - 237.7, - 233, - 228.9, - 226.2, - 224.5, - 222.6, - 220.8, - 218.9, - 216.8, - 215.1, - 213.3, - 210.7, - 207.6, - 204.5, - 201.5, - 197.7, - 194.1, - 190.2, - 186.3, - 182.3, - 178.1, - 174, - 170.2, - 166.2, - 161.8, - 156.7, - 151.7, - 147, - 139.8, - 134.6, - 130.4, - 126.5, - 122.6, - 119.4, - 116.8, - 114.9, - 112.9, - 111, - 109.8, - 108.5, - 106.5, - 104.5, - 102.7, - 100.5, - 96.7, - 94, - 92.6, - 92.6, - 93.2, - 93.3, - 93.5, - 93.9, - 94, - 93.6, - 94.8, - 97.3, - 98.9, - 99, - 99.3, - 99.8, - 99.5, - 99.5, - 99.6, - 100, - 99.8, - 100, - 100.1, - 99.4, - 98.9, - 98.5, - 98.2, - 98.5, - 98.7, - 98.7, - 99.1, - 98.7, - 98.6, - 98.1, - 97.3, - 96.5, - 95.9, - 96.6, - 95.3, - 92.4, - 90.8, - 88.2, - 85.8, - 85, - 85.3, - 86, - 86.3, - 86.7, - 87.5, - 88.2, - 88.8, - 89.5, - 90.1, - 90.7, - 91.2, - 91.9, - 92.6, - 93.4, - 94.3, - 95.2, - 96.2, - 97.5, - 99.8, - 101.8, - 631.1, - 631, - 630.7, - 630.7, - 630.3, - 630, - 629.4, - 629.2, - 629, - 628.9, - 629.2, - 629.5, - 629.9, - 630.4, - 631, - 631.6, - 632.3, - 633, - 633.4, - 633.3, - 632.8, - 632.3, - 632.1, - 631.8, - 631.7, - 631.8, - 632.2, - 632.9, - 633.3, - 633.3, - 633.2, - 632.9, - 632.4, - 632.2, - 632.5, - 632.2, - 631.6, - 631.5, - 631.2, - 630.7, - 630, - 629.4, - 628.8, - 628.4, - 628, - 627.5, - 626.6, - 625.4, - 624.2, - 623.6, - 623.2, - 622.7, - 622.2, - 621.8, - 620.9, - 620.2, - 619.8, - 619.1, - 617.6, - 615.9, - 614, - 612.3, - 610.2, - 607.5, - 604, - 599.3, - 591.8, - 584.8, - 580.9, - 579.4, - 576.5, - 574.5, - 574.1, - 572.7, - 571, - 568.7, - 567.2, - 564.9, - 563, - 560.3, - 557.5, - 555.4, - 554.1, - 551.1, - 546.2, - 541.1, - 535, - 529.4, - 524.5, - 518.7, - 514.1, - 511.3, - 509.1, - 506.1, - 499.8, - 481.4, - 461.7, - 452.7, - 439.3, - 418.3, - 404.8, - 397.7, - 394.8, - 390.9, - 385.3, - 380, - 374.7, - 369.3, - 366.4, - 365.5, - 363.9, - 367.1, - 371.2, - 372.3, - 368.4, - 363.3, - 360.8, - 358.8, - 354.9, - 354.2, - 355.3, - 355.4, - 352.2, - 345.6, - 338.1, - 333.9, - 332.1, - 330.3, - 328.1, - 325.6, - 324.3, - 323.1, - 321.1, - 318.7, - 316.6, - 315.5, - 313.1, - 310, - 307.1, - 303.3, - 298.3, - 293, - 287.9, - 282.6, - 276.4, - 268.9, - 262.2, - 255.8, - 249.6, - 243.4, - 237.8, - 232.8, - 228.9, - 226.4, - 224.5, - 222.6, - 221, - 219.2, - 217.6, - 216.3, - 214.3, - 211.8, - 208.7, - 205.8, - 202.5, - 198.8, - 194.9, - 191.2, - 187.4, - 183.6, - 179.5, - 175.7, - 172.1, - 168.8, - 165.2, - 160.1, - 154.9, - 150.4, - 145.6, - 140.7, - 137.9, - 134.7, - 130.5, - 126.5, - 123.6, - 120.9, - 118.4, - 116.1, - 114.2, - 112, - 109.6, - 107.1, - 104.9, - 102, - 98.1, - 95, - 94, - 94.3, - 94.1, - 95.4, - 97, - 97.4, - 97.8, - 99.9, - 101.7, - 102.3, - 102.3, - 102.4, - 102.8, - 102.3, - 101.6, - 101.7, - 101.7, - 101.6, - 101.9, - 102.4, - 101.9, - 101.4, - 100.9, - 100.5, - 100.6, - 100.6, - 100.9, - 101.3, - 101.4, - 101.5, - 100.9, - 100.2, - 99.5, - 99.4, - 99.7, - 99.1, - 97.1, - 95.3, - 92.5, - 89.4, - 86.9, - 86.6, - 87.4, - 87.8, - 88, - 88.6, - 89.6, - 90.3, - 91, - 91.6, - 92.1, - 92.7, - 93.4, - 94.1, - 94.7, - 95.6, - 96.5, - 97.4, - 98.5, - 100.1, - 102.4, - 632.2, - 632.3, - 632.1, - 631.9, - 631.9, - 631.8, - 631.5, - 631, - 630.6, - 630.8, - 631, - 631, - 631.1, - 631.5, - 632.1, - 632.8, - 633.6, - 634.2, - 634.6, - 634.3, - 634.3, - 634, - 633.9, - 633.6, - 633.3, - 633.3, - 633.9, - 634.7, - 634.8, - 634.8, - 634.7, - 634.4, - 634.4, - 634.1, - 633.7, - 633.4, - 632.8, - 632.5, - 632.2, - 631.6, - 630.9, - 630.6, - 630.4, - 629.9, - 629.2, - 628.6, - 628, - 627.1, - 626, - 625.5, - 625, - 624.7, - 624.5, - 623.4, - 622.7, - 622.2, - 621.7, - 620.6, - 619.3, - 617.4, - 615.7, - 613.3, - 610.8, - 608.7, - 606.3, - 600.3, - 590.3, - 584.3, - 580.5, - 578.7, - 574.9, - 571.8, - 570.3, - 569.5, - 567.7, - 566.5, - 566.1, - 564.2, - 560.8, - 557.7, - 556.1, - 555.1, - 554, - 551.6, - 547.7, - 543.5, - 538.5, - 533.9, - 529.5, - 522.9, - 517.1, - 513.6, - 511.4, - 508.9, - 505.8, - 498.5, - 490.5, - 488.4, - 470.9, - 445.1, - 428.2, - 414.2, - 405.6, - 404, - 400.9, - 395.2, - 387.4, - 379, - 374.1, - 373.4, - 374.8, - 378.8, - 382.9, - 381.9, - 377.4, - 370.6, - 365.8, - 364, - 362.9, - 362.2, - 361.8, - 359.5, - 355.8, - 350.3, - 343.5, - 337, - 333.8, - 332.4, - 330.9, - 329.2, - 328.2, - 326.6, - 324.5, - 322.1, - 320.6, - 319.2, - 317, - 314.4, - 311.5, - 308.5, - 304.5, - 299.4, - 294, - 289.2, - 283.7, - 277, - 269.7, - 263, - 255.6, - 249.1, - 243.5, - 238.1, - 233.3, - 229.4, - 226.5, - 224.5, - 222.9, - 221.5, - 219.9, - 218.5, - 217.1, - 215.4, - 212.7, - 209.3, - 205.9, - 202.9, - 199.3, - 195.5, - 192, - 188.4, - 184.7, - 181.1, - 177.4, - 174, - 171.1, - 168.2, - 164.5, - 159.6, - 154.6, - 149.9, - 146.1, - 144.1, - 140.5, - 136.1, - 132.4, - 128.8, - 125.5, - 123.2, - 121.2, - 118.8, - 115.2, - 112.5, - 109.9, - 107.5, - 104.9, - 101.7, - 98.8, - 99.2, - 99.5, - 100.2, - 102, - 103.3, - 103.6, - 106.1, - 106.6, - 105.5, - 104.9, - 104.6, - 104.6, - 104.6, - 104.1, - 103.8, - 103.6, - 103.6, - 103.9, - 104.9, - 104.9, - 104.1, - 103.7, - 103.2, - 103.1, - 103, - 103.4, - 103.6, - 104.7, - 104.6, - 104.1, - 103.6, - 103.2, - 102.5, - 102, - 102, - 101.4, - 99.3, - 96.5, - 93.7, - 89.8, - 88.2, - 88.8, - 89.3, - 89.5, - 89.9, - 90.9, - 91.8, - 92.5, - 93.1, - 93.6, - 94.2, - 94.8, - 95.5, - 96.1, - 96.9, - 97.8, - 98.7, - 99.7, - 100.9, - 103, - 633.9, - 633.5, - 633.4, - 633.2, - 633.3, - 633.1, - 633, - 632.7, - 632, - 632.1, - 632.6, - 632.8, - 633.1, - 633.1, - 633.4, - 634.2, - 635, - 635.7, - 636.2, - 636.1, - 636.1, - 636.2, - 635.6, - 635, - 634.8, - 635.3, - 635.8, - 636.2, - 636.1, - 636, - 635.9, - 636, - 636, - 635.6, - 635.2, - 634.8, - 634.2, - 633.7, - 633.4, - 632.9, - 632.1, - 631.8, - 631.8, - 631.2, - 630.6, - 630, - 629.3, - 628.5, - 627.8, - 627.5, - 627.1, - 626.7, - 626.5, - 625.6, - 625.1, - 624.3, - 623.2, - 621.6, - 620.1, - 618.4, - 616.7, - 614.1, - 610.2, - 605.9, - 602.8, - 595.5, - 587.1, - 583.7, - 580.2, - 578.1, - 574.6, - 570.6, - 567.9, - 565.6, - 564, - 563.8, - 563.6, - 561.2, - 557.3, - 555.4, - 555.2, - 554.6, - 553, - 549.2, - 546.1, - 543.8, - 540.5, - 536.5, - 533.3, - 528.6, - 521.5, - 516.4, - 513.8, - 511.8, - 509.7, - 506.5, - 503.5, - 505.4, - 495.5, - 475.9, - 452.4, - 435, - 425.7, - 417.2, - 415.2, - 414.6, - 406.3, - 393, - 384.2, - 380.6, - 382.6, - 384.6, - 385, - 384, - 382, - 378.6, - 372.7, - 368.4, - 366.7, - 366.3, - 366.6, - 364.4, - 359.8, - 354.8, - 349.1, - 341.2, - 336.3, - 334.2, - 333.2, - 332.1, - 330.7, - 329.4, - 326.6, - 324.6, - 324.2, - 323.3, - 321.3, - 318.9, - 316, - 313.1, - 309.8, - 305.7, - 300.3, - 295.2, - 290.6, - 284.9, - 277.7, - 270.3, - 262.6, - 255.7, - 249.9, - 244.4, - 239, - 234.1, - 229.8, - 227, - 224.9, - 223.7, - 222.6, - 221.1, - 219.7, - 218.4, - 216.7, - 213.9, - 210.4, - 206.9, - 203.7, - 200.3, - 196.7, - 193.1, - 189.4, - 185.8, - 182.5, - 179.1, - 175.7, - 172.8, - 170.3, - 167.1, - 163.3, - 159.4, - 154.6, - 150.8, - 146.9, - 142.8, - 139.1, - 135.4, - 131.6, - 129, - 127.1, - 124.2, - 119.7, - 117.1, - 115, - 113.4, - 111.4, - 107.8, - 104.8, - 105, - 105.5, - 106, - 106.4, - 107.3, - 109.4, - 111.9, - 111.6, - 110, - 108.4, - 107.4, - 106.8, - 106.8, - 107, - 106.4, - 105.3, - 105.4, - 106.2, - 106.8, - 106.7, - 106.4, - 106.3, - 105.9, - 105.5, - 105.3, - 105.4, - 106, - 107.6, - 108, - 107.3, - 107, - 106.6, - 105.6, - 104.5, - 103.8, - 103.5, - 101.8, - 99.7, - 97.6, - 93.9, - 90.4, - 90, - 90.7, - 90.9, - 91.3, - 92.4, - 93.3, - 94, - 94.6, - 95.2, - 95.7, - 96.3, - 96.9, - 97.6, - 98.3, - 99.1, - 100, - 101, - 102, - 103.4, - 635.8, - 635.1, - 634.5, - 634.6, - 634.5, - 634.4, - 634.5, - 634.1, - 633.4, - 633.4, - 634.1, - 634.8, - 635.4, - 635.4, - 635.7, - 636.2, - 636.6, - 636.9, - 637.2, - 637.5, - 637.8, - 637.9, - 637.3, - 636.6, - 636.7, - 637.3, - 637.5, - 637.7, - 637.4, - 637.1, - 637.1, - 637.2, - 637.2, - 637.2, - 636.7, - 636.1, - 635.5, - 635, - 634.8, - 634.4, - 633.7, - 633.2, - 633.1, - 632.6, - 631.9, - 631.2, - 630.5, - 629.8, - 629.4, - 629.2, - 628.7, - 628.2, - 627.8, - 627, - 626.4, - 625.3, - 624.2, - 622.8, - 620.9, - 619.4, - 617.5, - 614.6, - 608.2, - 600.9, - 595.2, - 589.7, - 586.1, - 583.2, - 579.7, - 577.6, - 573.8, - 570.7, - 568.1, - 564.1, - 562.1, - 560.9, - 560.1, - 557.4, - 554.8, - 554, - 553.7, - 553.1, - 550.5, - 546.6, - 544.6, - 542.8, - 539.9, - 536.6, - 534.2, - 532.1, - 526.1, - 519.9, - 516.3, - 515.1, - 513.5, - 512.8, - 511.4, - 511, - 508.3, - 502.9, - 483.1, - 455.3, - 441.9, - 434.1, - 426.3, - 427.2, - 427.2, - 418.7, - 403.4, - 390, - 386.5, - 388.1, - 388, - 385.8, - 384.7, - 383.2, - 378.7, - 374.5, - 373.2, - 370.4, - 369.9, - 368.1, - 362.9, - 357.4, - 352.6, - 345.9, - 340.6, - 336.9, - 335, - 334.3, - 333.2, - 331.8, - 328.6, - 327.1, - 327.2, - 327.1, - 326, - 324, - 321.1, - 318.2, - 315, - 311.1, - 306.6, - 301.7, - 296.7, - 292.1, - 286.1, - 278.2, - 270.4, - 262.8, - 256.7, - 251, - 245.6, - 240.1, - 234.8, - 230.9, - 228, - 226, - 225, - 224, - 222.6, - 221.2, - 219.8, - 217.6, - 214.9, - 211.8, - 208.5, - 205.1, - 201.6, - 198.1, - 194.3, - 190.8, - 187.3, - 183.8, - 180.5, - 177.4, - 174.6, - 171.8, - 168.8, - 165.9, - 162.7, - 159.1, - 154.1, - 149.7, - 147, - 143.1, - 139, - 136.1, - 133.7, - 130.8, - 127.6, - 124.9, - 122.3, - 119.3, - 116, - 113.3, - 109.8, - 108.8, - 109.5, - 109.9, - 109.8, - 110.7, - 114.4, - 116.7, - 115.8, - 114, - 113, - 111.6, - 110.9, - 110.3, - 110.3, - 109.9, - 107.9, - 107.5, - 108.2, - 109.1, - 109, - 108.3, - 108.3, - 108.9, - 108.8, - 108.2, - 108.1, - 108.5, - 109.8, - 110.7, - 110.5, - 109.5, - 108.4, - 107.6, - 106.8, - 106.1, - 105.1, - 104.1, - 102.7, - 101, - 98.3, - 94.7, - 92.6, - 92.3, - 92.4, - 92.6, - 93.7, - 94.7, - 95.5, - 96.2, - 96.7, - 97.2, - 97.8, - 98.3, - 99, - 99.7, - 100.4, - 101.3, - 102.2, - 103.2, - 104.2, - 637.8, - 637.2, - 636.7, - 636.4, - 635.8, - 635.8, - 635.8, - 635.5, - 635.1, - 635.5, - 636, - 636.5, - 636.8, - 636.8, - 637.1, - 637.3, - 637.6, - 638.1, - 638.5, - 638.9, - 639.2, - 639.3, - 638.9, - 638.8, - 638.9, - 639, - 639, - 638.9, - 638.7, - 638.5, - 638.3, - 638.4, - 638.6, - 638.4, - 637.9, - 637.2, - 636.6, - 636.2, - 636.1, - 635.6, - 634.8, - 634.3, - 634.1, - 633.7, - 633.2, - 632.5, - 632.1, - 631.6, - 631.3, - 630.9, - 630.4, - 629.7, - 629.2, - 628.3, - 627.2, - 626.1, - 625.1, - 623.6, - 622.1, - 620.6, - 618.8, - 615.9, - 608.3, - 598.3, - 590.7, - 587.5, - 585.9, - 582.3, - 579.1, - 576.4, - 572.3, - 570.9, - 568.6, - 564.2, - 560.6, - 557.8, - 556.6, - 554.4, - 552.6, - 551.7, - 550.4, - 548.3, - 546.3, - 544.5, - 543.2, - 540.9, - 538.5, - 536.2, - 534.5, - 532.8, - 528.7, - 523.6, - 519.6, - 518.6, - 517.9, - 516.6, - 514.2, - 513.1, - 510.9, - 508.6, - 501.2, - 485.4, - 469.7, - 450.4, - 438.7, - 433.4, - 436.5, - 435.6, - 426.8, - 411.2, - 398.4, - 392.6, - 392.3, - 390.9, - 388.8, - 386.9, - 383.3, - 379.5, - 380.9, - 377.8, - 375, - 372.9, - 366.4, - 360, - 356.3, - 351.5, - 345.6, - 340.8, - 337.4, - 336, - 335.2, - 333.9, - 332, - 329.9, - 329.3, - 329.3, - 329.3, - 328.5, - 326.2, - 323.6, - 320.7, - 317, - 312.5, - 307.8, - 303.1, - 298.5, - 293.7, - 287.6, - 279.6, - 271.4, - 264.1, - 257.8, - 252.3, - 246.8, - 241.5, - 236.5, - 232.5, - 229.6, - 227.9, - 226.6, - 225.6, - 224.1, - 222.7, - 221, - 218.7, - 216.1, - 212.7, - 209, - 205.6, - 202.3, - 199, - 195.5, - 191.8, - 188.2, - 184.5, - 181.5, - 178.6, - 175.8, - 173.2, - 170.4, - 167.4, - 164.7, - 161.8, - 157.9, - 154.1, - 150.3, - 146.3, - 142.9, - 140.1, - 137.2, - 134.6, - 132.1, - 129.9, - 127.5, - 123.9, - 119.4, - 117, - 115.4, - 114.1, - 113.7, - 114.1, - 115.8, - 118.6, - 121.5, - 120.5, - 118.5, - 117.5, - 115.8, - 114.8, - 114.6, - 114.1, - 112.7, - 111.6, - 111.5, - 111, - 111.6, - 112.1, - 111, - 110.7, - 112, - 111.9, - 111.3, - 111.2, - 110.9, - 111.8, - 112.2, - 112.4, - 112.2, - 111, - 109.5, - 108.5, - 108.1, - 107.4, - 106.2, - 105.7, - 104.7, - 102.2, - 99.4, - 97, - 94.7, - 93.8, - 94.1, - 95.2, - 96.2, - 97, - 97.7, - 98.2, - 98.7, - 99.3, - 99.8, - 100.4, - 101.1, - 101.8, - 102.6, - 103.4, - 104.3, - 105.3, - 639.1, - 638.9, - 638.5, - 637.8, - 637.5, - 637.6, - 637.4, - 637.2, - 637, - 637.3, - 637.6, - 637.6, - 637.7, - 637.9, - 638.4, - 638.6, - 639.1, - 639.5, - 639.8, - 640.4, - 640.8, - 640.9, - 640.6, - 640.6, - 640.6, - 640.7, - 640.5, - 640.1, - 639.9, - 639.8, - 639.8, - 639.7, - 639.6, - 639.5, - 639.1, - 638.8, - 638.1, - 637.5, - 637.3, - 636.9, - 636.1, - 635.5, - 635.3, - 635, - 634.4, - 634, - 633.8, - 633.3, - 632.8, - 632.4, - 631.8, - 631, - 630.6, - 629.4, - 627.9, - 626.8, - 625.8, - 624.4, - 623.1, - 621.6, - 620.3, - 617.8, - 608.5, - 596.5, - 589.1, - 587.1, - 584.2, - 580.8, - 577.8, - 574, - 570.9, - 569.6, - 567.9, - 564.3, - 559.5, - 556.3, - 554.1, - 551.8, - 550.2, - 549.6, - 548, - 545.6, - 544.2, - 543.1, - 541.9, - 539.6, - 537.7, - 535.3, - 533.8, - 532.4, - 529.4, - 526.1, - 523.1, - 521.5, - 520.5, - 518.5, - 514.9, - 512.7, - 510.6, - 508.4, - 506.2, - 502.5, - 495.4, - 481.2, - 464.9, - 447.1, - 442.9, - 443.7, - 439.6, - 431.2, - 419.8, - 405.5, - 397, - 396.5, - 395.5, - 392.2, - 388.5, - 384.9, - 386.9, - 386.4, - 382.1, - 378.8, - 372.7, - 365, - 360.7, - 358.2, - 352.8, - 344.1, - 339.9, - 337.9, - 336.7, - 335.8, - 334.5, - 333.1, - 331.9, - 332, - 331.7, - 330.6, - 329.1, - 327.7, - 325.6, - 322.7, - 318.9, - 314.2, - 309.5, - 304.9, - 300.4, - 295.6, - 289.6, - 281.8, - 273.5, - 265.4, - 259.4, - 253.8, - 248.2, - 243.1, - 238.3, - 234.4, - 231.6, - 229.6, - 228.4, - 227.4, - 225.8, - 224.3, - 222.4, - 220, - 217.3, - 214.1, - 210.4, - 206.9, - 203.4, - 199.8, - 196.1, - 192.4, - 188.3, - 184.7, - 181.9, - 179.3, - 176.7, - 174, - 171.3, - 168.8, - 166.5, - 164, - 160.8, - 157.1, - 153.2, - 149.7, - 146.5, - 143.6, - 140.8, - 138.3, - 136, - 133.6, - 131.2, - 128.8, - 126.7, - 124.6, - 121.4, - 118.7, - 118.9, - 121.8, - 124.6, - 125.7, - 125, - 123.3, - 121.5, - 119.8, - 118.7, - 118.6, - 118.5, - 116.7, - 115.8, - 115.6, - 114.8, - 114.8, - 115.2, - 114.9, - 114.6, - 115.2, - 115.4, - 114.8, - 114.1, - 113.5, - 114.7, - 114.6, - 115, - 115.3, - 114.6, - 112.3, - 110.5, - 109.6, - 109.3, - 108.8, - 108.1, - 107.3, - 105.4, - 102.7, - 100.1, - 96.9, - 95.5, - 95.9, - 96.8, - 97.7, - 98.5, - 99.2, - 99.7, - 100.3, - 100.8, - 101.3, - 101.9, - 102.5, - 103.1, - 103.9, - 104.7, - 105.6, - 106.5, - 640.3, - 640.1, - 639.8, - 639.5, - 639.3, - 639.3, - 639.2, - 638.9, - 638.7, - 638.7, - 638.8, - 638.8, - 638.9, - 639, - 639.8, - 640.3, - 640.6, - 640.8, - 641.3, - 641.8, - 642.2, - 642.3, - 642, - 642, - 642.1, - 642, - 641.7, - 641.4, - 641.2, - 641.1, - 641.3, - 641.2, - 640.9, - 640.6, - 640.4, - 640, - 639.4, - 638.9, - 638.4, - 638, - 637.6, - 637, - 636.8, - 636.5, - 636, - 635.6, - 635.2, - 634.6, - 634.2, - 633.8, - 632.9, - 632.4, - 631.8, - 630.5, - 629.1, - 627.8, - 626.7, - 625.1, - 623.7, - 622.4, - 620.4, - 617.2, - 605.3, - 593.3, - 588.9, - 586.3, - 581.8, - 578.1, - 575, - 572.1, - 569.9, - 568, - 565.8, - 562.8, - 558.3, - 555.6, - 552.2, - 549.9, - 548.7, - 547.4, - 545.8, - 544.5, - 543.2, - 541.9, - 540.3, - 538.6, - 536.2, - 533.7, - 532, - 530.2, - 528.3, - 525.3, - 523.6, - 521.9, - 520.2, - 517, - 513.6, - 510.9, - 509.2, - 507.1, - 504.8, - 502.2, - 498.9, - 493.9, - 487.5, - 475, - 461.5, - 457, - 451.5, - 441, - 431.1, - 420.5, - 408.1, - 400.8, - 400.3, - 398.3, - 394.1, - 390.8, - 391.6, - 393.1, - 390.6, - 386.7, - 380.8, - 371.2, - 363.5, - 361.3, - 356.5, - 346.3, - 341.3, - 339.4, - 337.9, - 336.8, - 335.8, - 335.3, - 334.9, - 334.8, - 334.7, - 333.6, - 331.9, - 330.7, - 329.4, - 327.3, - 324.6, - 321.1, - 316.2, - 311.3, - 306.9, - 302.4, - 297.6, - 291.6, - 284.5, - 275.7, - 267.2, - 260.9, - 254.8, - 249.4, - 244.4, - 240.2, - 236.6, - 233.6, - 231.5, - 230.4, - 229.1, - 227.1, - 225.5, - 223.8, - 221.4, - 218.7, - 215.9, - 212.5, - 208.9, - 204.9, - 201, - 196.6, - 191.5, - 188.1, - 184.9, - 181.9, - 179.6, - 177.5, - 175.4, - 173.1, - 170.8, - 168.5, - 165.9, - 162.7, - 159.2, - 156.2, - 153.1, - 149.9, - 147, - 144.4, - 142, - 140, - 137.3, - 134.7, - 132.5, - 130.5, - 128.3, - 126.6, - 127, - 128.5, - 129.3, - 129.1, - 128.7, - 128.1, - 126.2, - 124, - 122.6, - 122.3, - 122.4, - 120.9, - 119.7, - 119.3, - 119.5, - 118.4, - 118.1, - 118.3, - 118.3, - 118.6, - 119, - 118.4, - 118, - 118.5, - 118.7, - 117.6, - 117.4, - 118, - 116.9, - 115.2, - 113.7, - 112.6, - 111.3, - 111.1, - 110.7, - 109.5, - 107.9, - 106.1, - 103.1, - 100.1, - 98.5, - 97.8, - 98.3, - 99.2, - 100, - 100.7, - 101.3, - 101.8, - 102.3, - 102.8, - 103.3, - 103.9, - 104.5, - 105.2, - 106, - 106.9, - 107.7, - 641.5, - 641.1, - 640.8, - 640.9, - 640.7, - 640.4, - 640.3, - 640.2, - 639.8, - 639.6, - 639.7, - 640.1, - 640.3, - 640.6, - 641.2, - 641.6, - 641.8, - 642.3, - 642.6, - 643, - 643.3, - 643.1, - 643.2, - 643.3, - 643.3, - 643.1, - 642.8, - 642.7, - 642.7, - 642.6, - 642.5, - 642.2, - 642, - 641.7, - 641.3, - 641, - 640.6, - 640, - 639.6, - 639.3, - 638.9, - 638.3, - 638.2, - 637.9, - 637.4, - 636.7, - 636.2, - 635.9, - 635.6, - 635.1, - 634.3, - 633.6, - 632.5, - 631.3, - 630.1, - 628.9, - 627.8, - 626.3, - 624, - 619.2, - 614, - 609.2, - 597.7, - 591.2, - 588.1, - 584, - 578.8, - 574.7, - 572.7, - 570.8, - 569.2, - 566.7, - 564, - 561.1, - 558.6, - 557, - 554, - 550.3, - 548.2, - 546.1, - 544.3, - 542.8, - 541.8, - 540.6, - 538.8, - 536.8, - 534.7, - 532.6, - 530.5, - 528.4, - 526, - 523.8, - 522.5, - 520.3, - 517.8, - 514.9, - 513.2, - 510.6, - 508.3, - 506.2, - 503.7, - 500.9, - 498.7, - 496.3, - 493.2, - 488.4, - 482.5, - 474.7, - 467.6, - 455.3, - 442.4, - 430.8, - 419.9, - 409.6, - 404.5, - 403.5, - 400.3, - 396.2, - 394.9, - 395.5, - 394.1, - 392.5, - 388.6, - 376.5, - 365.5, - 362.4, - 358.6, - 349.1, - 343.3, - 340.9, - 339.5, - 338.1, - 336.9, - 336.6, - 336.7, - 336.6, - 337, - 336.6, - 335.2, - 333.4, - 332.5, - 331.5, - 329.1, - 326.4, - 322.8, - 318.1, - 313.4, - 309.2, - 304.7, - 299.6, - 294.3, - 287, - 278.5, - 269.8, - 262.9, - 256.6, - 251, - 246.6, - 242.5, - 238.6, - 235.3, - 233.6, - 232.3, - 230.3, - 228.2, - 226.9, - 225.1, - 222.6, - 220.2, - 217.5, - 214.1, - 210.3, - 205.8, - 201.3, - 194.9, - 190.9, - 188.1, - 185.2, - 182.7, - 181.3, - 179.9, - 177.8, - 175.3, - 172.8, - 170.3, - 167.4, - 164.3, - 161.7, - 159.2, - 156.2, - 153.5, - 151.1, - 149.3, - 147.3, - 144.3, - 140.9, - 138, - 135.9, - 134.6, - 134, - 134.6, - 133.9, - 133, - 132.5, - 132.5, - 132.2, - 131.2, - 129.1, - 127.1, - 125.9, - 125.7, - 124.2, - 123.9, - 124.2, - 124, - 123, - 121.5, - 121.9, - 121.9, - 121.9, - 122.4, - 122.7, - 122, - 121.5, - 121.7, - 120.9, - 120.3, - 120.6, - 119.6, - 117.7, - 116.5, - 115.4, - 113.7, - 112.9, - 112.8, - 112.5, - 111.2, - 109.4, - 107.4, - 106.4, - 105.3, - 102.3, - 100.9, - 100.6, - 101.3, - 102.2, - 102.9, - 103.4, - 103.9, - 104.3, - 104.7, - 105.3, - 105.9, - 106.5, - 107.2, - 108.1, - 108.9, - 642.6, - 642.1, - 641.8, - 641.9, - 641.6, - 641.5, - 641.3, - 641, - 640.8, - 640.5, - 640.7, - 641.3, - 641.7, - 642.1, - 642.6, - 642.8, - 643.4, - 643.8, - 643.7, - 644, - 644.2, - 644.2, - 644.4, - 644.4, - 644.3, - 644.1, - 644, - 643.9, - 643.9, - 643.7, - 643.5, - 643.2, - 642.8, - 642.5, - 642.3, - 642.1, - 641.8, - 641.2, - 641, - 640.8, - 640.2, - 639.6, - 639.4, - 639.2, - 638.7, - 638.1, - 637.5, - 637.1, - 636.9, - 636.5, - 635.8, - 634.6, - 633.4, - 632.1, - 631.1, - 630, - 628.7, - 627.2, - 621.6, - 611, - 603.9, - 597.4, - 591, - 587.8, - 583.5, - 579.5, - 575.6, - 573.1, - 571.5, - 570, - 568.3, - 565.7, - 563.6, - 561.9, - 560.5, - 559.1, - 556.3, - 552.9, - 550, - 547.3, - 544.9, - 542.2, - 539.9, - 538.5, - 537.1, - 535.5, - 533.8, - 531.6, - 529.5, - 527.3, - 524.7, - 522.7, - 520.7, - 518.2, - 515.5, - 513.9, - 512.1, - 509.7, - 507.6, - 505.5, - 502.6, - 500.3, - 498.3, - 495.8, - 493, - 490.3, - 487.4, - 482.3, - 476.5, - 470.3, - 458.6, - 444.3, - 430.5, - 419.3, - 410.9, - 408, - 406.7, - 402.6, - 399, - 397.4, - 396.9, - 396.3, - 392.4, - 376.6, - 367.2, - 363.9, - 359.3, - 351.5, - 345.9, - 343.3, - 341.7, - 340.7, - 339, - 338.3, - 338.1, - 338.1, - 338.5, - 338.6, - 337.6, - 336.5, - 335.4, - 334.4, - 332.6, - 330.3, - 327.6, - 324, - 319.8, - 315.2, - 311, - 306.8, - 302.2, - 296.3, - 289.4, - 281.1, - 272.4, - 265.5, - 258.7, - 253.3, - 249, - 244.8, - 240.9, - 237.4, - 235.2, - 233.4, - 231.3, - 229.6, - 227.9, - 225.8, - 223.7, - 221, - 218.3, - 215.7, - 212.4, - 208.6, - 201.8, - 194.5, - 191.7, - 189.3, - 187.2, - 185.9, - 184.3, - 182.2, - 179.9, - 177.4, - 174.8, - 172.2, - 169.3, - 166.6, - 164.2, - 161.7, - 159.7, - 158.3, - 156.2, - 153.8, - 151.3, - 148.4, - 145.1, - 142.5, - 142, - 142.4, - 140.9, - 138.8, - 137.5, - 136.3, - 136.2, - 136.4, - 135.8, - 134.6, - 132.6, - 130.5, - 129.7, - 128.6, - 128.1, - 127.9, - 127.5, - 127.3, - 125.4, - 124.7, - 126.1, - 125.8, - 125.4, - 126.1, - 126.1, - 124.9, - 124.4, - 124.1, - 123.5, - 123, - 121.9, - 120.4, - 118.4, - 117.2, - 116, - 115.8, - 116.1, - 116.2, - 115.3, - 112.8, - 111.3, - 111.5, - 110.9, - 108.3, - 106.7, - 104.3, - 102.3, - 103.3, - 104.5, - 105, - 105.4, - 105.9, - 106.3, - 106.8, - 107.4, - 108, - 108.6, - 109.3, - 110.2, - 643.8, - 643.4, - 643.2, - 643.3, - 643, - 642.5, - 642.4, - 642.1, - 642, - 641.8, - 642, - 642.5, - 642.8, - 643.2, - 643.6, - 644, - 644.5, - 644.7, - 644.7, - 644.9, - 645, - 645.1, - 645.2, - 645.2, - 645.1, - 645, - 645, - 645.1, - 645.1, - 645, - 644.8, - 644.6, - 643.9, - 643.7, - 643.5, - 643.3, - 643, - 642.6, - 642.5, - 642.2, - 641.8, - 641.2, - 640.7, - 640.4, - 640, - 639.6, - 639.1, - 638.4, - 638, - 637.4, - 636.7, - 635.8, - 634.3, - 633, - 632, - 630.9, - 629.6, - 626, - 615.4, - 603.1, - 594.5, - 589.4, - 585.2, - 582.5, - 579.3, - 576.7, - 574.5, - 572.8, - 571.2, - 569.7, - 568.2, - 566.5, - 564.7, - 562.6, - 560.8, - 559, - 556.4, - 553.6, - 550.5, - 548, - 545.5, - 542.9, - 539.9, - 536.9, - 535.4, - 534.3, - 532.7, - 530.8, - 529, - 526.3, - 523.6, - 521.5, - 518.4, - 516.3, - 514.6, - 513, - 511.2, - 509, - 506.8, - 504.8, - 502.4, - 500, - 497.7, - 495.2, - 492.5, - 490.3, - 487.4, - 484.6, - 481.2, - 476.7, - 470.3, - 459.6, - 444.2, - 429.4, - 418.2, - 412, - 411.6, - 411.9, - 409.5, - 404.6, - 401.5, - 399.3, - 393.7, - 377.3, - 368.7, - 365.6, - 360.3, - 354.2, - 349.7, - 346.5, - 344.7, - 343.2, - 342.2, - 341.4, - 340.4, - 339.7, - 339.7, - 339.6, - 339, - 338.2, - 337.4, - 336.2, - 334.9, - 333.6, - 331.7, - 329, - 325.2, - 321, - 316.8, - 312.6, - 308.6, - 303.9, - 298.3, - 291.5, - 283.6, - 275.9, - 268.3, - 261.3, - 255.8, - 251.4, - 247.3, - 243.1, - 239.3, - 236.5, - 234.2, - 232.4, - 231.1, - 229.1, - 227, - 224.7, - 222.1, - 219.6, - 217.1, - 214.1, - 207.7, - 198.3, - 195.5, - 193.3, - 191.4, - 190.1, - 188.2, - 186.3, - 184.2, - 181.9, - 179.5, - 176.9, - 174.3, - 172.3, - 170.4, - 167.7, - 165.5, - 164.5, - 162.4, - 159.5, - 157.2, - 154.7, - 152.3, - 149.3, - 146.9, - 145.6, - 144.7, - 143.2, - 142.5, - 140.6, - 139.9, - 141.4, - 141.7, - 140.4, - 137.9, - 135.5, - 134.2, - 132.8, - 131.3, - 130.3, - 129.8, - 130.3, - 130.1, - 128.8, - 129.9, - 130, - 129.3, - 128.9, - 129.6, - 128.9, - 128.1, - 127.6, - 126.4, - 125.3, - 124.8, - 123.3, - 121, - 119.5, - 118.3, - 118.8, - 119.5, - 119.6, - 119.4, - 117.3, - 115, - 114.7, - 113.8, - 111.7, - 109.7, - 107.4, - 104.5, - 105.1, - 106.2, - 106.6, - 107.1, - 107.5, - 107.9, - 108.4, - 108.9, - 109.3, - 110, - 110.7, - 111.4, - 644.8, - 645, - 645.1, - 644.9, - 644.6, - 644.1, - 643.9, - 643.4, - 643.5, - 643.5, - 643.8, - 643.9, - 643.9, - 644.2, - 644.7, - 645.2, - 645.4, - 645.5, - 645.7, - 645.8, - 646, - 646, - 646, - 646, - 646, - 646, - 646, - 646.1, - 646.1, - 646.2, - 646, - 645.7, - 645.4, - 644.9, - 644.7, - 644.5, - 644.2, - 643.9, - 643.7, - 643.4, - 643, - 642.4, - 641.8, - 641.5, - 641.2, - 640.6, - 640.2, - 639.7, - 639, - 638.4, - 637.5, - 636.4, - 635.2, - 634.1, - 633, - 631.8, - 629.3, - 619.6, - 605.5, - 594, - 587.9, - 584.8, - 582.1, - 580.2, - 578.3, - 576.4, - 574.8, - 573.2, - 571.7, - 570.3, - 568.8, - 566.8, - 564.6, - 562.3, - 560.4, - 558.2, - 555.8, - 553.2, - 549.9, - 547.6, - 545.1, - 543, - 540.7, - 537.5, - 534.9, - 532.9, - 530.9, - 529.3, - 527.6, - 524.6, - 522.2, - 519.6, - 516.9, - 515.4, - 513.9, - 512.6, - 511.1, - 508.9, - 506.5, - 504.3, - 502.1, - 499.8, - 497.2, - 494.8, - 492.6, - 490.2, - 487.2, - 484.7, - 482.6, - 480.4, - 476.1, - 470.5, - 459.6, - 443.1, - 428.2, - 417.3, - 413.7, - 415.8, - 417.8, - 417, - 412.1, - 403.4, - 395.5, - 384.8, - 375, - 368, - 362.6, - 357.8, - 353.2, - 349.6, - 347.6, - 346.5, - 345.2, - 344.3, - 343.5, - 342.7, - 341.8, - 341.4, - 340.1, - 339.3, - 338.6, - 337.5, - 336.5, - 335.6, - 334.7, - 332.9, - 330.2, - 326.5, - 322.5, - 318.6, - 314.5, - 310.3, - 305.7, - 300, - 293.7, - 286.4, - 279, - 271.2, - 264.4, - 258.6, - 253.9, - 249.6, - 245.4, - 241.7, - 238.4, - 236, - 234.4, - 232.3, - 229.9, - 227.9, - 225.6, - 223.1, - 220.9, - 218, - 211.6, - 203, - 199.9, - 198.4, - 196.4, - 194.4, - 192.4, - 190.4, - 188.3, - 186.1, - 184.1, - 181.8, - 179.6, - 178, - 176.8, - 174.4, - 172.5, - 171.1, - 168.7, - 165.9, - 163.3, - 160.8, - 158.4, - 156, - 152.8, - 150, - 148.2, - 147.1, - 146.6, - 144.9, - 144.1, - 146.7, - 148.7, - 147.2, - 143.8, - 141.1, - 138.8, - 136.9, - 135.2, - 133.3, - 132.2, - 132.6, - 133.2, - 132.7, - 132.9, - 133.2, - 132.8, - 132.5, - 133, - 132.6, - 132, - 131.1, - 129.5, - 127.8, - 127.1, - 126.6, - 124.6, - 123, - 121.7, - 122, - 123.2, - 124, - 123.9, - 121.3, - 118.3, - 117, - 116, - 114.2, - 111.6, - 109.3, - 107.7, - 107.4, - 107.9, - 108.3, - 108.7, - 109.1, - 109.5, - 109.9, - 110.3, - 110.8, - 111.4, - 112, - 112.7, - 646, - 646.2, - 646.4, - 646.3, - 646, - 645.8, - 645.5, - 645.2, - 645.1, - 645.1, - 645.1, - 645.1, - 645.1, - 645.4, - 645.8, - 646.1, - 646.2, - 646.3, - 646.4, - 646.7, - 646.9, - 646.9, - 646.9, - 646.9, - 646.9, - 646.9, - 647.1, - 647.2, - 647.2, - 647.2, - 647.2, - 647.1, - 646.8, - 646.2, - 645.9, - 645.6, - 645.2, - 645, - 644.8, - 644.5, - 644.2, - 643.7, - 643.2, - 642.8, - 642.4, - 641.5, - 641.1, - 640.6, - 640, - 639.3, - 638.4, - 637.2, - 636.2, - 635.2, - 634.1, - 632, - 623.9, - 609.1, - 595.7, - 589.5, - 587.3, - 585.7, - 584.6, - 583.5, - 581.3, - 578.7, - 576.9, - 575.3, - 573.4, - 572.1, - 570.6, - 567.9, - 565, - 562, - 559.5, - 557.2, - 555, - 552.5, - 549.6, - 547.3, - 544.9, - 542.9, - 540.8, - 538.5, - 535.8, - 533.3, - 531, - 528.3, - 525.8, - 522.2, - 519.1, - 517.2, - 515.6, - 514.5, - 513.3, - 512.2, - 510.8, - 508.6, - 506.4, - 504.3, - 502.1, - 500, - 497.5, - 495.1, - 493.2, - 490.7, - 487.5, - 485.1, - 483.1, - 481, - 478.8, - 475.9, - 472.6, - 461.9, - 442.7, - 427, - 417.8, - 417.2, - 419.7, - 422, - 422.1, - 416.2, - 405.2, - 394.7, - 384.6, - 374.3, - 367.2, - 361.5, - 356.8, - 353.2, - 350.1, - 348.6, - 347.6, - 346.9, - 347, - 346.6, - 346, - 345.5, - 342.6, - 340.3, - 339.6, - 338.7, - 337.7, - 337, - 336.7, - 335.4, - 333.3, - 330.9, - 327.5, - 323.8, - 320.1, - 316.2, - 312, - 307.1, - 301.9, - 296.2, - 289.6, - 282.2, - 275, - 268.1, - 262.2, - 257, - 252.4, - 248.4, - 244.4, - 240.6, - 237.7, - 235.5, - 233.3, - 231.3, - 229.2, - 226.7, - 224.3, - 221, - 214.2, - 209.3, - 205.2, - 202.4, - 200, - 198.3, - 196.7, - 194.8, - 192.9, - 190.7, - 188.4, - 186.2, - 184.4, - 183.2, - 182.3, - 180.5, - 179.2, - 177.4, - 174.8, - 172.2, - 169.8, - 167.3, - 165.2, - 162.1, - 158.4, - 155.4, - 153.1, - 152.2, - 151, - 149.3, - 148.9, - 152.3, - 157, - 156.1, - 152.3, - 148.4, - 144.9, - 142.1, - 140, - 137.7, - 135.4, - 135.2, - 135.6, - 135.3, - 135.2, - 135.8, - 136.2, - 136.2, - 135.7, - 135.6, - 134.9, - 133.8, - 132.3, - 130.1, - 128.7, - 128.2, - 127.3, - 125.7, - 124.2, - 124.5, - 125.8, - 126.4, - 126.1, - 124.2, - 122, - 121.2, - 119.6, - 116.9, - 114.3, - 111.7, - 109.7, - 109.3, - 109.6, - 110, - 110.4, - 110.8, - 111.1, - 111.5, - 111.8, - 112.3, - 112.7, - 113.3, - 114, - 647, - 647.1, - 647.4, - 647.4, - 647.4, - 647.2, - 646.9, - 646.7, - 646.7, - 646.6, - 646.3, - 646.1, - 646, - 646.2, - 646.6, - 646.9, - 647, - 647.1, - 647.2, - 647.5, - 647.6, - 647.7, - 647.7, - 647.8, - 647.8, - 647.8, - 647.9, - 648.1, - 648.2, - 648.2, - 648.2, - 648.2, - 648, - 647.5, - 647, - 646.6, - 646.2, - 646, - 645.8, - 645.7, - 645.4, - 645, - 644.6, - 644, - 643.4, - 642.6, - 642.1, - 641.6, - 641.1, - 640.4, - 639.4, - 638.5, - 637.4, - 636.2, - 634.6, - 629, - 618.2, - 603.5, - 595.2, - 592.6, - 591.1, - 589.9, - 589.2, - 588, - 585.7, - 582.6, - 580.2, - 577.4, - 575.4, - 573.8, - 572.5, - 570.3, - 567.8, - 564.8, - 561.6, - 557.9, - 555.2, - 552.7, - 550.2, - 547.7, - 545.3, - 543.3, - 541.2, - 539.1, - 536.7, - 534.6, - 531.7, - 529, - 526.7, - 523.4, - 518.8, - 516.2, - 514.7, - 513.5, - 512.4, - 511.3, - 510, - 508.2, - 506.1, - 504, - 502.1, - 499.8, - 497.7, - 495.6, - 493.6, - 491, - 487.5, - 485.6, - 483.8, - 481.5, - 479.5, - 477.6, - 475.7, - 472.5, - 459.5, - 439.9, - 425.9, - 419.6, - 420.6, - 423.1, - 425.4, - 424.5, - 417.4, - 406.1, - 394.3, - 382.8, - 374.1, - 367.6, - 361.3, - 356.2, - 352.1, - 350.1, - 349.6, - 349.4, - 349.9, - 350.2, - 349.8, - 348.8, - 346.4, - 342.7, - 340.7, - 339.8, - 338.9, - 338.6, - 338.3, - 337.5, - 335.6, - 333.6, - 331.5, - 328.7, - 325.2, - 321.4, - 317.5, - 313.5, - 308.8, - 303.8, - 298.4, - 292.2, - 285.2, - 278.3, - 271.9, - 265.9, - 260.3, - 255.2, - 250.7, - 246.6, - 242.5, - 239, - 236.3, - 234.4, - 232.6, - 230.5, - 227.9, - 224.8, - 219.8, - 214.7, - 211.1, - 207, - 204.2, - 202.2, - 200.5, - 198.9, - 197.2, - 195.3, - 193, - 190.6, - 188.4, - 187.1, - 186.5, - 185.6, - 183.8, - 181.9, - 180.1, - 178.6, - 177.1, - 174.5, - 172, - 168.4, - 163.1, - 159.5, - 157, - 156.1, - 155, - 153.6, - 154.7, - 159.3, - 164.3, - 162.7, - 159.8, - 156.3, - 152.4, - 148.5, - 145.2, - 142.3, - 139.5, - 138.2, - 138.7, - 138.4, - 138, - 138, - 138.6, - 138.7, - 138.4, - 138.3, - 137.1, - 135.4, - 133.9, - 132.3, - 130.5, - 129.2, - 128.8, - 127.8, - 126.3, - 126.2, - 127.3, - 128.2, - 128.1, - 127, - 125.6, - 124.6, - 122.8, - 120, - 117.3, - 114.4, - 112, - 111, - 111.4, - 111.7, - 112.1, - 112.5, - 112.8, - 113.1, - 113.4, - 113.8, - 114.2, - 114.7, - 115.3, - 647.7, - 648.1, - 648.3, - 648.3, - 648.3, - 648.1, - 647.9, - 647.8, - 647.7, - 647.5, - 647.3, - 647.1, - 647, - 647.2, - 647.5, - 647.8, - 648, - 648, - 648, - 648.1, - 648.2, - 648.4, - 648.6, - 648.7, - 648.8, - 648.7, - 648.7, - 648.9, - 649, - 649, - 649, - 648.9, - 648.7, - 648.4, - 647.9, - 647.7, - 647.4, - 647.1, - 646.9, - 646.9, - 646.8, - 646.2, - 645.5, - 644.9, - 644.3, - 643.6, - 643.3, - 643, - 642.4, - 641.4, - 640, - 639.1, - 638.1, - 636.9, - 633.5, - 624.2, - 613, - 603.8, - 600.5, - 598.1, - 596.3, - 594.8, - 593.8, - 592.6, - 590.7, - 588.2, - 585.2, - 581.1, - 578, - 576, - 574.8, - 573.1, - 571.2, - 568.4, - 565.7, - 563.3, - 559.7, - 555.4, - 552.1, - 548.4, - 546, - 543.8, - 541.7, - 539.5, - 537.1, - 534.9, - 532, - 529.8, - 527.8, - 525.4, - 522, - 517.8, - 515, - 513.1, - 511.5, - 510.2, - 508.9, - 507.4, - 505.6, - 503.8, - 501.9, - 499.8, - 497.6, - 495.7, - 493.5, - 490.8, - 488, - 486.1, - 484.5, - 482.2, - 480.2, - 478.3, - 476.1, - 474.1, - 470.2, - 455.2, - 437.2, - 425.1, - 421.9, - 424.1, - 426.8, - 427.8, - 423.8, - 417.1, - 406.2, - 393.3, - 381.7, - 373.7, - 367.3, - 359.6, - 352.9, - 351.4, - 351.3, - 351.6, - 352.2, - 352.5, - 352.3, - 351.3, - 349.2, - 346.3, - 343, - 341.1, - 340.4, - 340.2, - 339.7, - 338.9, - 337.5, - 335.7, - 334.1, - 332.2, - 329.5, - 326.1, - 322.2, - 318.2, - 314.3, - 310.3, - 305.4, - 300.4, - 295, - 288.2, - 281.5, - 275.7, - 269.8, - 263.5, - 258.1, - 253.6, - 248.9, - 244.8, - 241, - 237.9, - 235.6, - 233.6, - 231.3, - 228.8, - 225.8, - 221, - 215.8, - 212.8, - 209.5, - 206.5, - 204.4, - 202.5, - 200.6, - 198.8, - 196.9, - 194.8, - 192.8, - 190.8, - 189.7, - 189.4, - 188.1, - 186.4, - 184.9, - 183.8, - 182.1, - 179.7, - 177.2, - 174.6, - 169.7, - 164.9, - 161.6, - 160.8, - 161.2, - 162, - 161.3, - 164.4, - 168.5, - 168.9, - 167.3, - 163.7, - 159.4, - 155, - 151.1, - 147.7, - 144.3, - 141.7, - 141.2, - 140.8, - 140.7, - 140.7, - 140.5, - 140.4, - 140.5, - 140.2, - 139.1, - 137.6, - 135.6, - 134.1, - 132.5, - 130.8, - 130.6, - 130.6, - 128.9, - 127.7, - 129, - 130.7, - 130.3, - 129.6, - 129.1, - 127.2, - 125.1, - 122.5, - 119.5, - 116.5, - 114.4, - 113, - 113.1, - 113.4, - 113.7, - 114.1, - 114.4, - 114.7, - 114.9, - 115.3, - 115.7, - 116.1, - 116.7, - 648.6, - 648.8, - 648.8, - 648.7, - 648.6, - 648.6, - 648.5, - 648.3, - 648.1, - 648, - 647.8, - 647.7, - 647.8, - 648, - 648.4, - 648.6, - 648.7, - 648.7, - 648.7, - 648.7, - 648.9, - 649, - 649.2, - 649.3, - 649.3, - 649.3, - 649.5, - 649.6, - 649.6, - 649.6, - 649.6, - 649.5, - 649.4, - 649.1, - 648.7, - 648.5, - 648.3, - 648.1, - 648, - 647.9, - 647.8, - 647.4, - 646.7, - 645.9, - 645.3, - 644.7, - 644.3, - 643.9, - 643.2, - 642, - 640.7, - 639.6, - 638.7, - 636.6, - 630.5, - 621.4, - 612, - 607.7, - 605.9, - 603.8, - 601.7, - 600.1, - 599, - 597.2, - 595.1, - 592.3, - 588.7, - 584.9, - 582.5, - 580.9, - 578.9, - 576.3, - 574.5, - 572.8, - 571.9, - 569.6, - 565.2, - 559.8, - 554.7, - 550, - 546.8, - 544.4, - 542.4, - 540, - 537.2, - 534.7, - 532.5, - 530.4, - 528.3, - 526.2, - 523.5, - 520.9, - 517.5, - 514.3, - 511.6, - 509.6, - 508.2, - 506.8, - 505.4, - 503.8, - 501.6, - 499.6, - 497.4, - 495.5, - 493.4, - 491.2, - 488.9, - 486.7, - 484.9, - 483.1, - 481.2, - 479, - 476.7, - 475.1, - 473.2, - 467.5, - 453.8, - 435.8, - 425.7, - 425.4, - 428.1, - 429.5, - 427, - 422.7, - 415.5, - 403.3, - 391.2, - 384.4, - 373.8, - 360.5, - 354.4, - 353.8, - 353.7, - 353.5, - 354.8, - 355.3, - 354.4, - 353, - 351.4, - 349, - 345.8, - 343.7, - 342.3, - 341.5, - 340.8, - 339.7, - 338.6, - 337.5, - 336.2, - 334.7, - 332.6, - 330.1, - 326.8, - 322.7, - 318.9, - 315.2, - 311.5, - 307.1, - 302.5, - 297.6, - 292.1, - 286.1, - 280.1, - 273.9, - 267.9, - 262.1, - 256.7, - 251.5, - 247.1, - 243.1, - 239.8, - 237.1, - 234.4, - 231.9, - 229.2, - 224.9, - 220.4, - 216.9, - 213.2, - 209.7, - 207.3, - 206.1, - 204.7, - 202.8, - 200.4, - 197.8, - 195.8, - 194.1, - 192.9, - 192, - 191.5, - 190.4, - 188.6, - 187.2, - 185.9, - 184.1, - 182, - 179.7, - 176.5, - 172.6, - 168.8, - 166.7, - 167, - 167.5, - 167, - 168.3, - 172.4, - 173.9, - 173.1, - 170.3, - 166.1, - 161.7, - 157.2, - 153.1, - 149.3, - 145.9, - 144.1, - 143.5, - 143.1, - 143, - 142.8, - 142.7, - 142.4, - 142, - 140.8, - 139.5, - 138, - 136.1, - 134.5, - 133.1, - 132.4, - 132.9, - 131.4, - 129.7, - 130.4, - 132, - 132.3, - 131.4, - 130.4, - 128.9, - 127.4, - 125.2, - 121.5, - 118.8, - 117, - 115.4, - 114.9, - 115.1, - 115.5, - 115.8, - 116, - 116.3, - 116.5, - 116.8, - 117.2, - 117.6, - 118, - 649.3, - 649.3, - 649.1, - 648.9, - 648.9, - 648.9, - 648.8, - 648.6, - 648.5, - 648.3, - 648.3, - 648.4, - 648.5, - 648.5, - 648.8, - 649, - 649.1, - 649.2, - 649.2, - 649.3, - 649.3, - 649.6, - 649.7, - 649.6, - 649.6, - 649.8, - 650, - 650.1, - 650.2, - 650.3, - 650.2, - 650, - 649.8, - 649.7, - 649.5, - 649.3, - 649.2, - 649.1, - 649, - 648.8, - 648.6, - 648.3, - 647.7, - 646.9, - 646.4, - 646, - 645.5, - 644.5, - 643.7, - 642.6, - 641.3, - 640.2, - 638.2, - 633.6, - 626.9, - 619.8, - 613.8, - 611.5, - 610.2, - 608.7, - 607.2, - 606.5, - 605, - 602, - 599.4, - 596.5, - 592.4, - 589.6, - 587.9, - 585.1, - 582.2, - 578.8, - 575.9, - 574.8, - 573.7, - 572.1, - 566.8, - 559.6, - 554.8, - 551.2, - 547.7, - 545, - 542.9, - 540.3, - 537.7, - 535.5, - 533.2, - 531.1, - 529, - 526.6, - 524.1, - 522, - 518.6, - 515.5, - 513, - 510.3, - 507.9, - 506.3, - 504.8, - 503.2, - 501.1, - 499.4, - 497.5, - 495.6, - 493.8, - 491.7, - 489.7, - 487.5, - 485.6, - 483.7, - 481.6, - 479.5, - 477.2, - 475.6, - 473.8, - 469.9, - 464.4, - 450.1, - 434.6, - 428.3, - 429.4, - 431.2, - 428.8, - 425.9, - 420.6, - 411.2, - 401, - 396.7, - 385.7, - 363.9, - 357.9, - 357.3, - 356.5, - 355.7, - 356.9, - 357.9, - 357.1, - 355.5, - 353.7, - 351.9, - 348.5, - 346.1, - 344.2, - 342.5, - 341.7, - 340.7, - 339.7, - 338.7, - 337.8, - 336.9, - 335.1, - 333, - 330.8, - 327.4, - 323.3, - 319.8, - 316.1, - 312.5, - 308.6, - 304.2, - 299.9, - 295.2, - 290, - 284, - 278.2, - 272.5, - 266.8, - 261.1, - 255, - 249.7, - 245.7, - 242.3, - 239, - 235.9, - 232.5, - 228.8, - 225.2, - 221.9, - 217.9, - 213.7, - 210.8, - 209.8, - 208.8, - 207.4, - 205.3, - 202.9, - 200.4, - 197.8, - 196.2, - 195.2, - 193.8, - 192.7, - 191.3, - 189.8, - 189, - 187.8, - 186.5, - 184.7, - 181.7, - 178.3, - 174.9, - 172.1, - 171, - 171.4, - 173, - 174.1, - 176.7, - 177, - 176, - 173.6, - 170.6, - 167.1, - 163.2, - 159, - 154.9, - 151.1, - 148, - 146.6, - 145.6, - 145.2, - 145.1, - 145.1, - 144.7, - 144.2, - 143.2, - 142, - 140.8, - 139.1, - 137.6, - 136.1, - 134.7, - 134.9, - 134.1, - 132.5, - 132, - 133, - 133.6, - 133, - 131.4, - 130.2, - 129.9, - 129.1, - 126.4, - 122, - 119.1, - 117.4, - 116.7, - 116.7, - 117.1, - 117.6, - 117.8, - 118, - 118.2, - 118.5, - 118.7, - 119.1, - 119.4, - 649.8, - 649.6, - 649.5, - 649.3, - 649.3, - 649.2, - 649, - 648.9, - 648.8, - 648.9, - 649.1, - 649.1, - 649.1, - 649.3, - 649.3, - 649.4, - 649.6, - 649.8, - 649.7, - 649.7, - 649.8, - 650.1, - 650.1, - 650.1, - 650.2, - 650.4, - 650.4, - 650.4, - 650.5, - 650.5, - 650.5, - 650.5, - 650.6, - 650.5, - 650.5, - 650.5, - 650.3, - 650.1, - 650, - 649.8, - 649.4, - 649.1, - 648.7, - 648.1, - 647.6, - 647.2, - 646.4, - 645.4, - 644.4, - 643.4, - 641.8, - 640.1, - 635.9, - 629.7, - 624.1, - 620.3, - 617.4, - 616, - 614.8, - 613.5, - 612.6, - 611.8, - 609.7, - 606.7, - 603.6, - 600.8, - 597.8, - 595.1, - 592.2, - 588.1, - 584.8, - 581.2, - 577, - 574.4, - 572.6, - 569.7, - 564.1, - 559.1, - 555.3, - 552.2, - 548.9, - 545.7, - 542.7, - 539.8, - 537.7, - 536.1, - 533.7, - 531.4, - 529.1, - 526.4, - 524, - 521.5, - 518.6, - 516.3, - 513.9, - 511.2, - 508.6, - 506, - 504.4, - 502.4, - 500.5, - 498.7, - 496.9, - 495.4, - 493.8, - 491.9, - 490, - 488.2, - 486.2, - 484, - 481.9, - 480.1, - 478.1, - 476, - 473.8, - 470.1, - 465.9, - 459.1, - 447.7, - 437.5, - 434.9, - 437.4, - 432.1, - 427.4, - 423.1, - 415.9, - 409, - 406, - 392.5, - 367.3, - 363.5, - 363.3, - 360.6, - 358.5, - 358.7, - 359.6, - 359.6, - 358.2, - 356.4, - 354.9, - 351.9, - 347.7, - 345.4, - 343.5, - 342.5, - 341.5, - 340.5, - 339.7, - 339.1, - 338.3, - 337.2, - 335.5, - 333.6, - 331.3, - 327.9, - 324.2, - 320.7, - 317.1, - 313.5, - 309.6, - 305.7, - 301.6, - 297.5, - 293.1, - 287.9, - 282.4, - 276.9, - 271.5, - 265.5, - 259.5, - 253.8, - 249.5, - 244.9, - 241.2, - 237.9, - 234.4, - 231, - 227.7, - 224.5, - 220.9, - 216.1, - 213.1, - 212.5, - 211.5, - 210, - 208.1, - 206.1, - 203.9, - 201.6, - 199.6, - 197.5, - 195.4, - 193.5, - 192, - 191.7, - 190.9, - 190.4, - 188.9, - 186.3, - 183.3, - 179.8, - 177.4, - 177.4, - 179.5, - 181.9, - 181.3, - 180.2, - 179.3, - 178.2, - 175.7, - 172.6, - 169.8, - 167, - 163.7, - 160.3, - 156.6, - 152.9, - 149.6, - 148.2, - 147.8, - 147.3, - 147.1, - 147, - 146.5, - 145.5, - 144.5, - 143.6, - 142.5, - 141.3, - 140, - 137.7, - 137.3, - 136.6, - 135.7, - 134, - 134.1, - 134.8, - 134.7, - 133.6, - 132.2, - 131.3, - 130.8, - 130.1, - 126.5, - 121.3, - 119.1, - 118, - 118.2, - 118.8, - 119.3, - 119.6, - 119.7, - 119.9, - 120.1, - 120.3, - 120.6, - 120.9, - 650, - 649.9, - 649.8, - 649.6, - 649.6, - 649.6, - 649.5, - 649.5, - 649.5, - 649.4, - 649.4, - 649.5, - 649.7, - 650, - 650, - 650.1, - 650.2, - 650.5, - 650.5, - 650.6, - 650.6, - 650.7, - 650.8, - 651, - 651, - 651.2, - 651.1, - 650.9, - 650.9, - 650.9, - 650.8, - 650.9, - 651, - 650.9, - 650.8, - 650.9, - 650.8, - 650.7, - 650.6, - 650.5, - 650.2, - 649.9, - 649.6, - 648.8, - 648.3, - 647.6, - 646.7, - 645.5, - 644.2, - 642.8, - 640.7, - 637.6, - 633.2, - 628.6, - 625.4, - 623.2, - 621.9, - 620.6, - 618.8, - 617.4, - 616.3, - 615.4, - 614.1, - 611.4, - 607.7, - 604.6, - 602.2, - 599.4, - 594.6, - 590, - 587.7, - 584.5, - 578.5, - 573, - 569.5, - 566.6, - 564, - 560.5, - 556.8, - 553.2, - 549.4, - 545.8, - 542.9, - 539.8, - 538, - 536.2, - 533.5, - 531.5, - 529.1, - 526.2, - 523.4, - 520.4, - 518.4, - 516.6, - 514.1, - 511.1, - 508.7, - 506.3, - 504.2, - 501.7, - 499.9, - 497.9, - 496, - 494.8, - 493.2, - 491.4, - 489.7, - 488.2, - 486.4, - 484, - 481.9, - 480.2, - 478.5, - 476.5, - 473.9, - 470.3, - 465.5, - 460.6, - 456.4, - 451, - 448.1, - 446.8, - 438.5, - 429.9, - 424.9, - 419.3, - 414.4, - 412, - 399.9, - 375.7, - 372.7, - 376.3, - 374.6, - 365.1, - 361.9, - 361.8, - 362, - 360.7, - 359.5, - 358.4, - 356.6, - 352.4, - 347.5, - 344.7, - 343.3, - 342.2, - 341.2, - 340.6, - 340.1, - 339.5, - 338.7, - 337.7, - 336.2, - 334.3, - 331.7, - 328.5, - 325, - 321.6, - 318.2, - 314.6, - 311.1, - 307.2, - 303.3, - 299.6, - 295.7, - 291.2, - 286.1, - 280.7, - 275.2, - 269.3, - 263.5, - 257.9, - 253.1, - 248.8, - 244.6, - 239.9, - 236.5, - 233.3, - 230.1, - 227.1, - 223.7, - 219, - 215.8, - 214.8, - 213.3, - 212.1, - 210.9, - 209.4, - 207.6, - 205.3, - 203.5, - 201, - 198.1, - 195.7, - 194.7, - 193.9, - 193.2, - 192.1, - 189.9, - 187.6, - 185.1, - 184.3, - 186.8, - 187.3, - 187.3, - 186.4, - 183.9, - 182, - 180.7, - 177.8, - 174.6, - 171.5, - 168.7, - 167.1, - 165.3, - 161.9, - 158.4, - 154.4, - 151.4, - 150.3, - 149.7, - 149, - 148.6, - 148.4, - 147.8, - 147, - 146, - 145, - 144.1, - 143.2, - 141.6, - 141.2, - 139.9, - 138.2, - 136.4, - 135.3, - 135.7, - 135.4, - 134.5, - 133.5, - 132.7, - 132.9, - 133.4, - 130.8, - 125.5, - 122.2, - 121.1, - 121, - 120.9, - 121.1, - 121.3, - 121.5, - 121.6, - 121.7, - 121.8, - 122.1, - 122.4, - 650.2, - 650.1, - 650, - 649.9, - 649.9, - 649.9, - 650, - 650.1, - 650.1, - 650, - 649.9, - 650, - 650.2, - 650.3, - 650.4, - 650.6, - 650.8, - 650.8, - 651, - 651.1, - 651.2, - 651.3, - 651.4, - 651.5, - 651.6, - 651.6, - 651.6, - 651.5, - 651.4, - 651.4, - 651.3, - 651.3, - 651.5, - 651.3, - 651, - 651, - 650.9, - 650.9, - 650.9, - 650.9, - 650.7, - 650.3, - 649.9, - 649.2, - 648.7, - 648, - 646.6, - 644.5, - 642.6, - 640.8, - 638.6, - 636, - 633.6, - 630.9, - 628.3, - 626.9, - 626, - 624.2, - 621.9, - 620.5, - 619.3, - 618.3, - 617, - 614.8, - 612.2, - 610.3, - 608.6, - 604.5, - 597.7, - 593, - 590, - 586.5, - 580, - 572.9, - 569.4, - 566.6, - 564.5, - 561.9, - 558.3, - 553.5, - 549.1, - 545.5, - 543.1, - 540.1, - 538.1, - 535.8, - 533.4, - 531.3, - 528.8, - 526.1, - 523.1, - 520, - 517.9, - 516.1, - 513.7, - 510.9, - 508.8, - 506.9, - 504.7, - 501.7, - 499.1, - 496.9, - 495.2, - 494.1, - 492.4, - 490.6, - 489.2, - 487.8, - 486, - 483.6, - 481.7, - 480.2, - 478.7, - 476.5, - 473.6, - 469.7, - 465.4, - 460.9, - 457.2, - 454, - 452, - 450.3, - 445.3, - 434.9, - 425.9, - 420.6, - 416.7, - 413.6, - 402.5, - 386.2, - 385.6, - 388.2, - 391.9, - 382.9, - 371.1, - 365, - 364.4, - 363.1, - 362.1, - 361.4, - 359.1, - 355.8, - 352, - 348.1, - 345, - 343.3, - 342.3, - 341.7, - 341, - 340.3, - 339.7, - 339.1, - 338.4, - 336.7, - 334.8, - 332.1, - 328.9, - 325.8, - 322.7, - 319.5, - 316, - 312.6, - 309.1, - 305.5, - 301.6, - 297.9, - 294.2, - 289.8, - 284.4, - 278.7, - 272.8, - 266.9, - 260.9, - 256.2, - 252.1, - 247.4, - 242.6, - 238.7, - 235.5, - 232.5, - 229.8, - 226.7, - 222.8, - 219.4, - 216.2, - 214.5, - 213.7, - 213.3, - 212.5, - 210.7, - 208.7, - 206.7, - 204.4, - 201.7, - 198.8, - 196.8, - 195.9, - 194.7, - 193.2, - 191.4, - 190.1, - 191.1, - 193.3, - 192.7, - 192.8, - 191.7, - 188.9, - 185.9, - 183.8, - 180.8, - 177.4, - 174, - 171.6, - 170.6, - 169.8, - 167.5, - 163.9, - 160.2, - 156.5, - 153.5, - 152.1, - 151.6, - 150.8, - 150, - 149.6, - 148.7, - 147.8, - 146.5, - 145.8, - 145.7, - 145.6, - 145.2, - 143.6, - 141.2, - 138.8, - 137, - 136.7, - 136.3, - 135.5, - 135.6, - 135.9, - 136.4, - 136.1, - 134.5, - 131.6, - 128.1, - 125.4, - 124.1, - 122.9, - 122.9, - 123.1, - 123.2, - 123.3, - 123.4, - 123.4, - 123.5, - 123.7, - 650.3, - 650.3, - 650.2, - 650.2, - 650.2, - 650.3, - 650.6, - 650.5, - 650.6, - 650.5, - 650.4, - 650.4, - 650.4, - 650.5, - 650.8, - 651, - 651, - 651.1, - 651.2, - 651.4, - 651.5, - 651.7, - 651.8, - 651.8, - 651.9, - 652, - 652, - 651.9, - 651.9, - 651.8, - 651.9, - 652.1, - 652, - 651.8, - 651.7, - 651.6, - 651.4, - 651.3, - 651.2, - 651.1, - 651, - 650.6, - 650.1, - 649.5, - 648.8, - 647.9, - 646, - 643.9, - 642.1, - 640.6, - 639.4, - 637.6, - 635.5, - 633.5, - 631.4, - 630, - 629.1, - 627.4, - 625.2, - 623.4, - 622.9, - 621.1, - 619.3, - 617.3, - 614.7, - 613.3, - 611.6, - 607.5, - 602.8, - 597.6, - 592.3, - 588.3, - 580.8, - 575.3, - 572.1, - 568.7, - 564.9, - 562.2, - 558.5, - 553.9, - 549.7, - 546.1, - 543.3, - 540.6, - 538.3, - 535.6, - 533.2, - 530.8, - 528.4, - 525.9, - 523.2, - 519.9, - 517.5, - 515.5, - 513.2, - 510.9, - 508.8, - 506.8, - 504.6, - 502, - 499.5, - 496.9, - 494.6, - 493.3, - 491.8, - 490.3, - 489, - 487.4, - 485.2, - 483.1, - 481.2, - 479.8, - 478, - 475.3, - 472.4, - 469.2, - 465.6, - 461.5, - 457.6, - 455.3, - 453.7, - 451.8, - 448.3, - 437.9, - 427.6, - 421.8, - 418.5, - 413, - 396.9, - 393.1, - 397.3, - 398.2, - 402.7, - 398.4, - 386.1, - 373.4, - 369.3, - 366.4, - 365.1, - 364.6, - 362.6, - 358.3, - 355.7, - 353.1, - 350.1, - 346.7, - 344.1, - 343.2, - 342.7, - 342.6, - 341.5, - 340.2, - 339.6, - 338.7, - 337.3, - 335.1, - 332.2, - 329.1, - 326.1, - 323.5, - 320.8, - 317.6, - 314.3, - 311.6, - 308.4, - 304.1, - 300.1, - 296.4, - 292.3, - 287.2, - 281.8, - 276.2, - 270.1, - 264.2, - 259.2, - 254.9, - 250.4, - 246, - 241.6, - 237.9, - 234.8, - 232, - 229.1, - 225.6, - 221.6, - 218.3, - 216.7, - 216.2, - 216.1, - 215.1, - 213.3, - 211, - 209.3, - 207.4, - 204.5, - 201.3, - 199, - 197.1, - 195.9, - 195.2, - 197.1, - 199.2, - 199.6, - 197.6, - 198, - 196.7, - 194.2, - 191.3, - 188.6, - 185.8, - 182.5, - 179, - 176.2, - 174.7, - 173.7, - 171.9, - 169.1, - 165.8, - 162.2, - 158.6, - 155.5, - 154, - 153.2, - 152.2, - 151.2, - 150.4, - 149.8, - 148.5, - 147.7, - 147.5, - 147.3, - 146.5, - 145.1, - 143.4, - 141.2, - 139.3, - 139.1, - 138.6, - 138.4, - 138.8, - 139.5, - 139.6, - 138.8, - 137.4, - 135.3, - 133.3, - 130.1, - 127, - 125.4, - 124.8, - 125, - 125, - 125, - 125, - 125, - 125.1, - 125.2, - 650.5, - 650.5, - 650.4, - 650.4, - 650.5, - 650.7, - 650.7, - 650.8, - 650.8, - 650.8, - 650.8, - 650.8, - 650.8, - 651, - 651.2, - 651.2, - 651.3, - 651.4, - 651.5, - 651.5, - 651.6, - 651.8, - 651.9, - 652, - 652.2, - 652.3, - 652.4, - 652.4, - 652.4, - 652.4, - 652.5, - 652.6, - 652.5, - 652.4, - 652.4, - 652.2, - 652.2, - 651.9, - 651.7, - 651.5, - 651.4, - 651, - 650.3, - 649.8, - 649, - 648, - 646.6, - 645, - 643.1, - 641.4, - 640.6, - 639.4, - 637.7, - 635.9, - 634, - 632.4, - 631.3, - 629.5, - 628.2, - 627, - 625.7, - 623.6, - 620.8, - 617.9, - 614.9, - 612.7, - 609.5, - 605.5, - 601.8, - 596.6, - 592, - 587.9, - 582.3, - 576.8, - 573.3, - 569.6, - 565, - 561.3, - 558, - 554.1, - 550.1, - 546.1, - 543, - 540.5, - 538, - 535.5, - 533.3, - 530.6, - 528.2, - 525.9, - 523.3, - 520.6, - 517.9, - 515.8, - 513.5, - 511.3, - 508.9, - 506.7, - 504.5, - 502.2, - 499.8, - 497.6, - 495.1, - 492.8, - 491.6, - 490.4, - 489.1, - 487.4, - 484.9, - 482.8, - 481, - 479.5, - 477.7, - 474.9, - 472, - 468.9, - 465.5, - 462, - 460.4, - 458.5, - 455.8, - 453.2, - 449.9, - 441.8, - 430.2, - 425.6, - 421.2, - 415, - 401.7, - 400.8, - 404.6, - 405, - 407.6, - 404.5, - 397, - 389.6, - 383.1, - 373.1, - 369.2, - 368.1, - 367.4, - 363.1, - 360, - 358.7, - 356.6, - 352.4, - 347.5, - 345.9, - 347.5, - 349, - 347.1, - 343.3, - 341.3, - 340.2, - 339.2, - 337.6, - 335, - 332.4, - 329.6, - 326.6, - 324, - 321.6, - 318.9, - 316, - 313.1, - 310.2, - 306.5, - 302.4, - 298.5, - 294.2, - 289.5, - 284.5, - 278.9, - 273.4, - 268, - 262.6, - 257.8, - 253.4, - 249, - 244.3, - 240.2, - 236.6, - 233.6, - 231.2, - 228, - 223.7, - 220.6, - 219.6, - 219.5, - 219.1, - 217.9, - 216.1, - 213.9, - 212.3, - 210.5, - 207.7, - 204.7, - 201.6, - 198.9, - 198.2, - 201.2, - 203.3, - 203.3, - 202.1, - 202.1, - 202, - 200, - 196.9, - 193.8, - 190.9, - 187.5, - 183.8, - 181, - 179.7, - 177.9, - 176.2, - 173.8, - 171.6, - 168.7, - 165.3, - 161.5, - 157.7, - 155.7, - 154.7, - 153.6, - 152.4, - 151.7, - 151.1, - 149.7, - 148.9, - 148.5, - 147.7, - 146.2, - 145, - 143.6, - 142.1, - 142.1, - 142.1, - 142.7, - 143.5, - 143.7, - 143.1, - 141.8, - 140.1, - 138.4, - 136.1, - 133.5, - 129.7, - 127.5, - 126.8, - 126.7, - 126.7, - 126.7, - 126.7, - 126.6, - 126.6, - 126.7, - 650.6, - 650.6, - 650.6, - 650.7, - 650.7, - 650.8, - 650.8, - 650.9, - 651, - 651.2, - 651.3, - 651.1, - 651.2, - 651.4, - 651.4, - 651.6, - 651.7, - 651.7, - 651.7, - 651.7, - 651.8, - 651.9, - 652.1, - 652.4, - 652.8, - 652.5, - 652.6, - 652.7, - 652.7, - 652.8, - 652.9, - 652.8, - 652.8, - 652.9, - 652.8, - 652.6, - 652.5, - 652.4, - 652.2, - 651.9, - 651.8, - 651.6, - 651, - 650.4, - 649.8, - 648.8, - 647.5, - 646.4, - 644.8, - 642.6, - 641.4, - 640.5, - 639.4, - 637.8, - 635.7, - 634.1, - 632.3, - 630.2, - 628.7, - 627.5, - 625.8, - 623.1, - 619.4, - 616.2, - 613.6, - 611.3, - 607.1, - 602.7, - 598.7, - 593.8, - 589.2, - 584.9, - 580.3, - 575.4, - 571.5, - 567.7, - 563.1, - 559.1, - 555.9, - 552.7, - 549.6, - 546, - 543.2, - 540.3, - 537.5, - 535.1, - 533, - 530.4, - 528.1, - 526, - 523.8, - 521.3, - 518.8, - 516.6, - 514.1, - 512, - 509.8, - 507.3, - 504.7, - 502.6, - 500.2, - 498.1, - 495.9, - 493.3, - 491.9, - 490.6, - 489.2, - 487.1, - 484.6, - 482.6, - 480.8, - 479.4, - 477.1, - 474.6, - 471.7, - 467.9, - 465.6, - 465.5, - 465.6, - 462.1, - 457.5, - 454.2, - 451.4, - 445.4, - 434.3, - 434.9, - 428.3, - 420.1, - 413.9, - 409.7, - 411.1, - 410.6, - 410.3, - 408.3, - 406.8, - 405.6, - 400.8, - 385.6, - 373.9, - 371.5, - 370.9, - 368.6, - 366.3, - 364.9, - 362.6, - 359.2, - 354.5, - 351.7, - 352.7, - 352.6, - 350.5, - 347.8, - 346.8, - 345.5, - 341.6, - 339.6, - 337.5, - 335.1, - 332.6, - 329.8, - 326.8, - 324.4, - 322.2, - 319.7, - 316.9, - 313.9, - 311.1, - 308.4, - 304.9, - 300.6, - 296.1, - 291.5, - 286.7, - 281.6, - 276.2, - 270.6, - 265.3, - 260, - 255.4, - 252, - 247.6, - 242.4, - 238.1, - 235.5, - 233.5, - 230.6, - 226.7, - 225, - 224.4, - 223.5, - 222.4, - 221, - 219.2, - 217.1, - 215, - 213.5, - 211.1, - 208.3, - 205.1, - 203.2, - 204.9, - 206.1, - 206, - 205.5, - 205.1, - 206.1, - 204.7, - 201.9, - 198.8, - 195.7, - 192.6, - 189.4, - 186.6, - 184.6, - 182.8, - 181.4, - 179.6, - 177.7, - 175.2, - 172, - 168.2, - 164.1, - 160, - 157.2, - 155.7, - 154.5, - 153.7, - 152.9, - 151.9, - 150.7, - 149.7, - 149.1, - 148.2, - 146.8, - 145.7, - 144.8, - 144.2, - 144.5, - 145.5, - 145.9, - 145.4, - 144.7, - 143.8, - 142.4, - 140.8, - 138.4, - 135.9, - 132.8, - 130, - 128.9, - 128.6, - 128.5, - 128.4, - 128.3, - 128.2, - 128.1, - 128.1, - 650.8, - 650.8, - 650.9, - 650.9, - 650.9, - 650.9, - 651, - 651.1, - 651.1, - 651.3, - 651.4, - 651.5, - 651.6, - 651.7, - 651.7, - 651.8, - 651.9, - 651.9, - 651.9, - 652, - 652, - 652.2, - 652.4, - 652.6, - 652.8, - 652.7, - 652.7, - 652.8, - 652.9, - 653, - 653.1, - 653.1, - 653.2, - 653.3, - 653.1, - 653.1, - 653, - 652.8, - 652.7, - 652.5, - 652.3, - 652.3, - 651.7, - 651.1, - 650.7, - 649.8, - 648.2, - 647.4, - 646.2, - 644.1, - 642.4, - 641.4, - 640.2, - 638.2, - 635.9, - 634.3, - 632.3, - 629.5, - 627.7, - 626.4, - 624, - 621.2, - 617.6, - 614.3, - 611.5, - 608.4, - 604, - 599.8, - 595.5, - 591, - 587, - 582.3, - 577.6, - 573.5, - 569.5, - 565.3, - 561, - 557.5, - 554.4, - 551.3, - 548.6, - 545.3, - 542.2, - 539.7, - 537.5, - 535, - 532.9, - 530.6, - 528.4, - 526.5, - 524.3, - 521.8, - 519.3, - 517.4, - 514.9, - 512.6, - 510.1, - 507.4, - 505, - 502.9, - 500.7, - 498.6, - 496, - 493.8, - 492.5, - 490.7, - 489.3, - 487.2, - 484.8, - 482.9, - 481.1, - 479.1, - 476.8, - 474.5, - 471.1, - 468.2, - 469.3, - 470.1, - 468.4, - 465, - 460, - 455.2, - 452.3, - 449.2, - 444.4, - 441.9, - 436.4, - 427.3, - 420, - 415.6, - 416.8, - 415.8, - 413.6, - 413.2, - 418.3, - 419.7, - 409.9, - 396.8, - 384, - 376.5, - 374, - 372.7, - 370.3, - 369.9, - 368.5, - 364.6, - 360.4, - 357.6, - 356.3, - 355.6, - 353.4, - 353.4, - 354.6, - 353.4, - 348.3, - 342.5, - 339.3, - 337.5, - 335.1, - 332.4, - 329.6, - 326.9, - 324.6, - 322.5, - 320.3, - 317.7, - 314.9, - 312.4, - 310, - 307.1, - 303, - 298.3, - 293.6, - 288.9, - 284.1, - 278.7, - 273.3, - 268, - 262.7, - 258.4, - 255.3, - 250.9, - 245, - 239.8, - 236.7, - 234.7, - 232.9, - 230.9, - 228.3, - 227.3, - 226.3, - 224.9, - 223.4, - 221.9, - 219.7, - 218, - 216.8, - 215, - 212.7, - 210.5, - 210.3, - 210.3, - 209.6, - 208.5, - 207.7, - 208.1, - 207.6, - 205.7, - 203.5, - 200.9, - 198.2, - 195.2, - 192.3, - 190.9, - 189.8, - 188.7, - 186.3, - 183.2, - 180.5, - 177.3, - 173.8, - 170.3, - 166.5, - 162.4, - 159, - 156.3, - 154.9, - 154.2, - 153.7, - 152.8, - 151.3, - 150.3, - 149.7, - 148.7, - 147.4, - 146.8, - 146.3, - 145.9, - 146.3, - 146.7, - 146.4, - 145.5, - 144.5, - 143.4, - 142.4, - 141, - 139, - 137.3, - 134, - 130.4, - 130.1, - 130.4, - 130.1, - 129.9, - 129.8, - 129.7, - 129.6, - 650.9, - 650.9, - 651, - 651, - 651, - 651.1, - 651.1, - 651.2, - 651.2, - 651.3, - 651.4, - 651.6, - 651.7, - 651.8, - 651.9, - 652, - 652, - 652, - 652.1, - 652.2, - 652.3, - 652.6, - 652.6, - 652.7, - 652.7, - 652.8, - 652.9, - 652.9, - 652.9, - 653, - 653.2, - 653.3, - 653.5, - 653.5, - 653.5, - 653.6, - 653.5, - 653.4, - 653.4, - 653.3, - 653.1, - 653, - 652.7, - 652, - 651.6, - 650.8, - 649.3, - 648.2, - 647.2, - 645.4, - 643.1, - 641.7, - 640, - 637.6, - 634.9, - 633, - 630.4, - 627.9, - 626.3, - 624.5, - 622.1, - 619.1, - 615.6, - 612, - 609, - 605.6, - 601.1, - 596.3, - 592.1, - 587.8, - 583.7, - 579.4, - 575.5, - 571.6, - 567.8, - 563.1, - 559.6, - 556.9, - 553.8, - 550.7, - 548, - 544.8, - 541.5, - 539.7, - 538.2, - 535.8, - 533.4, - 530.8, - 528.5, - 526.6, - 524.6, - 522.2, - 519.8, - 517.8, - 515.5, - 513, - 510.5, - 508, - 505.5, - 503.3, - 501.2, - 499.3, - 496.4, - 494.6, - 493.6, - 491.3, - 488.7, - 486.8, - 485, - 483.1, - 481.4, - 479.4, - 477.4, - 475, - 472.3, - 471.6, - 472.9, - 472.5, - 470.4, - 465.5, - 459.2, - 455.2, - 452.7, - 450.4, - 447.9, - 443.7, - 440.5, - 435.1, - 430.6, - 426, - 422.9, - 422.4, - 422, - 421.8, - 423.8, - 422.4, - 412.5, - 402.4, - 392.9, - 384.2, - 378.4, - 376.5, - 375.8, - 376.6, - 376.5, - 373.5, - 368.7, - 363.8, - 362, - 361.2, - 360.4, - 360.9, - 360, - 357, - 354.2, - 349.1, - 341.9, - 338.7, - 337, - 334.7, - 331.9, - 329.4, - 326.8, - 324.7, - 322.9, - 320.9, - 318.4, - 315.8, - 313.4, - 311.3, - 308.6, - 304.9, - 300.6, - 295.8, - 290.8, - 286, - 281.1, - 276, - 271.3, - 265.9, - 262.7, - 258.9, - 254.4, - 248.4, - 242.1, - 237.5, - 235.9, - 235.6, - 234.7, - 232.5, - 230.5, - 228.8, - 227.4, - 225.8, - 223.8, - 222.6, - 221.8, - 220.8, - 219.4, - 218, - 217.1, - 216, - 214.1, - 211.9, - 210.7, - 210.4, - 210.4, - 209.3, - 207.9, - 205.9, - 203.5, - 201.2, - 199.3, - 197.6, - 196.2, - 194.3, - 191.4, - 188.3, - 185.9, - 183, - 179.6, - 175.9, - 172.5, - 169, - 165.5, - 161.5, - 158, - 156, - 155.2, - 154.6, - 153.6, - 151.9, - 151, - 150.3, - 149.5, - 148.9, - 148.8, - 148.5, - 147.7, - 147.5, - 147.6, - 147.3, - 145.4, - 144, - 143.2, - 142.5, - 141.9, - 142, - 138.9, - 132.7, - 131.4, - 132, - 131.9, - 131.6, - 131.3, - 131.2, - 131.1, - 651, - 651, - 651.1, - 651.1, - 651.1, - 651.2, - 651.2, - 651.2, - 651.3, - 651.4, - 651.5, - 651.7, - 651.8, - 651.9, - 652, - 652, - 652.1, - 652.1, - 652.2, - 652.4, - 652.5, - 652.7, - 652.7, - 652.8, - 652.8, - 652.9, - 652.9, - 652.9, - 652.9, - 653.1, - 653.3, - 653.4, - 653.5, - 653.6, - 653.6, - 653.7, - 653.8, - 653.9, - 654.1, - 654.2, - 653.9, - 653.7, - 653.5, - 653.1, - 652.7, - 651.9, - 650.7, - 649.3, - 647.6, - 645.7, - 643.5, - 641.3, - 639, - 636.4, - 633.9, - 631.6, - 629.1, - 626.8, - 624.7, - 622.9, - 620.2, - 616.9, - 612.9, - 609, - 605.9, - 602.4, - 597.2, - 592.4, - 588.7, - 584.5, - 580.7, - 576.7, - 573, - 569.5, - 566.2, - 561.7, - 559, - 556.4, - 553.8, - 550.7, - 547.9, - 544.7, - 541.8, - 540.1, - 538.5, - 536.2, - 533.9, - 531.7, - 529.1, - 527.2, - 525.1, - 522.6, - 520.2, - 518.2, - 516.1, - 513.6, - 511.3, - 509.2, - 506.5, - 503.8, - 501.8, - 500, - 497, - 495.6, - 493.9, - 491.3, - 488.5, - 486.4, - 484.8, - 483.2, - 481.5, - 479.7, - 478, - 476.3, - 474.9, - 474.1, - 473.5, - 472.4, - 468.5, - 461.8, - 457, - 454.3, - 452.2, - 450.1, - 447.8, - 444.5, - 442.6, - 440.2, - 438, - 436.5, - 431.2, - 428.5, - 427.9, - 425.9, - 424.4, - 420.5, - 412.6, - 405.5, - 396.7, - 390.7, - 384.7, - 380.6, - 380.6, - 381.7, - 383, - 383, - 378.4, - 372, - 369.2, - 369, - 368, - 369.2, - 368.3, - 362.1, - 356.3, - 352.9, - 347.2, - 341.8, - 338.2, - 336.3, - 333.8, - 331.8, - 329.3, - 326.9, - 325, - 323.4, - 321.6, - 319.2, - 316.5, - 314.3, - 312.1, - 309.4, - 306.1, - 302.2, - 297.5, - 292.6, - 288.5, - 283.6, - 279, - 274.4, - 269.5, - 265, - 261.1, - 257.4, - 251.9, - 244.7, - 241.1, - 241, - 239.8, - 237.6, - 235.5, - 233.2, - 231, - 229.4, - 227.7, - 226.8, - 226.2, - 225, - 224.2, - 224.1, - 223.1, - 221.4, - 219.3, - 216.8, - 214.6, - 213.2, - 213.1, - 212.9, - 212.1, - 209.9, - 208.4, - 207.2, - 204.9, - 202.8, - 201.4, - 199, - 195.9, - 193.3, - 191, - 188.1, - 184.9, - 181.5, - 178.1, - 175, - 171.7, - 167.7, - 163.4, - 159.9, - 157.7, - 156.7, - 156.1, - 154.7, - 153.2, - 152.2, - 152, - 152.1, - 152.2, - 151.9, - 150.9, - 149.5, - 148.9, - 148.6, - 147.3, - 145.6, - 144.3, - 143.8, - 143.7, - 143.8, - 142.2, - 136.6, - 133.1, - 133.4, - 133.5, - 133.2, - 132.9, - 132.7, - 132.5, - 651.1, - 651.2, - 651.2, - 651.2, - 651.2, - 651.2, - 651.3, - 651.3, - 651.4, - 651.5, - 651.6, - 651.7, - 651.8, - 651.9, - 652, - 652.1, - 652.2, - 652.3, - 652.4, - 652.6, - 652.6, - 652.7, - 652.8, - 652.8, - 652.9, - 653, - 653, - 653, - 653.1, - 653.2, - 653.4, - 653.5, - 653.7, - 653.6, - 653.7, - 653.9, - 654.1, - 654.1, - 654.4, - 654.5, - 654.3, - 654.1, - 654.1, - 653.8, - 653.7, - 652.9, - 651.4, - 649.8, - 647.6, - 645.5, - 643.1, - 640.5, - 637.8, - 635.3, - 632.8, - 630.3, - 627.5, - 624.8, - 622.8, - 620.7, - 617.9, - 613.9, - 609.9, - 605.9, - 602.4, - 598.9, - 592.7, - 588.4, - 585.2, - 582, - 578.8, - 574.8, - 572, - 569, - 565.3, - 561, - 558.5, - 556.2, - 553.7, - 550.8, - 548, - 545.1, - 542.6, - 540.6, - 538.5, - 536.1, - 534.2, - 532.1, - 529.4, - 527.3, - 525.4, - 523, - 520.7, - 519, - 516.8, - 514.5, - 512.2, - 510.3, - 507.9, - 504.5, - 501.9, - 500.1, - 498, - 496.4, - 493.9, - 491.2, - 489, - 486.9, - 484.5, - 482.9, - 481.3, - 479.5, - 478.1, - 476.7, - 475.4, - 474.1, - 472.5, - 469, - 463.7, - 459.7, - 456.5, - 453.9, - 451.8, - 449.9, - 447.9, - 446.1, - 443.9, - 441.2, - 438.8, - 436.4, - 432.4, - 429.2, - 427.8, - 425.8, - 423.5, - 418.7, - 413.9, - 408.5, - 399.4, - 392.7, - 387.9, - 385.4, - 384.5, - 384.2, - 386.1, - 386.4, - 383.5, - 380.3, - 376.9, - 377.9, - 376.6, - 377, - 377.6, - 371.6, - 361.1, - 356.6, - 353.2, - 347.7, - 341.1, - 337.2, - 335.6, - 334, - 332, - 329.4, - 327.1, - 325.4, - 324.1, - 322.4, - 320, - 317.5, - 315.2, - 312.7, - 309.8, - 306.8, - 303.2, - 298.8, - 294.9, - 291, - 286.2, - 282.2, - 277.3, - 271.9, - 267.1, - 263.5, - 260.6, - 255.1, - 250.4, - 247.8, - 245.6, - 243.6, - 241.2, - 238.3, - 235.2, - 233, - 231.7, - 231, - 230.4, - 229.2, - 229, - 229, - 227.7, - 226.1, - 224.2, - 222, - 219.5, - 217.5, - 216.6, - 216, - 215.7, - 214.9, - 215, - 213.1, - 210.2, - 207.5, - 206.2, - 203.9, - 201.5, - 198.7, - 195.8, - 192.8, - 189.8, - 186.6, - 183.3, - 180, - 176.8, - 173, - 169, - 165.2, - 161.3, - 159.7, - 158.9, - 157.5, - 156.2, - 155.1, - 154.8, - 154.8, - 154.3, - 153.7, - 153, - 151.9, - 150.3, - 149.4, - 148.7, - 147.6, - 146.2, - 145.5, - 145.2, - 145.1, - 145.1, - 142, - 136.2, - 134.4, - 134.8, - 134.7, - 134.4, - 134.2, - 134, - 651.2, - 651.2, - 651.3, - 651.4, - 651.3, - 651.4, - 651.4, - 651.5, - 651.6, - 651.6, - 651.7, - 651.8, - 652, - 652, - 652.1, - 652.2, - 652.3, - 652.4, - 652.5, - 652.7, - 652.7, - 652.8, - 652.8, - 652.8, - 652.9, - 653, - 653, - 653.1, - 653.1, - 653.3, - 653.5, - 653.7, - 653.8, - 653.8, - 653.9, - 654.1, - 654.1, - 654.2, - 654.3, - 654.3, - 654.1, - 654.1, - 654.2, - 654, - 654, - 653.1, - 651.3, - 649.5, - 647.7, - 645.1, - 642.4, - 640, - 637.5, - 634.6, - 631.7, - 628.4, - 624.8, - 623.2, - 620.6, - 618.2, - 614.6, - 610.7, - 607.1, - 603.1, - 599, - 595.2, - 590, - 586.2, - 583.4, - 580.3, - 577.4, - 574.4, - 571.7, - 568.3, - 564.2, - 560.6, - 558.4, - 556.4, - 554, - 551.4, - 548.7, - 545.8, - 543.3, - 541.1, - 538.8, - 536.7, - 534.9, - 532.6, - 530, - 527.6, - 525.7, - 523.9, - 522, - 520.1, - 518, - 515.5, - 513, - 511, - 509.2, - 505.4, - 502.1, - 500.7, - 499, - 496.9, - 494.3, - 491.7, - 490, - 488.2, - 485.1, - 482.7, - 480.9, - 479.2, - 477.6, - 476, - 474.2, - 471.9, - 469.1, - 465.2, - 462.1, - 459.4, - 456.5, - 454.2, - 451.4, - 449.4, - 447.8, - 446.7, - 444.4, - 441.1, - 438.3, - 434.9, - 431.6, - 429.4, - 427, - 425.3, - 422.1, - 419.1, - 416.1, - 409.6, - 400.7, - 394.7, - 391.3, - 389.5, - 389.3, - 388.1, - 388.3, - 387.6, - 387.3, - 385.9, - 383.1, - 384.6, - 384.6, - 382.3, - 380.5, - 377.1, - 370.4, - 364.4, - 358.8, - 352.9, - 346.3, - 339.1, - 336.7, - 335.5, - 334.2, - 332.1, - 329.5, - 327.2, - 325.7, - 324.5, - 322.9, - 320.9, - 318.6, - 316.1, - 313.4, - 310.6, - 307.7, - 304.3, - 300.5, - 297.1, - 293, - 288.4, - 284.5, - 280, - 274.4, - 269.8, - 266, - 262.7, - 259, - 254.4, - 250.4, - 247.9, - 246.5, - 243.7, - 240, - 237.6, - 236.4, - 235.4, - 234.3, - 233.7, - 233.9, - 233.1, - 231.9, - 230.3, - 228.5, - 226.6, - 224.5, - 222.5, - 220.8, - 219.4, - 219.8, - 220.8, - 220.2, - 217.3, - 214.6, - 213, - 212.2, - 210.2, - 207.7, - 204.5, - 201.2, - 197.7, - 194.5, - 191.4, - 188.1, - 184.5, - 181.1, - 178, - 174.4, - 170.7, - 166.3, - 163.1, - 162, - 160.8, - 159.5, - 158.6, - 158.1, - 157.3, - 156.6, - 155.6, - 154.5, - 153.4, - 152, - 150.5, - 149.5, - 148.6, - 147.6, - 146.8, - 146.4, - 146.3, - 146.4, - 145.7, - 140.4, - 135.9, - 135.8, - 136.1, - 136, - 135.7, - 135.5, - 651.4, - 651.4, - 651.4, - 651.4, - 651.4, - 651.4, - 651.5, - 651.6, - 651.7, - 651.8, - 651.9, - 652, - 652.1, - 652.1, - 652.2, - 652.3, - 652.4, - 652.4, - 652.5, - 652.6, - 652.7, - 652.8, - 652.8, - 652.9, - 652.9, - 653, - 653, - 653, - 653.1, - 653.4, - 653.7, - 653.9, - 653.9, - 654.1, - 654.3, - 654.1, - 654.2, - 654.2, - 654.2, - 654.1, - 654.1, - 654.2, - 654.1, - 653.9, - 653.6, - 652.8, - 651.3, - 649.1, - 647.2, - 644.5, - 641.8, - 639.2, - 636.9, - 633.3, - 629.3, - 625.3, - 623.4, - 621.4, - 617.8, - 614.8, - 611.1, - 607.2, - 604.4, - 600.7, - 596.9, - 593, - 588.2, - 585.3, - 582.5, - 579.5, - 577.4, - 574.4, - 571.1, - 567.2, - 563.8, - 561.5, - 559.1, - 557.1, - 554.7, - 551.9, - 549.3, - 546.6, - 544.1, - 542, - 539.6, - 537.6, - 535.8, - 533.6, - 531.1, - 528.7, - 526.6, - 524.7, - 522.9, - 520.8, - 518.8, - 516.2, - 513.6, - 511.4, - 509.7, - 507.2, - 503.6, - 501.2, - 499.4, - 496.8, - 493.8, - 492.1, - 490.8, - 488.5, - 485.6, - 482.8, - 480.4, - 478.8, - 476.8, - 475.1, - 472.7, - 470.3, - 467.4, - 464.4, - 461.8, - 459.6, - 456.9, - 454.6, - 451.8, - 449.3, - 447.5, - 446.1, - 443.3, - 440.2, - 436.9, - 434.1, - 431.8, - 429.9, - 427.6, - 425, - 421.5, - 418.7, - 414.1, - 408.6, - 404.4, - 400.3, - 395.4, - 394.7, - 395.6, - 394.9, - 392.3, - 390.3, - 389.9, - 389.7, - 388.3, - 387.4, - 386.9, - 384.9, - 381, - 377.7, - 373.7, - 367.6, - 362.1, - 357.1, - 351.2, - 343, - 338.6, - 336.6, - 335.6, - 334.1, - 332.2, - 329.4, - 327.4, - 326.2, - 324.8, - 323.4, - 321.7, - 319.5, - 316.9, - 314.2, - 311.3, - 308.4, - 305.3, - 302, - 298.6, - 294.7, - 290.2, - 286.3, - 282.2, - 276.4, - 271.9, - 268.1, - 264.7, - 261.9, - 257.2, - 251.6, - 249.5, - 248.2, - 246.8, - 244.7, - 242.1, - 239.9, - 238.4, - 238.2, - 238.5, - 237.8, - 236.4, - 234.5, - 232.8, - 230.9, - 228.9, - 227, - 225.2, - 223.4, - 224.3, - 225.8, - 225, - 221.3, - 220.1, - 219.5, - 217.6, - 215.7, - 213.2, - 210.3, - 206.7, - 203.1, - 199.6, - 196.7, - 193.3, - 189.2, - 185.4, - 181.8, - 178.7, - 175.9, - 172.1, - 167.9, - 165.4, - 164.4, - 163.2, - 161.5, - 160.8, - 160, - 159.3, - 158.5, - 157.4, - 155.9, - 153.8, - 152.2, - 151, - 149.5, - 148.6, - 147.9, - 147.6, - 147.5, - 147.4, - 146.8, - 143, - 137.8, - 136.6, - 136.6, - 137.3, - 137.1, - 136.9, - 651.4, - 651.5, - 651.4, - 651.4, - 651.5, - 651.6, - 651.6, - 651.8, - 651.9, - 652, - 652, - 652.1, - 652.1, - 652.2, - 652.3, - 652.3, - 652.4, - 652.4, - 652.6, - 652.7, - 652.8, - 652.8, - 652.8, - 652.8, - 652.8, - 652.8, - 652.9, - 652.9, - 653.1, - 653.5, - 653.5, - 653.8, - 653.9, - 654.1, - 654.1, - 654.1, - 654.1, - 654.2, - 654.1, - 654.1, - 654, - 654.1, - 653.9, - 653.6, - 653.4, - 652.9, - 651.7, - 649, - 646.7, - 644.5, - 641.4, - 638.3, - 634.8, - 630.5, - 625.9, - 623.2, - 621.2, - 618.4, - 615.4, - 611.8, - 608.1, - 604.5, - 602, - 598.7, - 595.3, - 591.6, - 587.4, - 584.6, - 582.3, - 580, - 577.6, - 574.3, - 570.5, - 566.5, - 564.6, - 562.9, - 560.1, - 557.5, - 554.8, - 552.5, - 550.3, - 547.5, - 545, - 542.8, - 540.3, - 538.4, - 536.5, - 534.1, - 531.5, - 529.6, - 528, - 525.7, - 523.7, - 521.5, - 519.6, - 516.9, - 514, - 511.7, - 509.8, - 508, - 505.8, - 502.7, - 499.5, - 497.2, - 494.9, - 492.7, - 491.2, - 488.9, - 486.2, - 483, - 480, - 477.8, - 475.9, - 473.8, - 471.6, - 469.5, - 466.9, - 464.3, - 462, - 459.5, - 457.5, - 455.3, - 452.9, - 449.5, - 447.3, - 445.7, - 442.7, - 439.4, - 436.8, - 435.1, - 432.7, - 430.2, - 427.8, - 425.1, - 421.9, - 418.3, - 414.1, - 411.7, - 409.1, - 404.7, - 401.5, - 402.8, - 401.9, - 399.8, - 396.9, - 394, - 391.7, - 390.2, - 388.1, - 387.2, - 386.4, - 383.4, - 379.4, - 377.2, - 373.9, - 368.5, - 363.3, - 358.7, - 353.4, - 347.4, - 342.3, - 338.5, - 336.8, - 335.1, - 333.8, - 331.7, - 329.5, - 327.9, - 326.6, - 325.5, - 324.2, - 322.5, - 320.3, - 317.7, - 315.1, - 312.4, - 309.5, - 306.4, - 303.3, - 300, - 296.3, - 292.1, - 288.1, - 283.4, - 278.3, - 273.5, - 269.6, - 266.1, - 262.6, - 257.9, - 252.9, - 252.3, - 253.4, - 252.4, - 249.4, - 245.6, - 242.9, - 242.4, - 242.5, - 241.4, - 239.9, - 238.3, - 236.8, - 235.1, - 233.3, - 231.4, - 229.5, - 227.9, - 229.2, - 232.4, - 231.5, - 228.6, - 226.7, - 224.1, - 221.4, - 219.9, - 218.1, - 215.2, - 211.4, - 208.4, - 205, - 201.6, - 198.5, - 194.6, - 190.3, - 186.2, - 182.6, - 180, - 176.8, - 172.9, - 169.2, - 167.6, - 166.6, - 164.6, - 163.1, - 163, - 162.2, - 161.4, - 160.2, - 158.2, - 155.9, - 154.8, - 153.7, - 152, - 150.4, - 149.2, - 148.8, - 148.7, - 148.5, - 147.8, - 145.3, - 140.6, - 138.1, - 137.3, - 138.6, - 138.6, - 138.4, - 651.4, - 651.5, - 651.5, - 651.5, - 651.5, - 651.7, - 651.8, - 651.9, - 652, - 652, - 652.1, - 652.1, - 652.2, - 652.2, - 652.3, - 652.3, - 652.3, - 652.5, - 652.7, - 652.7, - 652.7, - 652.7, - 652.8, - 652.7, - 652.7, - 652.7, - 652.8, - 652.9, - 653.1, - 653.4, - 653.4, - 653.5, - 653.6, - 653.7, - 653.8, - 653.9, - 654, - 654.1, - 654.1, - 654.1, - 654, - 653.9, - 653.7, - 653.6, - 653.3, - 652.9, - 651.8, - 649.3, - 647, - 644.3, - 640.6, - 636.3, - 631.6, - 626.9, - 623.6, - 621.5, - 618.9, - 616.3, - 613.2, - 609.4, - 606.2, - 603.1, - 600.7, - 597.5, - 594.2, - 590.5, - 587.2, - 584.6, - 582.6, - 580.4, - 577.9, - 574.1, - 570.3, - 567.6, - 566.2, - 563.8, - 560.7, - 558.3, - 555.7, - 553, - 550.7, - 548.1, - 545.3, - 543.1, - 541, - 539.2, - 536.8, - 534.6, - 532.1, - 530.3, - 528.9, - 526.7, - 524.3, - 522.1, - 520.5, - 518.2, - 515.1, - 512, - 510.1, - 507.8, - 505.7, - 503.7, - 501, - 499.1, - 496.4, - 493.1, - 491.4, - 489.7, - 486.7, - 483.7, - 480.6, - 477.4, - 475, - 472.9, - 471, - 469.1, - 466.9, - 464.9, - 462.6, - 460.2, - 458.2, - 456.1, - 454, - 450.2, - 447.6, - 445.5, - 441.8, - 438.5, - 436.8, - 435.5, - 433.4, - 430.4, - 427.4, - 425.2, - 422.1, - 420.2, - 418.7, - 415.8, - 412.2, - 410.6, - 409.7, - 407.9, - 403.8, - 400.9, - 399.2, - 396.2, - 392.9, - 390, - 387.7, - 386.5, - 384.7, - 381.2, - 378.6, - 376.5, - 373.4, - 368.5, - 363.8, - 358.5, - 355.5, - 353.3, - 348.5, - 342.1, - 339.8, - 337.2, - 335.3, - 334, - 332.4, - 330.4, - 328.8, - 327.4, - 326.4, - 325, - 323.2, - 321, - 318.5, - 316.2, - 313.4, - 310.5, - 307.7, - 304.9, - 301.9, - 298.2, - 294.2, - 290, - 285, - 280.6, - 276.1, - 271.4, - 267.5, - 265.3, - 261.6, - 259.2, - 258.1, - 257, - 254.9, - 251.6, - 248.2, - 245.7, - 244.9, - 244.2, - 243, - 241.5, - 240.1, - 238.6, - 237, - 235.4, - 233.8, - 232.4, - 233.8, - 236.6, - 235.9, - 234, - 231.3, - 228, - 224.6, - 222.9, - 221.8, - 219.7, - 216.6, - 213.6, - 210.3, - 206.9, - 203.4, - 199.8, - 195, - 190.6, - 187.1, - 184.1, - 181.5, - 177.8, - 173.8, - 171.1, - 169.7, - 168, - 167.1, - 166.7, - 165.4, - 163.8, - 161.7, - 159.9, - 159.1, - 157.7, - 156.2, - 154.6, - 153.2, - 151.7, - 150.4, - 150, - 149.9, - 149.5, - 147.9, - 144.1, - 140.5, - 138.5, - 140, - 140.3, - 140, - 651.5, - 651.5, - 651.5, - 651.5, - 651.6, - 651.7, - 651.8, - 652, - 652, - 652, - 652.1, - 652.1, - 652.2, - 652.2, - 652.3, - 652.3, - 652.3, - 652.4, - 652.5, - 652.6, - 652.6, - 652.5, - 652.5, - 652.5, - 652.6, - 652.6, - 652.7, - 652.8, - 653, - 653.1, - 653.2, - 653.3, - 653.4, - 653.5, - 653.7, - 653.8, - 653.8, - 653.9, - 653.9, - 653.9, - 653.8, - 653.8, - 653.7, - 653.4, - 653.1, - 652.5, - 651.3, - 650, - 647.1, - 642.9, - 638.1, - 633.3, - 628.5, - 625.8, - 623.1, - 620, - 617.1, - 614.4, - 611.2, - 608.1, - 605.2, - 602.7, - 600.4, - 597.5, - 594.2, - 590.8, - 587.7, - 585.1, - 583.1, - 581.6, - 579.4, - 575.4, - 572.1, - 569.7, - 567.3, - 564.3, - 561.6, - 559.4, - 556.6, - 553.6, - 550.7, - 548.2, - 546.1, - 543.9, - 541.6, - 539.6, - 536.8, - 534.7, - 532.8, - 530.6, - 528.7, - 526.7, - 524.7, - 522.3, - 520.3, - 518.2, - 515.5, - 512.4, - 510.5, - 508.3, - 505.8, - 504.1, - 502.2, - 499.6, - 496.1, - 493.5, - 491.7, - 490, - 487.6, - 484.5, - 481.6, - 477.9, - 474.5, - 472.1, - 470.6, - 469.2, - 467.3, - 465.2, - 463.2, - 461.6, - 459.4, - 456.8, - 454.4, - 450.2, - 447.6, - 445.3, - 442.3, - 440, - 437.1, - 435.2, - 432.6, - 429.8, - 427.4, - 426, - 424.1, - 423.2, - 422.2, - 419.8, - 416.4, - 414.5, - 412.7, - 410, - 405.4, - 402.4, - 400, - 396.2, - 393.2, - 390.2, - 387.5, - 385.5, - 383.2, - 380.8, - 378.7, - 376.4, - 374, - 370.2, - 364.2, - 360.3, - 358.8, - 356.1, - 351.7, - 347, - 344.5, - 341.6, - 338.2, - 335.5, - 333.9, - 332.7, - 331.3, - 330, - 328.7, - 327.4, - 325.7, - 323.7, - 321.4, - 319, - 316.8, - 314.2, - 311.5, - 309, - 306.4, - 303.5, - 300, - 296.1, - 291.8, - 287.2, - 283.2, - 278.7, - 274, - 271.1, - 268.9, - 265.8, - 262.8, - 261, - 259.3, - 255.8, - 252.5, - 249.4, - 247.6, - 247.4, - 246.6, - 244.8, - 243.5, - 242.1, - 240.9, - 239.8, - 238.4, - 237.9, - 239.5, - 239.7, - 238.7, - 237.1, - 234.7, - 231.7, - 229.2, - 227.2, - 226.5, - 224.9, - 222.2, - 219.3, - 215.7, - 212, - 208.6, - 204.4, - 199.1, - 195.2, - 191.7, - 188.8, - 185.9, - 182.7, - 179.2, - 175.6, - 172.3, - 170.5, - 169.7, - 169.4, - 168.1, - 165.8, - 163.9, - 162.9, - 162, - 161.1, - 159.7, - 157.6, - 156.2, - 155.1, - 152.9, - 151.7, - 151.5, - 151, - 149.8, - 147.1, - 143, - 141, - 141.9, - 141.8, - 141.5, - 651.5, - 651.5, - 651.5, - 651.5, - 651.6, - 651.8, - 651.9, - 652.1, - 652, - 652, - 652.1, - 652.1, - 652.2, - 652.2, - 652.2, - 652.3, - 652.3, - 652.3, - 652.2, - 652.3, - 652.3, - 652.4, - 652.4, - 652.4, - 652.5, - 652.6, - 652.7, - 652.7, - 652.9, - 653, - 653, - 653.1, - 653.3, - 653.4, - 653.5, - 653.6, - 653.7, - 653.7, - 653.7, - 653.8, - 653.7, - 653.6, - 653.5, - 653.1, - 652.9, - 652, - 651.2, - 650, - 645.3, - 639.7, - 635.3, - 630.7, - 627.6, - 625, - 621.7, - 618.8, - 616, - 613.2, - 610.6, - 608, - 605.7, - 603.1, - 600.5, - 597.6, - 594.5, - 591.5, - 588.3, - 585.5, - 583.9, - 582.3, - 578.6, - 575.4, - 573.3, - 570.8, - 568, - 565.9, - 563.2, - 560.1, - 557, - 554.1, - 551.3, - 548.9, - 546.8, - 544.6, - 542.2, - 540, - 537.8, - 535.5, - 533.5, - 530.9, - 528.4, - 526.5, - 524.3, - 521.9, - 520, - 517.8, - 515.3, - 512.4, - 510.9, - 508.9, - 506.6, - 504.5, - 502.1, - 499.1, - 495.9, - 493.8, - 491.8, - 489.5, - 487, - 484.3, - 481.1, - 477.3, - 473.9, - 471.7, - 470.5, - 469.2, - 467.6, - 465.9, - 464.3, - 462.7, - 460, - 457.1, - 454.1, - 450.3, - 447.6, - 445.6, - 443.5, - 441.7, - 438.8, - 435.2, - 432.4, - 430.6, - 429.3, - 429.1, - 427.1, - 424.4, - 424.1, - 421.9, - 418.6, - 416.4, - 414.8, - 411.5, - 407.1, - 404.7, - 401.7, - 397.6, - 393.4, - 390.9, - 387.3, - 384.6, - 382.8, - 380.9, - 378.9, - 376.9, - 375.1, - 370.3, - 364.8, - 363.6, - 361, - 356.3, - 352.2, - 349.9, - 347.6, - 344.2, - 340.1, - 336.7, - 334.8, - 333.7, - 332.8, - 331.9, - 330.9, - 329.6, - 328, - 326.3, - 324.2, - 321.4, - 319, - 316.9, - 314.7, - 312.4, - 310, - 307.4, - 305.2, - 302.5, - 298.9, - 294.9, - 290.7, - 286.5, - 281.8, - 278, - 274.5, - 271.2, - 268.4, - 266, - 263, - 258.3, - 255.1, - 254, - 253, - 251.2, - 250.1, - 248.4, - 247.2, - 246.6, - 245.3, - 244, - 242.8, - 241.9, - 242.5, - 242.5, - 241.7, - 239.9, - 238, - 235.7, - 233.5, - 232.6, - 232, - 230, - 227.7, - 224.9, - 221.5, - 217.5, - 213.2, - 209.3, - 205.4, - 201, - 197.3, - 194.7, - 191.5, - 187.9, - 184.1, - 180.6, - 176.8, - 173.2, - 171.5, - 170.9, - 169.8, - 168.1, - 166.9, - 165.3, - 164.2, - 163.1, - 162, - 160.4, - 158.6, - 157.6, - 156, - 153.6, - 152.6, - 151.9, - 151.1, - 149.2, - 145.3, - 142.7, - 143.4, - 143.3, - 143, - 651.5, - 651.6, - 651.5, - 651.6, - 651.7, - 651.9, - 652.1, - 652.1, - 652, - 652, - 652, - 652.1, - 652.1, - 652.2, - 652.2, - 652.2, - 652.3, - 652.2, - 652.2, - 652.2, - 652.1, - 652.1, - 652.2, - 652.3, - 652.4, - 652.5, - 652.5, - 652.6, - 652.7, - 652.8, - 652.8, - 653, - 653.1, - 653.1, - 653.2, - 653.3, - 653.5, - 653.5, - 653.6, - 653.6, - 653.5, - 653.4, - 653.2, - 652.9, - 652.8, - 652, - 650.5, - 647, - 641.3, - 636.8, - 632.7, - 629.1, - 626.5, - 623.6, - 621, - 618.8, - 615.9, - 613.1, - 610.8, - 608.6, - 606.3, - 603.5, - 600.8, - 597.9, - 594.7, - 591.5, - 588.4, - 585.7, - 583.9, - 581.4, - 578.4, - 576, - 574.2, - 571.7, - 568.9, - 566.6, - 563.2, - 560.2, - 557.7, - 554.8, - 552.2, - 549.9, - 547.3, - 544.7, - 542.2, - 540.5, - 538.8, - 536.4, - 533.8, - 530.8, - 528.5, - 526.9, - 524.1, - 521.6, - 519.9, - 517.8, - 515.5, - 512.9, - 510.9, - 509.1, - 507, - 504.4, - 502, - 499.2, - 496.1, - 494.1, - 491.3, - 488.8, - 486.5, - 483.8, - 480, - 476.1, - 473.6, - 472, - 470.8, - 469.6, - 468.2, - 466.5, - 464.8, - 463.2, - 461.2, - 458.7, - 454.8, - 451.6, - 449.1, - 446.9, - 444.7, - 441.9, - 438.7, - 435.8, - 434.8, - 433.8, - 432, - 430.8, - 428.8, - 426, - 425.1, - 422.7, - 419, - 417.2, - 415.1, - 411.3, - 408.8, - 406.7, - 402.7, - 397.6, - 393.9, - 391.2, - 387.4, - 384.6, - 383, - 381.1, - 379.1, - 377.5, - 373.9, - 368.2, - 367.2, - 365.6, - 359.3, - 355.4, - 352.8, - 349.5, - 347.2, - 344.8, - 339.7, - 337.1, - 335.7, - 334.8, - 334, - 333, - 332, - 330.9, - 329.6, - 328.5, - 327.1, - 324.9, - 321.8, - 318.9, - 316.9, - 315.1, - 313.2, - 311.3, - 310.5, - 308.2, - 304.5, - 300.8, - 297.2, - 293.3, - 289.1, - 284.9, - 280.7, - 276.6, - 273.2, - 270.1, - 268.2, - 266.1, - 262.9, - 260.9, - 258.2, - 255.2, - 252.8, - 251.2, - 250.2, - 250.1, - 248.7, - 247.2, - 246, - 245.2, - 244.8, - 245.1, - 244.4, - 243.6, - 241.8, - 239.6, - 237.3, - 236.6, - 236.7, - 235, - 232.9, - 229.9, - 227.2, - 223.8, - 219.9, - 215.6, - 212.1, - 208.3, - 204.6, - 200.9, - 197.1, - 193.6, - 190.1, - 186.2, - 182.1, - 178.1, - 174.8, - 172.7, - 171.6, - 171.2, - 170.3, - 168.4, - 167.4, - 165.6, - 163.8, - 162.1, - 160.5, - 159, - 157.5, - 155.7, - 154, - 153.1, - 152.6, - 151.6, - 148.4, - 145.1, - 144.7, - 144.8, - 144.5, - 651.6, - 651.6, - 651.6, - 651.7, - 651.9, - 651.9, - 652, - 651.9, - 652, - 652, - 652.1, - 652.1, - 652.1, - 652.1, - 652.2, - 652.2, - 652.1, - 652.1, - 652, - 652, - 652, - 651.9, - 652, - 652.1, - 652.2, - 652.3, - 652.4, - 652.4, - 652.5, - 652.6, - 652.6, - 652.8, - 652.9, - 653, - 653, - 653, - 653.1, - 653.1, - 653.3, - 653.2, - 653.2, - 653.2, - 653, - 652.8, - 652.5, - 650.8, - 647.1, - 642.7, - 638.7, - 634.7, - 631.1, - 627.7, - 625.2, - 623.5, - 621.2, - 619, - 616.2, - 613.6, - 611.2, - 608.7, - 606.1, - 603.8, - 601, - 598, - 594.8, - 591.7, - 588.9, - 586.3, - 584.1, - 581.9, - 579.6, - 577.6, - 575.8, - 572.6, - 569.4, - 566.6, - 563.5, - 560.6, - 558.2, - 555.9, - 553.4, - 550.4, - 547.4, - 544.8, - 542.8, - 540.8, - 538.9, - 536.4, - 534.2, - 531.6, - 528.7, - 526.5, - 524.1, - 521.6, - 519.9, - 518.3, - 516.2, - 513.6, - 511.3, - 509.4, - 507.3, - 504.7, - 502.1, - 499.3, - 496.4, - 494, - 491.1, - 488.5, - 486.1, - 482.7, - 478.5, - 475.4, - 474.1, - 473.2, - 471.6, - 470.4, - 468.9, - 466.8, - 464.6, - 462.7, - 460.7, - 458.8, - 455.9, - 453.5, - 450.8, - 448.3, - 445.8, - 443, - 440.2, - 438.8, - 437.9, - 436.3, - 433.8, - 431.5, - 429.5, - 426.5, - 424.7, - 422.6, - 419.2, - 417, - 414, - 411.2, - 409.5, - 406, - 400.5, - 397, - 395.4, - 392.3, - 388.7, - 385.3, - 382.8, - 381.1, - 379.5, - 377, - 371.9, - 369.7, - 369.4, - 365.6, - 358.7, - 354.7, - 350.7, - 347.7, - 346.7, - 343.6, - 339.1, - 337.5, - 336.5, - 335.8, - 335.1, - 334, - 333, - 332.3, - 331.2, - 330.3, - 329.4, - 328.1, - 325.9, - 322.3, - 319.2, - 317.1, - 315.4, - 314, - 313.7, - 313.1, - 310, - 306.1, - 302.2, - 298.5, - 294.9, - 291.1, - 287.3, - 283.3, - 279.6, - 276.3, - 273.5, - 271, - 268.5, - 265.6, - 262.5, - 259.3, - 256.6, - 254.2, - 252.7, - 252.5, - 251.7, - 250, - 248.8, - 248.2, - 247.7, - 247.3, - 246.6, - 246.2, - 245.1, - 243.3, - 241, - 239.8, - 240.2, - 239.2, - 237.8, - 234.8, - 231.7, - 228.4, - 225, - 222, - 218.8, - 214.6, - 210.7, - 207.3, - 203.6, - 199.5, - 195.7, - 192, - 187.9, - 183.8, - 179.9, - 176.8, - 174.9, - 174.5, - 173.7, - 172.5, - 171.8, - 169.6, - 167.1, - 164.8, - 162.8, - 160.7, - 158.4, - 157.2, - 156.3, - 155.6, - 154.8, - 154, - 152.3, - 148.8, - 146.1, - 146.2, - 145.9, - 651.6, - 651.6, - 651.6, - 651.8, - 651.9, - 651.9, - 651.9, - 651.9, - 651.9, - 651.9, - 652, - 652, - 651.9, - 651.9, - 652, - 652, - 651.9, - 651.9, - 651.9, - 651.9, - 651.9, - 651.8, - 651.9, - 652, - 652.1, - 652.1, - 652.2, - 652.3, - 652.4, - 652.4, - 652.4, - 652.5, - 652.6, - 652.8, - 652.9, - 652.9, - 652.9, - 653, - 653, - 653.1, - 653.1, - 653.1, - 653, - 652.8, - 652.3, - 649.9, - 645.6, - 641.5, - 637.1, - 633.4, - 630.1, - 627, - 625.5, - 623.6, - 621, - 618.5, - 616.2, - 613.9, - 611.4, - 608.6, - 606.1, - 604.1, - 601.4, - 598.5, - 595.2, - 592.2, - 589.6, - 587.2, - 585.2, - 583.3, - 581.3, - 579.2, - 576.6, - 573.4, - 570, - 567.1, - 564.5, - 561.3, - 558.6, - 556.3, - 553.2, - 549.4, - 546.8, - 544.7, - 542.7, - 540.7, - 538.5, - 536.2, - 534.1, - 531.8, - 529.2, - 527.2, - 524.6, - 521.8, - 519.9, - 518.3, - 516.4, - 513.9, - 511.5, - 509.7, - 507.6, - 505.1, - 502.7, - 499.5, - 496.5, - 493.9, - 490.9, - 488, - 485, - 481.6, - 477.9, - 476, - 475.9, - 475.6, - 473.7, - 472.3, - 470.8, - 468.5, - 465.5, - 462.2, - 459, - 456.9, - 455.1, - 453.2, - 450.9, - 448.8, - 446.4, - 444.3, - 442.4, - 440.4, - 438.5, - 436.3, - 434, - 432.1, - 429.6, - 426.5, - 424.6, - 422.8, - 420.3, - 416.6, - 413.7, - 411.4, - 408.4, - 404.7, - 400.8, - 398, - 396.5, - 393.8, - 390.3, - 386.1, - 383, - 381.4, - 379.4, - 376, - 373, - 371.7, - 369.8, - 365.2, - 358.7, - 352.1, - 348.1, - 345.8, - 343.8, - 340.9, - 339.2, - 338.2, - 337.4, - 336.8, - 336.1, - 334.9, - 334.1, - 333.6, - 332.7, - 331.8, - 330.9, - 329.9, - 328.6, - 326.4, - 323.2, - 320.1, - 318.1, - 317, - 317.4, - 318.4, - 315.6, - 311.8, - 307.5, - 303.5, - 299.9, - 296.3, - 292.7, - 289.1, - 285.6, - 282.7, - 279.5, - 276, - 272.9, - 270.3, - 267.9, - 264.9, - 261.8, - 258.6, - 256.1, - 254.7, - 254.3, - 253.3, - 252, - 251.3, - 250.5, - 249.8, - 249.3, - 248.7, - 247.8, - 246.5, - 244.7, - 243.2, - 243, - 242.7, - 241.6, - 239.2, - 236.2, - 232.9, - 228.9, - 225.8, - 223, - 220, - 216.6, - 213.1, - 209.1, - 205.3, - 201.3, - 197.1, - 193.2, - 189.3, - 185.5, - 181.7, - 179, - 177.7, - 176.9, - 176.5, - 175.9, - 174, - 171.2, - 168.2, - 165.9, - 163.2, - 160.7, - 159.1, - 158.9, - 158.8, - 157.6, - 156.5, - 155.4, - 153.3, - 149.4, - 147.3, - 147.2, - 651.6, - 651.6, - 651.7, - 651.8, - 651.8, - 651.9, - 651.9, - 651.9, - 651.9, - 651.9, - 651.9, - 651.8, - 651.8, - 651.8, - 651.8, - 651.7, - 651.6, - 651.7, - 651.7, - 651.7, - 651.7, - 651.6, - 651.7, - 651.8, - 651.9, - 651.9, - 651.9, - 652, - 652.2, - 652.3, - 652.3, - 652.3, - 652.3, - 652.5, - 652.7, - 652.8, - 652.8, - 652.9, - 653, - 653, - 653, - 653.1, - 653, - 653, - 652.7, - 651.2, - 646.6, - 642.5, - 638.4, - 634.3, - 630.5, - 628, - 626.1, - 623.9, - 621.1, - 618.3, - 615.9, - 613.6, - 611.4, - 608.9, - 606.7, - 604.5, - 601.7, - 598.7, - 595.7, - 593.1, - 590.6, - 588.5, - 586.7, - 585.4, - 583.7, - 580.9, - 577.6, - 574.6, - 570.9, - 567.8, - 564.4, - 561.1, - 557.8, - 555.2, - 552.7, - 549.6, - 546.9, - 544.5, - 542.5, - 540.5, - 538.2, - 536.2, - 534.1, - 531.7, - 529.4, - 527.9, - 525.5, - 522.9, - 520.2, - 518, - 516.2, - 514.1, - 512.1, - 510.1, - 507.8, - 505.7, - 503.2, - 499.9, - 496.6, - 493.1, - 489.6, - 487.1, - 484.1, - 480.8, - 478.7, - 478.4, - 478.4, - 477.5, - 476, - 474.5, - 473.2, - 471.7, - 470.3, - 467.1, - 461.8, - 457.7, - 454.4, - 452.5, - 450.7, - 448.8, - 446.5, - 444.5, - 443.1, - 441.3, - 439, - 436.8, - 434.8, - 432.6, - 429.4, - 426.5, - 424.9, - 423.1, - 421, - 417.5, - 415.9, - 412.1, - 407.6, - 404.8, - 402.1, - 399.1, - 397.4, - 394.9, - 391.4, - 388.5, - 384.6, - 381.4, - 379.1, - 376.9, - 374.9, - 371.9, - 368.5, - 363.4, - 357.5, - 351.9, - 348.2, - 345.3, - 342.7, - 341, - 339.9, - 339.1, - 338.5, - 337.8, - 337.2, - 336.4, - 335.1, - 334.5, - 333.9, - 332.9, - 331.8, - 330.8, - 329.7, - 328.6, - 327.5, - 325.3, - 322.5, - 322, - 321.7, - 322.3, - 320.2, - 316.4, - 312.8, - 309.2, - 305.5, - 301.7, - 298, - 294.4, - 291, - 287.8, - 284.4, - 280.8, - 277.3, - 274.4, - 272.2, - 269.9, - 267.2, - 264.4, - 261.1, - 257.9, - 256.8, - 256.1, - 255.5, - 254.8, - 253.7, - 252.8, - 252.9, - 252.4, - 251.4, - 250.4, - 248.6, - 247.4, - 246.3, - 246, - 245.3, - 243.1, - 240.6, - 237.8, - 233.9, - 230.1, - 226.9, - 224.2, - 221.1, - 217.8, - 214.1, - 210, - 206.5, - 202.8, - 198.6, - 194.4, - 190.7, - 187, - 183.4, - 180.7, - 179.3, - 179, - 178.6, - 177.3, - 174.8, - 171.9, - 169.3, - 166.1, - 163.3, - 162, - 161.6, - 161.1, - 160.5, - 159, - 157.7, - 156.5, - 154.2, - 149.7, - 148.1, - 651.6, - 651.6, - 651.7, - 651.8, - 651.8, - 651.9, - 651.9, - 651.9, - 651.9, - 651.8, - 651.9, - 652, - 651.8, - 651.7, - 651.7, - 651.6, - 651.5, - 651.5, - 651.6, - 651.6, - 651.5, - 651.4, - 651.3, - 651.3, - 651.4, - 651.7, - 651.7, - 651.9, - 652, - 652.1, - 652.1, - 652.1, - 652.1, - 652.4, - 652.5, - 652.7, - 652.7, - 652.8, - 652.9, - 652.9, - 653, - 653.1, - 653.1, - 653.2, - 652.8, - 650.8, - 646.4, - 642.1, - 638.4, - 635.3, - 631.7, - 628.8, - 626.3, - 623.9, - 621.3, - 618.6, - 616.3, - 614.1, - 611.9, - 609.4, - 607.2, - 604.6, - 601.9, - 599.1, - 596.4, - 594, - 592, - 590.1, - 589, - 587.7, - 585.3, - 582.5, - 578.5, - 574.9, - 571.2, - 567.8, - 564.4, - 560.5, - 557.2, - 555.5, - 553.6, - 550.4, - 547.5, - 545.1, - 542.9, - 540.7, - 538.5, - 536, - 533.9, - 531.8, - 530, - 528.1, - 526.3, - 524.2, - 521.6, - 518.8, - 516.7, - 514.5, - 512.3, - 510.1, - 507.7, - 505.6, - 503.3, - 500.2, - 496.4, - 492.1, - 488.6, - 486.4, - 483.8, - 481.8, - 481.3, - 481.3, - 481, - 479.8, - 478.3, - 476.9, - 476.4, - 475.7, - 474.2, - 472, - 467.1, - 461.2, - 456.1, - 452.3, - 450.6, - 448.9, - 446.8, - 444.6, - 443, - 441.6, - 439.8, - 437.6, - 435.9, - 433.2, - 430, - 427.1, - 424.8, - 423.3, - 421.8, - 419.5, - 416.8, - 413.3, - 409.9, - 405.4, - 403, - 400.9, - 398.3, - 394.5, - 393.7, - 391.9, - 385.8, - 381.5, - 379.5, - 377.6, - 374, - 370.7, - 367.5, - 362.9, - 359.1, - 354.3, - 349.1, - 346.1, - 344, - 342.6, - 341.4, - 340.3, - 339.4, - 338.5, - 337.9, - 337.5, - 336.5, - 335.5, - 334.9, - 333.9, - 332.8, - 331.7, - 330.8, - 330.2, - 329.8, - 329.1, - 328.2, - 327.1, - 325.1, - 324, - 322.6, - 319.7, - 316.4, - 313.2, - 310.2, - 307.2, - 303.8, - 300.1, - 296.2, - 292.6, - 289, - 285.4, - 282.3, - 280.1, - 277, - 273.9, - 271.3, - 268.9, - 265.9, - 262.8, - 260.8, - 258.8, - 258, - 257.9, - 256.9, - 255.7, - 255.9, - 255.8, - 255.4, - 255.2, - 252.8, - 251.2, - 249.9, - 249.1, - 248.6, - 246.6, - 244, - 241.9, - 238.8, - 235.4, - 231.9, - 229.1, - 226.3, - 223.1, - 219.4, - 215.6, - 211.5, - 208, - 204.4, - 199.9, - 195.5, - 192, - 188.7, - 185.5, - 182.5, - 181.2, - 181, - 180.3, - 178.3, - 174.5, - 171.6, - 169.5, - 166.9, - 165.3, - 164.3, - 163, - 162, - 161.1, - 159.6, - 158.9, - 158, - 155, - 152.8, - 651.5, - 651.6, - 651.6, - 651.7, - 651.8, - 651.8, - 651.8, - 651.8, - 651.8, - 651.8, - 651.8, - 651.8, - 651.7, - 651.6, - 651.5, - 651.4, - 651.4, - 651.4, - 651.4, - 651.4, - 651.3, - 651.1, - 650.9, - 650.8, - 651, - 651.4, - 651.5, - 651.7, - 651.9, - 651.8, - 651.6, - 651.6, - 651.8, - 652.2, - 652.4, - 652.6, - 652.7, - 652.8, - 652.9, - 652.9, - 653, - 653, - 653.1, - 653.1, - 652.6, - 650.6, - 646.1, - 640.9, - 636.7, - 634.8, - 632.5, - 629.4, - 626.6, - 624.1, - 621.9, - 619.5, - 617.3, - 615, - 612.4, - 609.8, - 607.5, - 605, - 602.3, - 599.8, - 597.4, - 595.4, - 594, - 593, - 591.9, - 590.3, - 587.3, - 583.3, - 578.7, - 574.5, - 570.9, - 567, - 563.2, - 558.8, - 557.1, - 556.1, - 554, - 550.6, - 547.8, - 545.5, - 543.3, - 540.9, - 538.7, - 536.1, - 534, - 531.9, - 530.2, - 528.5, - 526.3, - 524.3, - 522.2, - 520.3, - 517.9, - 515.1, - 512.1, - 509.6, - 507.6, - 505.1, - 502.4, - 499.3, - 495.4, - 491.3, - 488.5, - 486.8, - 485.6, - 484.7, - 485, - 484.8, - 483.7, - 482.3, - 481.2, - 480.1, - 479.3, - 477.7, - 475.1, - 472.5, - 468, - 461.6, - 457.3, - 452.8, - 450.7, - 448.9, - 447, - 444.9, - 442.8, - 441.3, - 439.9, - 438, - 436.3, - 434.1, - 430.3, - 427.3, - 424.7, - 423.6, - 422.6, - 420, - 417.3, - 414.8, - 411.1, - 407.2, - 404.5, - 402.3, - 398.7, - 397.9, - 398.5, - 394.6, - 386.8, - 383, - 380.1, - 376.5, - 372.8, - 370.8, - 369.1, - 365.3, - 360.3, - 355.2, - 349.9, - 347.3, - 345.8, - 344.5, - 343.2, - 341.8, - 340.6, - 339.6, - 338.9, - 338.7, - 338.4, - 337.2, - 336.2, - 335.3, - 334.4, - 333.1, - 332.3, - 332, - 331.8, - 331.3, - 330.8, - 329.2, - 326.7, - 324.6, - 322.8, - 320.9, - 318.9, - 316.2, - 313.5, - 310.7, - 308, - 305, - 301.8, - 298.4, - 294.8, - 290.8, - 287.8, - 286.7, - 283.4, - 278.3, - 275.2, - 273, - 270.7, - 268.7, - 266.2, - 262.9, - 260.6, - 260.3, - 260.1, - 259.2, - 259.2, - 259, - 259, - 258.6, - 256.5, - 254.7, - 253.5, - 252.2, - 251.2, - 249.6, - 247.5, - 245.2, - 242.6, - 239.8, - 237, - 234, - 231.1, - 228.2, - 224.6, - 220.9, - 216.6, - 212.3, - 208.9, - 205.2, - 200.5, - 196.9, - 193.6, - 190.5, - 187.3, - 184.5, - 183.3, - 182.8, - 180.8, - 176.7, - 174.2, - 173.4, - 172.4, - 170, - 168, - 165.9, - 164, - 163.1, - 162.1, - 160.8, - 160, - 159.5, - 159.1, - 651.5, - 651.5, - 651.5, - 651.6, - 651.7, - 651.8, - 651.8, - 651.8, - 651.8, - 651.7, - 651.6, - 651.4, - 651.2, - 651.2, - 651.1, - 651.1, - 651.2, - 651.2, - 651, - 650.8, - 650.3, - 649.9, - 649.8, - 649.8, - 650.3, - 650.9, - 651.2, - 651.3, - 651, - 650.3, - 650.1, - 650.7, - 651.4, - 652, - 652.2, - 652.5, - 652.7, - 652.8, - 652.9, - 652.9, - 653, - 653, - 653.1, - 653.1, - 653, - 652.2, - 648.3, - 641.5, - 636.6, - 635.2, - 633.3, - 630.6, - 627.3, - 625, - 623.2, - 621, - 618.5, - 615.8, - 612.8, - 610.1, - 607.9, - 605.6, - 603.2, - 601, - 599.2, - 597.8, - 596.7, - 596, - 594.9, - 591.8, - 587.4, - 582.1, - 577.3, - 573.4, - 569.2, - 565.1, - 561.2, - 558.4, - 557.4, - 556.1, - 554.1, - 551.5, - 549, - 546.5, - 543.6, - 541.3, - 539.2, - 537.1, - 534.5, - 531.8, - 529.7, - 528.3, - 525.9, - 524, - 522.1, - 520.2, - 517.8, - 515, - 512, - 509.4, - 507.8, - 505.1, - 501.9, - 499.4, - 496.4, - 492.9, - 490.1, - 489, - 488.8, - 489.4, - 489.3, - 488.3, - 486.7, - 485.3, - 484.2, - 482.2, - 480, - 477.5, - 474.9, - 470.9, - 465.6, - 460.6, - 457.2, - 453.3, - 451.3, - 449.1, - 447.1, - 445.1, - 442.7, - 440.8, - 439.6, - 438.2, - 436.5, - 434.4, - 429.9, - 426.9, - 424.4, - 423.3, - 421.8, - 419.5, - 417.4, - 413.9, - 411.2, - 409.2, - 405.8, - 404, - 402.7, - 402.3, - 399.8, - 393.7, - 387.9, - 385.8, - 382.9, - 376.2, - 373.3, - 371.7, - 369.6, - 365.5, - 359.6, - 354.1, - 350.6, - 349, - 347.7, - 346.3, - 345, - 343.6, - 342.6, - 342, - 341.8, - 341, - 340.4, - 339.7, - 338.4, - 338.2, - 337.7, - 336, - 334.6, - 334.6, - 334.2, - 333.5, - 332.9, - 331.6, - 329.5, - 327.3, - 324.6, - 321.8, - 320, - 318, - 315.9, - 313.6, - 311.1, - 308.5, - 305.8, - 302.8, - 299.5, - 296.2, - 293, - 291.6, - 289.6, - 284.1, - 279.4, - 277.2, - 275.3, - 273.3, - 270.9, - 267.8, - 265.3, - 263.2, - 262.7, - 262.9, - 262.8, - 262.7, - 262.4, - 261.4, - 259.6, - 258.6, - 257.6, - 255.8, - 254.4, - 253.2, - 251, - 248.3, - 245.7, - 243.2, - 240.7, - 238, - 235, - 232, - 228.8, - 225.4, - 221.6, - 216.8, - 213.1, - 209.7, - 206.1, - 202.2, - 198.6, - 195.3, - 192.1, - 188.9, - 186.4, - 185.3, - 183.7, - 180.4, - 178.3, - 178.4, - 178.7, - 175.9, - 172.1, - 169.2, - 166.7, - 165.7, - 165, - 163.1, - 161.6, - 161.5, - 162.1, - 651.4, - 651.4, - 651.5, - 651.6, - 651.6, - 651.7, - 651.7, - 651.7, - 651.7, - 651.5, - 651.3, - 651.1, - 650.9, - 650.7, - 650.4, - 650, - 650.2, - 650.5, - 650.1, - 649.6, - 648.5, - 647.7, - 647.5, - 647.8, - 648.9, - 650, - 650.4, - 650.1, - 648.7, - 647.9, - 648.7, - 650, - 651, - 651.8, - 652, - 652.2, - 652.6, - 652.7, - 652.8, - 652.9, - 653, - 653.1, - 653.2, - 653.2, - 653, - 652.4, - 650.4, - 645.7, - 639.2, - 635.8, - 634.1, - 631.1, - 628.2, - 625.9, - 623.2, - 620.8, - 618.6, - 616.1, - 613.2, - 610.8, - 608.6, - 606.6, - 604.7, - 603.2, - 602, - 600.7, - 598.1, - 596, - 593.8, - 589.6, - 584.6, - 580.3, - 576, - 571.7, - 567.9, - 565.1, - 562.1, - 559.2, - 557.5, - 555.9, - 554.3, - 551.9, - 549.6, - 546.9, - 543.9, - 541.7, - 539.5, - 537.3, - 534.3, - 531.7, - 529.6, - 527.8, - 525.9, - 524.1, - 522.1, - 519.9, - 517.7, - 515.2, - 512.8, - 510.5, - 508, - 505.6, - 503.2, - 501.3, - 498.6, - 495.3, - 493.4, - 493, - 493.4, - 493.1, - 492.1, - 490.7, - 488.7, - 486.9, - 485.5, - 483.1, - 480.3, - 477.6, - 474.4, - 470.7, - 465.7, - 460.9, - 457.5, - 454.6, - 452, - 449.4, - 447.2, - 444.9, - 442.6, - 440.5, - 439.2, - 437.9, - 436.3, - 433.4, - 429.8, - 427.9, - 425, - 422.7, - 421.1, - 419.9, - 418.4, - 415.8, - 412.1, - 408.7, - 406.9, - 406.9, - 405.7, - 403.1, - 399, - 394.4, - 389.8, - 388.3, - 386.2, - 379.3, - 375.3, - 371.9, - 368.7, - 363.8, - 358, - 354.2, - 352.2, - 350.9, - 349.5, - 348.2, - 346.9, - 345.7, - 344.8, - 344.9, - 346.2, - 347, - 343.5, - 342.4, - 341.6, - 341.3, - 341.3, - 339.9, - 338.2, - 339.1, - 337.7, - 335.6, - 334.4, - 333.1, - 331.9, - 330.2, - 327.9, - 324.8, - 322.7, - 320.3, - 318.8, - 317, - 315, - 312.4, - 309.4, - 306.8, - 304.2, - 301.1, - 297.6, - 295.9, - 295, - 290.4, - 284.9, - 282.6, - 280.2, - 278.1, - 275.7, - 272.6, - 270.2, - 268.5, - 267.3, - 266.5, - 266.1, - 265.7, - 265.8, - 265.1, - 263.5, - 262.7, - 261.5, - 260, - 258.6, - 257.3, - 255.2, - 252.4, - 249.2, - 246.4, - 243.8, - 241.3, - 238.9, - 236, - 232.8, - 229.6, - 225.9, - 221.4, - 217.3, - 213.9, - 210.4, - 207, - 203.3, - 199.9, - 196.8, - 193.7, - 190.7, - 188.4, - 186.8, - 185.3, - 184.7, - 185.2, - 183.9, - 181.4, - 176.8, - 173.7, - 170.6, - 169.5, - 167.9, - 165.2, - 163.4, - 163.4, - 164.3, - 651.3, - 651.3, - 651.4, - 651.5, - 651.6, - 651.6, - 651.5, - 651.4, - 651.3, - 651.1, - 650.6, - 650.4, - 649.7, - 648.8, - 648, - 647.3, - 647.8, - 648.6, - 648.4, - 647.6, - 646, - 645.2, - 645, - 645.9, - 646.9, - 647.3, - 647.1, - 646.4, - 645.7, - 646.4, - 647.8, - 649, - 650.3, - 651.5, - 651.8, - 652.1, - 652.4, - 652.5, - 652.7, - 652.8, - 653, - 653.1, - 653.2, - 653.3, - 652.9, - 652.3, - 650.8, - 646.6, - 639.7, - 636.2, - 634.6, - 631.9, - 628.5, - 626, - 623.5, - 621.1, - 618.7, - 616.5, - 614.2, - 612.2, - 610.4, - 608.8, - 607.7, - 606.8, - 605.4, - 603.5, - 599.7, - 595.7, - 591.7, - 587.4, - 582.9, - 578.9, - 575.4, - 572.1, - 569.2, - 566.4, - 563.7, - 560.5, - 558, - 555.8, - 554, - 552, - 549.7, - 547, - 543.9, - 541.9, - 539.4, - 537.2, - 534.1, - 532.1, - 530.2, - 528.3, - 526.4, - 524.4, - 522.6, - 520.2, - 517.8, - 515.8, - 514.1, - 512.1, - 509.6, - 507.4, - 504.9, - 502.7, - 500, - 498.5, - 498.3, - 497.9, - 497, - 495.7, - 494.2, - 492.1, - 489.8, - 487.4, - 485.4, - 483, - 480.2, - 477.6, - 474.4, - 472.1, - 468, - 462.5, - 458, - 455, - 452.2, - 449.7, - 447.4, - 444.7, - 442.5, - 440.4, - 439, - 437.6, - 435.5, - 433.1, - 430.6, - 428.3, - 425.5, - 423.1, - 421.9, - 420.9, - 419.7, - 417.3, - 412.5, - 409.2, - 408.9, - 408.7, - 406.2, - 402.8, - 400.5, - 397.2, - 393.2, - 392.3, - 390.8, - 385.8, - 377.7, - 372, - 366.9, - 362.9, - 358.1, - 355.4, - 354.1, - 352.8, - 351.5, - 350.1, - 348.9, - 348, - 347.2, - 347.5, - 350.1, - 353.8, - 352.7, - 349.2, - 345.8, - 344.6, - 344.9, - 345.6, - 346.3, - 349.2, - 345.9, - 339, - 336.2, - 334.4, - 332.7, - 331.2, - 329.5, - 327.4, - 325.4, - 323.5, - 321.8, - 320, - 318.1, - 316, - 314.2, - 311.6, - 308.6, - 305.8, - 302.8, - 300.7, - 298.9, - 296.2, - 291.8, - 287.6, - 284.9, - 282.8, - 280.8, - 277.8, - 275.1, - 274.4, - 272.8, - 271.5, - 270.5, - 268.8, - 268.8, - 269, - 267.6, - 266.7, - 265.5, - 264.1, - 262.9, - 261.1, - 258.6, - 256.3, - 253.3, - 250.5, - 247.8, - 245.3, - 242.1, - 239.3, - 236.4, - 233.3, - 229.8, - 226, - 221.9, - 218.2, - 214.2, - 210.6, - 207.1, - 203.9, - 201.3, - 198.6, - 195.7, - 192.6, - 190.2, - 188.7, - 190.3, - 190.8, - 189.3, - 186.2, - 182.4, - 178.4, - 175, - 173.1, - 170.9, - 168, - 165.5, - 165.7, - 167.3, - 651.1, - 651, - 651, - 651.1, - 651.3, - 651.4, - 651.2, - 650.8, - 650.6, - 649.9, - 648.7, - 647.9, - 646.9, - 645.9, - 645.5, - 645.3, - 645.2, - 644.9, - 644.5, - 644.3, - 643.5, - 642.9, - 643.3, - 644.2, - 644.7, - 644.1, - 643.4, - 643.5, - 644.3, - 645.3, - 646.6, - 648.1, - 649.3, - 650.5, - 651.1, - 651.3, - 651.6, - 652, - 652.3, - 652.8, - 653, - 653, - 653.1, - 652.9, - 652.5, - 652.2, - 651.1, - 645.3, - 639, - 636.3, - 634.9, - 632.2, - 629, - 627.4, - 625.7, - 622.4, - 619.6, - 617.7, - 615.8, - 614.3, - 613, - 611.5, - 610.9, - 610.3, - 607.9, - 605.4, - 601.7, - 597.9, - 593.5, - 588.5, - 583.6, - 579.6, - 576.5, - 573.3, - 570.3, - 567.5, - 564.6, - 561.6, - 558.6, - 555.8, - 553.8, - 552.1, - 549.7, - 547.3, - 544.3, - 541.9, - 539.5, - 537.1, - 534.8, - 532.5, - 530.4, - 529, - 526.7, - 524.6, - 522.9, - 520.9, - 518.6, - 517, - 515.1, - 513.1, - 511.3, - 508.9, - 506.4, - 503.8, - 502.7, - 502.9, - 502.6, - 501.2, - 499.5, - 497.7, - 495.7, - 493.6, - 490.6, - 487.8, - 484.9, - 482.4, - 479.9, - 477.7, - 475.4, - 472.6, - 469.3, - 465.1, - 460.1, - 456.1, - 453.3, - 450.4, - 447.7, - 444.9, - 442.2, - 440.1, - 438.7, - 437.2, - 435.2, - 433.1, - 431.1, - 428.8, - 426.9, - 424.8, - 422.9, - 421.7, - 420.3, - 417.7, - 413.2, - 411.7, - 411.6, - 410, - 407.4, - 405.6, - 404.4, - 402.5, - 399.8, - 396.5, - 392.4, - 387.1, - 378.4, - 372.3, - 366.6, - 361.8, - 358.8, - 357.3, - 356.1, - 354.8, - 353.6, - 352.9, - 351.8, - 350.7, - 350, - 350.6, - 353.2, - 358.5, - 360.6, - 358.4, - 353.1, - 350.7, - 352.5, - 356.3, - 359.7, - 361.1, - 356, - 346.2, - 339.2, - 336.6, - 334.1, - 331.9, - 330.5, - 329.4, - 327.5, - 325.3, - 323.8, - 321.3, - 319.4, - 318, - 316.4, - 314.8, - 312.9, - 310.5, - 307.6, - 305.1, - 302.7, - 299.9, - 297.2, - 293.5, - 290.2, - 288, - 285.4, - 283, - 280.5, - 278.8, - 277.4, - 275.9, - 274.3, - 272.5, - 272.9, - 272.8, - 271.8, - 270.3, - 269.3, - 268.5, - 266.7, - 264.7, - 261.9, - 259.7, - 257.1, - 254.8, - 252.3, - 249.6, - 246.1, - 242.2, - 239.1, - 236.4, - 233.4, - 230, - 226.2, - 222.3, - 218.4, - 214.6, - 210.9, - 207.4, - 204.4, - 201.5, - 198.9, - 195.9, - 193.3, - 192.4, - 195.5, - 196, - 195.7, - 193, - 189.4, - 184.5, - 178.6, - 175.9, - 173.8, - 171.8, - 169.3, - 169.5, - 170.7, - 650.5, - 650.5, - 650.6, - 650.4, - 650.3, - 650.3, - 649.9, - 649.4, - 648.8, - 647.5, - 646.2, - 645, - 644, - 643.7, - 643.8, - 643.4, - 642.7, - 642.1, - 641.7, - 641.6, - 641.3, - 641.1, - 641.4, - 642.1, - 642, - 641.3, - 641.6, - 642.3, - 643.3, - 644.3, - 645.5, - 647, - 648.2, - 649.2, - 649.6, - 649.8, - 650, - 650.6, - 651.5, - 652.3, - 652.5, - 652.7, - 652.8, - 652.7, - 652.3, - 651.7, - 648.7, - 643.8, - 640.2, - 637, - 634.9, - 632.9, - 630.9, - 629.1, - 626.9, - 623.2, - 620.5, - 619.1, - 617.8, - 616.4, - 615.2, - 614.3, - 613.5, - 612.4, - 609.6, - 606.7, - 603.3, - 599.1, - 594.4, - 589.3, - 584.3, - 579.8, - 576.6, - 573.7, - 570.6, - 567.7, - 565, - 561.9, - 558.9, - 556.2, - 554, - 552.1, - 549.6, - 547, - 544.5, - 542.4, - 540.1, - 537.9, - 535.8, - 533.5, - 531.6, - 529.4, - 527.1, - 524.8, - 523.3, - 521.4, - 519.7, - 518, - 516.3, - 514.5, - 512.9, - 510, - 507.1, - 505.9, - 506.6, - 506.1, - 504.7, - 503, - 501.2, - 498.8, - 496.2, - 494.1, - 491.1, - 488.4, - 485.6, - 482.5, - 479.7, - 477.2, - 474.8, - 472.2, - 469.1, - 466.2, - 462.6, - 458.3, - 455.2, - 451.8, - 448.2, - 445.2, - 442.3, - 439.8, - 438.3, - 437, - 435.5, - 433.5, - 431.7, - 429.9, - 428.2, - 425.9, - 424.3, - 423, - 421.1, - 418.6, - 416.5, - 415, - 413.4, - 411.7, - 410.3, - 409.6, - 410.1, - 408, - 402.2, - 397, - 392, - 387.4, - 377, - 371.8, - 366, - 362.1, - 360.6, - 359.4, - 358.1, - 356.9, - 355.7, - 355.5, - 355.2, - 354, - 352.7, - 353.3, - 357.6, - 361.4, - 362.9, - 363.4, - 360.7, - 358.9, - 362.9, - 367, - 370.1, - 369.8, - 364.2, - 354.4, - 343.3, - 340.3, - 337.9, - 334.8, - 332.5, - 331.8, - 330.5, - 327.8, - 325.7, - 323.4, - 321, - 319.5, - 318.1, - 316.4, - 314.4, - 313, - 311.4, - 309.7, - 307.7, - 304.6, - 301.2, - 298.3, - 296.2, - 293.7, - 291, - 289.4, - 287.1, - 284.6, - 282.7, - 280.1, - 277.8, - 276.3, - 277.5, - 277.8, - 276.8, - 274.8, - 273.4, - 271.9, - 269.8, - 268.4, - 265.5, - 263.1, - 260.9, - 258.5, - 255.6, - 253, - 250, - 246.2, - 242.1, - 239, - 236.5, - 233.4, - 230.1, - 226.3, - 222.4, - 218.8, - 215.2, - 211.4, - 208.2, - 205, - 201.3, - 196.9, - 194.3, - 196.3, - 201, - 201.8, - 202.2, - 200.9, - 197.4, - 192.6, - 186.2, - 179.6, - 176.8, - 175.3, - 174.5, - 174.9, - 175.1, - 648.6, - 649.3, - 649.5, - 649.2, - 648.8, - 648.4, - 647.5, - 646.6, - 645.5, - 644, - 642.7, - 642, - 641.5, - 641.6, - 641.6, - 641.1, - 640.7, - 640.3, - 639.9, - 639.8, - 639.8, - 639, - 638.7, - 638.9, - 639.2, - 639.5, - 640.3, - 641, - 642.2, - 643.6, - 644.8, - 646, - 647.2, - 647.7, - 648.1, - 648.5, - 648.5, - 648.5, - 649.4, - 650.1, - 650.4, - 650.9, - 651.8, - 652.3, - 651.8, - 649.1, - 645.3, - 642.9, - 641, - 638.3, - 635.4, - 633.4, - 630.7, - 628.2, - 625.6, - 623.6, - 622.1, - 620.7, - 619.8, - 618.7, - 617.8, - 617.1, - 616.1, - 614.6, - 612, - 609.4, - 606.5, - 600.7, - 594.3, - 589.3, - 584.7, - 580.1, - 576.7, - 573.8, - 570.6, - 567.5, - 564.8, - 561.7, - 559.4, - 557.2, - 554.7, - 552.3, - 550, - 547.4, - 545, - 543.1, - 541.2, - 539, - 536.7, - 534.4, - 532.7, - 530.5, - 528.1, - 525.6, - 524.1, - 522.5, - 520.7, - 519.5, - 518, - 516.1, - 513.1, - 510.2, - 509.1, - 509, - 509, - 507.6, - 505.8, - 503.9, - 501.7, - 499.3, - 496.7, - 494, - 491, - 487.8, - 484.6, - 481.3, - 478.7, - 476.4, - 473.3, - 470.5, - 468.1, - 465.6, - 462.9, - 459.6, - 456.1, - 452.4, - 448.5, - 445.6, - 442.4, - 439.7, - 438.2, - 437, - 435.7, - 434.2, - 432.6, - 431.1, - 429.2, - 427.5, - 426.3, - 424.4, - 422.3, - 420.6, - 418.9, - 417.1, - 415.4, - 413.9, - 413.5, - 413.7, - 412.1, - 407.7, - 401.6, - 396.4, - 392.1, - 387.4, - 379.6, - 372.1, - 366.1, - 363.9, - 362.6, - 361.4, - 360.3, - 359.4, - 359, - 358.9, - 358.1, - 357.2, - 356.2, - 357.1, - 360.3, - 362.1, - 363.9, - 365.8, - 367, - 367.5, - 370.2, - 373.2, - 375.9, - 376.8, - 371.8, - 360, - 348.1, - 343.4, - 340.1, - 338.2, - 337.1, - 336.8, - 333.5, - 330.5, - 327.9, - 325.7, - 323.8, - 321.7, - 320.3, - 318.7, - 316.4, - 314.4, - 313, - 311.6, - 310.4, - 308.4, - 305.8, - 303.1, - 301.2, - 299.6, - 297.3, - 295.3, - 292.3, - 289.8, - 287.4, - 284.8, - 282.5, - 281.8, - 282.3, - 282.9, - 281.4, - 279.1, - 277.6, - 275.7, - 273.6, - 271.7, - 268.9, - 266.6, - 264.4, - 261.1, - 258.1, - 255.7, - 253.3, - 250.1, - 246.6, - 242.8, - 239.3, - 236.2, - 233, - 229.8, - 226.3, - 222.7, - 219, - 215.1, - 211.5, - 207.6, - 202.2, - 197.4, - 196.3, - 200.9, - 204.9, - 205.6, - 205.2, - 204.4, - 202.9, - 200.7, - 197.5, - 190.7, - 182.6, - 179.5, - 179.9, - 181.2, - 181.3, - 644.9, - 646, - 647, - 646.8, - 645.9, - 645, - 643.8, - 642.6, - 641.4, - 640.3, - 639.8, - 639.8, - 639.7, - 639.6, - 639.4, - 639, - 638.9, - 638.8, - 638.7, - 638.3, - 637.8, - 637.4, - 637, - 636.9, - 637.3, - 638.1, - 638.6, - 639.5, - 641.1, - 642.5, - 643.6, - 645, - 646, - 646.4, - 646.6, - 646.7, - 646.6, - 646.8, - 647.4, - 647.8, - 648.2, - 648.8, - 650.3, - 651.8, - 651, - 647.7, - 644.5, - 642.6, - 640.7, - 637.4, - 634.8, - 632.6, - 630.4, - 628.6, - 626.9, - 625.7, - 623.9, - 622.8, - 622.8, - 621.9, - 621, - 620, - 618.4, - 616, - 613.4, - 610.3, - 607.5, - 602.5, - 593.9, - 589, - 584.6, - 580.3, - 577, - 574, - 570.7, - 567.5, - 564.6, - 562, - 560.2, - 557.9, - 555.4, - 552.8, - 550.7, - 548.5, - 546.2, - 544, - 542.2, - 540.1, - 538, - 535.6, - 533.7, - 531.9, - 530, - 527.5, - 525.2, - 523.9, - 522, - 520.9, - 518.9, - 515.5, - 513.3, - 513.1, - 512.6, - 511.8, - 510, - 508.3, - 506.7, - 504.4, - 501.9, - 499.5, - 497, - 493.9, - 490.2, - 486.5, - 483.2, - 480.1, - 477.9, - 475.5, - 472.6, - 469.8, - 467.5, - 465.2, - 463, - 459.4, - 455.5, - 451.7, - 449.2, - 446.8, - 443.6, - 440.2, - 438.5, - 437.3, - 436.2, - 435.2, - 434, - 432.5, - 430.9, - 429.8, - 427.4, - 424.8, - 423.3, - 421.8, - 420.4, - 418.9, - 417.5, - 416.4, - 417, - 415.9, - 411.9, - 405.3, - 399.1, - 394.3, - 391.9, - 388.6, - 381.3, - 372.5, - 367.1, - 365.8, - 364.6, - 363.5, - 362.5, - 362.4, - 363.1, - 362.8, - 361.9, - 362, - 360.6, - 360, - 361.2, - 362.7, - 364.9, - 367.7, - 371.2, - 374.1, - 376.4, - 379.5, - 382.4, - 380.2, - 375.7, - 366, - 353, - 346.3, - 343.3, - 342.3, - 341.6, - 340.6, - 338.6, - 333.4, - 330, - 327.8, - 325.8, - 324.3, - 322.4, - 320.5, - 318.4, - 316.8, - 315.5, - 313.8, - 312.3, - 310.9, - 309.2, - 307.6, - 305.6, - 303.6, - 301.9, - 299.8, - 297.4, - 295.2, - 292.5, - 289.4, - 287.7, - 287.6, - 288.2, - 288.2, - 286, - 283.6, - 281.5, - 279.2, - 277.2, - 274.6, - 271.5, - 269.3, - 267.3, - 264.2, - 260.7, - 258.1, - 256.2, - 253.5, - 251, - 247.7, - 243.5, - 239.7, - 235.8, - 232.5, - 229.5, - 226.2, - 222.4, - 218.6, - 215, - 210.5, - 203.5, - 199.1, - 200.1, - 204.6, - 207.4, - 207.7, - 207.1, - 206.3, - 205.3, - 204, - 202.4, - 200.3, - 195, - 188.2, - 186.2, - 187.1, - 187.1, - 642.1, - 642.3, - 642.7, - 642.8, - 642.2, - 641.2, - 640.4, - 639.3, - 638.5, - 638.1, - 638, - 638, - 637.9, - 637.7, - 637.6, - 637.3, - 637.4, - 637.4, - 637.5, - 636.9, - 636.3, - 635.7, - 635.1, - 635.4, - 636.2, - 636.6, - 637.3, - 638.7, - 640.1, - 641.3, - 642.4, - 643.6, - 644.5, - 644.9, - 644.8, - 644.7, - 644.9, - 645.3, - 645.9, - 646.1, - 646, - 646.9, - 648.5, - 650.2, - 649.7, - 647.3, - 644.4, - 642.1, - 640, - 638, - 636.1, - 634, - 632.1, - 630.6, - 630, - 629.1, - 627.7, - 626.7, - 626, - 625, - 623.2, - 621.2, - 618.9, - 616.4, - 613.2, - 610.1, - 607.2, - 600.9, - 593, - 588.3, - 584.3, - 580.3, - 576.9, - 573.9, - 570.6, - 567.7, - 565.1, - 563, - 561.1, - 558.6, - 556.1, - 553.8, - 551.7, - 549.6, - 547.6, - 545.1, - 542.9, - 541, - 539.3, - 536.7, - 534.5, - 532.4, - 530.7, - 529, - 526.6, - 525.2, - 523.7, - 521.6, - 518.4, - 516.6, - 516.4, - 516.5, - 515.1, - 513, - 510.6, - 508.7, - 507.1, - 504.7, - 502.1, - 499.5, - 496.5, - 492.7, - 488.6, - 485, - 482.1, - 479.8, - 477.4, - 474.5, - 471.5, - 468.9, - 466.5, - 463.9, - 460.9, - 457.9, - 455, - 453, - 450.9, - 448.2, - 445, - 441.7, - 439.7, - 438.2, - 436.9, - 435.8, - 434.6, - 433.4, - 432, - 429.7, - 427.3, - 425.7, - 424.3, - 423.2, - 422.1, - 420.6, - 419.1, - 419.1, - 419.2, - 416, - 407.2, - 399.6, - 396.9, - 394.5, - 391.6, - 387.9, - 380.5, - 374.6, - 370.2, - 368, - 366.8, - 365.7, - 365, - 365.4, - 367, - 367.8, - 368.2, - 368, - 366.5, - 364, - 363, - 363.8, - 366.8, - 371.8, - 377.9, - 383.4, - 387.8, - 391.9, - 390.9, - 384, - 375.6, - 365, - 353.2, - 348.3, - 347.1, - 346.3, - 345.1, - 343.2, - 340.5, - 337, - 331.7, - 329, - 327.6, - 326.5, - 325.1, - 322.8, - 320.3, - 318.8, - 317.4, - 315.9, - 314.4, - 313.2, - 311.5, - 309.8, - 308.1, - 306.2, - 305.6, - 303.6, - 301.2, - 299, - 296.7, - 294.6, - 293, - 292.8, - 293.9, - 293.4, - 291.2, - 288.9, - 286.3, - 283.2, - 280.5, - 277.5, - 274.7, - 272, - 269.7, - 267.1, - 263.7, - 260.8, - 258.6, - 256.7, - 255.3, - 252.1, - 247.3, - 242.4, - 238.3, - 234.3, - 231.4, - 228.9, - 225.3, - 221.6, - 217.8, - 211.3, - 204.4, - 201.3, - 204, - 208.3, - 210.1, - 209.7, - 209.1, - 208.1, - 207.1, - 205.9, - 205.2, - 204, - 201.6, - 197.4, - 194.6, - 193.4, - 192.8, - 640.4, - 640.2, - 640, - 639.4, - 638.8, - 638.2, - 637.3, - 636.7, - 636.4, - 636.4, - 636.3, - 636.2, - 636.1, - 635.9, - 636.1, - 636.1, - 635.9, - 635.9, - 635.8, - 635.2, - 634.2, - 633.2, - 633.4, - 634.2, - 634.4, - 635.1, - 636.5, - 637.9, - 639.2, - 640.1, - 641.2, - 642.2, - 643, - 643.2, - 643, - 642.8, - 643.1, - 643.5, - 644, - 644.3, - 644.4, - 645.3, - 646.4, - 647.4, - 647.7, - 647.1, - 645.4, - 643.6, - 642, - 639.7, - 637.5, - 635.7, - 634.2, - 633.2, - 632.4, - 631.1, - 629.8, - 628.4, - 627, - 625.1, - 622.4, - 619.4, - 616.4, - 613.5, - 610.2, - 607.4, - 603.9, - 596.9, - 591.5, - 586.8, - 583, - 579.4, - 576.4, - 573.4, - 570.5, - 568.2, - 566, - 563.8, - 561.8, - 559.3, - 557.2, - 555.3, - 553.1, - 550.9, - 548.9, - 546.4, - 544.4, - 542.4, - 540.8, - 538.4, - 535.7, - 533.4, - 531.6, - 529.7, - 527.7, - 526.2, - 524.6, - 522.5, - 520.4, - 519.3, - 518.1, - 517.3, - 515.6, - 513.8, - 511.4, - 509.2, - 507.1, - 504.4, - 501.9, - 499.2, - 496.3, - 492.2, - 487.8, - 484.7, - 482.3, - 480.1, - 477.8, - 474.9, - 471.7, - 468.9, - 466.2, - 463.1, - 460.1, - 458.1, - 456.9, - 455.4, - 452.7, - 449.3, - 447, - 444.9, - 442.7, - 440.6, - 438.4, - 436.1, - 434.3, - 433, - 431.5, - 429.3, - 427.7, - 426.7, - 425.6, - 425.1, - 424.2, - 422.3, - 421.6, - 421.6, - 418.9, - 407, - 398.3, - 396.3, - 395.1, - 393.8, - 390.7, - 384.1, - 379.5, - 377.5, - 373.5, - 370.1, - 369, - 367.9, - 367.5, - 368.4, - 369.9, - 372.1, - 374.5, - 375.4, - 374, - 372, - 370.2, - 369.9, - 374.8, - 382.5, - 388.1, - 392.3, - 396.4, - 398.4, - 396.2, - 390.4, - 381.5, - 371, - 359.3, - 351.7, - 349.7, - 349.3, - 348.1, - 345.5, - 343, - 339.5, - 335.2, - 332.1, - 329.6, - 328, - 327, - 325.1, - 322.5, - 321, - 318.8, - 317.3, - 316, - 315.3, - 313.9, - 311.8, - 310.7, - 309.5, - 308.3, - 306.8, - 304.6, - 302.3, - 300.1, - 297.9, - 296.5, - 297.1, - 298.2, - 297.6, - 296.2, - 294.4, - 291.6, - 288, - 284.7, - 281.5, - 278.4, - 275.1, - 272.4, - 269.7, - 266.3, - 263.7, - 261.5, - 259.1, - 257.8, - 255.6, - 251.3, - 243.9, - 239.2, - 235.5, - 232.6, - 230.2, - 227.4, - 224.4, - 219.9, - 212.3, - 207, - 204.4, - 207.6, - 211.6, - 213.3, - 213.1, - 212.3, - 211.2, - 210, - 208.5, - 208.4, - 207.2, - 205.5, - 202.9, - 201.4, - 199.1, - 197.7, - 638.7, - 638.3, - 637.6, - 636.5, - 635.9, - 635.5, - 635, - 635, - 634.8, - 634.5, - 634.5, - 634.3, - 634.1, - 634, - 634.3, - 634.6, - 634.4, - 634.4, - 633.8, - 632.4, - 630.8, - 631.2, - 632.3, - 632.4, - 632.9, - 634, - 635.2, - 636.8, - 637.8, - 638.8, - 640, - 640.9, - 641.3, - 641.5, - 641.4, - 641.5, - 641.6, - 641.4, - 641.5, - 642.1, - 643, - 643.9, - 644.8, - 645.8, - 646.4, - 647.6, - 647.7, - 646.5, - 644.4, - 641.6, - 638.9, - 638.1, - 636.8, - 635.2, - 633.3, - 631.2, - 629.1, - 627.1, - 625.3, - 623, - 618.9, - 615.1, - 612, - 608.8, - 605.9, - 602.5, - 598.6, - 593.6, - 588.3, - 583.9, - 580.8, - 578.3, - 575.7, - 573.3, - 571.1, - 569.1, - 567, - 564.5, - 562, - 560.2, - 558.3, - 556.8, - 554.8, - 552.4, - 549.9, - 547.7, - 546.2, - 544.9, - 543, - 540.3, - 537.1, - 534.8, - 532.5, - 530.4, - 528.9, - 527.5, - 525.9, - 523.7, - 522, - 520.9, - 519.1, - 517.4, - 515.8, - 514.1, - 511.8, - 509.4, - 506.7, - 504.1, - 501.6, - 498.6, - 495.3, - 491.1, - 487.5, - 484.9, - 482.7, - 480.4, - 477.8, - 475, - 472, - 469, - 465.2, - 462.3, - 460.6, - 459.4, - 458.1, - 456.3, - 453.6, - 451.1, - 449.5, - 447.7, - 445.4, - 443.4, - 440.2, - 436.8, - 434.5, - 432.8, - 431.3, - 429.9, - 428.7, - 427.8, - 427.3, - 426.9, - 425.4, - 424.2, - 423.8, - 423, - 412.1, - 398.1, - 395.6, - 394.3, - 392.6, - 389.6, - 385.8, - 382.3, - 380.2, - 377.9, - 374.1, - 372.3, - 371.3, - 370.3, - 370.1, - 370.8, - 372.6, - 375.5, - 378.8, - 380.8, - 381.2, - 381.6, - 382.5, - 385.9, - 389.3, - 392.6, - 395.9, - 399.9, - 403, - 404, - 401.7, - 395, - 386.7, - 378.6, - 369.8, - 358.2, - 351.9, - 350.5, - 349.4, - 347.4, - 344.5, - 342, - 339.4, - 336.5, - 334.4, - 332.5, - 329.6, - 326.9, - 325.4, - 324.3, - 321.3, - 319.5, - 318.1, - 317.3, - 316.9, - 315.7, - 314.6, - 313.7, - 310.8, - 308.7, - 307.2, - 305.3, - 303.1, - 300.8, - 299.4, - 300.4, - 301.7, - 301.3, - 300.2, - 297.8, - 295, - 291.9, - 288.6, - 285.2, - 281.9, - 278.2, - 274.8, - 271.9, - 269, - 266.4, - 264.6, - 261.9, - 259.4, - 257.7, - 254.1, - 246.7, - 241.4, - 236.8, - 233.5, - 230.9, - 228.8, - 226.5, - 221.8, - 214.6, - 209.7, - 207.4, - 210.4, - 214, - 216.5, - 216.8, - 216.3, - 215.5, - 214.2, - 212.5, - 212.9, - 211.2, - 209, - 207, - 205, - 203, - 201.6, - 636.4, - 635.5, - 634.7, - 633.9, - 633.5, - 633.4, - 633.3, - 633.1, - 632.8, - 632.4, - 632.4, - 632.1, - 632.3, - 632.5, - 632.5, - 632.6, - 632.7, - 631.9, - 630.2, - 628.6, - 628.7, - 629.5, - 630, - 630.6, - 631.6, - 632.9, - 634, - 635.2, - 636.5, - 637.6, - 638.5, - 639.2, - 639.6, - 639.6, - 639.7, - 639.7, - 639.6, - 639.5, - 640, - 640.9, - 641.6, - 642.2, - 643.3, - 644.5, - 646.3, - 648.9, - 648.9, - 647.4, - 644.6, - 643, - 642.3, - 640.7, - 639, - 636.4, - 632.7, - 629.2, - 626.6, - 624.1, - 621.9, - 618.9, - 615, - 611, - 607.4, - 604.5, - 602.2, - 598.2, - 593.4, - 589.2, - 585.3, - 582.1, - 579.9, - 578, - 575.9, - 573.9, - 571.8, - 569.7, - 567.6, - 565.6, - 563.7, - 561.9, - 559.8, - 558, - 556.1, - 553.7, - 550.7, - 548.3, - 546.8, - 545.4, - 543.8, - 541, - 537.7, - 535.4, - 533.3, - 530.9, - 529.2, - 527.8, - 526.5, - 524.5, - 522.4, - 521, - 519.6, - 517.9, - 516.1, - 513.8, - 511.2, - 508.7, - 506.2, - 503.7, - 501.1, - 497.7, - 493.6, - 489.6, - 487, - 485, - 483, - 480.6, - 477.8, - 474.9, - 471.9, - 468.3, - 464.9, - 463.3, - 461.7, - 460, - 458.5, - 456.8, - 455, - 453.7, - 452, - 450.5, - 449.2, - 445.3, - 440.9, - 437.3, - 435.2, - 433.6, - 432, - 430.8, - 429.7, - 429, - 428.5, - 427.6, - 426.6, - 426, - 425, - 420.8, - 407.6, - 398.3, - 395.4, - 393.3, - 389.7, - 386.3, - 384.5, - 382.7, - 380, - 377.9, - 375.8, - 374.7, - 373.7, - 372.8, - 372.6, - 373.9, - 376.1, - 378.9, - 381, - 382.9, - 385.5, - 387.4, - 389.2, - 392.1, - 395.7, - 399.9, - 403.6, - 406.8, - 408.5, - 410.5, - 410.7, - 401.5, - 389.1, - 382.2, - 375.1, - 361.4, - 354.1, - 350.9, - 349, - 347.2, - 345.3, - 344.1, - 342, - 339.8, - 338.4, - 337.4, - 334.2, - 329.8, - 327.3, - 326.3, - 324.3, - 322.1, - 320.8, - 319.3, - 319.2, - 318.8, - 317.4, - 316.2, - 314, - 310.8, - 308.5, - 307.7, - 305.6, - 303.5, - 302.3, - 303.7, - 305.3, - 305, - 303.1, - 300.5, - 297.7, - 294.7, - 291.7, - 288.6, - 285.3, - 281.6, - 278.1, - 274.6, - 271.5, - 269.1, - 267.4, - 265, - 261.5, - 258.8, - 255.4, - 250, - 244.8, - 238.9, - 234.3, - 231.5, - 229.5, - 227.3, - 222.8, - 217.3, - 211.7, - 210.1, - 213, - 215.9, - 219.1, - 220.8, - 220.5, - 219.5, - 218.8, - 217.5, - 217.6, - 216.3, - 213.7, - 211.5, - 208.8, - 206.4, - 205, - 633.4, - 632.7, - 632.2, - 631.8, - 631.8, - 631.6, - 631.5, - 631, - 630.4, - 630.1, - 629.8, - 630, - 631, - 631.3, - 631, - 630.8, - 629.7, - 628.1, - 626.9, - 626.6, - 627.2, - 627.7, - 628.4, - 629.1, - 630.3, - 631.8, - 632.6, - 633.6, - 635.1, - 636.4, - 637, - 637.5, - 638, - 638, - 637.8, - 637.6, - 637.7, - 638.3, - 639.3, - 639.9, - 640.5, - 641.1, - 642.1, - 644, - 646.4, - 647.8, - 648.3, - 648.5, - 647.4, - 646.8, - 645.5, - 643.4, - 641.1, - 636.9, - 633.1, - 628.3, - 624, - 621.3, - 619.2, - 616.4, - 613.3, - 609.4, - 605.6, - 601.3, - 597.5, - 593.6, - 590.5, - 588.1, - 585.8, - 583.6, - 581.6, - 579.6, - 577.5, - 575.1, - 573.2, - 571.1, - 569, - 567.4, - 565.6, - 564, - 562.4, - 560, - 557.7, - 555.4, - 552.6, - 548.9, - 546.2, - 544.6, - 542.7, - 539.9, - 537.7, - 535.6, - 533.5, - 531.1, - 529.1, - 527.9, - 526.5, - 524.5, - 522.8, - 521.2, - 519.6, - 517.5, - 515, - 511.9, - 509.6, - 507.7, - 505.6, - 503.1, - 500.5, - 496.1, - 492.4, - 489.5, - 487.1, - 485.3, - 483.5, - 481.2, - 478.3, - 475.4, - 472.1, - 469, - 467.5, - 466, - 463.7, - 461.2, - 459, - 457.4, - 456.4, - 455.8, - 454.9, - 453.4, - 451.3, - 446.8, - 441.9, - 438.1, - 435.8, - 434.2, - 433, - 431.9, - 430.7, - 429.6, - 428.7, - 428.3, - 427.8, - 426.6, - 421.7, - 412.7, - 406.5, - 400.5, - 396.6, - 393.2, - 389.4, - 386.7, - 385.2, - 382.8, - 380.5, - 379.5, - 378.4, - 377.2, - 376.3, - 375.9, - 376.8, - 376.8, - 378.3, - 380.5, - 382.5, - 384.7, - 388, - 390.8, - 394.3, - 398.8, - 403.3, - 407.5, - 411, - 412.8, - 413.3, - 413.9, - 410.2, - 402.8, - 390.1, - 382.4, - 373.8, - 359.8, - 355.2, - 352.2, - 349.7, - 347.9, - 346.8, - 346.5, - 346.2, - 344.2, - 340.6, - 337.9, - 335.9, - 333.3, - 330.3, - 328.1, - 326.7, - 324.3, - 322.5, - 321.3, - 320.9, - 320.9, - 320.6, - 319.2, - 317.5, - 314.6, - 311.2, - 310.6, - 308.2, - 306.7, - 306.5, - 306.9, - 307.4, - 306.5, - 305.1, - 302.9, - 300.2, - 297.3, - 294.5, - 291.7, - 288.8, - 285.2, - 281.5, - 278.1, - 274.4, - 271.3, - 269.4, - 267.4, - 264.7, - 260.7, - 256.8, - 252.5, - 246.9, - 241.6, - 236.7, - 232.6, - 229.9, - 227.3, - 223, - 219.1, - 213.1, - 212.5, - 215.4, - 218.1, - 221.3, - 223.5, - 223.5, - 223.1, - 222.6, - 221.5, - 221.4, - 220.2, - 218.5, - 216.5, - 213.9, - 212.1, - 209.8, - 631.1, - 630.6, - 630.3, - 630.2, - 630, - 630, - 629.5, - 628.7, - 628.4, - 628, - 627.8, - 628.2, - 628.8, - 629.1, - 628.6, - 627.6, - 625.8, - 625, - 625, - 625.2, - 625.6, - 626.3, - 627.1, - 628, - 629, - 630.1, - 631.1, - 632.6, - 633.9, - 634.8, - 635.6, - 635.9, - 635.8, - 635.7, - 635.4, - 635.4, - 636.1, - 637.2, - 638.1, - 638.5, - 639.1, - 640.1, - 641.2, - 643, - 645.1, - 645.6, - 647.8, - 649.9, - 649.8, - 649.1, - 647.5, - 644.9, - 643.2, - 639.7, - 634.6, - 629.8, - 625.9, - 622.4, - 619.2, - 615.7, - 612.8, - 609.8, - 605.6, - 601, - 596.8, - 593.4, - 591.5, - 589.6, - 587.6, - 585.7, - 583.7, - 581.6, - 579.7, - 577.9, - 576, - 573.7, - 571, - 569.3, - 567.6, - 566.3, - 564.4, - 561.6, - 559.4, - 557.2, - 554.3, - 550.3, - 545.7, - 543.3, - 541.6, - 539.6, - 537.8, - 535.9, - 533.9, - 531.1, - 529.1, - 528.1, - 526.8, - 525.3, - 523.7, - 521.3, - 518.9, - 516.2, - 513.3, - 510.9, - 509, - 507, - 504.8, - 502.4, - 499.3, - 495.9, - 493.4, - 490.8, - 488, - 485.6, - 483.8, - 481.3, - 478.7, - 475.8, - 473.8, - 472.5, - 470.3, - 468.2, - 465.4, - 462.5, - 460.5, - 458.7, - 458.3, - 458.4, - 457.7, - 456.1, - 452.4, - 448.1, - 444.4, - 440.5, - 436.7, - 435, - 434.1, - 433, - 431.1, - 429.6, - 427.5, - 427, - 427.5, - 422.4, - 413.6, - 407.9, - 405.4, - 400.5, - 396.5, - 393.4, - 390.9, - 388.7, - 387.8, - 385.9, - 384.2, - 385.8, - 383.6, - 380.5, - 381.6, - 386.6, - 393.3, - 388.5, - 384.4, - 382.8, - 384.1, - 387.4, - 392.4, - 397, - 401.2, - 405.8, - 409.9, - 414.7, - 417, - 416.9, - 414.8, - 412.2, - 406.1, - 397.7, - 388.1, - 381.2, - 372.8, - 364.2, - 357.3, - 353.7, - 351.9, - 351.3, - 352, - 352, - 352.4, - 351.5, - 346, - 340.2, - 337.4, - 335.1, - 333.3, - 331.2, - 328.7, - 326.3, - 324.4, - 323.2, - 323, - 323.5, - 323.4, - 322.2, - 320.8, - 319, - 316.8, - 315.3, - 312.4, - 312.1, - 311.8, - 311.1, - 310.1, - 307.2, - 305.6, - 304.2, - 302, - 299.3, - 296.8, - 294.3, - 291.4, - 288, - 284.5, - 280.7, - 277.2, - 273.9, - 271.1, - 268.9, - 266.9, - 263, - 257.9, - 253.7, - 250, - 245.4, - 240.1, - 235.6, - 230.7, - 227.9, - 224.4, - 220, - 214.9, - 215, - 217.8, - 220.7, - 223.3, - 225.7, - 226.1, - 225.8, - 225.5, - 224.9, - 224.8, - 223.8, - 222.1, - 220.4, - 218, - 216.6, - 214.5, - 629, - 628.9, - 629, - 628.6, - 628.2, - 627.9, - 627.2, - 626.5, - 626.4, - 626.4, - 626.6, - 626.7, - 626.3, - 625.6, - 624.8, - 623.8, - 623, - 622.8, - 623.3, - 623.8, - 624.1, - 624.9, - 625.7, - 626.6, - 627.6, - 628.5, - 630, - 631.6, - 633, - 633.6, - 634.1, - 634, - 633.2, - 633, - 633.1, - 633.6, - 634.7, - 635.6, - 636.4, - 637, - 637.9, - 639, - 640.3, - 641.4, - 642.7, - 644.2, - 647.4, - 649.5, - 649.5, - 648.9, - 648.4, - 646.1, - 643.4, - 640.7, - 635.5, - 630.3, - 626.2, - 621.4, - 617.3, - 614.3, - 611.8, - 609.4, - 606.1, - 602.3, - 599, - 595.5, - 593.6, - 591.8, - 589.6, - 587.9, - 586.4, - 584.3, - 582.5, - 580.5, - 578.4, - 575.8, - 573.3, - 571.8, - 570.6, - 568.7, - 566, - 562.6, - 560.4, - 558.1, - 554.4, - 550.2, - 546.3, - 543.1, - 541, - 539.4, - 537.8, - 536.2, - 534.4, - 531.5, - 529.4, - 528.3, - 527.2, - 525.8, - 523.5, - 520.5, - 517.7, - 515.4, - 513, - 510.9, - 509, - 506.9, - 504.4, - 501.7, - 498.9, - 496.5, - 494.4, - 491.9, - 489.1, - 486.6, - 484.4, - 482.3, - 480.1, - 478.4, - 476.6, - 474.7, - 472.3, - 469.6, - 466.2, - 463.8, - 463.2, - 462.2, - 461.7, - 461.2, - 460.6, - 457, - 452.8, - 449.3, - 446.7, - 443.1, - 438.5, - 436.3, - 435.2, - 433.6, - 431.9, - 430.6, - 426.4, - 422.6, - 422, - 415.9, - 411.2, - 407.6, - 403.4, - 399.2, - 395.9, - 393.8, - 392.7, - 394.5, - 399, - 395.7, - 393.1, - 395, - 392.4, - 390.8, - 399.4, - 411.3, - 420, - 420.9, - 418.8, - 401.6, - 389.1, - 393.6, - 400, - 404.1, - 408, - 412.4, - 416.8, - 420.5, - 420.7, - 419.1, - 416.2, - 410.7, - 402.6, - 392.7, - 386.7, - 381, - 373.5, - 366.1, - 360, - 356.2, - 354.9, - 357.9, - 358.9, - 357.3, - 357.4, - 355.5, - 351.9, - 347.8, - 343, - 339.1, - 336.2, - 335.6, - 334.7, - 332.1, - 328.1, - 325.1, - 325.1, - 325.7, - 325.6, - 325.1, - 324.6, - 323.2, - 321.9, - 320.5, - 318.3, - 316.6, - 315.8, - 314.2, - 312.4, - 309.2, - 305.7, - 304.3, - 302.7, - 300.6, - 298.5, - 296.3, - 293.1, - 290, - 286.7, - 282.2, - 278.2, - 275.5, - 273.4, - 270.9, - 268.6, - 265.5, - 260.5, - 256.2, - 252.8, - 249.2, - 244.1, - 239.9, - 232.8, - 228.7, - 226, - 222.5, - 218, - 217.4, - 220.4, - 223.2, - 225.5, - 227.8, - 228.3, - 228.3, - 228.4, - 228.1, - 227.9, - 227.2, - 225.9, - 224.2, - 221.9, - 219.8, - 218, - 626.9, - 627.1, - 627.3, - 626.8, - 626.1, - 625.4, - 624.8, - 624.6, - 624.7, - 624.6, - 624.5, - 624.1, - 623.1, - 622, - 621.5, - 621, - 621, - 621.3, - 621.8, - 622.3, - 622.7, - 623.4, - 624.4, - 625.3, - 626.6, - 627.8, - 629.1, - 631, - 632.2, - 632.3, - 631.8, - 631.2, - 630.8, - 630.9, - 631.5, - 632.4, - 633.3, - 634, - 634.8, - 635.7, - 636.6, - 637.8, - 639.1, - 640.2, - 641.7, - 643.9, - 647.3, - 648.2, - 647.5, - 647.9, - 647.5, - 646.4, - 643, - 638.7, - 634.3, - 629.9, - 625.2, - 620.1, - 616.9, - 614.5, - 612.2, - 609.6, - 606.9, - 603.8, - 600.4, - 597.7, - 596, - 594.2, - 592.3, - 590.8, - 589.1, - 586.8, - 584.5, - 582.1, - 579.9, - 577.5, - 575.4, - 574.3, - 572.1, - 568.7, - 565.4, - 562.6, - 560.1, - 557.7, - 554.2, - 550.5, - 547.2, - 544.4, - 541.7, - 540.1, - 538.4, - 536.5, - 534.5, - 532.3, - 530, - 528.6, - 527.2, - 525.1, - 522.3, - 519.6, - 517.4, - 515.5, - 513, - 511, - 508.9, - 506.4, - 504.1, - 501.8, - 499.5, - 497.4, - 495.4, - 492.7, - 490.3, - 488.1, - 485.8, - 484.2, - 483.4, - 481.5, - 478.9, - 476.2, - 473.5, - 470.4, - 468.2, - 467.4, - 466.5, - 465.5, - 464.9, - 463.4, - 460.9, - 456.4, - 453.1, - 450.5, - 448.1, - 445, - 441, - 439.4, - 437.2, - 435.5, - 436.4, - 434.9, - 429.3, - 421.5, - 415.4, - 412.1, - 409.7, - 406.2, - 401.5, - 398.6, - 396.8, - 396, - 398.3, - 404.7, - 410, - 407.5, - 400.2, - 402, - 410.7, - 421.4, - 430.8, - 435.6, - 436.7, - 436.5, - 436.8, - 431, - 420.4, - 417.3, - 412.2, - 410.1, - 413.6, - 417.4, - 420.2, - 422.4, - 421.8, - 419.5, - 415.4, - 408.5, - 398.5, - 391.1, - 387.4, - 385.3, - 381, - 371.1, - 366, - 366.2, - 370.3, - 374.4, - 371.1, - 369.1, - 368.4, - 362.1, - 356.7, - 354.4, - 350.6, - 345.8, - 343, - 344.9, - 345.5, - 344.3, - 339.7, - 331.1, - 327.8, - 327.5, - 327.2, - 327.2, - 326.9, - 326.1, - 324.8, - 323.5, - 321.6, - 319.3, - 317.5, - 315.2, - 312.8, - 310, - 305.9, - 303.6, - 302.6, - 300.8, - 299, - 296.9, - 294.2, - 291.7, - 288.3, - 282.2, - 278.2, - 275.6, - 274.1, - 272.6, - 271, - 268.3, - 264.1, - 259.9, - 256.2, - 252.4, - 248.1, - 244.6, - 237.2, - 229.7, - 226.9, - 223.7, - 220.1, - 219.6, - 222.9, - 225.5, - 228, - 231, - 231.1, - 231, - 231.1, - 231.1, - 230.7, - 230.2, - 229.2, - 228.1, - 226, - 223.2, - 222.2, - 625.2, - 625.1, - 625, - 624.5, - 623.6, - 623.2, - 623, - 623.2, - 623.1, - 622.9, - 622.1, - 621.1, - 620.1, - 619.6, - 619.2, - 619, - 619.5, - 619.9, - 620.3, - 620.7, - 621.3, - 622.1, - 623, - 624.1, - 625.9, - 627.1, - 628.1, - 630, - 630.6, - 629.7, - 629, - 628.8, - 629, - 629.5, - 630.2, - 631.1, - 632, - 632.8, - 633.5, - 634.2, - 635.3, - 636.7, - 637.9, - 639.2, - 641.2, - 643.9, - 645.9, - 645.6, - 645.4, - 645.8, - 645.9, - 645.5, - 642.8, - 637.7, - 633.3, - 629, - 624.4, - 620.3, - 617.2, - 614.9, - 613.3, - 610.8, - 607.7, - 605, - 602.5, - 600.9, - 599.7, - 597.7, - 595.4, - 592.9, - 590.3, - 587.9, - 585.4, - 583.1, - 581.1, - 579.2, - 576.9, - 574.6, - 571.4, - 567.6, - 565.1, - 562.7, - 559.8, - 557.4, - 554.1, - 551.1, - 548.7, - 545.9, - 543.5, - 541.7, - 539.9, - 537.8, - 534.7, - 532.8, - 530.9, - 529, - 526.5, - 524, - 521.7, - 519.6, - 517.7, - 515.6, - 513.1, - 511.2, - 509, - 506.5, - 504.4, - 502.4, - 500.6, - 499, - 497.3, - 494.5, - 491.9, - 489.5, - 488, - 487.2, - 485.5, - 483, - 480.3, - 477.4, - 474.2, - 472.5, - 471.4, - 470.4, - 469, - 468.4, - 467.2, - 463.9, - 460.1, - 456.8, - 454, - 452, - 449.6, - 447, - 444.2, - 442.1, - 440.5, - 440.6, - 440.1, - 436.2, - 428.5, - 422.3, - 413.4, - 409.9, - 407.5, - 404.4, - 401.6, - 400.2, - 399.2, - 401, - 407.9, - 413, - 417.4, - 417.2, - 416.5, - 423.2, - 431.4, - 437.4, - 439.8, - 439.7, - 440.3, - 441.4, - 441.1, - 438.8, - 437.8, - 436.5, - 431, - 423.1, - 420.9, - 419.6, - 421.5, - 422.6, - 421.6, - 416.4, - 411.6, - 405.6, - 396.7, - 393.9, - 391.2, - 388.4, - 385.8, - 381, - 376.8, - 380.8, - 382.8, - 382.8, - 381.6, - 379.2, - 377.6, - 372.6, - 364.7, - 358.7, - 355.2, - 352.3, - 351, - 351.6, - 350.8, - 348.7, - 346.4, - 340.3, - 333.1, - 332, - 331.4, - 330.7, - 329.1, - 328, - 326.8, - 325.5, - 324.1, - 321.8, - 319.3, - 316.6, - 313.9, - 311.2, - 307, - 303.2, - 301.9, - 300.6, - 298.7, - 296.9, - 295, - 292.5, - 289.2, - 284.2, - 280.5, - 277.2, - 274.5, - 272.9, - 271.8, - 270.3, - 268.1, - 264.9, - 260.5, - 256.2, - 252, - 248.8, - 243.3, - 233.7, - 228.7, - 225.2, - 223.1, - 222.7, - 225.8, - 228.3, - 232, - 235.5, - 235, - 233.7, - 234.1, - 234.5, - 234.3, - 233.6, - 232, - 230.8, - 229.2, - 227.1, - 225.5, - 623.7, - 623.4, - 622.6, - 621.8, - 621.5, - 621.3, - 621.3, - 621.2, - 620.7, - 620.1, - 619.2, - 618.3, - 617.7, - 617.3, - 617.2, - 617.4, - 618.1, - 618.6, - 618.8, - 619.3, - 620, - 621, - 622.1, - 623.7, - 625.2, - 626.1, - 626.7, - 627.6, - 627.7, - 626.9, - 626.8, - 627.3, - 628, - 628.3, - 629, - 629.7, - 630.8, - 631.5, - 632.1, - 632.8, - 634, - 635.6, - 636.8, - 638.6, - 640.6, - 641.7, - 642.3, - 643.2, - 643.9, - 644.1, - 644.5, - 644.2, - 643.4, - 639.4, - 635.2, - 630.8, - 626.1, - 621.9, - 618.6, - 616.3, - 614.2, - 612, - 609.5, - 607.5, - 605.4, - 603.5, - 601.7, - 599, - 596.5, - 594.1, - 591.1, - 588.7, - 586.4, - 584.4, - 582.1, - 579.7, - 577.1, - 573.8, - 570.8, - 567.9, - 565.6, - 563.2, - 560.2, - 557.5, - 554.7, - 552.2, - 550.1, - 548, - 546, - 543.2, - 540.8, - 538.3, - 535.3, - 532.5, - 530.8, - 528.7, - 526.1, - 523.7, - 521.8, - 520.1, - 518.2, - 515.9, - 513.6, - 511.7, - 509.4, - 507.2, - 505.2, - 503.6, - 502.1, - 500.5, - 498.5, - 496, - 493, - 490.8, - 490.7, - 489, - 486.7, - 484, - 480.8, - 477.9, - 476, - 475.2, - 473.8, - 472.8, - 472, - 470.5, - 467.6, - 463.9, - 460.7, - 457.9, - 455.8, - 453.5, - 450.7, - 448.5, - 445.8, - 444.9, - 445, - 443.4, - 441, - 436.6, - 427.2, - 419.5, - 413.9, - 409.6, - 406.8, - 405.1, - 404.1, - 403.3, - 402.8, - 407, - 416.8, - 421.8, - 424.6, - 426, - 428, - 431.7, - 436.8, - 441.3, - 442.8, - 444, - 445.2, - 444.9, - 443.3, - 442.3, - 443.1, - 441.7, - 436.4, - 430.4, - 425.4, - 422.3, - 421.8, - 421.6, - 418.6, - 413.8, - 409.6, - 402.8, - 399.5, - 397.6, - 393.7, - 389.7, - 387.2, - 385.6, - 384.8, - 386, - 387.8, - 389.7, - 390.4, - 388.8, - 386.6, - 382.3, - 374.3, - 364.8, - 357.6, - 355.8, - 355, - 354.7, - 354.1, - 352.8, - 351.6, - 349.7, - 345.8, - 339.9, - 335, - 333.8, - 332, - 329.8, - 327.5, - 325.7, - 324.7, - 323.6, - 322, - 318.9, - 315.5, - 312.6, - 308.9, - 303.8, - 301.4, - 300.3, - 298.9, - 297.4, - 295.5, - 293.3, - 290.9, - 286.7, - 282.5, - 279.1, - 275.9, - 273.5, - 272.1, - 271, - 269.6, - 268.1, - 265.2, - 260.7, - 256.2, - 252.1, - 245.9, - 238.2, - 232.5, - 226.2, - 225.2, - 225.8, - 230.3, - 234.6, - 237.7, - 238.7, - 237.5, - 236.7, - 238.7, - 240, - 240.3, - 239.8, - 237.4, - 234, - 231.6, - 230, - 228.3, - 621.7, - 620.9, - 620, - 619.7, - 619.7, - 619.6, - 619.3, - 618.7, - 618, - 617.1, - 616.3, - 615.9, - 615.5, - 615.4, - 615.8, - 616.2, - 616.6, - 617.1, - 617.7, - 618.3, - 618.7, - 619.7, - 621.7, - 623, - 623.8, - 624.6, - 625.1, - 625.3, - 625, - 624.8, - 625.2, - 626.1, - 626.6, - 626.9, - 627.5, - 628.6, - 629.6, - 630.1, - 630.7, - 631.7, - 633, - 634.5, - 636.1, - 638.4, - 638.8, - 638.7, - 639.4, - 641.1, - 641.8, - 642.2, - 642.7, - 642.8, - 642.6, - 641.4, - 639.1, - 634.2, - 628.6, - 624.6, - 621.2, - 618.7, - 616.4, - 614.6, - 611.9, - 609.2, - 606.8, - 604.4, - 602.2, - 599.6, - 597, - 594.5, - 591.5, - 589.2, - 587.8, - 585.7, - 582.9, - 580.3, - 576.9, - 574, - 571.3, - 568.5, - 565.6, - 563.4, - 561.2, - 558.6, - 556.1, - 553.7, - 551.8, - 550.3, - 547.9, - 544.5, - 541.2, - 538.4, - 535.7, - 532.9, - 530.7, - 528.5, - 526.4, - 524.2, - 522.3, - 520.5, - 518.4, - 516.2, - 514, - 512.1, - 509.8, - 508, - 506.5, - 504.8, - 503.1, - 501.2, - 499.1, - 497, - 494.7, - 493.3, - 492.4, - 489.8, - 487.2, - 484.1, - 481.3, - 479.9, - 478.5, - 477.1, - 476.5, - 475.5, - 474, - 471.4, - 468.1, - 464.5, - 461.7, - 459.1, - 457.1, - 454.7, - 452, - 449.5, - 447.6, - 448, - 447, - 444.3, - 441.5, - 436.1, - 426.8, - 418.5, - 414.1, - 411.1, - 408.8, - 407.4, - 406.8, - 406.4, - 410.8, - 422.2, - 426.4, - 427.4, - 428.5, - 430.2, - 432.7, - 436.8, - 441.7, - 445.3, - 446.6, - 448, - 448.2, - 447.6, - 445.7, - 447.1, - 449, - 446.4, - 438.9, - 431.1, - 426.9, - 423.5, - 421.2, - 419.4, - 417, - 413.3, - 408.1, - 404.5, - 402, - 398.1, - 394.3, - 391.6, - 389.9, - 389.8, - 390.1, - 390.5, - 392.6, - 394.8, - 395.5, - 394.4, - 390.7, - 389, - 383.6, - 373.7, - 363.7, - 358.9, - 357, - 356.4, - 356.3, - 356.1, - 355.2, - 353.8, - 351.6, - 348, - 342, - 337.2, - 335.2, - 334.4, - 331.3, - 326.1, - 323.7, - 323, - 322.4, - 320.2, - 316.1, - 312.4, - 309.7, - 305.5, - 301.6, - 299.9, - 298.9, - 297.8, - 295.9, - 293.5, - 291.3, - 287.4, - 283.6, - 280.1, - 277.2, - 274.8, - 273, - 271.6, - 270.4, - 269.2, - 267.6, - 264.9, - 260.8, - 255.7, - 247.1, - 241.9, - 235.7, - 228.6, - 227.6, - 230.7, - 236, - 239.9, - 241.8, - 241.8, - 239.7, - 239.1, - 241.8, - 244.7, - 246.5, - 246.8, - 245.3, - 243.7, - 238.4, - 233.7, - 231.7, - 619.4, - 618.6, - 618.1, - 617.7, - 617.4, - 617.1, - 616.6, - 615.9, - 614.9, - 614.1, - 613.8, - 613.7, - 613.6, - 613.9, - 614.4, - 614.7, - 615, - 615.5, - 616.2, - 616.7, - 617.4, - 618.3, - 619.9, - 621.4, - 622.2, - 622.9, - 623, - 622.8, - 623, - 623.4, - 624.1, - 624.5, - 624.9, - 625.3, - 626.2, - 627.6, - 628.3, - 628.7, - 629.5, - 630.6, - 631.9, - 633.7, - 636, - 636.7, - 636.2, - 636.4, - 637.4, - 638.6, - 639.5, - 639.9, - 640.6, - 641.4, - 641.7, - 641.6, - 640.8, - 636.5, - 631, - 628.2, - 625.1, - 622.5, - 619.4, - 616.8, - 613.7, - 610.3, - 607.8, - 605.4, - 602.8, - 600.3, - 598, - 595, - 591.5, - 590.1, - 589.2, - 586.9, - 583.8, - 580.8, - 578, - 576, - 573.1, - 569.6, - 566.3, - 564.1, - 562.1, - 559.8, - 557.8, - 555.7, - 553.5, - 551.1, - 548.5, - 545.4, - 542, - 538.7, - 535.8, - 533.7, - 531.7, - 529.4, - 527.1, - 524.8, - 522.7, - 520.8, - 519, - 516.9, - 514.5, - 512.4, - 510.8, - 509.2, - 507.4, - 505.5, - 503.7, - 501.7, - 499.9, - 498.2, - 496.4, - 494.9, - 492.9, - 490.2, - 487.1, - 484.5, - 483, - 481.6, - 480.2, - 479.3, - 478.4, - 476.8, - 474.2, - 471.3, - 468, - 465.3, - 462.9, - 460.3, - 457.8, - 455.2, - 452.5, - 450.8, - 451.4, - 450.3, - 448.1, - 445.4, - 439.7, - 428.4, - 421.5, - 417.6, - 415.4, - 413.1, - 411, - 409.7, - 409.1, - 411.1, - 420.1, - 426.8, - 429.4, - 430.9, - 432.8, - 435.1, - 437.5, - 441.5, - 445.8, - 448.1, - 450.4, - 451.9, - 451.6, - 450.8, - 450.1, - 451.8, - 451.4, - 447.2, - 438.1, - 431.9, - 428.3, - 424.7, - 421.4, - 417.3, - 415.1, - 411.9, - 408.3, - 405.9, - 402.3, - 398.3, - 395.7, - 393.9, - 393.4, - 393.9, - 395.2, - 395.9, - 396.9, - 398.8, - 399.2, - 398.3, - 395.8, - 394, - 389.4, - 379.2, - 369, - 362.7, - 360.1, - 359.7, - 359.4, - 358.7, - 358.1, - 356.5, - 354.2, - 353.1, - 351.1, - 344.2, - 339.2, - 340.4, - 339.1, - 331.5, - 323.7, - 321.1, - 320.7, - 319.3, - 316.2, - 312.7, - 309.7, - 306.5, - 302.8, - 300, - 298.8, - 297.8, - 296.4, - 294.4, - 292, - 289.5, - 286.7, - 283.3, - 279.7, - 276.5, - 274.5, - 272.5, - 271.2, - 270.3, - 269.2, - 267.6, - 264.6, - 259.9, - 251.5, - 245.7, - 240.5, - 234.2, - 233.3, - 236.1, - 239.9, - 243, - 244.6, - 244.6, - 242.7, - 241.7, - 243.7, - 247.3, - 251, - 254.7, - 253.9, - 252.2, - 249.9, - 242.3, - 238.9, - 617.2, - 616.6, - 616, - 615.4, - 614.9, - 614.4, - 613.8, - 612.8, - 612, - 611.9, - 611.8, - 611.9, - 612.2, - 612.5, - 612.6, - 613, - 613.3, - 613.8, - 614.2, - 615.2, - 616.3, - 617.1, - 617.7, - 618.5, - 619.5, - 620.2, - 620.4, - 621.1, - 621.7, - 622.3, - 623, - 622.9, - 623.1, - 623.8, - 625, - 626, - 626.6, - 627.4, - 628.4, - 629.4, - 631, - 633.1, - 633.9, - 633.8, - 634.3, - 634.7, - 635, - 636, - 636.8, - 637.4, - 638.3, - 638.4, - 639.5, - 640.5, - 641.3, - 639.5, - 635.9, - 632.1, - 627.6, - 624, - 620.8, - 618, - 615.2, - 611.8, - 608.6, - 606, - 603.3, - 601.4, - 599.3, - 595.5, - 592.5, - 591.4, - 590.4, - 588.7, - 585.4, - 582.8, - 580.7, - 578.4, - 574.8, - 571, - 568.1, - 565.8, - 563.2, - 561.5, - 559.9, - 558.3, - 555.8, - 551.3, - 547.8, - 545.1, - 542.6, - 539.2, - 536.6, - 534.7, - 532.4, - 530, - 527.9, - 525.7, - 523.4, - 521.3, - 519.4, - 517.3, - 515.1, - 512.9, - 511.2, - 509.5, - 507.3, - 505.1, - 502.7, - 501.2, - 500, - 498.6, - 496.9, - 494.9, - 492.7, - 490.3, - 488.1, - 486.4, - 484.9, - 483.3, - 481.8, - 480.1, - 478.3, - 476.2, - 473.4, - 470.8, - 468.3, - 466, - 464.1, - 461.4, - 458.1, - 455.8, - 454.6, - 454.9, - 453.2, - 451.2, - 449.3, - 445.7, - 433.1, - 423.6, - 421, - 419, - 416.6, - 414.6, - 413.4, - 412.5, - 412.7, - 414, - 417.3, - 421.9, - 427.7, - 432.6, - 435.6, - 438.6, - 441.4, - 444.1, - 447.7, - 452.4, - 455.4, - 455.8, - 455.1, - 453.8, - 453.6, - 453.1, - 451.8, - 447.8, - 437.9, - 433, - 428.6, - 424.5, - 421.1, - 416.4, - 413.3, - 411, - 408.4, - 405.7, - 402.6, - 399.6, - 397.2, - 397, - 397.7, - 398.8, - 400.1, - 401.3, - 401.7, - 402.1, - 401, - 399.6, - 397.5, - 395, - 390.4, - 383, - 372.1, - 366.6, - 364.7, - 363.6, - 363.5, - 362.4, - 361.2, - 359.4, - 357.9, - 356.9, - 354.8, - 350.3, - 346.3, - 345.5, - 344.4, - 339.7, - 328.7, - 320.3, - 318.5, - 317.6, - 315.8, - 312.3, - 308.9, - 306.7, - 304.5, - 301, - 298.8, - 297.7, - 296.4, - 294.8, - 292.9, - 291, - 289.1, - 286.5, - 283.4, - 279.5, - 276.4, - 274, - 272.6, - 272, - 271.4, - 270.5, - 268.2, - 264.6, - 259.1, - 251.6, - 244.4, - 241.1, - 240.7, - 240.9, - 243.4, - 245.8, - 247.5, - 247.8, - 246.2, - 244.4, - 246, - 248.7, - 253, - 259.1, - 261.8, - 261.9, - 259.5, - 253, - 248.2, - 614.9, - 614.3, - 613.5, - 613.1, - 612.4, - 611.7, - 610.9, - 610.1, - 609.7, - 609.8, - 610.1, - 610.6, - 610.9, - 611, - 611.2, - 611.8, - 612.1, - 612.4, - 613, - 614.1, - 614.9, - 615.4, - 615.9, - 616.4, - 616.9, - 617.5, - 618.6, - 619.5, - 620.3, - 621.1, - 621.5, - 621, - 621.5, - 622.5, - 623.7, - 624.3, - 624.9, - 626, - 627.3, - 628.7, - 630, - 631.2, - 631.7, - 632.3, - 632.7, - 632.9, - 633, - 633.3, - 634, - 635.1, - 636.1, - 636.6, - 637.6, - 639.2, - 640.5, - 639.2, - 636.9, - 632.9, - 627.1, - 623.6, - 621.2, - 619.3, - 616.6, - 612.4, - 609, - 606.4, - 604.4, - 602.9, - 601.4, - 598.9, - 594.5, - 592.7, - 591.7, - 590.4, - 587.8, - 585.8, - 583.2, - 579.9, - 575.7, - 572.3, - 570.1, - 567.9, - 565.3, - 563.8, - 562.1, - 560.2, - 556.1, - 551.1, - 547.9, - 545.2, - 542.8, - 540.1, - 537.4, - 535.6, - 533.1, - 531, - 529.1, - 527.3, - 524.9, - 522.3, - 519.9, - 517.3, - 514.9, - 512.7, - 510.4, - 508.3, - 506.1, - 503.7, - 501.9, - 500.6, - 499.4, - 498.1, - 496.7, - 495.1, - 493.3, - 491.8, - 490.5, - 488.8, - 487.1, - 485.3, - 483.3, - 480.9, - 478.3, - 475.9, - 473.5, - 471.6, - 469.3, - 467, - 464.4, - 461.3, - 458.2, - 457.9, - 457.9, - 457, - 454, - 452.1, - 450.3, - 441.1, - 428.1, - 423.6, - 421.9, - 420.1, - 418.4, - 417.8, - 417.2, - 417.1, - 420.1, - 420.5, - 419.8, - 421.1, - 423.7, - 427.9, - 432.5, - 437.7, - 441.7, - 445.5, - 449.2, - 453.9, - 457.2, - 458.4, - 457.8, - 457.2, - 455.3, - 452.2, - 449.7, - 444.6, - 438.9, - 434, - 429.7, - 425.1, - 420.5, - 415.8, - 412.4, - 410.8, - 408.3, - 405.5, - 403.3, - 400.7, - 400, - 401.3, - 402.3, - 403.6, - 405.1, - 405.8, - 405.9, - 404.6, - 402.6, - 399.4, - 397.5, - 395.4, - 392.3, - 388.1, - 377.6, - 372.8, - 369.6, - 368.6, - 369.8, - 371.1, - 370.6, - 364.5, - 361.4, - 360.5, - 359.2, - 357.3, - 355.6, - 352.9, - 348.4, - 344.1, - 335, - 323.3, - 318.1, - 315.9, - 314.3, - 311.8, - 309.3, - 306.7, - 304.6, - 301.7, - 299.3, - 297.8, - 296.8, - 295.6, - 294, - 292.2, - 290.7, - 288.9, - 286.4, - 283.4, - 279.6, - 275.9, - 274.6, - 273.5, - 273.3, - 273.4, - 272.4, - 269.3, - 264.6, - 258.1, - 249.5, - 244.5, - 244.1, - 243.9, - 246.4, - 249, - 251.2, - 252, - 251, - 248.7, - 248.5, - 250.5, - 254.7, - 261.1, - 265.6, - 266.3, - 264.8, - 262, - 259.1, - 612.2, - 611.7, - 611.3, - 610.6, - 609.8, - 609, - 608.3, - 607.8, - 607.7, - 608.2, - 608.8, - 609.1, - 609.4, - 609.6, - 610, - 610.3, - 610.7, - 611.4, - 612.1, - 612.4, - 612.8, - 613.4, - 613.9, - 614.4, - 615.3, - 616.2, - 617.4, - 618.3, - 619.1, - 619.4, - 619, - 619.3, - 620.2, - 621.2, - 622, - 622.5, - 623.5, - 624.9, - 626.5, - 628.1, - 628.8, - 629.4, - 630.2, - 630.7, - 630.6, - 630.8, - 631, - 631.4, - 632.2, - 633, - 633.6, - 634.7, - 636.5, - 637.9, - 638.8, - 637.1, - 635.4, - 632.3, - 627, - 623.8, - 621.6, - 620.1, - 617, - 612.2, - 609.1, - 607.5, - 605.9, - 604.4, - 602.9, - 600.9, - 596.9, - 594, - 593, - 591.9, - 590.2, - 588.1, - 585.6, - 582.3, - 578.6, - 575.1, - 572.2, - 570, - 567.5, - 565.1, - 563, - 559.3, - 553.8, - 551.1, - 548.7, - 545.7, - 543.3, - 541.1, - 538.6, - 536.6, - 534.3, - 532.3, - 530.3, - 528.1, - 525.5, - 522.8, - 519.5, - 516.3, - 513.8, - 511.5, - 509, - 506.9, - 504.8, - 503.3, - 501.9, - 500.6, - 499.4, - 498.1, - 496.8, - 495.4, - 494.1, - 493.1, - 491.8, - 490.1, - 488.5, - 486.5, - 483.8, - 481, - 478.3, - 476.3, - 474.6, - 472.8, - 470.4, - 468, - 464.9, - 461.4, - 459.7, - 460.1, - 458.4, - 456.9, - 454.7, - 453, - 447.9, - 434.3, - 427.1, - 424.8, - 423.4, - 422.3, - 421.7, - 421.1, - 421, - 422.3, - 424.1, - 424.6, - 424.3, - 424.4, - 426.5, - 430.8, - 434.5, - 435.8, - 439.9, - 445.9, - 450.1, - 453.9, - 457.3, - 459, - 458.7, - 457.6, - 455.2, - 451.1, - 447.5, - 443.3, - 438.8, - 434.4, - 430.9, - 426.9, - 421.9, - 416.7, - 412.7, - 410.5, - 408.4, - 406.1, - 404.6, - 404.1, - 404.8, - 405.9, - 407.2, - 407.9, - 409.3, - 409.4, - 408.8, - 406.8, - 403.6, - 401.1, - 398.7, - 396.6, - 395.1, - 393.7, - 390, - 385.3, - 378.4, - 374.3, - 379.6, - 386.5, - 381, - 373, - 365.9, - 363.4, - 363.4, - 363.5, - 362.9, - 361.4, - 354.8, - 347.8, - 338.2, - 328.3, - 322.2, - 316.9, - 313.9, - 311.6, - 310, - 307.9, - 305, - 301.8, - 299.8, - 298.2, - 297.2, - 296.1, - 294.7, - 293, - 291.5, - 290, - 288.2, - 286.2, - 283.2, - 279.6, - 278.4, - 276.4, - 276.7, - 276.9, - 276.6, - 274.1, - 270.3, - 265.1, - 257.5, - 250.4, - 248.1, - 247.1, - 249.1, - 252.5, - 255.4, - 256.1, - 255.5, - 253.6, - 252.1, - 254, - 258.1, - 264, - 268.3, - 269.6, - 269.1, - 267, - 265.2, - 609.8, - 609.5, - 608.8, - 608.1, - 607.3, - 606.5, - 606, - 605.9, - 606.3, - 606.7, - 607.4, - 607.7, - 608, - 608.3, - 608.7, - 608.9, - 609.6, - 610.5, - 611.2, - 611.4, - 611.5, - 611.7, - 612.1, - 613, - 614, - 615.2, - 616.2, - 616.9, - 617.2, - 616.8, - 617.3, - 618, - 618.8, - 619.8, - 620.4, - 621.2, - 622.4, - 624, - 625.8, - 626.6, - 627.6, - 628.2, - 628.7, - 628.7, - 628.4, - 628.7, - 629.1, - 629.9, - 630.5, - 630.9, - 631.5, - 633.2, - 635.5, - 635.6, - 635.3, - 634.2, - 633, - 631.5, - 627.2, - 623.8, - 621.8, - 619.4, - 615.7, - 612.3, - 610.4, - 609.6, - 607.7, - 605.6, - 603.7, - 601.6, - 598.9, - 595.6, - 594.2, - 593.2, - 592.2, - 590.8, - 588.1, - 584.7, - 581.8, - 578.5, - 574.2, - 570.9, - 567.9, - 565.5, - 562.7, - 558.2, - 553.9, - 551.8, - 549.3, - 546.6, - 544.7, - 542.5, - 540.1, - 537.8, - 535.5, - 533.3, - 531.1, - 528.9, - 526, - 522.4, - 518.7, - 515.3, - 512.7, - 510.4, - 508.3, - 506.5, - 505, - 503.5, - 502.1, - 500.7, - 499.4, - 498, - 496.7, - 495.4, - 494.1, - 493.1, - 491.9, - 490.2, - 488.4, - 486.6, - 484, - 481.3, - 479.2, - 477.6, - 475.8, - 473.6, - 471.6, - 469, - 466, - 463.3, - 461.6, - 460.5, - 458.5, - 456.8, - 454.5, - 449.6, - 438.2, - 430.9, - 428.3, - 427, - 426.5, - 425.8, - 425.2, - 424.5, - 424.4, - 425.4, - 426.6, - 427.4, - 428.1, - 429.9, - 438.8, - 445.4, - 446.7, - 447.5, - 451, - 453.6, - 453.1, - 454.1, - 457.2, - 458.9, - 458.3, - 456, - 453.6, - 450.3, - 447.6, - 442.7, - 439.3, - 435.4, - 431.1, - 426.9, - 421.8, - 418.4, - 414.6, - 410.7, - 408.9, - 407.3, - 407, - 408.8, - 409.8, - 410.6, - 411.4, - 412.1, - 413.1, - 413.6, - 412.1, - 409.4, - 404.9, - 403.1, - 401.4, - 399.3, - 398.3, - 397, - 396.8, - 394.9, - 389.6, - 389.2, - 395.8, - 396.6, - 390.7, - 382.4, - 374.5, - 367.6, - 365.8, - 366.4, - 366.6, - 366.6, - 361.9, - 355.8, - 346.1, - 333.9, - 326.8, - 321.5, - 316.5, - 312.6, - 309.9, - 308, - 305.1, - 302.2, - 300.3, - 298.7, - 297.4, - 296.3, - 295.2, - 293.9, - 292.4, - 290.9, - 289.2, - 288, - 286.5, - 284.5, - 283.6, - 282.2, - 281.8, - 281.5, - 281, - 278.8, - 275.5, - 271.1, - 262.2, - 257.7, - 254.2, - 252.3, - 253.2, - 255.6, - 259.2, - 261, - 261.7, - 259.7, - 258, - 259.6, - 262.7, - 267.6, - 270.7, - 272.3, - 272.2, - 270.7, - 268.9, - 607.5, - 607, - 606.2, - 605.5, - 604.8, - 604.4, - 604.3, - 604.4, - 604.8, - 605.3, - 605.7, - 606.1, - 606.8, - 607.1, - 607.2, - 607.7, - 608.6, - 609.4, - 609.8, - 609.9, - 609.8, - 609.9, - 610.5, - 611.7, - 613.1, - 614.4, - 615.1, - 615.1, - 614.9, - 615.5, - 616.1, - 616.7, - 617.3, - 618.2, - 619, - 620, - 621.3, - 622.9, - 624.5, - 625.4, - 626.2, - 626.7, - 626.9, - 626.6, - 626.4, - 626.8, - 627, - 627.6, - 628.3, - 629.2, - 630.7, - 632.7, - 634, - 633.5, - 632.6, - 631.8, - 631.2, - 629.8, - 626.9, - 623.6, - 621.1, - 618.6, - 616.3, - 614.5, - 613, - 611.1, - 609.2, - 606.6, - 604.5, - 602.6, - 600.4, - 597.1, - 595.6, - 594.6, - 593.6, - 591.5, - 587.7, - 585, - 583.1, - 579.2, - 574.2, - 570, - 567, - 565.2, - 562.2, - 558.6, - 555.4, - 553.3, - 551, - 548.5, - 546.3, - 543.7, - 541.3, - 538.9, - 536.5, - 534.1, - 531.7, - 529.2, - 525.4, - 521.5, - 518, - 515.3, - 513.2, - 511, - 508.8, - 506.9, - 505.4, - 504, - 502.5, - 501, - 499.4, - 497.8, - 496.3, - 494.9, - 493.4, - 492.2, - 490.8, - 489.4, - 487.8, - 485.9, - 484.1, - 482.3, - 480.7, - 478.9, - 476.7, - 474.3, - 471.9, - 469.5, - 467.2, - 464.7, - 461.2, - 459.3, - 457.9, - 455.7, - 450.9, - 440.7, - 433.8, - 431.5, - 430.5, - 430.9, - 430.8, - 429.7, - 428.9, - 427.9, - 427.6, - 428.3, - 430.6, - 432.6, - 436.5, - 443, - 448.8, - 450.6, - 452.5, - 455.8, - 459.6, - 461.2, - 460.2, - 459.6, - 459.8, - 457.7, - 455.7, - 454, - 451.9, - 449.9, - 447.3, - 442.7, - 438.1, - 433.8, - 429.1, - 424.8, - 421.8, - 419.2, - 415.8, - 412.4, - 410.1, - 408.9, - 410.4, - 412.7, - 414.1, - 414.7, - 415.5, - 415.9, - 416.2, - 416.4, - 414.6, - 410.8, - 406.9, - 406.4, - 405.3, - 403.3, - 402.8, - 401.4, - 401.9, - 403.2, - 401.7, - 402.3, - 402.8, - 398.9, - 394, - 389.2, - 382.9, - 375.7, - 369.4, - 367.7, - 367.7, - 366.3, - 363.7, - 360.4, - 349.4, - 336.7, - 332.4, - 327.8, - 321.7, - 316.5, - 311.9, - 308.1, - 305.2, - 302.9, - 300.9, - 299.2, - 297.7, - 296.6, - 295.6, - 294.5, - 293.3, - 291.8, - 290.5, - 290.1, - 289.1, - 287.9, - 287.3, - 286.3, - 285, - 283.9, - 283.1, - 282.1, - 279.7, - 275.8, - 268.8, - 266.7, - 260.6, - 257.9, - 257.4, - 259.7, - 264.3, - 266.1, - 268.2, - 268.8, - 266.8, - 268.3, - 272, - 272.3, - 273.4, - 274.7, - 274.5, - 273.1, - 271.4, - 605, - 604.3, - 603.5, - 602.8, - 602.4, - 602.6, - 602.8, - 603, - 603.5, - 603.9, - 604.2, - 604.8, - 605.3, - 605.6, - 606, - 606.5, - 607.4, - 608, - 608, - 607.9, - 607.9, - 608.4, - 609.2, - 610.7, - 612.6, - 613.2, - 613.3, - 612.7, - 613.1, - 614.3, - 614.9, - 615.3, - 615.9, - 616.7, - 617.9, - 619.2, - 620.5, - 622.2, - 623.4, - 623.9, - 624.3, - 624.6, - 624.7, - 624.3, - 624.3, - 624.4, - 625.1, - 625.1, - 625.7, - 627.4, - 629.3, - 630.6, - 631.2, - 630.7, - 630.2, - 629.9, - 629.5, - 628.4, - 626.1, - 623.6, - 621.6, - 619.4, - 618.3, - 617.3, - 614, - 611.5, - 609.6, - 607.4, - 605.8, - 603.8, - 601.3, - 598.5, - 597, - 595.9, - 594.2, - 591, - 587.2, - 585.5, - 582.4, - 577.3, - 572.5, - 569.1, - 566.9, - 565.1, - 562.7, - 559.8, - 557.4, - 555.3, - 552.9, - 550.2, - 547.8, - 545.1, - 542.4, - 540.1, - 537.5, - 534.9, - 532.1, - 528.6, - 524.6, - 521.1, - 518.4, - 515.8, - 513.6, - 511.5, - 509.4, - 507.7, - 506.5, - 505.1, - 503.3, - 501.9, - 500.2, - 498.4, - 496.4, - 494.8, - 492.8, - 491.4, - 489.8, - 488.3, - 487, - 485.8, - 484.4, - 482.8, - 481.2, - 479.4, - 477.1, - 474.5, - 471.7, - 469.8, - 466.8, - 462.9, - 459.8, - 458.1, - 456.4, - 451.1, - 442.6, - 437.1, - 434.9, - 434, - 434.9, - 434.9, - 434.5, - 433.8, - 432.1, - 430.7, - 430.7, - 433, - 438.1, - 441.9, - 447.8, - 450.8, - 452.6, - 454.6, - 457.5, - 460.5, - 463, - 464.4, - 464.2, - 462.6, - 460.8, - 457.7, - 454.7, - 452.6, - 451, - 449.1, - 446.7, - 442.5, - 438, - 433.3, - 429, - 425.6, - 422.9, - 420.4, - 417.5, - 414.9, - 411.9, - 411, - 412.7, - 415.7, - 417.4, - 418.4, - 419.6, - 419.9, - 419.3, - 418.5, - 416.8, - 413.6, - 410.4, - 409.7, - 408.9, - 407.3, - 407, - 406.6, - 407, - 407.9, - 407.4, - 406.3, - 403.5, - 398.5, - 393.5, - 389.6, - 386.1, - 381.3, - 374.1, - 368.3, - 366.7, - 364.2, - 361.4, - 357.5, - 347.9, - 338.7, - 334.4, - 330.9, - 326.7, - 321.1, - 316, - 312, - 307.3, - 303.9, - 301.8, - 300.1, - 298.5, - 297.2, - 296.1, - 294.9, - 293.8, - 292.8, - 292.1, - 291.9, - 291.2, - 290.3, - 289.8, - 289.1, - 287.8, - 286.6, - 285.3, - 284.2, - 282.6, - 280.2, - 276.4, - 275.7, - 270, - 264.3, - 265.2, - 266.7, - 270.1, - 271.1, - 272.4, - 273.8, - 273.7, - 276.5, - 277.2, - 277.3, - 277.1, - 276.9, - 276, - 274, - 271.9, - 602.3, - 601.6, - 601, - 600.6, - 600.7, - 601.1, - 601.3, - 601.5, - 602, - 602.5, - 602.9, - 603.3, - 603.6, - 604.1, - 604.7, - 605.3, - 606.1, - 606.2, - 606.1, - 605.9, - 606.3, - 607.1, - 608.5, - 610.2, - 611.4, - 611.4, - 610.8, - 611, - 612, - 612.8, - 613.4, - 614, - 614.6, - 615.6, - 617, - 617.8, - 619.3, - 622.1, - 622.5, - 622.4, - 622.2, - 622.1, - 622.1, - 622.3, - 622.5, - 622.6, - 622.9, - 623.2, - 623.8, - 625.1, - 627.3, - 628.4, - 628.7, - 628.6, - 628.4, - 627.8, - 627.3, - 626.7, - 625.4, - 623.7, - 622.2, - 620.6, - 619.2, - 617.3, - 613.8, - 611.7, - 609.7, - 607.8, - 606.2, - 604.1, - 601.4, - 599.1, - 597.8, - 596.2, - 593.5, - 590.5, - 587.7, - 585.6, - 581.4, - 576.3, - 572.4, - 569.6, - 567.5, - 565.7, - 563, - 560.6, - 559, - 557.2, - 554.7, - 551.7, - 548.7, - 545.9, - 543.2, - 540.8, - 538.4, - 535.8, - 533, - 529.6, - 526.5, - 523.2, - 520.6, - 516.8, - 514, - 512.4, - 511.3, - 509.9, - 508.6, - 507.3, - 505.4, - 503.9, - 501.6, - 499.4, - 496.9, - 494.8, - 492.6, - 490.7, - 489.3, - 487.4, - 485.8, - 485, - 483.9, - 482.4, - 480.7, - 478.9, - 476.8, - 474.6, - 472.1, - 469, - 464.5, - 460.9, - 458.7, - 456.1, - 451.6, - 444.3, - 440.6, - 438.7, - 437.7, - 438.6, - 439.5, - 438.5, - 438.5, - 439.2, - 439.2, - 438.7, - 437.7, - 439, - 446, - 454.5, - 456.1, - 455.9, - 458.8, - 460.7, - 462.3, - 464.5, - 466.4, - 466.7, - 465.2, - 462.9, - 460.3, - 458.2, - 455.5, - 452.5, - 450.1, - 448.1, - 446.7, - 442.7, - 439.8, - 436.6, - 431.3, - 427.9, - 425.2, - 422.2, - 419.3, - 416.4, - 413.5, - 414.5, - 415.8, - 418.4, - 420.1, - 421.4, - 423.7, - 423.6, - 421.6, - 419.9, - 418.1, - 416.2, - 414.3, - 413.3, - 413.1, - 411.5, - 411.5, - 411.4, - 410.8, - 410.1, - 409.2, - 407.5, - 401.6, - 396.5, - 392.5, - 388.7, - 385.5, - 381.4, - 372.7, - 366.6, - 364.3, - 360.8, - 356.7, - 351.7, - 345.1, - 339.2, - 334.5, - 331.2, - 327.5, - 323.3, - 318.3, - 314.7, - 311.6, - 308, - 305.2, - 302.2, - 299.9, - 298.5, - 297.3, - 296.1, - 295.1, - 294, - 293.3, - 292.9, - 292.4, - 291.9, - 291.5, - 291, - 290.5, - 289.8, - 288.6, - 287.3, - 284.9, - 282.7, - 281.1, - 279.9, - 278.5, - 276.1, - 275.7, - 278.8, - 279.9, - 278.3, - 276.6, - 277.7, - 278.4, - 279.7, - 279.8, - 279.3, - 278.3, - 277.5, - 276.5, - 274.3, - 271.4, - 599.8, - 599.3, - 599.2, - 599.2, - 599.4, - 599.7, - 599.9, - 600, - 600.5, - 601, - 601.5, - 601.8, - 602.1, - 602.8, - 603.5, - 604.1, - 604.4, - 604.4, - 604.1, - 604.4, - 605.2, - 606.8, - 608.3, - 609.2, - 609.4, - 608.8, - 608.7, - 609.7, - 610.7, - 611.5, - 611.9, - 612.3, - 613.2, - 614.7, - 615.6, - 616.8, - 619.2, - 621.2, - 621.3, - 621.1, - 620.7, - 620.3, - 620.2, - 620.4, - 620.5, - 620.6, - 620.8, - 621.6, - 622.5, - 623.2, - 624.7, - 626.2, - 627, - 626.9, - 626.4, - 626, - 625.6, - 625, - 623.9, - 623.1, - 622.1, - 620.6, - 618.5, - 616.6, - 614, - 612.1, - 610.2, - 607.7, - 605.8, - 603.9, - 601.5, - 599.3, - 597.5, - 595.1, - 592.6, - 590.2, - 587.3, - 584.2, - 580.1, - 576.4, - 573.6, - 571.5, - 569.1, - 567.1, - 564.9, - 562.7, - 560.7, - 558.6, - 556.1, - 552.7, - 549.4, - 546.8, - 544.2, - 541.4, - 538.9, - 536.5, - 534.1, - 530.9, - 527.7, - 524.3, - 521.1, - 518.3, - 516.7, - 516.3, - 515.2, - 513.4, - 510.7, - 509.2, - 507.9, - 505.4, - 502.4, - 500, - 497.2, - 495, - 492.9, - 491, - 489.1, - 486.6, - 484.5, - 483.1, - 482.3, - 481.1, - 479.4, - 477.7, - 476.1, - 474.4, - 470.5, - 465.8, - 462.9, - 460.4, - 457.4, - 453.4, - 448.1, - 444.8, - 443.3, - 442.4, - 444.5, - 444.7, - 444.4, - 443.4, - 444.8, - 452.1, - 458.3, - 458.5, - 456, - 451.1, - 458.2, - 461.4, - 462.2, - 464.3, - 466.3, - 468.3, - 469.5, - 470.4, - 470, - 468.2, - 465.4, - 463, - 460.3, - 458.5, - 456.2, - 453.1, - 450.2, - 447.3, - 445.7, - 443, - 440.8, - 439.2, - 434.1, - 430.3, - 428.3, - 424.6, - 420.8, - 417.6, - 417.8, - 419.7, - 419.7, - 421.1, - 422.9, - 425.4, - 427.3, - 426.5, - 424.3, - 421.7, - 420.2, - 419.3, - 418.2, - 417.4, - 416, - 414.8, - 414.2, - 413.1, - 410.8, - 408.9, - 407.9, - 405.2, - 398.7, - 394.6, - 391.1, - 386.2, - 383.1, - 378.9, - 371.2, - 365.2, - 362.7, - 359.2, - 355.1, - 350.9, - 345.6, - 339.6, - 334.8, - 331.9, - 328.9, - 324.9, - 321.3, - 318, - 315.3, - 312.3, - 309.7, - 307.3, - 303.8, - 300.6, - 299.3, - 298, - 297.1, - 296.5, - 295.9, - 294.5, - 293.7, - 293.1, - 292.8, - 292.4, - 291.8, - 291.1, - 290.3, - 288.8, - 286.6, - 284.4, - 283, - 281.9, - 281, - 280.5, - 280.3, - 281.8, - 283, - 284.2, - 283.6, - 283.1, - 283.7, - 283.3, - 282.3, - 281.5, - 280.5, - 278.2, - 276.7, - 275.2, - 272, - 597.7, - 597.6, - 597.8, - 597.8, - 597.9, - 598.3, - 598.3, - 598.5, - 599, - 599.3, - 599.7, - 600.2, - 600.8, - 601.4, - 602.1, - 602.5, - 602.6, - 602.5, - 602.5, - 603, - 604.1, - 605.8, - 606.9, - 607.3, - 606.6, - 606.5, - 607.5, - 608.5, - 609.4, - 610, - 610.2, - 610.9, - 611.9, - 613.3, - 615.1, - 617, - 619.2, - 619.9, - 620, - 619.7, - 619.5, - 619, - 618.8, - 618.9, - 618.7, - 618.7, - 619, - 620, - 620.9, - 621.4, - 622.9, - 623.6, - 624.6, - 625.1, - 624.7, - 624, - 623.5, - 622.9, - 622.5, - 622.2, - 621.6, - 620, - 618.1, - 616.2, - 614.3, - 612.1, - 609.9, - 607.9, - 606, - 603.8, - 601.6, - 599.3, - 597.1, - 595, - 592.4, - 589.9, - 587.4, - 584.4, - 581.3, - 579.1, - 577.6, - 574.9, - 571.2, - 568.3, - 566.3, - 564.3, - 562.2, - 559.5, - 556.2, - 552.8, - 550.4, - 548.5, - 546.6, - 543.2, - 539.6, - 537, - 534.3, - 531.4, - 529.3, - 524.9, - 523.1, - 523.4, - 523.3, - 520.1, - 517.7, - 514.8, - 513.1, - 512.5, - 509.5, - 505.5, - 503.3, - 501.3, - 498.9, - 497, - 495.6, - 493.8, - 491.9, - 489.3, - 485.8, - 481.9, - 480.5, - 479.4, - 477.6, - 476.1, - 474.7, - 472.1, - 467.9, - 464.8, - 462.5, - 460.2, - 457.4, - 452.7, - 449.4, - 448, - 446.2, - 449.4, - 450.4, - 450.3, - 451, - 452.8, - 454.5, - 460.4, - 465, - 465.8, - 466.3, - 465, - 465, - 465.2, - 467.9, - 470, - 472.1, - 475.4, - 475.6, - 474.6, - 472.7, - 469.9, - 466, - 463.3, - 461, - 458.7, - 456.7, - 453.4, - 450.9, - 448, - 444.9, - 442.7, - 441.1, - 439.3, - 435.3, - 431.6, - 430.1, - 426.7, - 423, - 422.6, - 423.5, - 423.4, - 422.5, - 424.4, - 427.8, - 429.3, - 429.3, - 428.4, - 426.4, - 424.4, - 423.6, - 423, - 421.7, - 420.4, - 418.5, - 416.2, - 414.8, - 413.2, - 410.1, - 406.8, - 404.5, - 400.5, - 396, - 392.7, - 389.1, - 384.7, - 381.6, - 375.5, - 368.7, - 364.4, - 361.9, - 359.3, - 355.7, - 351.8, - 346.6, - 339.6, - 335, - 333, - 332.1, - 329.5, - 326.9, - 323.4, - 319.2, - 316.4, - 313.6, - 311, - 309.1, - 306.2, - 303.5, - 301.9, - 301.1, - 300.6, - 299.7, - 297.2, - 295.6, - 295.6, - 295.6, - 295.4, - 294.5, - 292.8, - 291.1, - 289.6, - 287.8, - 286.3, - 285.1, - 283.9, - 282.8, - 282, - 282.6, - 284.7, - 287, - 289.5, - 289.4, - 288.1, - 287.4, - 286.8, - 285.5, - 284.3, - 282.7, - 279.6, - 277.2, - 275.7, - 273.8, - 596.3, - 596.2, - 596.2, - 596.3, - 596.5, - 596.5, - 596.5, - 597, - 597.3, - 597.5, - 598.1, - 598.9, - 599.5, - 599.9, - 600.3, - 600.5, - 600.4, - 600.4, - 600.7, - 601.4, - 602.5, - 603.5, - 603.9, - 603.9, - 604.2, - 605.4, - 606.4, - 607.2, - 607.8, - 608.1, - 608.8, - 610.1, - 610.9, - 613.4, - 617.1, - 618.2, - 618.6, - 618.9, - 618.7, - 618.4, - 618.1, - 617.9, - 617.9, - 617.7, - 617.4, - 617.1, - 617.3, - 618.1, - 618.8, - 619.8, - 620.9, - 621.4, - 622.8, - 623.3, - 622.8, - 622.3, - 621.9, - 621.6, - 621.4, - 621.2, - 620.7, - 619.9, - 618.3, - 616.2, - 614.2, - 611.9, - 609.9, - 608.4, - 606.5, - 604, - 601.3, - 599.1, - 597.1, - 595.1, - 592.9, - 590.5, - 588.6, - 587.4, - 585.3, - 583, - 580.6, - 576.3, - 572.2, - 569, - 567.2, - 565.1, - 562.7, - 559.5, - 556.2, - 553.6, - 552, - 550.7, - 548.7, - 544.7, - 540.2, - 537, - 534.2, - 532.3, - 530.6, - 527.4, - 528.6, - 528.6, - 527.3, - 523, - 518.9, - 517.8, - 515.9, - 513.1, - 509, - 506.5, - 504.6, - 502.8, - 501.5, - 500.6, - 499.8, - 499.3, - 498.4, - 493.9, - 490.6, - 485, - 480.7, - 478.7, - 477.1, - 475.4, - 472.6, - 470, - 467.5, - 465.1, - 462.5, - 459.8, - 457.3, - 454.8, - 452.7, - 450.3, - 452.9, - 455.3, - 455.1, - 456.2, - 461.1, - 464.9, - 467.2, - 468.6, - 470.1, - 472.5, - 474.6, - 474.2, - 471.4, - 473.4, - 475.7, - 476.8, - 479.2, - 481.1, - 478.8, - 476.5, - 474.3, - 471.4, - 467.1, - 463.9, - 461.6, - 459.2, - 456.7, - 454.1, - 451.9, - 449.4, - 445.8, - 442.8, - 440.7, - 438.8, - 435.7, - 432.7, - 431.3, - 428.7, - 427.1, - 426.5, - 425.8, - 425.1, - 424.8, - 428.4, - 431.7, - 432, - 431.1, - 429.8, - 428.3, - 426.5, - 425.3, - 424.4, - 422.8, - 420.9, - 418.4, - 415.4, - 413.8, - 412.5, - 409.3, - 405.4, - 400.7, - 396.6, - 393.8, - 390.8, - 387, - 383.3, - 379.6, - 371.8, - 366.6, - 363.2, - 360.9, - 358.2, - 354.8, - 351.1, - 346.4, - 341.8, - 338.3, - 335.5, - 334.7, - 333.1, - 331.2, - 328.8, - 324.8, - 320.6, - 317.9, - 315.2, - 313.1, - 311.3, - 308.8, - 306.5, - 306.8, - 306.1, - 304.9, - 302.8, - 300.6, - 299.3, - 298.6, - 297.4, - 296.6, - 295.1, - 293.1, - 291, - 289.2, - 288.7, - 287.9, - 286.9, - 285.7, - 285.8, - 287.6, - 289.5, - 290.5, - 291.2, - 290.7, - 289.8, - 289.2, - 288.5, - 287.2, - 285.5, - 284, - 282, - 278.7, - 276, - 274.9, - 594.9, - 594.8, - 594.8, - 595, - 595, - 594.8, - 595.1, - 595.5, - 595.8, - 596.1, - 596.8, - 597.3, - 597.9, - 598.2, - 598.2, - 598, - 597.9, - 598.3, - 599, - 599.8, - 600.3, - 600.8, - 601.1, - 601.8, - 603, - 604.2, - 605, - 605.4, - 605.8, - 606.6, - 607.9, - 609, - 611, - 615.7, - 618.3, - 618, - 617.7, - 617.7, - 617.1, - 616.8, - 616.7, - 616.5, - 616.3, - 616.1, - 616.2, - 615.9, - 615.4, - 615.8, - 616.4, - 617.8, - 619.2, - 619.7, - 620.6, - 621.3, - 620.7, - 620.3, - 620.5, - 620.5, - 620.1, - 619.8, - 619.6, - 619, - 617.9, - 616.1, - 613.9, - 611.9, - 610.1, - 608.5, - 606.9, - 604.6, - 601.5, - 599.4, - 597.5, - 595.8, - 593.9, - 592.6, - 592.9, - 592.6, - 589.6, - 585.1, - 580.8, - 576.5, - 572.5, - 569.6, - 567.7, - 565.3, - 562.8, - 560, - 557.5, - 555.1, - 553.4, - 552.5, - 549.5, - 544.8, - 540.5, - 537.6, - 535.1, - 533.2, - 531.9, - 531.3, - 531.4, - 529.9, - 526.7, - 523.1, - 520.6, - 519.9, - 517.1, - 512.2, - 509.9, - 510.1, - 510.7, - 508.4, - 506.9, - 507.8, - 507.2, - 505.8, - 503.2, - 498.9, - 494.2, - 490.6, - 486, - 480.9, - 477.7, - 476, - 473.2, - 469.6, - 467.3, - 464.7, - 461.8, - 459.4, - 458.4, - 456.4, - 453.3, - 454.7, - 457.3, - 458.1, - 459.3, - 463.5, - 471.8, - 473.3, - 473.3, - 474, - 474.9, - 477.9, - 481.7, - 484.4, - 482.3, - 483.3, - 483.9, - 485.2, - 485.8, - 484.8, - 482.9, - 479.9, - 477.1, - 474.4, - 471.2, - 467.3, - 463, - 460.2, - 457.2, - 455.2, - 453.1, - 450.6, - 447.2, - 445.1, - 442.1, - 438.8, - 435.3, - 433.4, - 432.4, - 431.1, - 430.4, - 429.4, - 428.3, - 427.5, - 427, - 432.2, - 434.6, - 433.7, - 432, - 430.2, - 428.7, - 426.5, - 424.7, - 423.4, - 421.7, - 419.5, - 416.4, - 413.6, - 412.3, - 410.5, - 407.2, - 403.6, - 398.5, - 395, - 392.2, - 388.9, - 385.2, - 380.9, - 375.6, - 370.3, - 366.2, - 362.6, - 359.4, - 356.4, - 353.5, - 350.8, - 347.8, - 345.6, - 342.9, - 339.4, - 336.7, - 334.8, - 333.2, - 331.6, - 329.3, - 325.6, - 322.2, - 319.8, - 317.5, - 314.7, - 312.6, - 310.7, - 310.3, - 309.3, - 307.6, - 306.1, - 304.9, - 303.9, - 302.3, - 299.6, - 297.4, - 296.1, - 294.8, - 293.4, - 292.2, - 291.2, - 289.9, - 288.9, - 288.6, - 288.9, - 289.8, - 291.1, - 292.2, - 292.1, - 291.2, - 290.3, - 289.7, - 288.7, - 287.5, - 285.7, - 283.9, - 282.4, - 280.5, - 277.4, - 275.7, - 593.3, - 593.3, - 593.4, - 593.5, - 593.2, - 593.3, - 593.7, - 594.1, - 594.5, - 595, - 595.5, - 595.5, - 595.6, - 595.7, - 595.6, - 595.7, - 595.9, - 596.8, - 597.5, - 598, - 598.4, - 598.8, - 599.4, - 600.5, - 601.6, - 602.5, - 603.1, - 603.6, - 604.5, - 605.6, - 606.8, - 609.2, - 613.7, - 616.7, - 616.8, - 616.5, - 616.8, - 616.4, - 615.5, - 615.1, - 615.1, - 615, - 614.8, - 614.5, - 614.4, - 613.5, - 613.6, - 614, - 613.8, - 615.3, - 617, - 618.2, - 618.7, - 619.3, - 618.8, - 619.4, - 619.6, - 619.1, - 618, - 618.1, - 618.1, - 618, - 617.2, - 616, - 614.4, - 612.2, - 609.9, - 608.2, - 606.2, - 604.1, - 601.7, - 600, - 598.4, - 597.1, - 596.5, - 596, - 595.6, - 593.8, - 591.3, - 586.4, - 581, - 576.7, - 573.1, - 569.9, - 567.5, - 565.1, - 562.8, - 560.5, - 558.6, - 556.4, - 555.3, - 552.3, - 548.6, - 544.1, - 541, - 538.6, - 536, - 534.3, - 533.3, - 532.9, - 532.1, - 530.2, - 527.2, - 524.9, - 523.2, - 520.9, - 517.1, - 514.2, - 513.5, - 515.6, - 517.9, - 515.6, - 513.4, - 514.1, - 513.4, - 511.1, - 508, - 505.2, - 501.3, - 497, - 492.5, - 488.2, - 484.7, - 481.8, - 478, - 473.7, - 468.9, - 464.8, - 462.6, - 461.3, - 460, - 456.8, - 457.2, - 459.1, - 461.9, - 466.2, - 469.6, - 473.7, - 478.1, - 478.7, - 479.2, - 480, - 480.8, - 486.8, - 493, - 493.8, - 493.9, - 491.5, - 489.6, - 491.2, - 492.4, - 490.2, - 488.1, - 485.8, - 482.1, - 478.4, - 475.6, - 472, - 467.1, - 462, - 458.4, - 455.9, - 453.7, - 451.6, - 448.2, - 446.1, - 444.2, - 441.9, - 436.8, - 434.2, - 433.3, - 432.5, - 431.9, - 431.5, - 431.1, - 431.2, - 431.7, - 434.5, - 435.3, - 433.7, - 431.1, - 429.3, - 426.8, - 424.4, - 422.4, - 421.6, - 420.5, - 417.9, - 414.9, - 412.7, - 410.7, - 407.8, - 404.5, - 400.5, - 396.7, - 393.5, - 390.6, - 387.1, - 383, - 378.3, - 374.3, - 370.1, - 366.3, - 362.8, - 359.3, - 356.6, - 354.5, - 352.4, - 350.1, - 347.4, - 345, - 342.3, - 339.2, - 336.3, - 334, - 332.4, - 330.8, - 328.5, - 325.5, - 323.5, - 322.1, - 319.2, - 316.2, - 315.2, - 314, - 312.5, - 311.1, - 309.4, - 308.3, - 307.3, - 305, - 301.3, - 298.4, - 297, - 295.8, - 294.9, - 294.4, - 293.9, - 292.8, - 291.1, - 290.6, - 290.8, - 292.5, - 293.6, - 293.7, - 292.7, - 291.6, - 290.5, - 289.4, - 288, - 286.7, - 285.4, - 283.6, - 282.1, - 280.5, - 278.2, - 276.7, - 591.8, - 591.8, - 591.9, - 591.8, - 591.8, - 591.9, - 592.2, - 592.4, - 592.7, - 593.2, - 593.6, - 593.1, - 592.7, - 593, - 593.7, - 594, - 594.5, - 595.4, - 596, - 596.5, - 596.8, - 597.2, - 598.1, - 599, - 600, - 600.8, - 601.5, - 602.4, - 603.5, - 604.7, - 607.2, - 611.7, - 615.5, - 615.6, - 615.1, - 615.3, - 615.6, - 615.1, - 613.9, - 613.4, - 613.7, - 613.6, - 613.4, - 612.9, - 611.9, - 611.4, - 611.5, - 611.6, - 611.5, - 612.6, - 614.2, - 615.9, - 616.7, - 617.6, - 617.5, - 618.1, - 617.7, - 616.8, - 616, - 616.2, - 616.7, - 616.8, - 616.3, - 615.3, - 613.6, - 611.7, - 609.7, - 607.9, - 605.8, - 604, - 602.3, - 600.7, - 599.4, - 598.7, - 598, - 597.1, - 595.9, - 593.8, - 591.1, - 586.8, - 580.9, - 576.5, - 573, - 569.3, - 566.5, - 564.8, - 562.6, - 560.7, - 559.4, - 557.5, - 554.8, - 549.3, - 545.5, - 543, - 541, - 538.8, - 537, - 535.5, - 535, - 534.7, - 532.8, - 530.6, - 528.3, - 527.8, - 525.2, - 520.8, - 518.8, - 522, - 521.5, - 520.6, - 522.6, - 521.3, - 519, - 518.8, - 517.5, - 515.1, - 512, - 509.4, - 507, - 504.4, - 500.1, - 495.4, - 491, - 487.7, - 483.9, - 479.2, - 475.6, - 472.8, - 469.6, - 465.3, - 461.4, - 460.8, - 464.3, - 467.3, - 474.1, - 479.8, - 481.2, - 482.2, - 483.6, - 484.3, - 485, - 487.5, - 490.7, - 495.2, - 497.9, - 498.5, - 498.9, - 497.3, - 497.1, - 496.9, - 499.4, - 498.8, - 494.6, - 490.6, - 487.8, - 483.1, - 479.9, - 475.9, - 469.9, - 463.9, - 459.3, - 456.7, - 454.3, - 452.4, - 449.9, - 447.2, - 444.7, - 442.7, - 439.6, - 436.4, - 434.5, - 433.6, - 433.4, - 434.9, - 435.2, - 436.1, - 436.5, - 435.9, - 434.8, - 433, - 430.1, - 427.9, - 425.1, - 422.3, - 420.8, - 419.9, - 418.5, - 416.2, - 414, - 411.8, - 409, - 405.8, - 402, - 398.3, - 395, - 391.6, - 388.5, - 384.7, - 380.5, - 377.2, - 373.7, - 370.2, - 366.8, - 363.6, - 360.8, - 358.5, - 356.4, - 353.8, - 351.2, - 348.4, - 345.9, - 343.4, - 340.5, - 337.3, - 334.5, - 332.3, - 330.8, - 329.7, - 330.7, - 333.4, - 336.8, - 335.1, - 328.4, - 322.7, - 319.7, - 316.9, - 315.6, - 313.6, - 311.8, - 310.4, - 309.1, - 306.7, - 302.1, - 300.3, - 299.1, - 297, - 295.9, - 295.9, - 296.1, - 296.7, - 294.8, - 295.1, - 298.6, - 298.3, - 295.9, - 293.4, - 291.9, - 290.5, - 289, - 287.5, - 286.2, - 284.8, - 283.4, - 281.8, - 280.1, - 278.7, - 277.5, - 590.2, - 590.2, - 590.2, - 590.5, - 590.8, - 590.9, - 590.8, - 590.8, - 590.9, - 590.7, - 590.7, - 590.3, - 590.5, - 591.3, - 591.9, - 592.4, - 593.2, - 593.9, - 594.5, - 594.9, - 595.3, - 595.9, - 596.7, - 597.6, - 598.5, - 599.3, - 600.2, - 601.4, - 602.9, - 606, - 610.7, - 613.9, - 614.3, - 613.8, - 613.9, - 613.9, - 614.3, - 613.5, - 612.1, - 611.7, - 611.8, - 611.8, - 611.6, - 610.4, - 609.9, - 609.8, - 609.9, - 609.6, - 609.1, - 610, - 611.7, - 613.4, - 614.9, - 615.9, - 616, - 616.1, - 615.2, - 614.6, - 614.4, - 614.3, - 614.8, - 615.1, - 615.2, - 614.3, - 612.5, - 611.2, - 609.5, - 607.7, - 605.7, - 604.3, - 602.8, - 601.4, - 599.8, - 598.7, - 597.5, - 596.5, - 595, - 592.6, - 589.5, - 585.6, - 580.3, - 575.6, - 571.6, - 568.1, - 566.2, - 564.6, - 562.4, - 560.3, - 559, - 556.2, - 551.6, - 547.5, - 545, - 542.9, - 540.9, - 539.5, - 538.1, - 537.2, - 537.8, - 537.6, - 535, - 531.9, - 529.7, - 528.5, - 526.3, - 523.5, - 527.1, - 532.1, - 530.8, - 527.8, - 528.2, - 527, - 524.2, - 523.5, - 522.3, - 518.9, - 516.8, - 515.4, - 512.5, - 509.2, - 505.8, - 503.1, - 500.3, - 492.6, - 488.3, - 484.6, - 480.5, - 478.3, - 477, - 476, - 476.2, - 476, - 477.9, - 480.2, - 485.2, - 487.4, - 488.8, - 490.3, - 493.6, - 496.5, - 497.7, - 502.1, - 507.4, - 507.1, - 507.6, - 507.9, - 504.4, - 506, - 506.7, - 504.4, - 505.1, - 508.5, - 506.7, - 499.7, - 494.3, - 489, - 484.5, - 480.8, - 473.9, - 467.9, - 462.2, - 457.9, - 455.5, - 453.4, - 451.2, - 448.6, - 445.7, - 443.7, - 442.1, - 441.2, - 439.6, - 437.3, - 436.3, - 437.7, - 438, - 437.7, - 437, - 435.6, - 433.6, - 431.6, - 428.9, - 426.7, - 423.9, - 421, - 419.2, - 417.9, - 416.4, - 414.3, - 412.4, - 410, - 406.8, - 403.5, - 400.1, - 396.5, - 393.1, - 390.2, - 386.8, - 383.1, - 379.9, - 376.6, - 373.6, - 370.6, - 367.6, - 365.1, - 362.8, - 360.4, - 357.6, - 354.2, - 351.2, - 348.6, - 346.3, - 343.7, - 340.8, - 337.5, - 335.2, - 334.6, - 334.7, - 335.9, - 337.5, - 339.9, - 341.3, - 342.8, - 340.6, - 333.4, - 327.1, - 322.1, - 319.1, - 318, - 317.6, - 315.1, - 312.8, - 311.8, - 309.7, - 309.1, - 308.6, - 305.6, - 301.9, - 302.3, - 305.9, - 306.8, - 302.1, - 301.4, - 303.2, - 302, - 300.3, - 296.6, - 292.7, - 290.7, - 289, - 287.6, - 286.2, - 284.8, - 283.5, - 281.9, - 280.3, - 279, - 277.7, - 588.6, - 588.5, - 588.9, - 589.4, - 589.6, - 589.5, - 589.2, - 589.1, - 588.9, - 588.6, - 588.6, - 588.7, - 589.2, - 589.7, - 590.3, - 591, - 591.8, - 592.3, - 593, - 593.6, - 594, - 594.5, - 595.3, - 596.4, - 597.3, - 598.3, - 599.3, - 600.8, - 604.7, - 610.7, - 613.4, - 613.5, - 612.8, - 612.6, - 612.4, - 612.2, - 612.1, - 611.4, - 610.2, - 609.8, - 609.7, - 609.8, - 608.9, - 608.6, - 608.5, - 608.3, - 608.2, - 607.7, - 607.3, - 608.3, - 610.1, - 612.3, - 614.2, - 614.3, - 614.1, - 613.7, - 613, - 612.9, - 613, - 612.9, - 613, - 613.3, - 613.1, - 612.7, - 611.8, - 610.7, - 609.5, - 607.6, - 605.9, - 604.5, - 603.1, - 601.6, - 599.7, - 598.2, - 596.4, - 594.9, - 593.4, - 591.1, - 588.5, - 585.5, - 579.4, - 573.8, - 569.5, - 566.8, - 566.1, - 564.1, - 561, - 559.2, - 556.4, - 552.8, - 549.6, - 547.1, - 545.1, - 543.4, - 542, - 540.9, - 540, - 539.9, - 539.9, - 538.8, - 535.6, - 532.8, - 531.4, - 530.1, - 529.5, - 531.2, - 535, - 537, - 536.5, - 535.4, - 533.3, - 531.6, - 531.4, - 530.8, - 529.4, - 527.4, - 527.5, - 524.8, - 519.6, - 514.8, - 510.3, - 507.6, - 505.9, - 497.9, - 492.4, - 488.6, - 484.7, - 483.9, - 487.8, - 491, - 491.5, - 490.8, - 490, - 493, - 496, - 497.1, - 499.2, - 503.6, - 510.8, - 515.3, - 517.8, - 517.5, - 519.7, - 520.2, - 522.2, - 521.7, - 518.7, - 517.7, - 516.1, - 513.1, - 509.7, - 510.6, - 511.6, - 508.7, - 503.2, - 496.4, - 491.8, - 486.6, - 481.9, - 474.6, - 467.8, - 462.5, - 458, - 455.4, - 453.3, - 450.9, - 448.3, - 446.6, - 445.5, - 444.9, - 443.9, - 442.3, - 440.6, - 439.7, - 438.6, - 437, - 436, - 433.9, - 432.1, - 430.1, - 427.2, - 425.1, - 421.9, - 419.5, - 417.8, - 415.9, - 413.9, - 412.1, - 409.8, - 407.1, - 404.6, - 402.2, - 399.2, - 394.5, - 391.5, - 389.4, - 385.6, - 382.4, - 379.5, - 376.4, - 373.8, - 371.3, - 368.8, - 367, - 365, - 362.9, - 360.1, - 356.4, - 352.5, - 348.8, - 346.1, - 344.6, - 344.1, - 343, - 341, - 340, - 340.1, - 340.6, - 341.5, - 342.9, - 344.4, - 345.9, - 345.4, - 342, - 336.2, - 330.1, - 325.6, - 325.6, - 328.9, - 324.9, - 320.1, - 318.2, - 316.4, - 316.5, - 319.9, - 321, - 325.2, - 324.2, - 322.2, - 321.4, - 312.4, - 305.7, - 303.5, - 302.5, - 301.8, - 299.7, - 295.9, - 292.1, - 289.4, - 287.9, - 286.4, - 285, - 283.6, - 282.1, - 280.4, - 279, - 277.7, - 587.1, - 587.3, - 587.6, - 587.7, - 587.6, - 587.4, - 587.2, - 587.1, - 587, - 587.1, - 587.5, - 587.6, - 587.9, - 588.6, - 589.2, - 589.8, - 590.4, - 591, - 591.8, - 592.1, - 592.5, - 593.2, - 594.2, - 595.1, - 596.1, - 597.3, - 598.7, - 602.6, - 609.1, - 612.8, - 612.9, - 612.4, - 611.9, - 611.2, - 610.7, - 610.3, - 609.9, - 609.3, - 608.4, - 607.8, - 607.5, - 606.3, - 606.1, - 606.6, - 606.6, - 606.4, - 606.3, - 605.9, - 605.7, - 606.9, - 609.2, - 611.5, - 612.6, - 612.7, - 612.3, - 611.5, - 611, - 611, - 611.2, - 611.3, - 611.4, - 611.9, - 611.7, - 611.2, - 610.7, - 609.9, - 608.8, - 607.2, - 605.9, - 604.4, - 602.5, - 601, - 599.5, - 597.9, - 595.8, - 594, - 592.5, - 590.5, - 588.2, - 585.1, - 577.6, - 571.4, - 567, - 565.3, - 564.3, - 562, - 560, - 558, - 554.5, - 551.5, - 548.6, - 546.8, - 545.8, - 544.9, - 544.2, - 543.3, - 542.5, - 542.2, - 541.8, - 540.9, - 539, - 536.9, - 534.3, - 535.1, - 536.4, - 538.8, - 542, - 543.4, - 542.3, - 540.2, - 538.4, - 537.1, - 538.5, - 537.9, - 535.6, - 533.9, - 531.4, - 530, - 528.3, - 523.8, - 517.5, - 512, - 507.8, - 499.8, - 494.9, - 492.6, - 493.5, - 498.1, - 501.8, - 503.1, - 502.6, - 502.4, - 501.9, - 507, - 513.5, - 506.6, - 509.3, - 516.4, - 519.6, - 522.5, - 525.3, - 528.1, - 530.1, - 529.9, - 530.9, - 530.2, - 527.6, - 525.7, - 524.2, - 520.3, - 516.6, - 514.3, - 514.6, - 515.7, - 514.3, - 507.1, - 499.8, - 493.2, - 488.2, - 483.8, - 476.1, - 469.4, - 466.3, - 462, - 457.5, - 454.7, - 452.2, - 450.9, - 448.7, - 446.7, - 445, - 443.5, - 442.3, - 440.5, - 438, - 435.5, - 433.8, - 431.8, - 430.6, - 428.5, - 426.1, - 424, - 421.1, - 418.3, - 416, - 413.9, - 411.7, - 409.7, - 407.4, - 405.3, - 403.1, - 400.8, - 397, - 393.2, - 390.6, - 388.5, - 385.3, - 382.3, - 379.7, - 376.8, - 374.6, - 372, - 369.9, - 368.8, - 367.3, - 364.9, - 362.2, - 358.6, - 353.2, - 349.1, - 347.1, - 347.2, - 347.5, - 347.3, - 346.2, - 344.5, - 343.6, - 343.8, - 344.2, - 345.3, - 346.9, - 348.4, - 348.8, - 348.2, - 345.3, - 341.1, - 340.6, - 345, - 347.3, - 342.7, - 335.1, - 330.9, - 329.8, - 331.9, - 337.3, - 342.8, - 347.7, - 349, - 344.9, - 336.5, - 325.9, - 315.2, - 308.1, - 305.1, - 303.3, - 300.9, - 297.8, - 294, - 290.1, - 288.2, - 286.7, - 285.1, - 283.7, - 282.3, - 280.6, - 279.1, - 277.8, - 585.6, - 586, - 585.9, - 585.8, - 585.6, - 585.5, - 585.6, - 585.6, - 585.6, - 585.7, - 586.2, - 586.4, - 586.8, - 587.5, - 588.1, - 588.7, - 589.1, - 589.6, - 590.1, - 590.5, - 591.2, - 592, - 593, - 593.9, - 595.1, - 596.5, - 599.6, - 606.1, - 610.9, - 611.9, - 611.6, - 611.2, - 610.6, - 609.8, - 609.1, - 608.3, - 607.6, - 607.2, - 606.6, - 605.6, - 604.4, - 604.1, - 604.1, - 604.4, - 604.3, - 604, - 603.7, - 603.3, - 603.9, - 606.2, - 608.9, - 610.6, - 611.2, - 611.1, - 610.6, - 609.9, - 609.1, - 609.1, - 609.5, - 609.7, - 609.7, - 610, - 610.2, - 609.8, - 609.3, - 608.9, - 607.9, - 606.7, - 605.5, - 603.9, - 601.9, - 600.1, - 598.7, - 597.1, - 595.3, - 593.4, - 591.8, - 589.7, - 586.7, - 581.2, - 573.8, - 567.6, - 564.3, - 563, - 561.2, - 559.9, - 558.7, - 556.3, - 553.5, - 551, - 549.5, - 548.7, - 548, - 548.3, - 547.7, - 545.9, - 545, - 544.5, - 544.4, - 545.1, - 545.3, - 541.8, - 539.5, - 540.5, - 542.5, - 544.1, - 545, - 546, - 545.7, - 543.4, - 541.2, - 542.1, - 547.7, - 547.9, - 544.7, - 542.3, - 539.7, - 538.7, - 542.3, - 539, - 531.3, - 521.8, - 509.8, - 501.6, - 500.6, - 503.9, - 510.1, - 509.9, - 512.2, - 514.2, - 513.7, - 512.6, - 514.1, - 518.9, - 524.8, - 525.5, - 525, - 525, - 527.7, - 531, - 532.2, - 534.2, - 537.5, - 539.2, - 539.1, - 538.7, - 538.1, - 536.1, - 534.9, - 530.4, - 524, - 519.6, - 518.3, - 518.6, - 518.8, - 516.5, - 509, - 501.1, - 493.9, - 491.6, - 486, - 478.8, - 473.6, - 467.9, - 462.7, - 459.1, - 455.2, - 452.4, - 449.4, - 447.2, - 445.4, - 443.6, - 442.2, - 439.8, - 437.1, - 434.1, - 431.5, - 429.8, - 428.6, - 426.9, - 425.1, - 422.8, - 420.3, - 417.6, - 414.9, - 412.8, - 410.3, - 408.5, - 406.3, - 403.9, - 401.4, - 398.8, - 395.8, - 392.9, - 390.2, - 387.9, - 385.3, - 382.5, - 380.1, - 377.6, - 375.3, - 373, - 371.5, - 370.6, - 369.3, - 366.8, - 363.7, - 359.6, - 354.1, - 351.4, - 350.2, - 350, - 349.5, - 349.6, - 349.1, - 348.4, - 347.8, - 347.2, - 347.1, - 347.9, - 348.7, - 350.1, - 351.2, - 352, - 352.6, - 354.4, - 357, - 357.4, - 357.6, - 355, - 349.9, - 345.3, - 342.1, - 347.5, - 358.5, - 363.4, - 360, - 359.6, - 358.6, - 351, - 338.5, - 327.9, - 317.5, - 310.4, - 308, - 305.4, - 300.7, - 297, - 293, - 289.7, - 287.3, - 285.4, - 283.9, - 282.5, - 281.1, - 279.7, - 278.4, - 584.5, - 584.7, - 584.4, - 584.1, - 584.1, - 584.2, - 584.2, - 584.3, - 584.2, - 584.4, - 584.8, - 585.1, - 585.4, - 586, - 586.6, - 587.1, - 587.4, - 587.9, - 588.4, - 589.3, - 590.2, - 590.8, - 591.7, - 592.8, - 594.4, - 596.2, - 600.9, - 608.2, - 610.3, - 610.4, - 610.3, - 609.7, - 608.8, - 607.8, - 607.1, - 606.3, - 605.2, - 604.7, - 603.7, - 602.3, - 602.3, - 602.3, - 602.2, - 601.9, - 601.5, - 600.7, - 600.6, - 600.9, - 603.3, - 605.7, - 607.5, - 609.1, - 609.7, - 609.1, - 608.2, - 607.8, - 607.4, - 607.6, - 607.9, - 608.3, - 608.3, - 608.2, - 608.3, - 608.3, - 607.7, - 607.5, - 606.8, - 605.8, - 604.6, - 603.2, - 601.6, - 599.1, - 597.1, - 595.3, - 593.1, - 591.3, - 589, - 585.5, - 580.3, - 574.2, - 568.2, - 564.2, - 561.5, - 560.2, - 559.5, - 558.6, - 557.3, - 555.7, - 553.6, - 552.9, - 553.1, - 551.6, - 550.9, - 551.8, - 550.2, - 548.4, - 547.8, - 548.8, - 549.9, - 549.9, - 548.3, - 546.8, - 548, - 548.8, - 549.7, - 549.8, - 551.1, - 550.8, - 551.3, - 550, - 551.6, - 556, - 556.9, - 556.4, - 554.8, - 551.6, - 551.3, - 547.3, - 546.7, - 546.2, - 543, - 534.1, - 521.9, - 519.2, - 519.5, - 521.3, - 524.1, - 524.7, - 523.3, - 524.4, - 526, - 527.3, - 528.7, - 529.8, - 533.3, - 536.8, - 536.5, - 536.9, - 538.4, - 539.8, - 540.2, - 541.1, - 546.1, - 549, - 547.8, - 546, - 545.5, - 543.7, - 541.2, - 539, - 534.4, - 526.3, - 521.4, - 519.8, - 519.8, - 518.9, - 514.6, - 509.9, - 504.7, - 502.9, - 495.5, - 484.2, - 476.4, - 467.9, - 462.6, - 458.5, - 454.9, - 451.7, - 448.7, - 446.4, - 444.9, - 443.2, - 441.5, - 439.1, - 436.4, - 434.1, - 431.9, - 429.1, - 427.2, - 425.6, - 423.4, - 421.2, - 419.4, - 417.4, - 414.4, - 412, - 409.6, - 407.6, - 405.6, - 403.3, - 400.9, - 398.5, - 395.8, - 393.1, - 390.3, - 388, - 385.3, - 382.7, - 380.5, - 378.7, - 377, - 375.2, - 373.4, - 372.1, - 370.7, - 369.3, - 367.1, - 362.2, - 356.7, - 356, - 355.6, - 354.4, - 352.6, - 352.2, - 351.4, - 351, - 350.4, - 350, - 350.7, - 351.4, - 351.7, - 352.3, - 353.4, - 355, - 357, - 360.2, - 363.5, - 364.4, - 364.1, - 362.6, - 360, - 358.2, - 358.9, - 364.1, - 369.4, - 370.3, - 368, - 364.6, - 363.1, - 359.3, - 344.5, - 336.4, - 326.5, - 317.2, - 310.7, - 308, - 304.7, - 302.2, - 298.2, - 295.1, - 291.8, - 287.9, - 284.8, - 283.1, - 281.8, - 280.5, - 279.4, - 583.2, - 583.2, - 582.9, - 582.7, - 582.7, - 582.7, - 582.8, - 582.9, - 582.9, - 582.9, - 583.1, - 583.5, - 584, - 584.5, - 585, - 585.5, - 585.9, - 586.4, - 587.1, - 588.1, - 589, - 589.7, - 590.7, - 592.3, - 593.9, - 596.9, - 603.3, - 607.8, - 608.6, - 608.8, - 608.7, - 607.7, - 606.5, - 605.4, - 604.2, - 603, - 602.2, - 601.4, - 600, - 599.9, - 600.4, - 600.3, - 599.9, - 599.1, - 598.1, - 598.1, - 598.7, - 601.2, - 603.7, - 605, - 605.9, - 607.1, - 607.5, - 606.7, - 605.8, - 605.4, - 605.1, - 605.5, - 606.2, - 606.7, - 606.6, - 606.2, - 606.4, - 606.6, - 606.5, - 606.1, - 605.3, - 604.3, - 603.1, - 602, - 600.7, - 596.8, - 594.5, - 592.7, - 590.6, - 587.8, - 582.8, - 577.2, - 572.3, - 567.9, - 564.6, - 561.7, - 559.7, - 558.9, - 557.8, - 556, - 555, - 554.9, - 554.1, - 554.6, - 555.1, - 554.4, - 554.5, - 553.8, - 552.4, - 551.5, - 552.4, - 553.8, - 553.3, - 552.2, - 553.5, - 557.6, - 559.1, - 559.1, - 559.2, - 558.4, - 558.7, - 559.7, - 560.2, - 559.7, - 561.7, - 565.6, - 566.9, - 564.5, - 559.7, - 557.6, - 556.2, - 553.6, - 551.9, - 551.2, - 548.8, - 545.8, - 544.4, - 542.5, - 540.7, - 539.5, - 536.8, - 534.1, - 535.7, - 537.4, - 537.2, - 537.1, - 537.1, - 538.2, - 539.7, - 542.2, - 545.1, - 547.7, - 548.4, - 549.4, - 549.8, - 550.1, - 552.5, - 552.9, - 552, - 550.5, - 549.6, - 549, - 547.5, - 544.1, - 540.9, - 534, - 526.9, - 522.5, - 520.5, - 519.2, - 516, - 513.3, - 511.3, - 509, - 500.1, - 482, - 472.7, - 465, - 460.2, - 457, - 454, - 451.1, - 448.3, - 445.9, - 444.3, - 442.7, - 441, - 438.8, - 436.2, - 433.9, - 431.9, - 429.5, - 426.8, - 424.3, - 422.2, - 420.2, - 418.4, - 416.3, - 413.6, - 411, - 408.9, - 406.8, - 405, - 403.1, - 400.8, - 398.7, - 396.4, - 393.5, - 391, - 388.9, - 385.9, - 383, - 381.2, - 380.3, - 379.2, - 377.6, - 375.5, - 373.9, - 372.8, - 371.5, - 369.8, - 366.5, - 362.2, - 360.7, - 360.4, - 359.1, - 357.2, - 356.5, - 355.4, - 353.9, - 353.3, - 352.5, - 353, - 353.8, - 354.3, - 354.5, - 356.3, - 358.4, - 360.6, - 363.3, - 365.8, - 368.2, - 368.7, - 368.1, - 367.4, - 367.2, - 368.5, - 371.1, - 372.1, - 371.4, - 369.1, - 366.3, - 362.9, - 355.4, - 343.9, - 336.2, - 329.6, - 321.1, - 312.9, - 309.9, - 308.6, - 306.8, - 303.8, - 300, - 297, - 294.4, - 289.5, - 285.4, - 283, - 281.7, - 280.7, - 581.8, - 581.6, - 581.2, - 581.1, - 581.1, - 581.2, - 581.3, - 581.5, - 581.4, - 581.4, - 581.5, - 582.1, - 582.6, - 583.2, - 583.7, - 584.1, - 584.3, - 584.9, - 585.6, - 586.5, - 587.5, - 588.4, - 590, - 592.1, - 594.6, - 599.7, - 605.3, - 606.7, - 607.1, - 607.1, - 606.3, - 605.1, - 603.6, - 601.5, - 599.6, - 598.4, - 597.8, - 597.1, - 597.4, - 597.9, - 597.9, - 597, - 596.1, - 595.4, - 595.3, - 596.2, - 599.1, - 601.5, - 602.9, - 604, - 604.8, - 605.2, - 604.5, - 604.1, - 604, - 603.4, - 602.9, - 603.3, - 604, - 604.5, - 604, - 604.3, - 604.6, - 604.8, - 605, - 604.7, - 603.6, - 602.4, - 600.9, - 598.8, - 596.9, - 594.1, - 592.5, - 590.4, - 586.4, - 580.8, - 574.7, - 570.7, - 567.2, - 564.7, - 563, - 559.9, - 558.5, - 557.2, - 555.2, - 552.4, - 552.1, - 553.1, - 553.7, - 554.5, - 556.3, - 557.3, - 557.3, - 556.6, - 556, - 555.9, - 556.4, - 556, - 555.9, - 560.2, - 565.4, - 566.9, - 567, - 568, - 570.3, - 571.7, - 571.5, - 570.6, - 569.6, - 567.3, - 569.9, - 573.4, - 572.9, - 569.8, - 564.8, - 561.6, - 559.6, - 557.4, - 555, - 553.6, - 553.3, - 555.2, - 556.5, - 556.1, - 555.2, - 553.9, - 551.4, - 549.1, - 548.1, - 550.4, - 550.6, - 549.5, - 546.9, - 548.1, - 549.9, - 551, - 554.9, - 558.6, - 560.3, - 560.5, - 559.3, - 557.7, - 556, - 553.8, - 552.9, - 551.7, - 550.6, - 549.4, - 547.9, - 546.2, - 543.1, - 536.9, - 531.6, - 527, - 522.1, - 518.5, - 514.4, - 512.1, - 509.5, - 504.1, - 487.2, - 471.9, - 466.2, - 462.9, - 459.9, - 457.2, - 454.2, - 451.3, - 448.9, - 446.5, - 444.4, - 442.3, - 440.4, - 438.5, - 436.4, - 433.9, - 431.9, - 429.9, - 427.4, - 423.9, - 421.4, - 419.4, - 417.6, - 415.3, - 412.8, - 410.4, - 408.7, - 406.9, - 404.8, - 402.8, - 401.1, - 399.1, - 397, - 394.7, - 392.2, - 389.8, - 387.1, - 384, - 382.3, - 382.1, - 381.4, - 380.1, - 378.6, - 376.9, - 375.3, - 374.1, - 372.6, - 370, - 366, - 364, - 364.1, - 364, - 362.6, - 360.4, - 359.3, - 358.6, - 358.1, - 355.7, - 354.7, - 355.2, - 356.2, - 357.5, - 359.7, - 361.7, - 364, - 366.4, - 369.2, - 372.2, - 373.9, - 372.8, - 371.8, - 370.8, - 371.9, - 373.6, - 372.8, - 370.8, - 366.8, - 361.7, - 354, - 344.9, - 337.7, - 332.4, - 325.9, - 318.2, - 314, - 312.4, - 310.9, - 309.1, - 307.2, - 305, - 302.5, - 300.5, - 297.3, - 291.5, - 286.9, - 283.9, - 282.1, - 580.1, - 580, - 579.5, - 579.4, - 579.4, - 579.6, - 579.8, - 579.8, - 579.7, - 579.9, - 580.2, - 580.8, - 581.3, - 581.8, - 582.2, - 582.4, - 582.8, - 583.6, - 584.2, - 584.8, - 586, - 587.6, - 589.5, - 594, - 599.4, - 602.4, - 604.6, - 605.2, - 604.5, - 603.6, - 602.6, - 601.3, - 598.9, - 597, - 595.9, - 595.1, - 594.8, - 594.9, - 595.3, - 595.1, - 594.2, - 593.4, - 592.5, - 592, - 593.4, - 595.7, - 598.3, - 600.1, - 601.8, - 602.5, - 602.8, - 602.5, - 602, - 602.1, - 601.9, - 601.2, - 600.5, - 600.6, - 601.1, - 601.6, - 601.9, - 602.1, - 602.2, - 602.1, - 602.2, - 601.7, - 600.5, - 598.3, - 596.2, - 593.7, - 592.7, - 591.7, - 589.8, - 584.8, - 578.4, - 573.2, - 569.1, - 566.2, - 564.4, - 563, - 560.6, - 558.4, - 557.1, - 554.8, - 552.1, - 548.8, - 549.4, - 551.7, - 553, - 556.6, - 559.3, - 560.2, - 561.5, - 562, - 560.3, - 560.1, - 558.8, - 561.1, - 568.3, - 574.2, - 575.7, - 576.1, - 576.5, - 578.2, - 581.6, - 583.5, - 585.3, - 584.2, - 582.6, - 580.5, - 579.6, - 577.7, - 575.8, - 572.9, - 568, - 564.5, - 562.5, - 559.9, - 557.3, - 556.3, - 557.3, - 559, - 560.4, - 561.3, - 561.9, - 562.8, - 562.7, - 562.4, - 560.9, - 559.3, - 560.5, - 562.9, - 562.8, - 561.6, - 560.5, - 559.6, - 560.4, - 564.1, - 565.5, - 565.2, - 563.1, - 558.9, - 556.7, - 554.5, - 552.8, - 551.3, - 549.8, - 548, - 546.4, - 544.9, - 542.4, - 536.9, - 533.5, - 529.5, - 523.8, - 517.6, - 513.2, - 507.9, - 497.6, - 482.5, - 470.5, - 466.6, - 464.3, - 461.7, - 459.4, - 456.5, - 453.2, - 451.1, - 449.3, - 447.2, - 445.1, - 442.8, - 440.7, - 438.5, - 436.3, - 433.9, - 431.5, - 429.2, - 427, - 424.3, - 420.9, - 418.6, - 416.6, - 414.5, - 412.8, - 410.9, - 409, - 407.1, - 404.8, - 402.9, - 401.6, - 399.9, - 398, - 395.9, - 393.4, - 390.3, - 387.8, - 385.8, - 385, - 384.5, - 383.7, - 382.6, - 380.9, - 378.9, - 377.1, - 376.2, - 374.6, - 372, - 368.1, - 366.2, - 367, - 367.6, - 367, - 364.4, - 362.6, - 362.7, - 362.6, - 360.8, - 359, - 358.6, - 358.6, - 359.4, - 361.5, - 364.3, - 366.9, - 369.6, - 373.3, - 375.6, - 375.9, - 375.1, - 372.6, - 371.2, - 372.1, - 372.4, - 370.7, - 368.7, - 362.5, - 351.8, - 345.7, - 340.4, - 335.7, - 329.6, - 323.5, - 318, - 315.6, - 314.4, - 313.4, - 312.2, - 310.1, - 307.3, - 305.9, - 305, - 304.1, - 300.5, - 295.1, - 289.5, - 285.1, - 578.1, - 577.9, - 577.6, - 577.7, - 577.7, - 577.9, - 578, - 577.9, - 577.9, - 578.3, - 578.8, - 579.4, - 580.1, - 580.5, - 580.7, - 580.8, - 581.3, - 582.1, - 582.7, - 583.7, - 585.2, - 587.2, - 590.7, - 597.5, - 600.9, - 602, - 603, - 603, - 601.6, - 600.6, - 598.5, - 596.3, - 594.8, - 594, - 593.5, - 593, - 592.8, - 592.7, - 592.1, - 591, - 590.4, - 589.6, - 588.9, - 590.7, - 593.7, - 595.7, - 597.2, - 598.5, - 599.7, - 600.1, - 600.2, - 599.5, - 599.4, - 599.1, - 599, - 598.2, - 597.5, - 598, - 598.9, - 599.5, - 599.8, - 600, - 599.9, - 599.2, - 598.2, - 596.6, - 595.3, - 593.5, - 591.4, - 589.7, - 589.2, - 587.7, - 582.4, - 576, - 571.4, - 567.6, - 565.2, - 563.9, - 562.1, - 559.8, - 557.8, - 556.5, - 553.5, - 550.9, - 547.8, - 546.1, - 547, - 548.8, - 552.2, - 558.5, - 560.9, - 562.4, - 566.2, - 567.8, - 564.3, - 566, - 568.5, - 575.2, - 581.1, - 583.3, - 584.9, - 586.7, - 587.5, - 588.1, - 588.5, - 588.2, - 588, - 587.3, - 586.8, - 585.1, - 582.5, - 580.2, - 578.8, - 576.3, - 571.3, - 568.1, - 566.4, - 564.1, - 560.5, - 558.6, - 559.9, - 561.5, - 563.1, - 564.8, - 567, - 568.5, - 568.7, - 569.1, - 569.5, - 569.5, - 569.2, - 570.5, - 570.4, - 569.2, - 567.8, - 565.4, - 565.4, - 567, - 567, - 566.8, - 564.4, - 557.8, - 555.6, - 553.8, - 551.7, - 550, - 548.5, - 546.7, - 545.1, - 543.6, - 541, - 536.1, - 532.7, - 528.1, - 522.6, - 516.3, - 512.4, - 508, - 496.5, - 477.6, - 470.2, - 466.3, - 463.9, - 461.1, - 458.3, - 455, - 453.1, - 452.1, - 450.7, - 448.5, - 445.9, - 443.1, - 441.1, - 438.9, - 436.5, - 433.9, - 431.2, - 428.5, - 426.1, - 423.2, - 420.2, - 417.5, - 415.8, - 414.3, - 412.9, - 411.5, - 409.7, - 407.5, - 405.3, - 403.5, - 402.3, - 400.8, - 398.9, - 396.7, - 394.5, - 391.7, - 389.7, - 388.9, - 388.3, - 387.1, - 386.4, - 385.3, - 383.5, - 381, - 378.8, - 377.4, - 376.1, - 373.8, - 370.9, - 369.1, - 369.3, - 369.8, - 369.8, - 368.7, - 367.3, - 365.6, - 365.3, - 365, - 364.9, - 364.3, - 364.1, - 362.4, - 365.6, - 368.6, - 370.8, - 373.4, - 375.4, - 375.4, - 375, - 373, - 370.2, - 369.3, - 370, - 369.9, - 367, - 361.9, - 354.6, - 348.1, - 344.4, - 339.9, - 334.2, - 326.9, - 323.2, - 320.5, - 318, - 316.6, - 315.4, - 315, - 314.2, - 311.2, - 308.5, - 307.4, - 307.1, - 306, - 302.6, - 297.4, - 292.6, - 576, - 575.9, - 576, - 576, - 575.9, - 576, - 576, - 576, - 576.1, - 576.3, - 577.1, - 578, - 578.6, - 578.9, - 579.1, - 579.3, - 579.8, - 580.5, - 581.5, - 582.8, - 584.6, - 587.8, - 593.3, - 598, - 599.7, - 600.8, - 601.1, - 600.4, - 598.8, - 596.9, - 593.9, - 592.5, - 592, - 591.6, - 591.3, - 590.9, - 590.3, - 589.3, - 587.9, - 586.9, - 586.5, - 586.4, - 588, - 591.3, - 593.3, - 594.5, - 595.2, - 595.8, - 596.6, - 597.1, - 597.1, - 596.7, - 596, - 595.4, - 594.8, - 594, - 594.3, - 595.6, - 597, - 597.7, - 597.7, - 597.4, - 597.4, - 596.3, - 594.6, - 593.3, - 592.1, - 590.3, - 587.9, - 586.1, - 584.8, - 579.1, - 573.6, - 569.4, - 566, - 563.8, - 562.5, - 560.7, - 558.9, - 556.7, - 553.7, - 551.2, - 548.9, - 546.8, - 544.5, - 544.9, - 546.2, - 548, - 551.2, - 558.3, - 561.8, - 565.2, - 570.7, - 573.5, - 574.3, - 578.2, - 581.7, - 585.9, - 588, - 589.5, - 589.8, - 589.9, - 589.9, - 589.5, - 589, - 588.2, - 587.7, - 587.7, - 587, - 584.8, - 582.9, - 581.8, - 581.2, - 578.8, - 576.3, - 576.5, - 572.1, - 567, - 563.9, - 561, - 562.1, - 564.3, - 566.1, - 568, - 569.8, - 570.9, - 571.5, - 572, - 572.6, - 574.6, - 573.2, - 572, - 571.8, - 571.2, - 570.5, - 568.9, - 568.3, - 568.3, - 567, - 566, - 564, - 556.6, - 554.4, - 552.5, - 550.2, - 548.5, - 546.9, - 545.3, - 543.8, - 542.3, - 540.3, - 535.2, - 530.1, - 524.8, - 518.5, - 514.4, - 512.1, - 509.3, - 499.1, - 483.4, - 472.8, - 469.4, - 466.6, - 464.1, - 461, - 456.6, - 455.5, - 457.1, - 454.9, - 450.4, - 447.6, - 444.6, - 442.1, - 439.4, - 435.7, - 432.9, - 430.3, - 427.6, - 424.7, - 422.4, - 420.3, - 417.8, - 415.7, - 414.4, - 413.1, - 411.8, - 410.2, - 408.5, - 406.7, - 404.8, - 403.2, - 401.8, - 399.8, - 397.7, - 395.7, - 393.4, - 391.8, - 391.6, - 391.2, - 390.3, - 389.2, - 388, - 386.4, - 383.7, - 380.6, - 378.6, - 377.6, - 376.7, - 374.6, - 372.9, - 371.6, - 371.9, - 372.6, - 372.7, - 372.5, - 370.9, - 368, - 367.9, - 368.6, - 368.8, - 368.8, - 368.1, - 371.2, - 373.5, - 374.7, - 375.1, - 375, - 373.1, - 370.9, - 368.5, - 367, - 366.1, - 365, - 363.1, - 358.6, - 353.5, - 349.9, - 346.2, - 341.9, - 337.9, - 332, - 327.7, - 325.3, - 323.3, - 321.1, - 319.7, - 318.1, - 317.4, - 317.2, - 316, - 313.1, - 312, - 312.5, - 312.4, - 307, - 302.9, - 299.6, - 574.5, - 574.4, - 574.4, - 574.2, - 574.2, - 574, - 574, - 574.3, - 574.4, - 575.1, - 576.2, - 576.8, - 577.1, - 577.2, - 577.3, - 577.7, - 578.4, - 579.3, - 580.6, - 582.4, - 585.6, - 591, - 596, - 597.4, - 598.1, - 598.7, - 598.9, - 598, - 595.3, - 592.2, - 590.4, - 589.8, - 589.3, - 589.1, - 588.9, - 588.3, - 586.5, - 584.8, - 583.7, - 583.1, - 583.4, - 586, - 589.1, - 590.9, - 592, - 592.1, - 592.4, - 593.2, - 594.2, - 594.7, - 594.1, - 593.6, - 592.5, - 591.4, - 590.6, - 590.5, - 591.5, - 593.9, - 595.3, - 596.1, - 596.1, - 595.3, - 594.1, - 592.7, - 591.4, - 590, - 588.3, - 585.5, - 582.1, - 578.7, - 575.1, - 570.7, - 567, - 564, - 562.4, - 560.9, - 559.9, - 558.1, - 555.7, - 551.4, - 547.8, - 546.2, - 544.1, - 542.6, - 543, - 543.7, - 545.1, - 547.4, - 551.3, - 558.6, - 563.4, - 567.3, - 575.5, - 583.1, - 586, - 586.3, - 587, - 588.4, - 590.1, - 591, - 590.7, - 590.1, - 589.4, - 588.8, - 588.1, - 587.3, - 586.6, - 586.5, - 585.6, - 584.8, - 583.5, - 582.8, - 582.5, - 581.6, - 581, - 580.3, - 576, - 570.5, - 565.9, - 563.7, - 565.3, - 567.4, - 568.8, - 570.7, - 572.4, - 573.3, - 573.5, - 573.7, - 573.3, - 573, - 571.9, - 571.2, - 570.9, - 570.4, - 570, - 569.5, - 568.9, - 567.9, - 566.4, - 565, - 562.8, - 555.3, - 552.7, - 550.6, - 548.9, - 547.3, - 545.8, - 544.2, - 542.6, - 541, - 539.5, - 535.5, - 527.4, - 520.4, - 516.4, - 514.4, - 507.8, - 501.3, - 496.5, - 488.7, - 476.9, - 473, - 470.1, - 467, - 463.9, - 459.5, - 459.2, - 460.2, - 456.4, - 452.3, - 449.9, - 446.7, - 443.5, - 439.9, - 435, - 431.8, - 429.5, - 426.8, - 424.5, - 421.9, - 419.9, - 418.1, - 416.2, - 414.7, - 413.3, - 412, - 410.8, - 409.5, - 407.8, - 406, - 404.2, - 402.7, - 401, - 399, - 397.4, - 395.2, - 394.7, - 394.9, - 394.3, - 393.9, - 392.5, - 390.8, - 388.5, - 385.7, - 383.3, - 380.9, - 379.6, - 378.8, - 377, - 375.5, - 374, - 374.1, - 375, - 375.8, - 376, - 375.2, - 373, - 370.6, - 371, - 371.4, - 371.6, - 372.5, - 374.4, - 376.5, - 376.6, - 375.6, - 373.4, - 370, - 367.4, - 365.6, - 364.2, - 363, - 361.3, - 359.2, - 355.5, - 351.9, - 347.1, - 342.8, - 339.8, - 336.8, - 332.7, - 330.9, - 329.9, - 328.4, - 326.3, - 323.8, - 322.5, - 320.7, - 319.7, - 318.9, - 317.6, - 317.4, - 317.3, - 316.5, - 313, - 306.2, - 302.9, - 573, - 572.7, - 572.3, - 572.1, - 572, - 571.8, - 572.1, - 572.5, - 573.1, - 574.5, - 575.7, - 575.9, - 575.6, - 575.3, - 575.5, - 576.2, - 577.1, - 578.3, - 580.4, - 584.4, - 588.8, - 593.6, - 595.7, - 596.2, - 596.7, - 596.9, - 596.5, - 593.5, - 589.8, - 588, - 587.6, - 587.4, - 586.9, - 586.6, - 586.3, - 584.3, - 581.5, - 580, - 579.5, - 580.6, - 583.2, - 586.3, - 588.1, - 589.1, - 588.9, - 588.7, - 588.9, - 590.6, - 591.8, - 591.8, - 590.7, - 589.5, - 588.1, - 587.2, - 587.2, - 588.4, - 590.2, - 592.3, - 593.2, - 593.6, - 593.3, - 591.8, - 590.7, - 590.2, - 588.5, - 586, - 583, - 579, - 575.5, - 572, - 568.5, - 565.2, - 562.7, - 561.1, - 559.7, - 558.4, - 556.5, - 553.2, - 549.3, - 546.9, - 545.4, - 543, - 540.9, - 541, - 541.7, - 542.6, - 544, - 546.5, - 550.1, - 556.5, - 566.4, - 581.9, - 588.4, - 589.9, - 590.1, - 589.5, - 589.6, - 590.3, - 591.1, - 590.7, - 590, - 589.1, - 588.5, - 588, - 587.2, - 586.6, - 585.8, - 585.1, - 584.6, - 583.8, - 583, - 582.5, - 582, - 581.3, - 580.8, - 580.1, - 577.7, - 574.4, - 570.6, - 568.6, - 569.1, - 570.2, - 571.5, - 572.8, - 573.5, - 573.6, - 573.5, - 573, - 572.2, - 571.6, - 570.8, - 570.2, - 570, - 569.7, - 569, - 568.4, - 567.8, - 566.3, - 565.2, - 564, - 559.2, - 554, - 551, - 549.1, - 547.7, - 546.4, - 544.9, - 543.3, - 541.6, - 539.8, - 537.6, - 530.8, - 522.3, - 517, - 514.8, - 510.5, - 500.5, - 494, - 488.9, - 484.6, - 479.9, - 476, - 472.9, - 470.7, - 468.2, - 464.1, - 462.5, - 461.2, - 457.8, - 454.1, - 450.5, - 447.2, - 443.8, - 440.3, - 436.1, - 432.6, - 430, - 427.4, - 425.5, - 423.5, - 421.6, - 419, - 417, - 415.2, - 413.6, - 412.5, - 411.4, - 410.1, - 408.6, - 407, - 405.3, - 403.6, - 402.3, - 400.4, - 398.7, - 397.3, - 398, - 398.2, - 398, - 397.2, - 395.8, - 394, - 391.8, - 389, - 386.4, - 384, - 382.1, - 380.9, - 379.2, - 378.3, - 377.4, - 377.1, - 376.9, - 377.7, - 378.5, - 378.2, - 377.3, - 374.9, - 373.1, - 373.7, - 373.8, - 375.2, - 377.2, - 378.1, - 377.3, - 374.8, - 371.4, - 367.9, - 365.4, - 363.3, - 361.6, - 360.4, - 358.3, - 355.6, - 352.6, - 347.9, - 344.4, - 343, - 341.7, - 340, - 336.9, - 334.6, - 334.2, - 333.3, - 331.3, - 327.9, - 325.6, - 324.2, - 322.2, - 320.9, - 320.4, - 319.6, - 318.4, - 316, - 312.9, - 307.4, - 303.2, - 571, - 569.9, - 569.4, - 569.6, - 569.6, - 569.6, - 570, - 570.7, - 572.1, - 573.8, - 574.4, - 574.2, - 573.7, - 573.7, - 574, - 574.8, - 576, - 578.2, - 583.2, - 588.4, - 591.9, - 593.7, - 594.3, - 594.6, - 594.6, - 592.8, - 590.7, - 587.8, - 585.7, - 585.1, - 585, - 584.9, - 584.4, - 583.7, - 581.6, - 578.2, - 576.8, - 576.2, - 577.8, - 581.7, - 583.7, - 584.8, - 585.8, - 585.8, - 585.2, - 585.1, - 585.8, - 587.1, - 587.7, - 587, - 585.9, - 584.7, - 583.6, - 583.7, - 586.1, - 588.7, - 589.9, - 590.2, - 590.4, - 590.1, - 588.8, - 587.3, - 587, - 585.8, - 583.5, - 580.6, - 576.9, - 573.1, - 569.9, - 566.5, - 564.6, - 563, - 560.3, - 558, - 556.6, - 555.2, - 553, - 549.4, - 546.2, - 544.5, - 541.9, - 539.2, - 538.7, - 539.7, - 540.6, - 542.5, - 544.9, - 546.9, - 550.5, - 560.8, - 581.9, - 591.5, - 591.8, - 591.5, - 591.3, - 590.8, - 591, - 590.9, - 590.8, - 590.3, - 589.3, - 588.3, - 587.8, - 587.4, - 586.7, - 586, - 584.8, - 583.9, - 583.3, - 582.4, - 581.7, - 581, - 580.2, - 579.4, - 578.8, - 578.6, - 577.9, - 577, - 574.6, - 572.7, - 572.1, - 572.9, - 573.5, - 573.4, - 572.8, - 572.2, - 571.9, - 571.7, - 571.2, - 571.1, - 570.5, - 569.5, - 568.8, - 568.3, - 567.9, - 567.4, - 566.6, - 564.9, - 564, - 563, - 556.9, - 551.7, - 549.8, - 547.8, - 546.3, - 545.2, - 543.9, - 542.2, - 540.3, - 537.6, - 531.4, - 524.1, - 518.8, - 515.8, - 513.3, - 507.5, - 498.9, - 493.1, - 488, - 483.2, - 480.3, - 477.4, - 474.5, - 472.2, - 469, - 465, - 463.1, - 461.6, - 458.2, - 454.1, - 450.2, - 446.6, - 443.4, - 440.3, - 437.4, - 434.7, - 432.6, - 430.4, - 428.4, - 427, - 425.8, - 423.2, - 419.9, - 416.9, - 414.6, - 413.1, - 412, - 410.7, - 409.3, - 407.9, - 406, - 404.5, - 403, - 401.4, - 400.3, - 400.1, - 400.5, - 400.7, - 400.6, - 399.7, - 398.1, - 396.7, - 395.1, - 392.9, - 389.9, - 386.8, - 384.3, - 382.8, - 381.6, - 381.4, - 381, - 380, - 378.8, - 379.3, - 380.2, - 381, - 381.4, - 380, - 376.9, - 375.4, - 376, - 377, - 377.7, - 377.1, - 376.1, - 372.8, - 369.9, - 366.9, - 364.2, - 361.7, - 359, - 356.5, - 354.5, - 352, - 349.3, - 347, - 345.5, - 344.6, - 343.5, - 342.4, - 340.9, - 339.8, - 338.5, - 337, - 335.9, - 332, - 328.7, - 327.6, - 328.6, - 328.4, - 328.1, - 324.7, - 321.8, - 318.2, - 312.6, - 306.5, - 302.3, - 568.1, - 567.3, - 567.6, - 567.8, - 567.7, - 567.9, - 568.4, - 569.4, - 571, - 572.1, - 572.5, - 572.3, - 571.9, - 572, - 572.6, - 573.5, - 576.2, - 581.1, - 587.2, - 590, - 591.1, - 591.9, - 592.2, - 591.7, - 589.5, - 586.6, - 584, - 582.4, - 582.2, - 582.2, - 582.2, - 581.8, - 580.5, - 578.3, - 575.2, - 573.5, - 573.5, - 574.8, - 577.8, - 580.3, - 581.8, - 582.6, - 582.7, - 581.7, - 581.5, - 582.2, - 583.1, - 583.6, - 583.2, - 582.5, - 581.4, - 580.4, - 580.4, - 582.2, - 585.6, - 587.4, - 588, - 588, - 587.4, - 586.7, - 585.5, - 583.5, - 581.8, - 579.8, - 577.4, - 574.3, - 570.6, - 567.6, - 564.9, - 563.3, - 562.4, - 559.7, - 556.2, - 554.7, - 553.7, - 552.5, - 549, - 546, - 543.2, - 540.3, - 537.3, - 536.4, - 537.1, - 538.4, - 541, - 544.3, - 546.6, - 552.4, - 566.4, - 582.8, - 594.3, - 594.2, - 592.5, - 591.9, - 591.8, - 591.1, - 590.6, - 590.3, - 590.1, - 589.7, - 589.1, - 588.1, - 587.4, - 587.1, - 586, - 584.9, - 583.5, - 582.5, - 581.8, - 581.2, - 580.8, - 579.9, - 579, - 578.7, - 578.2, - 577.7, - 577.2, - 577.2, - 576.1, - 574.5, - 573.9, - 573.8, - 573.3, - 572.8, - 572.1, - 571.2, - 570.4, - 570.3, - 569.7, - 569.4, - 569.3, - 568.5, - 567.8, - 567.3, - 566.8, - 566.5, - 565.6, - 563.9, - 562.8, - 561.7, - 557, - 550.4, - 548.5, - 546.7, - 545.2, - 543.9, - 541.9, - 538.3, - 534.7, - 530.2, - 525.8, - 523, - 518, - 513, - 507.2, - 502.4, - 498.6, - 493.3, - 488.1, - 485.1, - 481.3, - 477.3, - 474, - 470.5, - 466.4, - 463.9, - 462.7, - 460.1, - 456.4, - 452.9, - 449.2, - 445.8, - 443.1, - 440.7, - 438.5, - 436.2, - 434.3, - 432.8, - 430.9, - 429.2, - 427.7, - 425.8, - 423.7, - 421, - 417.8, - 415.2, - 413.2, - 411.6, - 410.1, - 408.6, - 406.6, - 404.9, - 403.8, - 402.9, - 402.7, - 402.7, - 402.6, - 402.3, - 401.7, - 401.3, - 400.2, - 398.4, - 396.7, - 394.6, - 392.1, - 389.1, - 386.6, - 385.2, - 384.6, - 384.4, - 384.4, - 382.9, - 381.1, - 380.9, - 381.6, - 382.3, - 383.1, - 383.4, - 381.9, - 379.3, - 378.4, - 377.6, - 377.4, - 376.1, - 374.3, - 371.6, - 368.8, - 366, - 363.5, - 360.9, - 358.2, - 355.9, - 353.8, - 351.9, - 350.5, - 349.5, - 348.4, - 347.4, - 346.8, - 346.8, - 347.1, - 347, - 345.8, - 347.4, - 349.4, - 346.7, - 342.9, - 340.5, - 338.8, - 336.6, - 332.9, - 328.4, - 324.7, - 321.5, - 317.5, - 310.5, - 303.3, - 565.5, - 565.6, - 565.8, - 565.8, - 566, - 566.4, - 567.1, - 568.4, - 570, - 570.5, - 570.7, - 570.4, - 570, - 570.1, - 570.8, - 573.3, - 578.1, - 582.9, - 587.2, - 588.8, - 589.7, - 590, - 588.2, - 585, - 582.1, - 579.6, - 579.1, - 579.1, - 579.2, - 579.2, - 578.6, - 576.7, - 574.2, - 571.4, - 569.8, - 570.2, - 572.1, - 574.4, - 576.1, - 577.1, - 578.4, - 579.3, - 578.6, - 578.1, - 578.8, - 579.5, - 579.8, - 579.5, - 578.5, - 577.7, - 577.4, - 578, - 579.3, - 580.5, - 582.2, - 583.8, - 584.5, - 584.3, - 583.7, - 582.7, - 580.8, - 578.3, - 576, - 573.8, - 571, - 567.9, - 565.1, - 563.5, - 562.7, - 561.4, - 558.2, - 554.4, - 552.8, - 552.7, - 551.6, - 548.3, - 543.9, - 541.6, - 539.1, - 535.8, - 534.2, - 534.9, - 535.9, - 537.9, - 541.2, - 543.4, - 552.9, - 576.8, - 589.2, - 594.2, - 595.6, - 594.3, - 592.5, - 591.8, - 591.2, - 590.5, - 590, - 589.4, - 589, - 588.5, - 587.7, - 586.9, - 586.2, - 585.4, - 583.9, - 583.3, - 581.9, - 581.2, - 580.6, - 579.9, - 579.5, - 578.7, - 578.2, - 578, - 577.6, - 577, - 576.4, - 576.6, - 575.5, - 574.6, - 573.7, - 573.2, - 572.7, - 572, - 571.2, - 570.5, - 569.6, - 569.1, - 568.6, - 568, - 568.1, - 567.8, - 567.2, - 566.3, - 566.2, - 565.9, - 564.3, - 562.8, - 561.6, - 560.6, - 558.1, - 550, - 547.1, - 545.5, - 543.3, - 540.1, - 535.8, - 531.6, - 529.4, - 527, - 523.4, - 519.4, - 514.3, - 510, - 506.9, - 502.6, - 498.9, - 496, - 493.1, - 488.9, - 480.4, - 475.5, - 472.4, - 469.7, - 466.8, - 463.6, - 460.6, - 457.6, - 454.4, - 450.6, - 447.5, - 445.6, - 443.9, - 441.7, - 440, - 438.4, - 436.6, - 434.9, - 433, - 431.1, - 429.4, - 427.6, - 426, - 424.3, - 422.6, - 420.2, - 417.3, - 413.9, - 411.5, - 409.9, - 408.4, - 406.4, - 404.9, - 404.6, - 404.8, - 405, - 404.5, - 403.9, - 404.2, - 404.7, - 403.5, - 401.5, - 398, - 395.5, - 393.1, - 391, - 389.6, - 388.4, - 387.7, - 387.3, - 386.7, - 385.9, - 384.7, - 383.4, - 383.2, - 383.7, - 384.4, - 385.1, - 385.2, - 384.1, - 382.5, - 380.4, - 378.4, - 376.3, - 373.8, - 371.6, - 368.7, - 365.9, - 363.8, - 361.4, - 358.9, - 356.9, - 355.1, - 353.5, - 352.3, - 351.6, - 351.9, - 351.7, - 351.7, - 352.3, - 352.5, - 352.8, - 352.1, - 353.4, - 355.4, - 356.4, - 355.4, - 352.3, - 347.9, - 342.7, - 337.4, - 332.7, - 328.9, - 325.4, - 323.1, - 321.1, - 313.2, - 563.7, - 563.9, - 564.1, - 564.3, - 564.6, - 565.2, - 566.1, - 567.7, - 568.4, - 568.7, - 568.8, - 568.2, - 567.9, - 568.2, - 569.6, - 572.9, - 577.7, - 581, - 583.2, - 585.5, - 586.8, - 585.3, - 581.5, - 578.1, - 576.3, - 576.1, - 576.1, - 576, - 575.9, - 575.3, - 573.1, - 570.6, - 567.9, - 566.5, - 567.1, - 569.1, - 571.5, - 572.7, - 573.6, - 573.9, - 574.1, - 574.3, - 574.6, - 575.2, - 575.8, - 576, - 575.6, - 574.7, - 573.9, - 574, - 575.1, - 576.5, - 577.3, - 577.9, - 578.5, - 579.3, - 579.1, - 578.1, - 576.9, - 575.4, - 573.8, - 571.8, - 569.4, - 566.7, - 563.9, - 562.3, - 562.4, - 562.3, - 561.4, - 558.6, - 553.7, - 550.2, - 549.8, - 549.5, - 546.4, - 542, - 539.8, - 537.5, - 534.5, - 532.6, - 532.9, - 533.9, - 534.9, - 536.2, - 539, - 547, - 561.3, - 586.7, - 593.9, - 594.1, - 593, - 591.8, - 591.1, - 590.7, - 590.3, - 589.7, - 589.1, - 588.4, - 587.8, - 587.5, - 586.7, - 585.7, - 584.5, - 583.4, - 582.5, - 582.3, - 581, - 580.3, - 580, - 579, - 578.1, - 577.6, - 577.4, - 577, - 576.7, - 576.1, - 575.6, - 575.6, - 574.9, - 574.1, - 572.8, - 572.2, - 571.7, - 570.7, - 569.8, - 569.3, - 568.8, - 568.7, - 568.2, - 567.8, - 568.4, - 567.8, - 566.2, - 565.4, - 565.4, - 565.3, - 563.4, - 561.9, - 560.6, - 559.6, - 558.1, - 549.7, - 545.9, - 543.9, - 540.3, - 537.4, - 534, - 531.6, - 530.3, - 528.5, - 525.2, - 520.8, - 515.4, - 510.6, - 508.2, - 505, - 502, - 499.3, - 493, - 486.2, - 479, - 474.9, - 471.5, - 468.5, - 465.2, - 462.1, - 459.1, - 455.7, - 452, - 449.6, - 448.6, - 447.2, - 445.5, - 443.6, - 441.9, - 440.3, - 438.6, - 436.7, - 434.9, - 433.2, - 431.6, - 429.9, - 427.8, - 426.2, - 424.5, - 422.8, - 421.5, - 419.2, - 416.4, - 412.8, - 409.9, - 408, - 406.7, - 406.4, - 406.5, - 407.3, - 407.7, - 408.5, - 411.5, - 410.9, - 407.9, - 405.9, - 403.6, - 399.5, - 396.1, - 393.6, - 392.3, - 391.3, - 390.7, - 390.1, - 389.4, - 389, - 388.2, - 387.4, - 386.5, - 386.1, - 386.6, - 387, - 386.8, - 386.1, - 384.8, - 382.7, - 380, - 377.1, - 374.5, - 372.4, - 369.7, - 366.7, - 364.4, - 362.3, - 360.2, - 358.4, - 357, - 355.9, - 355.3, - 355.1, - 355.2, - 355.5, - 355.8, - 355.6, - 355.9, - 356.5, - 356.7, - 357.5, - 357.6, - 357, - 355.7, - 354.2, - 352.4, - 349.6, - 344.8, - 339, - 333.5, - 329.6, - 327.8, - 326, - 321.7, - 562.1, - 562.3, - 562.5, - 562.9, - 563.5, - 564, - 565.3, - 566.3, - 566.3, - 566.6, - 566.3, - 565.5, - 565.8, - 567.1, - 568.5, - 570.1, - 573.1, - 575.4, - 576.6, - 578, - 578.6, - 577, - 574.3, - 572.9, - 573, - 573.1, - 572.5, - 571.9, - 571.2, - 570, - 567.6, - 565, - 563.7, - 564.3, - 566.4, - 568.3, - 569.5, - 570.2, - 570.4, - 570.3, - 570.7, - 571.2, - 571.8, - 572.4, - 572.5, - 571.9, - 571, - 570.3, - 570.4, - 571.6, - 573.4, - 574.3, - 574.6, - 574.6, - 574.3, - 574.3, - 573.6, - 572.2, - 570.6, - 568.8, - 566.9, - 564.5, - 561.7, - 558.6, - 557.5, - 559.4, - 560.9, - 560.1, - 558, - 553.2, - 548, - 545.2, - 545.4, - 543.6, - 540.7, - 537.6, - 534.7, - 532, - 530.7, - 531.1, - 532, - 532.9, - 534, - 536.4, - 543.3, - 551.6, - 561.5, - 583, - 593.1, - 592.7, - 591.8, - 590.7, - 590.2, - 589.8, - 589.3, - 588.7, - 588, - 587.3, - 587, - 586.4, - 585.4, - 584.4, - 583.4, - 582.5, - 581.6, - 581, - 580, - 579.2, - 578.9, - 578.1, - 577.3, - 576.7, - 576.4, - 576.2, - 575.7, - 575.3, - 574.8, - 574.4, - 573.6, - 572.7, - 571.7, - 571.1, - 570.6, - 569.7, - 569.1, - 568.8, - 568.2, - 568, - 567.9, - 568, - 567.8, - 566.5, - 565.1, - 564.2, - 563.8, - 563, - 561.9, - 561, - 559.7, - 558.5, - 557.2, - 549.8, - 544.6, - 542.8, - 540.9, - 538.9, - 535.9, - 533.5, - 532.5, - 530.5, - 525.9, - 520.1, - 514.8, - 510.1, - 508.8, - 506.5, - 502.3, - 495.6, - 487.4, - 482.2, - 479.3, - 475.9, - 470.6, - 467, - 463.9, - 461.4, - 459.1, - 456.6, - 454.1, - 452.3, - 451, - 449.4, - 447.3, - 445.4, - 443.6, - 441.8, - 440.4, - 439.1, - 437.7, - 436.1, - 434.3, - 432.6, - 431.1, - 428.6, - 426.1, - 425.6, - 425, - 422.6, - 420.4, - 417.8, - 414, - 410.3, - 408.9, - 408.6, - 408.7, - 410.9, - 413.7, - 415.4, - 417, - 416.6, - 414.2, - 413.1, - 414, - 408.3, - 402.9, - 399.1, - 396.1, - 394.4, - 393.3, - 392.3, - 391.4, - 391, - 390.5, - 390.2, - 389.7, - 389.4, - 389.7, - 389.7, - 388.8, - 387, - 385.2, - 383.3, - 380.6, - 377.9, - 375.4, - 373.4, - 370.7, - 368.1, - 365.8, - 363.7, - 361.8, - 360.4, - 359.1, - 358.6, - 358.9, - 358.8, - 358.7, - 358.7, - 358.8, - 358.8, - 358.9, - 359.2, - 359.2, - 358.8, - 357.8, - 356.5, - 354.5, - 353, - 351.8, - 350.5, - 348.3, - 343.2, - 337.3, - 332.4, - 328.6, - 326.3, - 324.6, - 560.8, - 560.9, - 561.1, - 561.3, - 561.6, - 562.7, - 564.2, - 564.5, - 564.5, - 564.3, - 563.6, - 563.5, - 564.5, - 565.4, - 565.9, - 566.3, - 567, - 568.2, - 569.3, - 569.5, - 570, - 569.6, - 569.2, - 569.3, - 569.2, - 568.4, - 567.1, - 566.7, - 566.1, - 564.7, - 562, - 560.5, - 560.9, - 563.1, - 564.8, - 566, - 566.2, - 566.2, - 566.6, - 567.3, - 567.8, - 568.1, - 568.5, - 568.7, - 568.4, - 567.3, - 566.6, - 567.1, - 568.4, - 570, - 571, - 571.3, - 571.2, - 570.6, - 569.6, - 568.7, - 567.7, - 566.3, - 564.9, - 563, - 560.1, - 556.6, - 553.3, - 553, - 554.4, - 557.1, - 556.7, - 554.7, - 552.2, - 547.3, - 543.4, - 541.1, - 539.5, - 537.5, - 534.5, - 531.8, - 529.5, - 528.5, - 529.2, - 530.1, - 530.9, - 532.1, - 533.8, - 538.3, - 546.5, - 553.1, - 564.1, - 584.6, - 591.9, - 591.6, - 591, - 590.2, - 589.5, - 588.8, - 588, - 587.4, - 586.8, - 585.9, - 585.5, - 584.3, - 583.4, - 582.8, - 581.9, - 581.3, - 580.6, - 579.5, - 578.4, - 578, - 577.8, - 577, - 576.4, - 576, - 575.6, - 575.4, - 574.9, - 574.5, - 574.1, - 573.7, - 572.4, - 571.2, - 570.7, - 570, - 569.2, - 568.8, - 568.2, - 567.8, - 567.2, - 567.1, - 566.9, - 566.4, - 565.6, - 564.6, - 563.1, - 563, - 563, - 562, - 560.7, - 560, - 558.7, - 557.7, - 556.3, - 549, - 542.9, - 541.3, - 539.5, - 537.6, - 535.3, - 533.1, - 531.4, - 528.7, - 523.6, - 519.4, - 517, - 511.7, - 508.3, - 503.4, - 495.2, - 489.3, - 485.2, - 482.1, - 478.9, - 474.9, - 469.9, - 465.6, - 463.6, - 461.9, - 460.3, - 458.9, - 456.9, - 454.8, - 453, - 451.3, - 449.4, - 447.3, - 445.4, - 443.8, - 442.5, - 441.6, - 440.6, - 439.3, - 437.3, - 435.9, - 435.6, - 432.8, - 429.7, - 429.6, - 427.8, - 424.6, - 421.8, - 418.8, - 416.8, - 417.5, - 414.9, - 411.8, - 411.2, - 412.6, - 415.9, - 418.8, - 421.4, - 422.8, - 423.4, - 423, - 422.3, - 419.1, - 411.5, - 405.4, - 400.6, - 397.8, - 396.2, - 394.8, - 393.7, - 392.9, - 392.7, - 392.5, - 392.5, - 393.1, - 393, - 392.2, - 390.9, - 388.4, - 385.7, - 383.5, - 381, - 378.8, - 376.2, - 374.1, - 371.7, - 369.6, - 367.6, - 365.6, - 363.7, - 362.2, - 361.2, - 361.3, - 361.6, - 361.4, - 361.2, - 361.3, - 361.4, - 361.1, - 361.3, - 361.2, - 360.3, - 359.2, - 357.8, - 356.2, - 353.7, - 351.6, - 349.9, - 347.9, - 345.4, - 341.2, - 338.2, - 336, - 332.2, - 326.5, - 324.1, - 559.4, - 559.6, - 559.5, - 559.6, - 560.4, - 561.7, - 562.4, - 562.7, - 562.6, - 561.8, - 561.7, - 562.4, - 563.2, - 563.6, - 563.7, - 564, - 564.3, - 564.9, - 565, - 563.8, - 562.8, - 562.6, - 562.8, - 563.3, - 563.1, - 562.5, - 562.4, - 562.6, - 561.4, - 559.1, - 556.9, - 556.8, - 559, - 560.8, - 562, - 562.5, - 562.4, - 562.9, - 563.7, - 564.3, - 564.7, - 564.8, - 564.5, - 564.3, - 563.6, - 563.1, - 563.9, - 565.6, - 566.7, - 567.5, - 567.8, - 567.5, - 566.9, - 565.7, - 564.4, - 563.5, - 562.3, - 560.3, - 558, - 555.3, - 551.5, - 548.4, - 548.5, - 550.3, - 551.4, - 552, - 550.2, - 548.5, - 545.4, - 542, - 539.3, - 536.9, - 534.4, - 531.6, - 529.3, - 527.2, - 526.4, - 527.2, - 528.1, - 528.9, - 529.6, - 531, - 533.9, - 539.1, - 546.7, - 553.5, - 564.4, - 581.9, - 590.9, - 590.9, - 590.2, - 589.2, - 588.2, - 587.7, - 587.1, - 586.4, - 585.7, - 584.9, - 584.3, - 583.5, - 582.5, - 581.3, - 580.2, - 579.3, - 578.7, - 578, - 577.1, - 577, - 576.5, - 576, - 575.4, - 575, - 574.8, - 574.5, - 573.8, - 573.2, - 573.1, - 573, - 571.4, - 570.4, - 569.5, - 568.8, - 568.3, - 567.9, - 567.3, - 566.8, - 566.5, - 566.3, - 565.5, - 564.6, - 563.9, - 563, - 562.2, - 562, - 561.7, - 560.9, - 560, - 559.1, - 558.2, - 557.1, - 555.4, - 547, - 541.1, - 539.4, - 537.6, - 535.8, - 533.5, - 531.4, - 529.1, - 526.6, - 524.1, - 521.6, - 519.2, - 515, - 507.5, - 498.6, - 492.7, - 489, - 484.8, - 480.7, - 477.4, - 472.9, - 468, - 465.6, - 464.6, - 463.4, - 461.9, - 460.5, - 458.7, - 457.1, - 455.7, - 453.6, - 451.7, - 449.9, - 447.9, - 446.3, - 444.9, - 443.9, - 443.1, - 442.3, - 441.9, - 442.5, - 442, - 436.8, - 433.6, - 431.5, - 428.2, - 425.5, - 423.9, - 422.2, - 421.8, - 423.4, - 422.9, - 418.1, - 413.7, - 414.2, - 416.7, - 420.6, - 424.7, - 427.4, - 428.2, - 428, - 426.2, - 424.3, - 419.9, - 411.9, - 405.8, - 402.5, - 400.1, - 398.5, - 397, - 395.9, - 395.3, - 395.1, - 395.4, - 395.8, - 395.8, - 395.2, - 393.4, - 390.3, - 387, - 383.9, - 381.9, - 380.2, - 377.5, - 375.5, - 373.4, - 371.4, - 369.6, - 367.9, - 365.9, - 364.4, - 363.9, - 363.7, - 363.5, - 363.2, - 363.1, - 363.1, - 363.4, - 363.4, - 363.2, - 362.7, - 361.6, - 359.8, - 357.5, - 355.6, - 353.8, - 351.2, - 349.3, - 347.7, - 346.1, - 344.4, - 343.1, - 342.2, - 340.1, - 333.6, - 326.5, - 557.7, - 557.8, - 557.8, - 558.5, - 559.4, - 560, - 560.2, - 560.1, - 559.9, - 559.7, - 560.1, - 560.7, - 561.3, - 561.4, - 561.5, - 561.5, - 561.5, - 561.3, - 559.7, - 558.4, - 558.1, - 558, - 558.2, - 558.7, - 558.8, - 558.3, - 558.4, - 558.1, - 556.1, - 554.1, - 553.3, - 555, - 557.1, - 558.2, - 558.7, - 558.8, - 559.4, - 560.2, - 561, - 561.3, - 561.4, - 560.6, - 560.1, - 559.4, - 559, - 560.2, - 561.3, - 562.4, - 563.6, - 564.1, - 563.6, - 562.9, - 561.6, - 560.5, - 559.3, - 557.9, - 555.5, - 553.2, - 549.9, - 545.7, - 542.4, - 543.1, - 545.6, - 547.3, - 548.1, - 546.4, - 543.3, - 541.6, - 539.3, - 536.6, - 534.1, - 531.6, - 529.1, - 526.9, - 524.9, - 524.4, - 525.2, - 526.1, - 526.7, - 527.4, - 528.2, - 530.3, - 535, - 541.2, - 546.7, - 553.7, - 564.7, - 582.8, - 590, - 589.8, - 589.2, - 588.1, - 587.4, - 587, - 586.3, - 585.4, - 584.6, - 583.8, - 583.3, - 582.4, - 581.4, - 580.3, - 579.5, - 578.4, - 577.5, - 576.7, - 575.8, - 575.4, - 575.1, - 574.7, - 574.4, - 574.2, - 573.7, - 573.3, - 572.6, - 571.7, - 571.2, - 570.7, - 569.8, - 569.2, - 568.3, - 567.6, - 567.5, - 567.2, - 566.5, - 566, - 565.6, - 564.9, - 564.1, - 563.3, - 562.7, - 562.2, - 561.5, - 561.3, - 560.4, - 559.5, - 558.9, - 558.5, - 557.7, - 556.4, - 554.5, - 544.3, - 539.2, - 537.4, - 535.6, - 533.8, - 531.6, - 529.9, - 528.4, - 526.9, - 525, - 521.8, - 518.4, - 513.2, - 505.1, - 497.3, - 493.2, - 489, - 483.7, - 479.4, - 474.6, - 470.4, - 468.9, - 467.5, - 466.3, - 465.2, - 463.6, - 461.9, - 460.4, - 459.1, - 458.2, - 456.5, - 454.1, - 452, - 450.3, - 449.1, - 447.8, - 446.7, - 445.8, - 445.8, - 445.4, - 444.9, - 443, - 438.6, - 436, - 433.3, - 431.4, - 429.2, - 428.9, - 428.4, - 427.2, - 426.5, - 424.3, - 418.7, - 416.4, - 416.5, - 417.4, - 419.3, - 423.3, - 426.9, - 428.7, - 430.1, - 430.1, - 428.1, - 424.5, - 421.5, - 417, - 408.5, - 404.3, - 402.3, - 400.8, - 399.5, - 398.2, - 398.1, - 397.9, - 398, - 398.5, - 398.1, - 395.8, - 392.3, - 388.7, - 385.1, - 383.1, - 381.5, - 379.2, - 377, - 374.7, - 372.9, - 371.3, - 369.7, - 368.2, - 367.1, - 366.7, - 366.2, - 365.9, - 365.6, - 365.5, - 365.6, - 365.6, - 365.7, - 365.3, - 364.4, - 363.3, - 361.7, - 359.4, - 356.2, - 353.9, - 351.5, - 348.8, - 347.1, - 346.2, - 345.5, - 344.8, - 344, - 343.1, - 340.7, - 335, - 555.4, - 555.4, - 556.1, - 557.3, - 557.8, - 557.9, - 557.8, - 557.7, - 557.7, - 557.8, - 558, - 558.5, - 558.7, - 558.7, - 558.4, - 557.5, - 556.8, - 556.5, - 555.6, - 555.2, - 555.1, - 555, - 555.1, - 555.4, - 555.7, - 555.5, - 554.7, - 552.8, - 551.2, - 550.2, - 551.2, - 553, - 554.3, - 555.1, - 555.1, - 555.7, - 556.6, - 557.3, - 557.8, - 558, - 557.4, - 556.3, - 555.6, - 555.1, - 555.7, - 556.7, - 557.1, - 557.7, - 558.5, - 559.1, - 558.2, - 557.1, - 555.8, - 554.6, - 552.8, - 550.6, - 548.4, - 545.3, - 541.2, - 538, - 538, - 539.6, - 541.6, - 543.9, - 543, - 539.6, - 537.2, - 535.3, - 533.3, - 531.1, - 528.8, - 526.5, - 524.4, - 522.8, - 522.5, - 523.4, - 524.1, - 524.8, - 525.5, - 526.2, - 527.4, - 530.6, - 535.8, - 542.2, - 548.4, - 554, - 564.8, - 582.3, - 588.6, - 588.4, - 587.9, - 587.2, - 586.6, - 586.3, - 585.4, - 584.4, - 583.4, - 582.6, - 582.1, - 581.2, - 580.4, - 579.6, - 578.7, - 577.5, - 576.6, - 575.8, - 575, - 574.7, - 574.2, - 573.6, - 573.3, - 573, - 572.6, - 572.1, - 571.2, - 570.1, - 569.6, - 569.2, - 568.3, - 567.6, - 566.7, - 566.4, - 566.4, - 566.2, - 565.3, - 564.9, - 564.4, - 563.6, - 563.2, - 561.8, - 561.2, - 560.6, - 559.8, - 559, - 558.4, - 558, - 557.9, - 557.7, - 557, - 555.7, - 554, - 546, - 537.9, - 535.6, - 533.7, - 531.8, - 530.2, - 528.8, - 527.2, - 525.6, - 523.7, - 520.6, - 516.3, - 508.6, - 500.5, - 496.9, - 492.5, - 487.6, - 482.1, - 476.6, - 474.1, - 472.8, - 472.2, - 470.3, - 468, - 466.5, - 465.1, - 463.6, - 462.2, - 460.8, - 459.8, - 458.4, - 456.6, - 455.4, - 453.4, - 451.9, - 450.7, - 449.9, - 449.5, - 449, - 448.1, - 446.6, - 444.5, - 441.1, - 438.4, - 436.8, - 438.2, - 438.6, - 436, - 434.2, - 432.4, - 429.5, - 424.3, - 420.8, - 421.7, - 422.3, - 423, - 422.3, - 422.3, - 424.2, - 426.4, - 429.4, - 431.8, - 430.9, - 428.5, - 430.3, - 427.8, - 420.3, - 413.5, - 407.1, - 405.5, - 404.7, - 403.2, - 402.7, - 402.2, - 402.1, - 402.1, - 401, - 398.7, - 394, - 390.2, - 386.5, - 384.3, - 382.5, - 380.7, - 378.6, - 375.9, - 374.2, - 372.7, - 371.4, - 370.4, - 369.4, - 368.7, - 368.2, - 367.8, - 367.4, - 367.4, - 368, - 368.1, - 367.9, - 367.8, - 367, - 365.8, - 365.1, - 364.8, - 362.7, - 360, - 357.9, - 354.1, - 346.8, - 344.8, - 343.9, - 343, - 342.1, - 341.5, - 340.4, - 338.8, - 553.4, - 553.6, - 554.6, - 555.5, - 555.6, - 555.5, - 555.6, - 555.7, - 555.6, - 555.5, - 555.5, - 555.6, - 555.5, - 555, - 554.3, - 553.6, - 553.1, - 552.3, - 552, - 552.2, - 552, - 552, - 552.4, - 552.4, - 552.4, - 551.7, - 549.6, - 547.4, - 546.8, - 547.6, - 548.9, - 550.4, - 551.5, - 552.1, - 552.2, - 552.8, - 553.5, - 554.1, - 554.3, - 553.7, - 552.4, - 551.2, - 551.5, - 552.1, - 552.6, - 553.1, - 553.6, - 553.7, - 553.7, - 553.3, - 552.2, - 551.2, - 550, - 548, - 545.7, - 543, - 540, - 536.3, - 533.7, - 533.6, - 535.3, - 536.3, - 537.2, - 537.9, - 536.4, - 534.1, - 532.1, - 529.9, - 527.8, - 525.8, - 524.1, - 522.7, - 521.2, - 520.6, - 521.3, - 522.3, - 523, - 523.5, - 524.1, - 524.9, - 526.9, - 531.3, - 537.3, - 543, - 548.7, - 557.6, - 567.6, - 577.3, - 585.8, - 587.1, - 586.6, - 586.1, - 585.6, - 585.2, - 584.3, - 583.1, - 582.1, - 581.6, - 581, - 580.1, - 579.4, - 578.2, - 577.3, - 576.5, - 575.9, - 575.2, - 574.5, - 573.9, - 573.4, - 572.8, - 572.4, - 571.8, - 571.4, - 571, - 570.3, - 569, - 568.1, - 567.8, - 567.1, - 566.3, - 565.6, - 565.4, - 565.3, - 564.9, - 564.3, - 563.7, - 562.8, - 561.9, - 561.1, - 560.5, - 560.2, - 559.3, - 558.4, - 558, - 557.9, - 557.5, - 557.2, - 556.7, - 555.5, - 554.6, - 553.5, - 549.3, - 538.4, - 534.2, - 532.3, - 530.4, - 528.5, - 527, - 525.4, - 523.7, - 522, - 518.9, - 511.5, - 503.3, - 498.4, - 495.3, - 490.4, - 484.7, - 480.6, - 477.1, - 476.4, - 475.7, - 473.8, - 471.2, - 468.9, - 467.7, - 466.2, - 464.8, - 463.4, - 462.1, - 461.1, - 460.4, - 458.8, - 457.7, - 456.5, - 454.9, - 454, - 453.7, - 453.8, - 452.5, - 450.2, - 448.8, - 448.6, - 446.1, - 443.8, - 443.2, - 443.7, - 444.2, - 441.3, - 439.1, - 437.4, - 433.4, - 426.3, - 425.9, - 428.3, - 429, - 429.6, - 429.2, - 427.9, - 426.9, - 427.3, - 429, - 431.5, - 433.9, - 436.7, - 436.5, - 431.1, - 425.2, - 421.7, - 414.8, - 409.5, - 409.2, - 410.1, - 409.6, - 409.3, - 407.3, - 407, - 404.7, - 402.3, - 399.6, - 393.8, - 388.7, - 385.4, - 383.6, - 381.8, - 379.6, - 377.1, - 375.8, - 374.7, - 373.6, - 372.5, - 371.2, - 370.4, - 369.8, - 369.3, - 369, - 369.3, - 369.6, - 369.7, - 369.7, - 369.4, - 369, - 368.6, - 368.1, - 367.7, - 367.6, - 366.1, - 364.1, - 361.2, - 350.8, - 345.8, - 343.4, - 342.5, - 340.1, - 337.7, - 337.3, - 336.5, - 552, - 552.4, - 552.7, - 552.9, - 553.1, - 553.2, - 553.3, - 553, - 552.7, - 552.5, - 552.1, - 551.5, - 550.7, - 550, - 549.6, - 548.7, - 548.1, - 547.9, - 548.1, - 548.4, - 548.5, - 549.1, - 549.7, - 549.4, - 548.7, - 546.9, - 544.8, - 543.6, - 544.5, - 546, - 546.9, - 548.1, - 549, - 549.3, - 549.3, - 549.8, - 550.2, - 550.6, - 550.3, - 549.2, - 547.2, - 546.3, - 547.6, - 548.6, - 548.6, - 548.7, - 549.7, - 549.1, - 548.4, - 547.3, - 546.2, - 544.7, - 542.4, - 539.8, - 536.9, - 533.9, - 530.9, - 528.5, - 528.5, - 529.9, - 531.6, - 532.4, - 532.6, - 532.1, - 530.8, - 528.9, - 526.5, - 524.4, - 522.9, - 521.7, - 520.7, - 519.4, - 518.7, - 519.4, - 520.3, - 521.1, - 521.6, - 522.2, - 523.1, - 524.3, - 527.3, - 532, - 538, - 543.8, - 551.6, - 562.7, - 569.7, - 577.2, - 584.4, - 585.9, - 585.5, - 585.1, - 584.4, - 583.5, - 582.7, - 581.9, - 580.9, - 580.1, - 579.6, - 578.9, - 578.2, - 576.9, - 575.9, - 575.6, - 575.1, - 574.3, - 573.6, - 573, - 572.4, - 571.9, - 571.4, - 570.6, - 570, - 569.6, - 568.4, - 567.3, - 567.1, - 566.7, - 566, - 565.3, - 564.6, - 564.4, - 564, - 563.6, - 563, - 562.2, - 561, - 560.7, - 560.3, - 559.9, - 559.1, - 558.4, - 557.7, - 557.4, - 556.9, - 556.4, - 555.8, - 554.6, - 554.2, - 554, - 553.3, - 552, - 544, - 534.4, - 530.7, - 528.7, - 526.7, - 525.1, - 523.6, - 521.8, - 519.3, - 513.6, - 506.2, - 502.1, - 497.1, - 492.2, - 486.8, - 482.9, - 480.9, - 479.6, - 478.1, - 476.5, - 474.4, - 472.1, - 470.7, - 469.4, - 467.5, - 466, - 464.7, - 463.6, - 463.4, - 462.9, - 461.2, - 459.4, - 458.7, - 458.2, - 457.5, - 457.6, - 457.7, - 455.6, - 453.2, - 454.5, - 456.9, - 454.9, - 450.8, - 448.4, - 447.1, - 445.6, - 444.1, - 442.7, - 440.3, - 436.4, - 430.8, - 432.3, - 433.1, - 433.3, - 433.9, - 434.3, - 435.2, - 436, - 435.1, - 434, - 433.5, - 434.2, - 436.7, - 437.8, - 436.2, - 430.9, - 424.6, - 420.4, - 415.1, - 415.7, - 417.9, - 416.8, - 417.1, - 414.9, - 413.6, - 411.1, - 409.1, - 407.9, - 401.9, - 395, - 388.5, - 385, - 383.3, - 380.6, - 378.5, - 377.5, - 376.5, - 375.3, - 374.4, - 373.6, - 373.1, - 371.9, - 371.3, - 371.1, - 371.4, - 371.8, - 372.1, - 372.3, - 371.5, - 371, - 370.7, - 370.5, - 370.4, - 370, - 369.6, - 368.5, - 366.3, - 360.7, - 350.1, - 343.2, - 341.5, - 337, - 332.9, - 332.1, - 331, - 550.4, - 550.6, - 550.7, - 550.8, - 550.8, - 550.6, - 550.2, - 549.7, - 549.2, - 548.2, - 547.2, - 546.4, - 545.7, - 544.5, - 544.6, - 544.1, - 544.4, - 544.9, - 545, - 544.9, - 545.4, - 546, - 546.2, - 545.8, - 544.7, - 542.6, - 540.9, - 540.9, - 542.6, - 544.1, - 544.9, - 545.9, - 546.7, - 546.7, - 546.4, - 546.5, - 546.4, - 546.3, - 545.7, - 544.1, - 541.9, - 542.2, - 543.1, - 544, - 544.3, - 544, - 544, - 543.4, - 542.4, - 541.3, - 539.6, - 537.3, - 534.3, - 531.4, - 528.7, - 526.2, - 524, - 523.1, - 524.3, - 525.8, - 527, - 527.2, - 527.2, - 526.5, - 525, - 522.7, - 521.1, - 520.1, - 518.9, - 517.8, - 516.7, - 516.4, - 517.4, - 518.3, - 518.9, - 519.5, - 520.3, - 521.2, - 522.6, - 524.8, - 527.7, - 531.7, - 537, - 546.1, - 556.5, - 563.6, - 571.1, - 579.2, - 584.3, - 584.7, - 584.4, - 584, - 583.3, - 582.5, - 581.5, - 580.6, - 579.5, - 578.8, - 578.3, - 577.8, - 576.6, - 575.5, - 574.9, - 574.5, - 573.9, - 573.2, - 572.5, - 571.8, - 571.2, - 570.6, - 570.1, - 569.5, - 568.8, - 568.3, - 567.2, - 566.2, - 566.1, - 565.5, - 565, - 564, - 563.5, - 563.1, - 562.5, - 561.7, - 561.2, - 560.8, - 560, - 559.7, - 559.2, - 558.7, - 558, - 557.7, - 557.1, - 556.4, - 555.8, - 555.1, - 554.2, - 553.6, - 553.7, - 553.1, - 552.3, - 551.9, - 549.6, - 540.7, - 530.1, - 526.8, - 524.7, - 522.9, - 521.1, - 519.1, - 515.2, - 508.8, - 506, - 502.1, - 496.3, - 489.8, - 487.1, - 486.4, - 483.9, - 481.3, - 478.9, - 477.1, - 475.6, - 473.9, - 472.3, - 470.6, - 468.7, - 467.2, - 466.2, - 465.6, - 465.2, - 464.1, - 462.2, - 461.5, - 462.1, - 462.3, - 461.5, - 460.9, - 460.3, - 460.4, - 461.7, - 464.3, - 465, - 461.6, - 456.1, - 452.9, - 450.2, - 447.7, - 446, - 444.1, - 441.1, - 438.1, - 437.1, - 437.9, - 437.8, - 437.9, - 438.6, - 438.5, - 440.3, - 441.5, - 442.1, - 442.7, - 441.7, - 438.9, - 437.4, - 437.5, - 437.6, - 436.3, - 432, - 426.2, - 421.4, - 420.9, - 422, - 422.6, - 421.4, - 419.4, - 417.6, - 417.5, - 418.5, - 416, - 410.1, - 403.7, - 395.6, - 389.6, - 387.3, - 385.5, - 382.9, - 379.6, - 378, - 376.9, - 376.3, - 376.4, - 375.9, - 375.2, - 374.7, - 374, - 374.1, - 374.9, - 375.1, - 375.1, - 374.4, - 373.9, - 373.7, - 373.7, - 373.3, - 372.7, - 373.5, - 374.5, - 376.8, - 380.1, - 378.2, - 366, - 353.1, - 344.2, - 338, - 335, - 332.9, - 548.5, - 548.5, - 548.5, - 548.5, - 548.3, - 547.1, - 546.2, - 545.1, - 544.1, - 543.2, - 542.8, - 542.2, - 541.5, - 541.2, - 541.5, - 541.4, - 541.9, - 542.3, - 542.4, - 542.1, - 542.3, - 542.6, - 542.8, - 542.1, - 540.7, - 538.9, - 538.1, - 538.9, - 540.6, - 541.6, - 542.5, - 543.3, - 543.6, - 543.2, - 542.8, - 542.5, - 542.1, - 541.6, - 540.8, - 539.1, - 538.3, - 538.8, - 539.1, - 539.3, - 539.5, - 538.9, - 538, - 537, - 535.9, - 534.6, - 532.6, - 529.9, - 527.2, - 524.6, - 522.2, - 520.2, - 518.6, - 519.5, - 520.6, - 521.6, - 522.1, - 521.7, - 520.7, - 519.7, - 518.8, - 517.6, - 516.9, - 516, - 515.1, - 514, - 514.3, - 515.6, - 516.3, - 516.9, - 517.6, - 518.4, - 519.2, - 520.2, - 522.3, - 524.9, - 528.4, - 533.4, - 542, - 551.3, - 558.6, - 562.2, - 569.7, - 580.6, - 584.2, - 584, - 583.4, - 583, - 582.5, - 581.7, - 580.7, - 579.5, - 578.8, - 578.1, - 577.3, - 576.3, - 574.9, - 574.4, - 573.9, - 573.4, - 572.7, - 571.8, - 571, - 570.5, - 570.1, - 569.5, - 568.9, - 568.4, - 568, - 567.4, - 566.4, - 565.5, - 565, - 564.1, - 563.4, - 562.6, - 562.1, - 561.7, - 561.2, - 560.7, - 560, - 559.5, - 558.7, - 558, - 557.6, - 557.4, - 557.2, - 557, - 555.9, - 555, - 554.4, - 553.7, - 552.5, - 552.1, - 552.1, - 551.6, - 551.1, - 550.7, - 549.2, - 546.2, - 536.6, - 527.3, - 522.9, - 520.7, - 518.9, - 516.7, - 512.4, - 508.5, - 506, - 501.2, - 494.6, - 491.6, - 490.3, - 489.1, - 487.1, - 484.6, - 481.9, - 482.4, - 479.5, - 475.9, - 476.6, - 474.8, - 470.7, - 468.8, - 467.6, - 466.9, - 466.4, - 465.1, - 465, - 465.3, - 466.1, - 465.8, - 464.5, - 463.9, - 464.7, - 465.7, - 467.1, - 469.1, - 469.5, - 467.5, - 463.1, - 458.2, - 454.3, - 451.7, - 450, - 447.8, - 444.8, - 443.4, - 442.6, - 442.2, - 441.9, - 441.7, - 442.6, - 443, - 444.6, - 445.9, - 446.4, - 446.9, - 446.1, - 444, - 441.7, - 439.4, - 438.3, - 437.9, - 436.9, - 433.3, - 429.1, - 427.9, - 428.3, - 428.7, - 427.2, - 425.4, - 424.5, - 423.3, - 422.9, - 421.8, - 416.6, - 411.8, - 403.6, - 396.6, - 392.4, - 390.2, - 389.2, - 386.8, - 383.7, - 383.6, - 383.1, - 382.4, - 380.4, - 377.8, - 376.8, - 376.4, - 377.2, - 378, - 378.6, - 379, - 378.6, - 378.2, - 377.7, - 377.2, - 376.6, - 376.8, - 377.2, - 378.9, - 382.7, - 386.2, - 388.4, - 388.2, - 382.5, - 374, - 365.9, - 360.2, - 354.2, - 546.4, - 546.1, - 546.1, - 546.4, - 545.6, - 542.9, - 542, - 541.2, - 540.4, - 539.8, - 539.6, - 539.5, - 539.2, - 539, - 539, - 539.3, - 539.4, - 539.5, - 539.7, - 539.9, - 540.1, - 540, - 539.4, - 538, - 536.7, - 535.7, - 536.1, - 537.3, - 538.5, - 539.1, - 539.7, - 540.2, - 539.9, - 539.5, - 539.1, - 538.5, - 537.9, - 537.3, - 536.5, - 535.3, - 535.7, - 535.8, - 535.2, - 534.8, - 534.2, - 533.4, - 532, - 530.4, - 528.9, - 527, - 525.1, - 523, - 520.5, - 518.6, - 516.7, - 514.7, - 514.1, - 515.4, - 516.2, - 516.6, - 516.4, - 515.4, - 514.4, - 514.2, - 513.9, - 513.3, - 512.8, - 512, - 511.2, - 512.2, - 513.8, - 514.6, - 515.2, - 515.7, - 516.5, - 517.2, - 517.9, - 519.1, - 522, - 525.5, - 530.9, - 538.6, - 546.5, - 554.1, - 559.3, - 561.1, - 564.1, - 575.4, - 582.7, - 582.8, - 582, - 581.9, - 581.6, - 580.6, - 579.8, - 578.9, - 577.9, - 576.8, - 576, - 575.1, - 574.2, - 573.7, - 573.1, - 572.1, - 571.4, - 570.6, - 570.2, - 569.6, - 569, - 568.5, - 567.9, - 567.2, - 566.9, - 566.3, - 565.4, - 564.7, - 563.8, - 562.9, - 562.3, - 561.7, - 561, - 560.6, - 560.1, - 559.4, - 558.7, - 558, - 557.2, - 556.7, - 556.3, - 556.6, - 556.8, - 556.4, - 554.5, - 552.8, - 551.9, - 551.4, - 550.9, - 550.9, - 551, - 550.2, - 549.9, - 549.6, - 548.3, - 547.3, - 543.8, - 534, - 525.1, - 519.5, - 516.2, - 513.4, - 510.2, - 507.6, - 502.3, - 498.2, - 494.5, - 492.7, - 491.4, - 490.5, - 489.7, - 489.1, - 492.1, - 495.9, - 491.7, - 487, - 484.7, - 481.9, - 476.8, - 472.8, - 472.3, - 470.2, - 470.9, - 469.8, - 470.1, - 469.6, - 469.3, - 469.1, - 468.4, - 469, - 469.8, - 469.9, - 470, - 471.9, - 472.6, - 472.2, - 470, - 465.9, - 461.5, - 458.3, - 456.3, - 453.6, - 450.3, - 450.4, - 448, - 445, - 445.3, - 446.4, - 447.8, - 448, - 448.7, - 450, - 450.9, - 450.8, - 449.2, - 446.7, - 444.5, - 442.6, - 440.5, - 439, - 438.7, - 438, - 434.8, - 435, - 436.6, - 436.2, - 434.7, - 434.3, - 432.8, - 429.2, - 427.7, - 425.5, - 421.6, - 416.1, - 409.7, - 403.4, - 400.6, - 397.8, - 394.5, - 391.4, - 390, - 389.6, - 390.3, - 388.5, - 386.8, - 384.3, - 381.7, - 379.9, - 380.6, - 381.3, - 381.8, - 382, - 382, - 382, - 381.2, - 380.5, - 380, - 379.9, - 380.8, - 383.1, - 385.4, - 386.8, - 388.6, - 389.3, - 388, - 384.6, - 377.5, - 370.2, - 365.6, - 544.1, - 544.1, - 544.1, - 543.6, - 541.9, - 540, - 539, - 538.3, - 537.9, - 537.5, - 537.2, - 537.2, - 537.1, - 537, - 537, - 537.1, - 537.1, - 537.5, - 537.7, - 537.7, - 537.5, - 536.8, - 535.6, - 534.2, - 533.1, - 533.1, - 534.1, - 535.3, - 536.1, - 536.5, - 536.8, - 536.9, - 536.3, - 535.7, - 535.2, - 534.3, - 533.5, - 532.8, - 531.9, - 531.5, - 531.3, - 531, - 530.3, - 529.6, - 528.9, - 527.7, - 526.2, - 524.4, - 522.3, - 520.2, - 518.2, - 516.4, - 514.7, - 513.2, - 511.5, - 510, - 509.9, - 511.2, - 511.6, - 511.7, - 511.3, - 510.5, - 509.8, - 509.5, - 509.6, - 509.3, - 508.6, - 508.1, - 509.3, - 511.5, - 512.8, - 513.3, - 513.9, - 514.5, - 515.3, - 516, - 516.6, - 517.8, - 520.9, - 526, - 533.1, - 540.5, - 548.9, - 556.1, - 559.6, - 560.8, - 562.5, - 568, - 578.8, - 581.3, - 580.7, - 580.4, - 579.7, - 579.1, - 578.2, - 577.7, - 576.7, - 575.9, - 574.9, - 574, - 573, - 572.9, - 572.7, - 571.4, - 570.5, - 569.8, - 569.4, - 568.7, - 568, - 567.4, - 566.8, - 566.1, - 565.7, - 565.2, - 564.4, - 563.5, - 562.7, - 562, - 561.5, - 560.8, - 560.2, - 559.5, - 559, - 558.1, - 557.4, - 556.9, - 556.3, - 555.8, - 555.1, - 554.9, - 554.4, - 553.8, - 552.4, - 551.2, - 550.8, - 550.2, - 549.9, - 550, - 549.9, - 549.5, - 548.9, - 548.1, - 547.4, - 546.1, - 543.8, - 538.5, - 531.9, - 523.1, - 513.8, - 510.3, - 508.2, - 505.1, - 501.2, - 498.6, - 496.1, - 494.1, - 494.1, - 497.9, - 499.4, - 497.7, - 500.1, - 501.9, - 500.7, - 496.7, - 492.2, - 487.8, - 487.3, - 482, - 479.6, - 477.5, - 476.4, - 474.1, - 474, - 473.6, - 473.5, - 473.4, - 472.7, - 472.8, - 473.2, - 473.9, - 475.2, - 477, - 478.2, - 478, - 475.9, - 473.3, - 470.4, - 466.9, - 462.4, - 460, - 460.8, - 461.8, - 460, - 455.4, - 454.4, - 456.7, - 456.5, - 455.4, - 455.2, - 456.1, - 455.9, - 454.7, - 452.9, - 450.5, - 447.5, - 445.2, - 443.2, - 441.4, - 440.4, - 440.5, - 440.1, - 442, - 444.4, - 444.4, - 443.7, - 442.8, - 440.3, - 436.7, - 435, - 431.1, - 426.3, - 423.4, - 417.8, - 411, - 405, - 402.6, - 400.4, - 396.7, - 393.9, - 394.7, - 397.2, - 394.2, - 391.3, - 392.1, - 391.2, - 386.9, - 386.2, - 388.3, - 389.1, - 387.9, - 387.5, - 388.3, - 384.9, - 383.3, - 383.2, - 383.2, - 384.4, - 386.4, - 387, - 387.6, - 387.7, - 387.3, - 387.1, - 386.1, - 383.8, - 379.4, - 373.4, - 542.2, - 541.8, - 541.3, - 540.2, - 538.7, - 537.7, - 536.5, - 535.9, - 535.4, - 535, - 534.8, - 534.8, - 534.7, - 535, - 535.1, - 534.8, - 535.1, - 535.5, - 535.5, - 535.2, - 534.4, - 533.3, - 531.7, - 530.6, - 530.5, - 531.4, - 532.1, - 533, - 533.5, - 533.5, - 533.5, - 533.3, - 532.6, - 532, - 531.1, - 529.9, - 528.8, - 528, - 527.2, - 526.6, - 526, - 525.7, - 525.2, - 524.4, - 523.5, - 522, - 520, - 518.2, - 516.4, - 514.2, - 512.6, - 511.2, - 509.5, - 508.6, - 507.2, - 506.2, - 506.6, - 507.1, - 507.1, - 507, - 506.8, - 506.2, - 505.7, - 505.5, - 505.5, - 505.4, - 505.7, - 506.8, - 508.6, - 510.1, - 511.2, - 511.8, - 512.5, - 513.2, - 514.1, - 514.8, - 515.4, - 516.4, - 518.9, - 523.7, - 529.9, - 537.3, - 546.9, - 554.9, - 559, - 560.3, - 561, - 565.8, - 577, - 579.6, - 579.3, - 578.9, - 578.2, - 577.7, - 577, - 576.5, - 576.1, - 575.1, - 573.9, - 573.1, - 572.1, - 571.7, - 571.2, - 570.2, - 569.7, - 568.8, - 568.2, - 567.3, - 566.8, - 566.2, - 565.4, - 565.1, - 564.7, - 564.3, - 563.5, - 562.5, - 561.6, - 560.9, - 560, - 559.3, - 558.9, - 558.5, - 557.9, - 557.2, - 556.6, - 556, - 555.5, - 554.8, - 553.8, - 552.9, - 552.4, - 551.9, - 551.2, - 550.5, - 550.3, - 549.6, - 549, - 548.9, - 548.7, - 548.1, - 547.4, - 547, - 546.4, - 544.8, - 542.2, - 537, - 530, - 519.3, - 513.3, - 509.7, - 506.5, - 504.3, - 503.1, - 502.5, - 504.2, - 502.4, - 508.1, - 514.1, - 510.8, - 509.2, - 509.3, - 506.8, - 507, - 503.2, - 499.1, - 501.6, - 499.6, - 492.5, - 487.2, - 483.6, - 480.6, - 478.7, - 477.7, - 477.5, - 477.4, - 477.6, - 477.3, - 477, - 477.3, - 477.8, - 478.8, - 480.3, - 482.6, - 483.8, - 481.2, - 478.4, - 475.8, - 471.6, - 469.8, - 470.1, - 471, - 471.2, - 470.1, - 468.2, - 466.2, - 467.9, - 469.6, - 468.4, - 466.8, - 465.5, - 463.2, - 460, - 456.7, - 454.3, - 451.8, - 449.8, - 447.6, - 445.7, - 445, - 444.7, - 445, - 445.9, - 447.3, - 448.4, - 448.6, - 447.6, - 445.4, - 442.6, - 440.5, - 437.2, - 432.4, - 431.8, - 429.2, - 420.7, - 412.7, - 407.9, - 404.8, - 402, - 400, - 404.1, - 406.6, - 404, - 401.8, - 401.6, - 399.1, - 394.8, - 392.3, - 392.5, - 394.4, - 396.9, - 397, - 396.1, - 392.6, - 388.1, - 387.3, - 386.7, - 386.8, - 387.5, - 387.6, - 387.4, - 386.4, - 385.4, - 384.7, - 384, - 383.8, - 382.1, - 379.1, - 540, - 538.9, - 537.5, - 537, - 536.8, - 535.7, - 534, - 533.3, - 533.2, - 532.6, - 532.1, - 532.3, - 532.2, - 532.2, - 532.2, - 532.3, - 533.1, - 533.2, - 532.9, - 532.1, - 531.1, - 529.7, - 528.6, - 528.3, - 529, - 529.8, - 530.1, - 530.4, - 530.6, - 530.4, - 530.2, - 529.6, - 528.9, - 528.1, - 527.2, - 526, - 524.8, - 524.1, - 523.2, - 522.3, - 521.6, - 521.2, - 520.5, - 519.5, - 518, - 516.1, - 514.4, - 512.8, - 511.1, - 509.5, - 508.6, - 507.2, - 505.6, - 504.5, - 503.5, - 503.3, - 503.3, - 502.9, - 502.4, - 502.5, - 502.5, - 502.4, - 502.3, - 502.6, - 503, - 503.8, - 504.8, - 506.3, - 507.8, - 508.6, - 509.7, - 510.5, - 511.3, - 512.1, - 512.9, - 513.7, - 514.7, - 516.7, - 521.2, - 527, - 533.5, - 539.5, - 541.8, - 544.6, - 546.9, - 547.1, - 547.4, - 556.3, - 565.8, - 576.7, - 577.9, - 577.8, - 577.3, - 576.4, - 575.8, - 575, - 574.7, - 573.7, - 572.7, - 571.6, - 570.8, - 570.1, - 569.6, - 569, - 568.4, - 567.9, - 566.9, - 565.9, - 565.4, - 564.9, - 564.3, - 563.9, - 563.6, - 563.3, - 562.4, - 561.5, - 560.4, - 559.4, - 558.7, - 558.2, - 557.9, - 557.6, - 557, - 556.4, - 555.7, - 554.9, - 554.4, - 553.9, - 553.1, - 552.4, - 551.9, - 551.4, - 550.7, - 549.9, - 549.4, - 548.5, - 548, - 547.6, - 547.1, - 546.8, - 546.2, - 545.7, - 545.1, - 543.7, - 540.1, - 533.8, - 523.3, - 516.9, - 513.8, - 510.3, - 507.3, - 506.2, - 510.1, - 519.8, - 523.2, - 520, - 522, - 525.5, - 522.6, - 520.6, - 519.4, - 514.8, - 511, - 510.5, - 509.9, - 509.1, - 507.3, - 507.6, - 503.5, - 498.3, - 491.6, - 486.3, - 483.2, - 481.6, - 481.6, - 481.8, - 481.8, - 481.7, - 481.5, - 482, - 483, - 485.9, - 488.4, - 490.2, - 490.4, - 486.6, - 481.4, - 479.7, - 478.6, - 478, - 478.5, - 478.2, - 478, - 477.4, - 476.1, - 475.2, - 476.6, - 477.7, - 476.6, - 475, - 471.9, - 466.5, - 462.1, - 460.4, - 458.2, - 456.9, - 455.5, - 452.3, - 450.4, - 450.4, - 451.2, - 451.2, - 450.9, - 450.6, - 450.6, - 450.3, - 449, - 447.3, - 445.4, - 443.9, - 440.9, - 438.6, - 435.9, - 430.2, - 422.4, - 416, - 410.3, - 406.1, - 406.7, - 410, - 411.2, - 410.3, - 407, - 405.1, - 402.7, - 400, - 398, - 398.3, - 399, - 400.5, - 399.8, - 398.8, - 396.8, - 394, - 392.1, - 390.8, - 390.4, - 389.4, - 387.5, - 385.9, - 384.7, - 383.5, - 382.3, - 381.3, - 380.3, - 378.5, - 376.5, - 536.9, - 535.7, - 534.8, - 534.9, - 534.4, - 532.7, - 531.4, - 530.7, - 530.8, - 529.8, - 529.4, - 529.8, - 529.6, - 529.4, - 529.2, - 529.8, - 530.4, - 530.2, - 529.5, - 528.9, - 527.7, - 526.6, - 526.1, - 526.7, - 527.4, - 527.6, - 527.7, - 527.7, - 527.5, - 527.2, - 526.8, - 526.2, - 525.6, - 524.3, - 523.2, - 522.1, - 520.9, - 519.4, - 518.4, - 518, - 517.9, - 516.9, - 515.2, - 513.3, - 512.1, - 510.6, - 509.1, - 507.9, - 506.6, - 505.4, - 504.3, - 503.2, - 502.1, - 501.4, - 501, - 500.7, - 500.4, - 500.1, - 500, - 500.2, - 500.2, - 500.1, - 500.6, - 501.2, - 502, - 502.9, - 503.8, - 505.2, - 506.4, - 507.5, - 508.5, - 509.4, - 510.2, - 510.9, - 511.7, - 512.7, - 514.8, - 518.7, - 523.6, - 527.5, - 531.9, - 535.2, - 534.2, - 533.7, - 535.7, - 539.3, - 547.3, - 554.4, - 559.4, - 573.6, - 576.5, - 576.8, - 576.1, - 575, - 574.7, - 573.9, - 573.6, - 572.5, - 571.3, - 570.5, - 569.7, - 568.9, - 568.3, - 567.4, - 567, - 566.6, - 565.9, - 564.7, - 564.1, - 563.5, - 562.9, - 562.6, - 562.5, - 562.3, - 561.5, - 560.6, - 559.3, - 558.3, - 557.7, - 557.1, - 556.7, - 556.5, - 556.1, - 555.4, - 554.6, - 553.9, - 553.1, - 552.8, - 552.3, - 551.7, - 551.1, - 550.5, - 549.8, - 549.2, - 548, - 547.4, - 547, - 546.6, - 546.3, - 545.8, - 545.2, - 544.5, - 543.6, - 541.9, - 536.2, - 530.3, - 526.8, - 524.3, - 516.9, - 511.9, - 511.3, - 519.7, - 530.7, - 533.8, - 532.6, - 530.4, - 528.2, - 530.5, - 529.8, - 527.7, - 525.7, - 521.2, - 516.4, - 516.2, - 515.7, - 515.4, - 516.7, - 517.7, - 516.7, - 516.2, - 512, - 505, - 498.7, - 492.5, - 489, - 488, - 488.4, - 489.6, - 489.9, - 490.7, - 491.7, - 495.7, - 497.2, - 497.5, - 498, - 497.7, - 495.1, - 495.6, - 495.3, - 492.2, - 489.2, - 486.9, - 485.7, - 484.4, - 483.3, - 482, - 481.1, - 480.5, - 480.4, - 479.8, - 477.9, - 474.7, - 471.9, - 472, - 471.7, - 470.5, - 467.4, - 462.4, - 457.9, - 455.9, - 456.3, - 455.4, - 455.5, - 455.4, - 454.2, - 453.4, - 452.2, - 452, - 452.5, - 454.6, - 454.6, - 451.2, - 444.5, - 440.5, - 433.3, - 424.8, - 418.9, - 412.6, - 412.4, - 414, - 414.3, - 414.3, - 412.1, - 409.2, - 406.4, - 405.6, - 405.3, - 404.8, - 404.6, - 405.1, - 404, - 402.6, - 399.7, - 398.7, - 397.7, - 397, - 396.6, - 392.8, - 388.1, - 385.1, - 383.4, - 381.6, - 380.2, - 379, - 377.1, - 374.5, - 371.9, - 534.8, - 534.2, - 533.4, - 532.8, - 531.5, - 530.4, - 529.8, - 528.9, - 527.8, - 526.9, - 526.7, - 526.8, - 526.9, - 526.9, - 527.1, - 527.5, - 527.6, - 527, - 526.5, - 525.5, - 524.4, - 523.8, - 524.2, - 524.9, - 525.3, - 525.6, - 525.4, - 525.2, - 525, - 524.6, - 523.8, - 522.9, - 521.8, - 520.5, - 519.5, - 518.4, - 517.1, - 515.6, - 514.8, - 514.3, - 513.5, - 511.5, - 508.7, - 506.7, - 506.3, - 505.4, - 504.7, - 503.6, - 502.8, - 501.8, - 500.8, - 500.3, - 500, - 499.5, - 499.1, - 498.8, - 498.6, - 498.3, - 498.3, - 498.4, - 498.4, - 498.7, - 499.4, - 500.1, - 500.8, - 501.7, - 502.8, - 503.9, - 505.2, - 506.5, - 507.3, - 508.1, - 508.9, - 509.7, - 510.8, - 512.5, - 515.4, - 518.9, - 522, - 524.3, - 526.8, - 527.3, - 527.3, - 533.2, - 545.5, - 548.6, - 554, - 556.1, - 559.6, - 571.1, - 573.9, - 574.2, - 573.9, - 573.1, - 572.9, - 572.3, - 571.8, - 571, - 570.1, - 569.6, - 569, - 567.9, - 567.3, - 566.4, - 565.7, - 565.3, - 564.7, - 563.3, - 562.5, - 562, - 561.6, - 561.4, - 561.3, - 561.1, - 560.6, - 559.2, - 557.7, - 556.7, - 556.4, - 556, - 555.7, - 555.4, - 554.5, - 553.9, - 553.6, - 553.3, - 552.3, - 551.7, - 551.3, - 551, - 550.4, - 549.4, - 548.9, - 548.2, - 547.4, - 546.9, - 546.5, - 546.1, - 545.7, - 544.9, - 544.2, - 543.5, - 542.4, - 540, - 533.4, - 536.9, - 536.4, - 531.6, - 519.6, - 518, - 524.9, - 533.9, - 536, - 535.5, - 535, - 534.4, - 532.8, - 531.8, - 531.2, - 529.5, - 527.2, - 523.4, - 521.7, - 521, - 521.9, - 521.7, - 521.9, - 522.3, - 522.4, - 522.9, - 523.6, - 520.4, - 517.4, - 513.1, - 506.9, - 504.2, - 504.5, - 504.1, - 503.1, - 502.2, - 503.7, - 505.3, - 504.7, - 503.9, - 504.2, - 506.8, - 510.4, - 509.4, - 507.2, - 502.8, - 499.2, - 497, - 494.7, - 492.6, - 490.8, - 488.8, - 486.4, - 483.3, - 482.8, - 483, - 482.8, - 483.2, - 484.2, - 485.8, - 488.5, - 488.7, - 481.6, - 472.3, - 465.6, - 461, - 460.7, - 460.6, - 459.6, - 458.9, - 457.7, - 457.5, - 456.6, - 456.4, - 456.9, - 457.4, - 458.6, - 458.3, - 455.6, - 451.2, - 448.3, - 440.6, - 430.4, - 421.3, - 417.9, - 419, - 418.4, - 417.7, - 417.2, - 415.8, - 411.6, - 409.7, - 410.5, - 410.6, - 410.8, - 411.7, - 411.2, - 408.7, - 404.3, - 402.8, - 401.7, - 401.5, - 401.7, - 398.6, - 391.3, - 385, - 382.4, - 379.8, - 377.4, - 375, - 373.3, - 371.9, - 370, - 533.6, - 532.9, - 531.9, - 530.5, - 529.1, - 528.7, - 528.5, - 527.3, - 526.5, - 525.6, - 524.9, - 524.5, - 524.7, - 525.1, - 525.3, - 525.3, - 525, - 524.6, - 524, - 522.7, - 521.9, - 521.9, - 522.4, - 522.8, - 523.4, - 523.7, - 523.1, - 522.8, - 522.5, - 521.7, - 520.8, - 519.6, - 518, - 517, - 516.1, - 515.1, - 513.7, - 512.6, - 511.6, - 510.3, - 508.9, - 506.8, - 504.7, - 503, - 501.7, - 500.6, - 500.2, - 499.8, - 499.4, - 498.5, - 498, - 497.9, - 497.7, - 497.4, - 497.2, - 497, - 497, - 496.8, - 496.7, - 496.6, - 496.9, - 497.3, - 498, - 498.9, - 499.6, - 500.4, - 501.5, - 502.8, - 504.1, - 505.3, - 506.1, - 507, - 507.8, - 508.6, - 509.9, - 512.2, - 514.8, - 517.6, - 520.2, - 520.9, - 521.3, - 522.7, - 526.6, - 539.5, - 549.8, - 553.6, - 557.3, - 560.5, - 564.7, - 568.8, - 571.2, - 572.2, - 572.1, - 571.8, - 571.4, - 571.5, - 570.8, - 570.2, - 569.3, - 568.5, - 567.8, - 567.3, - 566.7, - 565.6, - 564.7, - 564.1, - 563.2, - 562.3, - 561.7, - 561, - 560.6, - 560.4, - 560.2, - 559.9, - 559, - 557.5, - 556, - 555.3, - 555.2, - 555, - 554.6, - 554.2, - 553.4, - 553, - 552.7, - 552.4, - 551.5, - 550.7, - 550.1, - 549.9, - 549.5, - 548.4, - 547.9, - 547.4, - 546.8, - 546.3, - 545.9, - 545.5, - 544.9, - 544, - 543.1, - 542.1, - 540.9, - 539.9, - 538.2, - 538.9, - 537.4, - 529.3, - 524.8, - 530.4, - 534.9, - 535.6, - 535.1, - 534.5, - 534.3, - 533.7, - 532.7, - 531.8, - 530.8, - 529, - 526.6, - 525.5, - 526, - 527, - 527.4, - 526.5, - 525.7, - 526.5, - 525.9, - 525.9, - 526.2, - 525.5, - 524.3, - 522.3, - 519.7, - 517.6, - 516.2, - 514.5, - 513.2, - 512.5, - 513.8, - 512.9, - 511.9, - 510.8, - 511.5, - 517, - 518, - 516.3, - 515.2, - 512.1, - 509.8, - 505.9, - 503.4, - 500.7, - 497.7, - 494.4, - 492.2, - 489.8, - 488.7, - 489, - 489.5, - 490.9, - 493.2, - 494.2, - 495.8, - 497.1, - 494.7, - 485.4, - 475.3, - 469.2, - 465.7, - 466.3, - 464.2, - 463.3, - 462.6, - 462, - 462.3, - 462.9, - 462.9, - 462.1, - 463, - 461.5, - 459.8, - 458.6, - 458.5, - 455.2, - 448.5, - 437.8, - 428.5, - 427.8, - 427.3, - 423.5, - 422, - 420.8, - 418.4, - 415.2, - 416, - 416.5, - 416.8, - 416.5, - 415.1, - 412.1, - 408.2, - 407.1, - 406.5, - 405.6, - 407, - 405.3, - 398.9, - 388.7, - 383.5, - 380.3, - 377, - 373, - 371, - 369.5, - 367.7, - 532.3, - 531.3, - 529.9, - 528.4, - 527, - 526.9, - 526.7, - 525.7, - 525.1, - 524.4, - 523.5, - 522.8, - 522.9, - 523, - 523.1, - 523, - 522.4, - 521.8, - 520.6, - 520, - 520.3, - 520.6, - 520.7, - 521, - 521.5, - 521.3, - 520.7, - 520.2, - 519.8, - 519.2, - 518.4, - 517, - 515.1, - 514.3, - 513.5, - 512.1, - 510.4, - 509.4, - 508, - 506.4, - 505.2, - 504.1, - 502.7, - 501.2, - 499.7, - 498.6, - 498.1, - 497.6, - 496.9, - 496.4, - 496.1, - 495.9, - 495.8, - 495.6, - 495.4, - 495.4, - 495.1, - 495, - 495.3, - 495.4, - 495.7, - 495.9, - 496.7, - 497.6, - 498.7, - 499.6, - 500.7, - 501.9, - 503.1, - 504.2, - 505.1, - 505.9, - 506.7, - 507.3, - 508.6, - 510.5, - 512, - 513.4, - 515.6, - 518.1, - 519.5, - 522.5, - 530.4, - 547.2, - 553.6, - 557, - 561.6, - 565.6, - 567.5, - 567.5, - 568.1, - 569.2, - 569.5, - 568.9, - 568.7, - 569.3, - 569.4, - 569.2, - 568.5, - 567.9, - 567, - 566.4, - 565.7, - 564.9, - 564, - 563.2, - 562.1, - 561.2, - 560.6, - 559.8, - 559.5, - 559.2, - 558.8, - 558.1, - 556.7, - 555.5, - 554.4, - 554.1, - 554.2, - 554, - 553.6, - 553.2, - 552.5, - 552.1, - 551.3, - 551, - 550.7, - 549.9, - 549.1, - 548.6, - 547.9, - 547.3, - 546.8, - 546.5, - 546, - 545.6, - 545.3, - 544.7, - 543.9, - 543, - 542.2, - 541.1, - 540.1, - 539.3, - 538.6, - 538, - 536.1, - 530.9, - 532.3, - 536.2, - 536.7, - 535.1, - 534.2, - 533.7, - 533.1, - 532.5, - 531.6, - 530.8, - 529.8, - 529, - 528.3, - 527.8, - 528.6, - 528.3, - 528, - 527.7, - 527, - 526.5, - 526, - 525.5, - 525.3, - 525.3, - 524.9, - 524.3, - 523.2, - 521.7, - 520.4, - 518.7, - 517, - 516.9, - 517.9, - 518.6, - 518.3, - 518, - 515.9, - 518.2, - 518.8, - 518.2, - 517.6, - 516.6, - 515.3, - 512.7, - 509.7, - 509, - 507.8, - 504.6, - 503.2, - 500, - 498.5, - 496.7, - 495.2, - 496, - 498.2, - 498.7, - 499.1, - 499.3, - 497.8, - 491.6, - 483.2, - 478.4, - 475.8, - 473.7, - 472, - 471.5, - 471.2, - 470.8, - 470, - 469.6, - 469.4, - 469.1, - 468.6, - 469.7, - 466.3, - 463, - 462.9, - 462.6, - 460.9, - 456.4, - 451.2, - 445.9, - 441.2, - 437.9, - 433.2, - 426.5, - 424.6, - 423.1, - 422.3, - 420.8, - 419.7, - 419.3, - 417.9, - 415.9, - 411.8, - 409.5, - 410.3, - 411.7, - 411.3, - 409, - 402.2, - 390.8, - 385.5, - 383.1, - 379.9, - 375.4, - 370.8, - 368.3, - 366.4, - 531.3, - 529.8, - 527.5, - 526.2, - 525.4, - 525.2, - 524.5, - 523.8, - 523.1, - 522.3, - 521.4, - 520.8, - 520.8, - 520.8, - 520.5, - 520.1, - 519.5, - 518.5, - 517.7, - 518.1, - 518.9, - 519.3, - 519.4, - 519.3, - 518.6, - 518.1, - 518, - 517.5, - 517.1, - 516.4, - 515.4, - 513.5, - 511.9, - 511.6, - 510.7, - 508.5, - 506.5, - 505.5, - 504.9, - 503.8, - 502.9, - 502.1, - 500.9, - 499.7, - 498.2, - 496.8, - 496.5, - 495.8, - 495.3, - 495, - 494.7, - 494.5, - 494.3, - 494.1, - 493.8, - 493.6, - 493.1, - 493.1, - 493.6, - 493.9, - 494.2, - 494.6, - 495.4, - 496.4, - 497.7, - 498.8, - 499.8, - 500.9, - 502, - 503.1, - 504, - 504.8, - 505.6, - 506.4, - 507.5, - 509, - 510.7, - 512.3, - 514.4, - 515.9, - 518.8, - 526.4, - 540.7, - 551.4, - 555.7, - 559.5, - 561.9, - 563.3, - 563.6, - 563.3, - 563.2, - 562.9, - 563.4, - 563.9, - 565.1, - 566.8, - 567.7, - 568, - 567.5, - 566.8, - 565.7, - 565.3, - 564.5, - 563.7, - 562.6, - 561.8, - 560.8, - 560.1, - 559.5, - 558.8, - 558.3, - 558.1, - 557.4, - 556.5, - 555.4, - 554.5, - 553.4, - 553.1, - 552.8, - 552.7, - 552.4, - 551.9, - 551.5, - 550.9, - 550.4, - 550.2, - 549.5, - 548.5, - 547.8, - 547.2, - 546.6, - 546.3, - 545.9, - 545.6, - 545, - 544.7, - 544.4, - 543.6, - 542.4, - 541.6, - 541.1, - 540.2, - 539.3, - 538.3, - 537.7, - 537.1, - 536.1, - 534, - 534.2, - 535.2, - 535.1, - 533.9, - 533.2, - 532.2, - 531.3, - 530.8, - 530.3, - 529.9, - 529.5, - 529.5, - 529, - 528.1, - 527.8, - 527.3, - 526.9, - 526.5, - 525.9, - 525.3, - 524.9, - 524.5, - 524.3, - 524.3, - 523.8, - 523.3, - 522.4, - 521.7, - 521.4, - 520.5, - 519.3, - 518.7, - 519, - 519.4, - 519.2, - 518.7, - 518, - 517.5, - 517.1, - 516.9, - 516.5, - 516.1, - 515.8, - 515.2, - 513.6, - 512.7, - 512.3, - 511.5, - 509.9, - 506.9, - 506.3, - 505.9, - 503.8, - 501.7, - 501.6, - 501.8, - 501.4, - 501.9, - 501.2, - 496.5, - 490.5, - 488.5, - 490.6, - 489.6, - 486.2, - 484.1, - 482.9, - 482.4, - 481.4, - 480.7, - 479.4, - 475.3, - 473.8, - 474.4, - 473.8, - 471.2, - 468.2, - 465.9, - 466.1, - 466.2, - 466.2, - 463.8, - 457, - 457.4, - 452.7, - 443.8, - 436.2, - 432.3, - 429, - 426.1, - 423, - 423.7, - 422.8, - 421.2, - 417.1, - 414.8, - 418.1, - 420.7, - 417.3, - 411.3, - 403.5, - 392.4, - 388.1, - 386.5, - 384.2, - 381.2, - 374.9, - 369.5, - 366.5, - 530.1, - 528.1, - 525.9, - 524.8, - 523.9, - 523, - 522.4, - 522, - 521.1, - 519.8, - 519, - 518.8, - 518.7, - 518.3, - 517.7, - 517, - 516.3, - 515.9, - 516, - 516.6, - 517.3, - 517.7, - 517.4, - 516.7, - 515.6, - 515.5, - 515.5, - 514.9, - 514.3, - 513.2, - 511.9, - 510.2, - 509.2, - 508.4, - 507.3, - 506.1, - 504.8, - 503.7, - 503, - 502.3, - 501.6, - 500.6, - 499.1, - 497.5, - 496.2, - 495.1, - 494.6, - 494.1, - 493.6, - 493.3, - 493.1, - 493, - 492.7, - 492.5, - 492.2, - 491.8, - 491.5, - 491.5, - 492, - 492.5, - 493.1, - 493.8, - 494.7, - 495.7, - 496.8, - 497.7, - 498.9, - 500.1, - 501.1, - 502, - 502.9, - 503.8, - 504.7, - 505.7, - 506.9, - 508.5, - 510.1, - 511.6, - 513.2, - 514.7, - 518.6, - 524, - 537.3, - 544.2, - 551.1, - 556.1, - 558, - 558.4, - 558.5, - 558.8, - 559.1, - 559.5, - 560.5, - 562.3, - 565, - 566.7, - 567.1, - 567.2, - 566.6, - 565.4, - 564.6, - 564.1, - 563.5, - 562.7, - 561.4, - 560.8, - 560.4, - 559.6, - 559, - 558.3, - 557.7, - 557.2, - 556.5, - 555.5, - 554.7, - 553.9, - 552.8, - 552.3, - 551.8, - 551.5, - 551, - 550.8, - 550.4, - 549.5, - 548.9, - 548.8, - 548.3, - 547.5, - 547, - 546.1, - 545.6, - 545.2, - 545, - 544.6, - 544.1, - 543.5, - 543.2, - 542.6, - 541.3, - 540.2, - 539.7, - 539.2, - 538.5, - 537.6, - 536.8, - 536.1, - 535.6, - 535, - 534.3, - 533.5, - 532.8, - 532.3, - 532, - 531.1, - 530.4, - 529.9, - 529.4, - 529.1, - 528.6, - 528.3, - 527.7, - 527, - 526.4, - 526.1, - 525.8, - 525.8, - 525.5, - 524.8, - 524, - 523.3, - 523.3, - 523.1, - 522.7, - 521.9, - 520.9, - 520.3, - 520.4, - 520.2, - 519.5, - 518.9, - 518.6, - 518.5, - 518.2, - 517.6, - 516.9, - 516.2, - 515.8, - 515.5, - 515.4, - 515.4, - 515.1, - 514.5, - 513.3, - 512.7, - 512.1, - 511.6, - 511, - 510, - 509.6, - 508.8, - 507.5, - 506.1, - 505.4, - 505, - 505.3, - 506.8, - 507.6, - 506.6, - 504.5, - 499, - 498.4, - 499.5, - 496.5, - 493.2, - 491.8, - 491.7, - 491.3, - 489, - 486.5, - 483.8, - 479.1, - 479.3, - 480.4, - 479.4, - 477.7, - 474.9, - 470.1, - 468.7, - 468.8, - 468.2, - 467.3, - 465.3, - 460.7, - 455.1, - 447.7, - 440.3, - 436.1, - 432.8, - 429.1, - 428.5, - 428.5, - 428.2, - 425.9, - 426.2, - 426.6, - 429.4, - 425.6, - 414.1, - 404.9, - 397.4, - 390.9, - 387.5, - 385, - 382.5, - 378.4, - 373, - 368.6, - 528.4, - 525.9, - 524.5, - 523.4, - 522, - 521.4, - 520.9, - 520.1, - 518.9, - 517.7, - 517.1, - 516.7, - 516.2, - 515.6, - 515.1, - 514.3, - 513.9, - 514.1, - 514.4, - 514.8, - 515.4, - 515.4, - 514.7, - 513.6, - 513.2, - 513.2, - 512.9, - 512.2, - 510.9, - 510, - 509.1, - 508, - 507, - 506.2, - 505.3, - 504.4, - 503.4, - 502.1, - 501, - 500.4, - 499.5, - 498.2, - 496.6, - 495.3, - 494.4, - 493.5, - 492.9, - 492.4, - 491.8, - 491.3, - 491.1, - 490.9, - 490.7, - 490.5, - 490.2, - 490, - 489.7, - 490.2, - 490.8, - 491.3, - 492.1, - 492.9, - 493.8, - 494.7, - 495.8, - 496.6, - 497.7, - 499.1, - 500, - 500.8, - 501.8, - 503.1, - 504, - 505.2, - 506.7, - 508.3, - 510.1, - 511.9, - 513.5, - 516.3, - 519.1, - 521.8, - 526.4, - 531, - 539.8, - 547.2, - 551.7, - 552.7, - 554.2, - 555.5, - 556.6, - 558, - 559.5, - 562.2, - 565.2, - 566.4, - 566.2, - 565.6, - 565.3, - 564.5, - 563.9, - 563.3, - 562.8, - 562, - 561, - 560.6, - 560, - 559.3, - 558.4, - 557.7, - 557, - 556.4, - 555.7, - 555, - 554.1, - 553.1, - 552.2, - 551.5, - 551.2, - 550.8, - 550.1, - 549.8, - 549.3, - 548.8, - 548.1, - 547.7, - 547.1, - 546.4, - 545.9, - 545.3, - 544.8, - 544.4, - 544, - 543.6, - 542.8, - 542.4, - 542, - 541.4, - 540.4, - 539, - 538.4, - 538.2, - 537.5, - 536.8, - 536.1, - 535.6, - 535.1, - 534.4, - 533.5, - 532.8, - 532, - 531.4, - 530.7, - 529.8, - 529.4, - 529.1, - 528.6, - 528.5, - 527.8, - 527, - 526.4, - 525.8, - 525.5, - 525.4, - 525.4, - 525.2, - 524.8, - 523.9, - 523, - 522.6, - 522.4, - 522.2, - 521.6, - 520.5, - 519.8, - 519.5, - 519.3, - 519, - 518.5, - 517.9, - 517.6, - 517.3, - 516.9, - 516.4, - 515.5, - 514.6, - 514.6, - 514.5, - 514.4, - 514.3, - 514, - 513.1, - 512.3, - 511.5, - 511.3, - 510.8, - 510.4, - 509.4, - 508.6, - 508.6, - 508.2, - 507.9, - 507.8, - 507.4, - 507.4, - 507.6, - 507.8, - 507.4, - 506.9, - 505.2, - 503.9, - 503.7, - 502.1, - 499, - 497.3, - 496.8, - 496.8, - 495.3, - 494.3, - 492.6, - 490.5, - 489, - 487.6, - 486, - 484.8, - 484.6, - 483.1, - 476.5, - 469.9, - 467.6, - 467.4, - 465.8, - 462.4, - 460.5, - 460.7, - 456.4, - 449.5, - 447.8, - 441.1, - 437.2, - 436.3, - 434.2, - 433.3, - 434.1, - 434.1, - 434.2, - 430.1, - 418, - 404.3, - 398.9, - 391.8, - 387.1, - 384.6, - 382, - 378.6, - 375.2, - 371.7, - 526.5, - 524.6, - 523.1, - 521.3, - 520.2, - 520, - 519.3, - 518, - 517.2, - 516.6, - 516, - 515.4, - 514.7, - 513.8, - 513.3, - 512.9, - 512.6, - 512.4, - 512.6, - 512.7, - 512.8, - 512.5, - 511.3, - 510.8, - 510.9, - 510.8, - 510.7, - 510.1, - 509.2, - 508.3, - 507.4, - 506.6, - 505.8, - 505, - 504.1, - 503.1, - 501.9, - 500.5, - 499.2, - 498.1, - 497, - 495.8, - 494.9, - 494.3, - 493.6, - 492.3, - 491.2, - 490.6, - 490.2, - 489.7, - 489.1, - 488.8, - 488.7, - 488.4, - 488.2, - 488, - 488.3, - 488.8, - 489.5, - 490, - 490.9, - 491.9, - 492.8, - 493.6, - 494.6, - 495.5, - 496.6, - 497.9, - 498.9, - 499.7, - 500.7, - 501.9, - 503.1, - 505, - 507.3, - 509.6, - 512.1, - 514.1, - 516.2, - 519.1, - 521.6, - 524.7, - 527.6, - 531.9, - 539.4, - 545, - 548.1, - 550.1, - 552.2, - 553.7, - 554.8, - 556.7, - 558.7, - 561.3, - 563.7, - 564.8, - 565, - 564.7, - 564.5, - 564, - 563.2, - 562.7, - 562.1, - 561.4, - 560.5, - 559.8, - 559.1, - 558.4, - 557.5, - 556.8, - 556.2, - 555.5, - 554.7, - 554.1, - 553.5, - 552.4, - 551.6, - 550.9, - 550.3, - 549.8, - 549.4, - 548.8, - 548.1, - 547.7, - 547.2, - 546.9, - 546.1, - 545.3, - 544.8, - 544.3, - 543.7, - 543.4, - 543, - 542.6, - 541.5, - 541.2, - 540.4, - 539.7, - 538.8, - 537.7, - 537.3, - 537.2, - 536.8, - 536.1, - 535.5, - 535, - 534.4, - 533.7, - 533.1, - 532.4, - 531.6, - 530.8, - 529.6, - 528.8, - 528.6, - 528.1, - 527.5, - 527.3, - 526.7, - 526.1, - 525.5, - 525.2, - 524.9, - 524.7, - 524.7, - 524.4, - 523.6, - 522.4, - 521.9, - 521.7, - 521.5, - 520.9, - 520.3, - 519.7, - 518.9, - 518.8, - 518.4, - 517.7, - 517.2, - 516.8, - 516.5, - 516.1, - 515.6, - 515, - 514.1, - 513.5, - 513.7, - 513.6, - 513.3, - 512.9, - 512.5, - 511.8, - 511.1, - 510.8, - 510.5, - 509.7, - 509.1, - 508.2, - 507.8, - 507.7, - 507.3, - 507.1, - 506.9, - 506.6, - 506.4, - 506.4, - 506.2, - 506.1, - 505.8, - 505.7, - 504.9, - 504.5, - 503.6, - 501.8, - 501.2, - 500.9, - 501.4, - 501, - 500, - 498.5, - 497.2, - 496.1, - 494, - 492.5, - 491.9, - 491.7, - 488.9, - 484.5, - 475.6, - 468.4, - 464.8, - 463.7, - 463.6, - 464.5, - 466.5, - 467.2, - 464.6, - 459.3, - 454.4, - 448.5, - 443.9, - 440.4, - 438.6, - 438.1, - 437.7, - 436.7, - 435.1, - 430.4, - 413.5, - 400.8, - 392.9, - 387.5, - 385, - 382.5, - 379.6, - 377, - 372.7, - 525.3, - 523.5, - 521.9, - 520.3, - 519.5, - 519.1, - 518.3, - 517.2, - 516.4, - 515.7, - 515.1, - 514.4, - 513.6, - 512.9, - 512.5, - 512.2, - 511.8, - 511.3, - 510.8, - 510.4, - 509.9, - 509.3, - 509.1, - 509.1, - 509.1, - 508.9, - 508.8, - 508.4, - 507.9, - 507.2, - 506.3, - 505.4, - 504.7, - 504, - 503.1, - 502.1, - 500.9, - 499.5, - 498.1, - 496.7, - 495.7, - 494.8, - 494.1, - 493.6, - 492.7, - 491.3, - 490.2, - 489.4, - 488.8, - 488.4, - 487.9, - 487.5, - 487, - 486.7, - 486.5, - 486.7, - 487.1, - 487.7, - 488.2, - 489, - 489.9, - 490.9, - 491.8, - 492.7, - 493.5, - 494.4, - 495.5, - 496.9, - 497.7, - 498.7, - 499.7, - 501.4, - 503.6, - 506.1, - 509, - 511.5, - 513.5, - 516, - 519.4, - 522.6, - 526.3, - 530.9, - 535.2, - 539.7, - 543.9, - 546.7, - 548.5, - 549.9, - 550.6, - 551.6, - 552.7, - 554.9, - 559.4, - 561.8, - 562.8, - 563.7, - 564.4, - 564.4, - 563.8, - 563, - 562.4, - 562.1, - 561.5, - 560.9, - 559.9, - 559.2, - 558.5, - 557.9, - 557.1, - 556.3, - 555.4, - 554.7, - 553.7, - 553, - 552.4, - 551.6, - 551, - 550.4, - 549.7, - 548.9, - 548.5, - 547.8, - 547.1, - 546.8, - 546.5, - 546, - 545.2, - 544.2, - 543.4, - 542.8, - 542.3, - 542.1, - 541.6, - 541.1, - 540, - 539.4, - 538.8, - 538, - 537.2, - 536.8, - 536.4, - 536, - 535.7, - 535.1, - 534.6, - 534, - 533.5, - 532.9, - 532.3, - 531.6, - 530.8, - 529.9, - 528.9, - 528.3, - 527.8, - 527.3, - 527, - 526.6, - 526, - 525.2, - 524.8, - 524.5, - 524.1, - 523.7, - 523.5, - 522.8, - 521.8, - 521.3, - 520.9, - 520.8, - 520.6, - 519.8, - 519.3, - 518.5, - 517.8, - 517.5, - 517.1, - 516.3, - 516, - 515.6, - 515.1, - 514.8, - 514.3, - 513.6, - 513, - 512.6, - 512.7, - 512.5, - 511.8, - 511.4, - 510.8, - 510.2, - 509.8, - 509.8, - 509.3, - 508.5, - 507.7, - 507.2, - 507.1, - 507.1, - 506.6, - 506.5, - 506.1, - 505.6, - 505.3, - 505, - 504.7, - 504.7, - 504.7, - 504.3, - 503.7, - 503.2, - 502.9, - 502.3, - 502, - 501.7, - 501.5, - 501.2, - 501, - 500, - 498.9, - 498.5, - 497.9, - 496.9, - 496.5, - 494.7, - 491.5, - 488.3, - 483, - 473.3, - 464.6, - 461.6, - 462.4, - 463.8, - 465.4, - 468.2, - 470.6, - 465.9, - 460.9, - 456.6, - 450.4, - 447, - 444.6, - 443, - 441.9, - 440.8, - 437.8, - 433.1, - 422.2, - 408.6, - 396.5, - 388.9, - 386.8, - 384.8, - 381.2, - 378.5, - 375.5, - 524.5, - 522.8, - 521.2, - 519.8, - 518.8, - 518.1, - 517.3, - 516.4, - 515.6, - 514.9, - 514.1, - 513.2, - 512.6, - 512, - 511.5, - 511.2, - 510.8, - 510.5, - 509.7, - 508.9, - 508.3, - 507.9, - 507.8, - 507.6, - 507.4, - 507.3, - 507.2, - 507.1, - 506.7, - 506.1, - 505.3, - 504.6, - 503.9, - 503.1, - 502.3, - 501.2, - 500.1, - 498.6, - 497, - 495.7, - 494.7, - 494.1, - 493.3, - 492.6, - 491.5, - 490.2, - 489, - 488.1, - 487.7, - 487.1, - 486.5, - 485.9, - 485.4, - 485.2, - 485.3, - 485.5, - 485.9, - 486.6, - 487.3, - 487.9, - 488.8, - 489.8, - 490.7, - 491.6, - 492.3, - 493.2, - 494.3, - 495.5, - 496.5, - 497.8, - 499.7, - 502, - 504.2, - 506.8, - 509.1, - 511.4, - 514.1, - 518.2, - 523, - 527.9, - 533.4, - 537, - 540.3, - 542.9, - 544.4, - 544.7, - 544.9, - 545.9, - 547.5, - 550.2, - 553.8, - 560.7, - 564.4, - 564.5, - 563.8, - 563.5, - 563.7, - 563.4, - 562.4, - 561.9, - 561.7, - 561.3, - 560.8, - 560.4, - 559.3, - 558.1, - 557.4, - 556.9, - 556.1, - 555.4, - 554.7, - 553.9, - 552.9, - 552.2, - 551.6, - 550.9, - 550.4, - 549.8, - 549, - 548.3, - 547.9, - 547.1, - 546.6, - 546.1, - 545.7, - 544.7, - 543.9, - 543.1, - 542.3, - 541.7, - 541.3, - 540.7, - 540.1, - 539.5, - 538.3, - 537.5, - 537, - 536.8, - 536, - 535.8, - 535.5, - 535, - 534.5, - 534.3, - 533.4, - 532.7, - 532.3, - 531.7, - 531.1, - 530.3, - 529.7, - 528.7, - 528.1, - 527.4, - 526.8, - 526.6, - 526, - 525.3, - 524.7, - 524.3, - 523.9, - 523.6, - 523.1, - 522.4, - 521.8, - 520.8, - 520.1, - 519.5, - 519.2, - 519.4, - 519.2, - 518.2, - 517.6, - 517.3, - 516.8, - 516.6, - 516.2, - 515.4, - 514.9, - 514.4, - 513.7, - 513.6, - 513.3, - 512.3, - 511.8, - 511.7, - 511.5, - 511.1, - 510.3, - 510, - 509.7, - 509.3, - 509.1, - 508.9, - 508.2, - 507.4, - 506.7, - 506.6, - 506.5, - 506.2, - 505.7, - 505.5, - 505.2, - 504.7, - 504.2, - 504, - 503.8, - 503.8, - 503.6, - 503.1, - 502.7, - 502.5, - 502.2, - 501.7, - 501, - 500.4, - 500.3, - 500.2, - 499.9, - 499.1, - 498.4, - 497.9, - 497.4, - 496.8, - 496.6, - 495.3, - 493.3, - 492.3, - 490.6, - 484.1, - 473.5, - 462, - 459.7, - 460.7, - 461.8, - 465.8, - 471.6, - 470.9, - 469.3, - 465.6, - 460.3, - 456.1, - 451.1, - 448.1, - 444.3, - 441.9, - 438.8, - 432.7, - 422.9, - 413.1, - 399.6, - 391.7, - 388.7, - 387, - 382.3, - 379.5, - 376.7, - 523.9, - 522.4, - 521, - 519.4, - 518.3, - 517.4, - 516.5, - 515.6, - 514.7, - 513.9, - 512.9, - 511.9, - 511.4, - 510.8, - 510.3, - 509.8, - 509.6, - 509.6, - 509.1, - 508.2, - 507.5, - 507, - 506.6, - 506.5, - 506.4, - 506.2, - 506, - 506, - 505.6, - 505, - 504.3, - 503.7, - 503.1, - 502.5, - 501.7, - 500.6, - 499.4, - 497.9, - 496.4, - 495, - 493.8, - 493.2, - 492.3, - 491.3, - 490.1, - 488.8, - 487.7, - 486.8, - 486.2, - 485.7, - 485.1, - 484.4, - 484.1, - 484, - 483.9, - 484.1, - 484.6, - 485.5, - 486.2, - 486.9, - 487.7, - 488.6, - 489.5, - 490.3, - 491.1, - 492.2, - 493.3, - 494.3, - 495.4, - 497.2, - 499.7, - 501.8, - 503.9, - 506.4, - 508.5, - 512.2, - 517, - 522.2, - 526.7, - 531.4, - 534.7, - 537.2, - 539.9, - 541.3, - 540.1, - 539.2, - 540.5, - 543.6, - 548.3, - 554.9, - 560.1, - 564.8, - 564.6, - 564.1, - 563.4, - 563, - 562.7, - 561.9, - 561.5, - 561.1, - 560.7, - 560.3, - 559.8, - 559.1, - 557.8, - 556.7, - 556.1, - 555.6, - 554.7, - 554.1, - 553.8, - 552.7, - 552.1, - 551.4, - 550.8, - 550.1, - 549.5, - 548.8, - 548.1, - 547.5, - 547.1, - 546.3, - 545.8, - 545.2, - 544.7, - 543.8, - 543.1, - 542.4, - 541.2, - 540.4, - 540.3, - 539.4, - 538.5, - 537.8, - 536.9, - 536.4, - 536.1, - 535.8, - 535, - 534.7, - 534.3, - 534, - 533.6, - 533.1, - 532.1, - 531.6, - 531.3, - 530.8, - 530.1, - 529.3, - 528.6, - 527.6, - 526.9, - 526.2, - 525.7, - 525.3, - 524.8, - 524.3, - 523.6, - 523.2, - 522.8, - 522.5, - 521.9, - 521.2, - 520.6, - 519.9, - 519.1, - 518.5, - 518.3, - 518.3, - 517.9, - 517.1, - 516.6, - 516.1, - 515.6, - 515.2, - 515.1, - 514.6, - 514.2, - 513.7, - 513.1, - 512.8, - 512.2, - 511.4, - 510.9, - 510.8, - 510.5, - 509.8, - 509.3, - 509, - 508.6, - 508.6, - 508.5, - 507.9, - 507.3, - 506.5, - 506.1, - 505.9, - 505.6, - 505.2, - 504.6, - 504.1, - 503.9, - 503.6, - 503.3, - 503.1, - 502.9, - 502.9, - 502.2, - 502.1, - 501.9, - 501.7, - 501.2, - 500.9, - 500.2, - 499.7, - 499.4, - 499.2, - 498.4, - 498, - 497.4, - 496.5, - 496.1, - 495.7, - 495.5, - 495.2, - 494.4, - 493.9, - 491.4, - 485.7, - 474.8, - 462.8, - 457.8, - 457.7, - 458.3, - 463.3, - 471.3, - 473.1, - 472.4, - 469.5, - 466.4, - 462.3, - 457.1, - 452.2, - 447, - 441.3, - 437.4, - 433.2, - 425.7, - 419.8, - 414.7, - 404.8, - 390.7, - 386, - 380.4, - 376.7, - 374, - 523, - 521.7, - 520, - 518.2, - 517.3, - 516.3, - 515.6, - 514.7, - 513.8, - 512.9, - 511.9, - 511, - 510.3, - 509.7, - 509.2, - 508.8, - 508.5, - 508.5, - 508, - 507.3, - 506.5, - 505.8, - 505.4, - 505.1, - 505.1, - 505, - 504.8, - 504.5, - 504.2, - 503.6, - 503.1, - 502.6, - 502.2, - 501.6, - 501.1, - 500.2, - 499, - 497.5, - 495.9, - 494.3, - 493.1, - 492.1, - 491, - 489.9, - 488.7, - 487.5, - 486.5, - 485.4, - 484.7, - 484.3, - 483.8, - 483.2, - 482.8, - 482.7, - 482.5, - 482.8, - 483.4, - 484.2, - 484.8, - 485.6, - 486.5, - 487.4, - 488.3, - 489.2, - 490, - 490.9, - 492.2, - 493.2, - 494.6, - 497, - 499.8, - 501.3, - 503, - 506.1, - 510.3, - 515.4, - 520.2, - 524.1, - 528, - 530.8, - 532.2, - 533.5, - 534.3, - 534.6, - 535.2, - 538.2, - 543.5, - 548.6, - 557.2, - 561.8, - 564.4, - 564.2, - 563.7, - 563.2, - 562.7, - 562.3, - 561.7, - 561, - 560.7, - 559.9, - 559.4, - 559.1, - 558.3, - 557.4, - 556.1, - 555.1, - 554.1, - 553.7, - 553.1, - 552.6, - 552.1, - 551.3, - 550.8, - 550.2, - 549.7, - 549.3, - 548.7, - 548.1, - 547.4, - 546.7, - 546.1, - 545.5, - 544.9, - 544.2, - 543.5, - 542.9, - 542, - 541.1, - 540.2, - 539.5, - 539.2, - 538.3, - 537.5, - 536.6, - 536, - 535.6, - 535.3, - 534.5, - 533.9, - 533.7, - 533.3, - 532.5, - 532.2, - 531.7, - 531.2, - 530.6, - 530.5, - 529.6, - 528.9, - 528.5, - 527.7, - 526.5, - 525.6, - 524.8, - 524.4, - 524.2, - 523.7, - 523.2, - 522.7, - 522.1, - 521.5, - 520.8, - 520.1, - 519.6, - 519.3, - 518.5, - 517.7, - 517.2, - 517.2, - 517.2, - 516.7, - 516.2, - 515.8, - 515.3, - 514.6, - 514.4, - 514.3, - 514, - 513.2, - 512.6, - 512.1, - 511.5, - 510.9, - 510.3, - 510.1, - 510, - 509.5, - 508.9, - 508.7, - 508.1, - 507.6, - 507.5, - 507.1, - 506.5, - 506.1, - 505.5, - 505.3, - 505.1, - 504.7, - 504.1, - 503.6, - 502.8, - 502.3, - 502.3, - 502.2, - 501.8, - 501.5, - 501.2, - 500.7, - 500.8, - 500.8, - 500.2, - 499.9, - 499.8, - 499.4, - 499, - 498.7, - 498.2, - 497.8, - 497.5, - 496.4, - 495.5, - 495.2, - 494.9, - 494.3, - 493.9, - 493.7, - 493.4, - 492.2, - 489.2, - 478.1, - 465.1, - 457.2, - 455.1, - 455, - 458.4, - 465.8, - 468.7, - 467.5, - 466.1, - 464.3, - 462.8, - 461.1, - 457.8, - 452.5, - 445.1, - 436.9, - 430.4, - 423.7, - 421, - 418.1, - 412.5, - 396.2, - 383.5, - 374.4, - 368.1, - 364.4, - 521.6, - 519.8, - 518.3, - 517.1, - 516, - 515.2, - 514.6, - 513.8, - 512.9, - 512, - 511.1, - 510.2, - 509.4, - 508.7, - 508.2, - 507.8, - 507.5, - 507.5, - 507.4, - 506.6, - 505.5, - 504.7, - 504.2, - 503.8, - 503.6, - 503.7, - 503.3, - 503, - 502.8, - 502.4, - 502.1, - 501.8, - 501.3, - 500.8, - 500.2, - 499.4, - 498.2, - 496.9, - 495.2, - 493.6, - 492.3, - 491, - 489.6, - 488.4, - 487.2, - 486.1, - 485, - 483.7, - 482.9, - 482.6, - 482, - 481.8, - 481.6, - 481.4, - 481.3, - 481.4, - 482.1, - 482.6, - 483.1, - 484.1, - 485.2, - 486.1, - 487.1, - 488.1, - 488.9, - 489.9, - 491, - 492.4, - 494.4, - 496.5, - 498.8, - 500.9, - 504.2, - 508.3, - 512.9, - 517.3, - 520, - 523.3, - 526.1, - 527.6, - 528.4, - 529.1, - 530.3, - 532.7, - 538.1, - 550.5, - 561.3, - 560.8, - 564.1, - 564.5, - 564.2, - 563.4, - 562.9, - 562.4, - 561.8, - 561.1, - 560.4, - 559.9, - 559.6, - 558.8, - 558.4, - 557.7, - 556.2, - 555.6, - 554.7, - 553.5, - 552.7, - 552.3, - 551.7, - 551.3, - 550.8, - 549.9, - 549.3, - 548.9, - 548.5, - 548.2, - 547.7, - 547.1, - 546.4, - 545.7, - 545.3, - 544.8, - 544.2, - 543.5, - 542.6, - 542, - 541.3, - 540.2, - 539.3, - 538.7, - 537.6, - 536.8, - 536.1, - 535.7, - 535.1, - 534.9, - 534.4, - 533.7, - 533.1, - 532.5, - 531.9, - 531.3, - 531, - 530.5, - 530.1, - 529.5, - 529.1, - 528.5, - 528.2, - 528, - 527, - 525.7, - 524.7, - 523.9, - 523.2, - 522.8, - 522.4, - 521.8, - 521.3, - 520.7, - 520.1, - 519.6, - 519.1, - 518.7, - 518.2, - 517.1, - 516.3, - 516.3, - 516.3, - 516.1, - 515.4, - 515, - 514.7, - 514.2, - 513.6, - 513.4, - 513.2, - 512.7, - 511.9, - 511.1, - 510.2, - 509.5, - 509.1, - 508.8, - 508.9, - 508.9, - 508.5, - 508.1, - 507.9, - 507.3, - 507.1, - 506.9, - 506.3, - 505.6, - 505.1, - 504.2, - 504, - 503.9, - 503.5, - 503.1, - 502.3, - 501.8, - 501.3, - 501.2, - 501.1, - 500.8, - 500.2, - 499.6, - 499.4, - 499.5, - 499.3, - 498.9, - 498.7, - 498.4, - 498.3, - 498.1, - 497.7, - 497.2, - 497.2, - 496.4, - 495.5, - 495, - 494.6, - 493.9, - 493.3, - 492.8, - 492.4, - 491.9, - 490.9, - 489.7, - 485, - 467.9, - 456.4, - 452.9, - 452.2, - 453.9, - 458.5, - 462.1, - 462.7, - 462.4, - 462, - 461.3, - 460, - 458.3, - 454.6, - 446.6, - 437, - 428.9, - 423.4, - 419.9, - 415.5, - 408.3, - 397.9, - 387.1, - 374.1, - 361.9, - 357.3, - 520.4, - 518.8, - 517.7, - 516.5, - 515.3, - 514.4, - 513.6, - 512.7, - 511.8, - 511, - 510.1, - 509.2, - 508.4, - 507.7, - 507.2, - 506.7, - 506.1, - 506, - 506, - 505.1, - 503.9, - 503.5, - 503.1, - 502.6, - 502.4, - 502.2, - 501.8, - 501.6, - 501.5, - 501.3, - 501.1, - 500.8, - 500.4, - 500, - 499.5, - 498.8, - 497.5, - 496, - 494.3, - 492.6, - 491.3, - 490, - 488.6, - 487.6, - 486.2, - 484.9, - 483.5, - 482.1, - 481.7, - 481.3, - 480.6, - 480.3, - 480.1, - 479.9, - 479.9, - 480.2, - 480.7, - 481.1, - 481.6, - 482.3, - 483.7, - 484.7, - 485.7, - 486.9, - 487.9, - 488.8, - 489.8, - 491.2, - 493.1, - 494.9, - 497.5, - 501.4, - 506, - 510.3, - 514, - 517.2, - 519.3, - 521.6, - 523.8, - 524.5, - 525.3, - 527.4, - 530.5, - 534.7, - 548.4, - 563.4, - 565.5, - 564.9, - 564, - 565.1, - 564.7, - 563.1, - 562.3, - 561.6, - 560.6, - 560, - 559.3, - 558.8, - 558.4, - 558, - 557.5, - 556.2, - 555.1, - 554.3, - 553, - 552, - 551.7, - 551.3, - 550.5, - 550.3, - 549.8, - 548.9, - 548.3, - 547.9, - 547.6, - 547.3, - 546.7, - 546, - 545.3, - 544.7, - 544.4, - 544, - 543.4, - 542.8, - 542.2, - 541.4, - 540.8, - 539.4, - 537.9, - 537, - 536, - 535.5, - 535.1, - 534.7, - 534.4, - 534.1, - 533.5, - 532.6, - 532.1, - 531.7, - 531, - 530.5, - 530.2, - 529.5, - 529.2, - 528.5, - 527.9, - 527.6, - 527.2, - 526.6, - 526, - 524.8, - 523.9, - 523.2, - 522.6, - 522.1, - 521.5, - 520.8, - 520.3, - 519.6, - 518.8, - 518.5, - 518.1, - 517.9, - 517.4, - 516.5, - 515.9, - 515.5, - 515.1, - 515, - 514.5, - 513.9, - 513.5, - 513.1, - 512.4, - 512.1, - 511.9, - 511.2, - 510.6, - 510, - 509, - 508.4, - 507.9, - 507.8, - 507.8, - 507.8, - 507.5, - 507.5, - 507.3, - 506.5, - 506.3, - 506.1, - 505.7, - 504.8, - 504.1, - 502.9, - 502.4, - 502, - 501.9, - 501.7, - 501.1, - 500.6, - 499.9, - 499.9, - 500, - 499.6, - 499.3, - 498.7, - 498.4, - 498.2, - 497.8, - 497.5, - 497.3, - 497.2, - 497.1, - 496.8, - 496.6, - 496.1, - 495.7, - 494.9, - 494.3, - 494, - 493.6, - 493, - 492.6, - 492.2, - 491.8, - 491, - 489.8, - 488.1, - 484.1, - 468.4, - 455.1, - 451.5, - 450.5, - 450.6, - 452.5, - 455.4, - 457.9, - 459.8, - 459.8, - 458.2, - 456.3, - 454.2, - 452.1, - 446.2, - 435.3, - 429.5, - 424.4, - 417.8, - 412, - 408.6, - 402.4, - 390.8, - 380.4, - 361.7, - 354.2, - 519.6, - 518.2, - 517.5, - 516.3, - 515, - 513.7, - 512.5, - 511.4, - 510.5, - 509.9, - 508.9, - 508.1, - 507.4, - 506.7, - 506.2, - 505.6, - 505, - 504.3, - 503.6, - 502.9, - 502.5, - 502.2, - 502, - 501.6, - 501.3, - 501.1, - 500.7, - 500.5, - 500.2, - 500, - 499.8, - 499.6, - 499.4, - 499.1, - 498.7, - 498, - 496.9, - 495.3, - 493.8, - 492.2, - 490.8, - 489.4, - 488.2, - 487, - 485.5, - 484.1, - 482.4, - 481.2, - 480.7, - 480.1, - 479.4, - 478.8, - 478.5, - 478.2, - 478.5, - 478.8, - 479.4, - 480, - 480.4, - 480.9, - 482.1, - 483.1, - 484.2, - 485.8, - 487, - 487.9, - 488.9, - 490.2, - 491.9, - 494.9, - 498.8, - 502.6, - 507.2, - 511.1, - 514.5, - 517, - 518.6, - 519.6, - 521.1, - 522.8, - 524.8, - 527.4, - 530.4, - 541.2, - 558.3, - 563.9, - 564.5, - 562.5, - 562.3, - 565.6, - 565.4, - 562.7, - 561.2, - 560.4, - 559.7, - 559, - 558.1, - 557.6, - 557.2, - 556.6, - 555.5, - 554.4, - 553.6, - 552.9, - 551.7, - 551.1, - 550.7, - 550.2, - 549.6, - 549.2, - 548.7, - 548.1, - 547.5, - 547.1, - 546.8, - 546.2, - 545.6, - 545, - 544.3, - 543.7, - 543.4, - 543, - 542.6, - 542.1, - 541.4, - 540.5, - 540.3, - 539.2, - 536.8, - 535.6, - 535.1, - 534.8, - 534.4, - 533.8, - 533.4, - 533.1, - 532.4, - 531.3, - 530.8, - 530.5, - 530, - 529.5, - 528.8, - 528.2, - 527.8, - 527.3, - 526.7, - 526.2, - 525.7, - 525.2, - 524.4, - 523.7, - 523.2, - 522.7, - 522, - 521.4, - 520.8, - 520.1, - 519.2, - 518.5, - 518.1, - 517.7, - 517.2, - 516.7, - 516, - 515.1, - 514.5, - 514.2, - 513.9, - 513.9, - 513.5, - 512.9, - 512.3, - 511.7, - 511.4, - 510.9, - 510.7, - 509.9, - 509.2, - 508.6, - 507.9, - 507.6, - 507.2, - 507, - 506.9, - 506.6, - 506.3, - 506.4, - 505.9, - 505.4, - 505, - 504.5, - 503.9, - 503.1, - 502.3, - 501.6, - 501, - 500.8, - 500.4, - 500.1, - 499.9, - 499.4, - 498.8, - 498.6, - 498.7, - 498.4, - 498.2, - 497.8, - 497.3, - 497, - 496.5, - 496.3, - 496.4, - 496.2, - 496, - 495.9, - 495.6, - 495.1, - 494.4, - 493.7, - 493.3, - 492.9, - 492.6, - 492.3, - 492.1, - 491.7, - 491.2, - 490, - 488.3, - 485.1, - 480.2, - 467.4, - 453.4, - 450.1, - 447.6, - 446.9, - 448.4, - 451.3, - 455.1, - 457.4, - 456.4, - 453.8, - 451.9, - 450.7, - 449.7, - 447.3, - 436.3, - 427.7, - 424.8, - 418.9, - 414.1, - 411.6, - 402.4, - 391.6, - 373.8, - 359.8, - 350.8, - 518.7, - 517.7, - 517.1, - 516, - 514.6, - 513, - 511.5, - 510.1, - 509, - 508.5, - 507.8, - 507.1, - 506.4, - 505.7, - 505.2, - 504.6, - 503.8, - 503.2, - 502.5, - 501.9, - 501.4, - 501.1, - 500.8, - 500.5, - 500.1, - 499.9, - 499.6, - 499.2, - 498.9, - 498.8, - 498.6, - 498.5, - 498.4, - 498.2, - 497.8, - 497.2, - 496.1, - 495, - 493.5, - 491.7, - 490.5, - 489.3, - 487.8, - 486.3, - 484.9, - 483.2, - 481.2, - 480.4, - 479.6, - 478.7, - 478.1, - 477.4, - 476.9, - 476.5, - 477, - 477.3, - 478.1, - 478.9, - 479.4, - 479.7, - 480.8, - 481.7, - 482.9, - 484.6, - 485.7, - 486.8, - 487.9, - 489.4, - 491.6, - 495.9, - 500.9, - 502.9, - 506.5, - 511, - 514.5, - 516.5, - 517.8, - 518.9, - 520.2, - 522.1, - 524.1, - 526.7, - 529.1, - 539.1, - 556.3, - 558.1, - 558.9, - 560.2, - 562.4, - 564.2, - 563.7, - 561.2, - 559.7, - 559, - 558.6, - 557.7, - 557, - 556.8, - 556.1, - 554.7, - 553.8, - 553.1, - 552, - 551.5, - 550.8, - 550.3, - 549.9, - 549.3, - 548.8, - 548.1, - 547.6, - 547, - 546.4, - 546, - 545.5, - 544.9, - 544.5, - 543.8, - 543.2, - 542.8, - 542.3, - 541.9, - 541.4, - 540.7, - 540.1, - 539.4, - 539, - 537.6, - 535.6, - 534.8, - 534.4, - 534.1, - 533.5, - 532.7, - 532, - 531.7, - 531.1, - 530.1, - 529.7, - 529.5, - 528.9, - 528.5, - 527.8, - 527.4, - 527, - 526.2, - 525.6, - 525, - 524.5, - 523.7, - 522.8, - 522.6, - 522.2, - 521.8, - 521, - 520.4, - 519.9, - 519.2, - 518.4, - 517.6, - 517.2, - 516.7, - 516, - 515.3, - 514.5, - 513.4, - 513.2, - 512.7, - 512.5, - 512.2, - 512, - 511.5, - 510.8, - 510.2, - 509.9, - 509.6, - 509.4, - 508.8, - 508.1, - 507.3, - 507, - 506.7, - 506.3, - 506, - 505.8, - 505.5, - 505.1, - 504.6, - 503.9, - 503.2, - 503.1, - 502.8, - 502.1, - 501.4, - 500.9, - 500.5, - 500, - 499.9, - 499.6, - 499.3, - 498.8, - 498.3, - 497.9, - 497.5, - 497.2, - 497.1, - 496.9, - 496.4, - 495.9, - 495.6, - 495.2, - 495.2, - 495.8, - 495.6, - 495.3, - 495, - 494.5, - 493.8, - 493.3, - 492.8, - 492.4, - 491.7, - 491.5, - 491.5, - 491.2, - 490.8, - 490.1, - 489.3, - 487.8, - 482.6, - 475.3, - 463.1, - 451.9, - 447.9, - 446, - 444.5, - 445.2, - 448, - 450.7, - 453.2, - 451.7, - 449.9, - 449, - 448.1, - 446.8, - 444.9, - 437.3, - 425.4, - 421.3, - 417.7, - 414.6, - 409.5, - 402, - 394.9, - 379.4, - 361.8, - 351, - 517.7, - 517.1, - 516.3, - 515.2, - 513.6, - 511.9, - 510.2, - 508.8, - 507.8, - 507.1, - 506.7, - 506.1, - 505.3, - 504.6, - 504, - 503.5, - 502.7, - 502, - 501.3, - 500.8, - 500.4, - 500, - 499.6, - 499.4, - 499.1, - 498.6, - 498.1, - 497.8, - 497.7, - 497.7, - 497.3, - 497, - 497.1, - 497, - 496.5, - 496, - 494.9, - 493.8, - 492.5, - 491, - 490.2, - 489.3, - 487.6, - 485.8, - 484.2, - 481.7, - 480.1, - 479.1, - 478.2, - 477.4, - 476.8, - 476.1, - 475.7, - 475.2, - 475.3, - 475.7, - 476.5, - 477.2, - 478, - 478.6, - 479.4, - 480.1, - 481.1, - 482.7, - 484.3, - 485.7, - 487.3, - 489.1, - 491.9, - 495.9, - 500.5, - 504.2, - 507.6, - 511.6, - 514.3, - 515.9, - 517.2, - 518.6, - 520.1, - 521.9, - 524.1, - 526.9, - 528.8, - 533.2, - 545.8, - 556, - 560.8, - 562.2, - 561.9, - 561.1, - 560.2, - 559.7, - 558.8, - 558.4, - 557.8, - 557, - 556.3, - 555.9, - 555.2, - 554, - 552.8, - 551.9, - 550.8, - 550.2, - 549.8, - 549.6, - 549.2, - 548.6, - 548, - 547.4, - 546.8, - 546.1, - 545.5, - 545, - 544.4, - 543.7, - 543.1, - 542.5, - 542.1, - 541.6, - 541.1, - 540.6, - 540.1, - 539.3, - 539, - 537.9, - 536.7, - 535.8, - 534.9, - 534.3, - 533.9, - 533, - 532.1, - 531.6, - 530.8, - 530.5, - 530, - 528.8, - 528.4, - 528.4, - 528, - 527.4, - 526.7, - 526.2, - 525.7, - 525.1, - 524.5, - 524, - 523.4, - 522.5, - 521.8, - 521.4, - 521, - 520.6, - 519.9, - 519.1, - 518.5, - 517.8, - 517.2, - 516.7, - 516.1, - 515.7, - 514.9, - 514, - 512.9, - 512.1, - 511.7, - 511.2, - 510.8, - 510.4, - 510.2, - 510.3, - 509.8, - 509, - 508.5, - 508.2, - 507.9, - 507.2, - 506.5, - 506.4, - 506.2, - 505.7, - 505.3, - 505, - 504.8, - 504.5, - 504, - 503.3, - 502.6, - 501.8, - 501.1, - 501.1, - 500.7, - 500.3, - 499.9, - 499.6, - 499.4, - 499.3, - 498.7, - 498.1, - 497.7, - 497.3, - 497.2, - 496.9, - 496.5, - 496, - 495.5, - 495.1, - 494.7, - 494.5, - 494.3, - 494.4, - 494.8, - 494.5, - 494.1, - 493.8, - 493.2, - 492.4, - 492, - 491.7, - 491.1, - 490.8, - 490.6, - 490.4, - 490, - 489.6, - 489.1, - 488.5, - 487.8, - 485.1, - 477.4, - 464.1, - 453, - 446.9, - 442, - 439.6, - 441.6, - 444.8, - 446.5, - 448.2, - 447.5, - 446, - 445.4, - 444.1, - 442.8, - 437, - 429.4, - 421.4, - 417.8, - 415.3, - 411, - 405.7, - 401.8, - 394.7, - 386.7, - 365.4, - 353.7, - 516.8, - 516.1, - 515.3, - 514.1, - 512.2, - 510.7, - 509, - 507.6, - 506.5, - 505.9, - 505.5, - 504.9, - 504.1, - 503.3, - 502.7, - 502.2, - 501.5, - 500.9, - 500.3, - 499.7, - 499.3, - 498.8, - 498.3, - 498.2, - 497.9, - 497.3, - 496.7, - 496.6, - 496.6, - 496.5, - 496, - 495.8, - 495.9, - 495.8, - 495.6, - 494.9, - 492.9, - 492, - 491.3, - 490.6, - 489.7, - 488.6, - 487.1, - 485.3, - 482.8, - 480, - 478.8, - 477.8, - 476.9, - 476.2, - 475.6, - 475, - 474.5, - 473.9, - 473.9, - 474.2, - 474.8, - 475.6, - 476.5, - 477.2, - 478, - 478.6, - 479.5, - 480.9, - 482.7, - 484.5, - 487.6, - 490.4, - 493.6, - 497.3, - 500.3, - 504.9, - 508.7, - 512.4, - 514.4, - 515.9, - 517.6, - 518.9, - 520.7, - 522.7, - 524.8, - 526.8, - 528, - 529.6, - 535, - 551.9, - 561.2, - 561.3, - 560.8, - 559.5, - 559, - 558.7, - 557.9, - 557.5, - 556.9, - 556, - 555.1, - 554.8, - 554.3, - 552.9, - 551.7, - 550.6, - 550, - 549.5, - 549.1, - 548.9, - 548.4, - 547.8, - 547.3, - 546.4, - 545.7, - 545.2, - 544.6, - 544.1, - 543.6, - 542.7, - 541.9, - 541.4, - 540.8, - 540.2, - 540, - 539.6, - 539, - 538.3, - 537.7, - 536.6, - 535.5, - 534.7, - 534, - 533.3, - 532.8, - 531.9, - 531, - 530.3, - 529.8, - 529.3, - 528.9, - 528, - 527.7, - 527.5, - 527.1, - 526.3, - 525.5, - 524.7, - 524.2, - 523.8, - 523.4, - 523.1, - 522.4, - 521.6, - 520.9, - 520.4, - 519.9, - 519.4, - 518.7, - 518.2, - 517.3, - 516.5, - 515.7, - 515.5, - 515.2, - 514.7, - 514, - 513, - 511.8, - 511.2, - 510.6, - 510.1, - 509.6, - 509.3, - 509, - 509, - 508.5, - 507.4, - 506.9, - 506.5, - 505.8, - 505.2, - 505.1, - 505.2, - 505.1, - 504.7, - 504.4, - 504, - 503.8, - 503.6, - 503, - 502, - 501.4, - 500.5, - 499.7, - 499.5, - 499.6, - 499.5, - 499.4, - 499.1, - 498.8, - 498.6, - 497.7, - 497.2, - 496.6, - 496.1, - 496.2, - 496, - 495.8, - 495.4, - 494.9, - 494.5, - 494.2, - 494, - 493.7, - 493.6, - 493.4, - 493.3, - 492.9, - 492.4, - 491.7, - 490.9, - 490.9, - 490.8, - 490.2, - 490, - 489.7, - 489.4, - 489, - 488.6, - 488.1, - 487.5, - 486.7, - 486.1, - 482.4, - 468.6, - 453.3, - 445.5, - 439.1, - 432.1, - 434, - 438.6, - 441.3, - 443, - 441.6, - 439.1, - 437.6, - 434.1, - 432.1, - 427.7, - 422.7, - 418.8, - 415.9, - 412.8, - 409.5, - 405.8, - 398.8, - 391.4, - 383.8, - 365.5, - 355.5, - 515.8, - 514.8, - 514, - 512.3, - 510.6, - 509.2, - 507.6, - 506.2, - 505.3, - 504.8, - 504.3, - 503.7, - 502.9, - 502.2, - 501.6, - 501, - 500.4, - 499.8, - 499.2, - 498.6, - 498.1, - 497.6, - 497.1, - 496.9, - 496.6, - 496, - 495.4, - 495.3, - 495.2, - 495, - 494.9, - 494.7, - 494.6, - 494.7, - 494.5, - 493.6, - 491.9, - 491.3, - 490.5, - 489.8, - 488.8, - 487.6, - 486.1, - 484.2, - 480.7, - 478.6, - 477.5, - 476.6, - 475.7, - 475, - 474.4, - 473.8, - 473.1, - 472.4, - 472.1, - 472.4, - 473.1, - 474.1, - 475.2, - 476, - 476.8, - 477.4, - 478.2, - 479.7, - 482.2, - 485.5, - 488.5, - 491.7, - 494.7, - 497.8, - 499.3, - 502.3, - 509.1, - 513.6, - 515.3, - 517.4, - 519.5, - 520.5, - 522, - 523.7, - 525.1, - 525.9, - 527.1, - 529.8, - 532.6, - 537.6, - 546.2, - 554.5, - 558.8, - 558.5, - 558.2, - 557.6, - 556.8, - 556.5, - 555.9, - 554.9, - 554.1, - 553.6, - 552.5, - 551.1, - 550.2, - 549.5, - 548.9, - 548.6, - 548.4, - 547.9, - 547.2, - 546.6, - 545.9, - 544.9, - 544.4, - 544, - 543.5, - 543.1, - 542.5, - 541.6, - 540.7, - 540, - 539.3, - 538.9, - 538.8, - 538.5, - 538, - 537.3, - 536.6, - 535.4, - 534.4, - 533.6, - 532.7, - 531.9, - 531.4, - 530.7, - 529.7, - 528.9, - 528.6, - 528.2, - 528, - 527.4, - 527, - 526.8, - 526.3, - 525.6, - 524.7, - 524, - 523.4, - 523, - 522.5, - 522.2, - 521.7, - 520.9, - 520.4, - 519.9, - 519.4, - 518.8, - 517.8, - 517, - 516.3, - 515.5, - 514.6, - 514.2, - 513.8, - 513.4, - 512.6, - 511.7, - 511, - 510.5, - 509.7, - 509.2, - 508.5, - 508.2, - 508, - 507.6, - 506.8, - 505.7, - 505.4, - 505, - 504.5, - 504.2, - 504.2, - 504.2, - 504, - 503.6, - 503.3, - 502.9, - 502.6, - 502.3, - 501.7, - 500.9, - 500.2, - 499.3, - 498.9, - 498.7, - 498.7, - 498.8, - 498.6, - 498.2, - 497.5, - 496.8, - 496.5, - 496.2, - 495.4, - 494.8, - 494.6, - 494.5, - 494.5, - 494.3, - 494.1, - 493.7, - 493.5, - 493.2, - 492.8, - 492.7, - 492.4, - 491.7, - 491, - 490.7, - 490.4, - 490.2, - 489.9, - 489.5, - 489.3, - 489, - 488.8, - 488.6, - 488.1, - 487.5, - 487, - 486.2, - 485.6, - 485.2, - 484.1, - 475.1, - 455, - 444.2, - 438.8, - 431.4, - 427.4, - 428.7, - 432.8, - 436.7, - 436.8, - 432.7, - 428.4, - 427.2, - 425.8, - 422.6, - 420.4, - 418.5, - 415.9, - 412.5, - 410.5, - 403.7, - 392.2, - 388.2, - 380.3, - 366.8, - 357.8, - 514.6, - 513.1, - 511.9, - 510.4, - 509.1, - 507.7, - 506.4, - 505.4, - 504.4, - 503.8, - 503.3, - 502.5, - 501.7, - 501.1, - 500.6, - 499.9, - 499.3, - 498.6, - 497.9, - 497.3, - 496.9, - 496.3, - 495.6, - 495.1, - 494.8, - 494.2, - 493.8, - 493.9, - 493.9, - 494.1, - 494.1, - 493.9, - 493.4, - 493.2, - 492.9, - 492, - 491.1, - 490.6, - 489.6, - 488.5, - 487.2, - 485.9, - 484.5, - 482.2, - 478.8, - 477.4, - 476.4, - 475.6, - 474.8, - 473.9, - 473, - 472.2, - 471.5, - 470.9, - 470.5, - 470.2, - 471.6, - 473.3, - 474.2, - 474.8, - 475.7, - 476.3, - 476.8, - 479, - 483.2, - 486.4, - 489.1, - 492.3, - 494.3, - 496.6, - 498.2, - 501.5, - 508.2, - 513.8, - 515.9, - 517.5, - 518.8, - 519.4, - 520.6, - 522.4, - 524, - 524.5, - 525.9, - 529.2, - 532.1, - 534.8, - 538.5, - 544.2, - 553.6, - 557.1, - 556.9, - 556, - 555.6, - 555.1, - 554.5, - 553.5, - 552.7, - 552, - 551.1, - 550, - 549.4, - 548.8, - 548.1, - 547.7, - 547.6, - 546.9, - 546, - 545.2, - 544.4, - 543.6, - 543.1, - 542.6, - 542.2, - 541.6, - 541.2, - 540.4, - 539.6, - 539, - 538.4, - 537.8, - 537.5, - 537.2, - 536.8, - 536.3, - 535.5, - 534, - 532.6, - 531.6, - 531, - 530.2, - 529.8, - 528.9, - 528, - 527.6, - 527.4, - 527.2, - 526.8, - 526.4, - 526, - 525.2, - 524.6, - 524.2, - 523.4, - 523.1, - 522.6, - 522.2, - 521.7, - 521.3, - 520.8, - 520.1, - 519.5, - 519.2, - 518.5, - 517.7, - 516.9, - 515.9, - 515.4, - 514.6, - 513.9, - 513.2, - 512.5, - 511.5, - 510.6, - 510, - 509.7, - 509.3, - 508.6, - 508.2, - 507.7, - 507.3, - 506.7, - 506.2, - 505.2, - 504.8, - 504.6, - 504.1, - 503.9, - 503.6, - 503, - 502.8, - 502.6, - 502.2, - 501.9, - 501.5, - 501, - 500.9, - 500.2, - 499.5, - 499.3, - 498.9, - 498.4, - 497.9, - 497.7, - 497.7, - 497.3, - 496.7, - 495.8, - 495.1, - 494.9, - 494.6, - 494, - 493.7, - 493.3, - 493.3, - 493.2, - 493, - 492.8, - 492.3, - 492.1, - 492, - 491.7, - 491.5, - 490.9, - 490.2, - 489.9, - 489.6, - 489.4, - 489.2, - 488.7, - 488.3, - 488, - 487.6, - 487.8, - 487.7, - 487, - 486.4, - 485.8, - 485.1, - 484.5, - 484, - 481, - 471.1, - 456.6, - 445.1, - 437.7, - 433.3, - 428.2, - 424.5, - 423.4, - 425.7, - 426.9, - 425.1, - 424.3, - 423.5, - 421.8, - 420.3, - 419.3, - 418.6, - 417.4, - 414.3, - 411.4, - 401.2, - 390.4, - 386.5, - 378.9, - 369.7, - 360, - 513.3, - 511.9, - 510.6, - 509.3, - 508, - 507, - 505.8, - 504.7, - 503.9, - 503.1, - 502.3, - 501.4, - 500.7, - 500.1, - 499.5, - 498.7, - 497.9, - 497, - 496.3, - 495.5, - 494.7, - 493.8, - 493.2, - 493, - 492.5, - 492, - 492.1, - 492.3, - 492.6, - 493.1, - 492.9, - 492.7, - 492.3, - 491.8, - 491.2, - 490.5, - 489.7, - 488.8, - 487.7, - 486.4, - 484.9, - 483.4, - 481.9, - 479.5, - 477.2, - 476, - 475, - 474.2, - 473.4, - 472.5, - 471.5, - 470.7, - 470, - 469.5, - 469.1, - 468.2, - 469.9, - 472.4, - 473.2, - 473.4, - 474.1, - 474.9, - 475.5, - 478.3, - 482.3, - 485.4, - 488.4, - 491.3, - 494.3, - 497.1, - 499.8, - 504.3, - 509.2, - 512.1, - 513.8, - 514.4, - 514.8, - 515.8, - 518.3, - 520, - 521.8, - 522.7, - 524.2, - 527.2, - 530.4, - 534.1, - 537.8, - 542.6, - 551.3, - 555.5, - 555.8, - 555.3, - 554.4, - 553.7, - 553.1, - 552.2, - 551.4, - 550.8, - 550, - 548.9, - 548.2, - 547.7, - 547.2, - 546.8, - 546.3, - 545.4, - 544.8, - 543.9, - 543, - 542.4, - 542, - 541.2, - 540.5, - 540.1, - 539.7, - 539.1, - 538.6, - 538.1, - 537.6, - 536.9, - 536.3, - 535.7, - 535.3, - 534.9, - 534.3, - 532.6, - 531.3, - 530.2, - 529.3, - 528.7, - 528.2, - 527.5, - 526.9, - 526.7, - 526.7, - 526.1, - 525.4, - 524.8, - 524.2, - 523.1, - 522.4, - 521.8, - 521.6, - 521.7, - 521.5, - 521.1, - 520.5, - 519.9, - 519.5, - 519.2, - 518.2, - 517.3, - 516.8, - 516.5, - 515.7, - 514.4, - 513.8, - 513.3, - 512.9, - 512.1, - 511.3, - 510.3, - 509.3, - 508.7, - 508.4, - 507.9, - 507.4, - 507.2, - 506.6, - 506.2, - 505.8, - 505.4, - 504.8, - 504.3, - 503.8, - 503.3, - 503.1, - 502.9, - 502.1, - 501.5, - 501.1, - 500.7, - 500.4, - 500, - 499.5, - 499.1, - 498.5, - 498.1, - 498, - 497.9, - 497.7, - 497, - 496.5, - 496.3, - 496, - 495, - 494.3, - 494, - 493.7, - 493.3, - 492.7, - 492.4, - 492.2, - 492.1, - 492, - 491.8, - 491.5, - 491.2, - 491.1, - 491, - 490.8, - 490.6, - 490.1, - 489.8, - 489.1, - 488.4, - 488.1, - 487.8, - 487.4, - 487.1, - 486.6, - 486.1, - 486.2, - 486.1, - 485.7, - 485.3, - 484.8, - 484.1, - 483.5, - 482.9, - 479.2, - 469, - 456.4, - 450, - 440.9, - 434.2, - 430.4, - 426.5, - 421.4, - 419.6, - 419.7, - 420.1, - 420.7, - 420.4, - 419.9, - 419.2, - 418.8, - 418.3, - 417.1, - 414.1, - 411.5, - 401.3, - 389, - 385.8, - 378.8, - 370.7, - 363.6, - 511.8, - 510, - 508.6, - 507.8, - 507, - 506, - 504.5, - 503.7, - 503, - 502.2, - 501.2, - 500.3, - 499.6, - 498.9, - 497.9, - 497.1, - 496.1, - 495.2, - 494.2, - 493, - 492.1, - 491.4, - 490.7, - 490.2, - 489.9, - 490.1, - 490.4, - 490.9, - 491.3, - 491.6, - 491.6, - 491.5, - 491.2, - 490.8, - 490, - 489, - 487.9, - 486.7, - 485.6, - 484.4, - 483.2, - 481.6, - 479.4, - 477.1, - 475.4, - 474.3, - 473.4, - 472.7, - 471.8, - 470.9, - 470.1, - 469.2, - 468.5, - 468.2, - 467.6, - 467.1, - 467.4, - 469.8, - 471.2, - 471.3, - 472.4, - 473.6, - 474.4, - 477, - 480.5, - 483.2, - 488.2, - 492.6, - 496.1, - 499.4, - 502.1, - 505.3, - 507.1, - 508.1, - 509.1, - 509.9, - 511.6, - 513.9, - 516.1, - 517.5, - 519.9, - 521.8, - 523.3, - 525.8, - 529.5, - 533.4, - 537.4, - 540.9, - 547.3, - 552.7, - 554.1, - 554.1, - 553.5, - 552.9, - 552, - 551.2, - 550.5, - 549.6, - 548.9, - 548, - 547.3, - 546.8, - 546.4, - 545.9, - 545.3, - 544.6, - 544.1, - 543, - 542.1, - 541.5, - 540.9, - 540.1, - 539.5, - 539.1, - 538.5, - 538, - 537.4, - 537, - 536.3, - 535.9, - 535.2, - 534.6, - 534.2, - 533.7, - 532.8, - 531.2, - 530, - 529.1, - 528.5, - 528.1, - 527.4, - 526.6, - 526.2, - 526.1, - 526, - 525.3, - 524.4, - 523.4, - 522.2, - 521.4, - 520.9, - 520.4, - 520.4, - 520.3, - 520.1, - 519.8, - 519.2, - 518.7, - 518.3, - 517.8, - 517, - 515.9, - 515.1, - 514.7, - 514.1, - 513.2, - 512.6, - 512.1, - 511.5, - 510.5, - 509.8, - 509, - 507.9, - 507.4, - 507.2, - 506.9, - 506.5, - 506.2, - 505.8, - 505.2, - 505, - 504.6, - 504.3, - 503.7, - 503, - 502.5, - 502.2, - 502.1, - 501.3, - 500.4, - 499.9, - 499.6, - 498.9, - 498.6, - 498.4, - 498, - 497.5, - 497.3, - 497, - 496.2, - 495.8, - 495.5, - 495.1, - 494.9, - 494.3, - 493.2, - 493.1, - 493.1, - 492.6, - 492.1, - 491.5, - 491.2, - 491.1, - 490.9, - 490.7, - 490.6, - 490.4, - 490.3, - 490.5, - 490.2, - 489.9, - 489.6, - 489.2, - 488.9, - 488.2, - 487.6, - 487.1, - 486.7, - 486.4, - 486, - 485.5, - 485.2, - 484.8, - 484.3, - 484.1, - 483.9, - 483.7, - 483.2, - 482.4, - 481.7, - 480.2, - 475.2, - 470.5, - 469.8, - 454.8, - 440.9, - 432.3, - 427.8, - 422.1, - 417.4, - 416.6, - 416.5, - 417.2, - 418.1, - 418.3, - 418.2, - 417.7, - 416.5, - 414.4, - 412.4, - 408.8, - 396.6, - 388.1, - 386.1, - 382.3, - 373.5, - 365.8, - 509.7, - 507.9, - 507, - 506.4, - 505.8, - 504.7, - 503.1, - 502.2, - 501.5, - 500.7, - 499.8, - 498.9, - 498, - 497, - 496, - 494.9, - 494, - 493.1, - 492, - 490.9, - 490.1, - 489.1, - 488.3, - 487.9, - 488, - 488.2, - 488.7, - 489.3, - 490, - 490.2, - 490.3, - 490.4, - 490.1, - 489.5, - 488.6, - 487.3, - 486, - 485, - 483.8, - 482.6, - 481.2, - 478.5, - 476.2, - 474.8, - 473.7, - 472.8, - 471.9, - 471, - 470.2, - 469.3, - 468.4, - 467.6, - 467, - 466.6, - 466, - 465.7, - 465.8, - 466.7, - 468.4, - 469.4, - 471.3, - 472.4, - 473.2, - 475.5, - 479, - 484, - 491, - 494.9, - 497.7, - 499.6, - 501.3, - 503.1, - 504.8, - 505.9, - 507.4, - 508.9, - 510.6, - 512.6, - 514.2, - 516.3, - 519.6, - 522.5, - 524.3, - 526, - 529.3, - 533.1, - 537.6, - 542.2, - 547.4, - 552.1, - 553.4, - 552.9, - 552.4, - 551.8, - 550.8, - 549.7, - 548.9, - 548.3, - 547.9, - 547.1, - 546.6, - 546.3, - 545.8, - 545, - 544.4, - 543.9, - 543.1, - 542.1, - 541.2, - 540.2, - 539.8, - 539.4, - 538.9, - 538.4, - 537.9, - 537.2, - 536.6, - 536, - 535.3, - 534.9, - 534.3, - 533.6, - 533, - 532.5, - 531.5, - 530, - 529.3, - 528.7, - 528.2, - 527.5, - 526.8, - 525.9, - 525.5, - 525.2, - 524.7, - 524.2, - 523.2, - 522.1, - 520.8, - 520.3, - 519.8, - 519.4, - 519.4, - 519.1, - 518.9, - 518.6, - 518.2, - 517.8, - 517, - 516.1, - 515.5, - 514.7, - 513.9, - 513, - 512.2, - 511.9, - 511.3, - 510.6, - 510.1, - 509.3, - 508.6, - 507.7, - 507, - 506.7, - 506.3, - 506, - 505.6, - 505.2, - 504.7, - 504.2, - 504, - 503.7, - 503.4, - 503, - 502.2, - 501.6, - 501.1, - 500.5, - 499.7, - 499.3, - 499.2, - 499, - 498.2, - 497.9, - 497.5, - 497, - 496.6, - 496.2, - 495.6, - 494.6, - 494, - 493.9, - 494, - 493.6, - 492.9, - 492, - 491.9, - 491.9, - 491.6, - 491.1, - 490.6, - 490.4, - 490.4, - 490.2, - 489.9, - 489.6, - 489.3, - 489.1, - 489.1, - 489, - 488.8, - 488.5, - 488.2, - 487.9, - 487.3, - 486.7, - 486.1, - 485.6, - 485.3, - 485, - 484.6, - 484.2, - 483.9, - 483.5, - 483.1, - 482.6, - 482.2, - 481.8, - 481.3, - 480.4, - 479.4, - 479, - 478.1, - 475.7, - 467.8, - 454.8, - 443.1, - 432.8, - 423.6, - 415.7, - 415, - 414.9, - 415.4, - 416.8, - 417, - 416.3, - 414.9, - 412.7, - 410.5, - 407.3, - 398.2, - 388.4, - 386.7, - 385, - 380.5, - 371.1, - 362, - 508.2, - 506.5, - 505.7, - 505.1, - 504.5, - 503.3, - 501.4, - 500.3, - 499.4, - 498.4, - 497.4, - 496.3, - 495.3, - 494.2, - 493.3, - 492.5, - 491.8, - 490.8, - 489.8, - 488.8, - 487.7, - 486.7, - 486, - 486.4, - 486.8, - 487, - 487.2, - 487.5, - 488.1, - 488.8, - 489, - 489, - 488.8, - 488, - 486.5, - 485.2, - 484.4, - 483.3, - 481.9, - 480.4, - 477.7, - 475.2, - 473.8, - 473, - 472.2, - 471.3, - 470.3, - 469.4, - 468.6, - 467.6, - 466.7, - 466, - 465.5, - 465.1, - 464.5, - 464.2, - 464.4, - 465.3, - 466.7, - 468.1, - 469.7, - 471, - 472.2, - 476, - 479.8, - 485.2, - 490.8, - 494.4, - 496.5, - 498.1, - 500, - 501.6, - 502.9, - 504.3, - 506.2, - 508, - 509.6, - 511.7, - 514.9, - 519.4, - 524.3, - 526.8, - 527.1, - 528.4, - 532.2, - 537.1, - 540.1, - 544, - 548.2, - 551.4, - 552.7, - 552.2, - 551.7, - 551, - 550.1, - 548.9, - 548, - 547.3, - 546.7, - 546.2, - 545.8, - 545.4, - 544.7, - 544, - 543.4, - 543, - 542.3, - 541.3, - 540.2, - 539.3, - 539, - 538.7, - 538.3, - 537.8, - 537.2, - 536.6, - 535.9, - 534.9, - 534.2, - 533.9, - 533.1, - 532, - 531.3, - 531.2, - 530.2, - 528.8, - 528.2, - 527.8, - 527.3, - 526.6, - 525.9, - 525.1, - 524.9, - 524.5, - 524, - 523, - 522, - 521.1, - 520.1, - 519.6, - 519.2, - 518.7, - 518.2, - 517.9, - 517.7, - 517.4, - 517.1, - 516.5, - 515.8, - 515.1, - 514.4, - 513.8, - 513.1, - 512.1, - 511.2, - 510.3, - 509.6, - 509.3, - 509.3, - 508.5, - 508, - 507, - 506.4, - 506.1, - 505.6, - 504.9, - 504.4, - 504.1, - 503.5, - 503.2, - 503.1, - 502.9, - 502.6, - 502.1, - 501.4, - 500.7, - 500.1, - 499.3, - 498.5, - 498.3, - 498.1, - 497.6, - 497.4, - 497.1, - 496.7, - 496.2, - 495.8, - 495.6, - 494.8, - 494.1, - 493.7, - 493.3, - 493.3, - 492.7, - 491.9, - 491.2, - 490.8, - 490.7, - 490.3, - 490.1, - 489.9, - 489.8, - 489.5, - 489.1, - 488.9, - 488.4, - 488.1, - 488.1, - 487.8, - 487.6, - 487.7, - 487.5, - 487.4, - 486.9, - 486.1, - 485.6, - 485.1, - 484.5, - 484, - 483.6, - 483.4, - 483.3, - 482.9, - 482.3, - 482, - 481.5, - 480.6, - 480.1, - 479.9, - 479.5, - 478.4, - 477.5, - 476.1, - 474.6, - 470.6, - 459.9, - 447.9, - 440.8, - 427.2, - 414.6, - 413.6, - 413.9, - 414, - 414.6, - 414.1, - 412.1, - 409.8, - 403.2, - 397.7, - 393.1, - 389, - 386.1, - 385.2, - 382.1, - 374.5, - 364.1, - 349.4, - 507.2, - 505.4, - 504.5, - 503.7, - 503, - 501.7, - 499.7, - 498.5, - 497.3, - 495.9, - 494.7, - 493.6, - 492.6, - 491.6, - 490.8, - 490.1, - 489.4, - 488.2, - 487.1, - 485.9, - 484.6, - 483.7, - 483.8, - 484.7, - 485.6, - 485.9, - 485.9, - 486, - 486.1, - 486.4, - 486.9, - 487.1, - 486.7, - 485.2, - 483.8, - 483, - 482.2, - 481.2, - 479.9, - 477.2, - 474.5, - 473.4, - 472.4, - 471.5, - 470.7, - 469.7, - 468.8, - 468.1, - 467.3, - 466.2, - 465.1, - 464.4, - 463.9, - 463.4, - 463, - 462.7, - 463.2, - 463.9, - 464.7, - 466.5, - 468.3, - 469.4, - 470.6, - 476, - 480.3, - 484, - 487.8, - 491, - 493.3, - 495.3, - 497.4, - 498.8, - 500.4, - 502.4, - 504.6, - 506.9, - 509.6, - 513.5, - 518.5, - 522.5, - 526.8, - 532.1, - 535.4, - 537.1, - 539.8, - 543.1, - 544.9, - 548.9, - 550.6, - 551.5, - 551.7, - 551.4, - 550.7, - 550, - 549.1, - 548, - 547.3, - 546.7, - 546.1, - 545.6, - 545.4, - 545.5, - 544.2, - 543, - 542.5, - 542, - 541.3, - 540.3, - 539.6, - 538.8, - 538.1, - 537.8, - 537.4, - 537, - 536.4, - 535.6, - 534.8, - 534, - 533.4, - 532.8, - 532.1, - 531, - 529.8, - 529.3, - 528.5, - 527.4, - 526.9, - 526.5, - 526, - 525.6, - 525, - 524.4, - 524.1, - 523.6, - 523, - 522, - 520.7, - 519.9, - 519.1, - 518.5, - 518.2, - 517.8, - 517.3, - 517.1, - 516.7, - 516.4, - 516.1, - 515.5, - 515, - 514.3, - 513.6, - 512.8, - 512, - 511.1, - 510.5, - 509.4, - 508.4, - 507.9, - 507.7, - 507.3, - 506.9, - 506, - 505.5, - 505.2, - 504.9, - 504.1, - 503.5, - 502.8, - 502.6, - 502.2, - 501.8, - 501.4, - 501, - 500.7, - 500.1, - 499.4, - 498.6, - 498, - 497.4, - 497.1, - 496.8, - 496.5, - 496.3, - 496.2, - 496, - 495.5, - 494.8, - 494.3, - 493.4, - 492.9, - 492.6, - 492.6, - 492.4, - 491.7, - 491.1, - 490.6, - 490.1, - 489.6, - 489.2, - 489.1, - 488.8, - 488.7, - 488.3, - 488, - 487.9, - 487.6, - 487.2, - 487.1, - 486.9, - 486.7, - 486.8, - 486.9, - 486.5, - 485.9, - 485.2, - 484.7, - 484.3, - 483.6, - 483.1, - 482.7, - 482.5, - 482.4, - 481.9, - 481.3, - 481, - 480.2, - 479.6, - 479.2, - 478.5, - 477.9, - 477, - 475.9, - 474, - 472.9, - 469.6, - 461.9, - 447.2, - 439.9, - 429.6, - 415.2, - 412.7, - 412.6, - 411.4, - 410.8, - 408.4, - 403, - 398.4, - 394.8, - 390.4, - 389, - 386.5, - 383.9, - 380.7, - 374.7, - 363.7, - 350.7, - 344.3, - 507.8, - 505.7, - 503.5, - 502.3, - 501.5, - 499.6, - 498.1, - 497.1, - 496.1, - 494.6, - 493.1, - 491.9, - 490.9, - 489.6, - 488.4, - 487.3, - 486.2, - 485.1, - 483.7, - 482.8, - 481.8, - 481.1, - 481.4, - 482.6, - 484.2, - 484.5, - 484.4, - 484.5, - 484.5, - 484.3, - 484.2, - 484.1, - 483.6, - 482.5, - 481.5, - 480.7, - 479.8, - 478.7, - 476.1, - 473.6, - 472.5, - 471.8, - 470.9, - 469.9, - 469.1, - 468, - 467.3, - 466.4, - 465.5, - 464.6, - 463.7, - 462.9, - 462.4, - 461.9, - 461.5, - 461.4, - 462, - 462.7, - 463.3, - 464.4, - 466.2, - 467.3, - 468.4, - 471.8, - 476.3, - 480.5, - 483.4, - 486.6, - 489.7, - 491.8, - 493.9, - 496, - 499.1, - 502, - 504.6, - 507.7, - 511.5, - 515.8, - 519, - 521.8, - 525.1, - 532, - 539.1, - 541.5, - 543.7, - 546, - 548.4, - 550.8, - 551.5, - 551.5, - 551, - 550.4, - 549.6, - 549, - 548.2, - 547, - 546.3, - 545.9, - 545.3, - 544.9, - 544.8, - 544.9, - 543.4, - 542.2, - 541.6, - 540.9, - 540, - 539.1, - 538.9, - 538, - 537.1, - 536.6, - 536.3, - 536, - 535.4, - 534.6, - 533.7, - 533.1, - 532.4, - 531.7, - 531, - 529.7, - 528.3, - 527.5, - 526.8, - 526.4, - 526, - 525.5, - 524.8, - 524.1, - 523.8, - 523.5, - 523, - 522, - 521.3, - 520.5, - 519.5, - 518.9, - 518.1, - 517.2, - 517, - 516.7, - 516.2, - 516.2, - 516, - 515.5, - 515, - 514.5, - 513.9, - 513.2, - 512.5, - 511.6, - 510.6, - 509.8, - 509.4, - 508.6, - 507.8, - 507.1, - 506.5, - 505.9, - 505.6, - 504.9, - 504.6, - 504.2, - 503.8, - 503.3, - 502.6, - 501.8, - 501.6, - 501.1, - 500.4, - 499.6, - 499.2, - 498.8, - 498.3, - 497.7, - 497, - 496.4, - 496.2, - 496, - 495.6, - 495.5, - 495.4, - 495.3, - 495.2, - 494.5, - 493.3, - 492.8, - 492.2, - 491.9, - 491.6, - 491.4, - 491.2, - 490.7, - 490.1, - 489.8, - 489.3, - 488.8, - 488.3, - 488.1, - 487.9, - 487.6, - 487.3, - 487.1, - 486.9, - 486.7, - 486.3, - 486.2, - 486.1, - 485.8, - 485.7, - 485.5, - 485.3, - 485, - 484.2, - 483.8, - 483.2, - 482.7, - 482.4, - 482.2, - 481.8, - 481.5, - 481.1, - 480.6, - 480.1, - 479.2, - 478.9, - 478.2, - 477.3, - 476.6, - 475.6, - 473.7, - 471.4, - 470.3, - 467.3, - 463, - 450.1, - 439.2, - 430.2, - 418.2, - 410.4, - 407.2, - 406.1, - 403, - 398.9, - 394.6, - 388.1, - 387.3, - 386.7, - 383.8, - 381, - 377, - 372.8, - 365.4, - 351.3, - 346.4, - 345.1, - 508.4, - 507, - 504, - 501.1, - 499.7, - 497.6, - 496.6, - 495.6, - 494.6, - 492.9, - 491.5, - 490.5, - 489.6, - 488.2, - 486.3, - 485.1, - 484.1, - 483.1, - 481.7, - 480.7, - 479.8, - 479.2, - 479.6, - 480.6, - 481.5, - 481.9, - 482.1, - 482.5, - 482.5, - 482.4, - 482.1, - 481.7, - 481.3, - 480.5, - 479.3, - 478, - 476.7, - 475, - 472.3, - 471.1, - 470.4, - 469.7, - 469, - 468.1, - 467, - 466.2, - 465.4, - 464.7, - 463.9, - 463.1, - 462.3, - 461.5, - 460.8, - 460.3, - 459.9, - 460, - 460.4, - 461.3, - 462.3, - 463.5, - 464.7, - 465.9, - 467.8, - 471.4, - 475.7, - 479.6, - 482.6, - 485.8, - 489.2, - 491.8, - 494.6, - 497.4, - 500.9, - 504.2, - 507.2, - 510.1, - 512.1, - 515.9, - 518.6, - 520.6, - 523.7, - 529.3, - 541.3, - 546.7, - 548, - 549, - 550.2, - 551.5, - 551.4, - 550.8, - 549.9, - 549.2, - 548.3, - 547.7, - 546.9, - 545.6, - 545.2, - 544.9, - 544.5, - 544, - 543.4, - 543, - 542, - 541.2, - 540.5, - 539.8, - 539, - 538.3, - 537.6, - 536.6, - 536, - 535.4, - 535, - 534.7, - 534.2, - 533.6, - 532.9, - 532.2, - 531.5, - 530.8, - 530.1, - 528.8, - 527.4, - 526.3, - 525.8, - 525.6, - 525.2, - 524.6, - 524, - 523.1, - 522.9, - 522.5, - 521.6, - 520.8, - 520.1, - 519.4, - 518.8, - 517.9, - 516.7, - 516.2, - 516, - 515.6, - 515.3, - 515.3, - 515.1, - 514.5, - 514, - 513.5, - 512.9, - 512, - 511.1, - 510.3, - 509.3, - 508.5, - 508, - 507.5, - 506.9, - 506.2, - 505.8, - 505, - 504.6, - 504.1, - 503.8, - 503.3, - 502.8, - 502.4, - 501.5, - 500.9, - 500.2, - 499.8, - 499.2, - 498.7, - 498, - 497.4, - 496.5, - 496, - 495.8, - 495.6, - 495.4, - 495, - 494.8, - 494.7, - 494.7, - 494.5, - 494, - 492.9, - 491.9, - 491.5, - 491.1, - 490.8, - 490.5, - 490.2, - 489.6, - 489, - 488.5, - 488.4, - 488, - 487.4, - 487.1, - 487, - 486.7, - 486.5, - 486.2, - 486.1, - 485.9, - 485.6, - 485.4, - 485.4, - 485.2, - 485.1, - 484.9, - 484.6, - 484.1, - 483.8, - 483.2, - 482.5, - 481.7, - 481.6, - 481.5, - 481.5, - 481, - 480.4, - 480, - 479.6, - 479, - 478.3, - 477.9, - 477.2, - 476.1, - 475, - 473.2, - 471, - 469.1, - 466.8, - 464.9, - 461.4, - 454.8, - 440.2, - 428.9, - 419.8, - 408.7, - 396.9, - 397.2, - 390.9, - 387.7, - 385.4, - 381.8, - 382.2, - 382.3, - 381.4, - 378.1, - 372.4, - 366.1, - 357.3, - 351.3, - 348, - 347.1, - 508.6, - 507.4, - 504.7, - 500.4, - 498.2, - 496.4, - 495.1, - 493.6, - 492.3, - 490.9, - 489.6, - 488.4, - 487.5, - 486.1, - 484.2, - 482.9, - 481.9, - 480.5, - 479.2, - 478.1, - 477.2, - 477.1, - 478.2, - 478.9, - 479.4, - 479.7, - 479.6, - 479.6, - 479.6, - 479.5, - 478.9, - 478.6, - 477.9, - 476.7, - 475.2, - 473.3, - 471.7, - 470.7, - 469.9, - 469.2, - 468.4, - 467.7, - 467.2, - 466.3, - 465.2, - 464.4, - 463.7, - 463, - 462.4, - 461.6, - 460.8, - 460, - 459.2, - 458.7, - 458.5, - 458.7, - 459.2, - 460.2, - 461.6, - 463.1, - 464.3, - 465.7, - 468.2, - 471.1, - 475.2, - 480.7, - 484.1, - 487.2, - 490.6, - 494.5, - 497.1, - 499.3, - 501.9, - 504.9, - 507.6, - 509.2, - 510.8, - 513.6, - 516.7, - 518.8, - 522, - 527.9, - 544.1, - 551.8, - 551.5, - 551.4, - 551.7, - 551.7, - 550.3, - 549.7, - 548.6, - 548.1, - 547.4, - 546.7, - 545.9, - 545, - 544.5, - 544.2, - 543.7, - 543.1, - 542.4, - 542.1, - 541.1, - 540.2, - 539.6, - 538.9, - 538.1, - 537.4, - 536.5, - 535.7, - 535.2, - 534.7, - 534.1, - 533.5, - 533.1, - 532.5, - 531.8, - 531.1, - 530.7, - 529.8, - 528.8, - 527.4, - 526.4, - 525.7, - 524.9, - 524.6, - 524.2, - 523.8, - 523.2, - 522.4, - 522, - 521.4, - 520.4, - 519.6, - 518.7, - 518.3, - 517.8, - 516.5, - 515.4, - 515, - 515, - 514.5, - 514.1, - 513.6, - 513.4, - 513.3, - 512.9, - 512.4, - 511.8, - 511, - 510.1, - 509.2, - 508.3, - 507.7, - 507.5, - 506.9, - 506.3, - 505.5, - 504.9, - 504.1, - 503.5, - 503.3, - 503, - 502.4, - 501.8, - 501.2, - 500.6, - 500.3, - 499.5, - 499, - 498.5, - 497.7, - 497.1, - 496.4, - 495.1, - 494.5, - 494.6, - 494.7, - 494.4, - 494, - 493.7, - 493.8, - 493.8, - 493.2, - 492.3, - 491.3, - 490.6, - 490.2, - 489.9, - 489.5, - 489.2, - 488.9, - 487.9, - 487.3, - 487.2, - 487, - 486.5, - 486.4, - 486.1, - 485.9, - 485.6, - 485.3, - 484.8, - 484.7, - 484.6, - 484.5, - 484.4, - 484.4, - 484.3, - 484.1, - 483.8, - 483.5, - 483, - 482.5, - 482, - 481.4, - 480.8, - 480.5, - 480.3, - 480.2, - 479.8, - 479.3, - 479.1, - 478.7, - 478.3, - 477.6, - 476.8, - 476.1, - 474.9, - 473.3, - 471.3, - 469.5, - 467.8, - 464.4, - 460.4, - 455.9, - 450.8, - 439.4, - 427.3, - 418, - 411.6, - 395, - 383.6, - 380.3, - 379.6, - 379.3, - 378.8, - 379.3, - 379.5, - 378.8, - 373.9, - 363.5, - 357.2, - 354.4, - 351.7, - 349.7, - 349.1, - 507.6, - 506.9, - 505, - 500.4, - 497, - 495.3, - 493.4, - 491.5, - 489.8, - 488.4, - 487.2, - 485.4, - 484.4, - 483.4, - 482, - 480.5, - 479.2, - 477.8, - 476.7, - 475.7, - 475.1, - 475.6, - 476.6, - 477.2, - 478, - 478.2, - 477.9, - 477.4, - 476.8, - 475.9, - 474.5, - 473.3, - 472.1, - 471, - 470.1, - 469.4, - 468.8, - 468.3, - 467.7, - 467, - 466.1, - 465.4, - 464.8, - 463.9, - 462.7, - 461.6, - 461.2, - 460.9, - 460.5, - 460, - 459.2, - 458.5, - 457.6, - 457.2, - 457.3, - 457.7, - 458.3, - 459.4, - 461, - 462.7, - 464.2, - 466, - 468.6, - 471, - 473.7, - 478.9, - 484, - 487.2, - 489.6, - 493.9, - 497, - 499.2, - 501.2, - 503.3, - 505.3, - 507.4, - 509.8, - 511.6, - 514.8, - 517.4, - 520, - 525.2, - 535.6, - 547.8, - 551.8, - 551.2, - 550.7, - 550.4, - 549, - 548.4, - 547.8, - 547.4, - 546.8, - 546.2, - 545.5, - 544.7, - 544.1, - 543.6, - 542.9, - 542.2, - 541.4, - 540.8, - 539.9, - 539.5, - 538.8, - 538, - 537.1, - 536.4, - 535.9, - 535.2, - 534.4, - 533.6, - 533.1, - 532.4, - 531.7, - 531, - 530.5, - 530, - 529.3, - 528.5, - 526.9, - 525.8, - 525, - 524.4, - 523.5, - 523.5, - 523.3, - 522.8, - 522, - 521.2, - 520.8, - 520, - 519, - 518.3, - 517.4, - 516.9, - 516.2, - 515.1, - 514.1, - 513.6, - 513.3, - 512.7, - 512, - 511.8, - 511.9, - 511.9, - 511.5, - 511.2, - 510.6, - 509.9, - 509.2, - 508.3, - 507.6, - 507, - 506.8, - 506.2, - 505.5, - 504.8, - 504.1, - 503.3, - 502.8, - 502.4, - 502.1, - 501.6, - 500.9, - 500.2, - 499.7, - 499.1, - 498.7, - 498, - 497.3, - 496.7, - 496.1, - 495.2, - 494, - 493.5, - 493.5, - 493.5, - 493.1, - 492.8, - 492.6, - 492.5, - 492.3, - 491.6, - 490.8, - 490, - 489.3, - 488.8, - 488.6, - 488.3, - 487.9, - 487.5, - 486.6, - 486.1, - 485.4, - 485.1, - 484.7, - 484.9, - 484.9, - 484.8, - 484.6, - 484, - 483.6, - 483.5, - 483.4, - 483.5, - 483.4, - 483.4, - 483.2, - 483, - 482.8, - 482.3, - 481.7, - 481.2, - 480.9, - 480.6, - 479.8, - 479.3, - 478.8, - 478.7, - 478.5, - 478.3, - 478.4, - 477.8, - 477.4, - 476.5, - 475.8, - 475.3, - 474.4, - 472.9, - 471, - 468.9, - 467, - 462.7, - 455, - 450.9, - 445.2, - 436.9, - 427.6, - 417.7, - 409.8, - 394.9, - 379.3, - 377.3, - 376.2, - 375.9, - 376, - 376.2, - 374.8, - 368.8, - 361.3, - 357.4, - 355.8, - 354.3, - 352.8, - 352, - 351.4, - 505.9, - 505.7, - 505.4, - 502.3, - 497, - 494.3, - 492.2, - 490.1, - 488.5, - 487.3, - 485.6, - 484, - 482.9, - 481.7, - 480.2, - 478.5, - 476.8, - 475.5, - 474.5, - 473.7, - 473.5, - 474.1, - 474.6, - 474.9, - 475.3, - 475.2, - 474.7, - 473.7, - 472.4, - 471.2, - 469.9, - 469.1, - 468.7, - 468.1, - 467.6, - 467, - 466.5, - 466, - 465.3, - 464.5, - 463.5, - 462.6, - 461.6, - 460.2, - 458.9, - 458.3, - 458.2, - 458.1, - 457.9, - 457.5, - 457, - 456.3, - 455.7, - 455.6, - 456.1, - 456.5, - 457.5, - 459.2, - 460.7, - 462.3, - 463.9, - 465.6, - 467.7, - 469.8, - 472.7, - 476.7, - 481, - 484.4, - 487.6, - 492.2, - 495.6, - 499.3, - 501.2, - 503, - 504.8, - 507.1, - 510.4, - 512.9, - 515.9, - 518, - 519.5, - 521.5, - 526.3, - 542.9, - 549.1, - 549.3, - 549, - 548.9, - 548.5, - 547.7, - 547.1, - 546.7, - 545.8, - 545.2, - 544.6, - 544.1, - 543.6, - 542.9, - 542, - 541, - 540.4, - 539.8, - 539.1, - 538.3, - 537.3, - 536.5, - 536, - 535.4, - 534.8, - 534.1, - 533.3, - 532.5, - 532, - 531.5, - 530.6, - 529.7, - 529.3, - 528.9, - 528.3, - 527.4, - 525.2, - 524.5, - 524, - 523.6, - 522.8, - 522.5, - 522.2, - 521.7, - 520.7, - 519.7, - 519.2, - 518.2, - 517.6, - 517, - 516.4, - 515.7, - 514.7, - 513.8, - 513.2, - 512.6, - 511.9, - 511.1, - 510.5, - 510.3, - 510.3, - 510.3, - 509.9, - 509.5, - 509.1, - 508.7, - 508.3, - 507.2, - 506.5, - 506.1, - 505.7, - 505.2, - 504.5, - 503.9, - 503.2, - 502.1, - 501.7, - 501.4, - 501.2, - 500.7, - 500.1, - 499.5, - 498.9, - 498.2, - 497.6, - 496.8, - 496.1, - 495.6, - 494.9, - 493.7, - 492.9, - 492.6, - 492.4, - 492.3, - 491.8, - 491.5, - 491.4, - 491.1, - 490.7, - 490.2, - 489.1, - 488.3, - 488.1, - 487.7, - 487.4, - 487.1, - 486.9, - 486.4, - 485.6, - 484.8, - 484, - 483.6, - 483.1, - 483.3, - 483.6, - 483.7, - 483.6, - 483.2, - 482.8, - 482.5, - 482.2, - 482.3, - 482.3, - 482.1, - 481.9, - 481.8, - 481.7, - 481.2, - 480.3, - 479.5, - 479.4, - 479, - 478.4, - 478, - 477.8, - 477.6, - 477.4, - 477.1, - 477.1, - 476.6, - 476.3, - 475.5, - 474.9, - 474.6, - 473.8, - 472.5, - 470.8, - 469.1, - 466.2, - 460.7, - 452.7, - 447.8, - 441.1, - 436.3, - 431.9, - 421, - 409.7, - 397.8, - 379.5, - 375.5, - 374.1, - 372.8, - 372.1, - 370.6, - 365.5, - 361.4, - 359.6, - 358.1, - 356.8, - 356, - 355.3, - 355.4, - 354.6, - 504.3, - 504.4, - 504.4, - 503.1, - 499.8, - 496.2, - 492.9, - 489.7, - 487.8, - 486.2, - 484.3, - 482.8, - 481.4, - 479.4, - 477.7, - 476.7, - 475, - 473.4, - 472.5, - 471.9, - 472, - 472.2, - 472.4, - 472.4, - 472.4, - 472, - 471.2, - 470.1, - 469.2, - 468.4, - 467.6, - 467, - 466.5, - 465.9, - 465.4, - 464.8, - 464.2, - 463.5, - 462.7, - 461.9, - 460.9, - 459.5, - 457.6, - 456.6, - 456.1, - 455.6, - 455.4, - 455.3, - 455.2, - 454.8, - 454.4, - 454, - 454, - 454.3, - 454.8, - 455.3, - 456.6, - 459.1, - 460.8, - 462.4, - 463.7, - 465.2, - 467, - 469, - 471.8, - 475.6, - 479.2, - 483.1, - 487.5, - 492.1, - 496.2, - 500.2, - 502.6, - 504.9, - 508.5, - 511.9, - 513.8, - 515.9, - 517.3, - 518.8, - 522, - 526.2, - 528.9, - 534, - 537.6, - 539.9, - 544.3, - 547.4, - 547.5, - 546.9, - 546.4, - 545.8, - 544.8, - 543.6, - 543.1, - 542.9, - 542.4, - 541.5, - 540.4, - 539.8, - 539.3, - 538.9, - 538.2, - 537.3, - 536.2, - 535.5, - 534.7, - 534.2, - 533.5, - 532.7, - 532.2, - 531.5, - 531, - 530.4, - 529.4, - 528.7, - 528.2, - 527.7, - 527.4, - 526.1, - 524.2, - 523.4, - 522.9, - 522.5, - 521.9, - 521.6, - 521.1, - 520.3, - 519.2, - 518.4, - 518.1, - 517.3, - 516.5, - 515.6, - 515, - 514.4, - 513.6, - 513, - 512.2, - 511.6, - 511.2, - 510.6, - 509.9, - 509.5, - 509.1, - 508.6, - 508, - 507.7, - 507.8, - 507.6, - 507.2, - 506.4, - 505.6, - 505.1, - 504.7, - 504, - 503.1, - 502.5, - 501.9, - 501, - 500.6, - 500.4, - 500.1, - 499.6, - 499.1, - 498.5, - 498, - 497.3, - 496.7, - 495.6, - 494.7, - 494.1, - 493.6, - 492.6, - 491.9, - 491.7, - 491.4, - 491.1, - 490.8, - 490.5, - 490.2, - 489.8, - 489.4, - 488.7, - 488, - 487.5, - 487.2, - 486.9, - 486.6, - 486.2, - 485.9, - 485.3, - 484.7, - 484, - 483.3, - 482.8, - 482.1, - 482, - 482.1, - 482.4, - 482.6, - 482.2, - 481.7, - 481.2, - 481, - 480.9, - 480.9, - 480.9, - 480.8, - 480.7, - 480.7, - 480, - 479.1, - 478.3, - 477.8, - 477.3, - 477, - 476.8, - 476.8, - 476.8, - 476.4, - 476, - 475.8, - 475.4, - 474.8, - 474.3, - 474, - 473.6, - 472.9, - 471.6, - 470.8, - 468.8, - 465, - 458.5, - 450, - 444.6, - 440.6, - 437.8, - 435.3, - 426.6, - 409, - 395.4, - 382, - 375.4, - 373.1, - 371, - 368.7, - 366.1, - 363.7, - 362.3, - 361.1, - 360.1, - 359.4, - 359.4, - 359.3, - 359, - 357.8, - 503, - 503.5, - 503.3, - 502.7, - 501.6, - 500.1, - 497.6, - 492.7, - 488.2, - 485.6, - 483.5, - 481.9, - 480.4, - 477.8, - 475.6, - 474.9, - 473.6, - 471.6, - 470.6, - 470.1, - 470.1, - 470.1, - 470, - 469.7, - 469.4, - 469.1, - 468.5, - 467.5, - 466.7, - 466.2, - 465.5, - 464.9, - 464.4, - 463.6, - 462.9, - 462.2, - 461.4, - 460.6, - 459.7, - 458.6, - 457.5, - 455.9, - 454.6, - 453.8, - 453.5, - 453.5, - 453.2, - 452.8, - 452.3, - 452, - 452, - 452.3, - 453, - 453.4, - 453.5, - 454.1, - 456.2, - 459.1, - 461.1, - 462.6, - 464.3, - 466.5, - 469.7, - 473.3, - 477.1, - 480.9, - 485.1, - 489.1, - 493, - 497.5, - 502.1, - 507.3, - 511.8, - 516.5, - 520.9, - 521.5, - 519.8, - 519, - 520.4, - 525.9, - 531.5, - 532.2, - 532.5, - 533.6, - 534.6, - 536.7, - 542.1, - 546.1, - 546.7, - 546.4, - 545.4, - 544.5, - 543.7, - 542.5, - 541.6, - 541, - 540.3, - 539.8, - 539.3, - 539, - 538.4, - 538.1, - 537.3, - 536.5, - 535.6, - 534.6, - 533.6, - 533.1, - 532.5, - 531.8, - 531.4, - 530.7, - 529.9, - 529.3, - 528.7, - 528, - 527.3, - 526.6, - 526.1, - 524.9, - 523.5, - 522.8, - 522.1, - 521.4, - 520.9, - 520.4, - 519.7, - 518.9, - 518.2, - 517.5, - 517.1, - 516.3, - 515.2, - 514.2, - 513.5, - 512.9, - 512.3, - 511.9, - 511.1, - 510.7, - 510.5, - 510.1, - 509.4, - 508.6, - 508.1, - 507.6, - 507.1, - 506.9, - 506.8, - 506.5, - 506.2, - 505.4, - 504.7, - 504.1, - 503.6, - 503.1, - 502.3, - 501.3, - 500.5, - 499.8, - 499.2, - 499.2, - 498.7, - 498.4, - 498, - 497.6, - 497.1, - 496.6, - 495.8, - 494.5, - 493.3, - 492.8, - 492.3, - 491.2, - 490.8, - 490.8, - 490.5, - 490.2, - 489.9, - 489.5, - 489.2, - 488.7, - 488.4, - 487.7, - 487.2, - 486.8, - 486.4, - 486, - 485.5, - 485, - 484.8, - 484.3, - 483.9, - 483.2, - 482.4, - 481.9, - 481.3, - 480.8, - 480.9, - 481.2, - 481.2, - 480.8, - 480.1, - 479.8, - 479.7, - 479.8, - 479.8, - 479.7, - 479.7, - 479.5, - 479.4, - 478.2, - 477.9, - 477.3, - 476.6, - 476.1, - 475.8, - 475.8, - 475.7, - 475.5, - 475.4, - 475, - 474.6, - 474.2, - 473.5, - 473.1, - 473, - 472.3, - 471.6, - 470.6, - 469.5, - 467.6, - 463.1, - 455.3, - 449.2, - 444.4, - 439.7, - 435.9, - 433.9, - 429.6, - 410.6, - 391, - 382.5, - 377.2, - 372.9, - 370.6, - 368.8, - 367, - 365.5, - 364.5, - 363.7, - 363.2, - 363.3, - 363.7, - 363, - 360.9, - 360, - 501.9, - 502.3, - 502.1, - 501.5, - 501, - 500.5, - 499.1, - 496.6, - 491.5, - 485.6, - 482.8, - 480.8, - 478.9, - 476.4, - 474.2, - 472.9, - 471.8, - 469.9, - 468.9, - 468.5, - 468.2, - 467.9, - 467.6, - 467, - 466.4, - 465.9, - 465.4, - 464.6, - 463.7, - 463.3, - 462.8, - 462.1, - 461.5, - 460.8, - 460, - 459.3, - 458.5, - 457.6, - 456.5, - 455.7, - 454.8, - 453.8, - 452.6, - 451.8, - 451.5, - 451, - 450.5, - 450, - 449.7, - 449.9, - 450.5, - 451.4, - 452, - 452.2, - 451.9, - 453, - 456.3, - 459.8, - 462.3, - 465.3, - 468.4, - 471.7, - 475.8, - 479.8, - 483.8, - 488.5, - 493.4, - 497.4, - 501.1, - 505.8, - 510.6, - 515.5, - 519.5, - 522.1, - 523.8, - 523.4, - 523.8, - 527.3, - 533, - 536, - 536.4, - 536.4, - 535.9, - 535.3, - 535.8, - 537.5, - 542, - 545.2, - 545.1, - 544.6, - 544.3, - 543.8, - 542.2, - 541.2, - 540.4, - 539.9, - 539.5, - 538.9, - 538.2, - 537.8, - 537.5, - 537, - 536.3, - 535.3, - 534.3, - 533.4, - 532.6, - 532, - 531.4, - 530.9, - 530.3, - 529.5, - 528.8, - 528.4, - 527.8, - 526.9, - 526.1, - 525.5, - 524.4, - 523.3, - 522.4, - 521.8, - 520.8, - 520.1, - 519.7, - 519.2, - 518.4, - 518, - 517.4, - 516.6, - 516.1, - 515.1, - 513.7, - 512.6, - 512.2, - 511.8, - 511.3, - 510.9, - 510.2, - 509.9, - 509.5, - 508.8, - 508.3, - 507.9, - 507.7, - 506.9, - 506.1, - 505.9, - 505.8, - 505.5, - 505.2, - 504.2, - 503.1, - 502.8, - 502.5, - 502.2, - 501.4, - 500.4, - 499.8, - 498.8, - 498.1, - 498, - 497.7, - 497.3, - 496.6, - 496.3, - 496, - 495.6, - 494.9, - 493.4, - 492, - 491.5, - 490.9, - 490.1, - 489.9, - 489.7, - 489.5, - 489.4, - 488.9, - 488.3, - 488.1, - 487.9, - 487.6, - 486.8, - 486.1, - 485.7, - 485.5, - 485.2, - 484.7, - 484.1, - 483.9, - 483.5, - 483.2, - 482.4, - 481.5, - 481, - 480.4, - 479.8, - 479.9, - 479.9, - 479.6, - 479.1, - 478.6, - 478.4, - 478.6, - 478.7, - 478.7, - 478.5, - 478.3, - 477.5, - 477.1, - 476.3, - 475.9, - 475.9, - 475.6, - 475.2, - 475.1, - 474.9, - 474.7, - 474.4, - 474.2, - 474, - 473.5, - 473.1, - 472.6, - 472, - 471.6, - 471.2, - 470.7, - 470.1, - 468.3, - 466.7, - 463.3, - 457.7, - 453.7, - 445.8, - 439.3, - 433.9, - 430.2, - 426.2, - 409.8, - 392, - 384.3, - 380, - 375, - 372.1, - 370.5, - 368.9, - 367.8, - 367.3, - 366.5, - 366.3, - 366.5, - 366.6, - 365.2, - 362.9, - 362.8, - 500.5, - 500.8, - 500.7, - 500.7, - 500.3, - 499.8, - 498.5, - 497.4, - 494.7, - 488.4, - 484.3, - 481.1, - 478.3, - 475.4, - 473.4, - 471.5, - 470.1, - 468.7, - 467.6, - 467.1, - 466.3, - 465.6, - 465.3, - 464.8, - 463.9, - 463, - 462.2, - 461.7, - 460.8, - 460, - 459.5, - 458.8, - 458.2, - 457.8, - 457.3, - 456.5, - 455.6, - 454.8, - 454.2, - 453.6, - 453, - 452.4, - 450.9, - 449.8, - 449.2, - 448.2, - 447.9, - 448, - 448.2, - 448.7, - 449.3, - 450, - 450.4, - 450.6, - 450.3, - 451.6, - 454.9, - 460.3, - 465.6, - 469.8, - 474, - 478, - 482.8, - 488.5, - 492.8, - 496.5, - 499.9, - 503.5, - 507.9, - 513, - 518, - 521.7, - 523.2, - 523.6, - 526, - 529.9, - 535.1, - 538.7, - 540.4, - 540, - 539.5, - 539.3, - 539, - 538.3, - 537.7, - 538.2, - 542.1, - 544.2, - 543.4, - 542.6, - 542.1, - 541.8, - 540.2, - 539.6, - 539.1, - 538.8, - 538.5, - 537.5, - 536.8, - 536.6, - 536.4, - 535.8, - 535.3, - 534.3, - 533.3, - 532.4, - 531.6, - 531, - 530.3, - 529.9, - 529.2, - 528.4, - 527.7, - 527.2, - 526.6, - 525.4, - 525, - 524.5, - 523.3, - 522.2, - 521.3, - 520.4, - 519.7, - 519.3, - 518.8, - 518.2, - 517.7, - 517.4, - 516.5, - 515.7, - 514.9, - 513.8, - 512.5, - 511.7, - 511.5, - 511, - 510.3, - 509.6, - 509.2, - 509, - 508.5, - 507.4, - 506.9, - 507, - 506.8, - 505.6, - 505.2, - 505, - 504.8, - 504.3, - 504, - 502.6, - 501.2, - 501.3, - 501.4, - 501.1, - 500.2, - 499.5, - 498.6, - 497.9, - 497.2, - 497.1, - 496.6, - 495.9, - 495.2, - 495, - 494.8, - 494.2, - 493.2, - 491.8, - 491, - 490.8, - 490.5, - 489.6, - 489.2, - 488.9, - 488.6, - 488.5, - 488.1, - 487.4, - 487.1, - 487, - 486.7, - 486.2, - 485.6, - 484.8, - 484.7, - 484.4, - 483.7, - 483.4, - 483.1, - 482.3, - 481.7, - 481.2, - 480.5, - 480.1, - 479.6, - 479, - 478.7, - 478.4, - 477.9, - 477.5, - 477.5, - 477.5, - 477.6, - 477.7, - 477.6, - 477.2, - 476.8, - 476.1, - 475.5, - 475, - 474.6, - 474.5, - 474.5, - 474.3, - 473.8, - 473.6, - 473.4, - 473, - 472.6, - 472.1, - 471.4, - 471.1, - 471, - 470.6, - 470, - 469.6, - 469.1, - 468.3, - 467.1, - 465.8, - 463.5, - 460.6, - 456.5, - 448.9, - 439.4, - 431.9, - 426.6, - 420, - 406.2, - 392.8, - 385.2, - 381.5, - 376.6, - 373.8, - 372.3, - 370.9, - 370.1, - 369.7, - 369.3, - 369.5, - 369.9, - 369.9, - 367.9, - 365.7, - 367.7, - 498.3, - 498.7, - 499.2, - 499.3, - 498.9, - 497.5, - 496.5, - 495.2, - 492.9, - 489.4, - 486, - 482.4, - 479.1, - 476.2, - 473.6, - 471, - 469, - 467.2, - 466, - 465.1, - 464.3, - 463.6, - 463.1, - 462.4, - 461.3, - 460.2, - 459.1, - 458.1, - 457.1, - 456.2, - 455.7, - 455.3, - 454.9, - 454.9, - 454.2, - 453.3, - 452.9, - 452.4, - 451.6, - 451.1, - 451, - 450.1, - 448.5, - 447.2, - 446.6, - 446.3, - 446.3, - 446.4, - 446.9, - 447.5, - 447.7, - 448.2, - 448.7, - 448.9, - 448.9, - 450.2, - 453.6, - 460.6, - 468.7, - 474, - 478.8, - 484, - 489.4, - 494.2, - 498.5, - 502.5, - 507.5, - 512.2, - 514.9, - 519.3, - 522.4, - 524.1, - 527.1, - 532, - 536.5, - 542, - 545.2, - 546, - 545.1, - 544.2, - 543.6, - 543.2, - 542.7, - 542.1, - 541.3, - 541.1, - 543.2, - 543.3, - 542.5, - 541.6, - 540.8, - 540, - 539, - 538.4, - 538.1, - 537.8, - 537, - 536.2, - 535.9, - 535.5, - 534.9, - 534.3, - 534, - 533.2, - 532.1, - 531.3, - 530.4, - 529.7, - 529.1, - 528.5, - 528, - 527.3, - 526.8, - 526.1, - 525.1, - 524.3, - 523.8, - 523.2, - 522.1, - 521.2, - 520.1, - 519.3, - 519, - 518.5, - 518, - 517.4, - 517, - 516.7, - 515.7, - 514.7, - 514, - 513.2, - 512, - 511.3, - 510.7, - 509.9, - 509.3, - 508.6, - 508.1, - 507.9, - 507.2, - 506.3, - 506.2, - 506, - 505.1, - 504.2, - 504.2, - 504, - 503.4, - 502.6, - 502.1, - 500.3, - 499.5, - 499.8, - 499.8, - 499.4, - 498.6, - 498, - 497.2, - 496.9, - 496.3, - 495.8, - 495.1, - 494.5, - 494.1, - 493.6, - 492.6, - 492.1, - 491.6, - 490.5, - 490.2, - 489.9, - 489.7, - 489.1, - 488.4, - 488.1, - 487.9, - 487.7, - 487.4, - 486.9, - 486.4, - 486, - 485.9, - 485.6, - 484.9, - 484.1, - 483.7, - 483.2, - 482.6, - 482.4, - 482, - 481.1, - 480.3, - 479.9, - 479.6, - 479.2, - 478.7, - 478.1, - 477.9, - 477.5, - 476.9, - 476.5, - 476.5, - 476.6, - 476.6, - 476.5, - 476.1, - 475.6, - 474.8, - 474.5, - 474.2, - 473.7, - 473.5, - 473.5, - 473.3, - 472.8, - 472.2, - 472.3, - 472.1, - 471.8, - 471.4, - 470.5, - 469.8, - 469.2, - 469, - 468.9, - 468.5, - 467.9, - 467.6, - 466.7, - 465.8, - 464.4, - 461.8, - 458.5, - 454.3, - 449.7, - 442.9, - 431.5, - 424.5, - 413.6, - 405, - 397.1, - 387.8, - 381.8, - 377.6, - 375.6, - 374.2, - 373, - 372.3, - 371.9, - 372.5, - 373.6, - 373.9, - 373.7, - 371, - 369.9, - 374, - 495.3, - 495.4, - 495.8, - 496, - 496.1, - 494.5, - 493.7, - 492.3, - 491, - 487.9, - 485.1, - 482.2, - 479.2, - 476, - 473.2, - 470.1, - 467.5, - 465.6, - 464.6, - 463.6, - 462.7, - 461.6, - 460.3, - 458.7, - 457.2, - 456.2, - 455.2, - 454, - 452.7, - 451.8, - 451.9, - 451.7, - 452.1, - 451.9, - 451.2, - 450.9, - 450.8, - 450.1, - 449.3, - 448.9, - 448.2, - 446.7, - 445.6, - 445, - 444.8, - 444.7, - 444.7, - 445, - 445.6, - 445.9, - 446.1, - 446.9, - 447.1, - 447.4, - 447.5, - 448.5, - 452.3, - 460.9, - 470, - 476.9, - 481.9, - 486.7, - 491.7, - 497, - 503.8, - 514.5, - 526.4, - 535.4, - 527.3, - 525.2, - 527.6, - 532.8, - 538.3, - 542, - 547.3, - 549.5, - 549.7, - 549, - 547.5, - 546.8, - 546.3, - 545.6, - 544.8, - 544.3, - 543.6, - 543.1, - 543, - 542.5, - 541.8, - 540.8, - 539.9, - 539.2, - 538.4, - 537.8, - 537.3, - 536.8, - 536.2, - 535.6, - 535, - 534.5, - 533.7, - 533, - 532.6, - 531.8, - 530.5, - 529.7, - 529, - 528.4, - 527.6, - 527.1, - 526.7, - 526.1, - 525.8, - 525, - 523.8, - 523, - 522.4, - 521.8, - 521, - 520.3, - 519.3, - 518.6, - 518.1, - 517.6, - 517.1, - 516.7, - 516.1, - 515.5, - 514.6, - 513.9, - 513.5, - 512.4, - 511.4, - 510.6, - 509.7, - 508.8, - 507.9, - 507.2, - 507, - 506.8, - 506, - 505.3, - 505.2, - 504.8, - 503.6, - 502.9, - 503, - 503, - 502, - 501.2, - 500.4, - 498.8, - 498.3, - 498.4, - 498.4, - 498.1, - 497.3, - 496.6, - 495.9, - 495.5, - 495.2, - 494.4, - 493.5, - 493, - 492.8, - 491.9, - 490.4, - 490, - 490, - 489.8, - 489.4, - 489.1, - 488.9, - 488.4, - 487.5, - 487.2, - 487.1, - 486.7, - 486.4, - 485.8, - 485.4, - 484.9, - 484.7, - 484.3, - 483.7, - 483.1, - 482.5, - 481.9, - 481.2, - 480.6, - 480.4, - 479.8, - 479.4, - 479.1, - 478.7, - 478.3, - 477.6, - 477, - 476.9, - 476.5, - 476.1, - 475.8, - 475.9, - 475.8, - 475.5, - 475.3, - 474.9, - 474.2, - 473.4, - 473.1, - 472.8, - 472.4, - 472.3, - 472.4, - 471.9, - 471.5, - 471.3, - 471.2, - 470.9, - 470.6, - 470.3, - 469.3, - 468.3, - 467.7, - 467.4, - 467.2, - 467.1, - 466.7, - 466.3, - 465.7, - 464.7, - 463.1, - 460.7, - 455.7, - 450.5, - 448.4, - 445.7, - 436.7, - 426.4, - 415.9, - 407.5, - 401.4, - 391.4, - 383.1, - 380.4, - 378.2, - 376, - 374.9, - 374.4, - 374.2, - 375.3, - 376.7, - 377.9, - 377.4, - 373.6, - 374.7, - 378.7, - 492, - 492.2, - 492.3, - 492.2, - 492, - 491.1, - 490.5, - 490.4, - 489, - 486.1, - 483.3, - 481, - 478.3, - 475.7, - 472.9, - 469.4, - 466.5, - 463.8, - 462, - 460.4, - 458.6, - 457.3, - 455.7, - 454, - 452.4, - 451.5, - 450.5, - 449.8, - 448.8, - 448.5, - 448.9, - 449.2, - 449.3, - 449.1, - 449, - 449, - 449.1, - 448.6, - 447.2, - 446.1, - 445.1, - 444.3, - 443.7, - 443.2, - 443, - 442.9, - 443.1, - 443.5, - 443.9, - 444.1, - 444.7, - 445.2, - 445.6, - 445.8, - 446.3, - 447.3, - 451.5, - 463.6, - 474.4, - 481.9, - 487.4, - 493.6, - 499.4, - 505.6, - 517.2, - 528.9, - 536, - 543.9, - 543.3, - 537.7, - 540.3, - 544.9, - 547.4, - 548.3, - 550.5, - 550.1, - 549.5, - 548.7, - 547.6, - 546.9, - 546.2, - 545.4, - 544.6, - 544.1, - 543.4, - 543.1, - 542.7, - 541.8, - 540.9, - 540, - 539.3, - 538.6, - 537.9, - 537.1, - 536.4, - 535.9, - 535.4, - 534.9, - 534.2, - 533, - 532.4, - 531.9, - 531.2, - 530.2, - 529.1, - 528.2, - 527.8, - 527.4, - 526.6, - 525.8, - 525.4, - 525, - 524.6, - 523.8, - 523, - 522.1, - 521.4, - 520.7, - 520.1, - 519.4, - 518.6, - 517.9, - 517.2, - 516.7, - 516.4, - 515.9, - 515.2, - 514.4, - 513.6, - 513.1, - 512.5, - 511.3, - 510.3, - 509.8, - 508.9, - 507.6, - 506.7, - 506.1, - 505.8, - 505.5, - 505, - 504.3, - 503.8, - 503.1, - 502.3, - 501.8, - 501.8, - 501.6, - 500.7, - 500, - 499.4, - 498.2, - 497.5, - 497.4, - 497.2, - 496.7, - 495.9, - 495, - 494.2, - 493.8, - 493.5, - 492.9, - 492, - 491.4, - 490.9, - 489.8, - 489.1, - 489, - 489, - 488.8, - 488.6, - 488.1, - 487.8, - 487.2, - 486.6, - 486.3, - 485.9, - 485.7, - 485.5, - 485, - 484.4, - 483.7, - 483.2, - 482.6, - 482.2, - 481.8, - 481.2, - 480.7, - 479.8, - 479.3, - 479.1, - 478.8, - 478.6, - 478, - 477.4, - 476.9, - 476.5, - 476.3, - 475.9, - 475.2, - 475, - 474.8, - 474.8, - 474.7, - 474.7, - 474.4, - 473.6, - 472.8, - 472.2, - 472, - 471.7, - 471.4, - 471, - 470.9, - 470.6, - 470.5, - 470.4, - 470.1, - 469.7, - 469.4, - 469.1, - 468.2, - 466.9, - 466, - 465.7, - 465.7, - 465.4, - 464.9, - 464.4, - 463.9, - 463.3, - 461.8, - 459.5, - 453, - 448.5, - 445.9, - 443.6, - 437.4, - 427.6, - 420.3, - 410.5, - 401.7, - 393.3, - 387.6, - 387.5, - 385.6, - 380.4, - 378.3, - 378.3, - 377.6, - 377.3, - 378.6, - 379.7, - 379, - 376.6, - 378.9, - 381.7, - 489.6, - 489.1, - 488.7, - 488.1, - 488, - 487.8, - 487.5, - 487.6, - 486.4, - 483.9, - 481.5, - 479.9, - 477.5, - 475.7, - 473.7, - 471.1, - 467.7, - 464.1, - 461.2, - 458.9, - 456.6, - 454.9, - 453.1, - 451, - 449.4, - 448.1, - 447, - 446.3, - 446, - 446, - 446.5, - 447.1, - 447.2, - 447.4, - 447.5, - 447.5, - 447.5, - 447, - 445, - 443.6, - 443, - 442.4, - 441.8, - 441.5, - 441.3, - 441.1, - 441.3, - 441.7, - 442.3, - 442.8, - 443.4, - 443.7, - 443.9, - 444.5, - 445.2, - 447.1, - 454.3, - 464, - 473.1, - 480.9, - 488.6, - 497.9, - 504.9, - 510.8, - 524.5, - 537.6, - 542.4, - 547.2, - 550.7, - 551.3, - 551.3, - 552.3, - 551.7, - 550.8, - 550.3, - 549.6, - 548.9, - 548, - 546.9, - 546.3, - 545.7, - 545.2, - 544, - 543.3, - 542.9, - 542.4, - 541.9, - 541.1, - 540.1, - 539.2, - 538.5, - 537.8, - 537.1, - 536.5, - 535.9, - 535.2, - 534.6, - 534, - 533.3, - 532, - 531.5, - 530.7, - 529.5, - 528.7, - 527.9, - 527.4, - 526.7, - 526.3, - 525.6, - 525, - 524.7, - 523.9, - 523.1, - 522.5, - 522, - 521.1, - 520.5, - 519.9, - 519.3, - 518.5, - 517.8, - 517.1, - 516.3, - 515.8, - 515.5, - 515, - 514.3, - 513.4, - 512.6, - 512, - 511.3, - 510.4, - 509.5, - 508.9, - 508, - 507.2, - 506.3, - 505.8, - 505.1, - 504.4, - 503.7, - 503.2, - 502.7, - 501.8, - 500.9, - 500.7, - 500.8, - 500.3, - 499.4, - 498.9, - 498.4, - 497.5, - 496.6, - 496, - 494.9, - 494.1, - 493.5, - 493, - 492.4, - 492.3, - 492.1, - 491.6, - 490.8, - 490, - 489.4, - 488.7, - 488, - 487.7, - 487.5, - 487.4, - 487.1, - 486.6, - 486.5, - 485.9, - 485.5, - 485.3, - 484.6, - 484.5, - 484.4, - 483.9, - 483.1, - 482.3, - 481.8, - 481.3, - 480.9, - 480.6, - 480.2, - 479.7, - 479.3, - 478.7, - 478.5, - 478.2, - 477.6, - 476.8, - 476.3, - 475.8, - 475.5, - 475.3, - 474.9, - 474.3, - 474.1, - 473.7, - 473.7, - 473.6, - 473.4, - 473.1, - 472.3, - 471.4, - 471, - 470.8, - 470.5, - 470, - 469.7, - 469.5, - 469.6, - 469.6, - 469.4, - 469.1, - 468.7, - 468.2, - 467.7, - 467, - 465.9, - 464.9, - 464.3, - 463.7, - 463.6, - 463.4, - 462.7, - 462, - 461.3, - 459.6, - 457.5, - 451.1, - 447.3, - 443.9, - 440, - 434.8, - 426.6, - 421.6, - 412.8, - 403, - 394, - 394.3, - 397.9, - 399.9, - 396.1, - 390.2, - 393.9, - 390.9, - 383.3, - 380.6, - 380.9, - 379.7, - 379.9, - 381.7, - 383.4, - 487.8, - 487.2, - 486.7, - 486.4, - 486.3, - 486.2, - 485.9, - 485.2, - 483.5, - 481.1, - 479.3, - 477.8, - 476.1, - 475, - 473.8, - 472.1, - 469.5, - 466.7, - 463.7, - 461, - 458.7, - 455.9, - 453.5, - 451.3, - 449.7, - 447.9, - 446.3, - 444.9, - 443.9, - 443.5, - 443.6, - 444.5, - 445.3, - 445.7, - 445.9, - 445.9, - 445.5, - 444.6, - 443.1, - 441.8, - 441, - 440.3, - 439.9, - 439.5, - 439.2, - 439.2, - 439.6, - 440.4, - 441.2, - 441.6, - 442, - 442.2, - 442.6, - 443.4, - 444.3, - 448.1, - 456.4, - 462.3, - 468.1, - 473.6, - 480, - 489.7, - 498.4, - 504.5, - 529.9, - 540.4, - 548.6, - 552.7, - 553.9, - 553.6, - 552.9, - 552, - 551.5, - 550.4, - 549.7, - 549.3, - 548.4, - 547.4, - 546.3, - 545.7, - 545.1, - 544.5, - 543.6, - 542.9, - 542.3, - 541.6, - 541, - 540.3, - 539.4, - 538.5, - 537.8, - 537.1, - 536.4, - 535.8, - 534.8, - 534, - 533.7, - 532.9, - 532.2, - 531.2, - 530.5, - 529.5, - 528.5, - 527.8, - 527.2, - 526.6, - 525.7, - 525.2, - 524.7, - 524.1, - 523.5, - 522.8, - 522, - 521.2, - 520.8, - 520.3, - 519.6, - 519.1, - 518.5, - 517.5, - 516.8, - 516.1, - 515.3, - 514.6, - 514.2, - 513.8, - 513.3, - 512.6, - 511.8, - 511, - 510.2, - 509.5, - 508.8, - 508, - 507, - 506.4, - 505.8, - 505.2, - 504.5, - 503.6, - 502.8, - 502.3, - 501.6, - 500.7, - 499.9, - 499.8, - 499.7, - 499, - 497.7, - 497, - 496.6, - 496.2, - 495.4, - 494.4, - 493.3, - 492.7, - 492.4, - 492, - 491.8, - 491.6, - 491.1, - 490.1, - 489.4, - 488.7, - 488.2, - 487.4, - 486.9, - 486.5, - 486.2, - 486.3, - 486.2, - 485.2, - 485.1, - 484.7, - 484.3, - 484.3, - 483.7, - 483.3, - 482.4, - 482, - 481.7, - 480.9, - 480.4, - 480.2, - 479.9, - 479.5, - 478.9, - 478.3, - 478.1, - 477.6, - 477.4, - 477.2, - 476.5, - 475.6, - 475, - 474.5, - 474.2, - 473.9, - 473.6, - 473.1, - 472.9, - 472.6, - 472.5, - 472.2, - 471.9, - 471.5, - 471.2, - 470.3, - 469.9, - 469.6, - 469.2, - 468.8, - 468.6, - 468.4, - 468.4, - 468.4, - 468.3, - 467.9, - 467.7, - 467.1, - 466.6, - 465.9, - 464.9, - 463.7, - 463, - 462.3, - 462, - 461.5, - 460.8, - 460.3, - 459.6, - 457.4, - 453.8, - 448.4, - 444.5, - 441.3, - 438.3, - 433.8, - 427.2, - 420.4, - 412.3, - 405.7, - 398.4, - 404.1, - 407.5, - 407.5, - 405.7, - 403.5, - 403.6, - 402.8, - 400.3, - 391.4, - 383.2, - 381.7, - 382.2, - 383.6, - 387.4, - 486.1, - 485.6, - 485.3, - 485.1, - 485, - 484.5, - 483.5, - 482.1, - 480.5, - 478.6, - 477, - 475.3, - 474.2, - 473.5, - 472.4, - 470.5, - 468.3, - 466.3, - 464.5, - 462.7, - 460.7, - 458.3, - 455.2, - 452.4, - 450.5, - 448.5, - 446.5, - 444.4, - 442.9, - 442, - 441.6, - 442, - 443.5, - 444.6, - 445, - 444.7, - 443.6, - 442.6, - 440.9, - 439.6, - 438.9, - 438.3, - 437.7, - 437.3, - 437.2, - 437.4, - 437.9, - 438.6, - 439, - 439.7, - 440.2, - 440.7, - 441.5, - 442.5, - 444, - 449.5, - 456.6, - 462.4, - 466.4, - 470.7, - 474, - 478.9, - 486.5, - 517.6, - 548.1, - 552.9, - 554.3, - 554.2, - 553.8, - 553.2, - 552.4, - 551.3, - 550.5, - 549.8, - 549.1, - 548.5, - 547.6, - 546.8, - 545.6, - 545, - 544.3, - 543.8, - 543.2, - 542.6, - 541.7, - 540.9, - 540.3, - 539.4, - 538.4, - 537.6, - 536.7, - 536.1, - 535.6, - 534.9, - 533.8, - 533, - 532.6, - 531.8, - 531.2, - 530.3, - 529.6, - 528.8, - 528.1, - 527.3, - 526.6, - 525.7, - 524.9, - 524.2, - 523.7, - 523.1, - 522.4, - 521.9, - 521.1, - 520.3, - 519.8, - 519.3, - 518.7, - 518.1, - 517.5, - 516.6, - 515.9, - 515, - 514.3, - 513.8, - 513.4, - 512.9, - 512.4, - 511.8, - 511.1, - 510.2, - 509.2, - 508.6, - 507.9, - 507, - 506, - 505.3, - 504.9, - 504.3, - 503.4, - 502.4, - 501.7, - 501.1, - 500.4, - 499.6, - 499.1, - 498.7, - 498.4, - 497.7, - 496.3, - 495.5, - 495.2, - 494.7, - 494.2, - 493.4, - 492.5, - 492.2, - 491.9, - 491.5, - 490.9, - 490.4, - 489.5, - 488.2, - 487.8, - 487.1, - 486.7, - 485.9, - 485.6, - 485.6, - 485.5, - 485.4, - 485.4, - 484.4, - 483.8, - 483.3, - 483, - 483, - 482.6, - 481.9, - 480.8, - 480.4, - 480.4, - 479.9, - 479.3, - 479.1, - 478.6, - 478.2, - 477.6, - 477.1, - 476.9, - 476.4, - 475.8, - 475.5, - 475, - 474.3, - 473.8, - 473.4, - 473, - 472.6, - 472.1, - 471.9, - 471.6, - 471.4, - 471.3, - 471.1, - 470.7, - 470.4, - 470, - 469.2, - 468.7, - 468.4, - 467.9, - 467.5, - 467.3, - 467.1, - 467.1, - 467.1, - 466.7, - 466.2, - 466.1, - 465.8, - 465.2, - 464.6, - 463.8, - 462.7, - 461.8, - 461, - 460.2, - 459.6, - 459.1, - 458.3, - 457.2, - 454.2, - 450.7, - 447.6, - 444.5, - 439.5, - 436.4, - 433.8, - 428.3, - 420.1, - 413.4, - 407.9, - 406.4, - 409.4, - 411.1, - 410.9, - 409.1, - 406.9, - 406.1, - 405.2, - 403.8, - 397.9, - 386.3, - 383.8, - 384.1, - 386.5, - 390.8, - 484.7, - 484.3, - 483.9, - 483.1, - 482.2, - 481.4, - 480.1, - 479.2, - 478.1, - 476.2, - 474.5, - 473.5, - 472.8, - 471.9, - 470.3, - 468.1, - 466.1, - 464.8, - 463.3, - 461.5, - 459.5, - 457.4, - 455, - 452.5, - 450.2, - 448, - 445.6, - 443.2, - 441.7, - 440.8, - 440.4, - 440.5, - 442.3, - 443.7, - 444, - 443.6, - 442.6, - 441.2, - 439.4, - 438.1, - 437.4, - 436.9, - 436.3, - 435.9, - 435.8, - 436.2, - 436.5, - 436.9, - 437.2, - 437.8, - 438.7, - 439.5, - 440.5, - 441.5, - 443.4, - 448.3, - 454.3, - 460.8, - 465.1, - 469.4, - 473.5, - 476.6, - 490.2, - 522.9, - 547, - 554.7, - 554.5, - 553.8, - 553.1, - 552.3, - 551.7, - 550.7, - 549.9, - 549.1, - 548.5, - 547.7, - 547, - 546, - 544.9, - 544.3, - 543.6, - 543.1, - 542.7, - 542, - 540.9, - 539.9, - 539.6, - 539, - 537.7, - 536.8, - 535.8, - 535.1, - 534.7, - 534, - 533.1, - 532.3, - 531.4, - 530.7, - 530, - 529.4, - 528.8, - 528.1, - 527.4, - 526.8, - 526.1, - 525.2, - 524.2, - 523.5, - 522.8, - 522.1, - 521.4, - 520.9, - 520.1, - 519.4, - 518.8, - 518.4, - 517.9, - 517, - 516.2, - 515.6, - 514.9, - 513.9, - 513.4, - 513, - 512.5, - 511.9, - 511.5, - 510.7, - 509.8, - 508.9, - 508.2, - 507.6, - 506.9, - 506, - 504.7, - 503.9, - 503.7, - 502.8, - 501.7, - 500.6, - 500.1, - 499.7, - 499.3, - 498.6, - 498.1, - 497.6, - 497, - 496.4, - 495.4, - 494.8, - 494.4, - 493.8, - 493.3, - 492.7, - 491.8, - 491.4, - 491, - 490.3, - 489.5, - 488.6, - 487.5, - 486.8, - 485.9, - 485.3, - 485, - 484.7, - 484.7, - 484.7, - 484.8, - 484.6, - 484, - 483.4, - 483, - 482.5, - 482.2, - 481.8, - 481.4, - 480.9, - 480, - 479.4, - 479.2, - 479, - 478.6, - 478.1, - 477.4, - 476.7, - 476.4, - 476.1, - 475.8, - 475.2, - 474.2, - 473.8, - 473.4, - 472.9, - 472.5, - 472, - 471.6, - 471.4, - 471.1, - 470.8, - 470.6, - 470.5, - 470.3, - 470, - 469.6, - 469, - 468.8, - 468.4, - 467.8, - 467.3, - 466.8, - 466.5, - 466.3, - 466.1, - 465.9, - 465.5, - 465.1, - 465.1, - 464.5, - 464.1, - 463.5, - 463, - 462.5, - 461.6, - 461, - 460.1, - 459.1, - 458.4, - 457.8, - 456.4, - 455, - 453, - 451.1, - 448.3, - 444.6, - 440.6, - 435.9, - 432.7, - 427.5, - 420.2, - 415.5, - 411.4, - 411.3, - 413.4, - 413.6, - 413.5, - 412.5, - 410, - 408.1, - 405.9, - 401.7, - 393, - 387.5, - 386.5, - 386.6, - 387.8, - 390.4, - 483.2, - 482.9, - 482.1, - 481, - 480.2, - 479, - 477.7, - 476.7, - 475.7, - 474, - 472.7, - 471.8, - 470.9, - 469.5, - 467.5, - 465.9, - 464.6, - 463.3, - 461.7, - 459.9, - 458, - 455.9, - 453.9, - 451.5, - 448.8, - 446.6, - 444.2, - 442.1, - 440.6, - 439.4, - 438.8, - 439.4, - 441.6, - 442.9, - 443, - 442.7, - 442, - 440.8, - 439, - 437.4, - 436.7, - 436, - 435.3, - 434.8, - 434.7, - 435.1, - 435.4, - 435.6, - 436, - 436.9, - 437.6, - 438.7, - 439.8, - 440.2, - 442.1, - 446.4, - 451.4, - 457.1, - 462.6, - 467.1, - 470.1, - 474.6, - 488, - 524.8, - 547.6, - 553.7, - 553.9, - 553.3, - 552.3, - 551.4, - 550.7, - 549.9, - 549, - 548.3, - 547.6, - 546.8, - 546.1, - 545.5, - 544.1, - 543.3, - 542.8, - 542.2, - 541.7, - 541.2, - 540, - 538.8, - 538.1, - 537.8, - 537.1, - 535.6, - 534.9, - 534.4, - 533.7, - 533, - 532.2, - 531.6, - 530.7, - 529.8, - 529.1, - 528.6, - 528, - 527.4, - 526.7, - 526.1, - 525.3, - 524.4, - 523.4, - 522.7, - 521.9, - 521.1, - 520.4, - 519.7, - 519.1, - 518.5, - 517.9, - 517.3, - 516.6, - 515.5, - 514.8, - 514.4, - 513.6, - 512.8, - 512.2, - 511.8, - 511.3, - 510.4, - 510, - 509.2, - 508.3, - 507.6, - 507, - 506.4, - 505.5, - 504.6, - 503, - 502.1, - 501.6, - 500.9, - 500.3, - 499.7, - 499.2, - 498.9, - 498.5, - 497.8, - 497.3, - 496.7, - 496.1, - 495.5, - 494.7, - 494.1, - 493.7, - 493, - 492.4, - 491.8, - 490.9, - 490.5, - 489.9, - 489.1, - 488.2, - 487.1, - 486.3, - 485.8, - 484.8, - 484.4, - 484.2, - 484.2, - 484.1, - 484, - 483.9, - 483.3, - 482.9, - 482.3, - 481.8, - 481.6, - 481.2, - 480.5, - 480.2, - 479.8, - 478.8, - 478.4, - 478.1, - 477.7, - 477.1, - 476.5, - 476.1, - 475.5, - 475.2, - 475, - 474.3, - 473.6, - 472.3, - 472.2, - 472, - 471.7, - 471.4, - 470.6, - 470.1, - 469.9, - 469.9, - 469.6, - 469.5, - 469.4, - 469, - 468.5, - 468.1, - 467.7, - 467.6, - 467.2, - 466.5, - 466, - 465.7, - 465.4, - 465.3, - 465.2, - 464.8, - 464.4, - 464.1, - 463.8, - 463.3, - 462.7, - 462.2, - 461.8, - 461.4, - 460.7, - 460, - 459, - 458.1, - 457.3, - 456.6, - 455.5, - 453.9, - 452.4, - 451.8, - 449.6, - 445.3, - 441.9, - 435.9, - 430.7, - 424.4, - 418.9, - 416.3, - 415, - 416.2, - 417, - 416.5, - 416.2, - 414.8, - 412.9, - 408.5, - 403.2, - 397.3, - 391.9, - 390, - 389.2, - 389.4, - 390.4, - 391.7, - 481, - 480.4, - 479.5, - 478.5, - 477.9, - 476.6, - 475.4, - 474.5, - 473.6, - 472.3, - 470.8, - 469.6, - 468.4, - 466.7, - 465.3, - 464.3, - 463.1, - 461.7, - 460.2, - 458.5, - 456.6, - 454.5, - 452.3, - 449.8, - 446.5, - 444.3, - 442.1, - 440.2, - 438.6, - 437.1, - 436.5, - 438, - 440.4, - 442, - 442.1, - 441.8, - 441.1, - 440, - 438.3, - 436.4, - 435.3, - 434.5, - 433.8, - 433.4, - 433.5, - 433.9, - 434.1, - 434.2, - 434.9, - 435.7, - 436.3, - 437.6, - 439, - 439.1, - 441.3, - 445.5, - 450.2, - 454.5, - 458, - 460.4, - 463.8, - 479.8, - 506.4, - 527.1, - 543.9, - 552.1, - 553, - 552.5, - 551.6, - 550.6, - 549.7, - 549, - 548.3, - 547.6, - 546.8, - 545.9, - 545, - 544.2, - 543, - 542.6, - 542.1, - 541.3, - 540.6, - 540.4, - 539, - 538, - 537.4, - 536.5, - 535.3, - 534.6, - 534.3, - 533.8, - 532.9, - 532.1, - 531.2, - 530.6, - 529.9, - 529.1, - 528.5, - 527.9, - 527.3, - 526.6, - 525.9, - 525.4, - 524.4, - 523.5, - 522.6, - 521.7, - 520.9, - 520.1, - 519.2, - 518.4, - 517.8, - 517.4, - 516.9, - 516.1, - 515.3, - 514.4, - 513.6, - 512.9, - 512.2, - 511.6, - 510.7, - 510.2, - 509.7, - 509, - 508.5, - 507.7, - 507, - 506.2, - 505.4, - 504.4, - 503.2, - 502.2, - 501.2, - 500.7, - 500, - 499.6, - 499.3, - 498.9, - 498.3, - 498.1, - 497.6, - 497, - 496.6, - 495.9, - 495.4, - 494.8, - 493.9, - 493.3, - 492.8, - 492.2, - 491.5, - 490.6, - 489.8, - 489.6, - 489, - 488.1, - 486.9, - 485.9, - 485.2, - 484.7, - 484.3, - 484.1, - 483.9, - 483.8, - 483.6, - 483.5, - 483.2, - 482.3, - 481.8, - 481.2, - 480.4, - 480.4, - 480.1, - 479.4, - 479.1, - 478.7, - 477.7, - 477.5, - 477, - 476.6, - 475.7, - 474.9, - 474.5, - 474.1, - 473.8, - 473.2, - 472.1, - 471.3, - 470.9, - 470.6, - 470.5, - 470.3, - 470, - 469.3, - 468.6, - 468.4, - 468.2, - 467.8, - 468, - 468.2, - 467.8, - 467.4, - 467.1, - 466.7, - 466.1, - 465.7, - 465.2, - 464.9, - 464.7, - 464.4, - 464.2, - 464.1, - 463.6, - 463.1, - 462.7, - 462.4, - 462.2, - 461.8, - 461.3, - 460.6, - 460, - 459.4, - 458.7, - 457.9, - 456.9, - 456.1, - 455.5, - 454, - 452.2, - 451, - 450.7, - 449.8, - 444.8, - 439.5, - 433.9, - 428.5, - 422.1, - 418.6, - 417.6, - 420.8, - 423.6, - 422.9, - 420.3, - 419.5, - 417.6, - 415.6, - 407.3, - 399.7, - 396.4, - 394.3, - 392.7, - 392.2, - 392.5, - 393.7, - 394.7, - 479.2, - 478.6, - 478, - 477, - 475.7, - 474.4, - 473.5, - 472.8, - 472, - 470.4, - 468.2, - 466.7, - 465.7, - 464.6, - 463.5, - 462.5, - 461.4, - 460.1, - 458.6, - 457, - 454.9, - 452.8, - 450.5, - 447.8, - 444, - 441.5, - 439.3, - 437.3, - 435.4, - 434.2, - 434.9, - 436.9, - 439.4, - 440.9, - 440.9, - 440.5, - 439.9, - 438.9, - 436.9, - 434.6, - 433.6, - 432.7, - 432.1, - 432, - 432.4, - 432.9, - 432.8, - 433, - 433.6, - 434.3, - 435.1, - 436.1, - 437.4, - 438.5, - 441.2, - 445.5, - 449.3, - 451.7, - 453.4, - 454.5, - 466.5, - 502.7, - 530.2, - 542.5, - 550.1, - 551.3, - 551.6, - 551.6, - 550.8, - 549.7, - 549, - 548.4, - 547.5, - 546.6, - 545.8, - 545, - 544.2, - 543.4, - 542.6, - 542.1, - 541.3, - 540.5, - 539.7, - 539.1, - 537.9, - 536.8, - 536.4, - 535.6, - 534.6, - 534, - 533.6, - 532.9, - 532.1, - 531.2, - 530.3, - 529.6, - 529, - 528.4, - 527.7, - 527, - 526.4, - 525.6, - 525.1, - 524.6, - 523.6, - 522.6, - 521.4, - 520.5, - 519.7, - 518.7, - 518, - 517.3, - 516.7, - 516.2, - 515.6, - 515, - 514.4, - 513.7, - 512.7, - 511.8, - 511.1, - 510.4, - 509.3, - 508.5, - 508.3, - 507.7, - 507.1, - 506.2, - 505.5, - 504.6, - 503.5, - 502.3, - 501.4, - 500.3, - 499.9, - 499.7, - 499.3, - 499, - 498.5, - 497.8, - 496.8, - 496.6, - 496.2, - 495.8, - 495.5, - 494.9, - 494.4, - 493.8, - 492.9, - 492.2, - 491.8, - 491.1, - 490.2, - 489.4, - 489, - 488.6, - 487.8, - 486.9, - 486, - 485.1, - 484.4, - 484, - 483.7, - 483.4, - 483.3, - 483.2, - 483, - 482.8, - 482.4, - 481.5, - 480.8, - 480.1, - 479.5, - 479.2, - 478.6, - 477.8, - 477.3, - 476.8, - 476.4, - 476.1, - 475.7, - 475.2, - 474.5, - 473.6, - 473.3, - 472.8, - 472.1, - 471.4, - 470.2, - 469.8, - 469.8, - 469.6, - 469.4, - 469.1, - 468.7, - 468, - 467.5, - 467.3, - 466.5, - 466.4, - 466.7, - 466.9, - 466.6, - 466.3, - 465.9, - 465.6, - 465.2, - 464.6, - 464, - 463.9, - 463.7, - 463.4, - 463.1, - 462.9, - 462.4, - 462.1, - 461.7, - 461.4, - 461.1, - 460.7, - 460, - 459.4, - 458.9, - 458.5, - 457.9, - 457.1, - 456, - 455, - 454.2, - 452.6, - 451, - 449.5, - 448.4, - 447.2, - 441.2, - 436.5, - 431, - 426.5, - 421.7, - 420.1, - 423.5, - 429.8, - 431.2, - 429.5, - 426.7, - 423.1, - 419.8, - 416.5, - 409, - 401.3, - 399.1, - 398.1, - 396, - 396.8, - 398.7, - 399.2, - 398.9, - 477.9, - 477.3, - 476.4, - 475.2, - 473.9, - 472.7, - 471.9, - 471, - 469.9, - 468.1, - 465.8, - 464.6, - 463.6, - 462.5, - 461.2, - 460, - 458.9, - 457.9, - 456.7, - 454.9, - 452.8, - 450.8, - 448.6, - 445.9, - 441.8, - 438.4, - 436.3, - 433.9, - 432.3, - 432.4, - 433.8, - 435.8, - 438.3, - 439.3, - 439.1, - 438.5, - 438.1, - 437.2, - 434.5, - 432.3, - 431.3, - 430.6, - 430.5, - 430.7, - 431.4, - 431.5, - 431.4, - 431.7, - 432.4, - 433.2, - 434.1, - 434.9, - 435.9, - 438.1, - 440.9, - 444.3, - 447.4, - 449, - 450.4, - 454.1, - 475.2, - 511.7, - 534.7, - 544.9, - 548.8, - 549.3, - 549.7, - 549.8, - 549.3, - 548.7, - 548.2, - 547.5, - 546.7, - 546.1, - 544.9, - 544.1, - 543.3, - 542.8, - 542.1, - 541.4, - 540.6, - 540, - 539.2, - 538.5, - 537.7, - 536.4, - 535.8, - 534.9, - 534.1, - 533.4, - 532.6, - 531.7, - 531, - 530, - 529.3, - 528.7, - 528.1, - 527.6, - 526.6, - 526, - 525.4, - 524.6, - 523.9, - 522.9, - 522.1, - 521.4, - 520.5, - 519.5, - 518.7, - 517.7, - 516.9, - 516.2, - 515.7, - 515.2, - 514.6, - 514, - 513.4, - 512.7, - 511.7, - 511, - 510.2, - 509.1, - 508, - 507.2, - 506.9, - 506.4, - 505.7, - 504.8, - 504.2, - 503.2, - 501.9, - 500.9, - 500.1, - 499.2, - 498.9, - 498.7, - 498.5, - 498.1, - 497.7, - 496.7, - 495.7, - 495.3, - 494.7, - 494.4, - 494.1, - 493.6, - 493.2, - 492.8, - 492, - 491.2, - 490.8, - 490.2, - 489.4, - 488.8, - 488.4, - 487.2, - 486.4, - 485.8, - 485.3, - 484.6, - 483.9, - 483.5, - 483, - 482.7, - 482.6, - 482.5, - 482.1, - 481.8, - 481.3, - 480.4, - 479.4, - 478.8, - 478.4, - 477.9, - 476.9, - 476.1, - 475.7, - 475.3, - 475.1, - 474.8, - 474.5, - 474.1, - 473.5, - 472.6, - 472.1, - 471.5, - 470.5, - 469.8, - 469, - 468.8, - 468.7, - 468.6, - 468, - 467.7, - 467.6, - 467, - 466.5, - 466.1, - 465.5, - 465.3, - 465.2, - 465.3, - 465.1, - 464.9, - 464.8, - 464.4, - 463.8, - 463.3, - 463, - 462.8, - 462.7, - 462.4, - 461.9, - 461.5, - 461.2, - 461, - 460.4, - 460.3, - 460, - 459.4, - 458.8, - 458.4, - 458.1, - 457.7, - 457, - 456.2, - 455.1, - 453.5, - 452, - 450.5, - 449.1, - 447.5, - 445.4, - 441.9, - 437.5, - 433.9, - 428.4, - 424.9, - 422.9, - 425.7, - 434.6, - 437.5, - 438.6, - 437.5, - 432.8, - 428.7, - 422.9, - 416.4, - 409.6, - 404.1, - 406.2, - 403.4, - 401.6, - 402.4, - 402.2, - 401.9, - 401.8, - 475.9, - 474.8, - 474.2, - 473.6, - 472.2, - 470.7, - 469.6, - 467.8, - 466.7, - 465.3, - 463.8, - 462.5, - 461, - 459.5, - 457.8, - 456.7, - 455.8, - 455, - 453.8, - 451.9, - 450, - 448.1, - 445.3, - 441.3, - 437.7, - 434.8, - 432.5, - 430.6, - 430.3, - 430.9, - 432.5, - 434.7, - 436.6, - 437, - 436.8, - 436.6, - 436.2, - 435, - 432.1, - 430, - 429, - 428.5, - 428.8, - 429.4, - 429.9, - 429.8, - 429.9, - 430.5, - 431.2, - 431.8, - 432.9, - 434, - 436.2, - 438.3, - 440.8, - 442.9, - 444.8, - 446.7, - 450.1, - 465.9, - 483.5, - 504.5, - 528.6, - 541.1, - 546.1, - 546.9, - 547.6, - 548.1, - 548.1, - 547.7, - 546.9, - 546.3, - 545.8, - 545.2, - 544.2, - 543.3, - 542.6, - 542, - 541.4, - 540.8, - 540.1, - 539.4, - 538.7, - 538, - 537.2, - 536.1, - 534.9, - 533.9, - 532.9, - 532.1, - 531.3, - 530.4, - 529.5, - 528.7, - 528.3, - 527.6, - 527, - 526.4, - 525.6, - 524.8, - 524.2, - 523.5, - 522.5, - 521.5, - 520.5, - 519.9, - 519.5, - 518.8, - 518, - 517.1, - 516.2, - 515.3, - 514.8, - 514.4, - 513.6, - 513.1, - 512.4, - 511.6, - 510.8, - 510.1, - 509.2, - 508.1, - 507, - 506.2, - 505.5, - 505, - 504.4, - 503.7, - 503, - 501.9, - 500.7, - 499.7, - 499, - 498.3, - 497.8, - 497.6, - 497.5, - 496.9, - 496.4, - 495.1, - 494.6, - 494.5, - 494.1, - 493.6, - 492.9, - 492.4, - 492.1, - 491.7, - 490.9, - 490.3, - 490, - 489.3, - 488.5, - 487.7, - 487.2, - 486.3, - 485.7, - 485.1, - 484.5, - 484.1, - 483.5, - 483, - 482.5, - 482.1, - 481.7, - 481.3, - 481.1, - 480.5, - 479.8, - 479.1, - 478.1, - 477.7, - 477.3, - 476.9, - 476.1, - 475.3, - 474.8, - 474.3, - 474.1, - 473.8, - 473.4, - 473, - 472.5, - 471.5, - 470.7, - 470.3, - 469.1, - 468.3, - 467.7, - 467.6, - 467.4, - 467.3, - 466.7, - 466.4, - 466.3, - 465.9, - 465.8, - 465, - 464.4, - 464.3, - 464, - 464, - 464.1, - 463.7, - 463.4, - 463, - 462.6, - 462.3, - 462.1, - 461.7, - 461.5, - 461.2, - 460.9, - 460.5, - 460.2, - 459.8, - 459.4, - 459.3, - 459, - 458.3, - 457.9, - 457.5, - 457, - 456.7, - 456.1, - 455.4, - 454.1, - 451.8, - 449.8, - 448.7, - 447.6, - 445.3, - 441.8, - 438.1, - 434.7, - 430.8, - 427.8, - 426.4, - 430.1, - 438.4, - 441.6, - 441.9, - 441.7, - 439.4, - 431.1, - 427.5, - 422.9, - 416.2, - 412.2, - 412, - 413.2, - 408.9, - 406.1, - 405, - 404.6, - 404.3, - 409.3, - 471.9, - 469.6, - 468.7, - 465.6, - 464.9, - 465, - 465.8, - 465, - 463.6, - 462.8, - 461.5, - 459.7, - 457.7, - 454.4, - 451.7, - 450.3, - 449.2, - 448.9, - 448.7, - 447.5, - 445.8, - 443.5, - 438.8, - 435.5, - 433.5, - 431.1, - 429, - 428.3, - 428.5, - 429.4, - 431.3, - 433.6, - 434.9, - 434.9, - 434.5, - 434.2, - 433.7, - 432.2, - 429.5, - 427.7, - 426.8, - 426.7, - 427.3, - 427.8, - 428.2, - 428.2, - 428.6, - 429.4, - 430.2, - 430.8, - 431.8, - 434, - 436.6, - 438.1, - 440.2, - 441.8, - 443.7, - 445.4, - 453.8, - 472, - 483.3, - 497, - 519.3, - 534.9, - 541, - 543.4, - 544.3, - 545.1, - 545.9, - 546.6, - 546.3, - 545.6, - 545.2, - 544.6, - 543.6, - 542.5, - 541.9, - 541.2, - 540.6, - 540, - 539.4, - 538.7, - 538, - 537.5, - 536.5, - 535.7, - 534, - 532.8, - 531.7, - 530.8, - 530.1, - 529.3, - 528.3, - 527.6, - 527.2, - 526.5, - 525.8, - 525.1, - 524.2, - 523.5, - 523, - 522.4, - 521.7, - 520.7, - 519.7, - 518.9, - 518.5, - 517.9, - 517.1, - 516.2, - 515.3, - 514.6, - 513.8, - 513.2, - 512.7, - 512.1, - 511.4, - 510.7, - 509.8, - 508.9, - 508, - 507, - 506.1, - 505.3, - 504.5, - 504, - 503.4, - 502.8, - 501.9, - 500.8, - 499.8, - 498.6, - 497.9, - 497.3, - 496.7, - 496.5, - 496.3, - 495.5, - 494.9, - 494.1, - 493.8, - 493.6, - 493.2, - 492.7, - 492, - 491.5, - 491, - 490.5, - 489.7, - 489.4, - 489, - 488.2, - 487.5, - 486.7, - 486.2, - 485.5, - 485.1, - 484.4, - 483.9, - 483.6, - 482.9, - 482.4, - 481.9, - 481.3, - 480.8, - 480.2, - 479.6, - 478.7, - 478.1, - 477.6, - 477.2, - 476.8, - 476.4, - 476, - 475.5, - 474.6, - 473.8, - 473.2, - 472.9, - 472.5, - 472.3, - 471.7, - 471.7, - 470.9, - 469.8, - 469, - 468.1, - 467.4, - 466.7, - 466.3, - 466.1, - 466, - 465.4, - 465, - 464.6, - 464.5, - 464.6, - 464.1, - 463.6, - 463.2, - 463.1, - 463.2, - 462.9, - 462.4, - 462.2, - 462, - 461.5, - 461.5, - 461, - 460.6, - 460.3, - 459.9, - 459.8, - 459.5, - 459.1, - 458.7, - 458.4, - 457.9, - 457.3, - 456.9, - 456.7, - 456.3, - 455.7, - 455.5, - 455, - 454.4, - 453.3, - 450.9, - 448.6, - 447.3, - 446.2, - 444.2, - 439.9, - 436.1, - 433.8, - 431.1, - 429.8, - 433.7, - 442.2, - 444.4, - 444.2, - 443.5, - 441.6, - 437.7, - 428.6, - 425.4, - 421.4, - 418.2, - 416.6, - 415.9, - 414.4, - 411.3, - 409.4, - 409.2, - 408.4, - 414.5, - 424.8, - 468.5, - 466.6, - 463.6, - 458.1, - 456, - 455.9, - 457.1, - 458.2, - 456.2, - 457, - 457.8, - 455.5, - 452, - 449.5, - 447.4, - 445.7, - 443.9, - 442.1, - 440.2, - 438.9, - 437.8, - 436.2, - 433.6, - 431.8, - 430.1, - 428.2, - 427, - 426.6, - 426.8, - 427.7, - 429.8, - 432, - 432.6, - 432.3, - 431.8, - 431.4, - 430.6, - 429.1, - 427.3, - 425.9, - 425.4, - 425.5, - 426, - 426.4, - 426.5, - 426.9, - 427.6, - 428.3, - 429.1, - 429.7, - 431.5, - 434.7, - 436.3, - 437.7, - 439.4, - 440.9, - 443.1, - 448.8, - 461.3, - 473, - 482.5, - 490.5, - 500.4, - 514.6, - 529.8, - 535.8, - 538.8, - 540.7, - 543.3, - 544.8, - 545.1, - 544.9, - 544.4, - 543.6, - 542.7, - 541.7, - 541, - 540.3, - 539.7, - 539.1, - 538.7, - 537.9, - 537.1, - 536.7, - 535.6, - 534.4, - 533, - 531.9, - 530.7, - 529.9, - 529.1, - 528.2, - 527.2, - 526.5, - 526, - 525.4, - 524.3, - 523.6, - 523.1, - 522.1, - 521.5, - 521, - 520.5, - 520, - 519.1, - 518.3, - 517.7, - 516.8, - 516, - 515.4, - 514.6, - 513.5, - 512.7, - 512.1, - 511.7, - 511.2, - 510.3, - 509.6, - 508.8, - 507.8, - 507.1, - 506.2, - 505.2, - 504.4, - 503.8, - 503, - 502.3, - 501.6, - 500.6, - 499.8, - 499, - 497.8, - 497, - 496.5, - 495.8, - 495.4, - 495.1, - 494.4, - 493.9, - 493.4, - 493.1, - 492.6, - 491.9, - 491.4, - 490.9, - 490.5, - 490.1, - 489.6, - 488.9, - 488.5, - 487.7, - 487.2, - 486.5, - 485.8, - 485.2, - 484.6, - 484.2, - 483.6, - 483.2, - 482.5, - 482.1, - 481.9, - 481.2, - 480.3, - 479.7, - 479, - 478.4, - 477.5, - 476.9, - 476.7, - 476.4, - 476.1, - 475.8, - 475.5, - 474.7, - 473.7, - 472.7, - 472.1, - 471.7, - 471.5, - 471.3, - 470.9, - 470.2, - 469.4, - 468.7, - 467.9, - 467.4, - 466.6, - 465.9, - 465.5, - 465.2, - 464.9, - 464.4, - 463.9, - 463.6, - 463.5, - 463.3, - 463, - 462.6, - 462.3, - 462.2, - 461.9, - 461.5, - 461.2, - 461.2, - 460.9, - 460.4, - 460.3, - 459.9, - 459.4, - 459, - 458.7, - 458.5, - 458.3, - 457.9, - 457.5, - 457.2, - 456.6, - 456, - 455.8, - 455.6, - 455.2, - 454.6, - 454.4, - 453.9, - 453.1, - 452.2, - 450.2, - 448.3, - 446.3, - 444.2, - 441.3, - 438.2, - 436.1, - 434.6, - 433.6, - 436.3, - 441.1, - 445.9, - 446.1, - 444.7, - 442.3, - 438.9, - 434.2, - 427.9, - 426, - 425.1, - 423.5, - 420.5, - 417.5, - 416.9, - 415.8, - 414.7, - 413.9, - 418.3, - 428.8, - 431.4, - 466, - 463.9, - 458.9, - 455.1, - 453.7, - 452.5, - 451.6, - 450.6, - 449.1, - 448.2, - 447.2, - 445.3, - 443.9, - 443, - 441.6, - 439.9, - 438.1, - 436.3, - 434, - 432.3, - 431.9, - 431.2, - 429.6, - 428, - 426.8, - 425.6, - 425, - 424.6, - 424.4, - 425.3, - 427.6, - 429.4, - 429.5, - 429.3, - 429.1, - 428.9, - 428.3, - 427.1, - 425.4, - 424.4, - 424, - 424.1, - 424.4, - 424.5, - 424.9, - 426, - 426.6, - 427, - 427.7, - 428.8, - 431.6, - 434.4, - 436, - 437.4, - 438.9, - 440.4, - 444, - 456.2, - 464.8, - 472, - 478.7, - 484.2, - 490.4, - 496.9, - 509, - 520.6, - 529, - 536.7, - 540.3, - 542.2, - 543.4, - 543.8, - 543, - 542.2, - 541.2, - 540.4, - 539.6, - 538.9, - 538.5, - 538.2, - 537.8, - 537.1, - 536.3, - 535.4, - 534.2, - 532.9, - 531.6, - 530.6, - 529.6, - 528.8, - 528.1, - 527.3, - 526.4, - 525.8, - 525, - 524, - 523.2, - 522.2, - 521.6, - 521.1, - 520.6, - 520.3, - 519.9, - 519.2, - 518.5, - 517.7, - 516.9, - 516, - 515.2, - 514.4, - 513.4, - 512.1, - 511.2, - 510.9, - 510.5, - 509.8, - 508.9, - 508.2, - 507.7, - 506.9, - 505.9, - 505.1, - 504.3, - 503.4, - 502.7, - 502, - 501.2, - 500.5, - 499.5, - 498.7, - 497.4, - 496.5, - 495.9, - 495.4, - 494.7, - 494.4, - 494, - 493.5, - 492.9, - 492.6, - 492.2, - 491.5, - 490.8, - 490.6, - 489.9, - 489.5, - 489.1, - 488.7, - 488, - 487.4, - 486.6, - 486, - 485.4, - 484.7, - 484.1, - 483.7, - 483.4, - 482.8, - 482.3, - 481.5, - 481, - 480.8, - 480.2, - 479.5, - 478.8, - 478, - 477.3, - 476.5, - 476, - 475.8, - 475.5, - 475.3, - 475.1, - 474.5, - 473.9, - 472.9, - 472.1, - 471.4, - 470.9, - 470.6, - 470.3, - 469.7, - 468.8, - 468, - 467.4, - 467, - 466.4, - 465.6, - 464.9, - 464.5, - 464.2, - 463.9, - 463.6, - 463, - 462.6, - 462.4, - 462.1, - 461.9, - 461.6, - 461.2, - 460.9, - 460.5, - 460.4, - 460.1, - 460.1, - 459.7, - 459.3, - 459, - 458.8, - 458.4, - 457.9, - 457.5, - 457.1, - 457, - 456.8, - 456.4, - 455.7, - 455.1, - 454.8, - 454.7, - 454.4, - 453.9, - 453.4, - 452.9, - 452.5, - 451.9, - 451.1, - 449.9, - 448.6, - 446.4, - 442.8, - 440.4, - 438.8, - 437.3, - 436.9, - 440.7, - 443.3, - 445.1, - 446.6, - 445.8, - 443.8, - 440.9, - 436.6, - 433.1, - 431.3, - 430.5, - 429.6, - 428.2, - 425.8, - 421.4, - 419.1, - 418.6, - 418.5, - 422.5, - 430.8, - 433.6, - 433.2, - 462.5, - 458.4, - 454.6, - 453.2, - 452.1, - 450.9, - 449.8, - 448.5, - 447.3, - 445.5, - 442.8, - 441.4, - 440, - 437.8, - 436, - 434.7, - 433.3, - 431.5, - 430.1, - 429.3, - 427.9, - 426.4, - 425.4, - 424.5, - 423.9, - 423, - 422.2, - 421.5, - 421.1, - 421.8, - 424.5, - 426.2, - 426.3, - 426.1, - 426.6, - 426.9, - 426.3, - 425.1, - 423.5, - 422.6, - 422.3, - 422.3, - 422.4, - 422.8, - 423.9, - 425, - 425.5, - 425.7, - 426.5, - 428.3, - 431.2, - 433.4, - 435.3, - 437.1, - 438.5, - 440, - 444.8, - 457.5, - 464.3, - 470.4, - 475.8, - 479.6, - 484.7, - 492.1, - 503.5, - 514.8, - 526, - 536.9, - 541.8, - 542.7, - 542.7, - 542.1, - 541, - 540.2, - 539.4, - 538.8, - 538.3, - 537.9, - 537.7, - 537.2, - 536.6, - 536, - 534.8, - 534, - 532.8, - 531.5, - 530.2, - 529.4, - 528.4, - 527.8, - 527.2, - 526.6, - 525.9, - 525.2, - 523.9, - 522.8, - 522.3, - 521.4, - 520.7, - 520.1, - 519.6, - 519.3, - 518.9, - 518.3, - 517.5, - 516.4, - 515.3, - 514.5, - 513.8, - 513, - 511.9, - 510.9, - 510.2, - 509.8, - 509.3, - 508.6, - 508, - 507.3, - 506.9, - 506.1, - 505, - 504.1, - 503.3, - 502.5, - 501.9, - 501.2, - 500.5, - 499.7, - 498.6, - 497.7, - 496.6, - 495.4, - 494.7, - 494.1, - 493.6, - 493.4, - 493, - 492.6, - 492.1, - 491.6, - 491, - 490.3, - 489.8, - 489.6, - 488.8, - 488.3, - 488.2, - 487.8, - 487, - 486.4, - 485.5, - 484.9, - 484.5, - 484, - 483.6, - 483.1, - 482.6, - 481.9, - 481.4, - 480.9, - 480.3, - 479.8, - 479.3, - 478.3, - 477.5, - 477, - 476.3, - 475.4, - 474.9, - 474.7, - 474.7, - 474.5, - 474.1, - 473.1, - 472.5, - 471.8, - 471.2, - 470.9, - 470.3, - 469.9, - 469.1, - 468.4, - 467.9, - 467.1, - 466.4, - 465.9, - 465.3, - 464.5, - 464, - 463.8, - 463.4, - 463.1, - 462.7, - 461.8, - 461.3, - 461.1, - 460.7, - 460.5, - 460.1, - 459.7, - 459.6, - 459.2, - 459.1, - 458.9, - 458.7, - 458.5, - 458.2, - 458, - 457.9, - 457.5, - 457.2, - 456.7, - 456.1, - 455.7, - 455.3, - 455.3, - 454.7, - 454, - 453.6, - 453.3, - 453.1, - 452.7, - 452.1, - 451.6, - 451.4, - 451, - 450.2, - 449.3, - 448.1, - 446.5, - 443.5, - 441.3, - 440.7, - 441.1, - 442.8, - 445.6, - 446.3, - 446.1, - 445.9, - 445, - 443.2, - 441.3, - 438.8, - 437.7, - 437, - 435.3, - 433.6, - 432.2, - 430.8, - 428.3, - 423.4, - 424.7, - 429.8, - 434.9, - 435.9, - 435.2, - 434.7, - 458.8, - 454.2, - 452.5, - 451.5, - 450.3, - 449.2, - 448, - 446.8, - 445.2, - 442.8, - 440.8, - 439.3, - 437.6, - 434.5, - 431.7, - 430.2, - 429, - 427.3, - 426.7, - 425.4, - 423.8, - 422.8, - 422.4, - 421.8, - 420.7, - 419.3, - 418.4, - 417.7, - 417.7, - 419.5, - 421.5, - 422.6, - 422.7, - 423.3, - 424.2, - 424.5, - 424, - 423, - 421.7, - 420.9, - 420.3, - 420.1, - 420.3, - 421.6, - 422.8, - 423.4, - 424, - 424.3, - 425.7, - 427.8, - 430.2, - 432.5, - 434.3, - 436.5, - 438.3, - 441.1, - 448.1, - 457, - 462.6, - 467.5, - 471.2, - 476.2, - 482.2, - 490.1, - 497.7, - 508.2, - 521.1, - 535.4, - 540.6, - 541.3, - 540.5, - 539.2, - 538.8, - 538.2, - 537.5, - 537.1, - 536.9, - 536.8, - 536.6, - 535.9, - 534.9, - 534.2, - 533.5, - 532.8, - 531.6, - 530.3, - 529.2, - 528.1, - 527.4, - 527, - 526.4, - 525.9, - 525.1, - 524.4, - 523, - 522.2, - 521.5, - 520.8, - 519.9, - 518.9, - 518.4, - 518, - 517.6, - 517.1, - 516.4, - 515.3, - 514.3, - 513.2, - 512.3, - 511.6, - 510.5, - 509.8, - 509.3, - 508.9, - 508.3, - 507.8, - 507.2, - 506.6, - 506, - 505.2, - 504.4, - 503.4, - 502.4, - 501.5, - 500.8, - 500.1, - 499.4, - 498.7, - 497.8, - 496.9, - 495.8, - 494.5, - 493.7, - 492.9, - 492.7, - 492.3, - 491.4, - 491.1, - 491, - 490.6, - 490, - 489.2, - 488.9, - 488.5, - 487.5, - 487.1, - 487.4, - 486.8, - 485.6, - 485, - 484.6, - 484.3, - 483.9, - 483.5, - 482.8, - 482.4, - 481.7, - 481.1, - 480.6, - 480.1, - 479.7, - 479.2, - 478.3, - 477.2, - 476.7, - 476.2, - 475.3, - 474.4, - 473.7, - 473.6, - 473.8, - 473.5, - 472.8, - 471.9, - 471.3, - 470.8, - 470.3, - 469.9, - 469.3, - 468.8, - 468.3, - 467.6, - 466.9, - 466, - 465.3, - 464.7, - 464.1, - 463.4, - 463.1, - 462.8, - 462.5, - 462.1, - 461.4, - 460.5, - 459.9, - 459.7, - 459.4, - 459.1, - 458.7, - 458.4, - 458.3, - 458, - 457.8, - 457.6, - 457.5, - 457.3, - 457.1, - 457, - 456.9, - 456.5, - 456.2, - 455.7, - 455.1, - 454.6, - 454, - 453.9, - 453.4, - 452.9, - 452.6, - 452.3, - 452, - 451.8, - 451.3, - 450.9, - 450.6, - 450.1, - 449.4, - 448.5, - 447.7, - 446.4, - 444.5, - 443.2, - 444, - 445.5, - 445.6, - 445.6, - 445.8, - 445.6, - 445.4, - 445, - 444.1, - 443.4, - 442.9, - 442.2, - 440.8, - 439.1, - 438.4, - 437.5, - 436, - 434.1, - 434.5, - 437.9, - 438.9, - 438.4, - 437.5, - 436.7, - 437.3, - 455.1, - 451.7, - 450.7, - 449.8, - 448.6, - 447.4, - 446, - 444.5, - 442.2, - 440, - 438.3, - 436.7, - 435.3, - 432.6, - 429.4, - 426.8, - 425.2, - 424, - 422.6, - 421.1, - 420.2, - 420, - 419.7, - 418.9, - 417.8, - 416.7, - 415.5, - 414.7, - 415.4, - 417.5, - 418.3, - 418.7, - 419.4, - 420.3, - 421.2, - 421.7, - 421.5, - 420.7, - 419.7, - 419, - 418.5, - 418.4, - 419, - 420.5, - 421.6, - 422, - 422.5, - 423, - 424.9, - 426.9, - 428.8, - 431.7, - 433.5, - 435.9, - 438.8, - 443, - 448.1, - 454.7, - 459.2, - 462.1, - 467.4, - 473.7, - 479.9, - 488.2, - 496.5, - 504.1, - 511.3, - 525, - 536.8, - 538.1, - 537.8, - 537.4, - 537.4, - 537, - 536.4, - 535.8, - 535.5, - 535.3, - 534.9, - 534.2, - 533.6, - 533.1, - 532.6, - 531.8, - 530.8, - 529.5, - 528.2, - 527.3, - 526.5, - 526.3, - 525.6, - 524.8, - 524, - 523.1, - 521.9, - 521.3, - 520.6, - 520, - 518.9, - 518, - 517.3, - 516.9, - 516.5, - 516, - 515.5, - 514.3, - 513.4, - 512.2, - 511.3, - 510.4, - 509.5, - 509, - 508.5, - 507.9, - 507.2, - 506.5, - 505.9, - 505.4, - 504.7, - 504, - 503.3, - 502.4, - 501.6, - 500.7, - 499.7, - 499.1, - 498.4, - 497.4, - 496.9, - 496.2, - 494.9, - 493.6, - 492.7, - 492, - 491.8, - 491.3, - 490.1, - 489.7, - 489.7, - 489.4, - 488.8, - 488.4, - 488, - 487.6, - 486.6, - 486.6, - 486.6, - 485.6, - 484.3, - 484, - 484, - 483.8, - 483.4, - 482.8, - 481.8, - 481.3, - 480.8, - 480.2, - 479.7, - 479.3, - 479, - 478.4, - 477.3, - 476.4, - 475.8, - 475.3, - 474.6, - 473.5, - 472.9, - 472.7, - 472.5, - 472, - 471.5, - 471, - 470.4, - 469.9, - 469.5, - 469, - 468.5, - 468.1, - 467.6, - 466.8, - 465.9, - 465.1, - 464.5, - 464, - 463.4, - 462.8, - 462.1, - 461.7, - 461.4, - 460.8, - 460, - 459.6, - 459.1, - 458.5, - 458.2, - 457.8, - 457.4, - 457, - 457, - 456.9, - 456.6, - 456.3, - 456.4, - 456.2, - 456, - 455.8, - 455.6, - 455.4, - 455.1, - 454.6, - 453.9, - 453.6, - 453, - 452.4, - 451.9, - 451.6, - 451.4, - 450.9, - 450.5, - 450.4, - 450.2, - 449.8, - 449.5, - 449.1, - 448.5, - 447.9, - 447.1, - 446.1, - 445.1, - 445.2, - 445.5, - 445.4, - 445.2, - 445, - 444.9, - 445, - 445.1, - 444.7, - 444.5, - 444, - 443.1, - 442.3, - 441.5, - 440.8, - 440.3, - 439.8, - 439.4, - 439.1, - 439.8, - 439.8, - 439.3, - 438.8, - 438.3, - 437.9, - 438.1 - ] + "raw16": "heightmap.r16", + "min": 1.2, + "max": 6554.7 } } }, diff --git a/packages/terrain/starter/alpine/world.ts b/packages/terrain/starter/alpine/world.ts index 9e17179d3c..bf9431f4ff 100644 --- a/packages/terrain/starter/alpine/world.ts +++ b/packages/terrain/starter/alpine/world.ts @@ -1,30 +1,19 @@ -// The alpine starter world, as the game that copied it draws and collides with it. -// -// `bake.mjs` wrote a world package into `world/`: heightmap, splat masks, placements and the CC0 boulder. -// The engine's `WorldCells` streams it. `loadTerrainSplat` textures its ground. -// Everything that decides how the world looks, or what stops the player, is in this folder for the game to edit. -import type { IComputeDriven, ICtx } from "@threenative/core"; +import type { ICtx } from "@threenative/core"; +import { type IWorldPackage, loadTerrainSplat } from "@threenative/core/world"; +import type { IPhysicsContext } from "@threenative/physics"; import { - Heightfield, - type IWorldCellsLoadOptions, - type IWorldPackage, - WorldCells, - heightSamplerFromHeightmap, - loadTerrainSplat, - loadWorldHeightmap, -} from "@threenative/core/world"; -import { CollisionShape3D, type IPhysicsContext, RigidBody3D } from "@threenative/physics"; + type IStarterWorld, + type IStarterWorldSpec, + PropColliders as StarterPropColliders, + addStarterWorld, +} from "@threenative/terrain/starter"; import { EquirectangularReflectionMapping, - type Material, type Mesh, type MeshStandardMaterial, - Object3D, + type Object3D, type Texture, - Vector3, } from "three"; -import { HDRLoader } from "three/addons/loaders/HDRLoader.js"; - /** Where the game's asset source holds the baked `world/` folder. */ export const ALPINE_URL = "terrain/alpine/world.json"; @@ -42,294 +31,47 @@ export const PROP_SKY_LIGHT = 0.5; /** Metres around `follow` within which props collide. Beyond that, props only draw. */ export const PROP_COLLIDER_REACH = 60; -export interface IAlpineWorld { - readonly world: WorldCells; - /** One heightfield for the whole world, in place before `addAlpine` resolves. */ - readonly ground: RigidBody3D; - /** The prop colliders near `follow`. They stream with it (see `PROP_COLLIDER_REACH`). */ - readonly colliders: PropColliders; - /** The heightfield the ground stands on: `heightAt(x, z)` reads the terrain under any point. */ - readonly field: Heightfield; - /** Every placed prop's record, eight floats each, as the bake wrote it. */ - readonly placements: Float32Array; - /** The world's extent in metres, as the bake wrote it. */ - readonly extent: IWorldPackage["extent"]; +export interface IAlpineWorld extends IStarterWorld {} +const spec: IStarterWorldSpec<{ dispose(): void }> = { + name: "Alpine", + url: ALPINE_URL, + colliders: COLLIDERS, + colliderReach: PROP_COLLIDER_REACH, + loadSurface: (ctx, url) => loadTerrainSplat({ assets: ctx.assets, url }), + lighting: { sky: "sky.hdr", lods: false, dress: dressProp }, + streaming: { + ring: 5, + impostors: false, + gpuScene: false, + shadows: { cast: true, receive: true }, + budgets: { residentCells: 64, instances: 40_000, bytes: 64_000_000 }, + terrain: { streamRadius: 6 }, + }, +}; +export class PropColliders extends StarterPropColliders { + constructor( + ctx: ICtx, IPhysicsContext>, + follow: Object3D, + manifest: IWorldPackage, + records: Float32Array, + ) { + super(ctx, follow, manifest, records, COLLIDERS, PROP_COLLIDER_REACH, "Alpine"); + } } - -/** - * Stream the alpine world around `follow` (usually the player or the camera) and give it collision. - * `whileCurrent` stops a departed scene's async load from attaching anything. Its resources are - * released on cancellation, failure and when the game's entity registry ends this scene. - */ export async function addAlpine( ctx: ICtx, IPhysicsContext>, follow: Object3D, url = ALPINE_URL, whileCurrent: () => boolean = () => true, ): Promise { - const assertCurrent = () => { - if (whileCurrent()) return; - const error = new Error("Alpine world: its scene is no longer current."); - error.name = "AbortError"; - throw error; - }; - let surface: Material | undefined; - let releaseLighting: (() => void) | undefined; - let world: WorldCells | undefined; - let ground: RigidBody3D | undefined; - let colliders: PropColliders | undefined; - let released = false; - const dispose = () => { - if (released) return; - released = true; - colliders?.detach(); - colliders?.removeFromParent(); - ground?.dispose(); - ground?.object?.removeFromParent(); - world?.dispose(); - surface?.dispose(); - releaseLighting?.(); - }; - try { - assertCurrent(); - const manifest = (await (await fetchAsset(ctx, url, assertCurrent)).json()) as IWorldPackage; - assertCurrent(); - const base = url.slice(0, url.lastIndexOf("/") + 1); - const records = await ( - await fetchAsset(ctx, base + manifest.placements, assertCurrent) - ).arrayBuffer(); - assertCurrent(); - const [heightmapUrl] = await ctx.assets.resolve(base + manifest.terrain.heightmap); - assertCurrent(); - if (heightmapUrl === undefined) throw new Error("Alpine world: its heightmap is not served."); - const heightmap = await loadWorldHeightmap(heightmapUrl); - assertCurrent(); - surface = await loadTerrainSplat({ assets: ctx.assets, url }); - assertCurrent(); - releaseLighting = await lightProps(ctx, url, manifest, assertCurrent); - assertCurrent(); - const options: IWorldCellsLoadOptions = { - url, - assets: ctx.assets, - data: { manifest, placements: records, heightmap }, - surface, - follow, - ring: 5, - impostors: false, - gpuScene: false, - shadows: { cast: true, receive: true }, - budgets: { residentCells: 64, instances: 40_000, bytes: 64_000_000 }, - terrain: { streamRadius: 6 }, - }; - world = await WorldCells.load(options); - assertCurrent(); - const field = alpineField(manifest, heightmap); - ground = groundCollider(ctx, manifest, field); - const props = new Float32Array(records); - colliders = new PropColliders(ctx, follow, manifest, props); - ctx.add(world); - if (ground.object !== undefined) ctx.add(ground.object); - ctx.add(colliders); - ctx.entities.add(`alpine-world.${world.uuid}`, { dispose }); - return { world, ground, colliders, field, placements: props, extent: manifest.extent }; - } catch (error) { - dispose(); - throw error; - } + const result = await addStarterWorld(ctx, follow, spec, url, whileCurrent); + return result; } - -// Every prop gets the sky as its own envMap before WorldCells adopts the shared cached models. -// Without it, a boulder lit by the sun alone draws flat. -async function lightProps( - ctx: ICtx, IPhysicsContext>, - url: string, - manifest: IWorldPackage, - assertCurrent: () => void, -): Promise<() => void> { - const base = url.slice(0, url.lastIndexOf("/") + 1); - const [skyUrl] = await ctx.assets.resolve(`${base}sky.hdr`); - assertCurrent(); - if (skyUrl === undefined) throw new Error("Alpine world: 'sky.hdr' is not served."); - const sky = await new HDRLoader().loadAsync(skyUrl); - const originals = new Map(); - const dispose = () => { - for (const [material, original] of originals) { - if (material.envMap !== sky) continue; - material.envMap = original.envMap; - material.envMapIntensity = original.intensity; - } - sky.dispose(); - }; - try { - assertCurrent(); - sky.mapping = EquirectangularReflectionMapping; - for (const asset of Object.values(manifest.assets)) { - const model = await ctx.assets.model<{ scene: Object3D }>(base + asset.glb); - assertCurrent(); - model.scene.traverse((object) => { - const mesh = object as Mesh; - for (const material of [mesh.material ?? []].flat() as MeshStandardMaterial[]) - if (!originals.has(material)) - originals.set(material, { - envMap: material.envMap, - intensity: material.envMapIntensity, - }); - dressProp(mesh, sky); - }); - } - return dispose; - } catch (error) { - dispose(); - throw error; - } -} - /** One prop mesh: the sky as its envMap. */ function dressProp(mesh: Mesh, sky: Texture): void { for (const material of [mesh.material ?? []].flat() as MeshStandardMaterial[]) { + sky.mapping = EquirectangularReflectionMapping; material.envMap = sky; material.envMapIntensity = PROP_SKY_LIGHT; } } - -// The streamed terrain tiles arrive over several frames. A player spawned before its tile would -// fall through. One heightfield for the whole package avoids that, at a few hundred kilobytes. -/** The kit's one heightfield: the collider stands on it. */ -function alpineField(manifest: IWorldPackage, heightmap: Uint16Array): Heightfield { - const { extent, terrain } = manifest; - return Heightfield.fromSampler({ - columns: terrain.columns, - rows: terrain.rows, - width: extent.sizeX, - depth: extent.sizeZ, - origin: { x: extent.minX + extent.sizeX / 2, z: extent.minZ + extent.sizeZ / 2 }, - sampleHeight: heightSamplerFromHeightmap(terrain, extent, heightmap), - }); -} - -function groundCollider( - ctx: ICtx, IPhysicsContext>, - manifest: IWorldPackage, - field: Heightfield, -): RigidBody3D { - const { extent } = manifest; - const anchor = new Object3D(); - anchor.position.set(extent.minX + extent.sizeX / 2, 0, extent.minZ + extent.sizeZ / 2); - return new RigidBody3D({ - object: anchor, - physics: ctx.physics, - type: "fixed", - entity: "terrain", - shape: CollisionShape3D.heightfield(field.rows, field.columns, field.toColliderHeights(), { - x: extent.sizeX, - y: 1, - z: extent.sizeZ, - }), - }); -} - -/** - * Fixed bodies for the props within `PROP_COLLIDER_REACH` of `follow`, rebuilt when it has moved a few - * metres. Thousands of resident bodies cost Rapier about 0.5 s per 8 s (measured). A hundred do not. - */ -export class PropColliders extends Object3D implements IComputeDriven { - readonly warmupNodes: readonly unknown[] = []; - readonly processCadence = "render" as const; - readonly #live = new Map(); - readonly #runs: { asset: string; offset: number; count: number }[] = []; - readonly #at = { x: Number.NaN, z: Number.NaN }; - readonly #followPosition = new Vector3(); - #released = false; - - constructor( - private readonly ctx: ICtx, IPhysicsContext>, - private readonly follow: Object3D, - manifest: IWorldPackage, - private readonly records: Float32Array, - ) { - super(); - this.name = "alpine-prop-colliders"; - for (const cell of manifest.cells) - for (const run of cell.runs) if (COLLIDERS[run.asset]) this.#runs.push(run); - try { - this.#rebuild(); - } catch (error) { - this.detach(); - throw error; - } - } - - /** How many prop bodies exist right now. */ - get active(): number { - return this.#live.size; - } - - get released(): boolean { - return this.#released; - } - - attachRenderer(): void {} - - process(): void { - if (this.#released) return; - const p = this.follow.getWorldPosition(this.#followPosition); - if (Math.hypot(p.x - this.#at.x, p.z - this.#at.z) > 4 || Number.isNaN(this.#at.x)) - this.#rebuild(); - } - - detach(): void { - for (const body of this.#live.values()) body.dispose(); - this.#live.clear(); - this.#released = true; - } - - #rebuild(): void { - const { x: fx, z: fz } = this.follow.getWorldPosition(this.#followPosition); - this.#at.x = fx; - this.#at.z = fz; - const keep = new Set(); - for (const run of this.#runs) { - const shape = COLLIDERS[run.asset]; - if (!shape) continue; - for (let i = run.offset; i < run.offset + run.count; i++) { - const x = this.records[i * 8] as number; - const z = this.records[i * 8 + 2] as number; - if (Math.hypot(x - fx, z - fz) > PROP_COLLIDER_REACH) continue; - keep.add(i); - if (this.#live.has(i)) continue; - const collider = CollisionShape3D.sphere( - shape.sphere * (this.records[i * 8 + 7] as number), - ); - this.#live.set( - i, - new RigidBody3D({ - physics: this.ctx.physics, - type: "fixed", - entity: `${run.asset}.${String(i)}`, - position: { x, y: this.records[i * 8 + 1] as number, z }, - shape: collider, - }), - ); - } - } - for (const [i, body] of this.#live) - if (!keep.has(i)) { - body.dispose(); - this.#live.delete(i); - } - } -} - -async function fetchAsset( - ctx: ICtx, IPhysicsContext>, - url: string, - assertCurrent: () => void, -): Promise { - const [resolved] = await ctx.assets.resolve(url); - assertCurrent(); - if (resolved === undefined) throw new Error(`Alpine world: '${url}' is not served.`); - const response = await fetch(resolved); - assertCurrent(); - if (!response.ok) throw new Error(`Alpine world: '${url}' answered ${String(response.status)}.`); - return response; -} diff --git a/packages/terrain/starter/coastal/bake.mjs b/packages/terrain/starter/coastal/bake.mjs index 58750cf121..70097ec850 100644 --- a/packages/terrain/starter/coastal/bake.mjs +++ b/packages/terrain/starter/coastal/bake.mjs @@ -1,118 +1,6 @@ -// Bakes this folder's recipe into a self-contained world package. Run it once from your game -// project, then serve the folder it writes: -// node src/terrain/coastal/bake.mjs [--assets ] [--out ] -// The engine's `WorldCells` streams `world/world.json` and `loadTerrainSplat` textures its ground; -// the game never evaluates terrain at runtime. -import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { Terrain, applyPlacementOverrides, bakeWorldPackage } from "@threenative/terrain"; +// Run with [--assets ] [--out ]; defaults to installed assets and ./world. +import { bakeKit } from "@threenative/terrain/kit"; -const here = dirname(fileURLToPath(import.meta.url)); - -/** `--key value` after the script; `--assets` and `--out` default as noted below. */ -function flag(name, fallback) { - const at = process.argv.indexOf(`--${name}`); - return at === -1 ? fallback : resolve(process.argv[at + 1] ?? ""); -} - -// The installed kit ships its models and textures beside the package manifest, so a copied kit -// finds them without asking the game for a path. `--assets` overrides that for a local checkout. -const installed = dirname(fileURLToPath(import.meta.resolve("@threenative/terrain/package.json"))); -const assetsDir = flag("assets", join(installed, "starter-assets")); -const outDir = flag("out", join(here, "world")); - -const recipe = JSON.parse(await readFile(join(here, "recipe.json"), "utf8")); -const assetTable = JSON.parse(await readFile(join(here, "assets.json"), "utf8")); -const surface = JSON.parse(await readFile(join(here, "surface.json"), "utf8")); - -const state = applyPlacementOverrides( - Terrain.fromJSON(recipe.recipe).evaluate(), - recipe.placementOverrides ?? {}, -); - -const assets = Object.fromEntries( - Object.entries(assetTable).map(([id, spec]) => [ - id, - { - bounds: spec.bounds, - glb: `models/${spec.near.split("/").at(-1)}`, - ...(spec.mid - ? { lods: [{ distance: spec.midDistance, glb: `models/${spec.mid.split("/").at(-1)}` }] } - : {}), - maxDistance: spec.maxDistance, - }, - ]), -); - -const started = performance.now(); -const { manifest, splat, files } = bakeWorldPackage(state, { - assets, - layers: { table: "terrain-table.json" }, +await bakeKit(import.meta.url, { + sky: "hdri/kloofendal_48d_partly_cloudy_1k.hdr", }); -const written = new Map(); - -async function write(relative, bytes) { - await mkdir(join(outDir, dirname(relative)), { recursive: true }); - await writeFile(join(outDir, relative), bytes); - written.set(relative, bytes.length ?? bytes.byteLength); -} - -async function copy(from, to) { - await mkdir(join(outDir, dirname(to)), { recursive: true }); - await copyFile(join(assetsDir, from), join(outDir, to)); - written.set(to, (await readFile(join(outDir, to))).byteLength); -} - -await write("world.json", `${JSON.stringify(manifest, null, 2)}\n`); -// The lake and river the generator evaluated, so the game draws the water the bake shaped. -const round = (value) => Math.round(value * 1_000) / 1_000; -await write( - "water.json", - `${JSON.stringify( - { - lakes: state.waters.map((lake) => ({ - id: lake.id, - at: lake.at, - radius: lake.radius, - level: lake.level, - })), - rivers: state.rivers.map((river) => ({ - id: river.id, - width: round(river.width), - points: river.points.map((point) => point.map(round)), - })), - }, - null, - 2, - )}\n`, -); -for (const [name, bytes] of Object.entries(files)) await write(name, bytes); -for (const spec of Object.values(assetTable)) { - await copy(spec.near, `models/${spec.near.split("/").at(-1)}`); - if (spec.mid) await copy(spec.mid, `models/${spec.mid.split("/").at(-1)}`); -} -// The sky the props are lit by (CC0 Poly Haven Kloofendal); world.ts gives it to their materials. -await copy("hdri/kloofendal_48d_partly_cloudy_1k.hdr", "sky.hdr"); -for (const layer of [surface.base, ...surface.layers]) { - const source = surface.sources[layer.id]; - await copy(source.diff, `${surface.textures}/${layer.id}_diff.jpg`); - await copy(source.nrm, `${surface.textures}/${layer.id}_nrm.jpg`); -} -// `sources` is the bake's own lookup: the runtime reads only base/layers/splat/textures. -await write( - "terrain-table.json", - `${JSON.stringify({ ...surface, splat, textures: "textures" }, null, 2)}\n`, -); - -const perAsset = {}; -for (const cell of manifest.cells) - for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; -const total = [...written.values()].reduce((sum, bytes) => sum + bytes, 0); -console.log( - `${Object.entries(perAsset) - .map(([asset, count]) => `${asset} ${String(count)}`) - .join(", ")} placements; ${[...written] - .map(([name, bytes]) => `${name} ${String(bytes)} B`) - .join(", ")}; ${String(total)} B total; ${Math.round(performance.now() - started)} ms`, -); diff --git a/packages/terrain/starter/coastal/heightmap.r16 b/packages/terrain/starter/coastal/heightmap.r16 new file mode 100644 index 0000000000..b2db425b79 Binary files /dev/null and b/packages/terrain/starter/coastal/heightmap.r16 differ diff --git a/packages/terrain/starter/coastal/recipe.json b/packages/terrain/starter/coastal/recipe.json index fc4d1bfc82..1225f2beae 100644 --- a/packages/terrain/starter/coastal/recipe.json +++ b/packages/terrain/starter/coastal/recipe.json @@ -17,66057 +17,9 @@ "data": { "width": 257, "height": 257, - "values": [ - 111.1, - 106.3, - 103, - 101.2, - 95.6, - 90.5, - 85.2, - 82.3, - 81.3, - 80.3, - 78.9, - 77.5, - 75.6, - 72.7, - 69.4, - 67.9, - 66.7, - 65.2, - 64, - 62.9, - 62.2, - 61.5, - 60.9, - 59.5, - 58.2, - 57.2, - 56.3, - 54.4, - 52, - 50.8, - 49.9, - 49, - 47.8, - 47.2, - 46.6, - 45.8, - 45.2, - 44.6, - 44.1, - 43.5, - 42.9, - 42.6, - 42.3, - 42, - 41.1, - 40.8, - 40.3, - 40, - 39.8, - 39.2, - 38.6, - 38.2, - 37.7, - 37.3, - 37, - 36.8, - 36.6, - 36, - 35.1, - 34.2, - 33.3, - 32.3, - 31.5, - 30.7, - 30, - 29.6, - 29.1, - 28.8, - 28.3, - 27.9, - 27.7, - 27.4, - 27.1, - 26.8, - 26.4, - 26.1, - 25.8, - 25.4, - 25.2, - 25, - 24.8, - 24.6, - 24.3, - 23.9, - 23.5, - 23.1, - 22.6, - 22.2, - 21.8, - 21.3, - 20.7, - 20.1, - 19.4, - 19.2, - 19.3, - 19.3, - 19.2, - 19.1, - 18.7, - 17.9, - 17.3, - 16.8, - 16.2, - 15.6, - 15.2, - 14.9, - 14.6, - 14.1, - 13.2, - 12.1, - 11.2, - 10.8, - 10.5, - 10.4, - 10.3, - 9.7, - 9.7, - 9.8, - 9.7, - 9.7, - 9.7, - 9.6, - 9.5, - 9.3, - 9.1, - 8.9, - 8.7, - 8.4, - 8, - 7.3, - 6.7, - 6.1, - 5.5, - 4.9, - 4.2, - 4, - 4, - 4.2, - 4.4, - 4.4, - 4.5, - 4.6, - 4.7, - 4.8, - 5.1, - 5.4, - 5.6, - 5.8, - 6, - 6.3, - 6.6, - 7, - 7.3, - 7.5, - 7.6, - 7.8, - 7.9, - 7.9, - 8, - 8.1, - 8.2, - 8.2, - 8.3, - 8.3, - 8.3, - 8.4, - 8.5, - 8.6, - 8.7, - 8.8, - 8.8, - 8.8, - 8.7, - 8.8, - 8.9, - 9.1, - 9.3, - 9.5, - 9.6, - 9.8, - 9.9, - 10.1, - 10.2, - 10.4, - 10.6, - 10.7, - 10.9, - 11, - 10.9, - 10.9, - 11.2, - 11.5, - 11.8, - 12.1, - 12.2, - 12.3, - 12.5, - 12.7, - 12.9, - 13, - 13, - 13.1, - 13.2, - 13.3, - 13.3, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.5, - 13.4, - 13.5, - 13.6, - 13.6, - 13.6, - 13.7, - 13.9, - 13.8, - 13.7, - 13.8, - 13.8, - 13.8, - 13.8, - 13.9, - 14, - 14, - 14, - 14.1, - 14.1, - 14.1, - 14.2, - 14.2, - 14.2, - 14.2, - 14.1, - 14.1, - 14.2, - 14.1, - 14.1, - 14, - 14, - 13.9, - 14, - 13.9, - 13.8, - 13.9, - 13.8, - 14, - 13.9, - 14, - 14.1, - 14.2, - 14.2, - 14.2, - 14.2, - 109.8, - 105.4, - 101.8, - 100.2, - 93.9, - 88.8, - 83.3, - 81.1, - 80.3, - 79.4, - 78.2, - 76.9, - 75.6, - 72.9, - 69.7, - 67.4, - 66.1, - 64.7, - 63.5, - 62.5, - 61.7, - 61, - 60.4, - 59.5, - 58.7, - 57.9, - 56.4, - 54.4, - 52, - 50.4, - 49.6, - 48.8, - 48, - 47.1, - 46.2, - 45.6, - 45.1, - 44.5, - 44, - 43.5, - 42.9, - 42.6, - 42.3, - 42, - 41.1, - 40.7, - 40.3, - 40.2, - 40, - 39.3, - 38.7, - 38.2, - 37.7, - 37.4, - 37.2, - 37, - 36.5, - 35.9, - 35.1, - 34, - 33.2, - 32.4, - 31.5, - 30.7, - 30, - 29.6, - 29.1, - 28.8, - 28.3, - 27.9, - 27.6, - 27.3, - 27.1, - 26.8, - 26.5, - 26.2, - 25.9, - 25.6, - 25.4, - 25.3, - 25, - 24.7, - 24.4, - 24, - 23.6, - 23.2, - 22.8, - 22.3, - 21.8, - 21.3, - 20.7, - 20.2, - 19.3, - 19.2, - 19.3, - 19.3, - 19.2, - 19, - 18.7, - 18.1, - 17.4, - 16.7, - 16.2, - 15.6, - 15.4, - 15.2, - 14.7, - 13.8, - 12.8, - 11.8, - 11.2, - 10.9, - 10.8, - 10.6, - 10.3, - 10.2, - 10.2, - 10.2, - 10.1, - 10.2, - 9.9, - 9.7, - 9.5, - 9.3, - 9.1, - 8.8, - 8.2, - 7.6, - 6.8, - 6.3, - 5.8, - 5.4, - 5.1, - 5, - 4.4, - 4, - 4, - 4.1, - 4.2, - 4.2, - 4.5, - 4.5, - 4.5, - 4.5, - 4.8, - 5.3, - 5.5, - 5.7, - 5.8, - 6.1, - 6.3, - 6.6, - 7, - 7.3, - 7.4, - 7.6, - 7.7, - 7.8, - 7.9, - 8, - 8, - 8, - 8.1, - 8.1, - 8.1, - 8.2, - 8.3, - 8.4, - 8.5, - 8.6, - 8.6, - 8.6, - 8.6, - 8.8, - 8.9, - 9.1, - 9.2, - 9.3, - 9.5, - 9.7, - 9.9, - 10.1, - 10.3, - 10.4, - 10.6, - 10.8, - 11, - 11.1, - 11.1, - 11.3, - 11.5, - 11.7, - 12, - 12.2, - 12.4, - 12.5, - 12.7, - 12.8, - 13, - 13.1, - 13.1, - 13.2, - 13.3, - 13.3, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.5, - 13.5, - 13.5, - 13.5, - 13.5, - 13.6, - 13.6, - 13.7, - 13.8, - 13.7, - 13.6, - 13.6, - 13.6, - 13.7, - 13.7, - 13.9, - 13.9, - 13.9, - 14, - 14, - 14.1, - 14.1, - 14.2, - 14.2, - 14.2, - 14.2, - 14.2, - 14.1, - 14.1, - 14, - 13.8, - 13.8, - 13.7, - 13.6, - 13.6, - 13.6, - 13.7, - 13.7, - 13.7, - 13.8, - 13.9, - 14, - 14, - 14.1, - 14.2, - 14.3, - 14.3, - 108.1, - 104.7, - 100.6, - 98.6, - 92.3, - 87.2, - 82.4, - 79.7, - 79.2, - 78.4, - 77.3, - 76.3, - 75.1, - 73.6, - 70.3, - 67.7, - 65.2, - 64.2, - 63.4, - 62.3, - 61.3, - 60.7, - 60.1, - 59.4, - 58.9, - 57.9, - 56.2, - 54.6, - 52.2, - 50.4, - 49.5, - 48.7, - 47.9, - 47, - 46.1, - 45.4, - 44.8, - 44.4, - 43.9, - 43.4, - 42.9, - 42.6, - 42.4, - 42.1, - 41.2, - 40.7, - 40.5, - 40.4, - 40, - 39.1, - 38.6, - 38.1, - 37.7, - 37.4, - 37.2, - 37, - 36.5, - 35.9, - 35.2, - 34.2, - 33.2, - 32.4, - 31.5, - 30.7, - 30.1, - 29.6, - 29.2, - 28.8, - 28.3, - 27.9, - 27.7, - 27.4, - 27.2, - 26.9, - 26.6, - 26.4, - 26, - 25.7, - 25.4, - 25.3, - 25, - 24.7, - 24.4, - 24.1, - 23.8, - 23.3, - 22.9, - 22.3, - 21.9, - 21.4, - 20.9, - 20.3, - 19.6, - 19.1, - 19.2, - 19.3, - 19.2, - 19.1, - 18.8, - 18.2, - 17.4, - 16.8, - 16.3, - 16, - 15.7, - 15.3, - 14.8, - 13.9, - 12.9, - 11.9, - 11.4, - 11.1, - 11, - 10.8, - 10.6, - 10.6, - 10.6, - 10.6, - 10.4, - 10.2, - 10, - 9.8, - 9.6, - 9.3, - 8.7, - 7.8, - 7.1, - 6.5, - 6, - 5.7, - 5.4, - 5.1, - 5, - 4.8, - 4.7, - 4.1, - 4, - 4.1, - 4.1, - 4.3, - 4.4, - 4.4, - 4.4, - 4.4, - 4.6, - 5, - 5.4, - 5.5, - 5.7, - 5.9, - 6.1, - 6.4, - 6.7, - 7.1, - 7.3, - 7.4, - 7.5, - 7.6, - 7.7, - 7.7, - 7.8, - 7.9, - 8, - 8, - 7.9, - 8, - 8.1, - 8.2, - 8.4, - 8.4, - 8.4, - 8.6, - 8.6, - 8.7, - 8.9, - 9.1, - 9.2, - 9.4, - 9.7, - 9.8, - 10, - 10.2, - 10.4, - 10.5, - 10.8, - 10.9, - 11.1, - 11.3, - 11.4, - 11.6, - 11.7, - 11.9, - 12.1, - 12.3, - 12.5, - 12.7, - 12.8, - 13, - 13, - 13.1, - 13.1, - 13.3, - 13.3, - 13.3, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.5, - 13.5, - 13.6, - 13.6, - 13.4, - 13.5, - 13.6, - 13.6, - 13.7, - 13.6, - 13.6, - 13.6, - 13.6, - 13.6, - 13.7, - 13.8, - 13.8, - 13.9, - 14, - 14, - 14.1, - 14.1, - 14.2, - 14.2, - 14.2, - 14.2, - 14.2, - 14.1, - 14, - 14, - 13.9, - 13.9, - 13.7, - 13.6, - 13.6, - 13.5, - 13.7, - 13.7, - 13.7, - 13.7, - 13.8, - 13.8, - 13.9, - 14.1, - 14.2, - 14.3, - 14.4, - 105.9, - 102.5, - 99.5, - 97.3, - 91.2, - 85.5, - 81.5, - 78.8, - 78.2, - 77.5, - 76.6, - 75.7, - 74.7, - 73.8, - 71.6, - 67.3, - 64.5, - 63.7, - 62.9, - 61.8, - 61, - 60.6, - 60.1, - 59.4, - 58.8, - 57.7, - 55.1, - 54, - 52.2, - 50.3, - 49.5, - 48.6, - 47.8, - 47.1, - 46.2, - 45.4, - 44.7, - 44.2, - 43.8, - 43.4, - 42.9, - 42.6, - 42.3, - 41.8, - 41.2, - 40.9, - 40.6, - 40.4, - 39.8, - 39.1, - 38.6, - 38, - 37.7, - 37.5, - 37.3, - 37, - 36.5, - 35.9, - 35.2, - 34.3, - 33.3, - 32.3, - 31.4, - 30.6, - 30, - 29.5, - 29.2, - 28.7, - 28.3, - 27.9, - 27.7, - 27.4, - 27.2, - 27, - 26.7, - 26.5, - 26.2, - 25.9, - 25.6, - 25.4, - 25.1, - 24.7, - 24.4, - 24.2, - 23.9, - 23.4, - 22.9, - 22.4, - 21.9, - 21.4, - 21.1, - 20.6, - 19.8, - 19.1, - 19.2, - 19.2, - 19.2, - 19.1, - 18.8, - 18.1, - 17.3, - 16.9, - 16.5, - 16.3, - 16.1, - 15.6, - 15, - 14.2, - 13.4, - 12.5, - 11.8, - 11.3, - 11.1, - 11.1, - 11, - 10.9, - 10.9, - 10.8, - 10.6, - 10.3, - 10.1, - 9.8, - 9.3, - 8.6, - 7.8, - 7.1, - 6.6, - 6.1, - 5.6, - 5.3, - 5.1, - 4.9, - 4.8, - 4.7, - 4.7, - 4.2, - 3.9, - 4, - 4.1, - 4.2, - 4.2, - 4.2, - 4.2, - 4.3, - 4.4, - 4.8, - 5.2, - 5.4, - 5.5, - 5.7, - 5.9, - 6.2, - 6.6, - 6.9, - 7.1, - 7.2, - 7.4, - 7.5, - 7.7, - 7.6, - 7.7, - 7.8, - 7.9, - 7.7, - 7.7, - 7.9, - 8, - 8.2, - 8.3, - 8.2, - 8.2, - 8.3, - 8.5, - 8.9, - 9.1, - 9.2, - 9.4, - 9.6, - 9.8, - 10.1, - 10.3, - 10.6, - 10.7, - 10.9, - 11, - 11.1, - 11.3, - 11.5, - 11.6, - 11.8, - 11.8, - 11.9, - 12.2, - 12.4, - 12.6, - 12.9, - 13.1, - 13.2, - 13.1, - 13.2, - 13.2, - 13.3, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.3, - 13.3, - 13.4, - 13.5, - 13.5, - 13.5, - 13.4, - 13.3, - 13.4, - 13.6, - 13.6, - 13.6, - 13.6, - 13.6, - 13.6, - 13.6, - 13.6, - 13.6, - 13.7, - 13.7, - 13.8, - 13.9, - 14, - 14, - 14.1, - 14.2, - 14.2, - 14.2, - 14.2, - 14.2, - 14.1, - 14, - 14, - 13.9, - 13.8, - 13.7, - 13.6, - 13.5, - 13.5, - 13.6, - 13.7, - 13.7, - 13.7, - 13.8, - 13.8, - 13.9, - 14, - 14.2, - 14.3, - 14.5, - 103, - 100.4, - 98.8, - 95.8, - 89.9, - 84.1, - 80.6, - 78.4, - 77.4, - 76.7, - 76, - 75.2, - 74.3, - 73.4, - 71.3, - 66.3, - 64.4, - 63.4, - 62.4, - 61.5, - 60.8, - 60.4, - 59.8, - 59.2, - 58.8, - 57.6, - 54.5, - 53.6, - 52.5, - 50.7, - 49.1, - 48.4, - 47.5, - 46.9, - 46.1, - 45.3, - 44.6, - 44.3, - 43.9, - 43.5, - 42.9, - 42.6, - 42.3, - 41.8, - 41.2, - 40.8, - 40.5, - 40.2, - 39.7, - 39.3, - 38.9, - 38.6, - 38.1, - 37.5, - 37.4, - 37.1, - 36.5, - 35.8, - 35.2, - 34.4, - 33.4, - 32.4, - 31.4, - 30.6, - 30, - 29.5, - 29.1, - 28.6, - 28.2, - 27.9, - 27.6, - 27.4, - 27.2, - 26.9, - 26.7, - 26.5, - 26.2, - 25.9, - 25.7, - 25.5, - 25.1, - 24.7, - 24.5, - 24.2, - 24, - 23.5, - 23, - 22.5, - 22.1, - 21.9, - 21.7, - 20.9, - 19.8, - 19.1, - 19.2, - 19.2, - 19.2, - 19.1, - 18.7, - 18, - 17.4, - 17, - 16.7, - 16.4, - 16.1, - 15.7, - 15, - 14.4, - 13.8, - 12.9, - 12.1, - 11.5, - 11.3, - 11.3, - 11.3, - 11.3, - 11.1, - 10.9, - 10.6, - 10.4, - 10.2, - 9.5, - 8.4, - 7.9, - 7.3, - 6.7, - 6.1, - 5.6, - 5.2, - 5, - 4.8, - 4.7, - 4.7, - 4.6, - 4.4, - 4.2, - 3.8, - 3.8, - 4.1, - 4, - 4, - 4, - 4.1, - 4.3, - 4.5, - 4.7, - 4.9, - 5.2, - 5.3, - 5.5, - 5.8, - 6.1, - 6.4, - 6.7, - 7, - 7.2, - 7.3, - 7.5, - 7.7, - 7.8, - 7.5, - 7.5, - 7.5, - 7.4, - 7.6, - 7.8, - 7.9, - 7.9, - 8, - 8, - 8.1, - 8.3, - 8.6, - 8.9, - 9.2, - 9.4, - 9.6, - 9.8, - 10, - 10.2, - 10.4, - 10.5, - 10.7, - 10.8, - 11, - 11.2, - 11.4, - 11.6, - 11.6, - 11.8, - 12, - 12.1, - 12.3, - 12.5, - 12.7, - 13.2, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.4, - 13.5, - 13.5, - 13.4, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.4, - 13.4, - 13.4, - 13.3, - 13.4, - 13.4, - 13.5, - 13.6, - 13.5, - 13.5, - 13.6, - 13.6, - 13.5, - 13.6, - 13.6, - 13.6, - 13.7, - 13.8, - 13.9, - 14, - 14, - 14, - 14.1, - 14.2, - 14.2, - 14.2, - 14.2, - 14.1, - 14.1, - 14.1, - 14, - 13.9, - 13.7, - 13.6, - 13.5, - 13.4, - 13.6, - 13.7, - 13.7, - 13.7, - 13.8, - 13.9, - 14, - 14, - 14.1, - 14.3, - 14.4, - 100.8, - 99.5, - 97.5, - 94.3, - 88.8, - 83.4, - 80.1, - 78, - 76.5, - 76, - 75.3, - 74.6, - 73.9, - 72.8, - 70.3, - 66.1, - 64.6, - 63.2, - 62.3, - 61.6, - 60.7, - 60.2, - 59.6, - 59, - 58.5, - 57.4, - 54.2, - 53.1, - 52.3, - 50.7, - 48.8, - 48.2, - 47.5, - 47, - 46.1, - 45.2, - 44.5, - 44.3, - 44, - 43.7, - 43, - 42.6, - 42.1, - 41.7, - 41.3, - 40.8, - 40.5, - 40, - 39.6, - 39.3, - 39.1, - 38.9, - 38.5, - 37.8, - 37.3, - 37, - 36.5, - 35.9, - 35.2, - 34.4, - 33.5, - 32.5, - 31.6, - 30.7, - 30, - 29.5, - 29, - 28.6, - 28.2, - 27.9, - 27.7, - 27.5, - 27.2, - 26.9, - 26.7, - 26.5, - 26.2, - 26, - 25.8, - 25.5, - 25.1, - 24.8, - 24.5, - 24.3, - 23.9, - 23.4, - 23, - 22.6, - 22.4, - 22.3, - 22.2, - 21.6, - 20.1, - 19.1, - 19.2, - 19.2, - 19.2, - 19, - 18.6, - 17.9, - 17.3, - 17, - 16.7, - 16.5, - 16.2, - 15.5, - 14.9, - 14.4, - 13.8, - 12.8, - 12.2, - 11.7, - 11.5, - 11.7, - 11.6, - 11.4, - 11.2, - 11, - 10.7, - 10.4, - 9.7, - 8.8, - 8.1, - 7.5, - 6.9, - 6.5, - 6, - 5.5, - 5.1, - 4.9, - 4.7, - 4.6, - 4.5, - 4.4, - 4.3, - 3.9, - 3.7, - 3.8, - 4, - 3.9, - 3.9, - 3.9, - 4.1, - 4.4, - 4.5, - 4.6, - 4.7, - 4.9, - 5.1, - 5.4, - 5.7, - 6, - 6.3, - 6.7, - 6.9, - 7.1, - 7.3, - 7.4, - 7.6, - 7.8, - 7.3, - 7.2, - 7.2, - 7.2, - 7.4, - 7.6, - 7.5, - 7.6, - 7.9, - 7.9, - 8.1, - 8.5, - 8.7, - 9.1, - 9.3, - 9.5, - 9.7, - 9.8, - 10, - 10.1, - 10.3, - 10.4, - 10.7, - 10.8, - 11, - 11.2, - 11.4, - 11.6, - 11.7, - 11.9, - 12.1, - 12.2, - 12.4, - 12.5, - 12.7, - 13.1, - 13.6, - 13.5, - 13.5, - 13.4, - 13.4, - 13.5, - 13.5, - 13.5, - 13.4, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.4, - 13.4, - 13.4, - 13.3, - 13.3, - 13.4, - 13.5, - 13.5, - 13.5, - 13.5, - 13.5, - 13.5, - 13.5, - 13.6, - 13.7, - 13.7, - 13.8, - 13.9, - 14, - 14, - 14.1, - 14.2, - 14.2, - 14.2, - 14.2, - 14.2, - 14.2, - 14.1, - 14.1, - 14, - 14, - 13.8, - 13.6, - 13.5, - 13.4, - 13.4, - 13.6, - 13.6, - 13.7, - 13.7, - 13.8, - 13.9, - 14, - 14.1, - 14.3, - 14.4, - 100.3, - 98.4, - 95.7, - 92.5, - 87.9, - 82.9, - 79.5, - 77.8, - 76.2, - 75.1, - 74.3, - 73.8, - 73.3, - 72.2, - 69.6, - 65.5, - 64.5, - 63.3, - 62.3, - 61.5, - 60.4, - 59.9, - 59.5, - 58.8, - 58, - 57.1, - 54.3, - 52.6, - 52, - 50.7, - 48.6, - 48.1, - 47.6, - 46.9, - 45.9, - 45.2, - 44.5, - 44.2, - 44, - 43.6, - 43, - 42.5, - 42, - 41.5, - 41.3, - 40.9, - 40.4, - 39.8, - 39.6, - 39.3, - 39, - 38.7, - 38.4, - 37.8, - 37.3, - 36.9, - 36.5, - 35.8, - 35.2, - 34.4, - 33.6, - 32.6, - 31.7, - 30.8, - 30.1, - 29.5, - 29, - 28.6, - 28.2, - 27.9, - 27.8, - 27.6, - 27.2, - 26.9, - 26.7, - 26.4, - 26.2, - 26, - 25.7, - 25.4, - 25.1, - 24.8, - 24.5, - 24.3, - 23.9, - 23.5, - 23.1, - 22.8, - 22.5, - 22.3, - 22.1, - 21.7, - 20.5, - 19.1, - 19.2, - 19.2, - 19.2, - 19, - 18.6, - 17.9, - 17.3, - 17.1, - 16.8, - 16.5, - 15.8, - 15.1, - 14.5, - 14, - 13.5, - 12.8, - 12.3, - 11.9, - 11.8, - 12, - 11.8, - 11.6, - 11.3, - 11.1, - 10.7, - 10, - 9.1, - 8.4, - 8.1, - 7.7, - 7.4, - 6.9, - 6.2, - 5.5, - 5, - 4.8, - 4.6, - 4.4, - 4.3, - 4.3, - 3.9, - 3.5, - 3.8, - 4, - 4, - 3.9, - 3.8, - 3.8, - 4.2, - 4.5, - 4.6, - 4.6, - 4.7, - 4.8, - 5, - 5.1, - 5.6, - 5.9, - 6.2, - 6.6, - 6.8, - 6.9, - 7, - 7.1, - 7.2, - 7.1, - 6.9, - 6.8, - 6.9, - 7, - 7.1, - 7.2, - 7.3, - 7.4, - 7.6, - 7.8, - 8.5, - 8.8, - 9, - 9.2, - 9.3, - 9.4, - 9.7, - 9.8, - 10, - 10.2, - 10.2, - 10.5, - 10.7, - 10.8, - 11, - 11.2, - 11.4, - 11.6, - 11.7, - 11.9, - 12.1, - 12.3, - 12.4, - 12.6, - 12.8, - 13.1, - 13.5, - 13.5, - 13.5, - 13.5, - 13.4, - 13.4, - 13.4, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.4, - 13.5, - 13.5, - 13.5, - 13.5, - 13.5, - 13.5, - 13.5, - 13.6, - 13.7, - 13.8, - 13.8, - 13.9, - 14, - 14, - 14.1, - 14.1, - 14.2, - 14.1, - 14.2, - 14.2, - 14.1, - 14.1, - 14.1, - 13.9, - 13.8, - 13.6, - 13.5, - 13.4, - 13.3, - 13.5, - 13.5, - 13.6, - 13.7, - 13.7, - 13.9, - 13.9, - 14, - 14.2, - 14.4, - 99.3, - 96.4, - 93.7, - 90.3, - 86.4, - 82.2, - 78.7, - 76.9, - 75.5, - 74.3, - 73.7, - 72.9, - 72.4, - 71.3, - 68.2, - 64.9, - 64, - 63.2, - 62.2, - 61.6, - 60.7, - 59.9, - 59.4, - 58.7, - 57.8, - 56.9, - 54.8, - 52.4, - 51.5, - 49.5, - 48.6, - 48.1, - 47.3, - 46.6, - 45.9, - 45.4, - 44.7, - 44.1, - 43.8, - 43.4, - 42.9, - 42.6, - 42, - 41.4, - 41.2, - 40.8, - 40.4, - 39.8, - 39.6, - 39.3, - 38.9, - 38.5, - 38, - 37.6, - 37.3, - 36.9, - 36.4, - 35.8, - 35.1, - 34.5, - 33.6, - 32.7, - 31.8, - 31, - 30.1, - 29.5, - 29, - 28.6, - 28.3, - 28, - 27.8, - 27.6, - 27.2, - 27, - 26.7, - 26.4, - 26.2, - 26, - 25.7, - 25.4, - 25.2, - 24.8, - 24.5, - 24.2, - 23.9, - 23.5, - 23.1, - 22.8, - 22.6, - 22.3, - 22, - 21.7, - 20.3, - 19, - 19.2, - 19.2, - 19.1, - 19, - 18.6, - 17.9, - 17.4, - 17.1, - 16.7, - 15.9, - 15, - 14.6, - 14.2, - 13.7, - 13.4, - 12.9, - 12.4, - 12.1, - 12.3, - 12.2, - 11.9, - 11.7, - 11.4, - 11.1, - 10.6, - 9.9, - 9.2, - 8.7, - 8.3, - 7.9, - 7.6, - 7, - 6.1, - 5.4, - 4.9, - 4.7, - 4.5, - 4.2, - 4.1, - 4, - 3.6, - 3.6, - 4, - 4, - 3.9, - 3.9, - 3.8, - 3.7, - 4, - 4.4, - 4.7, - 4.7, - 4.7, - 4.8, - 4.9, - 5.1, - 5.3, - 5.5, - 5.9, - 6.3, - 6.6, - 6.7, - 6.8, - 6.8, - 6.7, - 6.6, - 6.5, - 6.5, - 6.6, - 6.9, - 7.1, - 7.2, - 7.3, - 7.4, - 7.6, - 8.2, - 8.6, - 8.8, - 8.9, - 9.1, - 9.3, - 9.5, - 9.7, - 9.9, - 10, - 10.1, - 10.3, - 10.5, - 10.7, - 10.9, - 11, - 11.2, - 11.4, - 11.6, - 11.7, - 11.9, - 12.2, - 12.4, - 12.4, - 12.6, - 12.8, - 13.1, - 13.2, - 13.3, - 13.5, - 13.4, - 13.3, - 13.3, - 13.3, - 13.2, - 13.2, - 13.2, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.4, - 13.4, - 13.3, - 13.3, - 13.4, - 13.4, - 13.4, - 13.5, - 13.5, - 13.5, - 13.4, - 13.5, - 13.6, - 13.7, - 13.8, - 13.9, - 14, - 14, - 14.1, - 14.2, - 14.2, - 14.1, - 14.2, - 14.2, - 14.1, - 14.1, - 14, - 13.9, - 13.7, - 13.6, - 13.5, - 13.5, - 13.4, - 13.4, - 13.4, - 13.5, - 13.6, - 13.6, - 13.7, - 13.8, - 13.9, - 14.1, - 14.2, - 97.7, - 94.7, - 91.9, - 88.9, - 85, - 80.9, - 77.7, - 75.7, - 74.5, - 73.7, - 72.9, - 72.3, - 71.9, - 70.9, - 68.5, - 64.8, - 63.7, - 62.9, - 62.1, - 61.6, - 60.9, - 59.9, - 59.2, - 58.4, - 57.6, - 56.6, - 53.6, - 52.1, - 51, - 49.4, - 48.8, - 48.1, - 47.3, - 46.4, - 45.8, - 45.3, - 44.7, - 44, - 43.6, - 43.3, - 42.9, - 42.5, - 42, - 41.5, - 41.1, - 40.8, - 40.4, - 39.9, - 39.5, - 39.2, - 38.8, - 38.3, - 37.8, - 37.5, - 37.2, - 36.9, - 36.5, - 35.9, - 35.4, - 34.6, - 33.7, - 32.7, - 31.7, - 30.8, - 30.1, - 29.5, - 29, - 28.6, - 28.4, - 28.1, - 27.8, - 27.6, - 27.3, - 27.1, - 26.8, - 26.5, - 26.3, - 26, - 25.7, - 25.4, - 25.2, - 24.8, - 24.5, - 24.1, - 23.8, - 23.5, - 23.2, - 22.9, - 22.8, - 22.4, - 22, - 21.7, - 20.4, - 19, - 19.1, - 19.1, - 19.1, - 19, - 18.7, - 18, - 17.4, - 17.1, - 16.3, - 15.3, - 14.6, - 14.4, - 14.1, - 13.6, - 13.3, - 13, - 12.6, - 12.6, - 12.5, - 12.3, - 12.1, - 11.8, - 11.4, - 11, - 10.5, - 10, - 9.5, - 9, - 8.6, - 8.1, - 7.6, - 6.6, - 5.6, - 5.1, - 4.7, - 4.5, - 4.3, - 4, - 3.9, - 3.8, - 3.6, - 3.6, - 3.9, - 3.9, - 3.9, - 3.8, - 3.6, - 3.8, - 4, - 4.5, - 4.7, - 4.7, - 4.8, - 5, - 5.1, - 5.1, - 5.2, - 5.3, - 5.4, - 5.7, - 5.9, - 6.1, - 6.2, - 6.3, - 6.3, - 6.3, - 6.2, - 6.4, - 6.7, - 7, - 7.3, - 7.6, - 7.6, - 7.9, - 8.2, - 8.4, - 8.6, - 8.8, - 8.9, - 9, - 9.2, - 9.4, - 9.6, - 9.7, - 9.9, - 10.1, - 10.3, - 10.5, - 10.7, - 10.9, - 11.1, - 11.3, - 11.4, - 11.6, - 11.8, - 11.9, - 12.1, - 12.3, - 12.4, - 12.7, - 13, - 13.3, - 13.4, - 13.3, - 13.3, - 13.3, - 13.3, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.2, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.4, - 13.4, - 13.5, - 13.5, - 13.4, - 13.4, - 13.4, - 13.5, - 13.5, - 13.6, - 13.7, - 13.8, - 13.9, - 14, - 14.1, - 14.2, - 14.1, - 14.1, - 14.1, - 14.1, - 14.1, - 14.1, - 14, - 13.9, - 13.7, - 13.6, - 13.5, - 13.4, - 13.4, - 13.4, - 13.3, - 13.4, - 13.5, - 13.6, - 13.6, - 13.7, - 13.8, - 13.9, - 14, - 96, - 92.9, - 89.8, - 86.1, - 82.3, - 78.8, - 76.4, - 74.8, - 73.6, - 73, - 72.5, - 72.1, - 71.5, - 70.4, - 67.6, - 64.5, - 63.7, - 62.8, - 62, - 61.5, - 60.7, - 59.8, - 59, - 58, - 57.4, - 56.5, - 54.1, - 51.8, - 50.6, - 49.6, - 48.8, - 48.1, - 47.4, - 46.4, - 45.6, - 44.9, - 44.4, - 43.9, - 43.5, - 43.2, - 42.8, - 42.3, - 41.8, - 41.5, - 41.2, - 40.9, - 40.5, - 40, - 39.5, - 39, - 38.6, - 38.2, - 37.8, - 37.5, - 37.2, - 36.8, - 36.5, - 36, - 35.5, - 34.7, - 33.6, - 32.8, - 31.7, - 30.8, - 30.1, - 29.4, - 28.9, - 28.6, - 28.4, - 28.1, - 27.9, - 27.6, - 27.4, - 27.2, - 26.8, - 26.5, - 26.3, - 26.1, - 25.7, - 25.3, - 25.2, - 24.8, - 24.4, - 24.1, - 23.8, - 23.5, - 23.3, - 23, - 22.8, - 22.5, - 22.2, - 21.8, - 20.1, - 19, - 19.1, - 19.1, - 19.1, - 19, - 18.7, - 18, - 17.4, - 16.7, - 15.6, - 15, - 14.6, - 14.3, - 13.9, - 13.5, - 13.2, - 13, - 13, - 12.9, - 12.7, - 12.4, - 12.2, - 11.9, - 11.4, - 10.9, - 10.5, - 10.1, - 9.6, - 9.1, - 8.9, - 8.2, - 7.5, - 6.7, - 5.6, - 4.9, - 4.5, - 4, - 3.7, - 3.7, - 3.7, - 3.7, - 3.6, - 3.4, - 3.7, - 3.9, - 3.8, - 3.8, - 3.6, - 3.6, - 3.9, - 4.4, - 4.7, - 4.8, - 4.9, - 5.1, - 5.3, - 5.4, - 5.5, - 5.5, - 5.4, - 5.5, - 5.6, - 5.8, - 6, - 6.1, - 6.3, - 6.4, - 6.5, - 6.8, - 7.1, - 7.3, - 7.5, - 7.8, - 8.1, - 8.2, - 8.2, - 8.4, - 8.6, - 8.7, - 8.8, - 9, - 9.2, - 9.4, - 9.6, - 9.8, - 9.9, - 10.1, - 10.4, - 10.6, - 10.8, - 11, - 11.1, - 11.3, - 11.5, - 11.6, - 11.8, - 11.9, - 12, - 12.3, - 12.4, - 12.7, - 13.2, - 13.4, - 13.4, - 13.3, - 13.3, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.3, - 13.3, - 13.4, - 13.4, - 13.5, - 13.5, - 13.4, - 13.4, - 13.5, - 13.5, - 13.6, - 13.7, - 13.8, - 13.9, - 14, - 14.1, - 14.1, - 14.1, - 14.1, - 14.1, - 14.1, - 14, - 14, - 13.9, - 13.8, - 13.6, - 13.5, - 13.5, - 13.4, - 13.4, - 13.3, - 13.3, - 13.3, - 13.4, - 13.5, - 13.6, - 13.7, - 13.7, - 13.8, - 14.1, - 94.2, - 91.1, - 87.2, - 82.6, - 78.6, - 76, - 75.2, - 74, - 72.2, - 72, - 71.8, - 71.5, - 70.7, - 68.9, - 66.2, - 64.4, - 63.6, - 62.6, - 62, - 61.4, - 60.6, - 59.7, - 58.5, - 57.9, - 57.3, - 56.3, - 53.9, - 51.7, - 50.3, - 49.4, - 48.7, - 48, - 47.4, - 46.5, - 45.4, - 44.7, - 44.3, - 43.9, - 43.6, - 43.1, - 42.6, - 42.2, - 41.8, - 41.6, - 41.3, - 40.8, - 40.4, - 40, - 39.4, - 38.8, - 38.3, - 38, - 37.7, - 37.4, - 37.2, - 36.8, - 36.5, - 36, - 35.5, - 34.7, - 33.7, - 32.9, - 31.9, - 31, - 30.1, - 29.4, - 28.9, - 28.6, - 28.4, - 28.2, - 27.9, - 27.6, - 27.4, - 27.1, - 26.8, - 26.6, - 26.3, - 26, - 25.6, - 25.2, - 24.9, - 24.6, - 24.2, - 24, - 23.7, - 23.5, - 23.3, - 23, - 22.9, - 22.7, - 22.4, - 22, - 20.4, - 19, - 19.1, - 19.1, - 19.1, - 18.9, - 18.7, - 18.1, - 17.1, - 15.9, - 15.2, - 14.8, - 14.5, - 14.2, - 13.8, - 13.5, - 13.2, - 13.3, - 13.2, - 13, - 12.8, - 12.6, - 12.3, - 11.8, - 11.4, - 11, - 10.6, - 10.2, - 9.8, - 9.3, - 8.9, - 8.3, - 7.5, - 6.8, - 5.9, - 5, - 4.3, - 3.8, - 3.6, - 3.5, - 3.5, - 3.4, - 3.4, - 3.6, - 3.6, - 3.5, - 3.7, - 3.7, - 3.4, - 3.6, - 4, - 4.4, - 4.6, - 4.7, - 4.9, - 5.1, - 5.4, - 5.5, - 5.7, - 5.7, - 5.7, - 5.7, - 5.8, - 5.9, - 6, - 6.2, - 6.5, - 6.7, - 7, - 7.2, - 7.3, - 7.5, - 7.7, - 8, - 8.1, - 8.2, - 8.3, - 8.4, - 8.6, - 8.7, - 8.9, - 9, - 9.2, - 9.4, - 9.6, - 9.8, - 10, - 10.2, - 10.4, - 10.6, - 10.7, - 11, - 11.1, - 11.3, - 11.4, - 11.5, - 11.7, - 11.9, - 12, - 12.2, - 12.3, - 12.5, - 13, - 13.4, - 13.4, - 13.4, - 13.3, - 13.2, - 13.2, - 13.1, - 13, - 13.1, - 13.1, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.2, - 13.3, - 13.4, - 13.4, - 13.4, - 13.5, - 13.4, - 13.4, - 13.4, - 13.4, - 13.5, - 13.7, - 13.7, - 13.8, - 13.9, - 14, - 14, - 14.1, - 14, - 14, - 14, - 13.9, - 13.9, - 13.8, - 13.7, - 13.6, - 13.5, - 13.4, - 13.4, - 13.4, - 13.3, - 13.2, - 13.3, - 13.4, - 13.4, - 13.5, - 13.6, - 13.7, - 13.7, - 14, - 92.6, - 88.8, - 84, - 79, - 75.7, - 75.1, - 74.5, - 72.9, - 70.6, - 70.2, - 70.7, - 70.2, - 68.9, - 67.2, - 65.2, - 64.2, - 63.2, - 62.2, - 61.6, - 61.2, - 60.6, - 59.4, - 58, - 57.6, - 57, - 55.8, - 53.6, - 51.5, - 50.5, - 49.6, - 48.7, - 47.9, - 47.4, - 46.5, - 45.4, - 44.7, - 44.4, - 43.9, - 43.4, - 42.9, - 42.4, - 42.1, - 41.8, - 41.7, - 41.3, - 40.8, - 40.3, - 39.8, - 39.2, - 38.5, - 38.1, - 37.8, - 37.7, - 37.5, - 37.2, - 36.9, - 36.5, - 35.9, - 35.5, - 34.8, - 33.9, - 32.8, - 32, - 31, - 30.1, - 29.4, - 28.9, - 28.6, - 28.4, - 28.2, - 27.9, - 27.7, - 27.4, - 27.1, - 26.8, - 26.5, - 26.2, - 25.8, - 25.5, - 25.2, - 24.9, - 24.5, - 24.1, - 23.8, - 23.7, - 23.5, - 23.4, - 23.2, - 23, - 22.6, - 22.4, - 22.2, - 20.5, - 19, - 19.1, - 19.1, - 19.1, - 18.9, - 18.7, - 18, - 16.9, - 15.7, - 15.2, - 14.7, - 14.4, - 14.1, - 13.9, - 13.6, - 13.6, - 13.5, - 13.4, - 13.2, - 13, - 12.7, - 12.4, - 11.9, - 11.5, - 11.1, - 10.6, - 10.2, - 9.8, - 9.5, - 9, - 8.4, - 7.6, - 6.7, - 5.9, - 5.1, - 4.2, - 3.6, - 3.4, - 3.4, - 3.4, - 3.2, - 3.5, - 3.6, - 3.7, - 3.5, - 3.4, - 3.4, - 3.5, - 3.7, - 4, - 4.3, - 4.4, - 4.6, - 4.8, - 5, - 5.3, - 5.5, - 5.7, - 5.9, - 6, - 6, - 6.1, - 6.2, - 6.2, - 6.5, - 6.8, - 7.1, - 7.2, - 7.3, - 7.5, - 7.7, - 7.9, - 8, - 8.1, - 8.3, - 8.4, - 8.6, - 8.7, - 8.8, - 8.9, - 9.1, - 9.3, - 9.4, - 9.6, - 9.8, - 10, - 10.2, - 10.3, - 10.5, - 10.7, - 10.8, - 11.1, - 11.2, - 11.4, - 11.5, - 11.7, - 11.9, - 12, - 12.1, - 12.3, - 12.5, - 12.9, - 13.3, - 13.3, - 13.4, - 13.3, - 13.2, - 13.1, - 13.1, - 13.1, - 13.1, - 13.1, - 13.1, - 13.1, - 13.1, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.4, - 13.5, - 13.5, - 13.6, - 13.7, - 13.8, - 13.9, - 14, - 14, - 14, - 13.9, - 13.9, - 13.8, - 13.8, - 13.7, - 13.6, - 13.5, - 13.4, - 13.4, - 13.4, - 13.3, - 13.2, - 13.2, - 13.3, - 13.3, - 13.4, - 13.4, - 13.5, - 13.6, - 13.7, - 14, - 90.9, - 86.1, - 80.8, - 76.2, - 74.9, - 74.2, - 73.8, - 71.3, - 69, - 67.8, - 68.1, - 67.8, - 67.1, - 65.8, - 64.4, - 63.7, - 62.6, - 61.8, - 61.3, - 60.9, - 60.4, - 58.9, - 58.1, - 57, - 56.4, - 55.5, - 53.7, - 51.5, - 50.7, - 49.8, - 49, - 48.2, - 47.4, - 46.4, - 45.3, - 44.8, - 44.6, - 44.1, - 43.3, - 42.8, - 42.4, - 42.1, - 41.9, - 41.7, - 41.2, - 40.7, - 40.2, - 39.6, - 39, - 38.5, - 38.2, - 38, - 37.8, - 37.6, - 37.2, - 36.8, - 36.5, - 36.1, - 35.4, - 34.7, - 34, - 32.9, - 32, - 31.1, - 30.2, - 29.5, - 29, - 28.7, - 28.4, - 28.2, - 28, - 27.7, - 27.3, - 27, - 26.7, - 26.4, - 26.1, - 25.7, - 25.4, - 25, - 24.7, - 24.3, - 24, - 23.8, - 23.7, - 23.6, - 23.4, - 23.2, - 23, - 22.6, - 22.5, - 22.4, - 20.3, - 18.9, - 19.1, - 19.1, - 19, - 18.9, - 18.6, - 18, - 16.8, - 15.7, - 15.2, - 14.7, - 14.5, - 14.1, - 14, - 13.9, - 13.9, - 13.7, - 13.5, - 13.3, - 13.1, - 12.7, - 12.4, - 12, - 11.6, - 11.2, - 10.6, - 10.3, - 9.9, - 9.6, - 9.2, - 8.5, - 7.7, - 6.8, - 6, - 5.3, - 4.3, - 3.4, - 3.3, - 3.3, - 3.2, - 3.3, - 3.5, - 3.5, - 3.6, - 3.7, - 3.5, - 3.4, - 3.8, - 3.9, - 4.1, - 4.2, - 4.3, - 4.5, - 4.8, - 5.2, - 5.5, - 5.7, - 5.9, - 6.1, - 6.2, - 6.4, - 6.4, - 6.6, - 6.7, - 6.8, - 7, - 7.1, - 7.3, - 7.4, - 7.6, - 7.8, - 8, - 8, - 8.1, - 8.3, - 8.5, - 8.6, - 8.8, - 8.8, - 9, - 9.2, - 9.3, - 9.5, - 9.6, - 9.8, - 9.9, - 10.1, - 10.3, - 10.5, - 10.7, - 10.8, - 11, - 11.2, - 11.4, - 11.5, - 11.7, - 11.8, - 12, - 12, - 12.2, - 12.5, - 12.9, - 13.2, - 13.3, - 13.3, - 13.2, - 13.1, - 13.1, - 13.1, - 13, - 13, - 13, - 13.1, - 13.1, - 13.1, - 13.1, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.4, - 13.4, - 13.4, - 13.3, - 13.4, - 13.3, - 13.3, - 13.4, - 13.4, - 13.5, - 13.6, - 13.7, - 13.8, - 13.9, - 13.9, - 13.9, - 13.8, - 13.8, - 13.7, - 13.7, - 13.6, - 13.5, - 13.4, - 13.3, - 13.3, - 13.3, - 13.3, - 13.2, - 13.1, - 13.2, - 13.3, - 13.4, - 13.4, - 13.5, - 13.7, - 13.8, - 13.9, - 89.7, - 84.8, - 78.6, - 75.5, - 74.1, - 73.2, - 72.7, - 70.1, - 66.4, - 65.5, - 65.5, - 65.7, - 65.7, - 64.7, - 63.6, - 63, - 62.1, - 61.5, - 61, - 60.6, - 60.1, - 59.1, - 58.3, - 56.6, - 56, - 55.3, - 53, - 51.4, - 50.7, - 49.9, - 49, - 48.3, - 47.5, - 46.7, - 45.5, - 44.9, - 44.5, - 44.2, - 43.4, - 42.9, - 42.6, - 42.3, - 41.9, - 41.7, - 41.3, - 40.6, - 40, - 39.3, - 38.8, - 38.5, - 38.2, - 38, - 37.9, - 37.5, - 37.2, - 36.7, - 36.3, - 35.9, - 35.2, - 34.6, - 33.9, - 33, - 32.2, - 31.1, - 30.4, - 29.7, - 29.1, - 28.8, - 28.6, - 28.3, - 28, - 27.6, - 27.3, - 26.9, - 26.6, - 26.2, - 25.8, - 25.5, - 25.2, - 24.9, - 24.6, - 24.2, - 23.9, - 23.7, - 23.7, - 23.6, - 23.3, - 23, - 22.9, - 22.8, - 22.8, - 22.5, - 20.6, - 18.9, - 19, - 19, - 19, - 18.8, - 18.6, - 17.9, - 16.6, - 15.6, - 15.1, - 14.7, - 14.4, - 14.1, - 14.1, - 14.2, - 14.1, - 13.9, - 13.7, - 13.5, - 13.2, - 13, - 12.6, - 12.1, - 11.6, - 11.2, - 10.8, - 10.4, - 10, - 9.7, - 9.3, - 8.7, - 7.8, - 7, - 6.1, - 5.3, - 4.2, - 3.4, - 3.1, - 3.2, - 3.4, - 3.5, - 3.5, - 3.5, - 3.6, - 3.7, - 3.3, - 3.5, - 3.8, - 3.8, - 3.9, - 4.1, - 4.2, - 4.6, - 5.1, - 5.4, - 5.8, - 5.9, - 6.2, - 6.3, - 6.5, - 6.7, - 6.8, - 7, - 7.1, - 7.1, - 7.2, - 7.4, - 7.5, - 7.6, - 7.7, - 7.8, - 8, - 8.1, - 8.2, - 8.4, - 8.5, - 8.6, - 8.8, - 8.9, - 9, - 9.2, - 9.4, - 9.5, - 9.6, - 9.8, - 10, - 10.2, - 10.3, - 10.5, - 10.6, - 10.8, - 11, - 11.2, - 11.3, - 11.4, - 11.6, - 11.7, - 11.9, - 12, - 12.2, - 12.4, - 12.7, - 13, - 13.3, - 13.3, - 13.2, - 13.2, - 13.1, - 13.1, - 13, - 13, - 13, - 13, - 13.1, - 13.1, - 13.1, - 13.1, - 13.1, - 13.1, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.2, - 13.2, - 13.2, - 13.3, - 13.3, - 13.4, - 13.3, - 13.3, - 13.3, - 13.3, - 13.3, - 13.4, - 13.5, - 13.6, - 13.6, - 13.7, - 13.8, - 13.8, - 13.8, - 13.7, - 13.7, - 13.6, - 13.6, - 13.5, - 13.4, - 13.3, - 13.3, - 13.2, - 13.2, - 13.1, - 13.1, - 13.1, - 13.1, - 13.2, - 13.3, - 13.4, - 13.5, - 13.6, - 13.7, - 13.9, - 87.4, - 81.7, - 76.2, - 75, - 73.3, - 72.1, - 70.6, - 68.9, - 65.7, - 64.7, - 64.6, - 64.5, - 64.3, - 63.4, - 62.9, - 62.3, - 61.7, - 61.2, - 60.7, - 60.2, - 59.8, - 59.2, - 58.2, - 56.7, - 55.8, - 55, - 53.7, - 51.8, - 50.7, - 49.7, - 48.9, - 48.2, - 47.4, - 46.7, - 45.6, - 44.9, - 44.5, - 44.2, - 43.4, - 43, - 42.7, - 42.4, - 42, - 41.7, - 41.3, - 40.6, - 39.8, - 39.2, - 38.8, - 38.5, - 38.3, - 38.1, - 37.9, - 37.6, - 37.3, - 36.8, - 36.3, - 35.7, - 35.2, - 34.7, - 33.9, - 33.1, - 32.2, - 31.3, - 30.6, - 29.9, - 29.3, - 28.9, - 28.6, - 28.3, - 27.9, - 27.6, - 27.2, - 26.8, - 26.4, - 26, - 25.8, - 25.4, - 25, - 24.7, - 24.4, - 24.1, - 23.8, - 23.7, - 23.6, - 23.4, - 23.2, - 23, - 22.9, - 22.8, - 22.8, - 22.4, - 20.4, - 18.9, - 19, - 19, - 19, - 18.8, - 18.5, - 17.9, - 16.5, - 15.5, - 15, - 14.7, - 14.4, - 14.4, - 14.4, - 14.4, - 14.3, - 14.1, - 13.9, - 13.6, - 13.5, - 13.3, - 12.8, - 12.3, - 11.7, - 11.2, - 10.8, - 10.4, - 10.1, - 9.8, - 9.4, - 8.9, - 7.9, - 7, - 6.1, - 5.1, - 4, - 3.4, - 3.1, - 3.3, - 3.4, - 3.4, - 3.4, - 3.3, - 3.5, - 3.4, - 3.3, - 3.5, - 3.7, - 3.7, - 3.8, - 3.9, - 4.2, - 4.8, - 5.3, - 5.6, - 5.9, - 6.1, - 6.4, - 6.6, - 6.8, - 7, - 7.1, - 7.2, - 7.3, - 7.4, - 7.4, - 7.6, - 7.8, - 7.8, - 7.8, - 7.9, - 8, - 8.2, - 8.3, - 8.4, - 8.6, - 8.7, - 8.9, - 9.1, - 9.2, - 9.2, - 9.4, - 9.6, - 9.7, - 9.7, - 10, - 10.2, - 10.3, - 10.4, - 10.6, - 10.8, - 11, - 11.1, - 11.3, - 11.4, - 11.5, - 11.6, - 11.8, - 11.9, - 12.1, - 12.4, - 12.6, - 13, - 13.3, - 13.3, - 13.2, - 13.2, - 13.1, - 13, - 13, - 13, - 13, - 13, - 13.1, - 13, - 13.1, - 13.1, - 13.1, - 13.1, - 13.1, - 13.1, - 13.3, - 13.3, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.3, - 13.3, - 13.3, - 13.2, - 13.3, - 13.3, - 13.4, - 13.4, - 13.6, - 13.6, - 13.7, - 13.7, - 13.7, - 13.7, - 13.6, - 13.6, - 13.5, - 13.5, - 13.4, - 13.3, - 13.2, - 13.2, - 13.1, - 13.1, - 13.1, - 13.1, - 13, - 13, - 13.1, - 13.3, - 13.3, - 13.5, - 13.6, - 13.7, - 13.8, - 83.1, - 78.1, - 75.3, - 73.9, - 72.5, - 71.3, - 70, - 68.5, - 64.9, - 64, - 63.9, - 63.6, - 63.3, - 62.9, - 62.3, - 61.8, - 61.4, - 60.9, - 60.4, - 59.8, - 59.5, - 59.1, - 57.6, - 56.3, - 55.4, - 54.9, - 54.2, - 51.8, - 50.5, - 49.6, - 48.7, - 48.1, - 47.2, - 46.6, - 45.6, - 44.9, - 44.5, - 44, - 43.4, - 43, - 42.7, - 42.4, - 42.1, - 41.8, - 41.2, - 40.6, - 39.7, - 39.3, - 38.8, - 38.6, - 38.3, - 38.2, - 38, - 37.7, - 37.2, - 36.8, - 36.3, - 35.8, - 35.1, - 34.6, - 34, - 33.2, - 32.3, - 31.4, - 30.7, - 30, - 29.4, - 29, - 28.5, - 28.2, - 27.9, - 27.6, - 27.1, - 26.8, - 26.4, - 26, - 25.6, - 25.3, - 24.9, - 24.7, - 24.3, - 24, - 23.9, - 23.7, - 23.5, - 23.4, - 23.2, - 23, - 22.9, - 22.8, - 22.6, - 22.4, - 20.4, - 18.9, - 19, - 19, - 19, - 18.8, - 18.5, - 18.1, - 16.8, - 15.5, - 15.1, - 14.8, - 14.7, - 14.7, - 14.7, - 14.6, - 14.4, - 14.2, - 14, - 14, - 13.8, - 13.3, - 12.9, - 12.5, - 11.9, - 11.4, - 10.9, - 10.5, - 10.1, - 9.7, - 9.5, - 9.1, - 8.2, - 7.1, - 6, - 5, - 3.9, - 3.3, - 3, - 3.2, - 3.3, - 3.3, - 3.3, - 3.3, - 3.5, - 3.4, - 3.2, - 3.5, - 3.7, - 3.5, - 3.7, - 3.9, - 4.2, - 4.8, - 5.3, - 5.6, - 5.9, - 6.2, - 6.4, - 6.7, - 6.9, - 7.1, - 7.2, - 7.4, - 7.5, - 7.6, - 7.7, - 7.8, - 7.9, - 7.9, - 7.9, - 8.1, - 8.1, - 8.3, - 8.5, - 8.6, - 8.7, - 8.8, - 9, - 9.1, - 9.2, - 9.3, - 9.4, - 9.6, - 9.7, - 9.7, - 9.9, - 10.1, - 10.2, - 10.4, - 10.5, - 10.7, - 10.9, - 11.1, - 11.2, - 11.4, - 11.5, - 11.6, - 11.8, - 11.9, - 12.1, - 12.3, - 12.5, - 12.9, - 13.2, - 13.3, - 13.2, - 13.2, - 13.1, - 13.1, - 13, - 13, - 13, - 13, - 13, - 13, - 13, - 13, - 13.1, - 13.1, - 13.1, - 13.1, - 13.4, - 13.3, - 13.3, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.4, - 13.5, - 13.5, - 13.6, - 13.6, - 13.5, - 13.5, - 13.5, - 13.5, - 13.4, - 13.4, - 13.3, - 13.2, - 13.1, - 13, - 13, - 12.9, - 12.9, - 12.9, - 12.9, - 13, - 13.1, - 13.2, - 13.3, - 13.4, - 13.5, - 13.6, - 13.7, - 80, - 77.1, - 75, - 73.1, - 71.6, - 70.5, - 69.5, - 67.9, - 64.9, - 63.6, - 63.2, - 62.9, - 62.6, - 62.3, - 61.8, - 61.3, - 61, - 60.7, - 60.1, - 59.5, - 59.2, - 58.8, - 57.2, - 55.9, - 55.3, - 54.7, - 54.2, - 51.8, - 49.8, - 49.1, - 48.4, - 47.8, - 47, - 46.5, - 45.9, - 45.2, - 44.4, - 43.7, - 43.3, - 43, - 42.8, - 42.4, - 42, - 41.6, - 41.1, - 40.6, - 40.1, - 39.4, - 38.8, - 38.5, - 38.4, - 38.2, - 37.9, - 37.6, - 37.1, - 36.7, - 36.2, - 35.7, - 35.1, - 34.6, - 34, - 33.3, - 32.3, - 31.5, - 30.7, - 30.1, - 29.4, - 28.9, - 28.6, - 28.2, - 27.9, - 27.5, - 27, - 26.7, - 26.3, - 25.9, - 25.5, - 25.1, - 24.9, - 24.5, - 24.2, - 24, - 23.8, - 23.7, - 23.5, - 23.3, - 23.1, - 22.9, - 22.9, - 22.8, - 22.5, - 22.3, - 20.6, - 18.9, - 19, - 19, - 18.9, - 18.8, - 18.6, - 18.2, - 17.1, - 15.7, - 15.1, - 14.9, - 14.9, - 15, - 14.9, - 14.8, - 14.6, - 14.4, - 14.3, - 14.3, - 13.8, - 13.2, - 12.9, - 12.5, - 12, - 11.4, - 11, - 10.6, - 10, - 9.9, - 9.6, - 9.3, - 8.6, - 7.5, - 6.3, - 5.1, - 4, - 3.4, - 3.2, - 3.1, - 3.3, - 3.4, - 3.4, - 3.3, - 3.3, - 3.4, - 3.1, - 3.4, - 3.5, - 3.5, - 3.6, - 3.8, - 4.3, - 4.8, - 5.1, - 5.3, - 5.8, - 6.3, - 6.5, - 6.8, - 6.9, - 7.1, - 7.3, - 7.5, - 7.6, - 7.8, - 7.9, - 7.9, - 8, - 8, - 8, - 8.1, - 8.3, - 8.4, - 8.5, - 8.7, - 8.8, - 8.9, - 9, - 9.2, - 9.3, - 9.3, - 9.4, - 9.6, - 9.7, - 9.8, - 9.9, - 10, - 10.2, - 10.4, - 10.5, - 10.7, - 10.9, - 11, - 11.1, - 11.3, - 11.4, - 11.6, - 11.8, - 11.9, - 12.1, - 12.2, - 12.4, - 12.6, - 13.1, - 13.3, - 13.1, - 13.1, - 13.1, - 13.1, - 13, - 13, - 12.9, - 12.9, - 13, - 13, - 13, - 13, - 13.1, - 13.1, - 13.1, - 13.1, - 13.2, - 13.3, - 13.3, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.1, - 13.1, - 13.2, - 13.2, - 13.3, - 13.4, - 13.5, - 13.5, - 13.6, - 13.5, - 13.5, - 13.4, - 13.4, - 13.3, - 13.3, - 13.2, - 13.1, - 13, - 12.9, - 12.8, - 12.8, - 12.8, - 12.8, - 12.8, - 12.8, - 12.8, - 13, - 13.1, - 13.1, - 13.2, - 13.3, - 13.4, - 13.6, - 78.4, - 77, - 74.7, - 72, - 70.2, - 69.4, - 68.4, - 65.9, - 63.6, - 62.9, - 62.4, - 62.1, - 61.8, - 61.5, - 61.2, - 60.8, - 60.5, - 60.2, - 59.6, - 59.2, - 58.6, - 58.2, - 57.4, - 55.9, - 55.1, - 54.6, - 53.7, - 51.4, - 49.3, - 48.7, - 48.2, - 47.6, - 47, - 46.6, - 46.1, - 45.4, - 44.5, - 43.6, - 43.1, - 43.1, - 42.7, - 42.2, - 41.8, - 41.4, - 41, - 40.5, - 40.1, - 39.7, - 38.8, - 38.5, - 38.4, - 38.2, - 37.8, - 37.6, - 37.1, - 36.7, - 36.3, - 35.8, - 35.3, - 34.7, - 34, - 33.2, - 32.3, - 31.5, - 30.8, - 30, - 29.4, - 28.9, - 28.5, - 28.1, - 27.8, - 27.5, - 27, - 26.6, - 26.2, - 25.8, - 25.4, - 25, - 24.7, - 24.3, - 24, - 23.8, - 23.8, - 23.6, - 23.4, - 23.2, - 23, - 22.9, - 23.1, - 22.9, - 22.5, - 22.2, - 20.9, - 18.8, - 18.9, - 18.9, - 18.9, - 18.7, - 18.5, - 18.2, - 17.2, - 15.9, - 15.2, - 15.1, - 15.2, - 15.2, - 15.1, - 14.9, - 14.7, - 14.7, - 14.7, - 14.4, - 13.8, - 13.3, - 12.9, - 12.5, - 11.9, - 11.4, - 11.1, - 10.7, - 10.3, - 10, - 9.6, - 9.4, - 8.9, - 8, - 6.7, - 5.6, - 4.4, - 3.6, - 3.2, - 3.1, - 3.2, - 3.4, - 3.3, - 3.4, - 3.3, - 3.3, - 3.1, - 3.3, - 3.4, - 3.6, - 3.5, - 3.8, - 4.3, - 4.7, - 4.9, - 5.2, - 5.7, - 6.1, - 6.4, - 6.8, - 7, - 7.2, - 7.5, - 7.6, - 7.8, - 8, - 8, - 8.1, - 8.1, - 8.1, - 8.2, - 8.3, - 8.4, - 8.5, - 8.7, - 8.9, - 8.9, - 9, - 9.1, - 9.2, - 9.3, - 9.4, - 9.5, - 9.6, - 9.7, - 9.7, - 9.9, - 10.1, - 10.2, - 10.3, - 10.5, - 10.7, - 10.8, - 11, - 11.1, - 11.2, - 11.4, - 11.5, - 11.7, - 11.9, - 12.1, - 12.1, - 12.2, - 12.5, - 12.9, - 13.3, - 13.2, - 13.1, - 13.1, - 13.1, - 13.1, - 13, - 13, - 12.9, - 12.9, - 12.9, - 13, - 13, - 13.1, - 13.1, - 13.1, - 13, - 13.1, - 13.2, - 13.3, - 13.1, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.4, - 13.4, - 13.5, - 13.5, - 13.4, - 13.4, - 13.3, - 13.3, - 13.2, - 13.1, - 13.1, - 13, - 12.9, - 12.8, - 12.7, - 12.7, - 12.7, - 12.7, - 12.7, - 12.8, - 12.8, - 12.8, - 12.9, - 13, - 13.1, - 13.2, - 13.3, - 13.4, - 77.5, - 75.8, - 73.5, - 71, - 68.6, - 67.6, - 66, - 64.6, - 63.3, - 62.3, - 61.5, - 61.1, - 60.9, - 60.7, - 60.5, - 60.3, - 59.9, - 59.4, - 59, - 58.7, - 58.2, - 57.8, - 57.1, - 56, - 55, - 54.2, - 53.4, - 51.2, - 49.2, - 48.5, - 48, - 47.5, - 47, - 46.6, - 46, - 45.8, - 44.6, - 43.5, - 43, - 42.7, - 42.4, - 41.9, - 41.5, - 41.3, - 40.9, - 40.5, - 40, - 39.7, - 38.9, - 38.5, - 38.4, - 38.3, - 37.9, - 37.6, - 37.2, - 36.7, - 36.3, - 35.8, - 35.4, - 34.5, - 34, - 33.3, - 32.4, - 31.5, - 30.8, - 30.1, - 29.4, - 28.9, - 28.4, - 28, - 27.7, - 27.3, - 26.9, - 26.4, - 26, - 25.7, - 25.3, - 25, - 24.6, - 24.2, - 24, - 23.8, - 23.7, - 23.6, - 23.4, - 23.1, - 22.9, - 22.8, - 22.9, - 22.8, - 22.4, - 22.2, - 20.3, - 18.8, - 18.9, - 18.9, - 18.9, - 18.7, - 18.6, - 18.3, - 17.5, - 16.2, - 15.5, - 15.5, - 15.5, - 15.4, - 15.3, - 15.1, - 14.9, - 14.7, - 14.8, - 14.2, - 13.7, - 13.2, - 12.8, - 12.4, - 12, - 11.5, - 11.2, - 10.9, - 10.5, - 10.1, - 9.7, - 9.4, - 9, - 8.3, - 7.3, - 5.9, - 4.6, - 3.8, - 3.3, - 3, - 3.2, - 3.3, - 3.2, - 3.3, - 3.3, - 3.4, - 3, - 3.2, - 3.3, - 3.5, - 3.5, - 3.7, - 4.3, - 4.6, - 4.7, - 5.2, - 5.6, - 6, - 6.4, - 6.7, - 7, - 7.2, - 7.5, - 7.8, - 8, - 8.1, - 8.1, - 8.2, - 8.3, - 8.3, - 8.4, - 8.4, - 8.5, - 8.6, - 8.8, - 9, - 9.1, - 9.2, - 9.3, - 9.4, - 9.4, - 9.5, - 9.5, - 9.6, - 9.7, - 9.8, - 10, - 10, - 10.3, - 10.4, - 10.5, - 10.6, - 10.7, - 10.8, - 10.8, - 11, - 11.2, - 11.3, - 11.5, - 11.8, - 12, - 12.1, - 12.1, - 12.3, - 12.7, - 13.2, - 13.2, - 13.2, - 13.1, - 13.1, - 13.1, - 13, - 13, - 13, - 13, - 13, - 13, - 13, - 13.1, - 13.1, - 13, - 13, - 13, - 13, - 13, - 13.1, - 13.2, - 13.3, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.3, - 13.3, - 13.3, - 13.4, - 13.4, - 13.4, - 13.4, - 13.3, - 13.3, - 13.2, - 13.2, - 13.1, - 13, - 13, - 12.9, - 12.8, - 12.7, - 12.6, - 12.6, - 12.6, - 12.7, - 12.7, - 12.7, - 12.7, - 12.7, - 12.7, - 12.9, - 13, - 13, - 13.1, - 13.3, - 76.5, - 74.6, - 72.5, - 70.2, - 67.6, - 66.4, - 65.1, - 63.8, - 62.6, - 61.7, - 60.9, - 60.4, - 60.2, - 59.9, - 59.8, - 59.8, - 59.5, - 59.1, - 58.6, - 58.3, - 57.9, - 57.4, - 56.8, - 56.1, - 54.8, - 54, - 53.3, - 51.4, - 49.4, - 48.3, - 47.8, - 47.5, - 46.9, - 46.4, - 46, - 45.7, - 44.4, - 43.4, - 42.8, - 42.5, - 42.1, - 41.7, - 41.4, - 41.3, - 41, - 40.6, - 39.9, - 39.6, - 39.1, - 38.7, - 38.6, - 38.4, - 38.1, - 37.6, - 37.3, - 36.7, - 36.2, - 35.7, - 35.2, - 34.5, - 34, - 33.3, - 32.4, - 31.5, - 30.8, - 30.2, - 29.5, - 29, - 28.4, - 28, - 27.6, - 27.1, - 26.7, - 26.3, - 25.9, - 25.5, - 25.2, - 24.8, - 24.4, - 24.1, - 24, - 23.8, - 23.6, - 23.4, - 23.2, - 23, - 22.8, - 22.7, - 22.8, - 22.6, - 22.5, - 21.8, - 19.8, - 18.8, - 18.9, - 18.9, - 18.9, - 18.7, - 18.7, - 18.4, - 17.7, - 16.5, - 15.8, - 15.8, - 15.7, - 15.6, - 15.4, - 15.2, - 15.1, - 15, - 14.7, - 14.2, - 13.7, - 13.2, - 12.8, - 12.5, - 12, - 11.6, - 11.2, - 11, - 10.5, - 10.2, - 9.9, - 9.5, - 9, - 8, - 6.8, - 5.3, - 4.3, - 3.6, - 3.3, - 3.2, - 3.2, - 3.2, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3, - 3.3, - 3.5, - 3.4, - 3.6, - 4.2, - 4.3, - 4.5, - 5, - 5.5, - 5.9, - 6.2, - 6.5, - 6.9, - 7.2, - 7.5, - 7.8, - 8.1, - 8.3, - 8.3, - 8.4, - 8.6, - 8.6, - 8.6, - 8.6, - 8.7, - 8.8, - 9, - 9.1, - 9.2, - 9.3, - 9.4, - 9.6, - 9.5, - 9.5, - 9.6, - 9.7, - 9.8, - 9.8, - 10, - 10, - 10.1, - 10.2, - 10.3, - 10.5, - 10.6, - 10.7, - 10.9, - 11, - 11.2, - 11.2, - 11.4, - 11.5, - 11.7, - 11.9, - 12.1, - 12.2, - 12.5, - 13.1, - 13.2, - 13.2, - 13.1, - 13.1, - 13, - 13, - 13, - 13, - 13, - 13, - 13, - 12.9, - 12.8, - 12.8, - 12.8, - 12.9, - 13, - 13.1, - 13.1, - 13.1, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13.1, - 13.1, - 13.3, - 13.4, - 13.4, - 13.5, - 13.5, - 13.4, - 13.3, - 13.3, - 13.3, - 13.2, - 13.1, - 13, - 12.9, - 12.9, - 12.8, - 12.7, - 12.6, - 12.6, - 12.5, - 12.5, - 12.5, - 12.5, - 12.5, - 12.6, - 12.6, - 12.6, - 12.6, - 12.8, - 12.9, - 13, - 13, - 13.1, - 75.3, - 73.5, - 71.5, - 69.3, - 66.8, - 65.4, - 64.4, - 63.1, - 62, - 61, - 60, - 59.7, - 59.5, - 59.2, - 59.3, - 59.4, - 59.1, - 58.5, - 58.2, - 58, - 57.5, - 57, - 56.4, - 55.7, - 54.4, - 53.7, - 53, - 50.9, - 49.6, - 48.5, - 47.7, - 47.3, - 46.8, - 46.5, - 46.2, - 45.6, - 44.3, - 43.5, - 42.9, - 42.4, - 42.1, - 41.6, - 41.4, - 41.2, - 41, - 40.7, - 40, - 39.8, - 39.4, - 38.8, - 38.7, - 38.5, - 38.2, - 37.5, - 37.1, - 36.6, - 36.2, - 35.7, - 35.1, - 34.6, - 33.9, - 33.1, - 32.3, - 31.6, - 30.9, - 30.2, - 29.5, - 29, - 28.4, - 28, - 27.4, - 26.9, - 26.5, - 26.1, - 25.8, - 25.4, - 24.9, - 24.6, - 24.3, - 24.1, - 23.9, - 23.7, - 23.5, - 23.3, - 23, - 22.8, - 22.6, - 22.5, - 22.3, - 22.2, - 21.8, - 20.8, - 19.8, - 18.8, - 18.9, - 18.9, - 18.8, - 18.7, - 18.7, - 18.4, - 18, - 16.8, - 16, - 16, - 15.9, - 15.7, - 15.6, - 15.4, - 15.3, - 15, - 14.6, - 14.2, - 13.6, - 13.1, - 12.7, - 12.3, - 11.9, - 11.6, - 11.3, - 10.9, - 10.5, - 10.3, - 9.9, - 9.5, - 8.7, - 7.7, - 6.4, - 5.1, - 4, - 3.3, - 3.2, - 3.2, - 3, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3, - 3.2, - 3.3, - 3.5, - 3.3, - 3.7, - 4.1, - 4.2, - 4.4, - 4.7, - 5.2, - 5.6, - 6, - 6.2, - 6.6, - 7.1, - 7.5, - 7.8, - 8.1, - 8.3, - 8.5, - 8.7, - 8.7, - 8.8, - 8.9, - 8.9, - 8.9, - 9, - 9.2, - 9.2, - 9.3, - 9.4, - 9.5, - 9.6, - 9.6, - 9.6, - 9.7, - 9.7, - 9.8, - 9.9, - 10, - 10, - 10.1, - 10.2, - 10.3, - 10.5, - 10.6, - 10.7, - 10.9, - 11, - 11.1, - 11.2, - 11.3, - 11.5, - 11.6, - 11.8, - 12, - 12.1, - 12.5, - 13, - 13.2, - 13.2, - 13.1, - 13.1, - 13.1, - 13.1, - 13, - 12.9, - 12.8, - 12.6, - 12.5, - 12.5, - 12.5, - 12.6, - 12.7, - 12.8, - 12.9, - 13, - 13.1, - 13.1, - 13.2, - 13.2, - 13.1, - 13.2, - 13.1, - 13.1, - 13.2, - 13.4, - 13.4, - 13.4, - 13.5, - 13.4, - 13.3, - 13.2, - 13.2, - 13.2, - 13.1, - 13, - 12.9, - 12.9, - 12.8, - 12.8, - 12.6, - 12.6, - 12.5, - 12.5, - 12.4, - 12.3, - 12.2, - 12.1, - 12.3, - 12.4, - 12.4, - 12.4, - 12.6, - 12.8, - 12.9, - 13, - 13.1, - 74.2, - 71.7, - 69.4, - 67.8, - 66, - 64.6, - 63.6, - 62.4, - 61.2, - 60.4, - 59.4, - 58.8, - 58.6, - 58.7, - 58.7, - 59, - 58.6, - 58.1, - 57.7, - 57.5, - 57.2, - 56.7, - 56.1, - 55.3, - 54.3, - 53.5, - 52.8, - 50.9, - 49.9, - 48.6, - 47.8, - 47.3, - 46.9, - 46.6, - 46.2, - 45.5, - 44.3, - 43.4, - 42.8, - 42.4, - 42, - 41.8, - 41.5, - 41.2, - 40.9, - 40.6, - 40.2, - 39.9, - 39.6, - 39, - 38.6, - 38.4, - 38, - 37.5, - 36.9, - 36.5, - 36.1, - 35.7, - 35.1, - 34.6, - 33.9, - 33.1, - 32.3, - 31.5, - 30.9, - 30.2, - 29.6, - 28.9, - 28.3, - 27.9, - 27.3, - 26.8, - 26.4, - 26, - 25.6, - 25.2, - 24.8, - 24.5, - 24.2, - 24, - 23.8, - 23.6, - 23.3, - 23.1, - 22.9, - 22.7, - 22.5, - 22.2, - 22.1, - 21.9, - 21.1, - 20.1, - 19.3, - 18.8, - 18.9, - 18.9, - 18.8, - 18.6, - 18.5, - 18.3, - 18, - 16.8, - 16.2, - 16.2, - 16, - 15.9, - 15.7, - 15.6, - 15.4, - 15, - 14.5, - 14, - 13.5, - 13, - 12.5, - 12.1, - 11.8, - 11.6, - 11.4, - 11, - 10.6, - 10.2, - 9.9, - 9.5, - 8.8, - 7.8, - 6.5, - 5.1, - 3.8, - 3.2, - 3.2, - 3.2, - 3, - 3.2, - 3.1, - 3.1, - 3, - 2.9, - 3.1, - 3.3, - 3.3, - 3.3, - 3.2, - 3.9, - 4, - 4.1, - 4.2, - 4.5, - 4.9, - 5.4, - 5.6, - 5.9, - 6.2, - 6.7, - 7.2, - 7.5, - 7.9, - 8.2, - 8.5, - 8.8, - 9, - 9, - 9.1, - 9.1, - 9.1, - 9.1, - 9.3, - 9.3, - 9.3, - 9.5, - 9.6, - 9.6, - 9.6, - 9.6, - 9.6, - 9.7, - 9.7, - 9.9, - 9.9, - 10, - 10.1, - 10.2, - 10.3, - 10.4, - 10.6, - 10.7, - 10.9, - 11, - 11, - 11.2, - 11.2, - 11.3, - 11.4, - 11.7, - 11.9, - 12.1, - 12.3, - 12.9, - 13.1, - 13.1, - 13.1, - 13, - 13, - 12.8, - 12.6, - 12.4, - 12.2, - 12.2, - 12.3, - 12.4, - 12.5, - 12.6, - 12.6, - 12.7, - 12.9, - 13, - 12.9, - 13.1, - 13, - 13.1, - 13.1, - 13.1, - 13, - 13, - 13.3, - 13.5, - 13.4, - 13.4, - 13.4, - 13.4, - 13.3, - 13.2, - 13.1, - 13.1, - 13, - 12.9, - 12.9, - 12.8, - 12.7, - 12.7, - 12.6, - 12.5, - 12.4, - 12.3, - 12.2, - 12.1, - 12, - 11.8, - 12.2, - 12.2, - 12.2, - 12.2, - 12.4, - 12.6, - 12.8, - 12.8, - 12.9, - 71.6, - 69.4, - 68.3, - 67.2, - 65.2, - 63.8, - 62.6, - 61.6, - 60.4, - 59.6, - 58.8, - 58.2, - 58, - 58.3, - 58.6, - 58.6, - 58.2, - 57.9, - 57.4, - 57.2, - 56.9, - 56.3, - 55.7, - 55, - 54.2, - 53.5, - 52.8, - 51.9, - 50.9, - 48.8, - 47.9, - 47.2, - 46.9, - 46.5, - 46.1, - 45.5, - 44.3, - 43.4, - 42.9, - 42.4, - 42.1, - 41.9, - 41.6, - 41.1, - 40.6, - 40.4, - 40.2, - 39.8, - 39.5, - 39, - 38.5, - 38.2, - 37.8, - 37.4, - 36.9, - 36.5, - 36, - 35.6, - 35.1, - 34.5, - 33.7, - 33, - 32.2, - 31.5, - 30.8, - 30.2, - 29.7, - 28.9, - 28.2, - 27.7, - 27.2, - 26.7, - 26.1, - 25.7, - 25.4, - 25, - 24.7, - 24.5, - 24.2, - 23.9, - 23.8, - 23.4, - 23.2, - 23, - 22.8, - 22.7, - 22.3, - 22.1, - 21.8, - 21.6, - 20.8, - 20, - 19.2, - 18.7, - 18.8, - 18.8, - 18.8, - 18.6, - 18.5, - 18.2, - 17.4, - 16.7, - 16.4, - 16.3, - 16.2, - 16, - 15.8, - 15.7, - 15.4, - 15, - 14.4, - 14, - 13.5, - 13, - 12.6, - 12.2, - 11.8, - 11.5, - 11.4, - 11, - 10.6, - 10.1, - 9.8, - 9.4, - 8.6, - 7.5, - 6.3, - 4.9, - 3.6, - 3.1, - 3.1, - 3.2, - 3, - 3.2, - 3.1, - 2.9, - 2.9, - 3.1, - 3.1, - 3.2, - 3.2, - 3.1, - 3.2, - 3.6, - 3.8, - 3.8, - 4, - 4.3, - 4.7, - 5.1, - 5.3, - 5.6, - 5.9, - 6.3, - 6.8, - 7.2, - 7.6, - 8, - 8.3, - 8.7, - 9, - 9.2, - 9.3, - 9.2, - 9.2, - 9.3, - 9.3, - 9.4, - 9.4, - 9.5, - 9.6, - 9.6, - 9.6, - 9.6, - 9.6, - 9.7, - 9.8, - 9.9, - 9.9, - 10, - 10.1, - 10.1, - 10.2, - 10.4, - 10.5, - 10.6, - 10.8, - 10.9, - 11, - 11.1, - 11.3, - 11.3, - 11.4, - 11.6, - 11.8, - 11.9, - 12.1, - 12.5, - 12.9, - 12.8, - 12.7, - 12.6, - 12.3, - 12.2, - 12.1, - 12.1, - 12.1, - 12.1, - 12.3, - 12.3, - 12.4, - 12.5, - 12.5, - 12.6, - 12.7, - 12.9, - 12.9, - 12.9, - 12.9, - 12.9, - 13, - 13, - 13, - 13, - 13.1, - 13.4, - 13.3, - 13.3, - 13.4, - 13.3, - 13.2, - 13.1, - 13, - 13, - 13, - 12.9, - 12.8, - 12.8, - 12.7, - 12.6, - 12.5, - 12.4, - 12.4, - 12.2, - 12.1, - 12, - 11.8, - 11.8, - 12.1, - 12.1, - 12, - 12.1, - 12.2, - 12.4, - 12.6, - 12.6, - 12.7, - 69.4, - 68.3, - 67.4, - 66.2, - 64.6, - 63, - 61.7, - 60.7, - 59.7, - 59, - 58.3, - 57.8, - 57.4, - 58.1, - 58.5, - 58.2, - 57.9, - 57.4, - 57.1, - 56.8, - 56.7, - 56.3, - 55.5, - 54.6, - 53.9, - 53, - 52.5, - 51.7, - 50.9, - 49.6, - 48.3, - 47.4, - 47.1, - 46.6, - 46, - 45.5, - 44.5, - 43.6, - 43, - 42.4, - 42.1, - 41.8, - 41.5, - 40.9, - 40.4, - 40.1, - 40, - 39.8, - 39.4, - 39, - 38.4, - 37.9, - 37.6, - 37.2, - 36.8, - 36.3, - 35.9, - 35.4, - 34.9, - 34.2, - 33.6, - 32.9, - 32.2, - 31.5, - 30.9, - 30.2, - 29.7, - 29, - 28.2, - 27.5, - 27.1, - 26.5, - 26, - 25.6, - 25.3, - 25, - 24.7, - 24.4, - 24.1, - 23.8, - 23.6, - 23.3, - 23, - 22.8, - 22.5, - 22.3, - 22, - 21.8, - 21.6, - 21.3, - 20.8, - 20, - 19.2, - 18.7, - 18.8, - 18.8, - 18.8, - 18.7, - 18.5, - 17.8, - 17.1, - 16.7, - 16.6, - 16.4, - 16.3, - 16.1, - 15.9, - 15.6, - 15.3, - 14.9, - 14.4, - 14, - 13.6, - 13.1, - 12.7, - 12.2, - 11.8, - 11.5, - 11.2, - 10.9, - 10.5, - 10.1, - 9.8, - 9.2, - 8.2, - 7.1, - 5.8, - 4.4, - 3.4, - 3.1, - 3.1, - 3, - 3.1, - 3.2, - 3.1, - 2.8, - 3, - 3.4, - 3.4, - 3.1, - 3.1, - 3, - 3.1, - 3.4, - 3.5, - 3.6, - 3.7, - 3.9, - 4.4, - 4.8, - 5, - 5.3, - 5.6, - 6, - 6.4, - 6.9, - 7.2, - 7.7, - 8.1, - 8.5, - 8.9, - 9.2, - 9.4, - 9.4, - 9.3, - 9.4, - 9.5, - 9.5, - 9.5, - 9.6, - 9.7, - 9.7, - 9.7, - 9.8, - 9.8, - 9.8, - 9.8, - 9.9, - 10, - 10, - 10, - 10.1, - 10.2, - 10.3, - 10.4, - 10.5, - 10.7, - 10.8, - 11, - 11.1, - 11.2, - 11.2, - 11.3, - 11.5, - 11.7, - 11.9, - 12, - 12.2, - 12.4, - 12.3, - 12.3, - 12.2, - 12.1, - 12, - 12, - 12, - 12, - 12.1, - 12.2, - 12.2, - 12.3, - 12.3, - 12.3, - 12.4, - 12.6, - 12.7, - 12.7, - 12.7, - 12.8, - 12.8, - 12.8, - 13, - 13, - 13, - 13.1, - 13.3, - 13.3, - 13.3, - 13.2, - 13.2, - 13.1, - 13.1, - 13, - 12.9, - 12.9, - 12.9, - 12.7, - 12.7, - 12.6, - 12.5, - 12.4, - 12.4, - 12.3, - 12.2, - 12, - 11.9, - 11.7, - 11.8, - 11.9, - 11.9, - 11.9, - 12, - 12.1, - 12.3, - 12.5, - 12.5, - 12.5, - 68, - 67.3, - 66.1, - 65.1, - 63.9, - 62.3, - 61, - 60, - 59.1, - 58.5, - 57.7, - 57.2, - 57.3, - 58.3, - 58.2, - 57.8, - 57.5, - 57.2, - 56.8, - 56.5, - 56.2, - 56, - 55.3, - 54.3, - 53.6, - 52.8, - 52.1, - 51.4, - 50.5, - 49.4, - 48.3, - 47.6, - 47.2, - 46.6, - 46.2, - 45.4, - 44.7, - 43.6, - 42.7, - 42.3, - 41.8, - 41.4, - 41.1, - 40.7, - 40.3, - 40, - 39.9, - 39.7, - 39.3, - 38.9, - 38.3, - 38.1, - 37.5, - 37.1, - 36.6, - 36.2, - 35.8, - 35.2, - 34.7, - 34.1, - 33.5, - 32.8, - 32.1, - 31.5, - 30.9, - 30.2, - 29.5, - 28.9, - 28.1, - 27.4, - 27, - 26.5, - 25.9, - 25.5, - 25.1, - 24.8, - 24.5, - 24.2, - 23.9, - 23.6, - 23.3, - 23.1, - 22.8, - 22.5, - 22.2, - 22.1, - 21.8, - 21.5, - 21.3, - 21, - 20.9, - 20.2, - 19.1, - 18.7, - 18.8, - 18.8, - 18.7, - 18.6, - 18.2, - 17.7, - 17.2, - 16.9, - 16.7, - 16.5, - 16.4, - 16.2, - 16, - 15.5, - 15, - 14.8, - 14.5, - 14, - 13.5, - 13.1, - 12.6, - 12.2, - 11.8, - 11.5, - 11.2, - 10.9, - 10.4, - 10, - 9.6, - 8.8, - 7.8, - 6.8, - 5.7, - 4.6, - 3.5, - 3.1, - 3, - 3, - 3.1, - 3.1, - 3, - 2.9, - 3.1, - 3.1, - 3.4, - 3.1, - 2.9, - 2.9, - 3.1, - 3.3, - 3.3, - 3.5, - 3.5, - 3.6, - 4.1, - 4.5, - 4.7, - 4.8, - 5.2, - 5.5, - 6.1, - 6.4, - 6.9, - 7.5, - 7.8, - 8.2, - 8.7, - 9.2, - 9.5, - 9.5, - 9.5, - 9.5, - 9.5, - 9.6, - 9.6, - 9.7, - 9.8, - 9.7, - 9.8, - 9.8, - 9.8, - 9.9, - 9.9, - 9.9, - 9.9, - 9.9, - 10, - 10, - 10.1, - 10.3, - 10.4, - 10.5, - 10.6, - 10.8, - 10.8, - 10.9, - 11, - 11.1, - 11.2, - 11.4, - 11.7, - 11.8, - 12, - 12, - 12.1, - 12.1, - 12.2, - 12.1, - 12, - 12, - 12, - 12, - 12, - 12, - 12.1, - 12.1, - 12.1, - 12.2, - 12.2, - 12.2, - 12.3, - 12.5, - 12.5, - 12.6, - 12.7, - 12.6, - 12.7, - 12.8, - 12.9, - 12.9, - 13, - 13.1, - 13.1, - 13.1, - 13.1, - 13.1, - 13, - 13, - 12.9, - 12.8, - 12.8, - 12.7, - 12.6, - 12.5, - 12.5, - 12.4, - 12.3, - 12.3, - 12.2, - 12, - 11.9, - 11.8, - 11.7, - 11.7, - 11.8, - 11.8, - 11.8, - 11.9, - 12, - 12.1, - 12.2, - 12.2, - 12.3, - 67.3, - 66.4, - 65.2, - 64.2, - 63.3, - 61.6, - 59.9, - 59.1, - 58.5, - 58, - 57.3, - 56.9, - 57.4, - 57.9, - 57.8, - 57.5, - 57.2, - 57, - 56.5, - 56.3, - 55.8, - 55.6, - 54.9, - 54.1, - 53.4, - 52.6, - 52, - 51.3, - 50.3, - 49.2, - 48.5, - 47.8, - 47.1, - 46.7, - 45.9, - 45.3, - 44.4, - 43.1, - 42.5, - 42, - 41.4, - 41, - 40.8, - 40.5, - 40.1, - 39.9, - 39.8, - 39.5, - 39.2, - 38.9, - 38.4, - 37.9, - 37.4, - 36.9, - 36.5, - 36.1, - 35.7, - 35.1, - 34.6, - 34.1, - 33.5, - 32.7, - 32.1, - 31.5, - 30.9, - 30.2, - 29.5, - 28.9, - 28.2, - 27.5, - 27, - 26.3, - 25.8, - 25.4, - 25.1, - 24.7, - 24.3, - 24, - 23.7, - 23.4, - 23.1, - 22.9, - 22.6, - 22.4, - 22.2, - 21.9, - 21.6, - 21.3, - 21.1, - 20.9, - 20.6, - 20.1, - 19.1, - 18.7, - 18.8, - 18.8, - 18.7, - 18.5, - 18.1, - 17.6, - 17.2, - 17, - 16.8, - 16.6, - 16.4, - 16.2, - 16, - 15.5, - 15.1, - 14.9, - 14.4, - 14.1, - 13.6, - 13.2, - 12.7, - 12.2, - 11.8, - 11.6, - 11.2, - 10.8, - 10.5, - 10.1, - 9.3, - 8.4, - 7.5, - 6.7, - 5.7, - 4.6, - 3.6, - 3.2, - 3, - 2.8, - 2.9, - 2.9, - 2.8, - 3, - 2.9, - 2.9, - 3, - 3, - 2.7, - 2.8, - 3.2, - 3.2, - 3.2, - 3.2, - 3.3, - 3.6, - 4, - 4.2, - 4.3, - 4.6, - 4.8, - 5.1, - 5.7, - 6, - 6.5, - 7, - 7.5, - 8, - 8.4, - 9, - 9.5, - 9.6, - 9.6, - 9.6, - 9.5, - 9.6, - 9.6, - 9.7, - 9.8, - 9.7, - 9.7, - 9.7, - 9.8, - 9.8, - 9.8, - 9.9, - 9.9, - 9.9, - 10, - 10, - 10.1, - 10.2, - 10.3, - 10.4, - 10.6, - 10.7, - 10.8, - 10.9, - 10.9, - 11, - 11.2, - 11.4, - 11.6, - 11.7, - 11.5, - 11.5, - 11.8, - 12, - 12, - 12, - 11.9, - 11.9, - 11.9, - 11.9, - 12, - 12, - 12, - 12, - 11.9, - 12, - 12.1, - 12.2, - 12.2, - 12.3, - 12.3, - 12.4, - 12.5, - 12.6, - 12.6, - 12.7, - 12.8, - 12.9, - 12.9, - 13, - 13, - 13, - 12.9, - 12.9, - 12.9, - 12.9, - 12.8, - 12.7, - 12.6, - 12.5, - 12.6, - 12.4, - 12.3, - 12.3, - 12.2, - 12.1, - 12, - 11.9, - 11.8, - 11.7, - 11.7, - 11.6, - 11.7, - 11.8, - 11.7, - 11.8, - 11.9, - 12, - 12, - 12, - 12.1, - 66.4, - 65.4, - 64.3, - 63.1, - 62.4, - 60.5, - 59, - 58.4, - 57.9, - 57.4, - 56.9, - 56.5, - 56.9, - 57.2, - 57.2, - 57.2, - 57.1, - 56.7, - 56, - 55.5, - 55.2, - 55, - 54.5, - 53.6, - 52.8, - 52.3, - 51.7, - 51, - 50, - 49, - 48.3, - 47.7, - 47.1, - 46.6, - 45.8, - 44.9, - 43.8, - 43, - 42.4, - 41.8, - 41.3, - 41.1, - 40.9, - 40.6, - 40.1, - 39.8, - 39.8, - 39.4, - 39.1, - 38.7, - 38.3, - 37.8, - 37.5, - 36.9, - 36.4, - 35.9, - 35.5, - 35.1, - 34.6, - 34.1, - 33.4, - 32.7, - 32.2, - 31.5, - 30.8, - 30.2, - 29.5, - 28.8, - 28.2, - 27.5, - 26.8, - 26.2, - 25.7, - 25.3, - 24.8, - 24.5, - 24.2, - 23.8, - 23.5, - 23.1, - 22.9, - 22.7, - 22.4, - 22.2, - 21.9, - 21.7, - 21.4, - 21.2, - 20.9, - 20.6, - 20.3, - 20, - 19.1, - 18.7, - 18.8, - 18.7, - 18.7, - 18.4, - 18, - 17.4, - 17.2, - 17, - 16.9, - 16.6, - 16.3, - 16, - 15.8, - 15.4, - 15, - 14.5, - 14.2, - 14, - 13.7, - 13.2, - 12.7, - 12.1, - 11.8, - 11.7, - 11.3, - 10.8, - 10.5, - 10.2, - 9.6, - 8.7, - 7.7, - 6.9, - 6, - 5, - 4.2, - 3.5, - 3.1, - 3, - 2.7, - 2.7, - 2.8, - 2.9, - 2.7, - 2.7, - 2.9, - 2.6, - 2.6, - 2.6, - 2.8, - 2.9, - 3.1, - 3, - 3.2, - 3.3, - 3.6, - 3.9, - 4, - 4.3, - 4.5, - 4.7, - 5.2, - 5.6, - 5.9, - 6.3, - 6.9, - 7.5, - 8, - 8.7, - 9.3, - 9.5, - 9.6, - 9.6, - 9.6, - 9.6, - 9.6, - 9.7, - 9.8, - 9.7, - 9.6, - 9.7, - 9.8, - 9.7, - 9.8, - 9.9, - 10, - 10, - 9.9, - 10, - 10.1, - 10.2, - 10.3, - 10.4, - 10.5, - 10.7, - 10.7, - 10.9, - 11.1, - 11.1, - 11.1, - 11.2, - 11.4, - 11.4, - 11.4, - 11.5, - 11.9, - 12, - 12, - 11.9, - 11.9, - 11.8, - 11.9, - 11.9, - 12, - 12, - 11.9, - 11.9, - 11.8, - 11.9, - 11.9, - 12, - 12, - 12.1, - 12.1, - 12.2, - 12.4, - 12.4, - 12.6, - 12.7, - 12.8, - 12.9, - 12.9, - 13, - 12.9, - 13, - 12.9, - 12.8, - 12.9, - 12.8, - 12.6, - 12.7, - 12.6, - 12.5, - 12.5, - 12.4, - 12.3, - 12.1, - 12, - 11.9, - 11.9, - 11.8, - 11.8, - 11.6, - 11.5, - 11.5, - 11.5, - 11.6, - 11.6, - 11.6, - 11.7, - 11.8, - 11.9, - 12.1, - 12.1, - 65.7, - 64.5, - 63.4, - 62.3, - 61, - 59.6, - 58.7, - 58, - 57.4, - 56.9, - 56.4, - 56.1, - 56.3, - 56.5, - 56.7, - 56.8, - 56.7, - 56.4, - 55.5, - 55.2, - 54.6, - 54.3, - 53.8, - 53.1, - 52.5, - 52, - 51.4, - 50.7, - 49.5, - 48.8, - 48.1, - 47.6, - 47.2, - 46.3, - 45.5, - 44.7, - 43.6, - 43, - 42.3, - 41.7, - 41.4, - 41, - 40.8, - 40.6, - 40.2, - 39.9, - 39.6, - 39.1, - 39, - 38.6, - 38.1, - 37.8, - 37.4, - 36.8, - 36.3, - 35.8, - 35.4, - 35, - 34.5, - 33.9, - 33.2, - 32.6, - 32.1, - 31.4, - 30.7, - 30.1, - 29.5, - 28.8, - 28.1, - 27.4, - 26.7, - 26.1, - 25.6, - 25.1, - 24.7, - 24.4, - 24, - 23.7, - 23.4, - 23, - 22.7, - 22.5, - 22.2, - 21.9, - 21.7, - 21.5, - 21.2, - 21, - 20.7, - 20.4, - 20.1, - 19.8, - 19.1, - 18.6, - 18.7, - 18.7, - 18.7, - 18.3, - 17.9, - 17.5, - 17.3, - 17.1, - 16.9, - 16.7, - 16.2, - 15.8, - 15.6, - 15.3, - 14.7, - 14.1, - 13.9, - 13.5, - 13.2, - 12.7, - 12.4, - 12.1, - 11.8, - 11.7, - 11.3, - 10.9, - 10.6, - 10.3, - 9.7, - 8.9, - 8.1, - 7.2, - 6.3, - 5.5, - 4.8, - 3.8, - 3.3, - 3.1, - 3.1, - 2.9, - 2.7, - 2.9, - 2.8, - 2.6, - 2.5, - 2.4, - 2.6, - 2.6, - 2.5, - 2.6, - 2.8, - 2.9, - 3.1, - 3.1, - 3.3, - 3.3, - 3.5, - 3.7, - 4.2, - 4.5, - 4.8, - 5.3, - 5.5, - 5.8, - 6.4, - 7.2, - 7.7, - 8.5, - 9, - 9.4, - 9.6, - 9.6, - 9.5, - 9.6, - 9.6, - 9.7, - 9.8, - 9.7, - 9.7, - 9.8, - 9.8, - 9.7, - 9.8, - 9.9, - 9.9, - 9.9, - 9.9, - 10, - 10, - 10.2, - 10.2, - 10.4, - 10.5, - 10.6, - 10.7, - 10.8, - 11, - 11, - 11, - 11.1, - 11.3, - 11.4, - 11.6, - 11.7, - 11.8, - 11.9, - 11.9, - 11.9, - 11.9, - 11.9, - 11.9, - 12, - 11.9, - 11.9, - 11.8, - 11.7, - 11.7, - 11.7, - 11.8, - 11.9, - 11.9, - 12, - 12.1, - 12.2, - 12.2, - 12.3, - 12.5, - 12.7, - 12.8, - 12.8, - 12.8, - 12.9, - 12.9, - 13, - 12.9, - 12.8, - 12.8, - 12.7, - 12.6, - 12.6, - 12.6, - 12.5, - 12.4, - 12.3, - 12.2, - 12.1, - 11.9, - 11.8, - 11.7, - 11.6, - 11.6, - 11.4, - 11.4, - 11.5, - 11.5, - 11.4, - 11.5, - 11.5, - 11.6, - 11.7, - 11.8, - 12, - 12.1, - 64.9, - 63.7, - 62.6, - 61.5, - 60.3, - 59.2, - 58.2, - 57.6, - 57.1, - 56.5, - 56.1, - 55.9, - 56.1, - 56.2, - 56.2, - 56.2, - 56.1, - 55.9, - 55.3, - 54.9, - 54.3, - 53.9, - 53.4, - 52.9, - 52.5, - 51.8, - 51, - 50.2, - 49.5, - 49, - 48.1, - 47.6, - 46.9, - 46.1, - 45.4, - 44.7, - 43.4, - 42.9, - 42.2, - 41.8, - 41.4, - 40.9, - 40.7, - 40.6, - 40.3, - 39.9, - 39.5, - 39.1, - 38.9, - 38.5, - 38.2, - 37.8, - 37.3, - 36.8, - 36.3, - 35.7, - 35.4, - 34.9, - 34.4, - 33.8, - 33.2, - 32.5, - 31.9, - 31.2, - 30.5, - 30, - 29.3, - 28.7, - 28.1, - 27.3, - 26.5, - 26, - 25.4, - 25, - 24.6, - 24.2, - 23.9, - 23.6, - 23.2, - 22.9, - 22.6, - 22.3, - 22, - 21.8, - 21.6, - 21.4, - 21, - 20.8, - 20.5, - 20.2, - 19.9, - 19.6, - 18.9, - 18.6, - 18.7, - 18.7, - 18.6, - 18.3, - 17.9, - 17.5, - 17.3, - 17.2, - 17, - 16.7, - 16.1, - 15.6, - 15.3, - 15.1, - 14.6, - 14, - 13.5, - 12.9, - 12.5, - 12, - 11.7, - 11.6, - 11.4, - 11.3, - 11.1, - 10.8, - 10.5, - 10.1, - 9.7, - 9.1, - 8.4, - 7.5, - 6.6, - 6, - 5.5, - 4.6, - 3.9, - 3.4, - 3.1, - 2.9, - 2.7, - 2.9, - 2.9, - 2.6, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.7, - 2.9, - 2.9, - 2.9, - 3, - 3.2, - 3.3, - 3.9, - 4.3, - 4.6, - 4.9, - 5.2, - 5.5, - 6, - 6.7, - 7.4, - 8.3, - 9, - 9.3, - 9.5, - 9.6, - 9.5, - 9.6, - 9.6, - 9.7, - 9.8, - 9.8, - 9.8, - 9.8, - 9.7, - 9.7, - 9.9, - 9.9, - 9.9, - 9.9, - 10, - 9.9, - 10, - 10.1, - 10.2, - 10.4, - 10.4, - 10.5, - 10.6, - 10.8, - 10.8, - 10.9, - 10.9, - 11, - 11.2, - 11.4, - 11.6, - 11.8, - 11.8, - 11.8, - 11.9, - 11.9, - 11.9, - 11.9, - 11.9, - 11.9, - 11.8, - 11.7, - 11.6, - 11.5, - 11.5, - 11.6, - 11.7, - 11.8, - 11.8, - 11.9, - 12.1, - 12.2, - 12.3, - 12.3, - 12.5, - 12.6, - 12.7, - 12.8, - 12.9, - 12.8, - 12.8, - 12.8, - 12.8, - 12.8, - 12.7, - 12.6, - 12.6, - 12.5, - 12.5, - 12.3, - 12.4, - 12.3, - 12.2, - 12, - 11.9, - 11.8, - 11.6, - 11.3, - 11.3, - 11.3, - 11.3, - 11.4, - 11.4, - 11.2, - 11.3, - 11.4, - 11.5, - 11.6, - 11.7, - 11.8, - 12, - 64, - 62.7, - 61.7, - 60.5, - 59.6, - 58.6, - 57.8, - 57.2, - 56.7, - 56.2, - 55.7, - 55.5, - 55.6, - 55.8, - 56, - 55.7, - 55.4, - 55, - 54.8, - 54.5, - 54, - 53.6, - 53, - 52.6, - 52.2, - 51.2, - 50.3, - 49.9, - 49.5, - 48.9, - 48.2, - 47.3, - 46.6, - 45.9, - 45.3, - 44.7, - 43.5, - 42.9, - 42.2, - 41.9, - 41.4, - 41, - 40.6, - 40.5, - 40.2, - 39.8, - 39.4, - 39.2, - 39, - 38.5, - 38.2, - 37.8, - 37.2, - 36.7, - 36.2, - 35.7, - 35.3, - 34.8, - 34.2, - 33.7, - 33.1, - 32.4, - 31.7, - 31, - 30.4, - 29.9, - 29.2, - 28.6, - 27.9, - 27.1, - 26.5, - 25.9, - 25.4, - 25, - 24.5, - 24.1, - 23.8, - 23.5, - 23.1, - 22.8, - 22.5, - 22.2, - 21.9, - 21.6, - 21.3, - 21.1, - 20.8, - 20.6, - 20.3, - 19.9, - 19.7, - 19.5, - 18.9, - 18.5, - 18.7, - 18.7, - 18.6, - 18.4, - 17.9, - 17.5, - 17.4, - 17.2, - 17, - 16.5, - 15.9, - 15.4, - 15.2, - 14.7, - 14.2, - 13.5, - 12.9, - 12.2, - 11.7, - 11.4, - 11.1, - 10.7, - 10.6, - 10.6, - 10.5, - 10.5, - 10.3, - 10, - 9.6, - 9.2, - 8.6, - 7.9, - 7.2, - 6.7, - 6.3, - 5.6, - 4.6, - 3.9, - 3.3, - 3, - 2.8, - 2.8, - 2.8, - 2.7, - 2.5, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.5, - 2.5, - 2.8, - 2.9, - 2.8, - 2.9, - 3, - 3.2, - 3.5, - 3.8, - 4.2, - 4.6, - 4.8, - 5.2, - 5.7, - 6.3, - 7.1, - 8, - 8.8, - 9.3, - 9.5, - 9.6, - 9.6, - 9.7, - 9.6, - 9.7, - 9.8, - 9.7, - 9.8, - 9.8, - 9.7, - 9.7, - 9.8, - 9.8, - 9.8, - 9.8, - 9.9, - 10, - 10, - 10.1, - 10.1, - 10.3, - 10.4, - 10.5, - 10.5, - 10.7, - 10.7, - 10.9, - 11, - 11.1, - 11.3, - 11.5, - 11.6, - 11.7, - 11.8, - 11.8, - 11.9, - 11.9, - 11.9, - 11.9, - 11.8, - 11.8, - 11.6, - 11.6, - 11.5, - 11.5, - 11.5, - 11.5, - 11.6, - 11.7, - 11.9, - 11.9, - 12.1, - 12.2, - 12.3, - 12.4, - 12.5, - 12.6, - 12.6, - 12.7, - 12.8, - 12.8, - 12.8, - 12.7, - 12.8, - 12.8, - 12.6, - 12.6, - 12.7, - 12.5, - 12.4, - 12.3, - 12.3, - 12.2, - 12, - 11.9, - 11.8, - 11.6, - 11.5, - 11.2, - 11.2, - 11.2, - 11.2, - 11.2, - 11.2, - 11.2, - 11.2, - 11.3, - 11.4, - 11.5, - 11.6, - 11.7, - 11.9, - 62.7, - 61.7, - 60.6, - 59.5, - 58.7, - 57.9, - 57.4, - 56.9, - 56.4, - 55.9, - 55.5, - 55.2, - 55, - 55.3, - 55.5, - 55.2, - 54.8, - 54.5, - 54.3, - 53.9, - 53.5, - 53.2, - 52.6, - 52.1, - 51.6, - 50.7, - 49.9, - 49.4, - 49.1, - 48.5, - 47.8, - 46.9, - 46.2, - 45.6, - 45, - 44.4, - 43.4, - 42.8, - 42.4, - 41.8, - 41.3, - 41, - 40.6, - 40.4, - 40.2, - 39.8, - 39.4, - 39.2, - 38.9, - 38.4, - 38, - 37.7, - 37.1, - 36.7, - 36.2, - 35.6, - 35.1, - 34.7, - 34.2, - 33.6, - 32.9, - 32.2, - 31.5, - 30.9, - 30.4, - 29.8, - 29.3, - 28.6, - 27.8, - 27.1, - 26.4, - 26, - 25.4, - 24.9, - 24.6, - 24.2, - 23.9, - 23.4, - 23.1, - 22.8, - 22.4, - 22.1, - 21.8, - 21.4, - 21.2, - 20.9, - 20.7, - 20.4, - 20.1, - 19.8, - 19.6, - 19.2, - 18.8, - 18.5, - 18.7, - 18.7, - 18.6, - 18.4, - 17.9, - 17.5, - 17.4, - 17.3, - 17, - 16.5, - 16, - 15.5, - 15.1, - 14.6, - 13.9, - 13.2, - 12.5, - 11.8, - 11.1, - 10.5, - 10.2, - 9.8, - 9.8, - 9.7, - 9.6, - 10.1, - 10, - 9.9, - 9.4, - 9.2, - 8.7, - 8.3, - 7.7, - 7.3, - 6.9, - 6.4, - 5.6, - 4.9, - 3.9, - 3.4, - 3.2, - 2.7, - 2.8, - 2.8, - 2.7, - 2.7, - 2.6, - 2.5, - 2.6, - 2.5, - 2.5, - 2.7, - 3.1, - 3, - 2.9, - 2.9, - 2.9, - 3.1, - 3.3, - 3.5, - 3.8, - 4.3, - 4.5, - 4.8, - 5.4, - 6.1, - 6.9, - 7.6, - 8.4, - 9.1, - 9.4, - 9.6, - 9.5, - 9.6, - 9.6, - 9.7, - 9.7, - 9.7, - 9.7, - 9.7, - 9.6, - 9.7, - 9.7, - 9.7, - 9.7, - 9.7, - 9.8, - 9.9, - 9.9, - 10, - 10.1, - 10.2, - 10.2, - 10.3, - 10.4, - 10.6, - 10.7, - 10.8, - 10.9, - 11, - 11.2, - 11.4, - 11.5, - 11.7, - 11.7, - 11.8, - 11.8, - 11.8, - 11.8, - 11.8, - 11.7, - 11.6, - 11.5, - 11.4, - 11.4, - 11.4, - 11.4, - 11.4, - 11.5, - 11.8, - 11.8, - 11.9, - 12.1, - 12.2, - 12.3, - 12.5, - 12.5, - 12.6, - 12.6, - 12.7, - 12.7, - 12.8, - 12.7, - 12.7, - 12.6, - 12.6, - 12.5, - 12.3, - 12.3, - 12.1, - 12.1, - 12.2, - 12.2, - 12, - 11.9, - 11.8, - 11.7, - 11.5, - 11.3, - 11.1, - 11, - 11, - 11, - 11, - 11.1, - 11.2, - 11.2, - 11.3, - 11.3, - 11.4, - 11.5, - 11.7, - 11.8, - 61.4, - 60.4, - 59.5, - 58.8, - 58.1, - 57.5, - 57.1, - 56.6, - 56.1, - 55.6, - 55.3, - 54.8, - 54.4, - 54.7, - 55.1, - 54.7, - 54.2, - 53.8, - 53.4, - 53.1, - 52.9, - 52.7, - 52.2, - 51.8, - 51.2, - 50.5, - 49.5, - 48.9, - 48.6, - 48.2, - 47.5, - 46.7, - 46, - 45.2, - 44.6, - 44.1, - 43.5, - 42.9, - 42.3, - 41.7, - 41.2, - 40.8, - 40.5, - 40.4, - 40.1, - 39.9, - 39.4, - 39, - 38.7, - 38.4, - 37.9, - 37.5, - 37, - 36.6, - 36, - 35.5, - 35.1, - 34.6, - 34, - 33.4, - 32.8, - 32.1, - 31.4, - 30.8, - 30.4, - 29.8, - 29.2, - 28.5, - 27.7, - 27, - 26.3, - 25.8, - 25.2, - 24.9, - 24.5, - 24.2, - 23.9, - 23.4, - 23, - 22.7, - 22.4, - 22, - 21.7, - 21.4, - 21.1, - 20.8, - 20.5, - 20.3, - 20, - 19.7, - 19.4, - 19, - 18.6, - 18.5, - 18.6, - 18.6, - 18.6, - 18.4, - 17.9, - 17.6, - 17.5, - 17.4, - 17.1, - 16.6, - 16.1, - 15.5, - 15.1, - 14.5, - 13.8, - 13.1, - 12.5, - 11.6, - 10.9, - 10.3, - 9.7, - 9.2, - 8.9, - 8.7, - 8.7, - 9.3, - 9.7, - 9.8, - 9.4, - 9.2, - 8.8, - 8.5, - 8.1, - 7.7, - 7.3, - 6.8, - 6.2, - 5.7, - 5, - 4.3, - 3.7, - 3, - 2.7, - 2.6, - 2.8, - 2.8, - 2.7, - 2.7, - 2.9, - 2.6, - 2.3, - 2.6, - 2.9, - 3, - 3.1, - 3.1, - 3, - 3.1, - 3.2, - 3.3, - 3.5, - 3.9, - 4.1, - 4.6, - 5, - 5.8, - 6.5, - 7.2, - 8, - 8.7, - 8.9, - 9.2, - 9.4, - 9.5, - 9.4, - 9.4, - 9.4, - 9.5, - 9.6, - 9.6, - 9.5, - 9.6, - 9.5, - 9.6, - 9.6, - 9.7, - 9.8, - 9.9, - 9.9, - 9.9, - 10, - 10.1, - 10.1, - 10.3, - 10.5, - 10.5, - 10.5, - 10.5, - 10.6, - 10.8, - 11.2, - 11.3, - 11.4, - 11.6, - 11.6, - 11.7, - 11.7, - 11.7, - 11.7, - 11.7, - 11.5, - 11.3, - 11.3, - 11.3, - 11.3, - 11.3, - 11.3, - 11.3, - 11.5, - 11.7, - 11.8, - 11.9, - 12.1, - 12.2, - 12.3, - 12.4, - 12.4, - 12.4, - 12.4, - 12.5, - 12.5, - 12.5, - 12.4, - 12.4, - 12.4, - 12.3, - 12.2, - 12.1, - 12, - 12, - 11.9, - 11.9, - 11.9, - 11.7, - 11.6, - 11.5, - 11.4, - 11.3, - 11.2, - 10.9, - 10.7, - 10.7, - 10.8, - 10.9, - 11, - 11.2, - 11.2, - 11.2, - 11.4, - 11.4, - 11.5, - 11.6, - 11.7, - 60.3, - 59.4, - 58.7, - 58.1, - 57.7, - 57.1, - 56.7, - 56.3, - 55.8, - 55.4, - 55, - 54.5, - 54.1, - 54.1, - 54.5, - 54.2, - 53.7, - 53.4, - 52.9, - 52.6, - 52.4, - 52.2, - 51.8, - 51.2, - 50.7, - 50.1, - 49.3, - 48.7, - 48.3, - 47.9, - 47.3, - 46.6, - 45.8, - 45, - 44.5, - 44.1, - 43.5, - 42.9, - 42.3, - 41.7, - 41.1, - 40.7, - 40.5, - 40.3, - 40, - 39.7, - 39.2, - 38.9, - 38.7, - 38.4, - 38, - 37.4, - 36.9, - 36.4, - 35.9, - 35.4, - 35, - 34.5, - 33.9, - 33.3, - 32.7, - 32, - 31.3, - 30.8, - 30.3, - 29.7, - 29, - 28.4, - 27.6, - 26.9, - 26.3, - 25.7, - 25.2, - 24.9, - 24.5, - 24.2, - 23.8, - 23.4, - 23, - 22.6, - 22.3, - 22, - 21.7, - 21.3, - 20.9, - 20.6, - 20.4, - 20.1, - 19.7, - 19.4, - 19.1, - 18.8, - 18.6, - 18.5, - 18.6, - 18.6, - 18.6, - 18.3, - 17.8, - 17.7, - 17.6, - 17.4, - 17.2, - 16.7, - 16.3, - 15.6, - 15.1, - 14.6, - 13.8, - 12.9, - 12.3, - 11.7, - 11, - 10.4, - 9.7, - 9.1, - 8.6, - 7.9, - 7.8, - 8.5, - 9.1, - 9.5, - 9.3, - 9.1, - 8.8, - 8.4, - 8, - 7.7, - 7.3, - 7, - 6.5, - 6.1, - 5.7, - 5.1, - 4.1, - 3.3, - 2.8, - 2.7, - 2.6, - 2.6, - 2.5, - 2.9, - 3.1, - 2.8, - 2.4, - 2.4, - 2.6, - 3.1, - 3.1, - 3, - 3, - 3, - 3.2, - 3.2, - 3.3, - 3.5, - 3.7, - 4.2, - 4.8, - 5.4, - 6.1, - 6.8, - 7.6, - 8.2, - 8.6, - 8.9, - 9.1, - 9.2, - 9.3, - 9.3, - 9.3, - 9.4, - 9.4, - 9.5, - 9.5, - 9.5, - 9.5, - 9.6, - 9.6, - 9.6, - 9.7, - 9.8, - 9.8, - 9.9, - 9.9, - 10, - 10.1, - 10.2, - 10.3, - 10.3, - 10.4, - 10.5, - 10.6, - 10.8, - 11, - 11.1, - 11.2, - 11.4, - 11.5, - 11.5, - 11.5, - 11.6, - 11.6, - 11.5, - 11.4, - 11.2, - 11.2, - 11.2, - 11.2, - 11.3, - 11.2, - 11.3, - 11.5, - 11.7, - 11.8, - 12, - 12.1, - 12.1, - 12.2, - 12.3, - 12.3, - 12.3, - 12.3, - 12.2, - 12.2, - 12.1, - 12.1, - 12, - 12, - 12, - 12, - 11.9, - 11.9, - 12, - 11.8, - 11.7, - 11.5, - 11.4, - 11.3, - 11.2, - 11.1, - 11.1, - 11, - 10.8, - 10.6, - 10.6, - 10.7, - 10.8, - 10.9, - 11.2, - 11.3, - 11.2, - 11.3, - 11.4, - 11.5, - 11.6, - 11.7, - 59.5, - 58.6, - 58.1, - 57.6, - 57.2, - 56.8, - 56.3, - 55.9, - 55.4, - 55, - 54.7, - 54.1, - 53.6, - 53.3, - 53.7, - 53.8, - 53.4, - 53, - 52.7, - 52.3, - 52, - 51.8, - 51.5, - 50.9, - 50.2, - 49.7, - 49.2, - 48.7, - 48.1, - 47.6, - 47, - 46.4, - 45.6, - 45, - 44.4, - 44, - 43.4, - 42.9, - 42.4, - 41.6, - 41.1, - 40.7, - 40.4, - 40.1, - 39.9, - 39.5, - 39.1, - 38.9, - 38.6, - 38.2, - 37.7, - 37.2, - 36.8, - 36.4, - 35.9, - 35.3, - 34.9, - 34.5, - 33.8, - 33.2, - 32.5, - 31.9, - 31.2, - 30.8, - 30.3, - 29.6, - 28.8, - 28.2, - 27.5, - 26.8, - 26.2, - 25.7, - 25.2, - 24.8, - 24.5, - 24.2, - 23.7, - 23.3, - 23, - 22.6, - 22.3, - 22.1, - 21.7, - 21.2, - 20.7, - 20.5, - 20.3, - 20, - 19.6, - 19.2, - 19, - 18.8, - 18.5, - 18.5, - 18.6, - 18.6, - 18.6, - 18.3, - 17.9, - 17.8, - 17.7, - 17.4, - 17.1, - 16.7, - 16.3, - 15.5, - 15.1, - 14.4, - 13.6, - 12.9, - 12.5, - 12, - 11.2, - 10.4, - 9.7, - 9.1, - 8.6, - 7.9, - 7.1, - 7.5, - 8.7, - 9.1, - 9.1, - 8.9, - 8.6, - 8.3, - 8, - 7.7, - 7.4, - 7.1, - 6.6, - 6.2, - 5.8, - 5.4, - 4.5, - 3.7, - 3, - 2.7, - 2.8, - 2.6, - 2.6, - 3, - 3.1, - 3, - 2.8, - 2.5, - 2.8, - 3.3, - 3.3, - 3, - 2.8, - 2.8, - 3.1, - 3.2, - 3.3, - 3.3, - 3.4, - 4, - 4.7, - 5.2, - 5.8, - 6.5, - 7.2, - 7.8, - 8.1, - 8.5, - 8.8, - 9, - 9, - 8.9, - 9.1, - 9.2, - 9.2, - 9.2, - 9.4, - 9.3, - 9.4, - 9.6, - 9.6, - 9.6, - 9.6, - 9.7, - 9.8, - 9.8, - 9.9, - 9.9, - 10, - 10.1, - 10.2, - 10.3, - 10.4, - 10.4, - 10.5, - 10.6, - 10.8, - 10.9, - 11, - 11.1, - 11.3, - 11.3, - 11.3, - 11.4, - 11.4, - 11.3, - 11.2, - 11.1, - 11.1, - 11.1, - 11.2, - 11.2, - 11.2, - 11.2, - 11.5, - 11.6, - 11.7, - 11.9, - 12, - 12.1, - 12.1, - 12.1, - 12.1, - 12.1, - 12, - 11.9, - 11.9, - 11.9, - 11.8, - 11.7, - 11.8, - 11.8, - 11.9, - 11.9, - 11.7, - 11.7, - 11.6, - 11.4, - 11.4, - 11.3, - 11.3, - 11.2, - 11.1, - 10.9, - 10.8, - 10.6, - 10.4, - 10.5, - 10.5, - 10.6, - 10.8, - 11.1, - 11.3, - 11.3, - 11.4, - 11.4, - 11.4, - 11.5, - 11.7, - 59.2, - 58.4, - 57.7, - 57.4, - 56.9, - 56.4, - 56, - 55.5, - 55.1, - 54.6, - 54.2, - 53.8, - 53.2, - 52.7, - 53.1, - 53.3, - 53, - 52.7, - 52.4, - 51.9, - 51.5, - 51.3, - 50.9, - 50.3, - 49.7, - 49.1, - 48.5, - 48.3, - 47.9, - 47.3, - 46.7, - 46.3, - 45.5, - 44.9, - 44.3, - 43.9, - 43.4, - 42.9, - 42.5, - 41.7, - 41.2, - 40.7, - 40.3, - 40.2, - 39.9, - 39.4, - 39.3, - 39, - 38.4, - 38, - 37.6, - 37.1, - 36.7, - 36.3, - 35.8, - 35.2, - 34.8, - 34.4, - 33.7, - 33.1, - 32.4, - 31.7, - 31.1, - 30.6, - 30.1, - 29.4, - 28.7, - 28, - 27.4, - 26.8, - 26.2, - 25.6, - 25.1, - 24.7, - 24.5, - 24.1, - 23.7, - 23.4, - 23.1, - 22.6, - 22.3, - 22, - 21.5, - 21.2, - 20.8, - 20.4, - 20.1, - 19.9, - 19.6, - 19.2, - 19, - 18.8, - 18.6, - 18.5, - 18.6, - 18.6, - 18.6, - 18.4, - 18, - 17.9, - 17.7, - 17.2, - 16.9, - 16.5, - 15.9, - 15.3, - 14.7, - 13.8, - 13.3, - 12.9, - 12.6, - 12.1, - 11.2, - 10.4, - 9.5, - 9, - 8.5, - 7.8, - 6.9, - 7.2, - 8.2, - 8.7, - 8.9, - 8.7, - 8.5, - 8.2, - 8, - 7.7, - 7.4, - 7, - 6.5, - 6, - 5.6, - 5.1, - 4.5, - 3.8, - 3.3, - 3.2, - 3.2, - 3, - 2.9, - 3.1, - 3.1, - 3.1, - 3, - 2.5, - 2.7, - 3, - 3.1, - 2.9, - 2.6, - 2.9, - 3.1, - 3.2, - 3.3, - 3.4, - 3.5, - 4, - 4.6, - 5.1, - 5.4, - 6.1, - 6.8, - 7.3, - 7.6, - 7.9, - 8.1, - 8.3, - 8.3, - 8.3, - 8.6, - 8.8, - 8.9, - 9, - 9.1, - 9.2, - 9.4, - 9.5, - 9.5, - 9.5, - 9.6, - 9.6, - 9.7, - 9.8, - 9.9, - 9.9, - 10, - 10, - 10.1, - 10.1, - 10.3, - 10.2, - 10.3, - 10.4, - 10.5, - 10.6, - 10.8, - 10.9, - 11, - 11, - 11, - 11.1, - 11.1, - 11, - 11, - 11, - 11, - 11, - 11.1, - 11.1, - 11, - 11.1, - 11.3, - 11.5, - 11.6, - 11.8, - 11.9, - 12, - 12, - 12, - 12, - 11.9, - 11.8, - 11.8, - 11.6, - 11.7, - 11.7, - 11.6, - 11.7, - 11.7, - 11.7, - 11.7, - 11.5, - 11.4, - 11.4, - 11.4, - 11.4, - 11.4, - 11.3, - 11.2, - 11, - 10.8, - 10.7, - 10.4, - 10.3, - 10.3, - 10.4, - 10.5, - 10.6, - 10.9, - 11.2, - 11.3, - 11.4, - 11.4, - 11.5, - 11.6, - 11.7, - 58.8, - 58.2, - 57.5, - 57.1, - 56.7, - 56.1, - 55.6, - 55.1, - 54.6, - 54.1, - 53.7, - 53.4, - 52.8, - 52.1, - 52.5, - 52.8, - 52.5, - 52.4, - 52, - 51.4, - 51, - 50.8, - 50.5, - 49.8, - 49.2, - 48.6, - 48.2, - 48, - 47.7, - 47.2, - 46.6, - 46.2, - 45.4, - 44.8, - 44.2, - 43.7, - 43.3, - 42.9, - 42.4, - 41.7, - 41.2, - 40.7, - 40.2, - 40, - 39.8, - 39.4, - 39.3, - 38.9, - 38.5, - 38.1, - 37.6, - 37.1, - 36.5, - 36.1, - 35.7, - 35.2, - 34.7, - 34.1, - 33.6, - 32.9, - 32.3, - 31.6, - 31, - 30.5, - 30, - 29.3, - 28.6, - 27.9, - 27.4, - 26.7, - 26.1, - 25.5, - 25, - 24.7, - 24.4, - 24, - 23.6, - 23.3, - 23, - 22.5, - 22.2, - 21.8, - 21.5, - 21.3, - 21, - 20.3, - 20, - 19.7, - 19.5, - 19.2, - 18.9, - 18.6, - 18.4, - 18.4, - 18.6, - 18.6, - 18.6, - 18.4, - 18.1, - 17.9, - 17.4, - 16.9, - 16.5, - 15.9, - 15.3, - 15.3, - 14.6, - 13.8, - 13.4, - 13.1, - 12.6, - 11.9, - 11, - 10.2, - 9.3, - 8.7, - 8.2, - 7.5, - 6.7, - 6.8, - 7.6, - 8.3, - 8.7, - 8.5, - 8.3, - 8.1, - 7.9, - 7.6, - 7.3, - 7, - 6.5, - 5.9, - 5.3, - 4.7, - 4.2, - 3.6, - 3.4, - 3.4, - 3.3, - 3.2, - 3.1, - 3.2, - 3.1, - 3, - 3, - 2.6, - 2.5, - 2.8, - 2.8, - 2.7, - 2.5, - 3, - 3.2, - 3.4, - 3.5, - 3.6, - 3.9, - 4.1, - 4.5, - 5, - 5.4, - 6.1, - 6.6, - 7, - 7.2, - 7.5, - 7.6, - 7.7, - 7.8, - 8, - 8.4, - 8.6, - 8.7, - 8.7, - 8.9, - 9.1, - 9.3, - 9.4, - 9.3, - 9.4, - 9.5, - 9.6, - 9.7, - 9.8, - 9.8, - 9.9, - 9.9, - 10, - 10, - 10.1, - 10.2, - 10.1, - 10.2, - 10.3, - 10.4, - 10.4, - 10.5, - 10.6, - 10.8, - 10.8, - 10.8, - 10.8, - 10.8, - 10.8, - 10.9, - 10.9, - 10.9, - 11, - 11, - 11, - 11, - 11, - 11.3, - 11.5, - 11.7, - 11.8, - 11.8, - 11.8, - 11.7, - 11.8, - 11.7, - 11.6, - 11.5, - 11.5, - 11.5, - 11.5, - 11.5, - 11.5, - 11.6, - 11.6, - 11.7, - 11.6, - 11.5, - 11.4, - 11.5, - 11.5, - 11.5, - 11.4, - 11.2, - 11.2, - 11.1, - 11, - 10.8, - 10.4, - 10.1, - 10.2, - 10.3, - 10.5, - 10.7, - 10.9, - 10.9, - 11.2, - 11.4, - 11.4, - 11.5, - 11.6, - 11.7, - 58.6, - 58, - 57.3, - 56.8, - 56.3, - 55.7, - 55.2, - 54.6, - 54, - 53.6, - 53.2, - 52.9, - 52.5, - 51.9, - 52, - 52, - 51.8, - 51.7, - 51.4, - 51, - 50.6, - 50.4, - 50, - 49.3, - 48.7, - 48.2, - 48, - 47.8, - 47.4, - 46.9, - 46.4, - 45.9, - 45.2, - 44.7, - 44.2, - 43.7, - 43.4, - 42.7, - 42.3, - 41.7, - 41.1, - 40.6, - 40.2, - 39.7, - 39.4, - 39.2, - 39.1, - 38.9, - 38.5, - 38.1, - 37.5, - 37, - 36.4, - 35.9, - 35.5, - 35, - 34.5, - 34, - 33.4, - 32.7, - 32.1, - 31.4, - 30.8, - 30.3, - 29.7, - 29.1, - 28.4, - 27.8, - 27.2, - 26.6, - 26, - 25.4, - 25, - 24.7, - 24.4, - 23.9, - 23.5, - 23.1, - 22.8, - 22.4, - 22.1, - 21.7, - 21.4, - 21.2, - 20.9, - 20.5, - 20.1, - 19.8, - 19.5, - 19.1, - 18.7, - 18.5, - 18.4, - 18.4, - 18.6, - 18.6, - 18.5, - 18.3, - 18.2, - 17.7, - 16.5, - 16.4, - 16.1, - 15.7, - 15.4, - 15.3, - 14.7, - 14.1, - 13.7, - 13.3, - 12.7, - 11.5, - 10.8, - 10.1, - 9.3, - 8.4, - 7.9, - 7.3, - 6.6, - 6.1, - 6.8, - 7.8, - 8.4, - 8.3, - 8.2, - 7.9, - 7.7, - 7.5, - 7.3, - 6.9, - 6.5, - 6, - 5.4, - 4.7, - 4.3, - 3.7, - 3.2, - 3.2, - 3.1, - 3.2, - 3.3, - 3.3, - 3.1, - 3, - 2.8, - 2.4, - 2.5, - 2.6, - 2.7, - 2.5, - 2.5, - 2.9, - 3.1, - 3.3, - 3.5, - 3.7, - 4, - 4.2, - 4.5, - 5, - 5.4, - 6, - 6.4, - 6.8, - 7.1, - 7.3, - 7.3, - 7.2, - 7.4, - 7.9, - 8.3, - 8.4, - 8.6, - 8.6, - 8.7, - 8.8, - 9, - 9.1, - 9.2, - 9.3, - 9.4, - 9.4, - 9.6, - 9.7, - 9.8, - 9.8, - 9.9, - 10, - 10, - 10, - 10.1, - 10.1, - 10.1, - 10.2, - 10.3, - 10.4, - 10.4, - 10.5, - 10.5, - 10.6, - 10.6, - 10.6, - 10.6, - 10.6, - 10.7, - 10.8, - 10.8, - 10.9, - 10.8, - 10.9, - 10.9, - 11, - 11.4, - 11.6, - 11.7, - 11.7, - 11.6, - 11.6, - 11.6, - 11.5, - 11.4, - 11.4, - 11.4, - 11.3, - 11.4, - 11.4, - 11.4, - 11.5, - 11.6, - 11.7, - 11.6, - 11.6, - 11.4, - 11.4, - 11.5, - 11.6, - 11.5, - 11.4, - 11.3, - 11.2, - 11.1, - 11, - 11, - 10.7, - 10, - 10, - 10.2, - 10.5, - 10.8, - 11, - 11.1, - 11.2, - 11.3, - 11.5, - 11.5, - 11.6, - 11.7, - 58.2, - 57.6, - 57, - 56.4, - 55.8, - 55.3, - 54.8, - 54.2, - 53.5, - 53.1, - 52.8, - 52.4, - 52.2, - 51.8, - 51.5, - 51.5, - 51.3, - 51.1, - 50.8, - 50.5, - 50.2, - 49.9, - 49.4, - 48.9, - 48.4, - 48.1, - 47.8, - 47.3, - 46.8, - 46.5, - 46.1, - 45.6, - 45, - 44.7, - 44.1, - 43.6, - 43.3, - 42.7, - 42.2, - 41.7, - 41.1, - 40.7, - 40.2, - 39.8, - 39.3, - 39.1, - 39, - 38.7, - 38.3, - 38, - 37.3, - 36.8, - 36.3, - 35.8, - 35.4, - 34.9, - 34.4, - 33.9, - 33.2, - 32.5, - 31.9, - 31.2, - 30.6, - 30.1, - 29.5, - 28.9, - 28.3, - 27.7, - 27.2, - 26.6, - 26, - 25.5, - 25, - 24.6, - 24.3, - 23.8, - 23.3, - 23, - 22.7, - 22.3, - 22, - 21.5, - 21.2, - 21, - 20.9, - 20.6, - 20.3, - 19.9, - 19.6, - 19.2, - 18.8, - 18.4, - 18.3, - 18.3, - 18.5, - 18.6, - 18.5, - 18.3, - 18.2, - 17.7, - 16.8, - 16.5, - 16.3, - 15.8, - 15.5, - 15.2, - 14.8, - 14.2, - 13.9, - 13.4, - 12.5, - 11.2, - 10.4, - 9.9, - 9.2, - 8.5, - 7.8, - 7.1, - 6.5, - 6, - 5.8, - 7, - 7.9, - 8.1, - 8, - 7.8, - 7.6, - 7.5, - 7.1, - 6.9, - 6.7, - 6.2, - 5.5, - 4.8, - 4.2, - 3.7, - 3.2, - 3, - 2.9, - 3, - 3, - 3, - 2.9, - 2.9, - 2.8, - 2.5, - 2.4, - 2.4, - 2.4, - 2.3, - 2.4, - 2.8, - 3, - 3.3, - 3.5, - 3.7, - 3.9, - 4.2, - 4.6, - 5.1, - 5.5, - 5.8, - 6.1, - 6.4, - 6.5, - 6.3, - 6.4, - 6.6, - 6.9, - 7.5, - 8.2, - 8.5, - 8.7, - 8.7, - 8.6, - 8.8, - 8.9, - 9, - 9.1, - 9.2, - 9.3, - 9.4, - 9.5, - 9.7, - 9.7, - 9.8, - 9.8, - 9.8, - 9.9, - 9.9, - 10, - 10.1, - 10.1, - 10.1, - 10.2, - 10.3, - 10.3, - 10.3, - 10.4, - 10.5, - 10.5, - 10.6, - 10.6, - 10.6, - 10.6, - 10.7, - 10.8, - 10.8, - 10.8, - 10.9, - 10.9, - 11.1, - 11.4, - 11.5, - 11.5, - 11.5, - 11.4, - 11.4, - 11.4, - 11.2, - 11.3, - 11.3, - 11.3, - 11.2, - 11.3, - 11.5, - 11.4, - 11.5, - 11.6, - 11.7, - 11.7, - 11.6, - 11.5, - 11.5, - 11.5, - 11.6, - 11.6, - 11.5, - 11.4, - 11.3, - 11.2, - 11.1, - 11, - 10.7, - 9.9, - 9.9, - 10.2, - 10.6, - 10.7, - 10.7, - 10.8, - 10.9, - 11, - 11.4, - 11.5, - 11.6, - 11.7, - 56.9, - 57, - 56.6, - 55.9, - 55.3, - 54.8, - 54.2, - 53.7, - 53.1, - 52.9, - 52.7, - 52.3, - 52, - 51.7, - 51.2, - 51, - 50.8, - 50.6, - 50.3, - 49.9, - 49.6, - 49.4, - 48.9, - 48.5, - 48.2, - 48, - 47.5, - 47.1, - 46.7, - 46.4, - 45.9, - 45.4, - 44.9, - 44.5, - 43.9, - 43.4, - 42.9, - 42.5, - 42.3, - 41.8, - 41, - 40.6, - 40.1, - 39.7, - 39.4, - 39, - 38.9, - 38.6, - 38.1, - 37.7, - 37.2, - 36.6, - 36.2, - 35.8, - 35.3, - 34.7, - 34.2, - 33.7, - 33, - 32.4, - 31.8, - 31.1, - 30.6, - 30, - 29.4, - 28.9, - 28.2, - 27.6, - 27.1, - 26.5, - 25.9, - 25.3, - 24.9, - 24.6, - 24.1, - 23.7, - 23.2, - 23, - 22.6, - 22.2, - 21.8, - 21.5, - 21.3, - 21.1, - 21, - 20.7, - 20.3, - 20, - 19.8, - 19.4, - 18.9, - 18.6, - 18.3, - 18.3, - 18.5, - 18.5, - 18.5, - 18.4, - 18.3, - 17.7, - 17.1, - 16.8, - 16.5, - 16.1, - 15.7, - 15.3, - 14.9, - 14.4, - 14, - 13.3, - 12, - 11.1, - 10.2, - 9.6, - 9.1, - 8.4, - 7.8, - 7, - 6.3, - 5.9, - 5.7, - 6.3, - 7.4, - 7.8, - 7.9, - 7.8, - 7.5, - 7.3, - 7.1, - 6.9, - 6.7, - 6.3, - 5.6, - 5, - 4.3, - 3.8, - 3.3, - 3, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.6, - 2.6, - 2.5, - 2.3, - 2.3, - 2.7, - 2.9, - 3.2, - 3.5, - 3.7, - 4, - 4.3, - 4.7, - 5, - 5.4, - 5.5, - 5.6, - 5.7, - 5.5, - 5.5, - 5.9, - 6.4, - 6.8, - 7.8, - 8.3, - 8.7, - 8.9, - 8.9, - 8.8, - 8.9, - 8.9, - 8.9, - 9, - 9.2, - 9.2, - 9.3, - 9.4, - 9.6, - 9.7, - 9.7, - 9.7, - 9.7, - 9.8, - 9.8, - 9.9, - 10, - 10, - 10.1, - 10.2, - 10.2, - 10.2, - 10.3, - 10.4, - 10.5, - 10.5, - 10.6, - 10.5, - 10.6, - 10.6, - 10.7, - 10.7, - 10.7, - 10.8, - 10.7, - 10.8, - 11.1, - 11.2, - 11.4, - 11.5, - 11.4, - 11.2, - 11.2, - 11.2, - 11.2, - 11.3, - 11.4, - 11.3, - 11.3, - 11.5, - 11.5, - 11.6, - 11.6, - 11.6, - 11.8, - 11.7, - 11.6, - 11.6, - 11.5, - 11.5, - 11.6, - 11.6, - 11.4, - 11.3, - 11.3, - 11.3, - 11.2, - 11.1, - 11.1, - 10.1, - 9.6, - 10.1, - 10.4, - 10.3, - 10.5, - 10.7, - 10.8, - 11.1, - 11.4, - 11.5, - 11.6, - 11.7, - 56.5, - 56.4, - 55.9, - 55.2, - 54.6, - 54.2, - 53.8, - 53.5, - 52.9, - 52.7, - 52.4, - 52, - 51.8, - 51.4, - 50.9, - 50.5, - 50.4, - 50, - 49.7, - 49.3, - 49, - 48.7, - 48.4, - 48.1, - 48, - 47.8, - 47.4, - 46.9, - 46.5, - 46.1, - 45.6, - 45.1, - 44.7, - 44.2, - 43.7, - 43.2, - 42.8, - 42.5, - 42.1, - 41.6, - 41.1, - 40.6, - 39.9, - 39.6, - 39.3, - 39.1, - 38.9, - 38.5, - 38, - 37.5, - 37, - 36.5, - 36.1, - 35.6, - 35.2, - 34.7, - 34.2, - 33.6, - 33, - 32.3, - 31.7, - 31, - 30.5, - 29.9, - 29.4, - 28.8, - 28.2, - 27.6, - 27.1, - 26.4, - 25.8, - 25.3, - 24.9, - 24.5, - 24.1, - 23.7, - 23.4, - 23, - 22.6, - 22.3, - 21.8, - 21.5, - 21.3, - 21.3, - 21.2, - 20.9, - 20.4, - 20, - 19.8, - 19.5, - 19, - 18.6, - 18.4, - 18.3, - 18.5, - 18.5, - 18.5, - 18.5, - 18.3, - 17.8, - 17.4, - 17.1, - 16.8, - 16.5, - 16.1, - 15.6, - 15.2, - 14.7, - 14.1, - 13.1, - 12.1, - 11, - 10.3, - 9.5, - 8.8, - 8.2, - 7.5, - 6.8, - 6.2, - 5.7, - 5.5, - 5.7, - 7.1, - 7.6, - 7.8, - 7.7, - 7.4, - 7.2, - 7.1, - 6.9, - 6.6, - 6.3, - 5.7, - 5.1, - 4.5, - 4, - 3.2, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.4, - 2.5, - 2.7, - 2.5, - 2.8, - 3.2, - 3.6, - 4, - 4.3, - 4.5, - 4.6, - 4.8, - 4.9, - 5, - 5.3, - 5.6, - 6.1, - 6.7, - 7.3, - 7.8, - 8.5, - 8.8, - 9, - 9.2, - 9.1, - 9, - 9.1, - 9.1, - 9, - 9, - 9.1, - 9.1, - 9.3, - 9.2, - 9.4, - 9.6, - 9.6, - 9.6, - 9.7, - 9.7, - 9.8, - 9.9, - 10, - 10, - 10.1, - 10.2, - 10.2, - 10.3, - 10.3, - 10.4, - 10.3, - 10.3, - 10.4, - 10.4, - 10.5, - 10.5, - 10.6, - 10.6, - 10.7, - 10.7, - 10.7, - 10.8, - 11, - 11.2, - 11.3, - 11.3, - 11.3, - 11.3, - 11.2, - 11.2, - 11.4, - 11.4, - 11.5, - 11.5, - 11.5, - 11.6, - 11.6, - 11.7, - 11.7, - 11.7, - 11.8, - 11.7, - 11.7, - 11.6, - 11.5, - 11.5, - 11.6, - 11.5, - 11.4, - 11.3, - 11.3, - 11.2, - 11.2, - 11.2, - 11.2, - 10.5, - 9.5, - 9.7, - 9.9, - 10.1, - 10.6, - 11, - 11.2, - 11.3, - 11.5, - 11.6, - 11.6, - 11.7, - 56.2, - 56, - 55.1, - 54.7, - 54.1, - 53.8, - 53.5, - 53.2, - 52.8, - 52.5, - 52.2, - 51.8, - 51.5, - 51.2, - 50.7, - 50.2, - 50, - 49.7, - 49.4, - 49.1, - 48.8, - 48.4, - 48.1, - 48, - 47.9, - 47.7, - 47.3, - 46.8, - 46.3, - 45.8, - 45.3, - 44.9, - 44.5, - 44, - 43.5, - 43.1, - 42.7, - 42.4, - 42, - 41.5, - 40.9, - 40.5, - 39.9, - 39.4, - 39.3, - 39.2, - 38.9, - 38.5, - 38, - 37.5, - 37, - 36.6, - 36, - 35.5, - 35.1, - 34.6, - 34.1, - 33.6, - 33, - 32.3, - 31.7, - 31.1, - 30.5, - 29.9, - 29.3, - 28.6, - 28.1, - 27.6, - 27, - 26.4, - 25.8, - 25.3, - 24.9, - 24.5, - 24.2, - 23.9, - 23.6, - 23.2, - 22.8, - 22.4, - 21.8, - 21.5, - 21.4, - 21.4, - 21.3, - 20.9, - 20.5, - 20.1, - 19.8, - 19.4, - 18.9, - 18.6, - 18.5, - 18.4, - 18.5, - 18.5, - 18.5, - 18.6, - 18.3, - 17.6, - 17.4, - 17.3, - 17, - 16.7, - 16.3, - 15.8, - 15.3, - 14.7, - 13.9, - 13, - 12.2, - 10.9, - 10.2, - 9.4, - 8.6, - 7.9, - 7.3, - 6.8, - 6.1, - 5.6, - 5.1, - 5.1, - 6.5, - 7.3, - 7.6, - 7.5, - 7.4, - 7.2, - 7, - 6.9, - 6.6, - 6.4, - 5.9, - 5.4, - 4.8, - 4.1, - 3.2, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.3, - 2.4, - 2.4, - 2.2, - 2.4, - 2.8, - 3.3, - 3.8, - 4.1, - 4.1, - 4.1, - 4.2, - 4.3, - 4.6, - 5.1, - 5.7, - 6.4, - 7.1, - 7.7, - 8.3, - 8.7, - 9.1, - 9.2, - 9.3, - 9.3, - 9.2, - 9.3, - 9.2, - 9.1, - 9.1, - 9.1, - 9.1, - 9.2, - 9.3, - 9.4, - 9.6, - 9.6, - 9.6, - 9.6, - 9.7, - 9.7, - 9.9, - 9.9, - 9.9, - 9.9, - 10, - 10.1, - 10.2, - 10.3, - 10.2, - 10.2, - 10.3, - 10.4, - 10.4, - 10.4, - 10.4, - 10.5, - 10.5, - 10.6, - 10.6, - 10.7, - 10.8, - 10.9, - 11.1, - 11.2, - 11.2, - 11.2, - 11.3, - 11.4, - 11.4, - 11.5, - 11.5, - 11.6, - 11.6, - 11.7, - 11.6, - 11.7, - 11.8, - 11.8, - 11.9, - 11.8, - 11.7, - 11.6, - 11.6, - 11.6, - 11.5, - 11.6, - 11.5, - 11.4, - 11.4, - 11.4, - 11.3, - 11.2, - 11.2, - 11.2, - 10.5, - 9.4, - 9.4, - 9.8, - 10.3, - 10.9, - 11.2, - 11.3, - 11.3, - 11.4, - 11.6, - 11.6, - 11.7, - 55.8, - 55.3, - 54.4, - 54, - 53.7, - 53.4, - 53.2, - 53, - 52.7, - 52.3, - 52, - 51.7, - 51.3, - 50.8, - 50.3, - 49.9, - 49.8, - 49.6, - 49.5, - 49.3, - 49, - 48.7, - 48.5, - 48.1, - 47.8, - 47.4, - 47, - 46.4, - 46, - 45.6, - 45.1, - 44.7, - 44.2, - 43.9, - 43.4, - 43, - 42.5, - 42.1, - 41.7, - 41.4, - 40.8, - 40.3, - 39.7, - 39.4, - 39.4, - 39.2, - 38.8, - 38.4, - 37.9, - 37.4, - 37, - 36.5, - 36, - 35.6, - 34.9, - 34.4, - 33.9, - 33.5, - 32.9, - 32.3, - 31.7, - 31.1, - 30.5, - 29.9, - 29.3, - 28.7, - 28.1, - 27.5, - 27, - 26.4, - 25.8, - 25.3, - 24.8, - 24.5, - 24.2, - 24, - 23.7, - 23.4, - 22.9, - 22.5, - 22, - 21.5, - 21.4, - 21.2, - 20.9, - 20.6, - 20.4, - 20.1, - 19.7, - 19.2, - 18.7, - 18.7, - 18.5, - 18.4, - 18.5, - 18.5, - 18.5, - 18.5, - 18.3, - 17.6, - 17.2, - 17.3, - 17.1, - 16.7, - 16.4, - 15.9, - 15.4, - 14.7, - 13.9, - 12.8, - 11.9, - 10.9, - 10, - 9.2, - 8.5, - 7.8, - 7.2, - 6.6, - 6, - 5.4, - 4.9, - 4.8, - 6, - 7.1, - 7.4, - 7.5, - 7.4, - 7.1, - 7, - 6.9, - 6.7, - 6.4, - 6.1, - 5.8, - 5.1, - 4.2, - 3.3, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.4, - 2.4, - 2.3, - 2.3, - 2.4, - 2.6, - 2.8, - 3.5, - 3.9, - 4, - 4, - 4, - 4.1, - 4.5, - 5, - 5.7, - 6.3, - 7, - 7.6, - 8.2, - 8.6, - 9, - 9.3, - 9.3, - 9.3, - 9.3, - 9.3, - 9.4, - 9.3, - 9.2, - 9.1, - 9.2, - 9.3, - 9.3, - 9.5, - 9.6, - 9.6, - 9.6, - 9.6, - 9.7, - 9.7, - 9.8, - 9.8, - 9.8, - 9.9, - 9.9, - 10, - 10.1, - 10.2, - 10.2, - 10.2, - 10.3, - 10.3, - 10.3, - 10.3, - 10.4, - 10.5, - 10.5, - 10.5, - 10.6, - 10.7, - 10.8, - 11, - 11.1, - 11.2, - 11.1, - 11.1, - 11.3, - 11.4, - 11.5, - 11.5, - 11.6, - 11.6, - 11.7, - 11.7, - 11.7, - 11.9, - 11.8, - 11.8, - 11.9, - 11.7, - 11.6, - 11.5, - 11.5, - 11.5, - 11.5, - 11.5, - 11.3, - 11.4, - 11.4, - 11.3, - 11.2, - 11.2, - 11.2, - 11.2, - 10.5, - 9.4, - 9.2, - 9.9, - 10.5, - 10.9, - 11.1, - 11.2, - 11.3, - 11.3, - 11.5, - 11.6, - 11.6, - 55.3, - 54.7, - 54.1, - 53.7, - 53.3, - 53.1, - 53, - 53, - 52.6, - 52.2, - 51.9, - 51.5, - 50.9, - 50.4, - 50.1, - 49.9, - 49.8, - 49.5, - 49.3, - 49.1, - 49.1, - 48.9, - 48.5, - 48.1, - 47.8, - 47.4, - 46.9, - 46.2, - 45.7, - 45.4, - 44.9, - 44.3, - 43.9, - 43.5, - 43.2, - 42.7, - 42.2, - 41.9, - 41.6, - 41.3, - 40.7, - 40.1, - 39.7, - 39.6, - 39.5, - 39.1, - 38.7, - 38.3, - 37.8, - 37.3, - 36.8, - 36.5, - 36, - 35.5, - 34.9, - 34.5, - 34, - 33.4, - 32.9, - 32.3, - 31.6, - 31, - 30.5, - 29.9, - 29.3, - 28.7, - 28.2, - 27.6, - 27, - 26.4, - 25.9, - 25.4, - 25, - 24.5, - 24.1, - 23.9, - 23.7, - 23.5, - 23.1, - 22.5, - 22.1, - 21.5, - 21.2, - 20.9, - 20.7, - 20.6, - 20.4, - 19.8, - 19.4, - 19.1, - 18.8, - 18.6, - 18.5, - 18.4, - 18.5, - 18.5, - 18.5, - 18.5, - 18.3, - 17.5, - 17, - 17, - 17.2, - 16.9, - 16.4, - 15.9, - 15.4, - 14.8, - 13.9, - 12.7, - 12, - 10.9, - 10.1, - 9, - 8.4, - 7.7, - 7.1, - 6.5, - 5.9, - 5.4, - 4.8, - 4.7, - 5.6, - 6.7, - 7.2, - 7.5, - 7.4, - 7.1, - 7.1, - 7, - 6.7, - 6.5, - 6.3, - 5.9, - 5.4, - 4.7, - 3.7, - 2.7, - 2.6, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.4, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 3.1, - 3.4, - 3.5, - 3.7, - 3.7, - 3.9, - 4.4, - 4.9, - 5.4, - 6.1, - 6.6, - 7.3, - 7.9, - 8.4, - 8.7, - 9, - 9.2, - 9.2, - 9.3, - 9.4, - 9.4, - 9.5, - 9.4, - 9.4, - 9.3, - 9.3, - 9.4, - 9.5, - 9.6, - 9.5, - 9.7, - 9.7, - 9.7, - 9.7, - 9.8, - 9.8, - 9.9, - 9.9, - 9.9, - 9.9, - 10, - 10, - 10.1, - 10.2, - 10.2, - 10.2, - 10.3, - 10.3, - 10.4, - 10.5, - 10.5, - 10.5, - 10.5, - 10.7, - 10.8, - 11, - 11.2, - 11.2, - 11.2, - 11.2, - 11.3, - 11.4, - 11.4, - 11.4, - 11.5, - 11.7, - 11.7, - 11.7, - 11.8, - 11.9, - 11.9, - 12, - 11.9, - 11.7, - 11.5, - 11.5, - 11.5, - 11.5, - 11.5, - 11.4, - 11.3, - 11.2, - 11.2, - 11.3, - 11.2, - 11.2, - 11.1, - 11.1, - 10.3, - 9.3, - 9.1, - 9.8, - 10.5, - 11, - 11.1, - 11.2, - 11.2, - 11.3, - 11.4, - 11.6, - 11.7, - 55, - 54.5, - 53.8, - 53.4, - 53.1, - 52.8, - 52.7, - 52.7, - 52.4, - 52, - 51.7, - 51.2, - 50.5, - 50.1, - 49.9, - 49.6, - 49.5, - 49.5, - 49.1, - 48.9, - 48.9, - 48.7, - 48.2, - 48, - 47.6, - 47.2, - 46.9, - 46.2, - 45.6, - 45.2, - 44.8, - 44.2, - 43.6, - 43.2, - 42.8, - 42.5, - 42.1, - 41.9, - 41.5, - 41, - 40.5, - 40, - 39.8, - 39.7, - 39.4, - 39, - 38.7, - 38.2, - 37.6, - 37.1, - 36.7, - 36.4, - 36, - 35.5, - 34.8, - 34.5, - 34, - 33.4, - 32.8, - 32.2, - 31.6, - 31.1, - 30.3, - 29.6, - 29.1, - 28.6, - 28.1, - 27.5, - 27, - 26.5, - 25.9, - 25.5, - 25, - 24.4, - 24.1, - 23.8, - 23.7, - 23.5, - 23.1, - 22.5, - 21.8, - 21.4, - 21, - 20.9, - 20.7, - 20.6, - 20.2, - 19.7, - 19.4, - 19.2, - 18.9, - 18.6, - 18.5, - 18.4, - 18.5, - 18.5, - 18.5, - 18.5, - 18.1, - 17.3, - 17.1, - 17.1, - 17.2, - 17, - 16.5, - 16, - 15.3, - 14.6, - 13.9, - 12.9, - 11.9, - 10.7, - 9.8, - 8.9, - 8.4, - 7.8, - 7, - 6.3, - 5.8, - 5.3, - 4.7, - 4.5, - 5.4, - 6.4, - 7, - 7.4, - 7.4, - 7.3, - 7.2, - 7, - 6.8, - 6.6, - 6.5, - 6.2, - 5.7, - 5, - 3.8, - 2.8, - 2.6, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.5, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.8, - 3, - 3.2, - 3.4, - 3.6, - 4, - 4.3, - 4.7, - 5.1, - 5.7, - 6.2, - 6.8, - 7.4, - 8, - 8.4, - 8.7, - 8.9, - 9.1, - 9.3, - 9.4, - 9.5, - 9.5, - 9.5, - 9.5, - 9.4, - 9.3, - 9.5, - 9.5, - 9.5, - 9.5, - 9.8, - 9.8, - 9.8, - 9.8, - 9.9, - 9.8, - 9.9, - 10, - 9.9, - 9.9, - 10, - 10, - 10.1, - 10.1, - 10.2, - 10.2, - 10.2, - 10.3, - 10.4, - 10.4, - 10.5, - 10.5, - 10.5, - 10.7, - 10.7, - 11, - 11.1, - 11.2, - 11.2, - 11.3, - 11.3, - 11.4, - 11.5, - 11.5, - 11.5, - 11.7, - 11.8, - 11.9, - 11.9, - 11.9, - 11.9, - 12, - 11.9, - 11.6, - 11.5, - 11.5, - 11.5, - 11.4, - 11.4, - 11.3, - 11.3, - 11.2, - 11.1, - 11.2, - 11.2, - 11.2, - 11.1, - 11.1, - 10.3, - 9.3, - 9.1, - 9.5, - 10.1, - 10.7, - 11, - 11.1, - 11.3, - 11.5, - 11.6, - 11.6, - 11.7, - 54.9, - 54.3, - 53.7, - 53.4, - 53.1, - 52.7, - 52.6, - 52.5, - 52.1, - 51.7, - 51.4, - 50.9, - 50.4, - 50.1, - 49.8, - 49.4, - 49.5, - 49.4, - 48.9, - 48.7, - 48.5, - 48.4, - 48.1, - 47.8, - 47.3, - 47, - 46.7, - 46.3, - 45.7, - 45.1, - 44.6, - 44.1, - 43.6, - 43.1, - 42.9, - 42.6, - 42.2, - 41.9, - 41.6, - 41, - 40.5, - 40.2, - 39.9, - 39.7, - 39.4, - 39.1, - 38.7, - 38.2, - 37.6, - 37.2, - 36.6, - 36.2, - 35.8, - 35.4, - 34.7, - 34.2, - 33.8, - 33.3, - 32.8, - 32.3, - 31.8, - 31, - 30.3, - 29.8, - 29.2, - 28.7, - 28.1, - 27.6, - 27.1, - 26.5, - 25.9, - 25.5, - 25, - 24.5, - 24, - 23.8, - 23.6, - 23.3, - 22.9, - 22.4, - 21.8, - 21.3, - 21, - 20.8, - 20.7, - 20.4, - 20.1, - 19.8, - 19.6, - 19.3, - 18.9, - 18.6, - 18.5, - 18.3, - 18.5, - 18.5, - 18.5, - 18.4, - 17.8, - 17.2, - 17.2, - 17.3, - 17.2, - 17, - 16.5, - 15.9, - 15.2, - 14.7, - 13.9, - 13.1, - 11.9, - 10.7, - 9.8, - 8.9, - 8.2, - 7.7, - 7, - 6.3, - 5.7, - 5.2, - 4.8, - 4.5, - 5.2, - 6, - 6.6, - 7.2, - 7.4, - 7.3, - 7.1, - 7, - 6.8, - 6.7, - 6.6, - 6.4, - 5.9, - 5.2, - 4, - 3, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.4, - 2.4, - 2.4, - 2.5, - 2.3, - 2.5, - 2.3, - 2.6, - 3, - 3.3, - 3.6, - 3.8, - 4.1, - 4.3, - 4.6, - 4.9, - 5.3, - 5.8, - 6.2, - 6.9, - 7.4, - 7.9, - 8.2, - 8.5, - 8.8, - 9, - 9.3, - 9.5, - 9.6, - 9.6, - 9.5, - 9.5, - 9.5, - 9.5, - 9.5, - 9.5, - 9.6, - 9.8, - 9.9, - 9.9, - 9.9, - 9.7, - 9.7, - 9.8, - 9.9, - 10, - 10, - 9.9, - 10, - 10.1, - 10.2, - 10.2, - 10.2, - 10.3, - 10.3, - 10.4, - 10.4, - 10.5, - 10.5, - 10.6, - 10.7, - 10.8, - 11, - 11.1, - 11.1, - 11.2, - 11.3, - 11.3, - 11.4, - 11.6, - 11.6, - 11.6, - 11.7, - 11.9, - 11.9, - 11.9, - 11.9, - 12, - 12, - 11.7, - 11.5, - 11.5, - 11.5, - 11.4, - 11.3, - 11.4, - 11.3, - 11.2, - 11.2, - 11.1, - 11.2, - 11.1, - 11.1, - 11, - 10.8, - 9.9, - 9.1, - 9.1, - 9.7, - 10.4, - 10.8, - 11, - 11.2, - 11.2, - 11.4, - 11.5, - 11.5, - 11.6, - 54.7, - 54.2, - 53.8, - 53.5, - 53.2, - 52.7, - 52.4, - 52.2, - 51.8, - 51.4, - 50.9, - 50.7, - 50.5, - 50.2, - 49.7, - 49.2, - 49, - 49.1, - 48.8, - 48.4, - 48.3, - 48, - 47.7, - 47.3, - 46.9, - 46.7, - 46.5, - 46.2, - 45.6, - 45, - 44.5, - 44, - 43.5, - 43.1, - 42.9, - 42.7, - 42.4, - 42, - 41.5, - 41, - 40.5, - 40.2, - 40, - 39.8, - 39.6, - 39.3, - 38.7, - 38, - 37.7, - 37.1, - 36.5, - 36.1, - 35.7, - 35.2, - 34.7, - 34.2, - 33.8, - 33.4, - 32.8, - 32.3, - 31.7, - 31, - 30.4, - 29.9, - 29.4, - 28.8, - 28.2, - 27.5, - 27, - 26.5, - 25.9, - 25.4, - 25, - 24.5, - 24, - 23.7, - 23.4, - 23.1, - 22.7, - 22.2, - 21.6, - 21.1, - 20.9, - 20.7, - 20.6, - 20.3, - 20.1, - 19.8, - 19.6, - 19.4, - 19, - 18.7, - 18.5, - 18.3, - 18.5, - 18.5, - 18.5, - 18.4, - 17.7, - 17.3, - 17.3, - 17.1, - 17, - 16.8, - 16.5, - 15.7, - 15.1, - 14.7, - 13.9, - 12.9, - 11.7, - 10.7, - 9.8, - 8.9, - 8.1, - 7.6, - 7, - 6.3, - 5.6, - 5.1, - 4.6, - 4.4, - 4.8, - 5.6, - 6.1, - 6.7, - 7.1, - 7.1, - 6.9, - 6.9, - 6.8, - 6.6, - 6.5, - 6.3, - 6.1, - 5.6, - 4.7, - 3.7, - 2.9, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.5, - 2.4, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.4, - 2.3, - 2.3, - 2.3, - 2.5, - 3.1, - 3.4, - 3.6, - 3.9, - 4, - 4.2, - 4.4, - 4.7, - 5, - 5.4, - 5.8, - 6.4, - 7, - 7.4, - 7.7, - 8, - 8.3, - 8.7, - 9.1, - 9.3, - 9.5, - 9.5, - 9.5, - 9.5, - 9.5, - 9.6, - 9.7, - 9.7, - 9.7, - 9.8, - 9.9, - 9.9, - 9.8, - 9.8, - 9.8, - 9.9, - 9.9, - 9.9, - 10, - 10, - 10.1, - 10.2, - 10.3, - 10.3, - 10.3, - 10.4, - 10.4, - 10.4, - 10.4, - 10.5, - 10.4, - 10.5, - 10.6, - 10.7, - 10.9, - 11, - 11, - 11.1, - 11.2, - 11.3, - 11.5, - 11.6, - 11.7, - 11.7, - 11.7, - 11.7, - 11.8, - 11.9, - 12, - 12, - 11.9, - 11.6, - 11.5, - 11.6, - 11.4, - 11.3, - 11.3, - 11.4, - 11.3, - 11.2, - 11.2, - 11.1, - 11.1, - 11.1, - 11, - 10.9, - 10.6, - 9.4, - 9, - 9.2, - 9.8, - 10.6, - 10.9, - 11, - 11.1, - 11.1, - 11.3, - 11.4, - 11.4, - 11.5, - 54.5, - 54.1, - 53.8, - 53.4, - 52.9, - 52.5, - 52.2, - 51.8, - 51.5, - 51.1, - 50.7, - 50.7, - 50.4, - 50.2, - 49.7, - 49, - 48.8, - 48.8, - 48.7, - 48.4, - 47.9, - 47.6, - 47.2, - 46.9, - 46.5, - 46.2, - 46, - 45.7, - 45, - 44.7, - 44.3, - 43.8, - 43.4, - 43.2, - 42.9, - 42.6, - 42.1, - 41.7, - 41.2, - 40.9, - 40.5, - 40.2, - 40.1, - 39.9, - 39.7, - 39.3, - 38.8, - 38, - 37.5, - 36.9, - 36.5, - 36.1, - 35.5, - 35.2, - 34.7, - 34.2, - 33.7, - 33.3, - 32.7, - 32.2, - 31.6, - 31, - 30.3, - 29.8, - 29.4, - 28.9, - 28.3, - 27.6, - 27, - 26.3, - 25.7, - 25.1, - 24.7, - 24.4, - 24, - 23.6, - 23.3, - 22.8, - 22.4, - 21.9, - 21.5, - 21.2, - 20.9, - 20.7, - 20.5, - 20.3, - 20.1, - 19.7, - 19.4, - 19.1, - 18.9, - 18.8, - 18.4, - 18.2, - 18.5, - 18.5, - 18.4, - 18.2, - 17.3, - 16.7, - 16.7, - 16.7, - 16.5, - 16.4, - 16.1, - 15.5, - 14.9, - 14.4, - 13.7, - 12.9, - 11.9, - 10.8, - 9.8, - 9, - 8.1, - 7.4, - 6.8, - 6.2, - 5.6, - 5, - 4.5, - 4.2, - 4.5, - 5.4, - 5.7, - 6.2, - 6.6, - 6.8, - 6.7, - 6.7, - 6.6, - 6.5, - 6.5, - 6.4, - 6.2, - 5.8, - 5.4, - 4.5, - 3.3, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.3, - 2.4, - 2.5, - 2.3, - 2.2, - 2.3, - 2.5, - 2.7, - 3.1, - 3.3, - 3.5, - 3.7, - 3.9, - 3.9, - 4.2, - 4.5, - 4.9, - 5.3, - 5.6, - 5.9, - 6.3, - 6.9, - 7.2, - 7.5, - 7.8, - 8.2, - 8.7, - 9, - 9.3, - 9.4, - 9.4, - 9.5, - 9.5, - 9.6, - 9.8, - 9.7, - 9.8, - 9.9, - 9.9, - 10, - 10, - 10.1, - 10.1, - 10.1, - 10.1, - 10.2, - 10.2, - 10.2, - 10.3, - 10.3, - 10.4, - 10.4, - 10.5, - 10.5, - 10.5, - 10.4, - 10.4, - 10.5, - 10.5, - 10.5, - 10.6, - 10.7, - 10.8, - 10.9, - 11, - 11, - 11.1, - 11.3, - 11.4, - 11.5, - 11.6, - 11.6, - 11.6, - 11.7, - 11.8, - 12, - 12, - 12, - 11.7, - 11.5, - 11.5, - 11.5, - 11.4, - 11.3, - 11.3, - 11.3, - 11.3, - 11.2, - 11.2, - 11.1, - 11.1, - 11.1, - 11, - 10.6, - 10.2, - 9, - 8.9, - 9.1, - 9.9, - 10.5, - 10.8, - 11, - 11.1, - 11.1, - 11.2, - 11.3, - 11.3, - 11.4, - 54.5, - 54.2, - 53.7, - 53.3, - 52.8, - 52.3, - 52, - 51.6, - 51.3, - 50.9, - 50.7, - 50.6, - 50.3, - 50.1, - 49.7, - 49, - 48.8, - 48.7, - 48.5, - 48.3, - 47.8, - 47.4, - 46.8, - 46.5, - 46.3, - 46, - 45.6, - 45.2, - 44.8, - 44.3, - 44, - 43.6, - 43.3, - 43, - 42.6, - 42.3, - 41.8, - 41.5, - 41.2, - 40.9, - 40.6, - 40.3, - 40.1, - 39.9, - 39.5, - 39.1, - 38.7, - 38.1, - 37.4, - 36.9, - 36.3, - 35.8, - 35.5, - 35.1, - 34.5, - 34.2, - 33.6, - 33, - 32.6, - 32.1, - 31.5, - 30.8, - 30.2, - 29.7, - 29.2, - 28.7, - 28.1, - 27.5, - 26.9, - 26.1, - 25.6, - 25.1, - 24.8, - 24.4, - 24, - 23.5, - 23, - 22.6, - 22.2, - 21.9, - 21.6, - 21.3, - 20.9, - 20.6, - 20.3, - 20.1, - 19.8, - 19.6, - 19.4, - 19.1, - 19, - 18.8, - 18.4, - 18.1, - 18.4, - 18.5, - 18.4, - 17.9, - 16.6, - 16, - 15.7, - 16.1, - 15.9, - 15.8, - 15.5, - 15.1, - 14.7, - 14.1, - 13.4, - 12.6, - 11.8, - 10.8, - 9.8, - 8.9, - 8, - 7.2, - 6.7, - 6.1, - 5.6, - 5.1, - 4.5, - 4, - 4.1, - 4.9, - 5.4, - 5.8, - 6.1, - 6.3, - 6.3, - 6.3, - 6.4, - 6.3, - 6.3, - 6.3, - 6.1, - 5.9, - 5.6, - 4.8, - 3.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.3, - 2.3, - 2.5, - 2.5, - 2.3, - 2.3, - 2.2, - 2.4, - 2.7, - 3, - 3.1, - 3.3, - 3.4, - 3.6, - 3.8, - 4.1, - 4.4, - 4.8, - 5.1, - 5.3, - 5.6, - 5.8, - 6.3, - 6.6, - 6.9, - 7.2, - 7.6, - 7.9, - 8.3, - 8.7, - 9, - 9.2, - 9.4, - 9.4, - 9.6, - 9.6, - 9.7, - 9.9, - 10, - 10, - 10, - 10.1, - 10.1, - 10.1, - 10.2, - 10.2, - 10.3, - 10.4, - 10.4, - 10.4, - 10.5, - 10.6, - 10.5, - 10.6, - 10.6, - 10.6, - 10.6, - 10.6, - 10.6, - 10.6, - 10.5, - 10.6, - 10.7, - 10.7, - 10.8, - 10.9, - 11, - 11, - 11.2, - 11.2, - 11.3, - 11.4, - 11.4, - 11.6, - 11.7, - 11.8, - 11.9, - 11.9, - 11.8, - 11.5, - 11.4, - 11.4, - 11.4, - 11.2, - 11.2, - 11.3, - 11.3, - 11.3, - 11.2, - 11.2, - 11.1, - 11.1, - 11, - 10.8, - 10.3, - 9.4, - 8.9, - 9, - 9.5, - 10.1, - 10.5, - 10.8, - 11, - 11, - 11.1, - 11.2, - 11.3, - 11.3, - 11.4, - 54.4, - 54.1, - 53.7, - 53.2, - 52.6, - 52, - 51.8, - 51.4, - 51.2, - 50.8, - 50.5, - 50.4, - 50.2, - 50, - 49.5, - 48.7, - 48.5, - 48.4, - 48.2, - 48, - 47.7, - 47.2, - 46.7, - 46.3, - 46.1, - 45.7, - 45.2, - 44.8, - 44.4, - 44, - 43.7, - 43.4, - 43.1, - 42.8, - 42.5, - 42.1, - 41.7, - 41.4, - 41.2, - 40.9, - 40.6, - 40.5, - 40.3, - 40, - 39.7, - 39, - 38.6, - 38, - 37.4, - 36.9, - 36.2, - 35.8, - 35.4, - 35, - 34.5, - 34.1, - 33.7, - 33.2, - 32.7, - 32.1, - 31.5, - 30.8, - 30.2, - 29.7, - 29.2, - 28.6, - 28.1, - 27.5, - 26.7, - 26.1, - 25.7, - 25.3, - 24.8, - 24.3, - 24, - 23.5, - 22.9, - 22.5, - 22.2, - 22.1, - 21.8, - 21.5, - 21.2, - 20.8, - 20.4, - 19.9, - 19.7, - 19.5, - 19.3, - 19.1, - 19, - 18.8, - 18.2, - 18.1, - 18.4, - 18.5, - 18.4, - 18.1, - 16.9, - 16, - 15.3, - 15.1, - 14.9, - 14.9, - 14.8, - 14.6, - 14.3, - 13.8, - 13.1, - 12.3, - 11.5, - 10.7, - 9.7, - 8.8, - 7.9, - 7.1, - 6.5, - 6, - 5.5, - 5, - 4.6, - 4, - 3.9, - 4.5, - 5, - 5.4, - 5.6, - 5.7, - 5.9, - 5.9, - 5.9, - 5.9, - 5.9, - 6, - 5.9, - 5.7, - 5.4, - 4.9, - 3.6, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.4, - 2.3, - 2.4, - 2.4, - 2.3, - 2.3, - 2.5, - 2.4, - 2.3, - 2.2, - 2.3, - 2.4, - 2.7, - 2.9, - 3, - 3.1, - 3.2, - 3.4, - 3.7, - 3.9, - 4.2, - 4.6, - 4.9, - 5.1, - 5.4, - 5.5, - 5.7, - 6, - 6.2, - 6.4, - 6.7, - 7.2, - 7.6, - 7.9, - 8.2, - 8.6, - 9, - 9.2, - 9.4, - 9.5, - 9.7, - 9.8, - 9.9, - 9.9, - 10, - 10, - 10.1, - 10.1, - 10.1, - 10.2, - 10.4, - 10.4, - 10.5, - 10.5, - 10.5, - 10.6, - 10.6, - 10.7, - 10.7, - 10.8, - 10.7, - 10.7, - 10.7, - 10.6, - 10.6, - 10.6, - 10.7, - 10.8, - 10.8, - 10.8, - 10.8, - 11, - 11, - 11.1, - 11.1, - 11.2, - 11.3, - 11.5, - 11.6, - 11.7, - 11.7, - 11.7, - 11.6, - 11.4, - 11.4, - 11.3, - 11.3, - 11.2, - 11.2, - 11.3, - 11.3, - 11.1, - 11, - 11.1, - 11, - 11, - 11, - 10.7, - 9.8, - 9, - 8.9, - 9.4, - 10, - 10.4, - 10.7, - 10.9, - 11, - 11, - 11, - 11.2, - 11.3, - 11.2, - 11.4, - 54.5, - 54, - 53.7, - 53.2, - 52.5, - 52, - 51.7, - 51.4, - 51, - 50.7, - 50.4, - 50.2, - 50, - 49.7, - 49.3, - 48.7, - 48.3, - 48.2, - 48, - 47.7, - 47.4, - 46.9, - 46.4, - 46.1, - 45.8, - 45.4, - 44.9, - 44.4, - 44.1, - 43.7, - 43.6, - 43.2, - 42.9, - 42.6, - 42.3, - 41.9, - 41.7, - 41.5, - 41.4, - 41.1, - 40.8, - 40.6, - 40.3, - 40, - 39.6, - 39, - 38.5, - 37.8, - 37.3, - 36.8, - 36.3, - 35.8, - 35.4, - 35, - 34.6, - 34.2, - 33.7, - 33.2, - 32.8, - 32.1, - 31.6, - 31, - 30.4, - 29.7, - 29.1, - 28.6, - 28.2, - 27.6, - 26.9, - 26.3, - 25.7, - 25.2, - 24.6, - 24.2, - 23.8, - 23.5, - 23.1, - 22.6, - 22.3, - 22.1, - 21.9, - 21.6, - 21.3, - 21, - 20.6, - 19.9, - 19.6, - 19.5, - 19.3, - 19.2, - 18.9, - 18.6, - 18.2, - 18.2, - 18.4, - 18.5, - 18.4, - 18, - 17, - 16.3, - 15.9, - 15.3, - 14.7, - 14.3, - 14.3, - 14.2, - 13.8, - 13.3, - 12.7, - 12, - 11.2, - 10.4, - 9.5, - 8.5, - 7.7, - 6.9, - 6.3, - 5.9, - 5.3, - 4.6, - 4, - 3.8, - 3.7, - 4.2, - 4.7, - 5.1, - 5.3, - 5.4, - 5.5, - 5.5, - 5.5, - 5.4, - 5.5, - 5.5, - 5.5, - 5.2, - 5.1, - 4.9, - 3.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.4, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.5, - 2.5, - 2.4, - 2.3, - 2.2, - 2.3, - 2.3, - 2.6, - 2.7, - 2.9, - 3.1, - 3.1, - 3.3, - 3.5, - 3.7, - 4, - 4.5, - 4.7, - 4.9, - 5.1, - 5.2, - 5.3, - 5.5, - 5.6, - 5.8, - 6.1, - 6.5, - 6.8, - 7.2, - 7.6, - 8, - 8.3, - 8.5, - 8.7, - 9.1, - 9.2, - 9.5, - 9.7, - 9.8, - 9.8, - 10, - 10.1, - 10.1, - 10.1, - 10.1, - 10.3, - 10.4, - 10.5, - 10.5, - 10.6, - 10.8, - 10.7, - 10.8, - 10.8, - 10.9, - 10.9, - 10.8, - 10.8, - 10.8, - 10.7, - 10.7, - 10.7, - 10.7, - 10.6, - 10.7, - 10.7, - 10.9, - 10.9, - 11, - 11.1, - 11.1, - 11.2, - 11.4, - 11.4, - 11.4, - 11.5, - 11.6, - 11.5, - 11.4, - 11.3, - 11.2, - 11.2, - 11.1, - 11.2, - 11.2, - 11.2, - 11.1, - 10.9, - 11, - 11.1, - 10.8, - 10.8, - 10.6, - 9.5, - 8.8, - 8.7, - 9.4, - 10.1, - 10.5, - 10.6, - 10.7, - 10.9, - 10.9, - 10.9, - 11.1, - 11.1, - 11.3, - 11.3, - 54.5, - 54.1, - 53.7, - 53.1, - 52.6, - 52.1, - 51.8, - 51.2, - 50.9, - 50.5, - 50.2, - 49.9, - 49.7, - 49.4, - 49, - 48.6, - 48.1, - 48, - 47.8, - 47.5, - 47.1, - 46.5, - 46.1, - 45.8, - 45.6, - 45.2, - 44.7, - 44.2, - 44, - 43.8, - 43.6, - 43.2, - 42.9, - 42.5, - 42.2, - 42.1, - 41.8, - 41.7, - 41.4, - 41.1, - 40.9, - 40.5, - 40.2, - 39.9, - 39.5, - 39, - 38.4, - 37.7, - 37.2, - 36.7, - 36.2, - 35.8, - 35.5, - 35.1, - 34.6, - 34.1, - 33.6, - 33.2, - 32.7, - 32.1, - 31.5, - 31, - 30.4, - 29.9, - 29.2, - 28.7, - 28.2, - 27.6, - 27, - 26.3, - 25.7, - 25.1, - 24.5, - 24.1, - 23.5, - 23.3, - 23.1, - 22.6, - 22.3, - 22, - 21.8, - 21.5, - 21.2, - 21, - 20.4, - 19.8, - 19.5, - 19.5, - 19.3, - 19.1, - 18.9, - 18.7, - 18.2, - 18.2, - 18.4, - 18.5, - 18.4, - 18.3, - 17.3, - 16.7, - 16.2, - 15.7, - 15, - 14.2, - 13.7, - 13.3, - 13.2, - 12.8, - 12.3, - 11.6, - 10.9, - 10.1, - 9.2, - 8.4, - 7.6, - 6.8, - 6.2, - 5.8, - 5.1, - 4.2, - 3.7, - 3.4, - 3.4, - 4, - 4.5, - 4.8, - 4.9, - 4.9, - 4.9, - 4.9, - 4.8, - 4.7, - 4.7, - 4.7, - 4.7, - 4.6, - 4.4, - 4.3, - 3.6, - 2.7, - 2.6, - 2.5, - 2.5, - 2.4, - 2.3, - 2.3, - 2.4, - 2.3, - 2.3, - 2.5, - 2.4, - 2.3, - 2.3, - 2.2, - 2.3, - 2.3, - 2.3, - 2.5, - 2.8, - 3, - 3, - 3.2, - 3.3, - 3.6, - 3.9, - 4.2, - 4.5, - 4.8, - 4.9, - 5.1, - 5.2, - 5.2, - 5.3, - 5.5, - 5.7, - 6, - 6.2, - 6.5, - 6.8, - 7.3, - 7.7, - 7.9, - 8.2, - 8.4, - 8.6, - 8.8, - 9.1, - 9.3, - 9.4, - 9.8, - 10, - 10, - 10.1, - 10.1, - 10.3, - 10.3, - 10.4, - 10.5, - 10.7, - 10.7, - 10.7, - 10.8, - 10.9, - 10.9, - 11, - 11, - 10.9, - 10.8, - 10.8, - 10.7, - 10.6, - 10.6, - 10.6, - 10.6, - 10.7, - 10.8, - 10.8, - 10.9, - 11, - 11.1, - 11.1, - 11.3, - 11.2, - 11.2, - 11.3, - 11.5, - 11.5, - 11.4, - 11.3, - 11.1, - 11.1, - 11.1, - 11.1, - 11.1, - 11, - 11, - 11, - 10.9, - 10.8, - 10.7, - 10.7, - 10.4, - 9.4, - 8.8, - 8.6, - 9.1, - 9.7, - 10.3, - 10.5, - 10.7, - 10.8, - 10.9, - 10.9, - 10.9, - 11, - 11.1, - 11.2, - 54.4, - 54, - 53.6, - 53.1, - 52.6, - 52.2, - 51.7, - 51.1, - 50.7, - 50.4, - 50, - 49.6, - 49.6, - 49.2, - 48.8, - 48.4, - 48.1, - 47.7, - 47.4, - 47.1, - 46.7, - 46.2, - 45.8, - 45.6, - 45.5, - 45.1, - 44.7, - 44.3, - 44.2, - 43.9, - 43.7, - 43.4, - 43, - 42.7, - 42.4, - 42.2, - 41.9, - 41.7, - 41.4, - 41.2, - 40.8, - 40.5, - 40.1, - 39.8, - 39.4, - 38.8, - 38.2, - 37.5, - 37, - 36.5, - 36.1, - 35.8, - 35.3, - 35, - 34.6, - 34, - 33.6, - 33.1, - 32.5, - 32, - 31.5, - 30.9, - 30.3, - 30, - 29.2, - 28.6, - 28.1, - 27.5, - 27.1, - 26.4, - 25.6, - 25, - 24.5, - 24, - 23.6, - 23.2, - 23, - 22.8, - 22.4, - 22, - 21.7, - 21.3, - 21.1, - 20.8, - 20.2, - 19.7, - 19.5, - 19.4, - 19.3, - 19.2, - 19, - 18.7, - 18.3, - 18.2, - 18.4, - 18.5, - 18.4, - 18.4, - 17.5, - 16.9, - 16.5, - 16.1, - 15.2, - 14.4, - 13.6, - 13.1, - 12.7, - 12.3, - 11.9, - 11.1, - 10.5, - 9.7, - 8.9, - 8.2, - 7.4, - 6.6, - 6.1, - 5.6, - 5.2, - 4.3, - 3.6, - 3.2, - 3.4, - 3.9, - 4.2, - 4.3, - 4.3, - 4.2, - 4.1, - 4.1, - 4, - 4, - 3.8, - 3.8, - 3.8, - 3.9, - 3.7, - 3.6, - 3.4, - 2.7, - 2.6, - 2.5, - 2.4, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.4, - 2.5, - 2.4, - 2.3, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.5, - 2.7, - 2.9, - 2.9, - 3.1, - 3.2, - 3.3, - 3.5, - 4, - 4.3, - 4.5, - 4.8, - 4.9, - 5, - 5.1, - 5.1, - 5.1, - 5.3, - 5.5, - 5.7, - 5.9, - 6.2, - 6.6, - 6.9, - 7.1, - 7.3, - 7.6, - 7.9, - 8.1, - 8.4, - 8.6, - 8.8, - 9.1, - 9.3, - 9.5, - 9.7, - 9.9, - 9.9, - 10, - 10.2, - 10.3, - 10.5, - 10.5, - 10.7, - 10.8, - 10.9, - 10.9, - 11, - 11.1, - 11, - 10.9, - 10.8, - 10.6, - 10.5, - 10.5, - 10.5, - 10.6, - 10.6, - 10.7, - 10.9, - 10.8, - 10.9, - 10.9, - 10.9, - 11, - 11, - 11.1, - 11.2, - 11.3, - 11.4, - 11.3, - 11.3, - 11.1, - 11, - 11, - 11, - 11, - 10.9, - 10.9, - 10.9, - 10.9, - 10.8, - 10.6, - 10.5, - 9.7, - 8.8, - 8.6, - 8.8, - 9.1, - 9.3, - 9.9, - 10.4, - 10.6, - 10.7, - 10.8, - 10.9, - 10.9, - 11, - 11.1, - 11.2, - 54.3, - 53.8, - 53.4, - 52.9, - 52.5, - 52.1, - 51.5, - 51, - 50.6, - 50.2, - 49.9, - 49.5, - 49.4, - 49.1, - 48.7, - 48.4, - 47.9, - 47.6, - 47.3, - 46.9, - 46.4, - 46, - 45.6, - 45.5, - 45.4, - 45.1, - 44.7, - 44.4, - 44.3, - 44, - 43.8, - 43.5, - 43.1, - 42.9, - 42.5, - 42.4, - 42.1, - 41.7, - 41.5, - 41.1, - 40.8, - 40.4, - 40, - 39.6, - 39.1, - 38.7, - 38.1, - 37.6, - 37, - 36.5, - 36, - 35.6, - 35.3, - 34.9, - 34.5, - 33.9, - 33.5, - 33, - 32.5, - 32.1, - 31.4, - 30.8, - 30.2, - 29.9, - 29.3, - 28.7, - 28.2, - 27.5, - 27.1, - 26.4, - 25.6, - 25, - 24.4, - 23.9, - 23.5, - 23.2, - 23, - 22.7, - 22.3, - 21.9, - 21.5, - 21.1, - 20.9, - 20.7, - 20.2, - 19.8, - 19.5, - 19.4, - 19.3, - 19.1, - 19, - 18.7, - 18.2, - 18.3, - 18.4, - 18.5, - 18.4, - 18.3, - 17.7, - 17.2, - 16.8, - 16.2, - 15.3, - 14.5, - 13.7, - 13.2, - 12.5, - 11.9, - 11.3, - 10.8, - 10.2, - 9.5, - 8.7, - 8.1, - 7.3, - 6.6, - 6, - 5.5, - 4.9, - 4.1, - 3.4, - 3.1, - 3.3, - 3.5, - 3.7, - 3.7, - 3.7, - 3.6, - 3.5, - 3.4, - 3.5, - 3.4, - 3.1, - 3.2, - 3.2, - 3.3, - 3.3, - 3.1, - 3.1, - 2.7, - 2.6, - 2.5, - 2.5, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.6, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.4, - 3.7, - 4.1, - 4.3, - 4.6, - 4.8, - 4.8, - 4.9, - 4.9, - 4.9, - 5, - 5.1, - 5.3, - 5.4, - 5.7, - 5.9, - 6.2, - 6.5, - 6.6, - 6.8, - 7.1, - 7.4, - 7.6, - 7.8, - 8.1, - 8.4, - 8.6, - 8.8, - 9, - 9.1, - 9.3, - 9.5, - 9.6, - 9.7, - 10, - 10.1, - 10.4, - 10.6, - 10.7, - 10.9, - 10.9, - 11, - 10.9, - 10.8, - 10.7, - 10.5, - 10.4, - 10.4, - 10.4, - 10.5, - 10.5, - 10.6, - 10.7, - 10.7, - 10.8, - 10.8, - 10.9, - 10.9, - 11, - 11, - 11.1, - 11.1, - 11.2, - 11.2, - 11.2, - 11.1, - 10.9, - 10.9, - 10.9, - 11, - 10.9, - 10.9, - 10.9, - 10.8, - 10.8, - 10.7, - 10.6, - 9.3, - 8.4, - 8.3, - 8.7, - 9.3, - 9.7, - 10.1, - 10.3, - 10.5, - 10.6, - 10.7, - 10.8, - 10.9, - 11, - 11.1, - 11.1, - 54.1, - 53.7, - 53.2, - 52.9, - 52.5, - 52, - 51.4, - 50.9, - 50.5, - 49.9, - 49.7, - 49.4, - 49.1, - 48.8, - 48.6, - 48.3, - 47.9, - 47.5, - 47.2, - 46.9, - 46.3, - 45.9, - 45.8, - 45.6, - 45.4, - 45, - 44.7, - 44.6, - 44.4, - 44, - 43.8, - 43.6, - 43.3, - 42.9, - 42.6, - 42.4, - 42.2, - 41.8, - 41.4, - 41.1, - 40.8, - 40.4, - 40, - 39.5, - 39, - 38.6, - 38, - 37.5, - 37, - 36.4, - 35.9, - 35.6, - 35.3, - 34.8, - 34.5, - 34, - 33.5, - 33.1, - 32.6, - 32, - 31.5, - 30.8, - 30.2, - 29.9, - 29.3, - 28.7, - 28.2, - 27.5, - 27, - 26.4, - 25.6, - 24.9, - 24.3, - 23.8, - 23.4, - 23.1, - 23, - 22.6, - 22.3, - 21.9, - 21.5, - 21.1, - 20.8, - 20.4, - 20, - 19.8, - 19.6, - 19.3, - 19.2, - 19.1, - 18.9, - 18.7, - 18.1, - 18.3, - 18.4, - 18.5, - 18.4, - 18.3, - 17.7, - 17.2, - 16.7, - 16.1, - 15.4, - 14.6, - 13.9, - 13.4, - 12.3, - 11.7, - 11.2, - 10.6, - 10.1, - 9.5, - 8.7, - 8.1, - 7.3, - 6.6, - 5.9, - 5.4, - 4.6, - 3.8, - 3.3, - 3, - 3, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3, - 2.9, - 2.9, - 2.9, - 2.7, - 2.7, - 2.6, - 2.7, - 2.8, - 2.8, - 2.9, - 2.8, - 2.6, - 2.5, - 2.4, - 2.4, - 2.3, - 2.3, - 2.2, - 2.4, - 2.4, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.6, - 2.8, - 3, - 3.1, - 3.1, - 3.3, - 3.5, - 3.9, - 4.1, - 4.4, - 4.5, - 4.5, - 4.6, - 4.7, - 4.7, - 4.8, - 4.9, - 5, - 5.1, - 5.3, - 5.5, - 5.7, - 5.8, - 6, - 6.1, - 6.4, - 6.6, - 7, - 7.2, - 7.3, - 7.5, - 7.8, - 7.9, - 8.3, - 8.7, - 8.9, - 9, - 9.1, - 9.1, - 9.3, - 9.6, - 9.8, - 10.1, - 10.3, - 10.4, - 10.6, - 10.8, - 10.8, - 10.7, - 10.7, - 10.6, - 10.5, - 10.5, - 10.5, - 10.5, - 10.5, - 10.6, - 10.6, - 10.6, - 10.7, - 10.7, - 10.7, - 10.7, - 10.8, - 10.9, - 10.9, - 10.9, - 11, - 11.1, - 11, - 11, - 10.8, - 10.8, - 10.9, - 10.9, - 10.8, - 10.8, - 10.8, - 10.8, - 10.8, - 10.7, - 10.4, - 9.5, - 8.3, - 8.3, - 8.6, - 9.2, - 9.6, - 10, - 10.2, - 10.2, - 10.4, - 10.6, - 10.8, - 10.9, - 10.9, - 11, - 11, - 54.2, - 53.8, - 53.2, - 52.8, - 52.5, - 51.9, - 51.3, - 50.8, - 50.3, - 49.8, - 49.5, - 49.3, - 48.9, - 48.6, - 48.5, - 48.4, - 48, - 47.5, - 47.2, - 47.1, - 46.4, - 46.1, - 45.9, - 45.5, - 45.2, - 45, - 44.8, - 44.7, - 44.5, - 44.1, - 43.8, - 43.6, - 43.3, - 42.9, - 42.6, - 42.3, - 42.1, - 41.7, - 41.4, - 41.1, - 40.8, - 40.4, - 40, - 39.5, - 39, - 38.6, - 37.9, - 37.5, - 37, - 36.4, - 35.9, - 35.6, - 35.3, - 34.9, - 34.5, - 34, - 33.4, - 33, - 32.6, - 32.1, - 31.5, - 30.9, - 30.3, - 29.9, - 29.3, - 28.8, - 28.2, - 27.5, - 27, - 26.5, - 25.6, - 24.8, - 24.2, - 23.8, - 23.4, - 23.1, - 22.9, - 22.7, - 22.3, - 21.8, - 21.5, - 21.1, - 20.5, - 20.1, - 19.9, - 19.8, - 19.7, - 19.4, - 19.1, - 19, - 18.9, - 18.5, - 18.1, - 18.3, - 18.4, - 18.5, - 18.4, - 18.3, - 17.6, - 17, - 16.5, - 15.9, - 15.4, - 14.6, - 13.7, - 13.1, - 12.2, - 11.4, - 10.9, - 10.4, - 9.9, - 9.4, - 8.7, - 7.9, - 7, - 6.3, - 5.8, - 5.3, - 4.4, - 3.7, - 3.2, - 3, - 2.8, - 2.9, - 3, - 2.9, - 2.9, - 2.8, - 2.7, - 2.7, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.6, - 2.8, - 2.6, - 2.5, - 2.4, - 2.4, - 2.3, - 2.3, - 2.2, - 2.4, - 2.5, - 2.4, - 2.3, - 2.2, - 2.1, - 2.2, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.6, - 2.9, - 3, - 3.2, - 3.3, - 3.5, - 3.7, - 3.9, - 4.1, - 4.4, - 4.4, - 4.5, - 4.5, - 4.5, - 4.6, - 4.8, - 4.9, - 5, - 5, - 5.2, - 5.4, - 5.5, - 5.6, - 5.7, - 5.8, - 6.1, - 6.4, - 6.5, - 6.6, - 6.8, - 7.1, - 7.3, - 7.5, - 7.9, - 8.2, - 8.4, - 8.6, - 8.7, - 8.8, - 9, - 9.3, - 9.6, - 9.7, - 10, - 10.3, - 10.4, - 10.6, - 10.6, - 10.6, - 10.5, - 10.4, - 10.4, - 10.4, - 10.4, - 10.5, - 10.4, - 10.4, - 10.5, - 10.5, - 10.5, - 10.5, - 10.6, - 10.6, - 10.7, - 10.7, - 10.8, - 10.9, - 11, - 11, - 10.9, - 10.8, - 10.8, - 10.8, - 10.8, - 10.7, - 10.7, - 10.8, - 11, - 10.8, - 10.4, - 9.8, - 8.8, - 8.2, - 8.3, - 8.4, - 8.7, - 9, - 9.5, - 9.8, - 10, - 10.3, - 10.5, - 10.7, - 10.8, - 10.8, - 10.8, - 10.9, - 54.3, - 53.8, - 53.4, - 52.9, - 52.4, - 51.8, - 51.2, - 50.7, - 50.3, - 49.8, - 49.4, - 49.2, - 48.9, - 48.6, - 48.5, - 48.4, - 48, - 47.5, - 47.2, - 46.9, - 46.6, - 46.3, - 46.1, - 45.8, - 45.4, - 45.1, - 44.9, - 44.8, - 44.5, - 44.2, - 44, - 43.7, - 43.3, - 43, - 42.5, - 42.2, - 42, - 41.7, - 41.4, - 41.1, - 40.8, - 40.4, - 40, - 39.6, - 39.1, - 38.6, - 37.9, - 37.4, - 36.8, - 36.2, - 35.8, - 35.5, - 35.2, - 35, - 34.4, - 34, - 33.5, - 33, - 32.6, - 32.1, - 31.5, - 31, - 30.4, - 29.9, - 29.4, - 28.9, - 28.4, - 27.6, - 27.1, - 26.4, - 25.6, - 24.8, - 24.1, - 23.8, - 23.6, - 23.2, - 22.9, - 22.6, - 22.2, - 21.8, - 21.5, - 20.9, - 20.4, - 20.1, - 20.1, - 19.9, - 19.7, - 19.3, - 19.1, - 18.9, - 18.7, - 18.3, - 18.1, - 18.3, - 18.4, - 18.5, - 18.4, - 18.4, - 17.7, - 17, - 16.2, - 15.6, - 15.1, - 14.3, - 13.5, - 12.8, - 12.1, - 11.5, - 10.8, - 10.3, - 9.7, - 9.3, - 8.5, - 7.7, - 6.9, - 6.3, - 5.8, - 5.2, - 4.4, - 3.7, - 3.3, - 3, - 2.9, - 2.7, - 2.8, - 2.8, - 2.7, - 2.6, - 2.5, - 2.4, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.6, - 2.7, - 2.5, - 2.4, - 2.3, - 2.4, - 2.2, - 2.2, - 2.4, - 2.4, - 2.4, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.4, - 2.8, - 2.9, - 3, - 3.1, - 3.3, - 3.6, - 3.7, - 4.1, - 4.2, - 4.3, - 4.4, - 4.3, - 4.3, - 4.5, - 4.7, - 4.7, - 4.8, - 4.9, - 5, - 5.2, - 5.3, - 5.4, - 5.4, - 5.5, - 5.6, - 5.8, - 5.9, - 6, - 6.2, - 6.5, - 6.7, - 7, - 7.3, - 7.5, - 7.8, - 7.9, - 8, - 8.2, - 8.5, - 8.9, - 9.1, - 9.3, - 9.6, - 9.9, - 10.1, - 10.4, - 10.5, - 10.5, - 10.4, - 10.4, - 10.3, - 10.3, - 10.3, - 10.3, - 10.4, - 10.3, - 10.3, - 10.3, - 10.4, - 10.4, - 10.5, - 10.5, - 10.6, - 10.6, - 10.6, - 10.7, - 10.7, - 10.8, - 10.9, - 10.7, - 10.7, - 10.7, - 10.6, - 10.5, - 10.5, - 10.6, - 10.7, - 10.3, - 9.7, - 8.8, - 8.2, - 8.2, - 8.3, - 8.9, - 9.4, - 9.5, - 9.7, - 9.6, - 9.7, - 10.1, - 10.4, - 10.4, - 10.6, - 10.7, - 10.8, - 10.9, - 54.4, - 54, - 53.4, - 52.8, - 52.2, - 51.7, - 51.1, - 50.7, - 50.2, - 49.7, - 49.4, - 49.1, - 48.9, - 48.7, - 48.5, - 48.4, - 48.1, - 47.6, - 47.3, - 47, - 46.8, - 46.6, - 46.3, - 46, - 45.7, - 45.3, - 45.1, - 44.8, - 44.6, - 44.2, - 44, - 43.6, - 43.3, - 43, - 42.6, - 42.2, - 42, - 41.7, - 41.5, - 41.2, - 40.8, - 40.4, - 40, - 39.6, - 39.1, - 38.7, - 37.9, - 37.3, - 36.8, - 36.3, - 35.8, - 35.4, - 35, - 34.8, - 34.4, - 34, - 33.5, - 33, - 32.5, - 32, - 31.6, - 31, - 30.5, - 30, - 29.3, - 28.8, - 28.4, - 27.7, - 27, - 26.3, - 25.5, - 24.7, - 24.2, - 24, - 23.6, - 23.2, - 22.8, - 22.4, - 22, - 21.6, - 21.2, - 20.6, - 20.4, - 20.2, - 20.1, - 19.9, - 19.6, - 19.2, - 19.1, - 18.9, - 18.5, - 18.3, - 18.2, - 18.4, - 18.4, - 18.5, - 18.4, - 18.4, - 17.4, - 16.4, - 15.7, - 15.1, - 14.6, - 13.9, - 13.2, - 12.6, - 11.9, - 11.1, - 10.5, - 10, - 9.4, - 9, - 8.3, - 7.6, - 7, - 6.4, - 5.8, - 5, - 4.3, - 3.7, - 3.3, - 2.9, - 2.8, - 2.8, - 2.6, - 2.7, - 2.5, - 2.5, - 2.4, - 2.3, - 2.3, - 2.3, - 2.2, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.4, - 2.8, - 2.6, - 2.4, - 2.3, - 2.3, - 2.2, - 2.3, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.3, - 2.6, - 2.8, - 2.9, - 3, - 3.2, - 3.3, - 3.5, - 3.8, - 4, - 4.2, - 4.2, - 4.2, - 4.2, - 4.4, - 4.4, - 4.5, - 4.7, - 4.7, - 4.8, - 5, - 5.1, - 5.2, - 5.3, - 5.3, - 5.3, - 5.5, - 5.6, - 5.7, - 5.9, - 6.1, - 6.3, - 6.5, - 6.8, - 7, - 7.2, - 7.3, - 7.5, - 7.7, - 8.2, - 8.5, - 8.8, - 9.1, - 9.4, - 9.6, - 9.9, - 10.1, - 10.3, - 10.4, - 10.3, - 10.3, - 10.2, - 10.2, - 10.2, - 10.2, - 10.3, - 10.2, - 10.2, - 10.3, - 10.2, - 10.3, - 10.3, - 10.4, - 10.4, - 10.5, - 10.5, - 10.5, - 10.5, - 10.7, - 10.7, - 10.5, - 10.5, - 10.5, - 10.4, - 10.4, - 10.3, - 10.3, - 10, - 9.4, - 8.7, - 8.2, - 8.1, - 8.3, - 8.8, - 9.7, - 10.1, - 10.2, - 10.2, - 10, - 9.8, - 10.1, - 10.3, - 10.4, - 10.6, - 10.6, - 10.8, - 10.8, - 54.3, - 53.9, - 53.3, - 52.6, - 52.2, - 51.7, - 51.1, - 50.6, - 50.2, - 49.6, - 49.4, - 49.2, - 49.1, - 48.8, - 48.6, - 48.3, - 48, - 47.7, - 47.6, - 47.3, - 47, - 46.8, - 46.4, - 46, - 45.8, - 45.6, - 45.3, - 44.9, - 44.6, - 44.3, - 43.9, - 43.7, - 43.3, - 42.9, - 42.5, - 42.4, - 42.1, - 41.9, - 41.5, - 41.2, - 40.8, - 40.4, - 40, - 39.6, - 39.2, - 38.7, - 38, - 37.2, - 36.8, - 36.3, - 35.8, - 35.4, - 35, - 34.7, - 34.3, - 33.8, - 33.4, - 33, - 32.4, - 32.1, - 31.5, - 31, - 30.5, - 29.8, - 29.2, - 28.9, - 28.4, - 27.7, - 26.9, - 26.2, - 25.4, - 24.7, - 24.2, - 24, - 23.5, - 23.1, - 22.7, - 22.3, - 22, - 21.5, - 21.1, - 20.7, - 20.4, - 20.2, - 20.1, - 19.8, - 19.5, - 19.3, - 19.1, - 18.8, - 18.3, - 18.3, - 18.2, - 18.4, - 18.4, - 18.5, - 18.4, - 18.4, - 17.2, - 16.2, - 15.4, - 14.7, - 14, - 13.4, - 12.9, - 12.5, - 11.7, - 10.9, - 10.3, - 9.9, - 9.4, - 8.9, - 8.1, - 7.4, - 7, - 6.4, - 5.8, - 4.8, - 4.3, - 3.8, - 3.4, - 3, - 2.9, - 2.7, - 2.7, - 2.8, - 2.6, - 2.5, - 2.4, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.6, - 2.7, - 2.4, - 2.3, - 2.2, - 2.3, - 2.4, - 2.4, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.2, - 2.2, - 2.3, - 2.2, - 2.2, - 2.3, - 2.5, - 2.7, - 2.9, - 3, - 3.1, - 3.4, - 3.6, - 3.7, - 3.9, - 4, - 4.1, - 4.1, - 4.3, - 4.3, - 4.3, - 4.5, - 4.5, - 4.6, - 4.7, - 4.9, - 5, - 5, - 5.1, - 5.2, - 5.3, - 5.4, - 5.6, - 5.7, - 5.8, - 6, - 6.2, - 6.4, - 6.6, - 6.8, - 7, - 7.2, - 7.4, - 7.9, - 8.3, - 8.6, - 8.9, - 9.2, - 9.4, - 9.6, - 9.9, - 10.2, - 10.3, - 10.3, - 10.2, - 10.2, - 10.1, - 10.1, - 10.1, - 10.1, - 10, - 10.1, - 10.1, - 10.1, - 10.1, - 10.2, - 10.2, - 10.2, - 10.3, - 10.4, - 10.3, - 10.4, - 10.5, - 10.4, - 10.4, - 10.3, - 10.3, - 10.3, - 10.3, - 10.1, - 9.9, - 9.2, - 8.4, - 8.1, - 8.1, - 8.2, - 8.9, - 9.7, - 10.2, - 10.4, - 10.4, - 10.3, - 10.1, - 10, - 10, - 10.1, - 10.3, - 10.5, - 10.6, - 10.8, - 10.9, - 54.2, - 53.7, - 53.2, - 52.6, - 52.3, - 51.6, - 51.1, - 50.6, - 50.1, - 49.8, - 49.6, - 49.5, - 49.2, - 49, - 48.7, - 48.4, - 48.1, - 47.9, - 47.7, - 47.5, - 47.1, - 46.8, - 46.5, - 46.1, - 45.9, - 45.6, - 45.3, - 44.9, - 44.6, - 44.3, - 44, - 43.6, - 43.2, - 42.9, - 42.5, - 42.3, - 42.1, - 41.9, - 41.5, - 41.2, - 40.8, - 40.4, - 40.1, - 39.7, - 39.3, - 38.6, - 37.9, - 37.2, - 36.8, - 36.2, - 35.8, - 35.3, - 35, - 34.6, - 34.3, - 33.8, - 33.4, - 32.9, - 32.5, - 32, - 31.4, - 30.8, - 30.3, - 29.7, - 29.1, - 28.8, - 28.3, - 27.5, - 26.9, - 26.1, - 25.4, - 24.8, - 24.4, - 24, - 23.5, - 23, - 22.6, - 22.2, - 22, - 21.5, - 21.1, - 20.8, - 20.5, - 20.3, - 20, - 19.7, - 19.4, - 19.2, - 19.1, - 18.9, - 18.5, - 18.4, - 18.3, - 18.4, - 18.4, - 18.5, - 18.4, - 18.4, - 17.2, - 15.9, - 15.2, - 14.6, - 13.8, - 13.1, - 12.6, - 12.4, - 11.6, - 10.7, - 10.2, - 9.8, - 9.4, - 8.7, - 8.1, - 7.5, - 6.9, - 6.4, - 5.9, - 5.1, - 4.2, - 3.9, - 3.5, - 3.2, - 2.9, - 2.7, - 2.6, - 2.7, - 2.6, - 2.5, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.4, - 2.7, - 2.5, - 2.4, - 2.5, - 2.4, - 2.4, - 2.4, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.1, - 2.2, - 2.2, - 2.4, - 2.6, - 2.7, - 2.8, - 2.9, - 3.2, - 3.5, - 3.6, - 3.8, - 3.9, - 3.9, - 4, - 4.1, - 4.2, - 4.2, - 4.3, - 4.4, - 4.6, - 4.7, - 4.8, - 4.8, - 4.8, - 4.9, - 5, - 5.1, - 5.2, - 5.4, - 5.5, - 5.6, - 5.8, - 6, - 6.1, - 6.3, - 6.6, - 6.8, - 6.9, - 7.2, - 7.6, - 8.1, - 8.5, - 8.8, - 9, - 9.2, - 9.5, - 9.8, - 10.1, - 10.2, - 10.2, - 10.1, - 10.1, - 10.1, - 10, - 10, - 10, - 9.9, - 9.8, - 9.8, - 9.8, - 9.8, - 9.9, - 9.9, - 10, - 10.2, - 10.2, - 10.1, - 10.1, - 10.2, - 10.3, - 10.3, - 10.2, - 10.1, - 10.1, - 10, - 9.7, - 9.2, - 8.4, - 8, - 8.1, - 8.2, - 8.9, - 9.7, - 10.2, - 10.4, - 10.5, - 10.4, - 10.3, - 10.2, - 10.2, - 10.2, - 10.4, - 10.5, - 10.6, - 10.8, - 10.9, - 11, - 54.2, - 53.7, - 53, - 52.6, - 52.2, - 51.5, - 51, - 50.5, - 50.2, - 50.1, - 49.8, - 49.6, - 49.3, - 49.2, - 48.9, - 48.5, - 48.1, - 48, - 47.8, - 47.5, - 47.1, - 46.8, - 46.5, - 46.2, - 46, - 45.7, - 45.4, - 44.9, - 44.6, - 44.3, - 43.9, - 43.5, - 43.2, - 42.9, - 42.5, - 42.3, - 42.2, - 41.9, - 41.6, - 41.2, - 40.7, - 40.4, - 40.1, - 39.7, - 39.2, - 38.6, - 37.9, - 37.3, - 36.7, - 36.1, - 35.7, - 35.3, - 35, - 34.6, - 34.3, - 33.8, - 33.4, - 33, - 32.5, - 31.9, - 31.3, - 30.8, - 30.2, - 29.7, - 29.2, - 28.7, - 28.3, - 27.4, - 26.8, - 26, - 25.4, - 25, - 24.5, - 24.1, - 23.5, - 23, - 22.4, - 22.1, - 21.9, - 21.5, - 21.1, - 20.8, - 20.5, - 20.4, - 20, - 19.7, - 19.5, - 19.2, - 19, - 19, - 18.6, - 18.4, - 18.3, - 18.4, - 18.4, - 18.5, - 18.4, - 18.3, - 17.3, - 15.9, - 15.1, - 14.5, - 13.7, - 13, - 12.4, - 12.3, - 11.7, - 10.5, - 10, - 9.6, - 9.1, - 8.6, - 8.1, - 7.6, - 7, - 6.5, - 5.8, - 5.1, - 4.2, - 3.9, - 3.5, - 3.1, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.5, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.5, - 2.8, - 2.6, - 2.5, - 2.4, - 2.4, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.3, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.6, - 2.6, - 2.7, - 2.8, - 3, - 3.2, - 3.4, - 3.6, - 3.8, - 3.8, - 3.9, - 3.9, - 4, - 4.1, - 4.2, - 4.3, - 4.4, - 4.4, - 4.6, - 4.7, - 4.7, - 4.8, - 4.8, - 4.9, - 5, - 5.2, - 5.3, - 5.4, - 5.6, - 5.8, - 5.9, - 6.1, - 6.4, - 6.6, - 6.7, - 7.1, - 7.5, - 7.9, - 8.2, - 8.4, - 8.6, - 8.8, - 9.2, - 9.6, - 10, - 10, - 10.1, - 10, - 10, - 10, - 9.9, - 9.9, - 9.8, - 9.7, - 9.6, - 9.7, - 9.7, - 9.6, - 9.6, - 9.6, - 9.7, - 9.9, - 10, - 9.9, - 9.9, - 9.9, - 10, - 10.1, - 9.9, - 9.9, - 9.8, - 9.6, - 9.2, - 8.3, - 7.9, - 7.9, - 8.1, - 8.9, - 9.7, - 10.3, - 10.5, - 10.5, - 10.5, - 10.4, - 10.4, - 10.3, - 10.4, - 10.4, - 10.4, - 10.6, - 10.6, - 10.6, - 10.8, - 11, - 54.1, - 53.6, - 52.9, - 52.5, - 52.2, - 51.6, - 51, - 50.6, - 50.3, - 50.1, - 50, - 49.8, - 49.5, - 49.4, - 49, - 48.7, - 48.4, - 48.2, - 48, - 47.7, - 47.4, - 47.1, - 46.7, - 46.4, - 46.1, - 45.7, - 45.4, - 45, - 44.7, - 44.3, - 43.9, - 43.6, - 43, - 42.9, - 42.7, - 42.3, - 42.2, - 42, - 41.7, - 41.1, - 40.8, - 40.4, - 40, - 39.6, - 38.9, - 38.5, - 37.7, - 37.2, - 36.6, - 36.1, - 35.8, - 35.4, - 35, - 34.7, - 34.3, - 33.9, - 33.5, - 33, - 32.5, - 31.9, - 31.4, - 30.8, - 30.3, - 29.8, - 29.2, - 28.7, - 28, - 27.4, - 26.7, - 26, - 25.4, - 25.1, - 24.5, - 24, - 23.4, - 22.9, - 22.4, - 22.1, - 21.8, - 21.5, - 21, - 20.8, - 20.5, - 20.3, - 20.1, - 19.8, - 19.4, - 19.2, - 19.2, - 18.9, - 18.6, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.3, - 17.4, - 16.1, - 15.4, - 14.6, - 13.7, - 12.8, - 12.5, - 12.2, - 11.6, - 10.4, - 9.9, - 9.5, - 9, - 8.6, - 8.1, - 7.6, - 7.1, - 6.4, - 5.8, - 5.1, - 4.3, - 3.8, - 3.5, - 3.1, - 2.7, - 2.5, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.5, - 2.8, - 2.7, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.1, - 2.2, - 2.3, - 2.5, - 2.6, - 2.7, - 2.8, - 3, - 3.2, - 3.4, - 3.5, - 3.6, - 3.7, - 3.8, - 3.9, - 3.9, - 4, - 4.1, - 4.1, - 4.1, - 4.2, - 4.4, - 4.5, - 4.5, - 4.5, - 4.6, - 4.7, - 4.8, - 5, - 5.1, - 5.3, - 5.4, - 5.6, - 5.8, - 5.9, - 6.2, - 6.3, - 6.6, - 6.9, - 7.3, - 7.6, - 7.9, - 8.1, - 8.2, - 8.5, - 9, - 9.5, - 9.8, - 9.9, - 9.9, - 9.9, - 9.8, - 9.9, - 9.8, - 9.7, - 9.7, - 9.6, - 9.5, - 9.5, - 9.5, - 9.5, - 9.5, - 9.5, - 9.6, - 9.6, - 9.8, - 9.7, - 9.7, - 9.7, - 9.7, - 9.8, - 9.7, - 9.6, - 9.3, - 9.1, - 8.4, - 7.8, - 7.8, - 7.8, - 8.5, - 9.6, - 10.1, - 10.3, - 10.3, - 10.5, - 10.5, - 10.5, - 10.4, - 10.4, - 10.5, - 10.5, - 10.5, - 10.6, - 10.6, - 10.6, - 10.7, - 10.9, - 53.8, - 53.4, - 52.8, - 52.4, - 52.1, - 51.6, - 51.1, - 50.8, - 50.5, - 50.3, - 50.3, - 50.1, - 49.7, - 49.5, - 49.1, - 48.9, - 48.6, - 48.4, - 48.2, - 47.9, - 47.4, - 47.2, - 46.8, - 46.6, - 46.2, - 45.8, - 45.4, - 45, - 44.7, - 44.4, - 43.9, - 43.5, - 43.1, - 43, - 42.7, - 42.4, - 42.2, - 42, - 41.7, - 41.2, - 40.9, - 40.4, - 39.9, - 39.4, - 38.7, - 38.2, - 37.6, - 37.2, - 36.6, - 36.1, - 35.7, - 35.4, - 35.1, - 34.7, - 34.3, - 34, - 33.6, - 33.1, - 32.6, - 32, - 31.5, - 30.8, - 30.3, - 29.8, - 29.1, - 28.6, - 27.9, - 27.4, - 26.8, - 26, - 25.4, - 25, - 24.4, - 23.9, - 23.4, - 22.8, - 22.4, - 22.1, - 21.7, - 21.4, - 21.2, - 20.8, - 20.5, - 20.2, - 20.1, - 19.8, - 19.4, - 19.3, - 19.2, - 19, - 18.6, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 17.4, - 16.1, - 15.2, - 14.5, - 13.7, - 13, - 12.5, - 12.2, - 11.5, - 10.6, - 10.1, - 9.6, - 9.1, - 8.6, - 8.2, - 7.6, - 7, - 6.3, - 5.6, - 4.9, - 4.2, - 3.7, - 3.4, - 3, - 2.7, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.3, - 2.4, - 2.4, - 2.4, - 2.3, - 2.4, - 2.3, - 2.2, - 2.2, - 2.3, - 2.5, - 2.8, - 2.7, - 2.4, - 2.2, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.5, - 2.6, - 2.6, - 2.8, - 2.9, - 3.1, - 3.2, - 3.3, - 3.5, - 3.6, - 3.7, - 3.8, - 3.9, - 3.9, - 4, - 4, - 4, - 4.1, - 4.1, - 4.2, - 4.3, - 4.3, - 4.4, - 4.5, - 4.7, - 4.8, - 5, - 5.1, - 5.3, - 5.4, - 5.6, - 5.8, - 6, - 6.2, - 6.5, - 6.8, - 7.1, - 7.5, - 7.7, - 7.8, - 7.9, - 8.4, - 8.9, - 9.3, - 9.6, - 9.6, - 9.8, - 9.7, - 9.7, - 9.7, - 9.6, - 9.6, - 9.6, - 9.5, - 9.4, - 9.3, - 9.3, - 9.3, - 9.3, - 9.4, - 9.4, - 9.5, - 9.6, - 9.5, - 9.6, - 9.4, - 9.5, - 9.5, - 9.4, - 9.1, - 8.7, - 8.2, - 7.8, - 7.7, - 7.8, - 8, - 9, - 9.9, - 10.3, - 10.3, - 10.4, - 10.5, - 10.5, - 10.5, - 10.5, - 10.5, - 10.5, - 10.5, - 10.6, - 10.6, - 10.7, - 10.7, - 10.7, - 10.9, - 54.2, - 53.4, - 52.7, - 52.3, - 52, - 51.6, - 51.2, - 50.9, - 50.7, - 50.6, - 50.4, - 50.1, - 49.9, - 49.6, - 49.2, - 48.9, - 48.6, - 48.4, - 48.2, - 47.9, - 47.5, - 47.2, - 46.9, - 46.6, - 46.2, - 45.8, - 45.4, - 45, - 44.7, - 44.3, - 43.9, - 43.5, - 43.2, - 43, - 42.7, - 42.4, - 42.2, - 42, - 41.6, - 41.1, - 40.8, - 40.3, - 39.8, - 39.2, - 38.7, - 38.1, - 37.5, - 37, - 36.5, - 36.2, - 35.9, - 35.4, - 35, - 34.7, - 34.3, - 34, - 33.5, - 33.1, - 32.6, - 32, - 31.5, - 30.9, - 30.3, - 29.8, - 29.3, - 28.5, - 27.7, - 27.2, - 26.6, - 26, - 25.5, - 24.9, - 24.4, - 23.9, - 23.4, - 22.9, - 22.4, - 22, - 21.8, - 21.4, - 21.2, - 20.8, - 20.5, - 20.2, - 20.1, - 19.9, - 19.7, - 19.5, - 19.2, - 18.9, - 18.5, - 18.4, - 18.3, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 17.5, - 16, - 14.9, - 14.2, - 13.5, - 12.9, - 12.5, - 12.2, - 11.7, - 10.7, - 10.2, - 9.8, - 9.1, - 8.7, - 8.2, - 7.5, - 6.9, - 6.3, - 5.7, - 4.9, - 4.2, - 3.6, - 3.2, - 3, - 2.7, - 2.6, - 2.5, - 2.5, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.3, - 2.2, - 2.5, - 2.7, - 2.7, - 2.4, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.4, - 2.5, - 2.6, - 2.7, - 2.9, - 3.1, - 3.2, - 3.2, - 3.4, - 3.5, - 3.6, - 3.6, - 3.7, - 3.7, - 3.9, - 3.9, - 3.9, - 3.9, - 4, - 4, - 4.1, - 4.1, - 4.3, - 4.4, - 4.5, - 4.7, - 4.9, - 5, - 5.1, - 5.3, - 5.4, - 5.6, - 5.8, - 6, - 6.3, - 6.5, - 6.8, - 7.1, - 7.3, - 7.5, - 7.8, - 8.2, - 8.7, - 9.1, - 9.4, - 9.6, - 9.7, - 9.6, - 9.5, - 9.5, - 9.4, - 9.4, - 9.4, - 9.3, - 9.1, - 9.2, - 9.3, - 9.2, - 9.3, - 9.3, - 9.3, - 9.4, - 9.4, - 9.4, - 9.5, - 9.4, - 9.4, - 9.2, - 9, - 8.5, - 7.8, - 7.6, - 7.6, - 7.7, - 8, - 8.6, - 9.5, - 10.1, - 10.4, - 10.4, - 10.5, - 10.6, - 10.6, - 10.5, - 10.6, - 10.6, - 10.6, - 10.6, - 10.6, - 10.6, - 10.7, - 10.7, - 10.8, - 10.9, - 54.3, - 53.3, - 52.8, - 52.4, - 52, - 51.6, - 51.3, - 51, - 50.9, - 50.8, - 50.7, - 50.2, - 50, - 49.8, - 49.4, - 49, - 48.8, - 48.6, - 48.2, - 48, - 47.6, - 47.2, - 46.9, - 46.6, - 46.2, - 46, - 45.5, - 45.1, - 44.7, - 44.3, - 44, - 43.7, - 43.3, - 43, - 42.7, - 42.4, - 42.2, - 41.8, - 41.5, - 41, - 40.6, - 40.1, - 39.6, - 39.1, - 38.7, - 38, - 37.5, - 37, - 36.5, - 36.1, - 35.9, - 35.4, - 35.1, - 34.7, - 34.4, - 34.1, - 33.7, - 33.1, - 32.6, - 32.1, - 31.5, - 30.9, - 30.2, - 29.7, - 29.2, - 28.4, - 27.8, - 27.2, - 26.7, - 26.1, - 25.4, - 24.8, - 24.4, - 23.9, - 23.3, - 22.9, - 22.4, - 22.1, - 21.8, - 21.4, - 21.1, - 20.9, - 20.6, - 20.4, - 20.3, - 20, - 19.8, - 19.5, - 19.1, - 18.8, - 18.5, - 18.4, - 18.3, - 18.4, - 18.4, - 18.5, - 18.4, - 18.4, - 17.6, - 16.1, - 15.1, - 14.5, - 13.8, - 13.1, - 12.6, - 12.3, - 11.7, - 10.9, - 10.3, - 10, - 9.5, - 8.7, - 8.2, - 7.6, - 7, - 6.5, - 5.8, - 5, - 4.3, - 3.6, - 3, - 2.8, - 2.6, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.4, - 2.3, - 2.2, - 2.3, - 2.6, - 2.7, - 2.5, - 2.2, - 2.2, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.5, - 2.6, - 2.7, - 2.8, - 3, - 3.1, - 3.2, - 3.4, - 3.4, - 3.5, - 3.6, - 3.6, - 3.7, - 3.7, - 3.8, - 3.9, - 3.8, - 3.8, - 3.9, - 3.9, - 4, - 4.1, - 4.2, - 4.4, - 4.5, - 4.7, - 4.8, - 5, - 5.1, - 5.2, - 5.4, - 5.6, - 5.9, - 6.1, - 6.3, - 6.6, - 6.9, - 7.1, - 7.3, - 7.7, - 8.1, - 8.5, - 8.9, - 9.3, - 9.4, - 9.5, - 9.5, - 9.4, - 9.3, - 9.3, - 9.3, - 9.1, - 9, - 8.9, - 9, - 9.1, - 9.1, - 9.2, - 9.1, - 9, - 9.2, - 9.3, - 9.4, - 9.5, - 9.3, - 9.1, - 8.9, - 8.6, - 7.8, - 7.3, - 7.3, - 7.5, - 7.7, - 8.5, - 9.4, - 9.9, - 10.3, - 10.4, - 10.4, - 10.5, - 10.7, - 10.7, - 10.6, - 10.6, - 10.6, - 10.7, - 10.7, - 10.7, - 10.6, - 10.7, - 10.8, - 10.9, - 10.9, - 54, - 53.6, - 53.1, - 52.4, - 52, - 51.7, - 51.4, - 51.1, - 51.1, - 50.9, - 50.7, - 50.4, - 50.2, - 49.9, - 49.6, - 49.3, - 49, - 48.6, - 48.4, - 48.1, - 47.6, - 47.2, - 47, - 46.6, - 46.3, - 46, - 45.5, - 45.1, - 44.8, - 44.3, - 44, - 43.7, - 43.4, - 43.1, - 42.6, - 42.3, - 42, - 41.8, - 41.3, - 40.9, - 40.4, - 39.9, - 39.5, - 39, - 38.5, - 38, - 37.6, - 37.1, - 36.6, - 36.1, - 35.7, - 35.4, - 35.1, - 34.8, - 34.5, - 34.2, - 33.7, - 33.2, - 32.7, - 32.2, - 31.6, - 31.1, - 30.4, - 29.7, - 29.2, - 28.6, - 27.9, - 27.3, - 26.7, - 26, - 25.3, - 24.7, - 24.3, - 23.8, - 23.3, - 22.9, - 22.5, - 22.2, - 21.9, - 21.4, - 21.1, - 20.9, - 20.6, - 20.5, - 20.4, - 20.2, - 19.8, - 19.4, - 19, - 18.8, - 18.5, - 18.4, - 18.4, - 18.4, - 18.4, - 18.5, - 18.4, - 18.4, - 17.6, - 16.2, - 15.4, - 14.8, - 14.2, - 13.5, - 12.9, - 12.3, - 11.8, - 11, - 10.3, - 10, - 9.6, - 8.9, - 8.4, - 7.8, - 7.3, - 6.7, - 6, - 5.1, - 4.4, - 3.7, - 3.2, - 2.8, - 2.7, - 2.6, - 2.5, - 2.5, - 2.5, - 2.6, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.4, - 2.2, - 2.2, - 2.3, - 2.6, - 2.6, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.6, - 2.7, - 2.8, - 3, - 3.2, - 3.3, - 3.3, - 3.4, - 3.5, - 3.5, - 3.6, - 3.6, - 3.6, - 3.7, - 3.6, - 3.7, - 3.7, - 3.8, - 3.9, - 4, - 4.1, - 4.2, - 4.4, - 4.5, - 4.6, - 4.8, - 4.9, - 5, - 5.3, - 5.5, - 5.7, - 5.9, - 6.2, - 6.4, - 6.7, - 7, - 7.3, - 7.6, - 8, - 8.3, - 8.7, - 9, - 9.2, - 9.3, - 9.3, - 9.2, - 9.2, - 9.2, - 9.1, - 8.9, - 8.9, - 8.8, - 8.8, - 8.8, - 8.9, - 8.9, - 9, - 9, - 9.1, - 9.2, - 9.2, - 9.2, - 9, - 8.8, - 8.6, - 8, - 7.3, - 7.1, - 7.2, - 7.5, - 8.4, - 9.2, - 9.8, - 10.2, - 10.4, - 10.4, - 10.4, - 10.5, - 10.7, - 10.7, - 10.7, - 10.6, - 10.6, - 10.7, - 10.7, - 10.7, - 10.6, - 10.7, - 10.8, - 10.9, - 10.9, - 53.9, - 53.6, - 53.2, - 52.6, - 52, - 51.7, - 51.4, - 51.2, - 51.2, - 51, - 50.7, - 50.5, - 50.4, - 50.1, - 49.8, - 49.4, - 49, - 48.7, - 48.4, - 48.2, - 47.7, - 47.4, - 47.1, - 46.6, - 46.2, - 45.9, - 45.6, - 45.1, - 44.7, - 44.4, - 44.1, - 43.7, - 43.3, - 43, - 42.7, - 42.3, - 42.1, - 41.7, - 41.3, - 40.7, - 40.2, - 39.8, - 39.4, - 39, - 38.5, - 38.1, - 37.6, - 37.1, - 36.7, - 36.2, - 35.9, - 35.4, - 35.1, - 34.9, - 34.5, - 34.2, - 33.7, - 33.3, - 32.8, - 32.3, - 31.8, - 31.1, - 30.5, - 30, - 29.3, - 28.8, - 28, - 27.3, - 26.7, - 26, - 25.2, - 24.6, - 24.1, - 23.7, - 23.3, - 23, - 22.6, - 22.3, - 21.9, - 21.4, - 21.1, - 20.8, - 20.7, - 20.6, - 20.5, - 20.2, - 20, - 19.5, - 19.2, - 18.8, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.5, - 18.4, - 18.4, - 17.5, - 16.2, - 15.4, - 14.8, - 14.1, - 13.5, - 12.8, - 12.3, - 11.6, - 11, - 10.5, - 10, - 9.6, - 9.1, - 8.5, - 8, - 7.5, - 6.9, - 6.2, - 5.3, - 4.5, - 3.9, - 3.5, - 3.1, - 3.1, - 2.9, - 2.6, - 2.6, - 2.7, - 2.7, - 2.7, - 2.6, - 2.7, - 2.8, - 2.8, - 2.6, - 2.3, - 2.2, - 2.3, - 2.6, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.5, - 2.7, - 2.9, - 3.1, - 3.2, - 3.3, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.5, - 3.5, - 3.5, - 3.6, - 3.6, - 3.7, - 3.7, - 3.8, - 4, - 4.1, - 4.2, - 4.3, - 4.5, - 4.6, - 4.7, - 4.9, - 5.1, - 5.4, - 5.5, - 5.8, - 6, - 6.3, - 6.6, - 6.9, - 7.2, - 7.5, - 7.8, - 8.1, - 8.5, - 8.8, - 9, - 9, - 9.1, - 9.1, - 9.1, - 9.1, - 9, - 8.8, - 8.9, - 8.8, - 8.8, - 8.8, - 8.7, - 8.7, - 8.8, - 8.9, - 9.1, - 9.1, - 9.1, - 9.1, - 8.9, - 8.6, - 8.1, - 7.4, - 7, - 7.1, - 7.3, - 8.1, - 8.8, - 9.5, - 9.9, - 10.1, - 10.3, - 10.3, - 10.4, - 10.4, - 10.6, - 10.6, - 10.6, - 10.6, - 10.6, - 10.6, - 10.7, - 10.7, - 10.8, - 10.8, - 10.9, - 10.9, - 10.9, - 53.9, - 53.5, - 53.1, - 52.5, - 52, - 51.6, - 51.3, - 51.2, - 51.2, - 51, - 50.9, - 50.7, - 50.5, - 50.2, - 49.9, - 49.4, - 49.1, - 48.7, - 48.5, - 48.1, - 47.9, - 47.5, - 47.1, - 46.6, - 46.3, - 46, - 45.6, - 45.3, - 44.9, - 44.4, - 44, - 43.6, - 43.3, - 42.8, - 42.5, - 42.3, - 42.1, - 41.7, - 41.2, - 40.6, - 40.1, - 39.6, - 39.3, - 38.9, - 38.4, - 38.1, - 37.7, - 37.3, - 36.7, - 36.2, - 35.9, - 35.5, - 35.2, - 34.8, - 34.6, - 34.2, - 33.8, - 33.4, - 32.9, - 32.5, - 31.9, - 31.3, - 30.7, - 30, - 29.4, - 28.7, - 28.2, - 27.4, - 26.7, - 25.8, - 25.1, - 24.5, - 24, - 23.7, - 23.3, - 23, - 22.7, - 22.3, - 21.9, - 21.5, - 21.1, - 20.8, - 20.8, - 20.6, - 20.4, - 20.1, - 19.9, - 19.6, - 19.4, - 19, - 18.4, - 18.3, - 18.3, - 18.4, - 18.4, - 18.5, - 18.4, - 18.3, - 17.4, - 16.1, - 15.2, - 14.5, - 13.9, - 13.2, - 12.7, - 12.4, - 11.5, - 11, - 10.5, - 10.1, - 9.7, - 9.2, - 8.6, - 8.2, - 7.7, - 7.1, - 6.3, - 5.4, - 4.7, - 4.2, - 3.9, - 3.4, - 3.3, - 3.3, - 3.2, - 3.1, - 2.8, - 2.7, - 2.7, - 2.7, - 2.9, - 2.9, - 2.8, - 2.6, - 2.4, - 2.5, - 2.7, - 2.5, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.6, - 2.8, - 3, - 3.2, - 3.2, - 3.3, - 3.3, - 3.3, - 3.3, - 3.4, - 3.4, - 3.4, - 3.5, - 3.4, - 3.5, - 3.6, - 3.7, - 3.7, - 3.8, - 4, - 4.1, - 4.2, - 4.3, - 4.5, - 4.5, - 4.7, - 4.9, - 5.1, - 5.4, - 5.6, - 5.9, - 6.2, - 6.5, - 6.7, - 7, - 7.4, - 7.7, - 7.9, - 8.3, - 8.6, - 8.8, - 8.9, - 9, - 9, - 8.9, - 8.9, - 8.8, - 8.7, - 8.7, - 8.6, - 8.6, - 8.7, - 8.6, - 8.7, - 8.8, - 8.9, - 9.1, - 9.1, - 9.1, - 9, - 8.8, - 8.4, - 7.8, - 7.1, - 7, - 7.1, - 7.8, - 8.6, - 9.2, - 9.7, - 10.1, - 10.2, - 10.2, - 10.3, - 10.4, - 10.4, - 10.4, - 10.5, - 10.5, - 10.6, - 10.6, - 10.7, - 10.7, - 10.7, - 10.7, - 10.8, - 10.8, - 10.9, - 10.9, - 53.6, - 53.2, - 52.8, - 52.3, - 51.9, - 51.5, - 51.5, - 51.4, - 51.2, - 51.1, - 50.9, - 50.7, - 50.5, - 50.3, - 49.9, - 49.5, - 49.1, - 48.8, - 48.5, - 48.2, - 47.9, - 47.5, - 47.2, - 46.7, - 46.5, - 46.1, - 45.6, - 45.2, - 44.9, - 44.4, - 44, - 43.6, - 43.2, - 42.8, - 42.5, - 42.3, - 42, - 41.6, - 41.2, - 40.7, - 40.2, - 39.6, - 39.2, - 38.8, - 38.5, - 38.2, - 37.8, - 37.3, - 36.7, - 36.3, - 36.1, - 35.6, - 35.2, - 34.9, - 34.7, - 34.3, - 33.9, - 33.5, - 33, - 32.5, - 32, - 31.4, - 30.8, - 30, - 29.4, - 28.9, - 28.3, - 27.5, - 26.4, - 25.6, - 25, - 24.5, - 24, - 23.6, - 23.2, - 23.1, - 22.7, - 22.3, - 22, - 21.6, - 21.4, - 21, - 20.8, - 20.7, - 20.5, - 20.2, - 19.9, - 19.6, - 19.3, - 19, - 18.5, - 18.3, - 18.3, - 18.3, - 18.5, - 18.5, - 18.4, - 18.3, - 17.2, - 16, - 15, - 14.3, - 13.9, - 13.3, - 12.8, - 12.3, - 11.7, - 11.2, - 10.7, - 10.1, - 9.6, - 9.2, - 8.7, - 8.2, - 7.8, - 7.3, - 6.5, - 5.6, - 4.8, - 4.5, - 4.2, - 3.7, - 3.4, - 3.4, - 3.5, - 3.2, - 2.9, - 2.7, - 2.8, - 2.9, - 3.1, - 2.9, - 2.7, - 2.7, - 2.7, - 2.8, - 2.7, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.5, - 2.7, - 3, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.4, - 3.4, - 3.5, - 3.6, - 3.7, - 3.8, - 4, - 4.1, - 4.2, - 4.3, - 4.4, - 4.5, - 4.6, - 4.8, - 5.1, - 5.5, - 5.7, - 6, - 6.3, - 6.6, - 6.9, - 7.2, - 7.4, - 7.7, - 8, - 8.3, - 8.6, - 8.8, - 8.8, - 8.8, - 8.8, - 8.7, - 8.6, - 8.5, - 8.4, - 8.4, - 8.5, - 8.6, - 8.7, - 8.8, - 8.8, - 9, - 9.1, - 9.2, - 9.1, - 8.9, - 8.6, - 8.1, - 7.3, - 6.9, - 6.9, - 7.1, - 8, - 8.8, - 9.4, - 9.7, - 10.1, - 10.2, - 10.2, - 10.3, - 10.4, - 10.4, - 10.4, - 10.6, - 10.6, - 10.6, - 10.7, - 10.7, - 10.6, - 10.7, - 10.7, - 10.8, - 11, - 10.9, - 10.9, - 53.5, - 53.1, - 52.6, - 52.1, - 51.8, - 51.6, - 51.6, - 51.5, - 51.3, - 51.1, - 51, - 50.7, - 50.6, - 50.3, - 49.9, - 49.6, - 49.2, - 48.8, - 48.6, - 48.3, - 47.9, - 47.6, - 47.3, - 46.8, - 46.5, - 46.1, - 45.8, - 45.4, - 44.8, - 44.4, - 44, - 43.6, - 43.1, - 42.7, - 42.5, - 42.3, - 42, - 41.7, - 41.1, - 40.7, - 40.2, - 39.7, - 39.2, - 38.9, - 38.4, - 38.1, - 37.7, - 37.3, - 36.7, - 36.3, - 36, - 35.6, - 35.3, - 35, - 34.7, - 34.3, - 33.9, - 33.5, - 33.1, - 32.6, - 32, - 31.4, - 31, - 30.2, - 29.5, - 28.9, - 28.3, - 27.3, - 26.4, - 25.6, - 24.9, - 24.4, - 24, - 23.5, - 23.2, - 23.1, - 22.9, - 22.6, - 22.2, - 21.7, - 21.4, - 21, - 20.8, - 20.7, - 20.5, - 20.3, - 20, - 19.6, - 19.3, - 18.9, - 18.4, - 18.2, - 18.2, - 18.3, - 18.5, - 18.5, - 18.4, - 18.4, - 17.2, - 15.9, - 14.9, - 14.4, - 14.1, - 13.5, - 12.7, - 12.3, - 11.8, - 11.3, - 10.7, - 10.2, - 9.6, - 9.1, - 8.7, - 8.3, - 7.8, - 7.3, - 6.5, - 5.7, - 5.1, - 4.9, - 4.5, - 3.9, - 3.6, - 3.3, - 3.2, - 2.9, - 2.8, - 2.8, - 3, - 3.2, - 3.3, - 3, - 2.8, - 2.9, - 2.9, - 2.7, - 2.6, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.4, - 2.4, - 2.6, - 2.9, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.3, - 3.4, - 3.4, - 3.5, - 3.7, - 3.8, - 3.9, - 3.9, - 4, - 4.1, - 4.3, - 4.5, - 4.7, - 5, - 5.3, - 5.6, - 5.9, - 6.2, - 6.4, - 6.7, - 6.9, - 7.2, - 7.6, - 7.9, - 8.1, - 8.4, - 8.6, - 8.6, - 8.7, - 8.6, - 8.5, - 8.5, - 8.4, - 8.4, - 8.6, - 8.7, - 8.8, - 8.8, - 8.8, - 8.9, - 9, - 9.1, - 9.3, - 9.2, - 8.8, - 8.2, - 7.4, - 6.9, - 6.8, - 6.8, - 7.2, - 8.1, - 8.8, - 9.4, - 9.8, - 10.1, - 10.2, - 10.2, - 10.3, - 10.3, - 10.2, - 10.4, - 10.5, - 10.5, - 10.6, - 10.7, - 10.7, - 10.7, - 10.7, - 10.7, - 10.8, - 11, - 11, - 10.9, - 53.2, - 52.9, - 52.5, - 52.2, - 52, - 51.9, - 51.9, - 51.8, - 51.5, - 51.2, - 51, - 50.8, - 50.6, - 50.3, - 49.9, - 49.5, - 49.2, - 48.9, - 48.6, - 48.3, - 48, - 47.6, - 47.3, - 46.8, - 46.5, - 46.1, - 45.8, - 45.4, - 44.9, - 44.3, - 44, - 43.5, - 42.9, - 42.6, - 42.5, - 42.4, - 42.1, - 41.7, - 41.2, - 40.6, - 40.2, - 39.7, - 39.3, - 38.9, - 38.4, - 38, - 37.6, - 37.1, - 36.8, - 36.3, - 35.9, - 35.6, - 35.3, - 34.9, - 34.7, - 34.4, - 34, - 33.6, - 33.2, - 32.6, - 32.2, - 31.6, - 31, - 30.3, - 29.6, - 29, - 28.3, - 27.4, - 26.6, - 25.6, - 24.9, - 24.5, - 24, - 23.6, - 23.3, - 23.1, - 23, - 22.8, - 22.3, - 21.9, - 21.5, - 21.1, - 20.9, - 20.8, - 20.5, - 20.2, - 20, - 19.6, - 19.3, - 18.8, - 18.4, - 18.3, - 18.2, - 18.2, - 18.5, - 18.5, - 18.4, - 18.4, - 16.9, - 15.6, - 15, - 14.5, - 14.1, - 13.4, - 12.7, - 12.3, - 11.8, - 11.3, - 10.8, - 10.1, - 9.6, - 9.1, - 8.7, - 8.1, - 7.7, - 7.2, - 6.4, - 5.9, - 5.6, - 5.4, - 4.9, - 4.3, - 3.9, - 3.5, - 3.4, - 3, - 2.8, - 3.2, - 3.6, - 3.6, - 3.4, - 3.1, - 2.9, - 2.9, - 2.7, - 2.6, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.5, - 2.8, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.3, - 3.4, - 3.5, - 3.6, - 3.7, - 3.8, - 3.8, - 3.9, - 4.1, - 4.3, - 4.6, - 4.9, - 5.2, - 5.4, - 5.7, - 5.9, - 6.2, - 6.4, - 6.6, - 6.9, - 7.4, - 7.7, - 7.9, - 8.1, - 8.3, - 8.3, - 8.4, - 8.3, - 8.4, - 8.6, - 8.6, - 8.7, - 8.7, - 8.9, - 8.9, - 8.9, - 9, - 9, - 9.2, - 9.2, - 9.3, - 9.3, - 8.8, - 7.9, - 7, - 6.8, - 6.8, - 6.8, - 7.1, - 8, - 8.7, - 9.2, - 9.8, - 10.1, - 10.1, - 10.2, - 10.3, - 10.3, - 10.2, - 10.3, - 10.5, - 10.5, - 10.6, - 10.7, - 10.8, - 10.8, - 10.8, - 10.8, - 10.8, - 10.8, - 10.9, - 11, - 53.1, - 52.7, - 52.5, - 52.5, - 52.3, - 52.3, - 52.1, - 51.9, - 51.6, - 51.4, - 51.1, - 50.9, - 50.6, - 50.2, - 49.9, - 49.5, - 49.1, - 48.8, - 48.5, - 48.3, - 47.9, - 47.6, - 47.3, - 46.9, - 46.5, - 46.1, - 45.7, - 45.4, - 44.8, - 44.5, - 43.9, - 43.3, - 42.8, - 42.5, - 42.4, - 42.3, - 42, - 41.7, - 41.1, - 40.7, - 40.3, - 39.9, - 39.4, - 38.9, - 38.4, - 37.9, - 37.4, - 37, - 36.7, - 36.3, - 36, - 35.7, - 35.3, - 35, - 34.8, - 34.4, - 34, - 33.7, - 33.3, - 32.7, - 32.3, - 31.8, - 31.2, - 30.6, - 29.7, - 29, - 28.3, - 27.5, - 26.6, - 25.7, - 24.9, - 24.4, - 24, - 23.7, - 23.4, - 23.2, - 23.1, - 23, - 22.6, - 22.1, - 21.6, - 21.1, - 20.9, - 20.8, - 20.5, - 20.2, - 19.9, - 19.6, - 19.2, - 18.9, - 18.5, - 18.4, - 18.2, - 18.2, - 18.5, - 18.5, - 18.4, - 18.4, - 16.8, - 15.5, - 14.8, - 14.2, - 13.6, - 13.2, - 12.5, - 12.2, - 11.8, - 11.3, - 10.7, - 10.1, - 9.7, - 9.2, - 8.8, - 8.5, - 7.7, - 7.3, - 6.9, - 6.4, - 6, - 5.8, - 5.2, - 4.8, - 4.4, - 4, - 4, - 3.7, - 3.7, - 3.9, - 4.1, - 4, - 3.7, - 3.3, - 2.9, - 2.8, - 2.7, - 2.6, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.5, - 2.8, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3.1, - 3.1, - 3.2, - 3.2, - 3.4, - 3.4, - 3.5, - 3.5, - 3.7, - 3.7, - 3.8, - 3.9, - 4.1, - 4.3, - 4.5, - 4.7, - 5, - 5.4, - 5.7, - 5.9, - 6.1, - 6.3, - 6.6, - 7, - 7.3, - 7.6, - 7.8, - 7.9, - 8, - 8.1, - 8.1, - 8.4, - 8.7, - 8.6, - 8.7, - 8.8, - 8.8, - 9, - 9, - 9.1, - 9.3, - 9.4, - 9.3, - 9.4, - 9.4, - 9.1, - 8.3, - 7.3, - 6.7, - 6.7, - 6.7, - 6.9, - 7.8, - 8.5, - 9.1, - 9.8, - 10, - 10.1, - 10.2, - 10.2, - 10.2, - 10.2, - 10.3, - 10.5, - 10.5, - 10.5, - 10.7, - 10.8, - 10.8, - 10.7, - 10.7, - 10.8, - 10.9, - 10.9, - 10.9, - 53.5, - 53.1, - 52.8, - 52.6, - 52.5, - 52.5, - 52.3, - 52.1, - 51.8, - 51.6, - 51.3, - 51, - 50.7, - 50.4, - 50, - 49.5, - 49.1, - 48.9, - 48.6, - 48.3, - 48, - 47.7, - 47.4, - 47, - 46.5, - 46.2, - 45.7, - 45.3, - 44.8, - 44.5, - 43.9, - 43.2, - 42.8, - 42.5, - 42.4, - 42.2, - 41.9, - 41.7, - 41.2, - 40.8, - 40.5, - 40, - 39.5, - 38.9, - 38.3, - 37.8, - 37.3, - 37, - 36.6, - 36.3, - 36, - 35.7, - 35.3, - 35.1, - 34.8, - 34.4, - 34.1, - 33.8, - 33.3, - 32.8, - 32.4, - 31.8, - 31.2, - 30.6, - 29.9, - 29.1, - 28.3, - 27.4, - 26.5, - 25.7, - 24.9, - 24.5, - 24.1, - 23.7, - 23.5, - 23.4, - 23.2, - 23, - 22.6, - 22.1, - 21.6, - 21.1, - 20.9, - 20.8, - 20.4, - 20.2, - 19.8, - 19.6, - 19.2, - 18.8, - 18.5, - 18.4, - 17.9, - 18.1, - 18.5, - 18.5, - 18.5, - 18.4, - 17.1, - 16.1, - 15.1, - 14.2, - 13.6, - 13.2, - 12.6, - 12.2, - 11.8, - 11.3, - 10.9, - 10.3, - 9.8, - 9.4, - 9, - 8.6, - 8.1, - 7.7, - 7.3, - 6.9, - 6.5, - 6.1, - 5.7, - 5.4, - 4.9, - 4.8, - 4.9, - 4.9, - 4.7, - 4.6, - 4.4, - 4.2, - 3.8, - 3.4, - 3, - 2.9, - 2.7, - 2.6, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.6, - 2.7, - 2.7, - 2.9, - 3, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.3, - 3.3, - 3.3, - 3.4, - 3.5, - 3.5, - 3.5, - 3.6, - 3.7, - 3.8, - 4, - 4.3, - 4.2, - 4.5, - 4.9, - 5.3, - 5.5, - 5.7, - 5.9, - 6.2, - 6.5, - 6.9, - 7.2, - 7.4, - 7.6, - 7.7, - 7.8, - 8.2, - 8.4, - 8.5, - 8.6, - 8.6, - 8.7, - 8.9, - 9, - 9.1, - 9.3, - 9.4, - 9.5, - 9.5, - 9.5, - 9.5, - 9.3, - 8.7, - 7.8, - 6.8, - 6.6, - 6.6, - 6.8, - 7.5, - 8.3, - 9, - 9.6, - 9.8, - 9.9, - 10, - 10, - 10.1, - 10.2, - 10.3, - 10.5, - 10.5, - 10.5, - 10.6, - 10.7, - 10.7, - 10.8, - 10.8, - 10.8, - 10.9, - 10.9, - 10.9, - 53.5, - 53.3, - 53.1, - 52.8, - 52.7, - 52.7, - 52.5, - 52.3, - 52, - 51.7, - 51.4, - 51.1, - 50.8, - 50.4, - 50, - 49.6, - 49.2, - 48.9, - 48.7, - 48.3, - 48, - 47.8, - 47.4, - 47, - 46.5, - 46.1, - 45.8, - 45.3, - 44.7, - 44.3, - 43.8, - 43.3, - 42.7, - 42.4, - 42.3, - 42.1, - 41.9, - 41.7, - 41.3, - 41, - 40.7, - 40.2, - 39.7, - 38.9, - 38.2, - 37.8, - 37.3, - 37, - 36.6, - 36.3, - 36.1, - 35.8, - 35.5, - 35.2, - 34.9, - 34.6, - 34.2, - 33.9, - 33.4, - 32.8, - 32.4, - 31.9, - 31.3, - 30.7, - 29.9, - 29.3, - 28.5, - 27.6, - 26.7, - 25.7, - 25.1, - 24.6, - 24.2, - 23.8, - 23.6, - 23.5, - 23.3, - 23, - 22.5, - 22.1, - 21.7, - 21.3, - 21, - 20.7, - 20.4, - 20.1, - 19.8, - 19.4, - 18.9, - 18.6, - 18.2, - 18, - 17.6, - 18.1, - 18.5, - 18.6, - 18.5, - 18.3, - 17.4, - 16.4, - 15.4, - 14.6, - 14, - 13.3, - 12.9, - 12.5, - 11.9, - 11.4, - 11.1, - 10.7, - 10.2, - 9.7, - 9.2, - 8.8, - 8.4, - 7.9, - 7.5, - 7.1, - 6.7, - 6.3, - 6, - 5.7, - 5.4, - 5.3, - 5.3, - 5.3, - 5.2, - 5, - 4.7, - 4.4, - 4, - 3.5, - 3.2, - 2.9, - 2.8, - 2.6, - 2.5, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.5, - 2.7, - 2.8, - 3, - 3, - 3, - 2.9, - 3, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.3, - 3.4, - 3.3, - 3.3, - 3.4, - 3.5, - 3.5, - 3.6, - 3.7, - 3.9, - 4.1, - 4.1, - 4.2, - 4.6, - 4.9, - 5.1, - 5.4, - 5.6, - 5.7, - 6.1, - 6.5, - 6.8, - 7.1, - 7.3, - 7.3, - 7.5, - 8, - 8.3, - 8.4, - 8.4, - 8.5, - 8.6, - 8.8, - 8.9, - 9.1, - 9.2, - 9.3, - 9.5, - 9.6, - 9.6, - 9.5, - 9.3, - 8.8, - 8, - 7.1, - 6.6, - 6.7, - 6.8, - 7.3, - 8.1, - 8.8, - 9.2, - 9.6, - 9.8, - 9.9, - 9.8, - 9.9, - 10, - 10.1, - 10.3, - 10.4, - 10.5, - 10.6, - 10.7, - 10.7, - 10.9, - 10.9, - 10.9, - 10.9, - 10.9, - 11, - 53.4, - 53.4, - 53.2, - 53.1, - 52.9, - 52.8, - 52.7, - 52.5, - 52.2, - 51.7, - 51.4, - 51.2, - 50.8, - 50.4, - 50.1, - 49.6, - 49.2, - 48.9, - 48.7, - 48.3, - 48.2, - 47.8, - 47.4, - 47, - 46.7, - 46.3, - 45.9, - 45.2, - 44.7, - 44.2, - 43.7, - 43.2, - 42.8, - 42.5, - 42.3, - 42.1, - 41.9, - 41.8, - 41.5, - 41.1, - 40.7, - 40.3, - 39.7, - 38.9, - 38.3, - 37.9, - 37.6, - 37.2, - 36.8, - 36.4, - 36.2, - 35.9, - 35.6, - 35.3, - 35, - 34.7, - 34.3, - 33.8, - 33.3, - 32.8, - 32.4, - 31.9, - 31.3, - 30.8, - 30.2, - 29.4, - 28.7, - 27.8, - 27, - 26, - 25.4, - 24.8, - 24.4, - 24, - 23.9, - 23.7, - 23.3, - 23, - 22.5, - 22.1, - 21.7, - 21.4, - 21.1, - 20.7, - 20.4, - 20.1, - 19.7, - 19.1, - 18.9, - 18.7, - 18.4, - 18.1, - 18, - 18.4, - 18.6, - 18.6, - 18.5, - 18.3, - 17.5, - 16.3, - 15.4, - 14.7, - 14.1, - 13.4, - 12.9, - 12.5, - 12, - 11.6, - 11.3, - 10.8, - 10.3, - 9.8, - 9.4, - 9, - 8.5, - 8.1, - 7.6, - 7.1, - 6.7, - 6.4, - 6, - 5.8, - 5.6, - 5.4, - 5.4, - 5.4, - 5.3, - 5.1, - 4.8, - 4.5, - 4.1, - 3.7, - 3.4, - 3.1, - 2.9, - 2.6, - 2.5, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.5, - 2.5, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.2, - 3.3, - 3.3, - 3.3, - 3.3, - 3.4, - 3.4, - 3.3, - 3.4, - 3.6, - 3.7, - 3.8, - 4, - 4.1, - 4.1, - 4.4, - 4.6, - 4.8, - 5, - 5.3, - 5.6, - 5.8, - 6.1, - 6.4, - 6.7, - 6.9, - 7, - 7.3, - 7.6, - 7.8, - 8, - 8.1, - 8.3, - 8.4, - 8.6, - 8.7, - 8.8, - 8.9, - 9, - 9.2, - 9.4, - 9.6, - 9.5, - 9.3, - 8.7, - 8, - 6.9, - 6.5, - 6.6, - 6.7, - 7.2, - 7.9, - 8.5, - 8.8, - 9.3, - 9.4, - 9.6, - 9.7, - 9.9, - 10, - 10.1, - 10.3, - 10.5, - 10.6, - 10.8, - 10.8, - 10.9, - 10.9, - 10.9, - 10.9, - 11, - 11, - 11.1, - 53.5, - 53.5, - 53.3, - 53.2, - 53, - 52.8, - 52.7, - 52.7, - 52.3, - 51.8, - 51.5, - 51.2, - 50.9, - 50.4, - 50, - 49.7, - 49.3, - 49, - 48.7, - 48.4, - 48.1, - 47.8, - 47.4, - 47, - 46.6, - 46.2, - 45.8, - 45.2, - 44.5, - 44, - 43.6, - 43.2, - 42.8, - 42.6, - 42.3, - 42.1, - 42, - 41.8, - 41.6, - 41.2, - 40.7, - 40.3, - 39.9, - 39.1, - 38.5, - 38, - 37.7, - 37.2, - 36.9, - 36.6, - 36.3, - 35.9, - 35.7, - 35.4, - 35.1, - 34.7, - 34.3, - 33.9, - 33.4, - 32.8, - 32.4, - 32, - 31.4, - 30.9, - 30.2, - 29.5, - 28.7, - 27.8, - 27, - 26.2, - 25.7, - 25.1, - 24.6, - 24.3, - 24.2, - 23.9, - 23.4, - 23, - 22.5, - 22.1, - 21.7, - 21.5, - 21.1, - 20.7, - 20.3, - 20, - 19.7, - 19.2, - 19.1, - 19, - 18.8, - 18.6, - 18.4, - 18.5, - 18.6, - 18.6, - 18.5, - 18.4, - 17.3, - 16.1, - 15.2, - 14.6, - 13.9, - 13.2, - 12.8, - 12.6, - 12.3, - 11.9, - 11.4, - 10.8, - 10.3, - 9.9, - 9.4, - 8.8, - 8.4, - 8, - 7.5, - 7.1, - 6.8, - 6.5, - 6.2, - 5.9, - 5.6, - 5.5, - 5.4, - 5.3, - 5.2, - 5.1, - 4.9, - 4.6, - 4.1, - 3.8, - 3.5, - 3.2, - 3, - 2.8, - 2.6, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.9, - 2.9, - 3, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.3, - 3.3, - 3.3, - 3.4, - 3.6, - 3.6, - 3.8, - 3.9, - 4, - 4.1, - 4.2, - 4.4, - 4.6, - 4.8, - 4.9, - 5.3, - 5.5, - 5.8, - 6, - 6.3, - 6.4, - 6.7, - 6.9, - 7.1, - 7.3, - 7.5, - 7.6, - 7.8, - 7.9, - 8, - 8.2, - 8.3, - 8.4, - 8.6, - 8.8, - 9.1, - 9.4, - 9.4, - 9.1, - 8.5, - 7.5, - 6.6, - 6.4, - 6.6, - 6.6, - 6.9, - 7.5, - 8, - 8.5, - 8.9, - 9.1, - 9.5, - 9.8, - 10, - 10.1, - 10.2, - 10.4, - 10.5, - 10.7, - 10.8, - 10.9, - 11, - 11, - 11, - 11, - 11.1, - 11, - 11.1, - 53.6, - 53.4, - 53.3, - 53.2, - 53.1, - 52.8, - 52.7, - 52.6, - 52.4, - 52, - 51.6, - 51.2, - 50.9, - 50.4, - 50.1, - 49.7, - 49.3, - 49.1, - 48.7, - 48.4, - 48, - 47.9, - 47.4, - 47.1, - 46.6, - 46.1, - 45.8, - 45.2, - 44.6, - 43.8, - 43.5, - 43.2, - 42.8, - 42.5, - 42.2, - 42, - 41.9, - 41.9, - 41.7, - 41.3, - 40.9, - 40.3, - 39.7, - 39.1, - 38.5, - 38.2, - 37.8, - 37.4, - 37, - 36.7, - 36.3, - 36, - 35.7, - 35.5, - 35.2, - 34.8, - 34.3, - 33.9, - 33.5, - 32.9, - 32.5, - 32.1, - 31.5, - 30.9, - 30.3, - 29.5, - 28.6, - 27.7, - 27, - 26.4, - 25.9, - 25.3, - 24.9, - 24.5, - 24.3, - 23.9, - 23.4, - 22.9, - 22.5, - 22.2, - 21.7, - 21.4, - 21, - 20.7, - 20.3, - 20, - 19.7, - 19.5, - 19.5, - 19.3, - 19.1, - 18.9, - 18.7, - 18.6, - 18.6, - 18.6, - 18.6, - 18.4, - 17.1, - 15.9, - 14.9, - 14.3, - 13.7, - 13.3, - 12.9, - 12.6, - 12.4, - 12.1, - 11.4, - 10.9, - 10.4, - 9.7, - 9.3, - 9, - 8.4, - 7.7, - 7.2, - 7.1, - 6.8, - 6.5, - 6.3, - 5.9, - 5.6, - 5.5, - 5.3, - 5.2, - 5.2, - 5, - 4.9, - 4.6, - 4.2, - 3.9, - 3.6, - 3.3, - 3.1, - 2.9, - 2.7, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.5, - 2.6, - 2.6, - 2.7, - 2.8, - 2.8, - 2.9, - 3, - 3.1, - 3.2, - 3.2, - 3.3, - 3.4, - 3.4, - 3.4, - 3.6, - 3.6, - 3.8, - 3.9, - 4, - 4.1, - 4.2, - 4.4, - 4.5, - 4.7, - 4.9, - 5.1, - 5.4, - 5.5, - 5.6, - 5.9, - 6.1, - 6.3, - 6.4, - 6.6, - 6.8, - 7, - 7.1, - 7.2, - 7.3, - 7.4, - 7.6, - 7.7, - 7.8, - 8, - 8.3, - 8.7, - 8.9, - 9, - 8.8, - 8.1, - 6.9, - 6.3, - 6.3, - 6.5, - 6.9, - 7.3, - 7.9, - 8.5, - 8.9, - 9.3, - 9.5, - 9.8, - 10, - 10.1, - 10.2, - 10.4, - 10.5, - 10.7, - 10.8, - 10.9, - 11.1, - 11.1, - 11.1, - 11.1, - 11.1, - 11.1, - 11.1, - 11.1, - 53.8, - 53.5, - 53.4, - 53.2, - 53.1, - 52.9, - 52.8, - 52.6, - 52.3, - 52, - 51.7, - 51.3, - 50.9, - 50.5, - 50.1, - 49.7, - 49.4, - 49, - 48.6, - 48.3, - 48, - 47.9, - 47.4, - 47, - 46.6, - 46.1, - 45.7, - 45.1, - 44.5, - 43.9, - 43.4, - 43, - 42.8, - 42.4, - 42.2, - 42.1, - 42.1, - 42, - 41.8, - 41.5, - 40.9, - 40.2, - 39.6, - 39.2, - 38.7, - 38.3, - 37.9, - 37.6, - 37, - 36.6, - 36.3, - 36, - 35.7, - 35.4, - 35.1, - 34.8, - 34.3, - 33.9, - 33.5, - 33, - 32.5, - 32.1, - 31.5, - 31, - 30.3, - 29.5, - 28.6, - 27.8, - 27, - 26.4, - 26, - 25.5, - 25.2, - 24.8, - 24.4, - 23.9, - 23.4, - 23, - 22.7, - 22.2, - 21.8, - 21.5, - 21, - 20.7, - 20.3, - 20.2, - 20.1, - 19.9, - 19.8, - 19.4, - 19.2, - 19, - 18.8, - 18.6, - 18.7, - 18.6, - 18.6, - 18.4, - 17.2, - 15.7, - 14.8, - 14.2, - 13.8, - 13.4, - 13, - 12.8, - 12.6, - 12, - 11.4, - 10.8, - 10.3, - 9.8, - 9.7, - 9.3, - 8.8, - 8.1, - 7.4, - 7.1, - 6.7, - 6.4, - 6.2, - 5.9, - 5.6, - 5.4, - 5.2, - 5.1, - 5.1, - 5.1, - 4.9, - 4.6, - 4.3, - 4, - 3.8, - 3.4, - 3.2, - 3, - 2.8, - 2.6, - 2.4, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.5, - 2.5, - 2.5, - 2.6, - 2.8, - 2.8, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.4, - 3.4, - 3.5, - 3.6, - 3.6, - 3.7, - 3.9, - 4.1, - 4.2, - 4.3, - 4.4, - 4.6, - 4.8, - 4.9, - 5.1, - 5.2, - 5.4, - 5.6, - 5.7, - 5.9, - 6, - 6.1, - 6.4, - 6.5, - 6.6, - 6.6, - 6.6, - 6.7, - 6.9, - 7.1, - 7.3, - 7.5, - 7.8, - 8.2, - 8.5, - 8.5, - 8.4, - 7.5, - 6.4, - 6.2, - 6.3, - 6.5, - 7.1, - 7.7, - 8.4, - 9, - 9.4, - 9.6, - 9.8, - 10, - 10.1, - 10.2, - 10.3, - 10.4, - 10.6, - 10.8, - 10.8, - 10.9, - 11, - 11.1, - 11.1, - 11.1, - 11.1, - 11.1, - 11.1, - 11.1, - 53.8, - 53.6, - 53.4, - 53.2, - 53.1, - 52.9, - 52.8, - 52.6, - 52.3, - 52, - 51.7, - 51.3, - 50.9, - 50.6, - 50.2, - 49.7, - 49.3, - 49, - 48.6, - 48.3, - 48.1, - 47.9, - 47.5, - 47, - 46.6, - 46.2, - 45.8, - 45.1, - 44.6, - 44.1, - 43.4, - 42.9, - 42.6, - 42.5, - 42.2, - 42.2, - 42.1, - 42, - 41.8, - 41.5, - 40.9, - 40.3, - 39.8, - 39.4, - 38.9, - 38.4, - 38, - 37.5, - 36.9, - 36.5, - 36.2, - 36.1, - 35.8, - 35.4, - 35.1, - 34.8, - 34.3, - 34, - 33.5, - 32.9, - 32.4, - 32, - 31.5, - 31.1, - 30.4, - 29.6, - 28.6, - 27.8, - 27, - 26.5, - 26.1, - 25.7, - 25.4, - 25, - 24.6, - 24.1, - 23.5, - 23, - 22.6, - 22.2, - 21.8, - 21.4, - 21.1, - 20.8, - 20.5, - 20.5, - 20.4, - 20.1, - 19.9, - 19.4, - 19.1, - 18.9, - 18.7, - 18.6, - 18.7, - 18.7, - 18.6, - 18.5, - 17.4, - 16, - 14.8, - 14.3, - 13.9, - 13.5, - 13.1, - 12.9, - 12.5, - 11.8, - 11.3, - 11.1, - 10.6, - 10.2, - 9.8, - 9.4, - 8.9, - 8.2, - 7.6, - 7.1, - 6.4, - 5.9, - 5.8, - 5.8, - 5.5, - 5.3, - 5.1, - 4.9, - 4.9, - 4.9, - 4.8, - 4.7, - 4.4, - 4.2, - 3.9, - 3.6, - 3.4, - 3.1, - 2.9, - 2.7, - 2.4, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.3, - 2.4, - 2.6, - 2.7, - 2.8, - 3, - 3.1, - 3.2, - 3.2, - 3.4, - 3.4, - 3.4, - 3.5, - 3.6, - 3.7, - 3.8, - 4, - 4.1, - 4.2, - 4.3, - 4.5, - 4.6, - 4.7, - 4.8, - 4.9, - 5.1, - 5.3, - 5.3, - 5.4, - 5.6, - 5.7, - 5.9, - 6, - 6, - 6.1, - 6, - 6.1, - 6.3, - 6.6, - 6.9, - 7, - 7.4, - 7.7, - 7.9, - 8, - 7.9, - 7, - 6.1, - 6.1, - 6.2, - 6.5, - 7.1, - 7.9, - 8.6, - 9.2, - 9.5, - 9.8, - 10, - 10.2, - 10.3, - 10.4, - 10.3, - 10.4, - 10.4, - 10.6, - 10.6, - 10.6, - 10.8, - 10.8, - 10.9, - 10.9, - 11, - 10.9, - 10.9, - 11, - 53.9, - 53.7, - 53.5, - 53.3, - 53.2, - 53, - 52.8, - 52.6, - 52.3, - 52, - 51.7, - 51.3, - 50.9, - 50.7, - 50.2, - 49.8, - 49.3, - 49, - 48.6, - 48.3, - 48.1, - 47.8, - 47.5, - 47.1, - 46.7, - 46.3, - 45.8, - 45.2, - 44.7, - 44.1, - 43.4, - 42.8, - 42.5, - 42.4, - 42.3, - 42.2, - 42.2, - 42.1, - 41.9, - 41.6, - 41.1, - 40.5, - 40.1, - 39.6, - 39, - 38.4, - 37.9, - 37.4, - 36.9, - 36.7, - 36.4, - 36.2, - 36, - 35.6, - 35.2, - 34.9, - 34.5, - 34, - 33.4, - 32.9, - 32.5, - 32.1, - 31.5, - 31.1, - 30.4, - 29.6, - 28.7, - 27.9, - 27.2, - 26.6, - 26.1, - 25.7, - 25.5, - 25.2, - 24.8, - 24.2, - 23.6, - 23, - 22.5, - 22, - 21.7, - 21.4, - 21.1, - 20.9, - 20.7, - 20.7, - 20.4, - 20.2, - 19.8, - 19.4, - 18.9, - 18.7, - 18.5, - 18.6, - 18.7, - 18.7, - 18.7, - 18.6, - 17.6, - 16.2, - 14.9, - 14.3, - 13.9, - 13.5, - 13.1, - 13.1, - 12.6, - 12.3, - 12, - 11.4, - 10.9, - 10.5, - 9.9, - 9.4, - 8.9, - 8.3, - 7.7, - 7, - 6.2, - 5.5, - 5.4, - 5.5, - 5.3, - 5.1, - 5, - 4.8, - 4.8, - 4.6, - 4.7, - 4.8, - 4.7, - 4.4, - 4.1, - 3.9, - 3.6, - 3.2, - 2.9, - 2.6, - 2.4, - 2.2, - 2.2, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.3, - 2.5, - 2.6, - 2.8, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.4, - 3.5, - 3.6, - 3.7, - 3.8, - 4, - 4.1, - 4.2, - 4.2, - 4.3, - 4.4, - 4.5, - 4.6, - 4.8, - 4.9, - 5, - 5.1, - 5.2, - 5.4, - 5.4, - 5.6, - 5.6, - 5.6, - 5.6, - 5.8, - 6, - 6.3, - 6.5, - 6.8, - 7, - 7.2, - 7.3, - 7.4, - 7.3, - 6.4, - 5.9, - 5.9, - 6.1, - 6.5, - 7.2, - 8, - 8.7, - 9.3, - 9.6, - 9.9, - 10, - 10.1, - 10.2, - 10.3, - 10.3, - 10.2, - 10.2, - 10.2, - 10.2, - 10.2, - 10.4, - 10.4, - 10.5, - 10.6, - 10.7, - 10.6, - 10.6, - 10.5, - 54.1, - 53.8, - 53.6, - 53.4, - 53.3, - 53.1, - 52.8, - 52.6, - 52.3, - 52, - 51.6, - 51.3, - 50.9, - 50.6, - 50.2, - 49.8, - 49.3, - 49, - 48.6, - 48.4, - 48.1, - 47.8, - 47.6, - 47.2, - 46.8, - 46.3, - 45.8, - 45.3, - 44.8, - 44.1, - 43.3, - 42.8, - 42.5, - 42.3, - 42.3, - 42.3, - 42.2, - 42.2, - 42, - 41.7, - 41.2, - 40.7, - 40.2, - 39.6, - 39, - 38.3, - 37.8, - 37.4, - 37, - 36.8, - 36.7, - 36.5, - 36.2, - 35.9, - 35.5, - 35, - 34.6, - 34, - 33.5, - 33, - 32.5, - 32.3, - 31.4, - 31, - 30.4, - 29.7, - 28.9, - 28.3, - 27.4, - 26.7, - 26.2, - 25.8, - 25.5, - 25.3, - 24.9, - 24.4, - 23.9, - 23.2, - 22.6, - 22.3, - 22, - 21.6, - 21.3, - 21.2, - 20.9, - 20.7, - 20.4, - 20.2, - 19.8, - 19.5, - 18.9, - 18.5, - 18.4, - 18.6, - 18.8, - 18.8, - 18.7, - 18.6, - 17.7, - 16.2, - 14.9, - 14.4, - 13.9, - 13.5, - 13.3, - 13.1, - 12.9, - 12.9, - 12.4, - 11.7, - 11.1, - 10.6, - 10.1, - 9.6, - 9.1, - 8.5, - 7.9, - 7.2, - 6.3, - 5.5, - 5.2, - 5.1, - 5.1, - 4.9, - 4.9, - 4.7, - 4.6, - 4.6, - 4.9, - 4.9, - 4.8, - 4.7, - 4.5, - 4.2, - 3.8, - 3.3, - 2.9, - 2.5, - 2.4, - 2.3, - 2.2, - 2.2, - 2.2, - 2.3, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.2, - 2.3, - 2.4, - 2.6, - 2.7, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.4, - 3.4, - 3.5, - 3.6, - 3.8, - 3.8, - 3.8, - 3.8, - 3.9, - 4, - 4, - 4.1, - 4.4, - 4.6, - 4.7, - 4.8, - 4.9, - 5, - 5.1, - 5.2, - 5.3, - 5.3, - 5.4, - 5.5, - 5.8, - 6, - 6.2, - 6.4, - 6.6, - 6.7, - 6.7, - 6.7, - 6.5, - 5.8, - 5.5, - 5.7, - 6.1, - 6.6, - 7.3, - 8.1, - 8.7, - 9.2, - 9.5, - 9.7, - 9.8, - 9.9, - 10, - 10, - 10, - 9.9, - 9.7, - 9.7, - 9.7, - 9.8, - 9.9, - 10, - 10.1, - 10.1, - 10.1, - 10.1, - 10.2, - 10.2, - 54.3, - 54, - 53.6, - 53.5, - 53.4, - 53.1, - 52.9, - 52.6, - 52.3, - 51.9, - 51.5, - 51.3, - 50.9, - 50.5, - 50.1, - 49.8, - 49.3, - 48.9, - 48.7, - 48.4, - 48.2, - 47.9, - 47.6, - 47.3, - 46.8, - 46.3, - 45.9, - 45.4, - 44.7, - 44.1, - 43.5, - 42.9, - 42.4, - 42.3, - 42.3, - 42.3, - 42.2, - 42.2, - 42, - 41.7, - 41.2, - 40.6, - 40.2, - 39.6, - 38.9, - 38.4, - 37.9, - 37.5, - 37.2, - 37, - 36.9, - 36.7, - 36.4, - 36, - 35.7, - 35.2, - 34.7, - 34.2, - 33.7, - 33.1, - 32.5, - 32.2, - 31.4, - 30.9, - 30.4, - 29.8, - 29, - 28.3, - 27.5, - 26.8, - 26.4, - 26, - 25.6, - 25.4, - 25.1, - 24.6, - 24.2, - 23.7, - 23, - 22.8, - 22.4, - 22, - 21.6, - 21.2, - 21, - 20.8, - 20.6, - 20.2, - 19.7, - 19.1, - 18.8, - 18.5, - 18.3, - 18.6, - 18.8, - 18.8, - 18.7, - 18.7, - 17.8, - 16.3, - 15.3, - 14.7, - 14.2, - 13.6, - 13.5, - 13.3, - 13.5, - 13.1, - 12.5, - 12, - 11.3, - 10.8, - 10.3, - 9.7, - 9.3, - 8.7, - 8.1, - 7.4, - 6.5, - 5.7, - 5.1, - 5.1, - 5, - 4.8, - 4.8, - 4.5, - 4.4, - 4.5, - 4.8, - 4.9, - 4.8, - 4.7, - 4.5, - 4.2, - 3.7, - 3.2, - 2.7, - 2.5, - 2.5, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.4, - 2.5, - 2.7, - 2.9, - 3, - 3, - 3.2, - 3.2, - 3.3, - 3.3, - 3.2, - 3.2, - 3.4, - 3.3, - 3.3, - 3.3, - 3.3, - 3.4, - 3.5, - 3.6, - 3.8, - 4.2, - 4.4, - 4.6, - 4.7, - 4.8, - 4.8, - 4.9, - 5, - 5.1, - 5.3, - 5.4, - 5.6, - 5.7, - 5.8, - 6, - 6.1, - 6.2, - 6.1, - 5.7, - 5.5, - 5.3, - 5.4, - 5.7, - 6.1, - 6.7, - 7.4, - 8.1, - 8.5, - 9, - 9.2, - 9.3, - 9.4, - 9.4, - 9.5, - 9.5, - 9.5, - 9.3, - 9.1, - 9.2, - 9.2, - 9.3, - 9.2, - 9.4, - 9.5, - 9.6, - 9.8, - 9.9, - 9.9, - 10, - 54.6, - 54.2, - 53.8, - 53.5, - 53.4, - 53.2, - 53, - 52.7, - 52.3, - 51.9, - 51.5, - 51.2, - 50.9, - 50.5, - 50, - 49.7, - 49.3, - 49, - 48.6, - 48.4, - 48.2, - 47.9, - 47.6, - 47.3, - 46.9, - 46.4, - 45.9, - 45.3, - 44.6, - 44.1, - 43.5, - 42.9, - 42.6, - 42.4, - 42.3, - 42.3, - 42.2, - 42.2, - 42, - 41.7, - 41.3, - 40.6, - 40, - 39.5, - 38.8, - 38.2, - 37.9, - 37.6, - 37.4, - 37.2, - 37.1, - 36.9, - 36.6, - 36.2, - 35.7, - 35.3, - 34.8, - 34.3, - 33.9, - 33.3, - 32.7, - 32.2, - 31.6, - 31, - 30.6, - 29.8, - 29, - 28.2, - 27.5, - 26.9, - 26.5, - 26.2, - 25.8, - 25.5, - 25.2, - 24.8, - 24.4, - 23.9, - 23.6, - 23.1, - 22.7, - 22.3, - 21.9, - 21.5, - 21.1, - 20.8, - 20.6, - 20.2, - 19.7, - 19.4, - 19, - 18.7, - 18.4, - 18.6, - 18.9, - 18.9, - 18.8, - 18.7, - 17.8, - 16.5, - 15.2, - 14.6, - 14.3, - 13.8, - 14.1, - 14.2, - 14, - 13.4, - 12.8, - 12.2, - 11.5, - 11, - 10.5, - 9.8, - 9.4, - 8.9, - 8.1, - 7.5, - 6.7, - 5.8, - 4.9, - 4.8, - 4.9, - 4.9, - 4.7, - 4.5, - 4.3, - 4.3, - 4.5, - 4.6, - 4.6, - 4.4, - 4.2, - 3.8, - 3.3, - 2.9, - 2.7, - 2.6, - 2.6, - 2.6, - 2.4, - 2.4, - 2.3, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.5, - 2.7, - 2.6, - 2.7, - 3, - 3.1, - 3.1, - 3.1, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.4, - 3.7, - 4.1, - 4.4, - 4.5, - 4.7, - 4.7, - 4.8, - 4.8, - 5, - 5, - 5.1, - 5.2, - 5.4, - 5.5, - 5.6, - 5.6, - 5.5, - 5.4, - 5.1, - 5.1, - 5.2, - 5.4, - 5.7, - 6.1, - 6.7, - 7.2, - 7.7, - 8.1, - 8.5, - 8.7, - 8.8, - 8.8, - 8.9, - 8.9, - 8.9, - 8.9, - 8.6, - 8.5, - 8.6, - 8.7, - 8.7, - 8.9, - 9, - 9.2, - 9.3, - 9.5, - 9.6, - 9.7, - 9.9, - 54.7, - 54.3, - 53.9, - 53.6, - 53.4, - 53.2, - 53, - 52.7, - 52.3, - 52, - 51.5, - 51.2, - 50.7, - 50.4, - 50, - 49.6, - 49.3, - 49.1, - 48.7, - 48.4, - 48.2, - 48, - 47.7, - 47.3, - 46.9, - 46.4, - 45.9, - 45.3, - 44.7, - 44.2, - 43.5, - 43, - 42.7, - 42.5, - 42.4, - 42.4, - 42.3, - 42.2, - 42, - 41.6, - 41.2, - 40.4, - 39.7, - 39.2, - 38.6, - 38.3, - 38, - 37.8, - 37.5, - 37.3, - 37.2, - 36.9, - 36.6, - 36.2, - 35.8, - 35.4, - 35, - 34.5, - 34.1, - 33.6, - 32.8, - 32.2, - 31.7, - 31.2, - 30.7, - 30.1, - 29.3, - 28.2, - 27.5, - 26.9, - 26.5, - 26.2, - 25.8, - 25.7, - 25.4, - 24.9, - 24.5, - 24.2, - 23.8, - 23.4, - 23, - 22.7, - 22.2, - 21.7, - 21.3, - 20.9, - 20.6, - 20.3, - 19.9, - 19.6, - 19.2, - 18.8, - 18.3, - 18.6, - 18.9, - 18.9, - 18.9, - 18.9, - 18.1, - 16.8, - 15.6, - 15.2, - 15.1, - 15, - 14.9, - 14.6, - 14.3, - 13.7, - 13.1, - 12.4, - 11.8, - 11.1, - 10.6, - 10, - 9.3, - 8.7, - 8.1, - 7.6, - 6.8, - 5.9, - 5.1, - 4.7, - 4.6, - 4.7, - 4.4, - 4.2, - 4.1, - 4.1, - 4.1, - 4.1, - 4.2, - 4, - 3.7, - 3.3, - 2.9, - 2.7, - 2.7, - 2.6, - 2.7, - 2.6, - 2.4, - 2.3, - 2.3, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.5, - 2.6, - 2.7, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.8, - 2.9, - 2.9, - 3, - 3.1, - 3.2, - 3.2, - 3.3, - 3.7, - 4.1, - 4.3, - 4.4, - 4.5, - 4.6, - 4.6, - 4.8, - 4.7, - 4.8, - 4.9, - 5, - 5.1, - 5.2, - 5.1, - 4.9, - 4.9, - 5, - 5.1, - 5.1, - 5.4, - 5.7, - 6.2, - 6.6, - 6.9, - 7.3, - 7.7, - 8, - 8.2, - 8.3, - 8.3, - 8.3, - 8.2, - 8.1, - 8, - 8, - 8, - 8.1, - 8.2, - 8.4, - 8.6, - 8.8, - 9, - 9.2, - 9.3, - 9.4, - 9.5, - 9.5, - 54.7, - 54.3, - 53.9, - 53.7, - 53.5, - 53.3, - 52.9, - 52.6, - 52.3, - 52.1, - 51.6, - 51.2, - 50.7, - 50.4, - 50.1, - 49.7, - 49.3, - 49.2, - 48.7, - 48.4, - 48.2, - 47.9, - 47.7, - 47.4, - 46.9, - 46.4, - 46, - 45.3, - 44.6, - 44, - 43.4, - 43, - 42.7, - 42.7, - 42.5, - 42.5, - 42.3, - 42.1, - 41.9, - 41.5, - 41, - 40.1, - 39.5, - 39, - 38.6, - 38.4, - 38.2, - 38, - 37.6, - 37.4, - 37.2, - 36.9, - 36.5, - 36.3, - 36, - 35.5, - 35, - 34.8, - 34.4, - 34, - 33, - 32.3, - 31.7, - 31.1, - 30.7, - 30.3, - 29.5, - 28.5, - 27.6, - 27, - 26.6, - 26.2, - 25.8, - 25.6, - 25.4, - 25, - 24.8, - 24.5, - 24.1, - 23.9, - 23.4, - 22.9, - 22.5, - 22.1, - 21.5, - 21, - 20.7, - 20.4, - 20, - 19.6, - 19, - 18.5, - 18.3, - 18.8, - 19, - 19, - 19, - 18.9, - 18.6, - 17.6, - 16.5, - 16.2, - 15.9, - 15.6, - 15.2, - 14.8, - 14.4, - 13.9, - 13.3, - 12.7, - 12.1, - 11.3, - 10.6, - 10, - 9.3, - 8.7, - 8.1, - 7.5, - 6.8, - 5.9, - 5.2, - 4.6, - 4.5, - 4.4, - 4.3, - 4, - 4, - 4, - 4, - 4, - 3.9, - 3.7, - 3.4, - 2.9, - 2.8, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3.1, - 3.3, - 3.7, - 4.1, - 4.2, - 4.3, - 4.5, - 4.5, - 4.5, - 4.5, - 4.5, - 4.5, - 4.6, - 4.6, - 4.7, - 4.6, - 4.6, - 4.7, - 4.8, - 5, - 5.1, - 5.5, - 5.9, - 6.2, - 6.4, - 6.6, - 6.9, - 7.1, - 7.3, - 7.4, - 7.5, - 7.6, - 7.7, - 7.5, - 7.6, - 7.6, - 7.6, - 7.7, - 7.8, - 8, - 8.3, - 8.5, - 8.7, - 8.9, - 9, - 9.2, - 9.2, - 9.3, - 9.4, - 54.7, - 54.3, - 53.9, - 53.7, - 53.5, - 53.3, - 52.9, - 52.6, - 52.2, - 52, - 51.6, - 51.2, - 50.8, - 50.4, - 50.1, - 49.7, - 49.4, - 49.2, - 48.7, - 48.5, - 48.2, - 47.9, - 47.8, - 47.4, - 46.9, - 46.4, - 45.9, - 45.3, - 44.5, - 43.8, - 43.4, - 43, - 42.8, - 42.8, - 42.6, - 42.5, - 42.3, - 42.1, - 41.7, - 41.2, - 40.7, - 39.9, - 39.4, - 39.1, - 38.9, - 38.5, - 38.2, - 38, - 37.6, - 37.5, - 37.4, - 37.1, - 36.8, - 36.5, - 36.2, - 35.7, - 35.3, - 34.9, - 34.6, - 34, - 32.9, - 32.4, - 31.7, - 31.1, - 30.7, - 30.3, - 29.6, - 28.6, - 27.7, - 27.1, - 26.8, - 26.3, - 26, - 25.7, - 25.5, - 25.2, - 25.1, - 24.7, - 24.3, - 24.1, - 23.7, - 23.2, - 22.9, - 22.4, - 21.7, - 21.1, - 20.7, - 20.4, - 20, - 19.4, - 18.8, - 18.3, - 18.4, - 18.9, - 19.1, - 19.1, - 19, - 19, - 18.9, - 18.1, - 17.2, - 16.7, - 16.2, - 15.8, - 15.3, - 14.8, - 14.4, - 14.1, - 13.5, - 12.9, - 12.3, - 11.4, - 10.7, - 10, - 9.3, - 8.7, - 8.1, - 7.6, - 6.8, - 5.9, - 5.3, - 4.8, - 4.3, - 4.3, - 4.3, - 4.1, - 4, - 4, - 3.9, - 3.8, - 3.7, - 3.4, - 3.1, - 2.8, - 2.8, - 2.8, - 2.8, - 2.9, - 2.8, - 2.8, - 2.7, - 2.6, - 2.5, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.6, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3.1, - 3.6, - 3.9, - 4, - 4.1, - 4.2, - 4.2, - 4.3, - 4.3, - 4.2, - 4.2, - 4.2, - 4.3, - 4.4, - 4.5, - 4.6, - 4.6, - 4.7, - 4.8, - 5.2, - 5.8, - 6, - 6.1, - 6.1, - 6.3, - 6.4, - 6.5, - 6.7, - 6.8, - 6.9, - 7, - 7.1, - 7.1, - 7.2, - 7.3, - 7.4, - 7.5, - 7.8, - 8, - 8.3, - 8.5, - 8.8, - 8.9, - 9, - 9.1, - 9.2, - 9.1, - 8.8, - 54.8, - 54.3, - 54, - 53.7, - 53.5, - 53.2, - 52.9, - 52.5, - 52.2, - 52, - 51.7, - 51.2, - 50.9, - 50.5, - 50, - 49.8, - 49.5, - 49.1, - 48.7, - 48.5, - 48.2, - 47.9, - 47.7, - 47.4, - 46.9, - 46.4, - 45.9, - 45.3, - 44.6, - 43.8, - 43.4, - 43, - 42.9, - 42.8, - 42.7, - 42.5, - 42.2, - 41.9, - 41.5, - 41, - 40.4, - 39.9, - 39.5, - 39.2, - 39, - 38.6, - 38.2, - 38, - 37.7, - 37.6, - 37.5, - 37.2, - 37, - 36.7, - 36.4, - 35.9, - 35.5, - 35.1, - 34.7, - 33.9, - 33.1, - 32.5, - 31.6, - 31.1, - 30.7, - 30.4, - 29.7, - 28.7, - 27.8, - 27.2, - 26.8, - 26.4, - 26.1, - 25.8, - 25.7, - 25.6, - 25.3, - 24.7, - 24.6, - 24.3, - 23.9, - 23.5, - 23.1, - 22.5, - 21.9, - 21.3, - 20.6, - 20.3, - 19.8, - 19.2, - 18.7, - 18.5, - 18.4, - 18.9, - 19.1, - 19.2, - 19.1, - 19.1, - 19, - 18.4, - 17.4, - 16.7, - 16.2, - 15.8, - 15.3, - 14.8, - 14.5, - 14, - 13.6, - 13, - 12.3, - 11.4, - 10.8, - 10.1, - 9.4, - 8.9, - 8.1, - 7.4, - 6.6, - 5.9, - 5.3, - 4.9, - 4.5, - 4.4, - 4.3, - 4.2, - 4, - 3.8, - 3.7, - 3.6, - 3.4, - 3.2, - 3, - 2.8, - 2.8, - 2.8, - 2.8, - 3, - 2.9, - 2.8, - 2.8, - 2.6, - 2.5, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.7, - 2.9, - 2.9, - 3, - 3.1, - 3.4, - 3.6, - 3.8, - 3.9, - 3.9, - 4, - 4.1, - 4.1, - 4, - 4.1, - 4.2, - 4.2, - 4.3, - 4.4, - 4.5, - 4.5, - 4.8, - 5.1, - 5.6, - 5.8, - 5.8, - 5.9, - 6, - 6, - 6.1, - 6.2, - 6.3, - 6.4, - 6.5, - 6.6, - 6.8, - 6.9, - 7, - 7.2, - 7.4, - 7.6, - 7.9, - 8.1, - 8.4, - 8.6, - 8.8, - 8.9, - 8.9, - 9, - 9.1, - 8.6, - 8.7, - 54.6, - 54.3, - 53.9, - 53.7, - 53.4, - 53.1, - 52.9, - 52.5, - 52.3, - 52.1, - 51.7, - 51.3, - 50.8, - 50.5, - 50.1, - 49.9, - 49.5, - 49.1, - 48.8, - 48.4, - 48.2, - 47.9, - 47.6, - 47.3, - 46.9, - 46.4, - 45.8, - 45.2, - 44.5, - 43.8, - 43.4, - 43.1, - 43.1, - 42.9, - 42.7, - 42.5, - 42.2, - 41.8, - 41.2, - 40.7, - 40.2, - 39.8, - 39.5, - 39.2, - 38.9, - 38.6, - 38.2, - 37.9, - 37.7, - 37.7, - 37.5, - 37.2, - 37, - 36.8, - 36.4, - 35.9, - 35.5, - 35.2, - 34.8, - 34, - 33.4, - 32.6, - 31.7, - 31.2, - 30.8, - 30.4, - 29.8, - 28.8, - 28, - 27.4, - 26.9, - 26.5, - 26.2, - 26.1, - 25.9, - 25.7, - 25.2, - 25.1, - 24.8, - 24.5, - 24.1, - 23.9, - 23.5, - 22.8, - 22, - 21.3, - 20.6, - 20, - 19.5, - 19.1, - 18.7, - 18.4, - 18.5, - 18.9, - 19.2, - 19.3, - 19.2, - 19.1, - 19, - 18.7, - 17.7, - 16.8, - 16.2, - 15.7, - 15.2, - 14.8, - 14.5, - 13.9, - 13.5, - 12.9, - 12.2, - 11.4, - 10.9, - 10.2, - 9.4, - 8.7, - 8, - 7.3, - 6.6, - 5.9, - 5.5, - 5.1, - 4.8, - 4.6, - 4.4, - 4.2, - 3.9, - 3.7, - 3.6, - 3.4, - 3.3, - 3.1, - 3, - 2.8, - 2.8, - 2.8, - 2.9, - 3.1, - 3, - 2.9, - 2.9, - 2.7, - 2.5, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.4, - 2.5, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.5, - 3.6, - 3.7, - 3.8, - 3.8, - 3.9, - 4, - 4, - 4.1, - 4.2, - 4.3, - 4.4, - 4.5, - 4.6, - 5, - 5.2, - 5.4, - 5.5, - 5.7, - 5.8, - 5.8, - 5.9, - 5.9, - 6, - 6.1, - 6.2, - 6.3, - 6.4, - 6.6, - 6.8, - 7.1, - 7.4, - 7.5, - 7.7, - 7.9, - 8.2, - 8.5, - 8.7, - 8.7, - 8.8, - 8.8, - 8.8, - 8.6, - 8.1, - 8.7, - 54.6, - 54.2, - 53.9, - 53.7, - 53.4, - 53.1, - 52.9, - 52.6, - 52.3, - 52.1, - 51.7, - 51.3, - 50.9, - 50.4, - 50, - 49.7, - 49.5, - 49.1, - 48.8, - 48.6, - 48.2, - 47.9, - 47.6, - 47.3, - 46.8, - 46.3, - 45.7, - 45.1, - 44.4, - 43.7, - 43.4, - 43.2, - 43, - 42.9, - 42.8, - 42.6, - 42.2, - 41.8, - 41.3, - 40.6, - 40.2, - 39.8, - 39.5, - 39.2, - 38.9, - 38.6, - 38.3, - 37.9, - 37.8, - 37.7, - 37.5, - 37.2, - 37.1, - 36.9, - 36.4, - 36, - 35.5, - 35.3, - 34.8, - 34.1, - 33.5, - 32.5, - 31.9, - 31.3, - 30.9, - 30.3, - 29.7, - 28.9, - 28.2, - 27.6, - 27.1, - 26.6, - 26.5, - 26.2, - 25.9, - 25.7, - 25.4, - 25.3, - 24.9, - 24.7, - 24.4, - 24.1, - 23.4, - 22.9, - 22.2, - 21.4, - 20.8, - 20.1, - 19.5, - 19.1, - 18.7, - 18.3, - 18.5, - 19.1, - 19.3, - 19.3, - 19.3, - 19.2, - 19.1, - 19, - 18.2, - 17.1, - 16.3, - 15.6, - 15.2, - 14.8, - 14.3, - 13.7, - 13.2, - 12.6, - 12, - 11.4, - 11, - 10.1, - 9.1, - 8.4, - 7.8, - 7.2, - 6.8, - 6.3, - 5.8, - 5.2, - 4.9, - 4.7, - 4.4, - 4.2, - 4, - 3.7, - 3.5, - 3.4, - 3.2, - 3.1, - 3.1, - 3, - 2.9, - 2.9, - 3.1, - 3.2, - 3.1, - 2.9, - 2.8, - 2.8, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.5, - 2.6, - 2.7, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.3, - 3.4, - 3.5, - 3.5, - 3.7, - 3.8, - 3.9, - 3.9, - 3.9, - 4, - 4.1, - 4.2, - 4.3, - 4.4, - 4.5, - 4.8, - 5, - 5, - 5.2, - 5.3, - 5.5, - 5.5, - 5.6, - 5.7, - 5.8, - 5.9, - 6, - 6.1, - 6.3, - 6.6, - 6.8, - 7.1, - 7.4, - 7.6, - 7.8, - 8, - 8.3, - 8.5, - 8.6, - 8.7, - 8.8, - 8.6, - 8.4, - 7.9, - 7.7, - 8.5, - 54.5, - 54.2, - 53.9, - 53.7, - 53.4, - 53.1, - 52.9, - 52.7, - 52.4, - 52, - 51.7, - 51.3, - 50.8, - 50.4, - 50, - 49.7, - 49.4, - 49.2, - 48.9, - 48.5, - 48.3, - 47.9, - 47.6, - 47.2, - 46.9, - 46.3, - 45.8, - 45, - 44.3, - 43.7, - 43.4, - 43.2, - 43.2, - 43.1, - 43, - 42.7, - 42.3, - 41.7, - 41.3, - 40.8, - 40.2, - 39.8, - 39.4, - 39.2, - 38.9, - 38.7, - 38.3, - 38, - 37.8, - 37.7, - 37.6, - 37.3, - 37.1, - 36.8, - 36.4, - 35.9, - 35.7, - 35.4, - 34.9, - 34.2, - 33.5, - 32.6, - 32, - 31.4, - 30.9, - 30.3, - 29.6, - 28.9, - 28.4, - 27.8, - 27.3, - 26.8, - 26.6, - 26.4, - 26.1, - 25.8, - 25.6, - 25.4, - 25.1, - 24.8, - 24.6, - 24.3, - 23.7, - 23.1, - 22.6, - 21.8, - 21.1, - 20.4, - 19.9, - 19, - 18.5, - 18.5, - 18.5, - 19.2, - 19.4, - 19.4, - 19.4, - 19.3, - 19.2, - 19.1, - 18.7, - 17.6, - 16.6, - 15.8, - 15.4, - 14.9, - 14.2, - 13.6, - 13.1, - 12.5, - 11.9, - 11.3, - 10.7, - 9.8, - 9, - 8.3, - 7.8, - 7.4, - 7.3, - 7, - 6.5, - 5.6, - 5, - 4.7, - 4.4, - 4.3, - 4, - 3.8, - 3.6, - 3.2, - 3.1, - 3.2, - 3.2, - 3.1, - 3.2, - 3.4, - 3.4, - 3.4, - 3.2, - 3, - 2.8, - 2.7, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.6, - 2.5, - 2.6, - 2.7, - 2.7, - 2.8, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.4, - 3.6, - 3.7, - 3.7, - 3.8, - 3.9, - 3.9, - 4, - 4, - 4.1, - 4.2, - 4.3, - 4.3, - 4.5, - 4.6, - 4.7, - 4.8, - 4.9, - 5.1, - 5.2, - 5.4, - 5.5, - 5.6, - 5.7, - 5.8, - 6, - 6.3, - 6.5, - 6.8, - 7, - 7.3, - 7.6, - 7.9, - 8, - 8.1, - 8.2, - 8.3, - 8.4, - 8.4, - 8.2, - 7.9, - 7.2, - 7.6, - 8.2, - 54.5, - 54.2, - 53.9, - 53.7, - 53.5, - 53.3, - 53, - 52.8, - 52.5, - 52.1, - 51.7, - 51.3, - 50.9, - 50.5, - 50.1, - 49.7, - 49.5, - 49.1, - 48.8, - 48.6, - 48.4, - 48, - 47.7, - 47.3, - 46.9, - 46.4, - 45.8, - 45.1, - 44.4, - 43.8, - 43.5, - 43.3, - 43.3, - 43.1, - 43, - 42.8, - 42.4, - 41.7, - 41.2, - 40.8, - 40.3, - 39.9, - 39.5, - 39.2, - 39, - 38.8, - 38.4, - 38.1, - 37.9, - 37.8, - 37.6, - 37.3, - 37, - 36.8, - 36.5, - 36.1, - 35.8, - 35.5, - 34.9, - 34.3, - 33.6, - 32.6, - 32, - 31.4, - 30.8, - 30.3, - 29.5, - 29, - 28.4, - 27.9, - 27.4, - 27.1, - 26.7, - 26.6, - 26.1, - 25.8, - 25.8, - 25.5, - 25.2, - 25, - 24.7, - 24.5, - 23.9, - 23.4, - 22.9, - 22.1, - 21.4, - 21, - 20.5, - 19.6, - 18.9, - 18.7, - 18.7, - 19.3, - 19.5, - 19.5, - 19.5, - 19.4, - 19.3, - 19.2, - 19, - 18.3, - 17.2, - 16.3, - 15.6, - 14.9, - 14.3, - 13.6, - 13, - 12.4, - 11.8, - 11.2, - 10.5, - 9.7, - 9, - 8.4, - 8, - 7.8, - 7.6, - 7.3, - 7, - 6, - 5.3, - 5.1, - 4.6, - 4.5, - 4.1, - 4.1, - 4, - 3.6, - 3.4, - 3.4, - 3.3, - 3.4, - 3.5, - 3.6, - 3.6, - 3.4, - 3.2, - 3, - 2.8, - 2.7, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 3.1, - 3.2, - 3.3, - 3.3, - 3.4, - 3.5, - 3.6, - 3.6, - 3.7, - 3.8, - 3.8, - 3.9, - 3.9, - 4, - 4.1, - 4.2, - 4.2, - 4.3, - 4.3, - 4.4, - 4.5, - 4.7, - 4.9, - 5, - 5.2, - 5.3, - 5.4, - 5.4, - 5.7, - 5.9, - 6.2, - 6.4, - 6.7, - 7, - 7.3, - 7.5, - 7.7, - 7.8, - 7.7, - 7.6, - 7.7, - 7.8, - 7.7, - 7.6, - 7.3, - 6.7, - 7.2, - 7.8, - 54.5, - 54.1, - 53.9, - 53.6, - 53.4, - 53.3, - 53.2, - 52.9, - 52.5, - 52.1, - 51.8, - 51.3, - 50.9, - 50.4, - 50.2, - 49.9, - 49.5, - 49.1, - 48.9, - 48.7, - 48.3, - 48, - 47.6, - 47.4, - 47, - 46.4, - 45.9, - 45.2, - 44.5, - 43.9, - 43.6, - 43.4, - 43.3, - 43.2, - 43.1, - 42.8, - 42.4, - 41.9, - 41.2, - 40.8, - 40.4, - 40, - 39.6, - 39.2, - 38.9, - 38.7, - 38.5, - 38.2, - 37.9, - 37.8, - 37.6, - 37.4, - 37, - 36.6, - 36.4, - 36.1, - 35.9, - 35.6, - 35, - 34.2, - 33.4, - 32.7, - 32, - 31.4, - 30.9, - 30.3, - 29.6, - 29, - 28.5, - 28, - 27.5, - 27.2, - 26.9, - 26.7, - 26.2, - 26.1, - 25.8, - 25.5, - 25.4, - 25.1, - 24.8, - 24.6, - 24.1, - 23.5, - 23.1, - 22.5, - 21.8, - 21.5, - 21.1, - 20.3, - 19.6, - 19, - 18.9, - 19.5, - 19.6, - 19.6, - 19.6, - 19.5, - 19.4, - 19.3, - 19.1, - 18.8, - 17.8, - 16.8, - 15.9, - 15.1, - 14.5, - 13.8, - 13, - 12.4, - 11.8, - 11.2, - 10.4, - 9.7, - 9.1, - 8.5, - 8.3, - 8, - 7.9, - 7.6, - 7.3, - 6.6, - 5.9, - 5.4, - 5, - 4.8, - 4.6, - 4.4, - 4.2, - 3.9, - 3.7, - 3.6, - 3.5, - 3.7, - 3.8, - 3.6, - 3.5, - 3.4, - 3.2, - 3.1, - 2.9, - 2.7, - 2.5, - 2.4, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.4, - 3.5, - 3.6, - 3.7, - 3.7, - 3.8, - 3.8, - 3.8, - 3.9, - 4, - 4, - 4, - 4, - 4.1, - 4.2, - 4.4, - 4.6, - 4.7, - 4.8, - 4.9, - 5, - 5.1, - 5.3, - 5.5, - 5.7, - 6, - 6.3, - 6.5, - 6.8, - 7.1, - 7.3, - 7.3, - 7.3, - 7.1, - 6.9, - 6.9, - 7, - 6.9, - 6.9, - 6.6, - 6.3, - 6.4, - 6.6, - 54.4, - 54.2, - 53.9, - 53.6, - 53.5, - 53.3, - 53.3, - 53, - 52.6, - 52.2, - 51.9, - 51.4, - 50.9, - 50.5, - 50.2, - 49.9, - 49.6, - 49.1, - 48.9, - 48.6, - 48.3, - 48, - 47.6, - 47.3, - 46.9, - 46.5, - 45.9, - 45.3, - 44.6, - 44, - 43.8, - 43.6, - 43.5, - 43.3, - 43.1, - 42.6, - 42.4, - 41.8, - 41.3, - 40.8, - 40.4, - 40.1, - 39.7, - 39.3, - 39, - 38.7, - 38.5, - 38.2, - 38, - 37.9, - 37.6, - 37.4, - 37, - 36.5, - 36.2, - 36, - 35.8, - 35.5, - 34.9, - 34.2, - 33.4, - 32.7, - 32.1, - 31.6, - 31, - 30.3, - 29.7, - 29.1, - 28.6, - 28.2, - 27.7, - 27.4, - 27.1, - 26.8, - 26.6, - 26.4, - 26, - 25.7, - 25.6, - 25.3, - 25.1, - 24.8, - 24.4, - 23.8, - 23.3, - 22.8, - 22.3, - 21.9, - 21.6, - 20.8, - 20, - 19.3, - 19.2, - 19.7, - 19.8, - 19.8, - 19.7, - 19.6, - 19.5, - 19.4, - 19.2, - 19, - 18.4, - 17.4, - 16.4, - 15.5, - 14.7, - 13.9, - 13.2, - 12.5, - 11.8, - 11.2, - 10.6, - 9.9, - 9.3, - 8.9, - 8.5, - 8.2, - 8.1, - 7.9, - 7.7, - 7.1, - 6.4, - 5.8, - 5.5, - 5.2, - 4.9, - 4.7, - 4.5, - 4.3, - 4, - 3.8, - 3.8, - 3.7, - 3.7, - 3.6, - 3.4, - 3.3, - 3.1, - 3, - 2.9, - 2.6, - 2.5, - 2.5, - 2.4, - 2.3, - 2.2, - 2.3, - 2.4, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.8, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.4, - 3.4, - 3.5, - 3.6, - 3.6, - 3.7, - 3.7, - 3.7, - 3.8, - 3.8, - 3.8, - 3.9, - 3.9, - 3.9, - 4, - 4.2, - 4.4, - 4.5, - 4.5, - 4.7, - 4.8, - 4.9, - 5.1, - 5.3, - 5.4, - 5.7, - 5.9, - 6.2, - 6.5, - 6.8, - 6.9, - 6.8, - 6.5, - 6.3, - 6.2, - 6.1, - 6.1, - 6.1, - 5.9, - 5.8, - 6, - 6, - 6.1, - 54.5, - 54.2, - 53.8, - 53.6, - 53.5, - 53.4, - 53.3, - 53.1, - 52.7, - 52.3, - 51.9, - 51.5, - 51.1, - 50.7, - 50.3, - 49.9, - 49.7, - 49.3, - 48.9, - 48.5, - 48.4, - 48, - 47.7, - 47.3, - 47, - 46.5, - 46, - 45.3, - 44.8, - 44.2, - 43.9, - 43.8, - 43.6, - 43.4, - 43.1, - 42.6, - 42.3, - 41.8, - 41.2, - 40.8, - 40.4, - 40.1, - 39.7, - 39.4, - 39.1, - 38.8, - 38.6, - 38.3, - 38.1, - 37.8, - 37.7, - 37.3, - 37, - 36.5, - 36.1, - 35.7, - 35.3, - 35.2, - 34.8, - 34.1, - 33.3, - 32.7, - 32.1, - 31.5, - 30.9, - 30.3, - 29.7, - 29.1, - 28.6, - 28.1, - 27.8, - 27.5, - 27.3, - 27.1, - 26.9, - 26.5, - 26.2, - 25.8, - 25.6, - 25.4, - 25.2, - 24.9, - 24.6, - 24.1, - 23.5, - 23, - 22.5, - 22.3, - 21.9, - 21.3, - 20.5, - 19.9, - 19.8, - 19.8, - 19.9, - 19.9, - 19.8, - 19.7, - 19.6, - 19.5, - 19.3, - 19.1, - 18.9, - 18.2, - 17, - 16, - 15, - 14.1, - 13.3, - 12.6, - 12, - 11.3, - 10.7, - 10.2, - 9.6, - 9, - 8.6, - 8.3, - 8.3, - 8.1, - 7.8, - 7.5, - 6.9, - 6.2, - 5.7, - 5.5, - 5.2, - 4.9, - 4.7, - 4.4, - 4.1, - 3.9, - 3.8, - 3.7, - 3.6, - 3.5, - 3.3, - 3.2, - 3.1, - 3, - 2.9, - 2.7, - 2.6, - 2.6, - 2.5, - 2.4, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.4, - 3.4, - 3.5, - 3.6, - 3.6, - 3.6, - 3.6, - 3.6, - 3.7, - 3.7, - 3.7, - 3.7, - 3.7, - 3.8, - 4, - 4.2, - 4.3, - 4.3, - 4.4, - 4.6, - 4.7, - 4.9, - 5, - 5.2, - 5.4, - 5.7, - 5.9, - 6.2, - 6.3, - 6.3, - 6.2, - 5.7, - 5.5, - 5.4, - 5.4, - 5.4, - 5.5, - 5.6, - 5.7, - 6, - 6.2, - 6.4, - 54.5, - 54.2, - 53.8, - 53.6, - 53.4, - 53.2, - 53.1, - 52.9, - 52.7, - 52.3, - 52, - 51.6, - 51.2, - 50.8, - 50.4, - 49.9, - 49.6, - 49.2, - 48.9, - 48.5, - 48.3, - 48, - 47.7, - 47.3, - 46.9, - 46.5, - 46, - 45.5, - 45.1, - 44.5, - 44.1, - 43.9, - 43.7, - 43.4, - 43.1, - 42.7, - 42.2, - 41.7, - 41.3, - 40.9, - 40.5, - 40, - 39.6, - 39.5, - 39.2, - 38.8, - 38.6, - 38.3, - 38, - 37.9, - 37.8, - 37.5, - 37.1, - 36.6, - 36.1, - 35.7, - 35.3, - 34.7, - 34.3, - 33.8, - 33.2, - 32.7, - 32.1, - 31.3, - 30.8, - 30.2, - 29.6, - 29.1, - 28.6, - 28.2, - 28.1, - 27.9, - 27.8, - 27.4, - 27.1, - 26.7, - 26.3, - 26, - 25.7, - 25.5, - 25.3, - 25.1, - 24.7, - 24.3, - 23.7, - 23.3, - 22.8, - 22.5, - 22.3, - 22, - 21.5, - 21, - 20.6, - 19.9, - 20, - 20, - 20, - 19.8, - 19.7, - 19.5, - 19.4, - 19.2, - 19, - 18.7, - 17.7, - 16.6, - 15.5, - 14.5, - 13.6, - 12.8, - 12.2, - 11.6, - 11.1, - 10.4, - 9.8, - 9.3, - 8.8, - 8.5, - 8.4, - 8.2, - 8, - 7.7, - 7.4, - 6.6, - 6.1, - 5.7, - 5.3, - 5.1, - 4.8, - 4.5, - 4.2, - 4, - 3.9, - 3.7, - 3.5, - 3.4, - 3.3, - 3.2, - 3.1, - 3, - 2.9, - 2.8, - 2.7, - 2.7, - 2.6, - 2.5, - 2.4, - 2.4, - 2.4, - 2.5, - 2.4, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.4, - 2.4, - 2.5, - 2.6, - 2.7, - 2.7, - 2.8, - 2.9, - 3, - 3.2, - 3.2, - 3.3, - 3.3, - 3.5, - 3.5, - 3.5, - 3.5, - 3.5, - 3.5, - 3.5, - 3.5, - 3.5, - 3.6, - 3.6, - 3.6, - 3.7, - 3.9, - 4, - 4.1, - 4.1, - 4.3, - 4.5, - 4.6, - 4.8, - 5, - 5.3, - 5.5, - 5.7, - 5.8, - 5.9, - 5.7, - 5.5, - 5.1, - 5, - 5.1, - 5.2, - 5.6, - 5.8, - 5.7, - 6.1, - 6.8, - 7.2, - 7.4, - 54.5, - 54.1, - 53.8, - 53.5, - 53.3, - 53.1, - 53, - 52.8, - 52.6, - 52.3, - 52, - 51.7, - 51.3, - 50.8, - 50.3, - 49.9, - 49.5, - 49.2, - 48.8, - 48.5, - 48.2, - 48, - 47.6, - 47.3, - 46.9, - 46.4, - 45.9, - 45.4, - 44.9, - 44.6, - 44.3, - 44.1, - 43.7, - 43.4, - 43.1, - 42.6, - 42.2, - 41.6, - 41.3, - 41, - 40.5, - 40.1, - 39.7, - 39.3, - 39.1, - 38.8, - 38.5, - 38.3, - 38.2, - 38, - 37.8, - 37.5, - 37.1, - 36.6, - 36.2, - 35.8, - 35.3, - 34.6, - 34, - 33.4, - 33, - 32.6, - 32, - 31.4, - 30.8, - 30.2, - 29.6, - 29.1, - 28.6, - 28.6, - 28.4, - 28.1, - 28, - 27.6, - 27.4, - 26.9, - 26.5, - 26.1, - 25.9, - 25.7, - 25.5, - 25.3, - 24.9, - 24.5, - 24, - 23.5, - 23.1, - 22.9, - 22.7, - 22.4, - 22, - 21.6, - 21, - 20, - 20.1, - 20.1, - 20.1, - 20, - 19.8, - 19.6, - 19.4, - 19.2, - 19.1, - 18.9, - 18.4, - 17.4, - 16.2, - 15, - 13.9, - 13.1, - 12.3, - 11.7, - 11.4, - 10.9, - 10.1, - 9.3, - 8.9, - 8.7, - 8.5, - 8.3, - 8.1, - 7.9, - 7.7, - 7.1, - 6.5, - 6, - 5.6, - 5.3, - 5.1, - 4.7, - 4.5, - 4.2, - 4.1, - 3.7, - 3.5, - 3.4, - 3.3, - 3.3, - 3.1, - 3, - 2.9, - 2.9, - 2.9, - 2.8, - 2.7, - 2.6, - 2.5, - 2.5, - 2.5, - 2.6, - 2.5, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.4, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.8, - 3, - 3.1, - 3.1, - 3.2, - 3.2, - 3.3, - 3.3, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.5, - 3.6, - 3.6, - 3.7, - 3.8, - 3.9, - 4.1, - 4.3, - 4.4, - 4.5, - 4.7, - 5, - 5.2, - 5.4, - 5.5, - 5.3, - 5.1, - 5, - 4.9, - 5, - 5.2, - 5.6, - 6.2, - 6.7, - 7, - 7.3, - 7.8, - 8.1, - 8.4, - 54.4, - 54.1, - 53.8, - 53.6, - 53.2, - 53, - 52.9, - 52.7, - 52.6, - 52.3, - 51.9, - 51.7, - 51.2, - 50.6, - 50.2, - 49.9, - 49.5, - 49.3, - 49, - 48.5, - 48.2, - 48, - 47.6, - 47.3, - 46.9, - 46.5, - 46, - 45.4, - 44.9, - 44.7, - 44.4, - 44.2, - 43.8, - 43.5, - 43, - 42.5, - 42.2, - 41.6, - 41.2, - 40.9, - 40.6, - 40.2, - 40, - 39.7, - 39.3, - 38.9, - 38.7, - 38.4, - 38.4, - 38.1, - 37.7, - 37.4, - 37.1, - 36.6, - 36.1, - 35.7, - 35.3, - 34.8, - 34.2, - 33.6, - 33.1, - 32.5, - 32, - 31.5, - 31, - 30.3, - 29.7, - 29.2, - 29, - 28.8, - 28.6, - 28.4, - 28.2, - 27.8, - 27.6, - 27.1, - 26.7, - 26.3, - 26, - 25.9, - 25.8, - 25.5, - 25.1, - 24.7, - 24.3, - 23.9, - 23.5, - 23.2, - 22.9, - 22.7, - 22.3, - 21.8, - 20.8, - 20.2, - 20.2, - 20.2, - 20.2, - 20.1, - 19.9, - 19.7, - 19.5, - 19.3, - 19.1, - 18.9, - 18.7, - 18, - 17, - 15.7, - 14.5, - 13.5, - 12.7, - 12, - 11.6, - 11.1, - 10.5, - 9.7, - 8.9, - 8.6, - 8.5, - 8.4, - 8.2, - 8, - 7.8, - 7.5, - 6.8, - 6.2, - 5.9, - 5.5, - 5.2, - 4.9, - 4.7, - 4.4, - 4.2, - 3.9, - 3.7, - 3.5, - 3.4, - 3.3, - 3.3, - 3.1, - 3, - 2.9, - 2.9, - 2.8, - 2.6, - 2.6, - 2.6, - 2.5, - 2.6, - 2.5, - 2.5, - 2.4, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.3, - 2.3, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.8, - 2.9, - 2.9, - 3, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.3, - 3.3, - 3.3, - 3.3, - 3.2, - 3.3, - 3.3, - 3.3, - 3.4, - 3.4, - 3.5, - 3.6, - 3.7, - 3.8, - 3.9, - 4.1, - 4.2, - 4.3, - 4.6, - 4.8, - 5, - 5.2, - 5.2, - 4.9, - 4.9, - 4.7, - 4.7, - 4.9, - 5.5, - 6, - 6.5, - 7.2, - 7.7, - 8.1, - 8.5, - 8.8, - 9.1, - 54.4, - 54.1, - 53.7, - 53.5, - 53.2, - 52.9, - 52.8, - 52.6, - 52.4, - 52.2, - 52, - 51.6, - 51.1, - 50.7, - 50.4, - 49.9, - 49.5, - 49.2, - 48.8, - 48.5, - 48.2, - 48, - 47.7, - 47.4, - 47, - 46.5, - 46.2, - 45.7, - 45.2, - 44.8, - 44.4, - 44.1, - 43.7, - 43.3, - 42.9, - 42.5, - 42.1, - 41.6, - 41.1, - 40.9, - 40.6, - 40.4, - 40.1, - 40, - 39.4, - 39.2, - 38.9, - 38.7, - 38.5, - 38, - 37.7, - 37.3, - 36.9, - 36.4, - 35.9, - 35.6, - 35.2, - 34.8, - 34.2, - 33.7, - 33.2, - 32.7, - 32.2, - 31.6, - 31, - 30.4, - 29.8, - 29.5, - 29.3, - 29, - 28.7, - 28.6, - 28.3, - 28, - 27.7, - 27.2, - 26.9, - 26.5, - 26.2, - 26, - 25.8, - 25.6, - 25.3, - 24.8, - 24.5, - 24.1, - 23.8, - 23.5, - 23.2, - 23, - 22.5, - 21.9, - 21, - 20.3, - 20.4, - 20.4, - 20.3, - 20.2, - 20, - 19.8, - 19.5, - 19.3, - 19.1, - 18.9, - 18.8, - 18.5, - 17.6, - 16.4, - 15.1, - 14.1, - 13.2, - 12.4, - 11.8, - 11.2, - 10.6, - 9.8, - 8.9, - 8.6, - 8.5, - 8.4, - 8.3, - 8, - 7.8, - 7.6, - 7, - 6.3, - 6, - 5.7, - 5.4, - 5.1, - 4.9, - 4.6, - 4.3, - 4.1, - 3.9, - 3.6, - 3.4, - 3.4, - 3.3, - 3.2, - 3.1, - 3, - 2.9, - 2.9, - 2.8, - 2.7, - 2.6, - 2.6, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.7, - 2.8, - 2.8, - 2.9, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.2, - 3.2, - 3.4, - 3.4, - 3.4, - 3.5, - 3.6, - 3.7, - 3.9, - 4, - 4.2, - 4.4, - 4.7, - 4.9, - 5, - 4.9, - 4.7, - 4.7, - 4.6, - 4.7, - 4.9, - 5.4, - 6.1, - 6.7, - 7.4, - 7.9, - 8.3, - 8.6, - 9, - 9.2, - 54.4, - 54.1, - 53.7, - 53.4, - 53.2, - 52.9, - 52.8, - 52.6, - 52.3, - 52.1, - 51.8, - 51.5, - 51.1, - 50.7, - 50.4, - 49.9, - 49.4, - 49, - 48.6, - 48.4, - 48.1, - 47.9, - 47.7, - 47.3, - 47, - 46.7, - 46.5, - 45.9, - 45.4, - 45, - 44.4, - 44, - 43.5, - 43.3, - 42.9, - 42.5, - 42, - 41.6, - 41.2, - 41.1, - 40.8, - 40.5, - 40.3, - 40.1, - 39.7, - 39.5, - 39.2, - 38.8, - 38.5, - 38.2, - 37.9, - 37.4, - 36.9, - 36.3, - 35.9, - 35.5, - 35.2, - 34.8, - 34.3, - 33.8, - 33.3, - 32.8, - 32.4, - 31.8, - 31.1, - 30.5, - 30, - 29.8, - 29.5, - 29.2, - 29, - 28.8, - 28.4, - 28.1, - 27.8, - 27.4, - 27.1, - 26.8, - 26.4, - 26.1, - 25.9, - 25.7, - 25.3, - 25, - 24.6, - 24.3, - 24, - 23.7, - 23.5, - 23.3, - 22.8, - 22, - 21.1, - 20.4, - 20.5, - 20.5, - 20.5, - 20.3, - 20.2, - 19.9, - 19.6, - 19.3, - 19.1, - 18.9, - 18.8, - 18.6, - 18.3, - 17.2, - 15.9, - 14.7, - 13.6, - 12.9, - 12.1, - 11.4, - 10.6, - 9.5, - 8.8, - 8.6, - 8.6, - 8.6, - 8.3, - 8.1, - 7.9, - 7.7, - 7.2, - 6.6, - 6.2, - 5.9, - 5.7, - 5.3, - 5.1, - 4.8, - 4.4, - 4.2, - 4, - 3.9, - 3.6, - 3.6, - 3.4, - 3.3, - 3.3, - 3.1, - 3, - 2.9, - 2.8, - 2.8, - 2.7, - 2.7, - 2.6, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3, - 3.1, - 3, - 3, - 3.1, - 3.1, - 3.2, - 3.2, - 3.3, - 3.4, - 3.5, - 3.6, - 3.7, - 3.8, - 4.1, - 4.3, - 4.5, - 4.7, - 4.8, - 4.7, - 4.6, - 4.6, - 4.5, - 4.6, - 4.8, - 5.3, - 5.9, - 6.6, - 7.3, - 7.8, - 8.1, - 8.4, - 8.8, - 9.2, - 54.4, - 54.1, - 53.6, - 53.4, - 53.1, - 52.8, - 52.7, - 52.5, - 52.3, - 51.9, - 51.7, - 51.3, - 50.9, - 50.6, - 50.2, - 49.7, - 49.3, - 48.9, - 48.5, - 48.1, - 47.9, - 47.7, - 47.5, - 47.1, - 46.9, - 46.7, - 46.3, - 45.9, - 45.4, - 45, - 44.5, - 43.9, - 43.4, - 43.1, - 42.9, - 42.5, - 42, - 41.6, - 41.3, - 41.1, - 41, - 40.6, - 40.4, - 40.3, - 39.9, - 39.6, - 39.3, - 38.9, - 38.7, - 38.4, - 38, - 37.5, - 37, - 36.4, - 36, - 35.6, - 35.2, - 34.8, - 34.4, - 33.9, - 33.4, - 32.9, - 32.5, - 31.9, - 31.3, - 30.7, - 30.3, - 30, - 29.6, - 29.3, - 29.1, - 28.9, - 28.6, - 28.3, - 28, - 27.6, - 27.3, - 26.9, - 26.5, - 26.2, - 26, - 25.8, - 25.6, - 25.1, - 24.8, - 24.5, - 24.2, - 24, - 23.7, - 23.6, - 23, - 22.1, - 21.1, - 20.6, - 20.6, - 20.7, - 20.6, - 20.5, - 20.3, - 20, - 19.7, - 19.3, - 19.1, - 18.9, - 18.7, - 18.6, - 18.4, - 17.9, - 16.6, - 15.4, - 14.1, - 13.1, - 12.5, - 11.7, - 10.6, - 9.3, - 8.7, - 8.6, - 8.6, - 8.5, - 8.3, - 8.2, - 8, - 7.8, - 7.4, - 6.8, - 6.5, - 6.3, - 5.9, - 5.5, - 5.3, - 5, - 4.7, - 4.4, - 4.2, - 4, - 3.8, - 3.7, - 3.5, - 3.3, - 3.3, - 3.2, - 3.1, - 3, - 3, - 2.9, - 2.8, - 2.7, - 2.6, - 2.5, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.4, - 3.5, - 3.7, - 3.7, - 4, - 4.1, - 4.3, - 4.5, - 4.5, - 4.5, - 4.5, - 4.5, - 4.5, - 4.6, - 4.7, - 5, - 5.6, - 6.3, - 6.8, - 7.3, - 7.8, - 8.1, - 8.4, - 8.8, - 54.4, - 54.1, - 53.7, - 53.3, - 52.9, - 52.7, - 52.5, - 52.3, - 52.2, - 52, - 51.8, - 51.5, - 50.9, - 50.5, - 50.1, - 49.7, - 49.3, - 49.1, - 48.7, - 48.1, - 47.9, - 47.8, - 47.5, - 47.2, - 46.9, - 46.5, - 46.3, - 46, - 45.5, - 44.9, - 44.5, - 43.9, - 43.3, - 43, - 42.9, - 42.6, - 42.1, - 41.7, - 41.6, - 41.4, - 41.1, - 40.8, - 40.5, - 40.3, - 39.9, - 39.7, - 39.4, - 39, - 38.7, - 38.5, - 38, - 37.4, - 36.8, - 36.4, - 36.1, - 35.8, - 35.3, - 35, - 34.5, - 33.9, - 33.5, - 33, - 32.5, - 31.9, - 31.3, - 30.8, - 30.4, - 29.9, - 29.7, - 29.5, - 29.3, - 29, - 28.7, - 28.4, - 28.1, - 27.8, - 27.5, - 27.1, - 26.7, - 26.5, - 26.2, - 26, - 25.7, - 25.2, - 24.9, - 24.7, - 24.5, - 24.2, - 24, - 24, - 23.6, - 22.4, - 21.2, - 20.7, - 20.8, - 20.8, - 20.7, - 20.6, - 20.4, - 20.1, - 19.7, - 19.3, - 19, - 18.8, - 18.7, - 18.5, - 18.4, - 18.1, - 17.4, - 16.1, - 14.7, - 13.6, - 12.7, - 12.1, - 10.8, - 9.6, - 9, - 8.8, - 8.6, - 8.5, - 8.4, - 8.3, - 8.1, - 7.9, - 7.5, - 7.1, - 6.9, - 6.4, - 6.1, - 5.7, - 5.5, - 5.2, - 4.8, - 4.5, - 4.3, - 4.1, - 3.9, - 3.7, - 3.5, - 3.4, - 3.4, - 3.3, - 3.2, - 3.2, - 3.1, - 3.1, - 2.9, - 2.8, - 2.8, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.6, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.3, - 3.5, - 3.6, - 3.6, - 3.8, - 4, - 4.1, - 4.3, - 4.2, - 4.4, - 4.4, - 4.3, - 4.4, - 4.5, - 4.7, - 4.9, - 5.3, - 6, - 6.4, - 6.8, - 7.2, - 7.6, - 7.9, - 8.3, - 54.3, - 54, - 53.7, - 53.3, - 53.1, - 52.8, - 52.6, - 52.4, - 52.2, - 52.1, - 51.8, - 51.4, - 50.8, - 50.4, - 50.1, - 49.8, - 49.5, - 49.3, - 49.1, - 48.7, - 48.2, - 47.8, - 47.6, - 47.4, - 47.1, - 46.7, - 46.3, - 45.9, - 45.4, - 45, - 44.4, - 44, - 43.6, - 43.3, - 43, - 42.6, - 42.2, - 42, - 41.9, - 41.6, - 41.4, - 41.1, - 40.8, - 40.6, - 40.1, - 39.9, - 39.5, - 39.1, - 38.8, - 38.5, - 38, - 37.4, - 36.8, - 36.4, - 36, - 35.7, - 35.3, - 34.9, - 34.5, - 33.9, - 33.4, - 33.1, - 32.5, - 32.1, - 31.5, - 31, - 30.5, - 30.2, - 30, - 29.9, - 29.5, - 29.1, - 28.8, - 28.6, - 28.2, - 28, - 27.6, - 27.2, - 26.9, - 26.7, - 26.4, - 26.1, - 25.7, - 25.4, - 25.1, - 24.9, - 24.7, - 24.5, - 24.3, - 24.4, - 24.3, - 23.7, - 22.1, - 20.8, - 20.9, - 20.9, - 20.9, - 20.8, - 20.6, - 20.3, - 19.8, - 19.4, - 18.9, - 18.7, - 18.6, - 18.4, - 18.3, - 18.2, - 17.8, - 16.7, - 15.4, - 14.2, - 13.1, - 12.3, - 11.6, - 10.5, - 9.5, - 9, - 8.8, - 8.6, - 8.4, - 8.3, - 8.1, - 7.9, - 7.6, - 7.3, - 7, - 6.6, - 6.1, - 5.9, - 5.5, - 5.2, - 4.8, - 4.6, - 4.4, - 4.2, - 4, - 3.9, - 3.7, - 3.5, - 3.4, - 3.4, - 3.3, - 3.2, - 3.2, - 3, - 2.9, - 2.8, - 2.7, - 2.6, - 2.5, - 2.4, - 2.4, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.7, - 2.8, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.9, - 2.8, - 2.9, - 2.9, - 3, - 3.1, - 3.2, - 3.4, - 3.5, - 3.6, - 3.7, - 3.9, - 4.1, - 4.1, - 4.2, - 4.3, - 4.3, - 4.3, - 4.3, - 4.4, - 4.7, - 4.9, - 5.2, - 5.7, - 6, - 6.3, - 6.7, - 7, - 7.3, - 7.8, - 54.4, - 54.1, - 53.7, - 53.4, - 53.1, - 52.8, - 52.7, - 52.4, - 52.2, - 51.9, - 51.5, - 51.2, - 50.9, - 50.6, - 50.3, - 50.1, - 49.9, - 49.8, - 49.6, - 49.1, - 48.6, - 48, - 47.7, - 47.4, - 47.2, - 47, - 46.5, - 45.9, - 45.5, - 45.2, - 44.8, - 44.3, - 43.8, - 43.3, - 43, - 42.8, - 42.5, - 42.3, - 42, - 41.9, - 41.7, - 41.3, - 40.9, - 40.6, - 40.2, - 40, - 39.7, - 39.3, - 38.9, - 38.5, - 38, - 37.4, - 36.9, - 36.5, - 36.1, - 35.7, - 35.4, - 34.9, - 34.5, - 34, - 33.5, - 33.1, - 32.5, - 32.1, - 31.7, - 31.2, - 30.7, - 30.3, - 30.2, - 30, - 29.6, - 29.2, - 29.1, - 28.8, - 28.4, - 28, - 27.7, - 27.4, - 27.1, - 26.9, - 26.6, - 26.3, - 25.9, - 25.6, - 25.4, - 25.1, - 24.9, - 24.6, - 24.4, - 24.7, - 24.7, - 24.1, - 22.3, - 21, - 21, - 21.1, - 21, - 20.9, - 20.8, - 20.5, - 20.1, - 19.7, - 19, - 18.5, - 18.4, - 18.3, - 18.2, - 18.1, - 17.9, - 17.2, - 16, - 14.8, - 13.6, - 12.7, - 11.9, - 11, - 9.9, - 9.1, - 8.9, - 8.6, - 8.4, - 8.3, - 8.2, - 8, - 7.8, - 7.5, - 7.2, - 6.7, - 6.3, - 6.1, - 5.8, - 5.3, - 4.9, - 4.7, - 4.5, - 4.2, - 4, - 3.8, - 3.6, - 3.4, - 3.5, - 3.4, - 3.3, - 3.2, - 3.1, - 3, - 2.9, - 2.8, - 2.7, - 2.7, - 2.5, - 2.4, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 3, - 3, - 3.1, - 3.3, - 3.4, - 3.5, - 3.6, - 3.9, - 4, - 4.1, - 4.2, - 4.2, - 4.2, - 4.3, - 4.3, - 4.4, - 4.7, - 4.9, - 5.2, - 5.4, - 5.8, - 6, - 6.3, - 6.6, - 7, - 7.4, - 54.4, - 54.1, - 53.8, - 53.5, - 53, - 52.8, - 52.7, - 52.3, - 52.2, - 51.9, - 51.7, - 51.4, - 51.1, - 50.9, - 50.7, - 50.5, - 50.3, - 50.1, - 50, - 49.5, - 48.8, - 48.2, - 47.8, - 47.5, - 47.2, - 46.9, - 46.5, - 46.2, - 45.7, - 45.5, - 45.2, - 44.7, - 44, - 43.3, - 43.1, - 42.9, - 42.8, - 42.5, - 42.3, - 41.9, - 41.7, - 41.3, - 41, - 40.6, - 40.3, - 40.1, - 39.8, - 39.3, - 39, - 38.5, - 37.9, - 37.5, - 36.9, - 36.5, - 36.1, - 35.8, - 35.5, - 35.1, - 34.7, - 34.1, - 33.7, - 33.2, - 32.7, - 32.3, - 31.8, - 31.4, - 30.9, - 30.5, - 30.3, - 30.1, - 29.7, - 29.4, - 29.2, - 29, - 28.6, - 28.2, - 27.8, - 27.5, - 27.3, - 27.1, - 26.7, - 26.4, - 26, - 25.8, - 25.6, - 25.3, - 25.1, - 24.7, - 24.6, - 24.8, - 24.9, - 24.1, - 22.1, - 21.2, - 21.2, - 21.2, - 21.2, - 21.1, - 21, - 20.7, - 20.2, - 19.9, - 19.2, - 18.5, - 18.2, - 18.1, - 18.1, - 18, - 17.8, - 17.5, - 16.5, - 15.3, - 14.1, - 12.9, - 12.1, - 11.2, - 10.5, - 9.6, - 9.2, - 8.8, - 8.6, - 8.4, - 8.4, - 8.2, - 7.9, - 7.5, - 7.2, - 6.8, - 6.5, - 6.2, - 6, - 5.5, - 5, - 4.7, - 4.4, - 4.3, - 4.1, - 3.9, - 3.6, - 3.5, - 3.5, - 3.5, - 3.4, - 3.3, - 3.2, - 3.1, - 3, - 2.9, - 2.7, - 2.7, - 2.6, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.7, - 2.7, - 2.6, - 2.6, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 3, - 3.1, - 3.3, - 3.3, - 3.4, - 3.5, - 3.7, - 3.9, - 4, - 4, - 4, - 4.1, - 4.1, - 4.2, - 4.4, - 4.6, - 4.8, - 5, - 5.1, - 5.4, - 5.6, - 5.9, - 6.2, - 6.5, - 6.7, - 54.4, - 54.1, - 53.8, - 53.4, - 53.1, - 52.9, - 52.7, - 52.5, - 52.2, - 52.1, - 51.9, - 51.8, - 51.6, - 51.2, - 51.1, - 50.8, - 50.6, - 50.4, - 50.1, - 49.7, - 49, - 48.6, - 48.1, - 47.7, - 47.4, - 47, - 46.7, - 46.4, - 45.9, - 45.7, - 45.3, - 44.8, - 44.3, - 43.7, - 43.3, - 43.2, - 43, - 42.7, - 42.5, - 42, - 41.8, - 41.3, - 41, - 40.8, - 40.3, - 40.1, - 39.8, - 39.4, - 39, - 38.4, - 37.8, - 37.5, - 36.9, - 36.5, - 36.2, - 35.9, - 35.5, - 35.1, - 34.7, - 34.2, - 33.8, - 33.4, - 32.9, - 32.5, - 32.1, - 31.6, - 31.1, - 30.7, - 30.4, - 30.2, - 29.9, - 29.6, - 29.4, - 29.1, - 28.7, - 28.4, - 28, - 27.7, - 27.5, - 27.3, - 26.9, - 26.5, - 26.2, - 26, - 25.8, - 25.6, - 25.3, - 25, - 25, - 25.1, - 25.1, - 24.3, - 22.1, - 21.3, - 21.3, - 21.4, - 21.3, - 21.2, - 21.1, - 20.8, - 20.3, - 20, - 19.5, - 18.6, - 18.1, - 17.9, - 17.9, - 17.8, - 17.7, - 17.6, - 17.1, - 15.8, - 14.5, - 13.4, - 12.4, - 11.6, - 10.9, - 10.1, - 9.4, - 8.9, - 8.6, - 8.4, - 8.4, - 8.2, - 8.1, - 7.7, - 7.4, - 7, - 6.6, - 6.3, - 6, - 5.6, - 5.1, - 4.8, - 4.5, - 4.2, - 4, - 3.8, - 3.7, - 3.7, - 3.6, - 3.6, - 3.4, - 3.3, - 3.2, - 3.1, - 3, - 2.9, - 2.8, - 2.8, - 2.6, - 2.5, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.6, - 2.6, - 2.7, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.4, - 3.4, - 3.5, - 3.7, - 3.8, - 3.8, - 3.9, - 4, - 4, - 4.2, - 4.4, - 4.5, - 4.6, - 4.8, - 5, - 5.1, - 5.3, - 5.5, - 5.8, - 6, - 6.2, - 54.3, - 54, - 53.7, - 53.5, - 53.3, - 53.3, - 53.1, - 52.7, - 52.4, - 52.2, - 52.1, - 52, - 51.9, - 51.5, - 51.3, - 51.1, - 51, - 50.8, - 50.5, - 50.2, - 49.3, - 48.9, - 48.3, - 47.9, - 47.5, - 47.2, - 46.9, - 46.7, - 46.2, - 45.9, - 45.5, - 45.1, - 44.6, - 44.1, - 43.6, - 43.4, - 43.2, - 42.7, - 42.4, - 42.2, - 42, - 41.6, - 41.2, - 40.8, - 40.4, - 40.2, - 39.9, - 39.5, - 39, - 38.4, - 38.1, - 37.6, - 37.1, - 36.7, - 36.3, - 36, - 35.7, - 35.3, - 34.8, - 34.4, - 33.9, - 33.6, - 33.1, - 32.6, - 32.3, - 31.7, - 31.3, - 30.9, - 30.6, - 30.4, - 30.1, - 29.8, - 29.5, - 29.1, - 28.9, - 28.6, - 28.2, - 27.9, - 27.7, - 27.4, - 27.1, - 26.7, - 26.4, - 26.2, - 26, - 25.7, - 25.4, - 25.2, - 25.3, - 25.4, - 25.3, - 24.8, - 22.9, - 21.4, - 21.5, - 21.5, - 21.5, - 21.4, - 21.3, - 21.2, - 20.7, - 20.2, - 19.5, - 18.5, - 17.9, - 17.7, - 17.7, - 17.7, - 17.6, - 17.5, - 17.2, - 16.3, - 15, - 13.7, - 12.6, - 11.8, - 11.1, - 10.2, - 9.6, - 9.1, - 8.7, - 8.5, - 8.4, - 8.4, - 8.2, - 8, - 7.5, - 7.1, - 6.7, - 6.3, - 6, - 5.6, - 5.2, - 4.8, - 4.4, - 4.1, - 3.8, - 3.8, - 3.8, - 3.9, - 3.8, - 3.7, - 3.6, - 3.5, - 3.3, - 3.2, - 3.1, - 3, - 2.9, - 2.8, - 2.7, - 2.5, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.8, - 3, - 3, - 3.1, - 3.2, - 3.3, - 3.4, - 3.5, - 3.6, - 3.7, - 3.8, - 3.9, - 3.9, - 4, - 4.2, - 4.2, - 4.4, - 4.5, - 4.7, - 4.9, - 4.9, - 5.1, - 5.2, - 5.5, - 5.8, - 6.1, - 54.2, - 54, - 53.8, - 53.7, - 53.5, - 53.4, - 53.2, - 52.9, - 52.7, - 52.4, - 52.2, - 52.3, - 52.3, - 52, - 51.8, - 51.6, - 51.5, - 51.3, - 50.9, - 50.4, - 49.6, - 49, - 48.5, - 48.2, - 47.7, - 47.5, - 47.2, - 46.8, - 46.5, - 46.2, - 45.8, - 45.4, - 44.8, - 44.5, - 44, - 43.7, - 43.2, - 42.8, - 42.5, - 42.2, - 42, - 41.7, - 41.4, - 40.9, - 40.5, - 40.3, - 39.9, - 39.5, - 38.9, - 38.6, - 38.3, - 37.8, - 37.3, - 36.8, - 36.4, - 36.1, - 35.8, - 35.4, - 34.9, - 34.4, - 34, - 33.7, - 33.3, - 32.7, - 32.4, - 31.9, - 31.4, - 31, - 30.8, - 30.5, - 30.3, - 30, - 29.7, - 29.3, - 29, - 28.7, - 28.4, - 28.1, - 27.9, - 27.5, - 27.3, - 26.9, - 26.6, - 26.3, - 26.1, - 25.9, - 25.5, - 25.3, - 25.4, - 25.5, - 25.5, - 25.2, - 23.3, - 21.5, - 21.6, - 21.6, - 21.6, - 21.5, - 21.5, - 21.4, - 21.3, - 20.7, - 19.6, - 18.6, - 17.8, - 17.5, - 17.5, - 17.5, - 17.4, - 17.3, - 17.2, - 16.7, - 15.5, - 14.2, - 12.9, - 11.9, - 11.2, - 10.4, - 9.7, - 9.1, - 8.7, - 8.6, - 8.4, - 8.5, - 8.3, - 8, - 7.6, - 7.2, - 6.8, - 6.4, - 6, - 5.6, - 5.2, - 4.8, - 4.4, - 4.2, - 4.1, - 4.1, - 4.1, - 4.1, - 4, - 3.8, - 3.7, - 3.6, - 3.5, - 3.3, - 3.2, - 3.1, - 2.9, - 2.8, - 2.6, - 2.6, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.6, - 2.6, - 2.8, - 2.9, - 2.9, - 2.8, - 2.9, - 2.9, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.4, - 3.5, - 3.6, - 3.7, - 3.8, - 3.8, - 3.8, - 4, - 4.2, - 4.2, - 4.4, - 4.6, - 4.7, - 4.9, - 5, - 5.1, - 5.3, - 5.5, - 5.7, - 54.4, - 54.3, - 54.2, - 54.1, - 53.9, - 53.8, - 53.4, - 53.1, - 52.9, - 52.8, - 52.6, - 52.5, - 52.5, - 52.3, - 52.2, - 52, - 51.9, - 51.4, - 50.7, - 50.2, - 49.5, - 48.9, - 48.5, - 48.3, - 48, - 47.7, - 47.4, - 47, - 46.8, - 46.5, - 46.1, - 45.7, - 45.2, - 45, - 44.6, - 43.9, - 43.4, - 42.9, - 42.6, - 42.4, - 42.2, - 41.9, - 41.6, - 41, - 40.5, - 40.3, - 40, - 39.5, - 39, - 38.7, - 38.4, - 37.9, - 37.4, - 36.9, - 36.5, - 36.2, - 35.9, - 35.4, - 34.9, - 34.5, - 34, - 33.7, - 33.3, - 32.9, - 32.4, - 31.9, - 31.5, - 31.1, - 30.9, - 30.8, - 30.5, - 30.2, - 29.9, - 29.5, - 29.3, - 29, - 28.6, - 28.3, - 28, - 27.7, - 27.4, - 27.1, - 26.8, - 26.5, - 26.3, - 26, - 25.6, - 25.5, - 25.6, - 25.7, - 25.7, - 25, - 23.4, - 21.8, - 21.7, - 21.7, - 21.7, - 21.7, - 21.6, - 21.6, - 21.6, - 21.3, - 20, - 18.7, - 17.9, - 17.4, - 17.3, - 17.3, - 17.3, - 17.2, - 17, - 16.8, - 15.9, - 14.7, - 13.4, - 12.3, - 11.4, - 10.6, - 10, - 9.4, - 8.8, - 8.6, - 8.5, - 8.4, - 8.4, - 8.2, - 7.8, - 7.3, - 6.9, - 6.5, - 6.1, - 5.7, - 5.3, - 4.9, - 4.4, - 4.4, - 4.4, - 4.3, - 4.4, - 4.3, - 4.1, - 4, - 3.8, - 3.7, - 3.6, - 3.4, - 3.3, - 3.1, - 3, - 2.9, - 2.7, - 2.6, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.5, - 2.4, - 2.5, - 2.6, - 2.5, - 2.6, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.6, - 2.6, - 2.8, - 2.9, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3.1, - 3.2, - 3.2, - 3.3, - 3.4, - 3.5, - 3.6, - 3.6, - 3.6, - 3.7, - 3.8, - 3.9, - 4.1, - 4.2, - 4.3, - 4.4, - 4.6, - 4.8, - 4.9, - 5, - 5.1, - 5.2, - 5.4, - 54.7, - 54.6, - 54.6, - 54.4, - 54.3, - 54.2, - 53.7, - 53.5, - 53.4, - 53.2, - 53, - 52.9, - 52.9, - 52.8, - 52.5, - 52.2, - 51.8, - 51.4, - 50.7, - 50.1, - 49.6, - 49, - 48.5, - 48.3, - 48.2, - 48, - 47.6, - 47.3, - 47.2, - 46.9, - 46.4, - 45.9, - 45.5, - 45.3, - 44.9, - 44.1, - 43.6, - 43.2, - 42.8, - 42.6, - 42.4, - 42, - 41.5, - 41.1, - 40.7, - 40.3, - 39.9, - 39.6, - 39.1, - 38.6, - 38.3, - 37.8, - 37.4, - 37, - 36.6, - 36.2, - 35.9, - 35.4, - 35, - 34.7, - 34.2, - 33.8, - 33.5, - 33.1, - 32.6, - 32.1, - 31.6, - 31.3, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 29.7, - 29.4, - 29.1, - 28.8, - 28.5, - 28.2, - 27.9, - 27.5, - 27.3, - 27, - 26.8, - 26.5, - 26.1, - 25.8, - 25.8, - 25.8, - 25.8, - 25.7, - 24.8, - 22.9, - 21.8, - 21.9, - 21.9, - 21.9, - 21.8, - 21.7, - 21.7, - 21.7, - 21.6, - 20.6, - 18.8, - 18.1, - 17.4, - 17.1, - 17, - 17.1, - 17, - 16.9, - 16.7, - 16.2, - 15.1, - 13.8, - 12.6, - 11.8, - 11.1, - 10.5, - 9.9, - 9.3, - 8.8, - 8.5, - 8.4, - 8.4, - 8.3, - 7.9, - 7.5, - 7, - 6.6, - 6.2, - 5.8, - 5.3, - 4.8, - 4.5, - 4.5, - 4.6, - 4.6, - 4.6, - 4.5, - 4.3, - 4.1, - 3.9, - 3.8, - 3.7, - 3.5, - 3.4, - 3.2, - 3.1, - 2.9, - 2.8, - 2.7, - 2.5, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.4, - 2.5, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.7, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 2.8, - 2.8, - 2.9, - 3, - 3, - 3.1, - 3.2, - 3.2, - 3.3, - 3.4, - 3.4, - 3.5, - 3.5, - 3.6, - 3.7, - 3.8, - 3.9, - 3.9, - 4.2, - 4.3, - 4.4, - 4.5, - 4.6, - 4.7, - 4.9, - 5, - 5.1, - 5.2, - 54.9, - 54.9, - 54.7, - 54.6, - 54.5, - 54.5, - 54.1, - 53.9, - 53.8, - 53.8, - 53.7, - 53.7, - 53.5, - 53.3, - 53.1, - 52.6, - 52.1, - 51.6, - 50.8, - 50.3, - 49.8, - 49.3, - 48.8, - 48.6, - 48.5, - 48.2, - 47.8, - 47.6, - 47.4, - 47.1, - 46.6, - 46.1, - 45.6, - 45.4, - 44.9, - 44.2, - 43.8, - 43.4, - 43.1, - 42.9, - 42.7, - 42.2, - 41.7, - 41.2, - 40.9, - 40.4, - 40, - 39.6, - 39.1, - 38.7, - 38.3, - 37.9, - 37.5, - 37.1, - 36.8, - 36.4, - 36.1, - 35.6, - 35.2, - 34.9, - 34.5, - 34.1, - 33.6, - 33.2, - 32.6, - 32.1, - 31.7, - 31.4, - 31.2, - 30.9, - 30.6, - 30.4, - 30.3, - 29.8, - 29.5, - 29.3, - 28.9, - 28.6, - 28.4, - 28.1, - 27.8, - 27.5, - 27.2, - 27, - 26.6, - 26.2, - 25.9, - 26, - 26, - 25.9, - 25.7, - 24.6, - 22.6, - 21.9, - 22, - 22, - 22, - 21.9, - 21.8, - 21.7, - 21.7, - 21.6, - 21.5, - 20.2, - 18.4, - 17.6, - 16.9, - 16.8, - 16.8, - 16.8, - 16.7, - 16.6, - 16.3, - 15.6, - 14.3, - 13, - 12, - 11.4, - 10.9, - 10.6, - 9.9, - 9.3, - 8.8, - 8.4, - 8.5, - 8.5, - 8.1, - 7.7, - 7.2, - 6.7, - 6.3, - 5.8, - 5.4, - 5, - 4.8, - 4.7, - 4.8, - 4.8, - 4.7, - 4.6, - 4.5, - 4.3, - 4.1, - 3.9, - 3.8, - 3.6, - 3.6, - 3.5, - 3.4, - 3.2, - 3.1, - 2.9, - 2.7, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3.1, - 3.1, - 3.2, - 3.2, - 3.3, - 3.4, - 3.4, - 3.4, - 3.6, - 3.6, - 3.7, - 3.8, - 3.9, - 4.2, - 4.3, - 4.3, - 4.4, - 4.5, - 4.6, - 4.9, - 5.1, - 5.1, - 5.2, - 55.1, - 54.9, - 54.8, - 54.7, - 54.7, - 54.7, - 54.5, - 54.4, - 54.2, - 54.2, - 54.1, - 54.1, - 53.7, - 53.7, - 53.7, - 53.1, - 52.7, - 52.1, - 51.5, - 50.6, - 50, - 49.5, - 49.2, - 49, - 48.9, - 48.9, - 48.4, - 47.8, - 47.3, - 47, - 46.5, - 46, - 45.7, - 45.4, - 44.9, - 44.4, - 44.1, - 43.7, - 43.3, - 43, - 42.7, - 42.3, - 41.8, - 41.4, - 41.1, - 40.6, - 40.1, - 39.7, - 39.4, - 39, - 38.5, - 38, - 37.6, - 37.2, - 37, - 36.6, - 36.2, - 35.8, - 35.4, - 35.1, - 34.6, - 34.2, - 33.9, - 33.3, - 32.7, - 32.1, - 31.8, - 31.5, - 31.2, - 30.9, - 30.7, - 30.6, - 30.3, - 29.9, - 29.6, - 29.4, - 29.1, - 28.8, - 28.6, - 28.3, - 28, - 27.7, - 27.4, - 27, - 26.6, - 26.3, - 26, - 26.1, - 26.1, - 26, - 25.7, - 24.5, - 22.8, - 22.1, - 22.1, - 22.2, - 22.1, - 22, - 21.9, - 21.7, - 21.6, - 21.6, - 21.5, - 21.4, - 20, - 17.8, - 17.1, - 16.6, - 16.6, - 16.6, - 16.6, - 16.4, - 16.3, - 15.8, - 14.7, - 13.4, - 12.4, - 11.7, - 11.3, - 10.8, - 10.2, - 9.5, - 9, - 8.6, - 8.5, - 8.6, - 8.3, - 7.9, - 7.3, - 6.8, - 6.4, - 5.8, - 5.5, - 5.3, - 5.1, - 5.1, - 5, - 5.1, - 4.9, - 4.7, - 4.6, - 4.4, - 4.4, - 4.2, - 4.1, - 3.9, - 3.8, - 3.6, - 3.5, - 3.4, - 3.2, - 3.1, - 2.8, - 2.6, - 2.4, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.4, - 2.5, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3.1, - 3.1, - 3.2, - 3.3, - 3.3, - 3.3, - 3.4, - 3.5, - 3.6, - 3.7, - 3.7, - 3.9, - 4.1, - 4.1, - 4.1, - 4.3, - 4.5, - 4.6, - 4.9, - 5.1, - 5.3, - 5.3, - 55.1, - 55, - 55, - 55, - 55, - 55, - 54.8, - 54.8, - 54.7, - 54.6, - 54.5, - 54.4, - 54.2, - 54.2, - 53.9, - 53.5, - 53.1, - 52.6, - 52.1, - 51.2, - 50.4, - 50, - 49.7, - 49.5, - 49.4, - 49.1, - 48.6, - 47.9, - 47.3, - 46.8, - 46.4, - 46, - 45.6, - 45.4, - 45.1, - 44.8, - 44.3, - 43.8, - 43.5, - 43, - 42.6, - 42.2, - 41.8, - 41.4, - 41.1, - 40.7, - 40.2, - 39.8, - 39.5, - 39.2, - 38.6, - 38.1, - 37.7, - 37.4, - 37, - 36.6, - 36.3, - 35.9, - 35.6, - 35.3, - 34.7, - 34.3, - 33.9, - 33.3, - 32.8, - 32.2, - 31.9, - 31.6, - 31.3, - 31, - 30.8, - 30.6, - 30.3, - 30.1, - 29.7, - 29.5, - 29.3, - 29, - 28.7, - 28.4, - 28.1, - 27.8, - 27.6, - 27.2, - 26.8, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 25.7, - 24.9, - 23.1, - 22.2, - 22.3, - 22.3, - 22.3, - 22.1, - 21.9, - 21.7, - 21.6, - 21.5, - 21.4, - 21.4, - 20.4, - 18.5, - 17.1, - 16.5, - 16.3, - 16.4, - 16.4, - 16.3, - 16.1, - 15.9, - 15.1, - 13.8, - 12.7, - 12, - 11.5, - 10.9, - 10.4, - 9.7, - 9.1, - 8.6, - 8.6, - 8.6, - 8.4, - 8, - 7.5, - 7, - 6.4, - 6, - 5.8, - 5.6, - 5.3, - 5.2, - 5.2, - 5.1, - 5.1, - 5, - 4.9, - 4.7, - 4.6, - 4.4, - 4.3, - 4.3, - 4.1, - 3.9, - 3.8, - 3.6, - 3.4, - 3.2, - 3, - 2.7, - 2.5, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.4, - 2.5, - 2.4, - 2.5, - 2.6, - 2.6, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3.1, - 3.2, - 3.2, - 3.3, - 3.3, - 3.4, - 3.5, - 3.6, - 3.6, - 3.7, - 3.8, - 3.8, - 3.9, - 4, - 4.1, - 4.3, - 4.7, - 4.9, - 5.1, - 5.3, - 5.5, - 55.4, - 55.4, - 55.3, - 55.3, - 55.3, - 55.2, - 55.2, - 55.2, - 55.1, - 55, - 54.9, - 54.5, - 54.5, - 54.4, - 54, - 53.5, - 53.1, - 52.7, - 52.2, - 51.6, - 50.9, - 50.5, - 50.2, - 50, - 49.6, - 49.3, - 48.7, - 47.9, - 47.5, - 47, - 46.5, - 46.1, - 45.7, - 45.4, - 45.3, - 44.9, - 44.5, - 44, - 43.5, - 42.9, - 42.6, - 42.2, - 41.8, - 41.5, - 41.2, - 40.7, - 40.2, - 39.9, - 39.6, - 39.3, - 38.7, - 38.1, - 37.7, - 37.4, - 37, - 36.8, - 36.4, - 36, - 35.7, - 35.3, - 34.9, - 34.4, - 33.9, - 33.5, - 33, - 32.5, - 32.1, - 31.8, - 31.5, - 31.1, - 30.8, - 30.6, - 30.4, - 30.1, - 29.8, - 29.7, - 29.4, - 29.2, - 28.9, - 28.5, - 28.3, - 28, - 27.8, - 27.4, - 26.9, - 26.7, - 26.5, - 26.3, - 26.2, - 26.2, - 25.7, - 25.1, - 23.4, - 22.3, - 22.4, - 22.4, - 22.4, - 22.3, - 21.8, - 21.3, - 21.3, - 21.3, - 21.3, - 21.2, - 21.2, - 20, - 17.2, - 16.5, - 16.1, - 16.2, - 16.2, - 16.1, - 16, - 15.8, - 15.4, - 14.2, - 13, - 12.1, - 11.6, - 11.1, - 10.6, - 10, - 9.3, - 8.8, - 8.7, - 8.7, - 8.6, - 8.1, - 7.6, - 7, - 6.5, - 6.1, - 6, - 5.7, - 5.4, - 5.3, - 5.4, - 5.3, - 5.3, - 5.4, - 5.2, - 5, - 4.8, - 4.7, - 4.5, - 4.5, - 4.3, - 4.3, - 4, - 3.8, - 3.6, - 3.4, - 3.1, - 2.9, - 2.6, - 2.4, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.7, - 2.7, - 2.8, - 2.9, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3.1, - 3.1, - 3.2, - 3.3, - 3.3, - 3.4, - 3.4, - 3.5, - 3.6, - 3.6, - 3.7, - 3.8, - 3.9, - 4, - 4, - 4.1, - 4.4, - 4.8, - 5.1, - 5.3, - 5.5, - 55.7, - 55.8, - 55.8, - 55.6, - 55.4, - 55.5, - 55.7, - 55.6, - 55.6, - 55.3, - 55, - 54.8, - 54.5, - 54.3, - 53.8, - 53.5, - 53.1, - 52.6, - 52.3, - 51.6, - 51.1, - 50.8, - 50.6, - 50.3, - 49.9, - 49.4, - 48.7, - 48, - 47.7, - 47.3, - 46.7, - 46.3, - 45.8, - 45.5, - 45.3, - 45, - 44.5, - 44.1, - 43.6, - 43.1, - 42.5, - 42.1, - 41.7, - 41.4, - 41.1, - 40.7, - 40.2, - 40, - 39.7, - 39.3, - 38.8, - 38.3, - 37.9, - 37.5, - 37.2, - 36.8, - 36.5, - 36.1, - 35.7, - 35.4, - 35, - 34.6, - 34.1, - 33.6, - 33.1, - 32.7, - 32.3, - 31.9, - 31.5, - 31.3, - 30.9, - 30.6, - 30.4, - 30.1, - 30, - 29.8, - 29.5, - 29.3, - 29.1, - 28.9, - 28.5, - 28.2, - 27.8, - 27.5, - 27.2, - 26.8, - 26.6, - 26.4, - 26.3, - 26.2, - 25.6, - 24.8, - 23.5, - 22.5, - 22.5, - 22.6, - 22.5, - 22.3, - 21.7, - 21.1, - 21, - 21.1, - 21.1, - 21.1, - 21.1, - 20.7, - 17.9, - 16.6, - 15.9, - 15.9, - 16, - 15.9, - 15.8, - 15.7, - 15.4, - 14.7, - 13.4, - 12.3, - 11.7, - 11.3, - 10.9, - 10.2, - 9.5, - 8.9, - 8.7, - 8.7, - 8.6, - 8.3, - 7.7, - 7, - 6.8, - 6.5, - 6.2, - 5.8, - 5.6, - 5.5, - 5.6, - 5.6, - 5.6, - 5.6, - 5.6, - 5.3, - 5.1, - 5, - 4.8, - 4.7, - 4.6, - 4.4, - 4.2, - 4, - 3.8, - 3.5, - 3.3, - 3.1, - 2.8, - 2.5, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 2.8, - 2.9, - 3, - 3, - 3.1, - 3.1, - 3.2, - 3.2, - 3.3, - 3.3, - 3.4, - 3.5, - 3.6, - 3.6, - 3.7, - 3.8, - 3.9, - 3.9, - 4, - 4.1, - 4.3, - 4.7, - 5, - 5.3, - 5.4, - 56.1, - 56.2, - 56, - 55.8, - 55.7, - 55.8, - 56.1, - 56.2, - 56.1, - 55.6, - 55.2, - 54.9, - 54.6, - 54.3, - 53.9, - 53.5, - 53.1, - 52.7, - 52.4, - 51.9, - 51.5, - 51.2, - 51, - 50.6, - 50, - 49.4, - 48.8, - 48.3, - 47.8, - 47.5, - 47, - 46.5, - 46, - 45.7, - 45.5, - 44.9, - 44.4, - 43.9, - 43.6, - 43, - 42.5, - 42, - 41.7, - 41.4, - 41.2, - 40.8, - 40.4, - 40.1, - 39.8, - 39.4, - 38.9, - 38.4, - 38, - 37.7, - 37.3, - 36.9, - 36.5, - 36.2, - 35.8, - 35.5, - 35.1, - 34.7, - 34.2, - 33.7, - 33.2, - 32.9, - 32.5, - 32.1, - 31.8, - 31.3, - 31, - 30.7, - 30.5, - 30.2, - 30.1, - 29.9, - 29.7, - 29.4, - 29.2, - 28.9, - 28.7, - 28.3, - 27.8, - 27.5, - 27.2, - 26.8, - 26.5, - 26.4, - 26.3, - 26.1, - 25.6, - 24.7, - 23.5, - 22.7, - 22.7, - 22.7, - 22.7, - 22.4, - 21.8, - 21.2, - 21, - 21, - 21, - 21, - 20.9, - 20.6, - 19, - 17, - 16.1, - 15.7, - 15.7, - 15.7, - 15.7, - 15.5, - 15.3, - 14.9, - 13.8, - 12.7, - 12, - 11.5, - 11, - 10.4, - 9.8, - 9.3, - 9, - 8.8, - 8.7, - 8.4, - 7.9, - 7.5, - 7.2, - 6.8, - 6.5, - 6.2, - 6, - 5.8, - 5.8, - 6, - 5.9, - 5.9, - 5.8, - 5.6, - 5.4, - 5.3, - 5.1, - 5, - 4.8, - 4.6, - 4.3, - 4.2, - 4, - 3.7, - 3.5, - 3.3, - 3, - 2.6, - 2.4, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 2.8, - 2.9, - 3, - 3.1, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.4, - 3.4, - 3.5, - 3.5, - 3.6, - 3.6, - 3.7, - 3.8, - 3.9, - 3.9, - 4.1, - 4.3, - 4.6, - 4.9, - 5.2, - 5.4, - 56.4, - 56.4, - 56.4, - 56.1, - 56, - 56.2, - 56.5, - 56.3, - 56.1, - 55.6, - 55.3, - 55, - 54.7, - 54.2, - 53.7, - 53.6, - 53.3, - 52.9, - 52.5, - 52.2, - 51.9, - 51.7, - 51.2, - 50.6, - 50, - 49.5, - 48.9, - 48.4, - 48, - 47.7, - 47.2, - 46.7, - 46.3, - 45.9, - 45.4, - 44.8, - 44.3, - 44, - 43.7, - 43, - 42.4, - 42, - 41.7, - 41.4, - 41.2, - 40.9, - 40.6, - 40.2, - 39.8, - 39.6, - 39.1, - 38.6, - 38.3, - 37.9, - 37.6, - 37.1, - 36.7, - 36.4, - 36.1, - 35.7, - 35.3, - 34.8, - 34.2, - 33.8, - 33.5, - 33.1, - 32.7, - 32.3, - 31.9, - 31.5, - 31.3, - 30.9, - 30.5, - 30.3, - 30.2, - 30.1, - 29.8, - 29.6, - 29.2, - 29, - 28.8, - 28.4, - 27.9, - 27.5, - 27.1, - 26.9, - 26.6, - 26.6, - 26.4, - 26, - 25.5, - 24.8, - 23.7, - 22.8, - 22.8, - 22.9, - 22.8, - 22.6, - 22, - 21.2, - 21, - 20.9, - 20.9, - 20.9, - 20.8, - 20.4, - 18.6, - 16.9, - 16.2, - 15.6, - 15.5, - 15.5, - 15.5, - 15.4, - 15.2, - 14.9, - 14.2, - 13, - 12.1, - 11.6, - 11.1, - 10.6, - 10, - 9.6, - 9.2, - 8.9, - 8.7, - 8.3, - 8, - 7.8, - 7.5, - 7.1, - 6.8, - 6.7, - 6.4, - 6.2, - 6.3, - 6.4, - 6.3, - 6.2, - 5.9, - 5.8, - 5.7, - 5.6, - 5.3, - 5.1, - 4.9, - 4.7, - 4.5, - 4.4, - 4.1, - 3.9, - 3.7, - 3.5, - 3.2, - 2.9, - 2.6, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.3, - 3.3, - 3.4, - 3.5, - 3.5, - 3.6, - 3.7, - 3.7, - 3.8, - 3.8, - 3.9, - 4, - 4.3, - 4.7, - 4.9, - 5.2, - 5.4, - 56.8, - 56.9, - 57.5, - 57.2, - 57, - 56.8, - 56.7, - 56.4, - 55.9, - 55.6, - 55.3, - 55.1, - 54.7, - 54.4, - 54, - 53.9, - 53.6, - 53.1, - 52.8, - 52.4, - 52.1, - 51.8, - 51.3, - 50.6, - 49.9, - 49.4, - 49.1, - 48.6, - 48.1, - 47.9, - 47.4, - 46.9, - 46.5, - 46, - 45.5, - 44.8, - 44.3, - 44, - 43.5, - 42.8, - 42.3, - 41.9, - 41.8, - 41.5, - 41.1, - 40.9, - 40.7, - 40.4, - 39.8, - 39.6, - 39.2, - 38.8, - 38.5, - 38.1, - 37.7, - 37.2, - 36.9, - 36.6, - 36.2, - 35.7, - 35.3, - 34.9, - 34.4, - 34, - 33.7, - 33.4, - 32.9, - 32.6, - 32.1, - 31.7, - 31.5, - 31.1, - 30.7, - 30.4, - 30.3, - 30.2, - 30.1, - 29.8, - 29.5, - 29.1, - 28.8, - 28.6, - 28.1, - 27.5, - 27.2, - 27, - 26.9, - 26.9, - 26.6, - 26.3, - 25.7, - 24.9, - 23.8, - 23, - 23, - 23, - 23, - 22.7, - 22.1, - 21.2, - 20.9, - 20.9, - 20.8, - 20.8, - 20.7, - 20.5, - 18.2, - 17, - 16.3, - 15.7, - 15.4, - 15.3, - 15.3, - 15.2, - 15, - 14.8, - 14.4, - 13.1, - 11.9, - 11.4, - 11.1, - 10.6, - 10.1, - 9.7, - 9.2, - 8.9, - 8.9, - 8.6, - 8.3, - 8, - 8, - 7.7, - 7.3, - 7.1, - 7, - 6.8, - 6.8, - 6.8, - 6.8, - 6.6, - 6.3, - 6.1, - 6, - 5.7, - 5.6, - 5.4, - 5.2, - 5, - 4.8, - 4.6, - 4.4, - 4.1, - 4, - 3.7, - 3.4, - 3.2, - 2.9, - 2.6, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.3, - 2.3, - 2.4, - 2.5, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.8, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.3, - 3.3, - 3.4, - 3.5, - 3.5, - 3.5, - 3.5, - 3.6, - 3.7, - 3.7, - 3.8, - 3.9, - 3.9, - 4.3, - 4.7, - 4.9, - 5.2, - 5.3, - 57.3, - 58, - 58.1, - 57.8, - 57.6, - 57.3, - 56.9, - 56.5, - 56, - 55.7, - 55.4, - 55.3, - 54.9, - 54.6, - 54.3, - 54.1, - 53.7, - 53.4, - 53, - 52.7, - 52.2, - 51.8, - 51.3, - 50.6, - 50, - 49.6, - 49.3, - 48.9, - 48.2, - 47.9, - 47.6, - 46.9, - 46.3, - 45.9, - 45.4, - 44.9, - 44.3, - 43.8, - 43.4, - 42.9, - 42.4, - 42.2, - 41.9, - 41.5, - 41.2, - 41, - 40.8, - 40.4, - 39.9, - 39.6, - 39.3, - 39.1, - 38.8, - 38.4, - 37.8, - 37.3, - 37, - 36.7, - 36.5, - 36, - 35.4, - 35, - 34.6, - 34.2, - 33.9, - 33.6, - 33.1, - 32.7, - 32.3, - 31.9, - 31.6, - 31.3, - 30.9, - 30.7, - 30.4, - 30.4, - 30.2, - 29.9, - 29.7, - 29.4, - 29, - 28.7, - 28.1, - 27.8, - 27.5, - 27.4, - 27.3, - 27.1, - 26.9, - 26.6, - 25.9, - 25.1, - 24, - 23, - 23.1, - 23.1, - 23.1, - 22.9, - 22.2, - 21.2, - 20.9, - 20.8, - 20.8, - 20.7, - 20.6, - 20.5, - 18, - 17, - 16.4, - 15.9, - 15.3, - 15.1, - 15.1, - 15.1, - 14.9, - 14.7, - 14.4, - 13.2, - 11.6, - 11.2, - 10.9, - 10.5, - 10.1, - 9.6, - 9.3, - 9.3, - 9.5, - 9.4, - 8.9, - 8.5, - 8.4, - 8.2, - 8.1, - 7.9, - 7.7, - 7.8, - 7.9, - 7.8, - 7.6, - 7.4, - 7.1, - 6.8, - 6.4, - 6.1, - 5.9, - 5.7, - 5.6, - 5.4, - 5.2, - 5, - 4.6, - 4.4, - 4.2, - 3.9, - 3.6, - 3.4, - 3.2, - 2.9, - 2.7, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 2.3, - 2.2, - 2.3, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3.1, - 3.1, - 3.2, - 3.2, - 3.3, - 3.3, - 3.5, - 3.5, - 3.5, - 3.5, - 3.6, - 3.6, - 3.7, - 3.8, - 3.8, - 3.9, - 4.3, - 4.6, - 4.7, - 5, - 5.3, - 58.5, - 58.6, - 58.3, - 58.1, - 57.7, - 57.3, - 56.8, - 56.5, - 56.2, - 55.9, - 55.7, - 55.4, - 55.1, - 54.7, - 54.5, - 54.4, - 53.9, - 53.6, - 53.1, - 52.8, - 52.3, - 51.8, - 51.3, - 50.5, - 50.2, - 50, - 49.5, - 48.8, - 48.3, - 48, - 47.7, - 47, - 46.4, - 45.8, - 45.3, - 44.8, - 44.3, - 43.7, - 43.4, - 43, - 42.7, - 42.4, - 42, - 41.6, - 41.3, - 41.1, - 40.8, - 40.4, - 40, - 39.7, - 39.5, - 39.3, - 38.9, - 38.5, - 38, - 37.5, - 37.1, - 36.9, - 36.5, - 36, - 35.6, - 35.2, - 34.9, - 34.5, - 34.1, - 33.7, - 33.2, - 32.7, - 32.3, - 32.1, - 31.8, - 31.4, - 31.1, - 30.8, - 30.6, - 30.6, - 30.4, - 30.1, - 29.8, - 29.5, - 29.2, - 28.7, - 28.3, - 28.1, - 27.9, - 27.9, - 27.7, - 27.4, - 27.2, - 26.8, - 26, - 25.2, - 24.2, - 23.1, - 23.2, - 23.3, - 23.3, - 23, - 22.3, - 21.2, - 20.8, - 20.8, - 20.7, - 20.6, - 20.6, - 19.9, - 17.9, - 17.1, - 16.5, - 15.9, - 15.3, - 14.9, - 14.9, - 14.9, - 14.8, - 14.6, - 14.4, - 13.8, - 12.5, - 11.8, - 11, - 10.4, - 9.9, - 9.6, - 9.5, - 9.7, - 9.9, - 10.1, - 9.9, - 9.5, - 9.4, - 9.3, - 9.1, - 9.2, - 9.1, - 9.2, - 9.2, - 9.3, - 9.1, - 8.7, - 8.3, - 7.9, - 7.3, - 6.9, - 6.5, - 6.4, - 6.3, - 6, - 5.7, - 5.4, - 5.1, - 4.7, - 4.4, - 4, - 3.8, - 3.6, - 3.4, - 3.2, - 2.9, - 2.7, - 2.6, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.8, - 2.9, - 3, - 3, - 3, - 3, - 3.1, - 3.2, - 3.2, - 3.2, - 3.3, - 3.4, - 3.5, - 3.5, - 3.5, - 3.6, - 3.6, - 3.8, - 3.9, - 3.9, - 3.9, - 4.1, - 4.4, - 4.6, - 4.8, - 5.1, - 59.2, - 58.9, - 58.5, - 58.2, - 57.7, - 57.3, - 57, - 56.7, - 56.4, - 56.3, - 56, - 55.7, - 55.3, - 55, - 54.8, - 54.5, - 54.2, - 53.8, - 53.2, - 52.8, - 52.3, - 51.8, - 51.3, - 50.6, - 50.4, - 50, - 49.4, - 48.8, - 48.1, - 47.8, - 47.6, - 46.9, - 46.2, - 45.6, - 45.2, - 44.7, - 44.2, - 43.7, - 43.4, - 43.2, - 42.9, - 42.5, - 42, - 41.7, - 41.4, - 41.1, - 40.8, - 40.4, - 40.1, - 39.8, - 39.7, - 39.3, - 38.9, - 38.5, - 38.1, - 37.7, - 37.4, - 37.1, - 36.5, - 36.1, - 35.7, - 35.3, - 35.1, - 34.7, - 34.2, - 33.7, - 33.3, - 32.9, - 32.5, - 32.2, - 31.8, - 31.5, - 31.3, - 31, - 30.8, - 30.7, - 30.5, - 30.3, - 30.2, - 29.7, - 29.3, - 28.8, - 28.6, - 28.4, - 28.3, - 28.1, - 27.9, - 27.7, - 27.4, - 26.9, - 26.1, - 25.3, - 24.4, - 23.3, - 23.4, - 23.4, - 23.4, - 23.2, - 22.3, - 21.2, - 20.8, - 20.7, - 20.7, - 20.6, - 20.5, - 20, - 18.3, - 17.3, - 16.5, - 16, - 15.4, - 14.8, - 14.7, - 14.7, - 14.6, - 14.5, - 14.3, - 14, - 13.5, - 12.7, - 11.8, - 11, - 10.4, - 10, - 10.1, - 10.4, - 10.6, - 10.8, - 10.9, - 10.8, - 10.6, - 10.6, - 10.6, - 10.5, - 10.5, - 10.4, - 10.4, - 10.2, - 10.1, - 10, - 9.7, - 9, - 8.5, - 8.2, - 7.9, - 7.5, - 7.3, - 6.8, - 6.6, - 6.1, - 5.6, - 5.1, - 4.6, - 4.3, - 4, - 3.8, - 3.5, - 3.3, - 3.1, - 2.9, - 2.8, - 2.7, - 2.6, - 2.4, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.7, - 2.9, - 3, - 3.1, - 3.1, - 3, - 3.1, - 3.1, - 3.2, - 3.2, - 3.4, - 3.4, - 3.5, - 3.5, - 3.5, - 3.6, - 3.7, - 3.8, - 3.9, - 4, - 4.1, - 4.1, - 4.2, - 4.3, - 4.5, - 4.8, - 59.5, - 59.1, - 58.6, - 58.3, - 58, - 57.6, - 57.2, - 56.9, - 56.7, - 56.6, - 56.4, - 56.1, - 55.7, - 55.4, - 55.1, - 54.8, - 54.4, - 53.9, - 53.4, - 53, - 52.5, - 51.9, - 51.4, - 50.9, - 50.5, - 50, - 49.5, - 49, - 48.2, - 47.7, - 47.5, - 46.8, - 46.2, - 45.5, - 45.2, - 44.8, - 44.3, - 43.9, - 43.6, - 43.2, - 43, - 42.6, - 42.2, - 41.9, - 41.5, - 41.2, - 40.8, - 40.4, - 40.2, - 40, - 39.8, - 39.4, - 39, - 38.6, - 38.2, - 37.8, - 37.5, - 37.1, - 36.6, - 36.2, - 35.7, - 35.4, - 35.2, - 34.7, - 34.3, - 33.9, - 33.5, - 33.2, - 32.6, - 32.3, - 32, - 31.7, - 31.3, - 31.1, - 30.9, - 30.7, - 30.5, - 30.4, - 30.2, - 29.9, - 29.4, - 29, - 28.8, - 28.6, - 28.5, - 28.4, - 28.1, - 27.8, - 27.5, - 26.9, - 26.1, - 25.2, - 24.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.3, - 22.4, - 21.2, - 20.8, - 20.7, - 20.7, - 20.6, - 20.5, - 20, - 18.3, - 17.3, - 16.6, - 16.1, - 15.6, - 14.8, - 14.5, - 14.5, - 14.5, - 14.3, - 14.2, - 13.9, - 13.7, - 13.5, - 13, - 12.3, - 11.5, - 11, - 11.1, - 11.3, - 11.4, - 11.5, - 11.6, - 11.5, - 11.4, - 11.3, - 11.1, - 11, - 10.8, - 10.7, - 10.6, - 10.5, - 10.3, - 10.2, - 10.1, - 9.9, - 9.7, - 9.5, - 9.2, - 8.9, - 8.6, - 8.1, - 7.6, - 7, - 6.4, - 5.6, - 5, - 4.6, - 4.3, - 4, - 3.7, - 3.5, - 3.3, - 3.3, - 3.1, - 2.9, - 2.9, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.4, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.9, - 3.1, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.2, - 3.3, - 3.3, - 3.4, - 3.6, - 3.6, - 3.7, - 3.8, - 3.9, - 4, - 4.1, - 4.1, - 4.2, - 4.6, - 4.8, - 4.8, - 5, - 60.1, - 59.3, - 58.8, - 58.4, - 58.2, - 57.9, - 57.5, - 57.2, - 57, - 56.9, - 56.6, - 56.4, - 56.2, - 55.8, - 55.4, - 55.2, - 54.6, - 54.1, - 53.7, - 53.2, - 52.6, - 52.1, - 51.7, - 51, - 50.6, - 50.2, - 49.7, - 49.1, - 48.4, - 47.8, - 47.5, - 47, - 46.2, - 45.5, - 45.2, - 44.7, - 44.3, - 44, - 43.7, - 43.5, - 43.2, - 42.9, - 42.5, - 42.2, - 41.8, - 41.4, - 40.9, - 40.6, - 40.4, - 40.1, - 39.8, - 39.5, - 39, - 38.7, - 38.4, - 38.1, - 37.8, - 37.2, - 36.7, - 36.3, - 35.9, - 35.5, - 35.2, - 34.8, - 34.4, - 34.1, - 33.8, - 33.4, - 32.9, - 32.4, - 32.1, - 31.8, - 31.5, - 31.2, - 31, - 30.9, - 30.6, - 30.5, - 30.4, - 30, - 29.5, - 29.1, - 28.9, - 28.7, - 28.7, - 28.5, - 28, - 27.7, - 27.5, - 26.8, - 26, - 25.2, - 24.4, - 23.6, - 23.7, - 23.7, - 23.7, - 23.4, - 22.5, - 21.2, - 20.8, - 20.7, - 20.7, - 20.6, - 20.5, - 20, - 18.5, - 17.5, - 16.9, - 16.3, - 15.7, - 15.2, - 14.6, - 14.3, - 14.3, - 14.2, - 14.1, - 13.9, - 13.7, - 13.5, - 13.3, - 13.1, - 12.7, - 12.3, - 12.2, - 12, - 12.1, - 11.9, - 11.8, - 11.7, - 11.5, - 11.3, - 11.1, - 11, - 10.9, - 10.7, - 10.6, - 10.5, - 10.3, - 10.2, - 10.1, - 10, - 9.9, - 9.8, - 9.7, - 9.5, - 9.2, - 8.9, - 8.4, - 8, - 7.3, - 6.4, - 5.6, - 5.1, - 4.6, - 4.4, - 4.1, - 3.8, - 3.7, - 3.6, - 3.4, - 3.2, - 3.1, - 3, - 2.8, - 2.8, - 2.8, - 2.9, - 2.8, - 2.7, - 2.7, - 2.7, - 2.6, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 2.4, - 2.4, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.8, - 3, - 3, - 3, - 3.1, - 3.1, - 3.2, - 3.4, - 3.3, - 3.2, - 3.4, - 3.5, - 3.6, - 3.6, - 3.7, - 3.9, - 4, - 4.1, - 4.2, - 4.2, - 4.4, - 4.8, - 5.1, - 5.2, - 5.3, - 61.8, - 60.1, - 59.1, - 58.7, - 58.3, - 58.1, - 57.9, - 57.6, - 57.3, - 57, - 56.7, - 56.6, - 56.1, - 55.8, - 55.5, - 55.3, - 54.7, - 54.3, - 54, - 53.4, - 52.9, - 52.5, - 51.8, - 51.1, - 50.7, - 50.3, - 49.7, - 49.1, - 48.4, - 48, - 47.5, - 47, - 46.4, - 45.7, - 45.3, - 44.9, - 44.5, - 44.1, - 43.9, - 43.7, - 43.6, - 43.1, - 42.8, - 42.4, - 42, - 41.5, - 41.1, - 40.8, - 40.5, - 40.1, - 39.8, - 39.7, - 39.3, - 39, - 38.6, - 38.3, - 37.9, - 37.4, - 36.9, - 36.4, - 36, - 35.7, - 35.4, - 35, - 34.6, - 34.2, - 33.9, - 33.5, - 33.2, - 32.7, - 32.3, - 32, - 31.6, - 31.4, - 31.2, - 31, - 30.8, - 30.5, - 30.4, - 30.1, - 29.6, - 29.2, - 28.9, - 28.7, - 28.7, - 28.5, - 28, - 27.6, - 27.3, - 26.6, - 25.8, - 25.1, - 24.3, - 23.8, - 23.8, - 23.8, - 23.8, - 23.6, - 22.6, - 21.2, - 20.8, - 20.7, - 20.7, - 20.6, - 20.4, - 19.7, - 18.3, - 17.7, - 17, - 16.3, - 15.8, - 15.6, - 15, - 14.3, - 14.1, - 14.1, - 14, - 13.8, - 13.6, - 13.4, - 13.2, - 13.1, - 12.9, - 12.7, - 12.5, - 12.4, - 12.2, - 12, - 11.8, - 11.7, - 11.5, - 11.3, - 11.2, - 11, - 10.9, - 10.7, - 10.6, - 10.4, - 10.3, - 10.2, - 10, - 10, - 9.9, - 9.8, - 9.7, - 9.6, - 9.5, - 9.3, - 9.1, - 9.1, - 8.3, - 7.3, - 6.3, - 5.5, - 5.1, - 4.7, - 4.3, - 4.1, - 4, - 3.8, - 3.6, - 3.5, - 3.3, - 3.3, - 3.1, - 3, - 3, - 3, - 3.1, - 3, - 3, - 3, - 2.9, - 2.7, - 2.5, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.3, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.6, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.2, - 3.3, - 3.3, - 3.3, - 3.3, - 3.5, - 3.7, - 3.7, - 3.8, - 3.9, - 4.1, - 4.2, - 4.3, - 4.3, - 4.6, - 5, - 5.2, - 5.4, - 5.6, - 62.7, - 61.8, - 60.9, - 59.6, - 58.8, - 58.6, - 58.2, - 57.8, - 57.5, - 57.2, - 56.9, - 56.6, - 56.1, - 55.9, - 55.8, - 55.5, - 54.9, - 54.4, - 54, - 53.5, - 53.1, - 52.6, - 51.8, - 51.5, - 51, - 50.3, - 49.6, - 48.9, - 48.4, - 48.2, - 47.6, - 46.9, - 46.3, - 45.6, - 45.3, - 45.1, - 44.8, - 44.4, - 44.2, - 44.1, - 43.8, - 43.4, - 43.1, - 42.7, - 42.2, - 41.7, - 41.3, - 40.9, - 40.6, - 40.2, - 40, - 39.8, - 39.5, - 39.3, - 38.8, - 38.4, - 38, - 37.5, - 37.1, - 36.6, - 36.2, - 35.8, - 35.5, - 35.1, - 34.6, - 34.2, - 33.9, - 33.6, - 33.3, - 32.9, - 32.6, - 32.2, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.7, - 30.5, - 30.2, - 29.8, - 29.3, - 29, - 28.7, - 28.7, - 28.5, - 28, - 27.4, - 26.9, - 26.3, - 25.6, - 25.2, - 24.4, - 23.9, - 23.9, - 24, - 23.9, - 23.7, - 22.7, - 21.3, - 20.8, - 20.7, - 20.7, - 20.6, - 20.4, - 20.1, - 18.7, - 17.8, - 17.1, - 16.7, - 16.3, - 15.9, - 15.4, - 14.6, - 14, - 14, - 13.9, - 13.8, - 13.6, - 13.4, - 13.3, - 13.1, - 12.9, - 12.7, - 12.5, - 12.4, - 12.2, - 12, - 11.8, - 11.7, - 11.5, - 11.4, - 11.2, - 11, - 10.9, - 10.7, - 10.6, - 10.5, - 10.3, - 10.1, - 10, - 9.9, - 9.8, - 9.7, - 9.6, - 9.6, - 9.5, - 9.4, - 9.3, - 9.3, - 9.1, - 8.3, - 7.3, - 6.4, - 5.6, - 5, - 4.6, - 4.4, - 4.3, - 4, - 3.8, - 3.7, - 3.6, - 3.5, - 3.3, - 3.3, - 3.3, - 3.2, - 3.2, - 3.1, - 3.1, - 3, - 3, - 2.8, - 2.7, - 2.6, - 2.4, - 2.3, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.3, - 2.4, - 2.5, - 2.4, - 2.5, - 2.6, - 2.7, - 2.8, - 2.9, - 3, - 3.1, - 3.2, - 3.2, - 3.2, - 3.3, - 3.3, - 3.5, - 3.7, - 3.8, - 3.9, - 4, - 4.1, - 4.2, - 4.3, - 4.4, - 4.7, - 5, - 5.3, - 5.5, - 5.6, - 63.1, - 62.4, - 62, - 61.5, - 61.2, - 60.2, - 59, - 58.2, - 57.7, - 57.3, - 56.9, - 56.4, - 56.2, - 56.1, - 56, - 55.5, - 54.9, - 54.5, - 53.8, - 53.3, - 52.9, - 52.6, - 52, - 51.7, - 51.1, - 50.4, - 49.9, - 49.1, - 48.6, - 48.2, - 47.6, - 47, - 46.3, - 45.8, - 45.7, - 45.4, - 44.9, - 44.7, - 44.5, - 44.4, - 44.1, - 43.6, - 43.3, - 42.8, - 42.3, - 42, - 41.5, - 41.1, - 40.8, - 40.4, - 40.2, - 40, - 39.7, - 39.5, - 39.1, - 38.6, - 38.2, - 37.7, - 37.3, - 36.8, - 36.4, - 36, - 35.7, - 35.2, - 34.8, - 34.4, - 34.1, - 33.8, - 33.4, - 33, - 32.7, - 32.3, - 32, - 31.7, - 31.4, - 31.3, - 31.1, - 30.8, - 30.6, - 30.3, - 29.8, - 29.4, - 29, - 28.7, - 28.6, - 28.4, - 27.9, - 27.3, - 26.7, - 26.1, - 25.5, - 24.9, - 24.3, - 24.1, - 24.1, - 24.1, - 24.1, - 23.8, - 22.8, - 21.3, - 20.8, - 20.7, - 20.7, - 20.6, - 20.4, - 20.1, - 18.8, - 17.9, - 17.3, - 17.1, - 16.6, - 16.2, - 15.7, - 15, - 14.2, - 13.8, - 13.8, - 13.7, - 13.5, - 13.4, - 13.3, - 13.1, - 12.9, - 12.7, - 12.6, - 12.4, - 12.2, - 12, - 11.8, - 11.6, - 11.5, - 11.4, - 11.2, - 11.1, - 10.9, - 11, - 10.9, - 10.7, - 10.6, - 10.4, - 10.2, - 9.9, - 9.7, - 9.7, - 9.6, - 9.5, - 9.5, - 9.4, - 9.3, - 9.2, - 9.2, - 9.1, - 8.3, - 7.3, - 6.3, - 5.5, - 5, - 4.7, - 4.6, - 4.3, - 4.1, - 3.9, - 3.8, - 3.7, - 3.5, - 3.5, - 3.4, - 3.4, - 3.4, - 3.3, - 3.3, - 3.2, - 3.1, - 3, - 2.9, - 2.8, - 2.7, - 2.6, - 2.5, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.4, - 2.3, - 2.4, - 2.5, - 2.6, - 2.7, - 2.8, - 2.9, - 3, - 3, - 3.1, - 3.2, - 3.3, - 3.5, - 3.6, - 3.8, - 3.8, - 3.9, - 4, - 4.1, - 4.3, - 4.3, - 4.5, - 4.9, - 5.1, - 5.4, - 5.6, - 5.7, - 63.6, - 62.7, - 62.3, - 62.2, - 61.8, - 60.8, - 59.1, - 58.4, - 57.9, - 57.3, - 56.8, - 56.5, - 56.3, - 56.4, - 56.2, - 55.5, - 55, - 54.2, - 53.6, - 53.2, - 52.8, - 52.4, - 51.9, - 51.5, - 51, - 50.4, - 50.1, - 49.3, - 48.8, - 48.2, - 47.7, - 47.2, - 46.8, - 46.3, - 45.8, - 45.4, - 45, - 44.8, - 44.7, - 44.4, - 44.2, - 43.9, - 43.5, - 43, - 42.5, - 42.1, - 41.8, - 41.3, - 40.9, - 40.6, - 40.4, - 40.2, - 39.9, - 39.6, - 39.2, - 38.8, - 38.4, - 37.9, - 37.5, - 37, - 36.5, - 36.2, - 35.9, - 35.3, - 34.9, - 34.5, - 34.2, - 33.9, - 33.5, - 33.2, - 32.8, - 32.6, - 32.3, - 31.9, - 31.6, - 31.5, - 31.2, - 30.9, - 30.7, - 30.4, - 29.9, - 29.5, - 29.1, - 28.9, - 28.6, - 28.2, - 27.9, - 27.3, - 26.7, - 26.1, - 25.4, - 24.9, - 24.4, - 24.1, - 24.2, - 24.2, - 24.2, - 24, - 23, - 21.4, - 20.8, - 20.7, - 20.7, - 20.6, - 20.5, - 20.1, - 18.9, - 18.1, - 17.6, - 17.3, - 16.8, - 16.4, - 15.9, - 15.3, - 14.6, - 13.9, - 13.6, - 13.6, - 13.5, - 13.4, - 13.2, - 13.1, - 12.9, - 12.8, - 12.6, - 12.4, - 12.2, - 11.9, - 11.7, - 11.5, - 11.4, - 11.3, - 11.2, - 11.1, - 11.1, - 11.1, - 11.1, - 10.9, - 10.8, - 10.6, - 10.4, - 10.1, - 9.8, - 9.6, - 9.6, - 9.5, - 9.5, - 9.4, - 9.3, - 9.2, - 9.1, - 9.2, - 9, - 8.3, - 7.2, - 6.3, - 5.7, - 5.2, - 4.8, - 4.5, - 4.3, - 4.1, - 4, - 3.9, - 3.8, - 3.7, - 3.6, - 3.5, - 3.5, - 3.5, - 3.4, - 3.5, - 3.3, - 3.2, - 3.1, - 3, - 2.8, - 2.8, - 2.7, - 2.6, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.4, - 2.4, - 2.5, - 2.7, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.2, - 3.3, - 3.5, - 3.6, - 3.8, - 4, - 4, - 4, - 4.2, - 4.3, - 4.4, - 4.7, - 5, - 5.3, - 5.5, - 5.7, - 5.8, - 63.7, - 62.8, - 62.5, - 62.4, - 61.5, - 60, - 58.6, - 58.1, - 57.7, - 57.2, - 56.7, - 56.6, - 56.6, - 56.5, - 56.4, - 55.6, - 55, - 54.2, - 53.7, - 53.2, - 52.8, - 52.2, - 51.5, - 50.9, - 50.5, - 50.2, - 49.8, - 49.5, - 49.2, - 48.7, - 48, - 47.5, - 47, - 46.5, - 45.8, - 45.5, - 45.1, - 44.9, - 44.7, - 44.5, - 44.3, - 44.1, - 43.8, - 43.4, - 42.8, - 42.4, - 42, - 41.4, - 41.1, - 40.7, - 40.6, - 40.5, - 40.1, - 39.7, - 39.4, - 39, - 38.6, - 38.1, - 37.7, - 37.1, - 36.7, - 36.2, - 35.8, - 35.3, - 34.9, - 34.5, - 34.2, - 34, - 33.7, - 33.4, - 33, - 32.7, - 32.4, - 32.1, - 31.9, - 31.8, - 31.6, - 31.2, - 30.9, - 30.5, - 30, - 29.5, - 29.2, - 28.9, - 28.5, - 28.2, - 27.8, - 27.3, - 26.7, - 26.1, - 25.5, - 25.1, - 24.7, - 24.3, - 24.4, - 24.4, - 24.4, - 24.2, - 23.2, - 21.7, - 20.9, - 20.8, - 20.7, - 20.6, - 20.5, - 20.2, - 19, - 18.3, - 17.8, - 17.4, - 17, - 16.5, - 16, - 15.6, - 15.1, - 14.2, - 13.6, - 13.5, - 13.4, - 13.3, - 13.2, - 13.1, - 12.9, - 12.8, - 12.9, - 12.9, - 12.7, - 12.3, - 11.9, - 11.6, - 11.4, - 11.3, - 11.2, - 11.1, - 11.1, - 11.2, - 11.2, - 11, - 10.9, - 10.8, - 10.6, - 10.2, - 9.8, - 9.6, - 9.5, - 9.5, - 9.4, - 9.4, - 9.3, - 9.2, - 9.2, - 9.1, - 9.1, - 9, - 8.2, - 7.3, - 6.5, - 5.7, - 5.1, - 4.8, - 4.6, - 4.4, - 4.2, - 4.1, - 4, - 3.9, - 3.8, - 3.8, - 3.7, - 3.7, - 3.6, - 3.6, - 3.5, - 3.4, - 3.3, - 3.2, - 3, - 2.9, - 2.9, - 2.7, - 2.7, - 2.5, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.5, - 2.6, - 2.7, - 2.8, - 2.9, - 3.1, - 3.1, - 3.1, - 3.3, - 3.6, - 3.7, - 3.8, - 4, - 4, - 4, - 4.2, - 4.4, - 4.5, - 4.7, - 5.1, - 5.3, - 5.5, - 5.7, - 5.9, - 63.9, - 63.1, - 62.8, - 62.4, - 61.1, - 59.7, - 58.4, - 58, - 57.6, - 57.1, - 56.7, - 56.6, - 56.6, - 56.4, - 56.1, - 55.5, - 55, - 54.4, - 53.9, - 53.2, - 52.6, - 51.9, - 51.1, - 50.4, - 50.2, - 50, - 49.8, - 49.7, - 49.5, - 48.9, - 48.4, - 47.6, - 47.1, - 46.5, - 45.9, - 45.6, - 45.3, - 45, - 44.8, - 44.7, - 44.5, - 44.3, - 44, - 43.6, - 43.2, - 42.5, - 42.1, - 41.7, - 41.2, - 40.9, - 40.6, - 40.5, - 40, - 39.7, - 39.4, - 39.1, - 38.7, - 38.4, - 37.8, - 37.2, - 36.8, - 36.3, - 35.8, - 35.4, - 35.1, - 34.6, - 34.3, - 34, - 33.7, - 33.5, - 33.3, - 33, - 32.8, - 32.6, - 32.5, - 32.3, - 32, - 31.6, - 31.2, - 30.7, - 30.1, - 29.6, - 29.1, - 28.9, - 28.6, - 28.2, - 27.7, - 27.1, - 26.7, - 26.2, - 25.7, - 25.4, - 25, - 24.4, - 24.5, - 24.5, - 24.5, - 24.3, - 23.3, - 21.7, - 20.9, - 20.8, - 20.8, - 20.7, - 20.6, - 20.2, - 19, - 18.3, - 17.9, - 17.5, - 17.2, - 16.7, - 16.3, - 15.9, - 15.4, - 14.8, - 13.9, - 13.4, - 13.3, - 13.3, - 13.2, - 13, - 12.9, - 13, - 13, - 13, - 13, - 12.7, - 12.2, - 11.9, - 11.5, - 11.3, - 11.2, - 11.1, - 11.1, - 11.1, - 11.1, - 11, - 10.9, - 10.8, - 10.6, - 10.2, - 9.8, - 9.6, - 9.5, - 9.5, - 9.4, - 9.4, - 9.3, - 9.3, - 9.2, - 9.1, - 9.1, - 9.1, - 9, - 8.2, - 7.4, - 6.3, - 5.6, - 5.1, - 4.8, - 4.6, - 4.4, - 4.3, - 4.2, - 4.2, - 4, - 3.9, - 3.9, - 3.8, - 3.8, - 3.8, - 3.7, - 3.6, - 3.5, - 3.3, - 3.1, - 3, - 3, - 2.9, - 2.9, - 2.7, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.3, - 2.4, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.7, - 2.8, - 3, - 3.1, - 3.1, - 3.3, - 3.5, - 3.6, - 3.8, - 3.9, - 3.9, - 4, - 4.2, - 4.4, - 4.5, - 4.7, - 5.1, - 5.3, - 5.5, - 5.7, - 5.9, - 64.2, - 63.6, - 62.7, - 61.9, - 60.1, - 59, - 58.3, - 57.9, - 57.5, - 57.1, - 56.8, - 56.8, - 56.6, - 56.5, - 56.1, - 55.5, - 55, - 54.5, - 53.8, - 53, - 52.2, - 51.6, - 50.8, - 50.5, - 50.2, - 50, - 49.8, - 49.5, - 49.3, - 48.9, - 48.3, - 47.6, - 47.2, - 46.4, - 45.9, - 45.5, - 45.3, - 45.1, - 45, - 45, - 44.8, - 44.5, - 44.1, - 43.7, - 43.3, - 42.8, - 42.4, - 41.9, - 41.5, - 41, - 40.9, - 40.7, - 40.2, - 39.8, - 39.5, - 39.3, - 38.9, - 38.6, - 37.9, - 37.4, - 36.9, - 36.4, - 35.8, - 35.6, - 35.2, - 34.9, - 34.5, - 34.1, - 33.9, - 33.6, - 33.5, - 33.3, - 33.3, - 33.2, - 33.1, - 32.8, - 32.4, - 31.9, - 31.4, - 30.9, - 30.2, - 29.6, - 29.1, - 28.8, - 28.5, - 28.1, - 27.7, - 27.3, - 26.8, - 26.3, - 25.8, - 25.4, - 25, - 24.6, - 24.6, - 24.7, - 24.6, - 24.4, - 23.3, - 21.8, - 20.8, - 20.8, - 20.8, - 20.7, - 20.6, - 20.2, - 19.1, - 18.5, - 18, - 17.7, - 17.4, - 16.9, - 16.5, - 16.1, - 15.6, - 15.1, - 14.5, - 13.9, - 13.3, - 13.2, - 13.1, - 13, - 12.9, - 13, - 13, - 13, - 13, - 12.8, - 12.5, - 12.1, - 11.8, - 11.4, - 11.2, - 11.1, - 11.1, - 11, - 11, - 10.9, - 10.8, - 10.7, - 10.7, - 10.1, - 9.7, - 9.6, - 9.6, - 9.5, - 9.4, - 9.4, - 9.3, - 9.2, - 9.2, - 9.1, - 9.1, - 9, - 9.1, - 8.9, - 8.2, - 7.2, - 6.3, - 5.5, - 5.1, - 4.9, - 4.7, - 4.5, - 4.4, - 4.3, - 4.3, - 4.2, - 4.1, - 4.1, - 4, - 4, - 3.9, - 3.8, - 3.7, - 3.5, - 3.4, - 3.4, - 3.2, - 3.1, - 3, - 2.8, - 2.7, - 2.6, - 2.4, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.3, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.8, - 2.8, - 2.9, - 3, - 3.2, - 3.4, - 3.6, - 3.7, - 3.8, - 3.9, - 4.1, - 4.3, - 4.4, - 4.5, - 4.8, - 5, - 5.4, - 5.5, - 5.7, - 5.9, - 64.4, - 63.6, - 61.4, - 60.3, - 59.4, - 58.6, - 58.2, - 57.8, - 57.5, - 57.2, - 57, - 56.8, - 56.7, - 56.3, - 56.2, - 55.9, - 55.1, - 54.5, - 53.5, - 52.7, - 51.9, - 51.2, - 50.8, - 50.5, - 50.3, - 50, - 49.8, - 49.6, - 49.1, - 48.5, - 48.1, - 47.6, - 47.2, - 46.5, - 45.8, - 45.5, - 45.4, - 45.2, - 45.2, - 45.1, - 44.9, - 44.6, - 44.1, - 43.8, - 43.3, - 42.9, - 42.5, - 42, - 41.6, - 41.3, - 41.1, - 40.8, - 40.4, - 40.1, - 39.8, - 39.5, - 39.2, - 38.8, - 38.1, - 37.5, - 37, - 36.4, - 36, - 35.7, - 35.3, - 34.9, - 34.6, - 34.3, - 34.1, - 33.9, - 33.8, - 33.9, - 33.9, - 33.8, - 33.7, - 33.3, - 32.9, - 32.3, - 31.8, - 31.2, - 30.4, - 29.7, - 29.1, - 28.7, - 28.4, - 28.1, - 27.8, - 27.5, - 27, - 26.5, - 26.1, - 25.6, - 25, - 24.7, - 24.8, - 24.8, - 24.8, - 24.4, - 23.4, - 21.8, - 20.9, - 20.8, - 20.8, - 20.8, - 20.7, - 20.4, - 19.4, - 18.7, - 18.4, - 18, - 17.6, - 17.2, - 16.8, - 16.3, - 15.9, - 15.5, - 14.9, - 14.3, - 13.4, - 13.1, - 13.1, - 13, - 12.9, - 12.9, - 13, - 13, - 13, - 12.9, - 12.7, - 12.4, - 12.1, - 11.7, - 11.3, - 11.1, - 11.1, - 11, - 11, - 10.9, - 10.8, - 10.8, - 10.7, - 10.3, - 9.8, - 9.6, - 9.6, - 9.5, - 9.4, - 9.4, - 9.3, - 9.3, - 9.2, - 9.2, - 9.1, - 9, - 9, - 9, - 8.9, - 8.1, - 7.1, - 6.1, - 5.5, - 5.2, - 4.9, - 4.7, - 4.5, - 4.5, - 4.5, - 4.4, - 4.3, - 4.3, - 4.2, - 4.2, - 4.2, - 4.1, - 3.8, - 3.5, - 3.5, - 3.5, - 3.4, - 3.2, - 3.1, - 3, - 2.9, - 2.8, - 2.6, - 2.5, - 2.4, - 2.4, - 2.5, - 2.5, - 2.4, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.3, - 2.4, - 2.4, - 2.5, - 2.6, - 2.7, - 2.8, - 3, - 3, - 3.1, - 3.4, - 3.5, - 3.7, - 3.8, - 3.8, - 4, - 4.2, - 4.4, - 4.6, - 4.7, - 5, - 5.3, - 5.5, - 5.7, - 5.9, - 65.4, - 64.2, - 62, - 59.4, - 59, - 58.5, - 58.1, - 57.7, - 57.5, - 57.3, - 57.1, - 56.9, - 56.6, - 56.5, - 56.3, - 55.7, - 55, - 54.2, - 53.3, - 52.5, - 51.8, - 51.3, - 50.9, - 50.6, - 50.4, - 50, - 49.9, - 49.5, - 48.9, - 48.3, - 47.9, - 47.5, - 47, - 46.4, - 45.8, - 45.7, - 45.9, - 45.5, - 45.3, - 45.2, - 45, - 44.7, - 44.2, - 43.9, - 43.5, - 42.9, - 42.5, - 42.2, - 41.9, - 41.5, - 41.2, - 40.9, - 40.6, - 40.3, - 40.1, - 39.8, - 39.4, - 38.9, - 38.3, - 37.7, - 37.1, - 36.6, - 36.1, - 35.8, - 35.4, - 35.1, - 34.7, - 34.4, - 34.2, - 34, - 34.1, - 34.4, - 34.5, - 34.3, - 34, - 33.8, - 33.4, - 32.7, - 32.1, - 31.3, - 30.6, - 30, - 29.3, - 28.7, - 28.3, - 28, - 27.9, - 27.6, - 27.2, - 26.9, - 26.5, - 25.9, - 25.2, - 24.7, - 24.9, - 24.9, - 24.9, - 24.7, - 23.6, - 22, - 20.9, - 20.9, - 20.9, - 20.8, - 20.7, - 20.5, - 19.5, - 19, - 18.6, - 18.2, - 17.8, - 17.4, - 16.9, - 16.4, - 16.1, - 15.7, - 15.2, - 14.7, - 14, - 13.2, - 13, - 13, - 12.9, - 12.8, - 12.8, - 13, - 13, - 12.9, - 12.7, - 12.5, - 12.3, - 12, - 11.6, - 11.2, - 11.1, - 11, - 11, - 10.9, - 10.8, - 10.8, - 10.7, - 10.6, - 10.6, - 10, - 9.6, - 9.5, - 9.5, - 9.4, - 9.3, - 9.3, - 9.2, - 9.1, - 9.1, - 9.1, - 9, - 9, - 9, - 8.8, - 8, - 7, - 6.2, - 5.6, - 5.1, - 4.9, - 4.7, - 4.8, - 4.7, - 4.6, - 4.6, - 4.5, - 4.4, - 4.4, - 4.3, - 4.2, - 3.7, - 3.7, - 3.8, - 3.7, - 3.6, - 3.4, - 3.4, - 3.2, - 3.1, - 2.9, - 2.8, - 2.7, - 2.6, - 2.6, - 2.7, - 2.7, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.7, - 2.8, - 3, - 3.1, - 3.1, - 3.1, - 3.3, - 3.5, - 3.6, - 3.7, - 3.8, - 4, - 4.3, - 4.5, - 4.7, - 5, - 5.2, - 5.4, - 5.7, - 5.9, - 65.9, - 65, - 63.1, - 59.9, - 58.7, - 58.3, - 57.9, - 57.6, - 57.4, - 57.4, - 57.3, - 57.1, - 56.8, - 56.7, - 56.3, - 55.6, - 55.1, - 54.2, - 53.2, - 52.6, - 52, - 51.4, - 50.9, - 50.5, - 50.2, - 50.1, - 49.9, - 49.3, - 48.8, - 48.4, - 48, - 47.4, - 46.7, - 46.2, - 45.9, - 45.9, - 46, - 45.8, - 45.5, - 45.4, - 45.1, - 44.9, - 44.4, - 44, - 43.6, - 43, - 42.7, - 42.4, - 42.1, - 41.7, - 41.5, - 41.3, - 41.1, - 40.6, - 40.3, - 40, - 39.5, - 39, - 38.4, - 37.8, - 37.3, - 36.8, - 36.4, - 36.1, - 35.7, - 35.3, - 34.9, - 34.6, - 34.3, - 34.3, - 34.5, - 34.6, - 34.8, - 34.8, - 34.5, - 34.1, - 33.7, - 33.1, - 32.3, - 31.4, - 30.7, - 30.2, - 29.5, - 28.9, - 28.5, - 28.2, - 28, - 27.7, - 27.4, - 27.1, - 26.6, - 26, - 25.3, - 24.9, - 25.1, - 25.1, - 25.1, - 24.8, - 23.7, - 22.1, - 21, - 20.9, - 20.9, - 20.9, - 20.8, - 20.5, - 19.8, - 19.1, - 18.7, - 18.3, - 17.9, - 17.5, - 17.1, - 16.7, - 16.2, - 15.9, - 15.4, - 15.1, - 14.5, - 13.7, - 13, - 12.9, - 12.9, - 12.9, - 12.8, - 12.8, - 12.8, - 12.7, - 12.5, - 12.5, - 12.5, - 12.2, - 11.7, - 11.3, - 11, - 11, - 11, - 10.9, - 10.9, - 10.8, - 10.8, - 10.7, - 10.6, - 10.5, - 9.9, - 9.5, - 9.5, - 9.4, - 9.3, - 9.3, - 9.2, - 9.2, - 9.1, - 9.1, - 9, - 9, - 8.9, - 8.9, - 8.7, - 8, - 7, - 6.3, - 5.6, - 5.3, - 5, - 5, - 4.9, - 4.9, - 4.8, - 4.8, - 4.7, - 4.7, - 4.4, - 4.2, - 3.9, - 4.1, - 4, - 4, - 3.8, - 3.7, - 3.5, - 3.4, - 3.2, - 3.1, - 3, - 2.9, - 2.8, - 2.9, - 2.9, - 2.8, - 2.5, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.7, - 2.8, - 2.9, - 3.1, - 3.1, - 3.1, - 3.2, - 3.3, - 3.4, - 3.7, - 3.8, - 4, - 4.2, - 4.3, - 4.6, - 4.9, - 5.3, - 5.5, - 5.7, - 6, - 66.3, - 65.6, - 63.1, - 60.3, - 59.2, - 58.6, - 58.1, - 57.8, - 57.6, - 57.5, - 57.3, - 57.1, - 56.9, - 56.7, - 56.1, - 55.8, - 55.1, - 54.2, - 53.3, - 52.7, - 52.3, - 51.7, - 51.2, - 50.6, - 50.3, - 50.1, - 49.9, - 49.4, - 48.8, - 48.3, - 47.9, - 47.3, - 46.6, - 46.3, - 46.2, - 46, - 45.9, - 45.9, - 45.7, - 45.6, - 45.2, - 44.9, - 44.5, - 44.1, - 43.6, - 43.3, - 43.2, - 42.7, - 42.4, - 42.1, - 41.6, - 41.5, - 41.3, - 40.9, - 40.5, - 40, - 39.5, - 39, - 38.5, - 38, - 37.5, - 37.1, - 36.6, - 36.2, - 35.9, - 35.5, - 35.1, - 34.8, - 34.6, - 34.7, - 34.9, - 35.1, - 35.1, - 35.1, - 34.8, - 34.4, - 33.9, - 33.1, - 32.3, - 31.5, - 30.8, - 30.2, - 29.6, - 29, - 28.7, - 28.4, - 28.1, - 27.9, - 27.7, - 27.4, - 26.8, - 26.1, - 25.6, - 25, - 25.2, - 25.2, - 25.2, - 25, - 23.8, - 22.3, - 21, - 21, - 21, - 21, - 20.9, - 20.7, - 20, - 19.2, - 18.8, - 18.4, - 18, - 17.7, - 17.3, - 16.9, - 16.4, - 16, - 15.7, - 15.3, - 14.9, - 14.2, - 13.6, - 13, - 12.9, - 12.8, - 12.8, - 12.7, - 12.7, - 12.6, - 12.6, - 12.5, - 12.5, - 12.2, - 11.7, - 11.2, - 11.1, - 11, - 11, - 10.9, - 10.9, - 10.8, - 10.8, - 10.7, - 10.7, - 10.6, - 10.5, - 9.7, - 9.5, - 9.4, - 9.4, - 9.3, - 9.2, - 9.2, - 9.1, - 9.1, - 9, - 9, - 8.9, - 8.9, - 8.9, - 8.7, - 7.9, - 7.1, - 6.3, - 5.7, - 5.3, - 5.2, - 5.1, - 5.1, - 5, - 5, - 4.9, - 4.8, - 4.5, - 4.4, - 4.4, - 4.3, - 4.2, - 4.1, - 4, - 3.8, - 3.7, - 3.6, - 3.4, - 3.2, - 3.1, - 3, - 2.9, - 2.9, - 2.8, - 2.7, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.3, - 2.4, - 2.5, - 2.5, - 2.5, - 2.6, - 2.8, - 3, - 3.1, - 3.1, - 3.1, - 3.2, - 3.4, - 3.5, - 3.6, - 3.7, - 3.9, - 4, - 4.1, - 4.4, - 4.9, - 5.2, - 5.5, - 5.8, - 6, - 66.7, - 65.8, - 63.6, - 61.8, - 60.6, - 59.7, - 58.9, - 58.4, - 58.1, - 58, - 57.8, - 57.4, - 57.1, - 56.8, - 56.3, - 55.8, - 55.1, - 54.4, - 53.6, - 53, - 52.7, - 52.5, - 52, - 51, - 50.6, - 50.5, - 50.2, - 49.7, - 49, - 48.4, - 47.9, - 47.3, - 46.8, - 46.5, - 46.3, - 46.1, - 45.9, - 45.9, - 45.9, - 45.7, - 45.4, - 45, - 44.7, - 44.3, - 43.9, - 43.5, - 43.2, - 43, - 42.6, - 42.2, - 42, - 41.7, - 41.5, - 40.9, - 40.5, - 40, - 39.5, - 39.1, - 38.6, - 38.1, - 37.4, - 37.1, - 36.6, - 36.3, - 35.9, - 35.5, - 35.3, - 35, - 34.9, - 35, - 35.2, - 35.3, - 35.3, - 35.1, - 34.9, - 34.5, - 33.7, - 32.9, - 32.3, - 31.6, - 31, - 30.3, - 29.8, - 29.2, - 28.8, - 28.5, - 28.3, - 28.1, - 27.8, - 27.5, - 27, - 26.4, - 25.7, - 25.2, - 25.4, - 25.4, - 25.3, - 25.1, - 24, - 22.5, - 21.1, - 21, - 21, - 21, - 21, - 20.9, - 20.3, - 19.4, - 18.9, - 18.5, - 18.2, - 17.8, - 17.5, - 17.1, - 16.7, - 16.3, - 16, - 15.7, - 15.3, - 14.7, - 14.1, - 13.3, - 12.8, - 12.8, - 12.8, - 12.7, - 12.7, - 12.6, - 12.6, - 12.5, - 12.5, - 12.3, - 11.6, - 11.1, - 11.1, - 11.1, - 11, - 11, - 10.9, - 10.8, - 10.8, - 10.7, - 10.7, - 10.6, - 10.5, - 10.2, - 9.6, - 9.4, - 9.4, - 9.3, - 9.3, - 9.2, - 9.1, - 9.1, - 9, - 9, - 8.9, - 8.8, - 8.8, - 8.9, - 8.7, - 7.9, - 7.1, - 6.2, - 5.8, - 5.5, - 5.4, - 5.3, - 5.2, - 5.2, - 5.1, - 4.9, - 4.7, - 4.7, - 4.6, - 4.5, - 4.4, - 4.2, - 4.1, - 4, - 3.9, - 3.7, - 3.6, - 3.5, - 3.3, - 3.2, - 3, - 2.8, - 2.7, - 2.6, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.6, - 2.7, - 2.8, - 2.9, - 3, - 3, - 3.1, - 3.1, - 3.3, - 3.5, - 3.6, - 3.7, - 3.8, - 3.9, - 4.1, - 4.6, - 4.9, - 5.2, - 5.4, - 5.8, - 6.1, - 66.5, - 65.9, - 64.6, - 63.5, - 62.8, - 62.6, - 61.8, - 60.8, - 59.5, - 58.7, - 58.3, - 57.9, - 57.5, - 57, - 56.5, - 56, - 55.3, - 54.6, - 53.9, - 53.3, - 53, - 52.6, - 52.1, - 51.3, - 51, - 51, - 50.6, - 50, - 49.2, - 48.6, - 48.1, - 47.4, - 47, - 46.8, - 46.5, - 46.2, - 46, - 46, - 46, - 45.8, - 45.6, - 45.2, - 44.8, - 44.4, - 44.1, - 43.7, - 43.4, - 43, - 42.7, - 42.5, - 42.1, - 41.8, - 41.4, - 41, - 40.6, - 40.2, - 39.7, - 39.1, - 38.6, - 38.2, - 37.6, - 37.1, - 36.6, - 36.3, - 36.1, - 35.8, - 35.6, - 35.4, - 35.4, - 35.4, - 35.5, - 35.6, - 35.4, - 35.2, - 34.9, - 34.3, - 33.6, - 32.9, - 32.3, - 31.8, - 31.2, - 30.5, - 30, - 29.4, - 29, - 28.7, - 28.4, - 28.2, - 27.9, - 27.6, - 27.3, - 26.7, - 26, - 25.4, - 25.5, - 25.5, - 25.5, - 25.2, - 24.1, - 22.7, - 21.1, - 21.1, - 21.1, - 21.1, - 21.1, - 21, - 20.5, - 19.6, - 19, - 18.6, - 18.2, - 17.9, - 17.6, - 17.3, - 16.9, - 16.5, - 16.2, - 15.9, - 15.6, - 15, - 14.3, - 13.9, - 13.3, - 12.9, - 12.8, - 12.7, - 12.7, - 12.6, - 12.6, - 12.5, - 12.5, - 12.4, - 12.2, - 11.9, - 11.4, - 11.1, - 11, - 11, - 10.9, - 10.8, - 10.8, - 10.7, - 10.7, - 10.6, - 10.6, - 10.5, - 10.4, - 9.8, - 9.4, - 9.3, - 9.3, - 9.2, - 9.2, - 9.1, - 9, - 9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.8, - 8.6, - 7.8, - 7, - 6.3, - 5.9, - 5.8, - 5.7, - 5.6, - 5.5, - 5.3, - 5.1, - 4.9, - 4.9, - 4.9, - 4.7, - 4.6, - 4.5, - 4.3, - 4.2, - 4, - 3.9, - 3.8, - 3.6, - 3.4, - 3.2, - 3, - 2.8, - 2.6, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.5, - 2.4, - 2.6, - 2.6, - 2.8, - 2.9, - 3.1, - 3, - 3.1, - 3.1, - 3.3, - 3.5, - 3.6, - 3.7, - 3.7, - 3.9, - 4.2, - 4.6, - 4.9, - 5.2, - 5.5, - 5.8, - 6, - 66.5, - 65.8, - 65.2, - 64.3, - 63.7, - 63.6, - 63.4, - 63, - 61.3, - 60.2, - 59.4, - 58.7, - 58, - 57.5, - 56.9, - 56.5, - 55.7, - 55, - 54.3, - 53.5, - 52.9, - 52.6, - 52.1, - 51.3, - 51.1, - 50.9, - 50.4, - 49.9, - 49.3, - 48.7, - 48.2, - 47.6, - 47.2, - 47, - 46.7, - 46.3, - 46.2, - 46.1, - 46, - 45.9, - 45.7, - 45.4, - 45.1, - 44.6, - 44.2, - 43.8, - 43.6, - 43.2, - 42.8, - 42.6, - 42.3, - 41.9, - 41.5, - 41, - 40.6, - 40.3, - 39.7, - 39.2, - 38.7, - 38.3, - 37.9, - 37.4, - 37, - 36.6, - 36.3, - 36.2, - 36, - 35.9, - 35.8, - 35.8, - 35.8, - 35.8, - 35.5, - 35.3, - 35, - 34.5, - 33.8, - 33.1, - 32.4, - 31.9, - 31.3, - 30.6, - 30.1, - 29.6, - 29.2, - 28.9, - 28.6, - 28.2, - 28, - 27.8, - 27.4, - 27, - 26.2, - 25.5, - 25.7, - 25.7, - 25.6, - 25.3, - 24.2, - 22.8, - 21.4, - 21.1, - 21.2, - 21.1, - 21.1, - 21.1, - 20.8, - 19.7, - 19.1, - 18.7, - 18.3, - 18, - 17.7, - 17.5, - 17.1, - 16.6, - 16.2, - 16, - 15.6, - 15, - 14.4, - 13.9, - 13.3, - 12.9, - 12.8, - 12.8, - 12.7, - 12.7, - 12.6, - 12.5, - 12.5, - 12.4, - 12.4, - 12.4, - 11.8, - 11.1, - 11.1, - 11, - 10.9, - 10.8, - 10.8, - 10.7, - 10.7, - 10.6, - 10.6, - 10.5, - 10.4, - 10.4, - 9.5, - 9.3, - 9.3, - 9.2, - 9.2, - 9.1, - 9.1, - 9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.7, - 8.8, - 8.4, - 7.9, - 7.1, - 6.5, - 6.2, - 6, - 5.9, - 5.8, - 5.6, - 5.3, - 5.1, - 5.2, - 5, - 4.9, - 4.8, - 4.7, - 4.6, - 4.4, - 4.2, - 4, - 3.9, - 3.7, - 3.6, - 3.4, - 3, - 2.7, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.4, - 2.4, - 2.3, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.7, - 2.9, - 3, - 3, - 3.1, - 3.1, - 3.2, - 3.5, - 3.6, - 3.7, - 3.7, - 3.8, - 4.3, - 4.6, - 4.9, - 5.1, - 5.5, - 5.7, - 5.9, - 66.8, - 66.4, - 65.8, - 65.4, - 64.4, - 63.8, - 63.5, - 63.3, - 62.7, - 61.6, - 60.6, - 59.7, - 58.9, - 58.2, - 57.6, - 56.9, - 56.2, - 55.5, - 55, - 54.1, - 53.4, - 52.7, - 52, - 51.3, - 51, - 50.7, - 50.2, - 49.9, - 49.3, - 48.9, - 48.4, - 47.9, - 47.5, - 47, - 46.8, - 46.6, - 46.3, - 46.2, - 46.1, - 46, - 45.7, - 45.5, - 45.2, - 44.9, - 44.4, - 44, - 43.7, - 43.3, - 43, - 42.8, - 42.4, - 42, - 41.6, - 41.1, - 40.7, - 40.3, - 40, - 39.6, - 39.2, - 38.8, - 38.5, - 37.9, - 37.4, - 36.9, - 36.6, - 36.5, - 36.4, - 36.3, - 36.1, - 36.1, - 36.2, - 36.1, - 35.8, - 35.6, - 35.4, - 34.9, - 34.1, - 33.3, - 32.5, - 32, - 31.5, - 30.8, - 30.1, - 29.7, - 29.4, - 29.1, - 28.8, - 28.6, - 28.4, - 28.1, - 27.7, - 27.2, - 26.4, - 25.7, - 25.8, - 25.8, - 25.8, - 25.5, - 24.2, - 22.9, - 21.7, - 21.2, - 21.2, - 21.2, - 21.2, - 21.2, - 21, - 20, - 19.3, - 18.9, - 18.5, - 18.1, - 17.8, - 17.6, - 17.3, - 16.8, - 16.4, - 16.1, - 15.7, - 15.1, - 14.2, - 13.5, - 13, - 12.8, - 12.8, - 12.8, - 12.7, - 12.7, - 12.6, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.3, - 11.6, - 11, - 11, - 10.9, - 10.9, - 10.8, - 10.7, - 10.7, - 10.7, - 10.6, - 10.6, - 10.5, - 10.4, - 10.3, - 9.6, - 9.3, - 9.2, - 9.2, - 9.1, - 9.1, - 9, - 9, - 8.9, - 8.8, - 8.8, - 8.7, - 8.7, - 8.7, - 8.6, - 8, - 7.3, - 6.7, - 6.3, - 6.2, - 6.1, - 5.9, - 5.8, - 5.5, - 5.3, - 5.3, - 5.1, - 4.9, - 4.9, - 4.7, - 4.6, - 4.4, - 4.2, - 4.1, - 3.9, - 3.7, - 3.3, - 2.9, - 2.6, - 2.4, - 2.2, - 2.1, - 2.1, - 2.2, - 2.3, - 2.2, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.4, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.5, - 2.4, - 2.5, - 2.6, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.2, - 3.5, - 3.5, - 3.6, - 3.7, - 4, - 4.3, - 4.6, - 4.8, - 5, - 5.4, - 5.7, - 5.9, - 66.8, - 66.5, - 65.9, - 65.7, - 65, - 64.2, - 63.8, - 63.6, - 63.6, - 63, - 62.1, - 61.2, - 60.3, - 59.4, - 58.8, - 58.5, - 59.3, - 58.4, - 56.9, - 55, - 53.7, - 52.8, - 52.1, - 51.4, - 51, - 50.6, - 50.2, - 49.8, - 49.4, - 49, - 48.6, - 48.2, - 47.7, - 47.2, - 47.1, - 46.7, - 46.6, - 46.5, - 46.2, - 46, - 45.8, - 45.6, - 45.3, - 45, - 44.5, - 44.2, - 43.9, - 43.5, - 43.2, - 42.8, - 42.6, - 42.1, - 41.7, - 41.3, - 41.1, - 40.7, - 40.5, - 40.1, - 39.7, - 39.4, - 38.9, - 38.3, - 37.7, - 37.4, - 37.2, - 36.9, - 36.7, - 36.8, - 36.6, - 36.3, - 36.3, - 36.2, - 36.2, - 36, - 35.7, - 35.3, - 34.6, - 33.6, - 32.7, - 32.1, - 31.5, - 30.8, - 30.2, - 29.9, - 29.7, - 29.4, - 29.2, - 29.1, - 29, - 28.7, - 28.2, - 27.7, - 26.8, - 25.8, - 26, - 26, - 25.9, - 25.6, - 24.5, - 23.1, - 21.9, - 21.3, - 21.3, - 21.3, - 21.3, - 21.3, - 21.2, - 20.4, - 19.5, - 19.1, - 18.7, - 18.3, - 17.9, - 17.6, - 17.3, - 16.9, - 16.6, - 16.2, - 15.9, - 15.3, - 14.4, - 13.4, - 13, - 12.9, - 12.8, - 12.8, - 12.7, - 12.7, - 12.6, - 12.6, - 12.5, - 12.4, - 12.4, - 12.3, - 12.2, - 12.2, - 11.5, - 11, - 11, - 10.9, - 10.8, - 10.8, - 10.7, - 10.7, - 10.6, - 10.6, - 10.5, - 10.4, - 10.4, - 10.2, - 9.4, - 9.3, - 9.2, - 9.1, - 9.1, - 9, - 9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.7, - 8.7, - 8.7, - 8.6, - 8, - 7.4, - 6.8, - 6.5, - 6.4, - 6.3, - 6.2, - 6, - 5.7, - 5.5, - 5.3, - 5.2, - 5, - 4.9, - 4.8, - 4.6, - 4.4, - 4.3, - 4.1, - 3.7, - 3.1, - 2.8, - 2.6, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.7, - 2.8, - 2.9, - 3, - 3, - 3.1, - 3.4, - 3.4, - 3.5, - 3.7, - 4.1, - 4.4, - 4.5, - 4.8, - 4.9, - 5.2, - 5.5, - 5.7, - 66.6, - 66.3, - 65.9, - 65.6, - 64.9, - 64.5, - 64.2, - 63.9, - 64.1, - 63.9, - 63.5, - 62.9, - 62, - 61.4, - 61.7, - 61.7, - 61.6, - 61.2, - 59.6, - 55.2, - 53.8, - 52.9, - 52.3, - 51.6, - 51, - 50.7, - 50.2, - 49.7, - 49.3, - 49, - 48.7, - 48.2, - 47.7, - 47.4, - 47.2, - 47, - 46.9, - 46.6, - 46.4, - 46.2, - 46, - 45.9, - 45.4, - 45.1, - 44.7, - 44.3, - 44, - 43.6, - 43.3, - 43, - 42.6, - 42.2, - 41.8, - 41.6, - 41.4, - 41.5, - 41.1, - 40.6, - 40.1, - 39.7, - 39.3, - 38.7, - 38.2, - 37.9, - 37.6, - 37.3, - 37.2, - 37.3, - 37, - 36.8, - 36.5, - 36.7, - 36.8, - 37, - 36.5, - 35.9, - 35.1, - 34.2, - 33, - 32.3, - 31.5, - 30.9, - 30.4, - 30.2, - 30, - 29.8, - 29.7, - 29.6, - 29.4, - 29, - 28.6, - 28.1, - 27.3, - 26, - 26.1, - 26.1, - 26.1, - 25.8, - 24.8, - 23.4, - 22.1, - 21.4, - 21.4, - 21.4, - 21.4, - 21.4, - 21.4, - 20.9, - 19.9, - 19.3, - 18.9, - 18.4, - 18.1, - 17.8, - 17.4, - 17, - 16.7, - 16.3, - 16, - 15.7, - 14.9, - 14.2, - 13.4, - 13, - 12.9, - 12.8, - 12.8, - 12.7, - 12.6, - 12.6, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.2, - 12.2, - 11.6, - 11, - 10.9, - 10.9, - 10.8, - 10.7, - 10.7, - 10.6, - 10.6, - 10.5, - 10.5, - 10.4, - 10.3, - 10.2, - 9.4, - 9.2, - 9.2, - 9.1, - 9, - 9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.7, - 8.7, - 8.7, - 8.7, - 8.6, - 8.1, - 7.5, - 7, - 6.7, - 6.6, - 6.3, - 6.2, - 6.1, - 5.8, - 5.6, - 5.4, - 5.3, - 5.2, - 4.9, - 4.7, - 4.5, - 4.4, - 4.1, - 3.5, - 3, - 2.8, - 2.6, - 2.4, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.8, - 3, - 3.1, - 3.2, - 3.3, - 3.6, - 3.7, - 3.9, - 4.2, - 4.5, - 4.7, - 4.8, - 5, - 5.3, - 5.6, - 66.6, - 66.2, - 65.9, - 65.5, - 65.1, - 64.8, - 64.8, - 64.4, - 64.2, - 64.1, - 63.8, - 63.3, - 62.9, - 62.4, - 62, - 61.8, - 61.7, - 61.5, - 59.9, - 56, - 54.2, - 53.1, - 52.4, - 51.8, - 51.2, - 50.7, - 50.2, - 49.7, - 49.4, - 49.1, - 48.8, - 48.2, - 47.8, - 47.6, - 47.4, - 47.1, - 47, - 46.8, - 46.6, - 46.5, - 46.3, - 46, - 45.6, - 45.3, - 44.9, - 44.4, - 44, - 43.6, - 43.4, - 43.1, - 42.6, - 42.3, - 42, - 41.8, - 41.8, - 41.8, - 41.5, - 41.2, - 40.7, - 40.2, - 39.5, - 39.1, - 38.8, - 38.3, - 38, - 37.7, - 37.8, - 37.7, - 37.5, - 37.4, - 37.3, - 37.5, - 37.4, - 37.3, - 36.9, - 36.3, - 35.5, - 34.3, - 33, - 32.3, - 31.9, - 31.2, - 30.7, - 30.4, - 30.4, - 30.3, - 30.2, - 30.2, - 29.7, - 29.4, - 29, - 28.5, - 27.5, - 26.3, - 26.3, - 26.3, - 26.2, - 26, - 25.2, - 23.8, - 22.5, - 21.6, - 21.5, - 21.5, - 21.5, - 21.5, - 21.5, - 21.2, - 20.3, - 19.6, - 19.2, - 18.7, - 18.2, - 17.8, - 17.4, - 17.1, - 16.7, - 16.4, - 16.2, - 15.9, - 15.3, - 14.6, - 13.9, - 13.2, - 13, - 12.9, - 12.8, - 12.7, - 12.6, - 12.6, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.2, - 12.2, - 12.2, - 11.4, - 10.9, - 10.9, - 10.8, - 10.8, - 10.7, - 10.6, - 10.6, - 10.5, - 10.5, - 10.4, - 10.3, - 10.3, - 10.2, - 9.4, - 9.2, - 9.1, - 9.1, - 9, - 9, - 8.9, - 8.8, - 8.8, - 8.7, - 8.7, - 8.7, - 8.7, - 8.7, - 8.6, - 8.1, - 7.5, - 7, - 6.8, - 6.6, - 6.4, - 6.3, - 6.1, - 5.8, - 5.7, - 5.6, - 5.4, - 5.3, - 5.1, - 4.7, - 4.4, - 4, - 3.4, - 2.9, - 2.7, - 2.5, - 2.3, - 2.3, - 2.3, - 2.3, - 2.4, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.7, - 2.8, - 2.9, - 2.9, - 2.8, - 2.6, - 2.6, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.3, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.8, - 3, - 3.1, - 3.2, - 3.5, - 3.6, - 3.8, - 4, - 4.2, - 4.5, - 4.7, - 4.7, - 5, - 5.4, - 66.7, - 66.1, - 65.6, - 65.4, - 65.3, - 65.3, - 65.2, - 64.8, - 64.5, - 64.2, - 63.7, - 63.4, - 63, - 62.5, - 62.1, - 61.8, - 61.7, - 61.5, - 60.2, - 57.6, - 54.9, - 53.4, - 52.4, - 51.9, - 51.4, - 50.9, - 50.5, - 50, - 49.6, - 49.3, - 48.9, - 48.5, - 48.1, - 47.8, - 47.5, - 47.3, - 47.2, - 47, - 46.9, - 46.7, - 46.4, - 46.1, - 45.8, - 45.5, - 45, - 44.5, - 44.1, - 43.7, - 43.4, - 43, - 42.7, - 42.4, - 42.2, - 41.9, - 42, - 41.9, - 41.6, - 41.3, - 40.9, - 40.5, - 39.8, - 39.4, - 39.2, - 38.6, - 38.3, - 38.3, - 38.3, - 38.2, - 38.1, - 38, - 38, - 38, - 37.7, - 37.4, - 36.9, - 36.4, - 35.7, - 34.8, - 33.5, - 32.6, - 32.3, - 31.6, - 31.2, - 30.9, - 30.9, - 30.8, - 30.6, - 30.3, - 29.9, - 29.7, - 29.3, - 28.7, - 27.4, - 26.3, - 26.4, - 26.4, - 26.4, - 26.2, - 25.5, - 24.3, - 22.8, - 21.9, - 21.6, - 21.6, - 21.6, - 21.6, - 21.6, - 21.4, - 20.6, - 19.9, - 19.4, - 18.9, - 18.5, - 18, - 17.6, - 17.2, - 16.9, - 16.6, - 16.4, - 16.1, - 15.6, - 15, - 14.3, - 13.8, - 13.2, - 12.9, - 12.8, - 12.7, - 12.7, - 12.6, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.3, - 12.2, - 12.1, - 11.9, - 11.3, - 10.9, - 10.8, - 10.8, - 10.7, - 10.6, - 10.6, - 10.5, - 10.5, - 10.4, - 10.4, - 10.3, - 10.2, - 10.2, - 9.4, - 9.1, - 9.1, - 9, - 9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.7, - 8.7, - 8.6, - 8.6, - 8.7, - 8.4, - 8, - 7.3, - 7.1, - 6.9, - 6.6, - 6.5, - 6.3, - 6.1, - 5.9, - 5.8, - 5.6, - 5.5, - 5.3, - 4.8, - 4.4, - 3.9, - 3.2, - 2.9, - 2.6, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.3, - 2.3, - 2.4, - 2.5, - 2.5, - 2.6, - 2.8, - 2.9, - 3.1, - 3.1, - 2.9, - 2.7, - 2.6, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.7, - 2.7, - 2.8, - 2.9, - 3, - 3.3, - 3.5, - 3.7, - 3.8, - 4, - 4.3, - 4.5, - 4.6, - 4.9, - 5.2, - 66.6, - 65.9, - 65.5, - 65.5, - 65.6, - 65.5, - 65.3, - 64.9, - 64.5, - 64.1, - 63.7, - 63.4, - 63, - 62.5, - 62.1, - 61.8, - 61.7, - 61.4, - 60.1, - 59.1, - 56.9, - 54.4, - 52.9, - 52.2, - 51.6, - 51.1, - 50.7, - 50.3, - 49.8, - 49.3, - 49, - 48.7, - 48.3, - 48, - 47.7, - 47.5, - 47.4, - 47.3, - 47.1, - 46.8, - 46.5, - 46.2, - 45.9, - 45.6, - 45, - 44.5, - 44.3, - 43.8, - 43.5, - 43.2, - 42.8, - 42.5, - 42.4, - 42.3, - 42.1, - 42.1, - 41.7, - 41.4, - 41.2, - 40.7, - 40.1, - 39.8, - 39.4, - 39, - 38.9, - 38.9, - 38.8, - 38.5, - 38.5, - 38.7, - 38.7, - 38.4, - 38.2, - 37.7, - 37.1, - 36.6, - 36.1, - 35.4, - 34.2, - 33.3, - 32.9, - 32.3, - 31.9, - 31.6, - 31.5, - 31.2, - 30.9, - 30.4, - 30.1, - 29.9, - 29.6, - 28.8, - 27.3, - 26.5, - 26.5, - 26.6, - 26.6, - 26.3, - 25.7, - 24.5, - 23.4, - 22.3, - 21.7, - 21.6, - 21.7, - 21.7, - 21.7, - 21.6, - 21.1, - 20.2, - 19.5, - 19, - 18.6, - 18.1, - 17.7, - 17.3, - 17, - 16.6, - 16.4, - 16.1, - 15.7, - 15.3, - 14.8, - 14.3, - 13.8, - 13.3, - 12.9, - 12.8, - 12.7, - 12.6, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.3, - 12.2, - 12.2, - 12.1, - 12, - 11.3, - 10.8, - 10.8, - 10.7, - 10.7, - 10.6, - 10.5, - 10.5, - 10.5, - 10.4, - 10.3, - 10.3, - 10.2, - 10.1, - 9.2, - 9.1, - 9, - 9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.7, - 8.7, - 8.7, - 8.6, - 8.6, - 8.6, - 8.5, - 7.9, - 7.4, - 7.1, - 6.8, - 6.7, - 6.5, - 6.3, - 6.1, - 6, - 5.8, - 5.6, - 5.4, - 4.8, - 4.2, - 3.6, - 3.1, - 2.8, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.7, - 2.8, - 3, - 3.2, - 3.2, - 3, - 2.7, - 2.6, - 2.6, - 2.7, - 2.8, - 2.8, - 2.8, - 2.8, - 2.6, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.9, - 3.1, - 3.2, - 3.4, - 3.7, - 3.8, - 4.1, - 4.3, - 4.6, - 4.8, - 5, - 66.4, - 65.7, - 65.4, - 65.5, - 65.8, - 65.6, - 65.2, - 64.9, - 64.5, - 64.1, - 63.7, - 63.5, - 63.1, - 62.6, - 62.1, - 61.6, - 61.5, - 61.3, - 60.9, - 60.1, - 58.1, - 55.6, - 53.4, - 52.6, - 51.9, - 51.3, - 50.8, - 50.6, - 50, - 49.4, - 49.2, - 48.9, - 48.7, - 48.3, - 47.9, - 47.6, - 47.5, - 47.4, - 47.2, - 46.8, - 46.6, - 46.3, - 46, - 45.6, - 45.2, - 44.7, - 44.4, - 43.9, - 43.6, - 43.3, - 43, - 42.7, - 42.5, - 42.5, - 42.2, - 42, - 41.7, - 41.5, - 41.4, - 40.9, - 40.3, - 40, - 39.7, - 39.5, - 39.5, - 39.3, - 39.1, - 39, - 39.3, - 39.3, - 39.1, - 38.8, - 38.5, - 38, - 37.5, - 36.8, - 36.2, - 35.6, - 34.6, - 33.7, - 33.3, - 33, - 32.5, - 32.1, - 31.8, - 31.5, - 30.9, - 30.4, - 30.1, - 29.9, - 29.7, - 29.1, - 27.7, - 26.5, - 26.6, - 26.7, - 26.7, - 26.5, - 25.7, - 24.8, - 23.9, - 22.9, - 22.1, - 21.7, - 21.7, - 21.8, - 21.8, - 21.8, - 21.4, - 20.2, - 19.5, - 19.1, - 18.8, - 18.3, - 17.8, - 17.4, - 17, - 16.8, - 16.4, - 16.1, - 15.8, - 15.5, - 15.1, - 14.7, - 14.2, - 13.7, - 13.2, - 12.9, - 12.7, - 12.6, - 12.6, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.3, - 12.2, - 12.1, - 12, - 12.1, - 11.2, - 10.8, - 10.7, - 10.7, - 10.6, - 10.6, - 10.5, - 10.4, - 10.4, - 10.3, - 10.3, - 10.2, - 10.1, - 9.9, - 9.2, - 9.1, - 9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.8, - 8.7, - 8.7, - 8.7, - 8.6, - 8.6, - 8.7, - 8.5, - 8, - 7.3, - 6.9, - 6.8, - 6.6, - 6.4, - 6.2, - 6.1, - 6, - 5.6, - 5.1, - 4.5, - 3.9, - 3.4, - 3.1, - 2.7, - 2.5, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.6, - 2.6, - 2.7, - 2.9, - 3, - 3.2, - 3.2, - 2.9, - 2.6, - 2.6, - 2.7, - 2.9, - 3, - 3, - 2.9, - 2.8, - 2.7, - 2.6, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.4, - 2.4, - 2.4, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 2.9, - 3.1, - 3.3, - 3.6, - 3.9, - 4.2, - 4.4, - 4.6, - 4.9, - 66.1, - 65.7, - 65.5, - 65.5, - 65.5, - 65.4, - 65.2, - 65, - 64.6, - 64.1, - 63.6, - 63.5, - 63, - 62.4, - 61.9, - 61.5, - 61.3, - 61.1, - 60.7, - 59.7, - 58, - 55.7, - 53.9, - 52.9, - 52.1, - 51.5, - 51.2, - 50.9, - 50.5, - 49.7, - 49.4, - 49.3, - 49.1, - 49, - 48.4, - 47.9, - 47.8, - 47.5, - 47.3, - 47, - 46.7, - 46.3, - 46, - 45.6, - 45.2, - 44.9, - 44.6, - 44, - 43.7, - 43.4, - 43.1, - 43, - 42.7, - 42.5, - 42.3, - 42, - 41.6, - 41.5, - 41.4, - 41.1, - 40.4, - 40.2, - 40.1, - 40, - 39.8, - 39.6, - 39.4, - 39.7, - 40.1, - 39.8, - 39.5, - 39.1, - 38.7, - 38.2, - 37.9, - 37.4, - 36.5, - 35.7, - 34.9, - 34.1, - 33.7, - 33.4, - 33, - 32.6, - 32.2, - 31.5, - 30.9, - 30.2, - 29.9, - 29.8, - 29.6, - 29.1, - 28.1, - 26.7, - 26.8, - 26.9, - 26.8, - 26.6, - 26, - 25.1, - 24.5, - 23.6, - 22.7, - 22, - 21.8, - 21.9, - 21.9, - 21.9, - 21.6, - 20.5, - 19.8, - 19.3, - 18.8, - 18.4, - 17.8, - 17.4, - 17.1, - 16.8, - 16.5, - 16.2, - 15.9, - 15.6, - 15.3, - 14.9, - 14.4, - 14, - 13.6, - 13, - 12.8, - 12.7, - 12.6, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.3, - 12.2, - 12.2, - 12.1, - 12, - 11.9, - 11.2, - 10.8, - 10.7, - 10.6, - 10.6, - 10.5, - 10.5, - 10.4, - 10.4, - 10.3, - 10.2, - 10.2, - 10, - 9.9, - 9.2, - 9, - 9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.8, - 8.7, - 8.7, - 8.7, - 8.7, - 8.8, - 8.6, - 8.1, - 7.6, - 7.2, - 7, - 6.8, - 6.6, - 6.4, - 6.2, - 6, - 5.6, - 4.9, - 4.1, - 3.6, - 3.3, - 3, - 2.7, - 2.5, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.7, - 2.7, - 2.9, - 3, - 3.1, - 3.1, - 2.9, - 2.6, - 2.7, - 2.9, - 3, - 3.1, - 3.1, - 3, - 3, - 2.9, - 2.7, - 2.5, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.4, - 2.5, - 2.4, - 2.4, - 2.3, - 2.3, - 2.3, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.7, - 2.8, - 2.9, - 2.9, - 3, - 3.1, - 3.3, - 3.5, - 4, - 4.3, - 4.5, - 4.7, - 65.9, - 65.6, - 65.4, - 65.4, - 65.4, - 65.4, - 65.2, - 65, - 64.5, - 64.1, - 63.6, - 63.3, - 62.8, - 62.2, - 61.6, - 61.3, - 61, - 60.9, - 60.2, - 58.9, - 57.4, - 55.8, - 54.4, - 53.3, - 52.3, - 51.8, - 51.3, - 50.9, - 50.4, - 49.8, - 49.6, - 49.4, - 49.1, - 49, - 48.7, - 48.2, - 48, - 47.8, - 47.4, - 47.1, - 46.8, - 46.4, - 46, - 45.6, - 45.2, - 44.9, - 44.7, - 44.3, - 43.8, - 43.5, - 43.2, - 43, - 42.8, - 42.6, - 42.4, - 42, - 41.7, - 41.4, - 41.3, - 41, - 40.6, - 40.5, - 40.4, - 40.3, - 40.1, - 40, - 39.9, - 40.3, - 40.3, - 39.9, - 39.6, - 39.4, - 39, - 38.6, - 38.2, - 37.6, - 36.8, - 36, - 35.2, - 34.4, - 33.9, - 33.5, - 33.2, - 32.9, - 32.3, - 31.6, - 30.9, - 30.2, - 29.8, - 29.7, - 29.5, - 29.1, - 28, - 26.9, - 27, - 27, - 27, - 26.8, - 26.3, - 25.5, - 24.9, - 24, - 23.2, - 22.5, - 22, - 22, - 22, - 22, - 21.8, - 21, - 20.2, - 19.5, - 19, - 18.4, - 17.9, - 17.4, - 17.1, - 16.8, - 16.5, - 16.1, - 16, - 15.7, - 15.3, - 14.9, - 14.5, - 14.2, - 13.8, - 13.4, - 12.9, - 12.7, - 12.6, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.3, - 12.2, - 12.2, - 12.1, - 12, - 12, - 12, - 10.9, - 10.7, - 10.7, - 10.6, - 10.5, - 10.5, - 10.4, - 10.4, - 10.3, - 10.3, - 10.2, - 10.1, - 10.1, - 9.9, - 9.1, - 9, - 9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.8, - 8.7, - 8.7, - 8.8, - 8.8, - 8.6, - 8.2, - 7.6, - 7.3, - 7.1, - 7, - 6.8, - 6.5, - 6.3, - 6, - 5.2, - 4.3, - 3.7, - 3.4, - 3.1, - 2.8, - 2.7, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.6, - 2.7, - 2.8, - 3, - 3, - 2.9, - 2.8, - 2.6, - 2.8, - 3, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 2.9, - 2.7, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.4, - 2.4, - 2.6, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3.1, - 3.3, - 3.8, - 4.1, - 4.3, - 4.5, - 65.8, - 65.6, - 65.4, - 65.4, - 65.3, - 65.2, - 65.1, - 64.8, - 64.5, - 64.2, - 63.7, - 63.2, - 62.6, - 62, - 61.5, - 61.1, - 60.7, - 60.4, - 59.4, - 58, - 56.5, - 55.4, - 54.6, - 53.6, - 52.5, - 51.9, - 51.5, - 51.2, - 50.6, - 50, - 49.8, - 49.5, - 49.3, - 49, - 48.8, - 48.3, - 48.1, - 48, - 47.6, - 47.1, - 46.8, - 46.5, - 46, - 45.6, - 45.2, - 45, - 44.7, - 44.3, - 43.9, - 43.6, - 43.3, - 43.1, - 42.8, - 42.6, - 42.3, - 42.1, - 41.8, - 41.3, - 41.1, - 40.9, - 40.9, - 40.8, - 40.7, - 40.6, - 40.5, - 40.4, - 40.5, - 40.7, - 40.5, - 40.3, - 40, - 39.5, - 39.2, - 38.7, - 38.2, - 37.6, - 36.9, - 36.2, - 35.4, - 34.6, - 34, - 33.7, - 33.6, - 33, - 32.3, - 31.6, - 31, - 30.2, - 29.7, - 29.5, - 29.4, - 28.9, - 27.7, - 27.1, - 27.1, - 27.2, - 27.1, - 27, - 26.4, - 25.8, - 25.1, - 24.4, - 23.7, - 23.1, - 22.4, - 22.1, - 22.2, - 22.1, - 22, - 21.3, - 20.3, - 19.6, - 19.1, - 18.6, - 18, - 17.4, - 17.1, - 16.8, - 16.5, - 16.2, - 16, - 15.7, - 15.3, - 14.9, - 14.6, - 14.3, - 13.9, - 13.3, - 12.8, - 12.8, - 12.7, - 12.5, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.2, - 12.2, - 12.1, - 12.1, - 12, - 11.9, - 11.8, - 11.1, - 10.7, - 10.6, - 10.6, - 10.5, - 10.4, - 10.4, - 10.3, - 10.3, - 10.2, - 10.2, - 10.1, - 10, - 9.9, - 9.1, - 9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.8, - 8.8, - 8.8, - 8.8, - 8.9, - 8.7, - 8.3, - 7.7, - 7.4, - 7.2, - 7.1, - 6.9, - 6.7, - 6.5, - 5.8, - 4.8, - 3.9, - 3.5, - 3.2, - 3, - 2.8, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.4, - 2.5, - 2.7, - 2.8, - 2.9, - 3, - 2.9, - 2.7, - 2.7, - 2.9, - 3.1, - 3.3, - 3.4, - 3.4, - 3.4, - 3.5, - 3.5, - 3.3, - 2.9, - 2.5, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.5, - 2.6, - 2.5, - 2.4, - 2.4, - 2.5, - 2.6, - 2.6, - 2.7, - 2.6, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 3, - 3.1, - 3.3, - 3.6, - 3.9, - 4.2, - 4.4, - 65.8, - 65.7, - 65.5, - 65.4, - 65.2, - 65.1, - 64.9, - 64.8, - 64.4, - 64.1, - 63.7, - 63.1, - 62.6, - 62.1, - 61.6, - 61, - 60.4, - 59.6, - 58.7, - 57.3, - 56, - 55.3, - 54.5, - 53.5, - 52.6, - 52, - 51.6, - 51.5, - 50.8, - 50.4, - 50, - 49.7, - 49.4, - 49.2, - 48.9, - 48.5, - 48.3, - 47.9, - 47.6, - 47.2, - 46.7, - 46.5, - 46.1, - 45.7, - 45.4, - 45, - 44.8, - 44.5, - 44, - 43.6, - 43.4, - 43.2, - 42.9, - 42.5, - 42.2, - 42.1, - 42, - 41.3, - 41.1, - 41.1, - 41.2, - 41.1, - 41, - 40.8, - 40.8, - 40.8, - 40.8, - 40.8, - 40.6, - 40.4, - 40.1, - 39.6, - 39.1, - 38.8, - 38.3, - 37.7, - 37.2, - 36.4, - 35.8, - 34.8, - 34.2, - 34, - 33.7, - 33, - 32.3, - 31.6, - 31, - 30.3, - 29.7, - 29.5, - 29.5, - 29.1, - 28.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.1, - 26.5, - 25.8, - 25.2, - 24.7, - 24.1, - 23.5, - 22.5, - 22.2, - 22.3, - 22.3, - 22.2, - 21.5, - 20.4, - 19.9, - 19.3, - 18.7, - 18.3, - 17.7, - 17.2, - 16.9, - 16.6, - 16.3, - 16.1, - 15.8, - 15.5, - 15, - 14.7, - 14.2, - 13.6, - 13, - 12.9, - 12.8, - 12.8, - 12.7, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.3, - 12.2, - 12.1, - 12.1, - 12, - 12, - 11.9, - 11.9, - 11.2, - 10.6, - 10.6, - 10.5, - 10.4, - 10.4, - 10.3, - 10.3, - 10.2, - 10.2, - 10.1, - 10.1, - 10, - 9.8, - 9.1, - 8.9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.8, - 8.8, - 8.8, - 9, - 8.9, - 8.4, - 7.9, - 7.6, - 7.4, - 7.2, - 7.2, - 6.8, - 6.3, - 5.4, - 4.2, - 3.4, - 3.3, - 3.1, - 2.9, - 2.7, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.6, - 2.7, - 2.6, - 2.5, - 2.5, - 2.8, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 3, - 3.2, - 3.4, - 3.5, - 3.5, - 3.7, - 3.7, - 3.6, - 3.5, - 3.2, - 2.8, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.4, - 2.6, - 2.7, - 2.6, - 2.5, - 2.5, - 2.6, - 2.7, - 2.7, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 3, - 3.2, - 3.2, - 3.5, - 3.8, - 4, - 4, - 65.9, - 65.8, - 65.6, - 65.5, - 65.1, - 65.1, - 64.9, - 64.9, - 64.6, - 64.3, - 63.9, - 63.3, - 62.9, - 62.3, - 61.5, - 60.7, - 59.9, - 59, - 58.1, - 57.2, - 56.3, - 55.5, - 54.7, - 53.8, - 52.9, - 52.1, - 52, - 51.5, - 51, - 50.7, - 50.3, - 50, - 49.6, - 49.2, - 48.9, - 48.5, - 48.3, - 48.1, - 47.6, - 47.3, - 47, - 46.7, - 46.1, - 45.8, - 45.5, - 45.2, - 44.9, - 44.5, - 44.1, - 43.7, - 43.4, - 43.2, - 42.9, - 42.6, - 42.2, - 42, - 41.8, - 41.5, - 41.4, - 41.4, - 41.4, - 41.4, - 41.3, - 41.2, - 41.2, - 41.2, - 40.9, - 41, - 40.8, - 40.6, - 40.5, - 39.8, - 39.2, - 38.8, - 38.3, - 37.9, - 37.4, - 36.7, - 36.1, - 35.1, - 34.4, - 34.1, - 33.7, - 33, - 32.4, - 31.8, - 30.9, - 30.2, - 29.8, - 29.6, - 29.6, - 29.4, - 28.4, - 27.4, - 27.4, - 27.4, - 27.4, - 27.2, - 26.7, - 25.7, - 25.1, - 24.7, - 24.2, - 23.5, - 22.7, - 22.3, - 22.4, - 22.4, - 22.3, - 21.6, - 20.6, - 20, - 19.5, - 19, - 18.4, - 17.8, - 17.3, - 16.9, - 16.6, - 16.3, - 16.1, - 15.9, - 15.6, - 15.1, - 14.4, - 13.7, - 13.1, - 13.1, - 13, - 12.9, - 12.8, - 12.7, - 12.6, - 12.5, - 12.4, - 12.4, - 12.3, - 12.3, - 12.2, - 12.2, - 12.1, - 12, - 12, - 11.9, - 11.9, - 11.9, - 10.9, - 10.6, - 10.5, - 10.5, - 10.4, - 10.3, - 10.3, - 10.2, - 10.2, - 10.1, - 10.1, - 10, - 10, - 9.8, - 9.1, - 8.9, - 8.9, - 8.9, - 8.8, - 8.8, - 8.8, - 8.9, - 8.9, - 9, - 8.6, - 8.2, - 7.8, - 7.6, - 7.4, - 7.1, - 6.6, - 5.9, - 4.7, - 3.7, - 3.2, - 3.2, - 3.1, - 2.9, - 2.6, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.5, - 2.6, - 2.8, - 2.8, - 2.6, - 2.6, - 2.7, - 2.9, - 2.9, - 2.8, - 2.7, - 2.8, - 3, - 3.1, - 3.3, - 3.4, - 3.5, - 3.7, - 3.8, - 3.8, - 3.7, - 3.6, - 3.4, - 3.1, - 2.5, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 2.6, - 2.7, - 2.7, - 2.7, - 2.6, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 2.8, - 2.8, - 2.9, - 3, - 3, - 3.1, - 3.2, - 3.4, - 3.6, - 3.7, - 3.9, - 66, - 65.8, - 65.7, - 65.5, - 65.3, - 65.2, - 65.1, - 65.1, - 64.9, - 64.9, - 64.2, - 63.9, - 63.6, - 63, - 62, - 60.5, - 59.7, - 59.1, - 58.7, - 57.4, - 56.3, - 55.6, - 55, - 54, - 53.1, - 52.4, - 52.2, - 51.7, - 51.3, - 51, - 50.6, - 50.2, - 49.6, - 49.3, - 49.1, - 48.7, - 48.5, - 48.6, - 47.8, - 47.4, - 47.2, - 46.8, - 46.3, - 46, - 45.8, - 45.4, - 45, - 44.6, - 44.3, - 43.8, - 43.6, - 43.3, - 43.1, - 42.8, - 42.3, - 42.1, - 41.9, - 41.8, - 41.8, - 41.8, - 41.7, - 41.6, - 41.6, - 41.6, - 41.6, - 41.4, - 41.1, - 41.1, - 40.9, - 40.9, - 40.6, - 40, - 39.5, - 39, - 38.5, - 38.1, - 37.7, - 37, - 36.2, - 35.3, - 34.5, - 34, - 33.6, - 33, - 32.5, - 32, - 31.1, - 30.4, - 30, - 29.9, - 29.9, - 29.6, - 28.4, - 27.4, - 27.5, - 27.6, - 27.6, - 27.3, - 26.7, - 25.8, - 25, - 24.6, - 24.3, - 23.8, - 22.8, - 22.4, - 22.5, - 22.5, - 22.4, - 21.8, - 20.7, - 20, - 19.6, - 19.2, - 18.6, - 18, - 17.4, - 16.9, - 16.6, - 16.4, - 16.2, - 15.9, - 15.4, - 14.7, - 14, - 13.3, - 13.2, - 13.1, - 13, - 12.9, - 12.9, - 12.8, - 12.7, - 12.6, - 12.5, - 12.4, - 12.3, - 12.3, - 12.2, - 12.2, - 12.1, - 12.1, - 12, - 11.9, - 11.9, - 11.8, - 11.8, - 11.1, - 10.5, - 10.5, - 10.4, - 10.3, - 10.3, - 10.2, - 10.2, - 10.2, - 10.1, - 10.1, - 10, - 9.9, - 9.7, - 9.2, - 9.1, - 9.1, - 9, - 8.8, - 8.9, - 8.9, - 8.9, - 9, - 8.7, - 8.3, - 8, - 7.7, - 7.5, - 7.1, - 6.3, - 5.3, - 4, - 3.2, - 3.2, - 3.2, - 3.1, - 2.8, - 2.6, - 2.5, - 2.5, - 2.5, - 2.6, - 2.5, - 2.5, - 2.5, - 2.4, - 2.6, - 2.8, - 2.8, - 2.8, - 2.7, - 2.6, - 2.6, - 2.8, - 2.9, - 2.9, - 2.8, - 2.9, - 3, - 3.2, - 3.4, - 3.5, - 3.6, - 3.8, - 3.9, - 3.9, - 3.8, - 3.7, - 3.5, - 3.4, - 2.9, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.6, - 2.8, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 2.9, - 2.9, - 3.1, - 3.2, - 3.3, - 3.4, - 3.5, - 3.7, - 3.8, - 66, - 65.9, - 65.8, - 65.8, - 65.5, - 65.6, - 65.7, - 65.5, - 65.5, - 65.2, - 64.7, - 64.4, - 64.1, - 63.9, - 63.4, - 62.3, - 61.8, - 60.6, - 59.7, - 59.2, - 58.1, - 56.7, - 55.5, - 54.4, - 53.7, - 53.1, - 52.6, - 52, - 51.7, - 51.4, - 50.8, - 50.6, - 50, - 49.6, - 49.3, - 48.9, - 48.6, - 48.4, - 47.9, - 47.6, - 47.2, - 46.8, - 46.4, - 46.1, - 45.9, - 45.4, - 45, - 44.5, - 44.1, - 43.8, - 43.7, - 43.5, - 43.4, - 43, - 42.6, - 42.4, - 42.1, - 42.1, - 42.2, - 42.2, - 42.2, - 42, - 41.8, - 41.9, - 41.8, - 41.5, - 41.2, - 41.2, - 41.2, - 41, - 40.8, - 40.4, - 39.8, - 39.2, - 38.8, - 38.4, - 37.8, - 37.2, - 36.4, - 35.3, - 34.7, - 34.2, - 33.7, - 33.1, - 32.7, - 32.1, - 31.4, - 30.8, - 30.2, - 30.2, - 30.1, - 29.8, - 28.8, - 27.6, - 27.7, - 27.7, - 27.7, - 27.4, - 26.6, - 25.9, - 25.2, - 24.7, - 24.3, - 23.6, - 22.9, - 22.6, - 22.6, - 22.7, - 22.5, - 21.8, - 20.6, - 20, - 19.7, - 19.2, - 18.7, - 18.1, - 17.4, - 17, - 16.6, - 16.4, - 16.2, - 15.8, - 15.1, - 14.1, - 13.5, - 13.4, - 13.3, - 13.3, - 13.1, - 13, - 12.9, - 12.9, - 12.8, - 12.7, - 12.6, - 12.4, - 12.3, - 12.3, - 12.2, - 12.2, - 12.1, - 12.1, - 12, - 12, - 11.9, - 11.8, - 11.8, - 11.6, - 10.7, - 10.5, - 10.4, - 10.4, - 10.3, - 10.3, - 10.2, - 10.1, - 10.1, - 10.1, - 10, - 10, - 9.9, - 9.7, - 9.4, - 9.4, - 9.3, - 9.1, - 8.9, - 9, - 9, - 9.1, - 8.9, - 8.4, - 8.1, - 7.8, - 7.5, - 6.7, - 5.7, - 4.5, - 3.5, - 3.1, - 3.2, - 3.1, - 2.9, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.6, - 2.7, - 2.6, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 2.8, - 2.8, - 2.7, - 2.6, - 2.8, - 3, - 3, - 3, - 3, - 3.1, - 3.2, - 3.4, - 3.5, - 3.7, - 3.9, - 4.1, - 4.1, - 4, - 3.9, - 3.7, - 3.4, - 3.1, - 2.4, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.5, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.5, - 3.6, - 3.7, - 66.3, - 66.1, - 66, - 65.8, - 65.7, - 66, - 66.2, - 66.1, - 65.9, - 65.4, - 65.1, - 64.7, - 64.4, - 64.2, - 63.9, - 63.7, - 63.4, - 63.1, - 61.2, - 61.6, - 61.1, - 59.3, - 57.3, - 55.6, - 54.3, - 53.5, - 53, - 52.4, - 52, - 51.7, - 51.4, - 51.1, - 50.5, - 49.8, - 49.4, - 49.1, - 48.8, - 48.6, - 48.2, - 47.8, - 47.4, - 47, - 46.5, - 46, - 45.9, - 45.5, - 45, - 44.6, - 44.1, - 43.9, - 43.8, - 43.8, - 43.6, - 43.4, - 43, - 42.8, - 42.7, - 42.7, - 42.8, - 42.8, - 42.6, - 42.3, - 42.1, - 42.1, - 41.8, - 41.5, - 41.4, - 41.3, - 41.3, - 41.1, - 40.9, - 40.6, - 39.9, - 39.3, - 39, - 38.7, - 37.8, - 37.3, - 36.6, - 35.4, - 34.8, - 34.1, - 33.6, - 33.2, - 32.9, - 32.4, - 31.7, - 31, - 30.6, - 30.4, - 30.1, - 29.9, - 29.2, - 27.7, - 27.8, - 27.9, - 27.8, - 27.4, - 26.5, - 25.8, - 25.3, - 24.7, - 24.3, - 23.6, - 22.8, - 22.7, - 22.8, - 22.8, - 22.7, - 21.7, - 20.6, - 20.1, - 19.7, - 19.2, - 18.7, - 18.1, - 17.5, - 17.1, - 16.8, - 16.6, - 16.3, - 15.7, - 14.7, - 13.6, - 13.4, - 13.4, - 13.4, - 13.4, - 13.2, - 13.1, - 13, - 12.9, - 12.8, - 12.7, - 12.6, - 12.5, - 12.4, - 12.3, - 12.3, - 12.2, - 12.1, - 12.1, - 12, - 12, - 11.9, - 11.8, - 11.8, - 11.7, - 11.6, - 10.8, - 10.5, - 10.4, - 10.4, - 10.3, - 10.2, - 10.2, - 10.1, - 10.1, - 10, - 10, - 9.9, - 9.8, - 9.7, - 9.7, - 9.5, - 9.3, - 9, - 9, - 9, - 9.1, - 9, - 8.5, - 8.2, - 7.7, - 7, - 6.1, - 5, - 3.9, - 3.2, - 3.3, - 3.3, - 3.1, - 2.7, - 2.5, - 2.6, - 2.6, - 2.5, - 2.5, - 2.6, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.7, - 2.6, - 2.8, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.3, - 3.5, - 3.7, - 3.9, - 4.1, - 4.2, - 4.2, - 4.1, - 3.9, - 3.5, - 3.2, - 2.6, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 2.4, - 2.6, - 2.7, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.3, - 3.3, - 3.5, - 3.6, - 3.7, - 66.4, - 66.2, - 66, - 65.8, - 65.8, - 66.3, - 66.5, - 66.4, - 66.3, - 66.3, - 65.9, - 65.2, - 64.7, - 64.4, - 64, - 63.8, - 63.6, - 63.4, - 63.2, - 63, - 62.5, - 61.3, - 59.5, - 58.3, - 56, - 53.9, - 53.3, - 53, - 52.5, - 52.1, - 51.7, - 51.1, - 50.6, - 50, - 49.5, - 49.3, - 49, - 48.7, - 48.3, - 47.9, - 47.7, - 47.5, - 47, - 46.3, - 45.8, - 45.5, - 45.1, - 44.8, - 44.5, - 44.4, - 44.2, - 44.1, - 44, - 43.7, - 43.4, - 43.5, - 43.4, - 43.5, - 43.5, - 43.4, - 43, - 42.6, - 42.4, - 42.2, - 41.8, - 41.5, - 41.5, - 41.4, - 41.4, - 41.2, - 40.9, - 40.5, - 40, - 39.4, - 39.1, - 38.5, - 37.7, - 37.3, - 36.7, - 35.4, - 34.8, - 34.1, - 33.7, - 33.4, - 33.3, - 32.6, - 31.9, - 31.4, - 31, - 30.7, - 30.2, - 30.1, - 29.1, - 27.8, - 27.9, - 28, - 28, - 27.4, - 26.4, - 25.8, - 25.2, - 24.8, - 24.2, - 23.6, - 23, - 22.8, - 22.9, - 22.9, - 22.8, - 21.7, - 20.6, - 20, - 19.6, - 19.1, - 18.8, - 18.1, - 17.7, - 17.2, - 16.9, - 16.8, - 16.5, - 15.9, - 15, - 14.1, - 13.5, - 13.4, - 13.3, - 13.3, - 13.3, - 13.2, - 13.1, - 13, - 12.9, - 12.8, - 12.6, - 12.6, - 12.4, - 12.3, - 12.2, - 12.2, - 12.1, - 12.1, - 12, - 12, - 11.9, - 11.8, - 11.8, - 11.7, - 11.7, - 11.7, - 10.8, - 10.4, - 10.4, - 10.3, - 10.2, - 10.2, - 10.1, - 10.1, - 10, - 9.9, - 9.9, - 9.9, - 9.9, - 9.8, - 9.6, - 9.3, - 9.1, - 9.1, - 9.1, - 9.2, - 9.1, - 8.6, - 8.1, - 7.3, - 6.3, - 5.3, - 4.2, - 3.4, - 3.2, - 3.4, - 3.3, - 3, - 2.6, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.6, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.6, - 2.8, - 3, - 3.1, - 3.1, - 3.2, - 3.2, - 3.3, - 3.3, - 3.5, - 3.7, - 3.9, - 4, - 4.1, - 4.2, - 4.2, - 3.9, - 3.6, - 3.3, - 2.8, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.5, - 2.6, - 2.7, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.3, - 3.5, - 3.6, - 3.7, - 66.3, - 66.2, - 66, - 65.9, - 65.9, - 66.4, - 66.6, - 66.6, - 66.6, - 66.5, - 66.2, - 65.6, - 64.9, - 64.6, - 64.2, - 64, - 63.8, - 63.7, - 63.4, - 63.2, - 62.7, - 62.3, - 61.7, - 60.8, - 57.9, - 54.9, - 54, - 53.7, - 53, - 52.5, - 51.9, - 51.3, - 50.8, - 50.3, - 49.7, - 49.5, - 49.1, - 48.8, - 48.5, - 48.3, - 48.1, - 47.7, - 47.2, - 46.5, - 46.1, - 45.7, - 45.4, - 45, - 45, - 45, - 44.8, - 44.6, - 44.6, - 44.4, - 44.3, - 44.2, - 44, - 44.1, - 44.1, - 43.7, - 43.2, - 42.8, - 42.5, - 42.2, - 41.8, - 41.6, - 41.7, - 41.5, - 41.4, - 41.4, - 40.9, - 40.5, - 40, - 39.6, - 39.1, - 38.4, - 37.8, - 37.3, - 36.3, - 35.5, - 35.2, - 34.5, - 34.1, - 34, - 33.7, - 33.2, - 32.5, - 31.7, - 31.5, - 31, - 30.6, - 30.3, - 29.4, - 28, - 28.1, - 28.1, - 28.1, - 27.6, - 26.4, - 25.7, - 25.1, - 24.7, - 24.1, - 23.5, - 23, - 23, - 23, - 23, - 22.9, - 21.6, - 20.5, - 19.8, - 19.5, - 19.1, - 18.7, - 18.2, - 17.8, - 17.4, - 17, - 16.9, - 16.7, - 16.2, - 15.5, - 14.9, - 14, - 13.5, - 13.4, - 13.3, - 13.2, - 13.2, - 13.1, - 13, - 12.9, - 12.8, - 12.7, - 12.6, - 12.5, - 12.3, - 12.2, - 12.2, - 12.1, - 12.1, - 12, - 12, - 11.9, - 11.9, - 11.8, - 11.7, - 11.7, - 11.6, - 11.6, - 10.7, - 10.4, - 10.3, - 10.3, - 10.2, - 10.1, - 10.1, - 10, - 9.9, - 9.9, - 9.9, - 9.9, - 9.8, - 9.7, - 9.4, - 9.2, - 9.2, - 9.2, - 9.3, - 9.1, - 8.5, - 7.8, - 6.6, - 5.3, - 4.5, - 3.7, - 3.2, - 3.3, - 3.5, - 3.3, - 3, - 2.7, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.7, - 2.8, - 2.8, - 2.8, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.8, - 2.8, - 2.7, - 2.7, - 3, - 3.1, - 3.2, - 3.2, - 3.3, - 3.3, - 3.4, - 3.5, - 3.7, - 3.8, - 4, - 4.1, - 4.1, - 4.1, - 3.9, - 3.6, - 3.4, - 3.1, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.8, - 3, - 3, - 3, - 3.1, - 3.1, - 3.2, - 3.3, - 3.3, - 3.3, - 3.4, - 3.4, - 3.4, - 3.4, - 3.7, - 3.9, - 66.3, - 66.1, - 65.8, - 65.7, - 65.9, - 66.4, - 66.6, - 66.6, - 66.5, - 66.4, - 66.1, - 65.4, - 65, - 64.6, - 64.3, - 64.1, - 64, - 63.9, - 63.6, - 63.4, - 63.2, - 62.6, - 62.1, - 61.6, - 59, - 56.4, - 55.2, - 54.5, - 53.5, - 52.7, - 52, - 51.5, - 51, - 50.7, - 50.2, - 49.7, - 49.7, - 49.3, - 49, - 48.6, - 48.2, - 47.8, - 47.3, - 46.7, - 46.2, - 45.8, - 45.6, - 45.4, - 45.4, - 45.5, - 45.4, - 45.4, - 45.3, - 45.1, - 44.9, - 44.6, - 44.3, - 44.3, - 44.4, - 44, - 43.4, - 43, - 42.6, - 42.3, - 42.1, - 41.9, - 41.9, - 41.6, - 41.5, - 41.4, - 41, - 40.5, - 39.9, - 39.5, - 39.1, - 38.4, - 37.8, - 37.2, - 36.5, - 35.8, - 35.3, - 34.9, - 34.6, - 34.9, - 34.6, - 34.1, - 33.2, - 32.1, - 31.6, - 31.3, - 31, - 30.4, - 28.9, - 28, - 28.2, - 28.3, - 28.2, - 27.7, - 26.3, - 25.5, - 24.9, - 24.4, - 24, - 23.5, - 23.1, - 23.1, - 23.1, - 23.1, - 22.8, - 21.5, - 20.6, - 19.9, - 19.6, - 19.1, - 18.7, - 18.3, - 17.9, - 17.5, - 17.1, - 16.9, - 16.7, - 16.3, - 15.9, - 15.4, - 14.8, - 14.1, - 13.5, - 13.2, - 13.2, - 13.2, - 13.1, - 13.1, - 13, - 12.8, - 12.7, - 12.6, - 12.5, - 12.4, - 12.3, - 12.2, - 12.1, - 12.1, - 12, - 12, - 11.9, - 11.9, - 11.8, - 11.8, - 11.7, - 11.7, - 11.6, - 11.5, - 10.6, - 10.4, - 10.3, - 10.2, - 10.1, - 10.1, - 10, - 9.9, - 9.8, - 9.9, - 10, - 9.9, - 9.7, - 9.4, - 9.3, - 9.3, - 9.3, - 9.4, - 9.1, - 8.3, - 7.2, - 6, - 4.7, - 3.9, - 3.4, - 3.3, - 3.4, - 3.5, - 3.2, - 2.9, - 2.7, - 2.6, - 2.6, - 2.6, - 2.5, - 2.6, - 2.7, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.8, - 2.8, - 2.7, - 2.7, - 2.9, - 3.1, - 3.2, - 3.2, - 3.3, - 3.3, - 3.4, - 3.5, - 3.7, - 3.8, - 3.9, - 4, - 4.1, - 4, - 3.9, - 3.7, - 3.5, - 3.3, - 2.4, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 2.4, - 2.6, - 2.6, - 2.7, - 2.7, - 2.7, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3.1, - 3.3, - 3.3, - 3.3, - 3.5, - 3.8, - 3.7, - 3.7, - 3.9, - 4.2, - 66.1, - 66, - 65.9, - 65.8, - 66, - 66.3, - 66.5, - 66.6, - 66.5, - 66.3, - 65.9, - 65.3, - 65, - 64.5, - 64.3, - 64.1, - 64.1, - 63.9, - 63.8, - 63.6, - 63.2, - 62.7, - 62.4, - 61.8, - 60.6, - 58.2, - 56.2, - 55.2, - 54.1, - 53.3, - 52.4, - 51.7, - 51.4, - 51, - 50.7, - 50.3, - 49.9, - 49.6, - 49.3, - 48.7, - 48.1, - 47.5, - 47.2, - 46.9, - 46.4, - 46.1, - 45.9, - 45.9, - 46, - 46.1, - 46.1, - 46.2, - 46, - 45.6, - 45.3, - 44.8, - 44.6, - 44.7, - 44.7, - 44.3, - 43.6, - 43.2, - 42.9, - 42.7, - 42.5, - 42.2, - 42, - 41.8, - 41.5, - 41.5, - 41, - 40.4, - 39.9, - 39.5, - 39, - 38.6, - 38.2, - 37.6, - 37, - 36.4, - 35.8, - 35.4, - 35.4, - 35.4, - 35.1, - 34.7, - 33.8, - 32.4, - 31.8, - 31.4, - 31, - 30.3, - 28.9, - 28.2, - 28.3, - 28.4, - 28.4, - 27.6, - 26.1, - 25.4, - 24.6, - 24.1, - 23.8, - 23.5, - 23.3, - 23.3, - 23.3, - 23.3, - 23, - 22.1, - 20.9, - 20.1, - 19.6, - 19.1, - 18.6, - 18.2, - 17.9, - 17.5, - 17.2, - 16.9, - 16.6, - 16.5, - 16.1, - 15.7, - 15.3, - 14.7, - 13.8, - 13.2, - 13.2, - 13.2, - 13.2, - 13.2, - 13, - 12.9, - 12.8, - 12.6, - 12.6, - 12.5, - 12.4, - 12.3, - 12.2, - 12.1, - 12, - 12, - 11.9, - 11.9, - 11.8, - 11.8, - 11.7, - 11.7, - 11.5, - 11.6, - 11.4, - 10.7, - 10.3, - 10.2, - 10.1, - 10.1, - 10, - 9.9, - 9.8, - 9.8, - 9.8, - 9.8, - 9.5, - 9.4, - 9.4, - 9.4, - 9.4, - 9.5, - 9, - 8, - 6.7, - 5.4, - 4.3, - 3.5, - 3.2, - 3.2, - 3.3, - 3.3, - 3.1, - 2.8, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.7, - 2.8, - 2.8, - 2.9, - 2.8, - 2.8, - 2.7, - 2.9, - 3.1, - 3.2, - 3.2, - 3.3, - 3.3, - 3.4, - 3.4, - 3.5, - 3.7, - 3.9, - 4, - 4, - 4, - 3.8, - 3.7, - 3.5, - 3.3, - 2.7, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.5, - 2.6, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.5, - 3.6, - 3.8, - 3.9, - 4.2, - 4.3, - 65.9, - 65.9, - 65.9, - 65.9, - 66.1, - 66.3, - 66.5, - 66.7, - 66.5, - 66.3, - 65.9, - 65.3, - 64.9, - 64.6, - 64.3, - 64.2, - 64.1, - 64, - 64, - 63.8, - 63.2, - 62.8, - 62.6, - 62.2, - 61.6, - 60.4, - 58.2, - 56.7, - 55.6, - 54.8, - 53.8, - 52.9, - 52, - 51.3, - 50.9, - 50.5, - 50.2, - 49.9, - 49.4, - 48.7, - 48.2, - 47.7, - 47.5, - 47.1, - 46.7, - 46.6, - 46.5, - 46.6, - 46.7, - 46.8, - 46.8, - 46.7, - 46.5, - 46.1, - 45.7, - 45.2, - 45, - 45.1, - 45.1, - 44.4, - 43.9, - 43.5, - 43.2, - 43.1, - 42.8, - 42.4, - 42.3, - 42.1, - 41.7, - 41.4, - 40.9, - 40.4, - 40.1, - 39.7, - 39.3, - 38.8, - 38.4, - 38, - 37.5, - 36.9, - 36.6, - 36.2, - 36.1, - 35.8, - 35.4, - 35, - 34.3, - 32.9, - 32.2, - 31.6, - 31, - 30.3, - 29.1, - 28.3, - 28.5, - 28.5, - 28.5, - 27.7, - 25.9, - 25.2, - 24.5, - 23.8, - 23.7, - 23.6, - 23.5, - 23.4, - 23.4, - 23.4, - 23.1, - 22.3, - 21, - 20.1, - 19.6, - 19.1, - 18.6, - 18.2, - 17.8, - 17.5, - 17.2, - 17, - 16.7, - 16.5, - 16.2, - 16, - 15.6, - 15.2, - 14.5, - 13.6, - 13.2, - 13.2, - 13.2, - 13.2, - 13.1, - 13, - 12.8, - 12.7, - 12.7, - 12.6, - 12.5, - 12.4, - 12.3, - 12.1, - 12.1, - 12, - 11.9, - 11.9, - 11.8, - 11.8, - 11.7, - 11.7, - 11.6, - 11.5, - 11.5, - 11.4, - 10.7, - 10.2, - 10.2, - 10.1, - 10, - 9.9, - 9.8, - 9.8, - 9.7, - 9.6, - 9.5, - 9.5, - 9.5, - 9.4, - 9.5, - 9.5, - 8.9, - 7.6, - 6.1, - 4.9, - 3.7, - 3.1, - 3.1, - 3.3, - 3.3, - 3.2, - 2.9, - 2.8, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.6, - 2.7, - 2.7, - 2.8, - 2.9, - 3, - 3, - 2.9, - 2.8, - 2.7, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.3, - 3.3, - 3.4, - 3.4, - 3.6, - 3.8, - 3.9, - 3.9, - 3.8, - 3.7, - 3.6, - 3.4, - 3.1, - 2.8, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.6, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.9, - 2.9, - 3, - 3.1, - 3.2, - 3.4, - 3.5, - 3.6, - 3.8, - 4, - 4.3, - 65.9, - 65.9, - 65.9, - 66, - 66.1, - 66.3, - 66.6, - 66.9, - 66.7, - 66.3, - 65.9, - 65.3, - 65, - 64.7, - 64.4, - 64.2, - 64.1, - 64, - 64, - 63.9, - 63.4, - 62.9, - 62.6, - 62.2, - 61.8, - 61.1, - 59.6, - 58.3, - 57.3, - 56.3, - 55.4, - 54.5, - 53.6, - 52.7, - 51.9, - 51.6, - 51.4, - 50.7, - 49.9, - 49.1, - 48.4, - 48.1, - 48.1, - 47.7, - 47.4, - 47.3, - 47.4, - 47.3, - 47.3, - 47.3, - 47.4, - 47.2, - 46.9, - 46.4, - 46, - 45.5, - 45.3, - 45.4, - 45.4, - 44.9, - 44.3, - 43.8, - 43.5, - 43.5, - 43.2, - 42.8, - 42.7, - 42.4, - 41.8, - 41.3, - 40.9, - 40.6, - 40.3, - 40, - 39.8, - 39.4, - 39.1, - 38.8, - 38.4, - 37.9, - 37.4, - 36.8, - 36.5, - 36.1, - 35.8, - 35.3, - 34.4, - 33.2, - 32.3, - 31.8, - 31.2, - 30.2, - 29, - 28.5, - 28.6, - 28.7, - 28.7, - 27.8, - 25.6, - 24.7, - 24, - 23.9, - 23.7, - 23.7, - 23.6, - 23.6, - 23.5, - 23.5, - 22.5, - 21.6, - 20.9, - 20.3, - 19.8, - 19.2, - 18.6, - 18.2, - 17.8, - 17.5, - 17.2, - 17, - 16.7, - 16.5, - 16.2, - 16, - 15.7, - 15.4, - 15, - 14.4, - 13.7, - 13.2, - 13.2, - 13.2, - 13.1, - 13, - 12.9, - 12.8, - 12.7, - 12.6, - 12.5, - 12.4, - 12.4, - 12.2, - 12.1, - 12, - 11.9, - 11.9, - 11.8, - 11.8, - 11.7, - 11.7, - 11.6, - 11.6, - 11.5, - 11.5, - 11.5, - 10.8, - 10.4, - 10.4, - 10.3, - 10, - 9.8, - 9.8, - 9.7, - 9.7, - 9.7, - 9.6, - 9.6, - 9.6, - 9.7, - 9.4, - 8.4, - 7, - 5.5, - 4.3, - 3.3, - 2.8, - 3.1, - 3.2, - 3.1, - 3, - 2.9, - 2.8, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 3, - 3.1, - 3, - 2.9, - 2.8, - 2.8, - 2.8, - 2.9, - 3, - 3.2, - 3.3, - 3.3, - 3.2, - 3.3, - 3.4, - 3.5, - 3.6, - 3.7, - 3.7, - 3.7, - 3.6, - 3.5, - 3.2, - 3, - 2.8, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.5, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3.1, - 3.2, - 3.3, - 3.5, - 3.6, - 3.8, - 4.1, - 4.4, - 66, - 65.9, - 66, - 66.2, - 66.3, - 66.4, - 66.7, - 66.9, - 66.8, - 66.5, - 66.1, - 65.4, - 65.1, - 64.7, - 64.4, - 64.2, - 64.1, - 64.1, - 64.1, - 64, - 63.5, - 63, - 62.5, - 62.1, - 61.6, - 60.9, - 59.4, - 58.4, - 58, - 57.5, - 56.6, - 55.8, - 55.1, - 54.5, - 53.5, - 53.2, - 52.7, - 51.8, - 51, - 49.9, - 49.2, - 48.9, - 48.6, - 48.4, - 48.3, - 48.2, - 48.1, - 48.1, - 47.8, - 47.7, - 47.7, - 47.6, - 47.2, - 46.6, - 46.2, - 45.8, - 45.6, - 45.7, - 45.6, - 45.2, - 44.6, - 44.1, - 43.8, - 43.6, - 43.5, - 43.4, - 42.9, - 42.5, - 41.8, - 41.4, - 41.1, - 41, - 40.8, - 40.8, - 40.6, - 40.4, - 40, - 39.5, - 38.9, - 38.5, - 37.9, - 37.4, - 37, - 36.4, - 36, - 35.5, - 34.6, - 33.2, - 32.4, - 31.8, - 31.2, - 30.2, - 29.1, - 28.6, - 28.8, - 28.8, - 28.8, - 27.7, - 25.2, - 24.3, - 24, - 24, - 23.9, - 23.8, - 23.7, - 23.7, - 23.6, - 23.6, - 22.7, - 21.6, - 21.1, - 20.6, - 20.2, - 19.5, - 18.7, - 18.2, - 17.8, - 17.4, - 17.2, - 16.9, - 16.7, - 16.5, - 16.2, - 16, - 15.7, - 15.5, - 15.2, - 14.7, - 14.3, - 13.5, - 13.2, - 13.2, - 13.1, - 13, - 13, - 12.9, - 12.8, - 12.7, - 12.6, - 12.4, - 12.4, - 12.2, - 12.1, - 12, - 12, - 11.9, - 11.8, - 11.8, - 11.7, - 11.7, - 11.6, - 11.6, - 11.5, - 11.5, - 11.4, - 11.4, - 10.9, - 10.9, - 10.7, - 10.3, - 9.9, - 9.8, - 9.8, - 9.8, - 9.8, - 9.7, - 9.7, - 9.7, - 9.8, - 9.3, - 8, - 6.6, - 5.2, - 3.9, - 3, - 2.8, - 3.1, - 3.1, - 3, - 2.9, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.6, - 2.7, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 2.9, - 2.8, - 2.8, - 2.8, - 2.9, - 3, - 3.2, - 3.3, - 3.3, - 3.2, - 3.2, - 3.3, - 3.4, - 3.5, - 3.6, - 3.6, - 3.5, - 3.4, - 3.3, - 3, - 2.8, - 2.7, - 2.4, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.4, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3.1, - 3.2, - 3.2, - 3.3, - 3.5, - 3.8, - 4.1, - 4.3, - 66.2, - 66.2, - 66.3, - 66.5, - 66.5, - 66.5, - 66.6, - 66.8, - 66.7, - 66.4, - 66, - 65.5, - 65.2, - 64.7, - 64.3, - 64.2, - 64.2, - 64.3, - 64.2, - 64, - 63.6, - 62.9, - 62.3, - 61.8, - 61.2, - 60.6, - 59.7, - 58.9, - 58.6, - 58.1, - 57.3, - 56.7, - 56.2, - 55.5, - 54.7, - 54.1, - 53.2, - 52, - 51.5, - 50.7, - 50.1, - 49.7, - 49.1, - 49.1, - 49.1, - 48.9, - 48.9, - 48.5, - 48.2, - 48, - 48, - 47.7, - 47.2, - 46.9, - 46.6, - 46.3, - 46.1, - 46, - 45.8, - 45.5, - 44.7, - 44.2, - 43.9, - 43.8, - 43.7, - 43.6, - 43.2, - 42.7, - 42.3, - 41.7, - 41.5, - 41.6, - 41.4, - 41.6, - 41.4, - 41.1, - 40.7, - 40, - 39.5, - 39.1, - 38.5, - 37.8, - 37.2, - 36.7, - 36.2, - 35.7, - 34.8, - 33.4, - 32.7, - 32, - 31.2, - 30.4, - 29.6, - 28.8, - 28.9, - 29, - 28.9, - 27.5, - 25, - 24.3, - 24.2, - 24.1, - 24, - 23.9, - 23.8, - 23.8, - 23.8, - 23.6, - 22.5, - 21.7, - 21.3, - 20.8, - 20.2, - 19.6, - 18.8, - 18.3, - 17.9, - 17.5, - 17.1, - 16.9, - 16.6, - 16.4, - 16.2, - 16, - 15.7, - 15.5, - 15.2, - 14.9, - 14.6, - 14.2, - 13.4, - 13.2, - 13.1, - 13.1, - 13, - 12.9, - 12.8, - 12.7, - 12.6, - 12.5, - 12.4, - 12.3, - 12.3, - 12.2, - 12, - 11.9, - 11.8, - 11.8, - 11.7, - 11.7, - 11.6, - 11.6, - 11.5, - 11.5, - 11.4, - 11.4, - 11.3, - 11.2, - 11, - 10.6, - 10.2, - 9.9, - 9.8, - 9.8, - 9.8, - 9.8, - 9.7, - 9.9, - 9.8, - 9.1, - 7.7, - 6.3, - 4.9, - 3.8, - 2.9, - 2.7, - 3, - 3.1, - 3, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.8, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3.2, - 3.3, - 3.3, - 3.2, - 3.2, - 3.2, - 3.3, - 3.3, - 3.4, - 3.4, - 3.3, - 3.2, - 3, - 2.9, - 2.8, - 2.8, - 2.5, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.6, - 2.7, - 2.8, - 2.8, - 2.9, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.2, - 3.3, - 3.4, - 3.8, - 4.1, - 4.2, - 66.3, - 66.5, - 66.6, - 66.6, - 66.5, - 66.5, - 66.5, - 66.7, - 66.4, - 66.1, - 65.9, - 65.6, - 65.2, - 64.6, - 64.3, - 64.2, - 64.3, - 64.4, - 64.2, - 63.8, - 63.5, - 62.8, - 62, - 61.5, - 61, - 60.5, - 59.9, - 59.4, - 58.9, - 58.3, - 57.7, - 57.1, - 56.6, - 55.9, - 55.2, - 54.6, - 53.9, - 53.1, - 52.3, - 51.7, - 51, - 50.5, - 50, - 49.7, - 49.7, - 49.5, - 49.2, - 48.8, - 48.5, - 48.3, - 48.2, - 47.8, - 47.4, - 47.1, - 47.1, - 46.8, - 46.4, - 46.2, - 46, - 45.7, - 44.9, - 44.3, - 43.9, - 43.9, - 43.9, - 43.7, - 43.2, - 42.8, - 42.4, - 42.1, - 42.1, - 42.2, - 42.1, - 41.8, - 41.6, - 41.4, - 40.9, - 40.3, - 39.8, - 39.5, - 39.1, - 38.4, - 37.7, - 37.1, - 36.5, - 36, - 35.1, - 33.8, - 33, - 32.6, - 31.6, - 30.7, - 29.6, - 29, - 29.1, - 29.1, - 29.1, - 27.9, - 25.2, - 24.4, - 24.3, - 24.2, - 24.1, - 24, - 24, - 23.9, - 23.9, - 23.9, - 22.7, - 22, - 21.4, - 21, - 20.3, - 19.5, - 18.8, - 18.3, - 17.9, - 17.5, - 17.2, - 16.9, - 16.6, - 16.5, - 16.2, - 15.9, - 15.7, - 15.5, - 15.2, - 15, - 14.7, - 14.3, - 13.6, - 13.4, - 13.2, - 13.1, - 13.1, - 13, - 12.9, - 12.8, - 12.6, - 12.5, - 12.4, - 12.5, - 12.4, - 12.3, - 12.1, - 11.9, - 11.9, - 11.8, - 11.7, - 11.7, - 11.6, - 11.6, - 11.5, - 11.5, - 11.4, - 11.3, - 11.3, - 11.3, - 11.1, - 10.7, - 10.5, - 10.2, - 10, - 9.9, - 9.9, - 9.9, - 9.9, - 10, - 9.7, - 8.8, - 7.3, - 6, - 4.6, - 3.5, - 2.6, - 2.5, - 3, - 3.1, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.9, - 3, - 3.1, - 3.2, - 3.1, - 3, - 3, - 3, - 2.9, - 2.9, - 2.8, - 2.9, - 2.9, - 3, - 3.1, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.1, - 3.1, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.6, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.6, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.1, - 3.2, - 3.3, - 3.3, - 3.7, - 3.9, - 4, - 66.5, - 66.6, - 66.7, - 66.8, - 66.5, - 66.3, - 66.3, - 66.3, - 66.1, - 66, - 65.8, - 65.6, - 65.1, - 65, - 64.7, - 64.3, - 64.3, - 64.4, - 64, - 63.7, - 63.3, - 62.7, - 61.9, - 61.3, - 61, - 60.6, - 60.1, - 59.4, - 58.8, - 58.2, - 57.6, - 57.3, - 56.7, - 56, - 55.4, - 54.9, - 54.2, - 53.5, - 52.9, - 52.3, - 51.7, - 51.2, - 50.8, - 50.3, - 50.2, - 49.9, - 49.4, - 49.2, - 48.9, - 48.4, - 48.2, - 47.9, - 47.6, - 47.3, - 47.2, - 46.9, - 46.7, - 46.4, - 46.1, - 45.8, - 45.1, - 44.4, - 43.9, - 43.9, - 43.9, - 43.7, - 43.3, - 42.8, - 42.6, - 42.5, - 42.7, - 42.7, - 42.3, - 41.9, - 41.7, - 41.5, - 41.1, - 40.6, - 40.2, - 39.8, - 39.4, - 38.8, - 38.2, - 37.6, - 37, - 36.4, - 35, - 34.1, - 33.6, - 33.2, - 32.4, - 31.2, - 29.8, - 29.2, - 29.2, - 29.2, - 29.2, - 28.3, - 25.3, - 24.5, - 24.4, - 24.3, - 24.2, - 24.1, - 24.1, - 24, - 24, - 23.9, - 23, - 22, - 21.4, - 20.9, - 20.4, - 19.7, - 18.9, - 18.3, - 17.9, - 17.6, - 17.3, - 16.9, - 16.7, - 16.5, - 16.3, - 16, - 15.8, - 15.6, - 15.3, - 15, - 14.6, - 14.2, - 14, - 14, - 13.5, - 13.2, - 13.1, - 13, - 12.9, - 12.8, - 12.7, - 12.6, - 12.5, - 12.4, - 12.3, - 12.3, - 12.2, - 12.1, - 11.9, - 11.8, - 11.7, - 11.7, - 11.6, - 11.6, - 11.6, - 11.5, - 11.4, - 11.4, - 11.3, - 11.2, - 11.1, - 11, - 10.8, - 10.5, - 10.2, - 10, - 10, - 10, - 10, - 10, - 9.6, - 8.3, - 7, - 5.6, - 4.2, - 3.1, - 2.5, - 2.5, - 2.9, - 3.1, - 3, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 3, - 3.1, - 3.2, - 3.2, - 3.1, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 2.7, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.4, - 2.6, - 3, - 3, - 3.1, - 3.1, - 3.3, - 3.3, - 3.3, - 3.6, - 3.8, - 3.9, - 66.7, - 66.7, - 66.7, - 66.6, - 66.2, - 66.1, - 66.2, - 66.3, - 66.2, - 66.1, - 65.9, - 65.7, - 65.3, - 65.1, - 64.8, - 64.3, - 64.2, - 64.1, - 63.8, - 63.5, - 63.2, - 62.5, - 61.8, - 61.6, - 61.3, - 60.8, - 60.1, - 59.4, - 58.8, - 58.2, - 57.7, - 57.3, - 56.7, - 56.1, - 55.4, - 54.9, - 54.4, - 53.8, - 53.3, - 52.7, - 52.1, - 51.6, - 51.2, - 50.8, - 50.5, - 50.2, - 49.8, - 49.5, - 49.1, - 48.6, - 48.2, - 47.9, - 47.6, - 47.6, - 47.4, - 47.1, - 46.8, - 46.4, - 46.2, - 45.8, - 45.2, - 44.5, - 44.1, - 43.8, - 43.8, - 43.8, - 43.4, - 43, - 42.8, - 43, - 42.9, - 42.8, - 42.4, - 42.1, - 42, - 41.7, - 41.3, - 40.9, - 40.4, - 39.9, - 39.5, - 39.1, - 38.5, - 37.9, - 37.3, - 36.9, - 35.7, - 34.5, - 33.9, - 33.6, - 33, - 31.7, - 30, - 29.3, - 29.4, - 29.4, - 29.3, - 28.4, - 25.3, - 24.6, - 24.5, - 24.4, - 24.3, - 24.2, - 24.2, - 24.2, - 24.2, - 23.7, - 22.7, - 21.9, - 21.3, - 20.8, - 20.4, - 19.6, - 19, - 18.3, - 17.9, - 17.6, - 17.3, - 17, - 16.7, - 16.5, - 16.2, - 16, - 15.8, - 15.6, - 15.3, - 14.8, - 14.4, - 14.3, - 14.1, - 14.1, - 14, - 13.5, - 13.1, - 13, - 12.9, - 12.8, - 12.8, - 12.8, - 12.6, - 12.4, - 12.3, - 12.2, - 12.2, - 12.1, - 12, - 11.8, - 11.8, - 11.7, - 11.6, - 11.6, - 11.5, - 11.5, - 11.4, - 11.4, - 11.3, - 11.3, - 11.2, - 11.1, - 10.9, - 10.6, - 10.4, - 10.1, - 10.1, - 10.1, - 10.1, - 10, - 9.3, - 7.9, - 6.6, - 5.3, - 4, - 2.9, - 2.4, - 2.4, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 2.9, - 3, - 3.1, - 3.1, - 3.1, - 3.2, - 3.1, - 3, - 3, - 3, - 3.1, - 3.1, - 3, - 3, - 2.9, - 3, - 3.1, - 3.2, - 3.2, - 3.3, - 3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.6, - 2.9, - 2.9, - 3.3, - 3.4, - 3.4, - 3.5, - 3.6, - 3.7, - 66.8, - 66.8, - 66.6, - 66.4, - 66, - 66, - 66.2, - 66.3, - 66.2, - 66, - 65.9, - 65.7, - 65.4, - 65.1, - 64.9, - 64.5, - 64.1, - 63.8, - 63.4, - 63.1, - 62.9, - 62.3, - 62, - 61.9, - 61.8, - 61.3, - 60.1, - 59.4, - 59, - 58.4, - 57.9, - 57.4, - 56.7, - 56, - 55.4, - 55, - 54.6, - 54.1, - 53.7, - 53.2, - 52.5, - 51.9, - 51.6, - 51.3, - 50.9, - 50.6, - 50.2, - 49.8, - 49.4, - 48.9, - 48.4, - 47.9, - 47.8, - 47.8, - 47.6, - 47.2, - 46.9, - 46.5, - 46.1, - 45.7, - 45.2, - 44.7, - 44.1, - 43.7, - 43.7, - 43.5, - 43.3, - 43.2, - 43.3, - 43.3, - 43.1, - 42.8, - 42.5, - 42.3, - 42.1, - 41.9, - 41.5, - 41.1, - 40.5, - 40, - 39.5, - 39.1, - 38.6, - 37.9, - 37.5, - 37.3, - 36.8, - 35.3, - 34.4, - 33.8, - 33.5, - 32.6, - 30.1, - 29.4, - 29.5, - 29.5, - 29.5, - 28.3, - 25.4, - 24.7, - 24.6, - 24.5, - 24.4, - 24.4, - 24.3, - 24.3, - 24.3, - 23.3, - 22.4, - 21.8, - 21.3, - 20.8, - 20.2, - 19.6, - 18.9, - 18.4, - 17.9, - 17.6, - 17.4, - 17, - 16.8, - 16.5, - 16.3, - 16, - 15.8, - 15.5, - 14.9, - 14.4, - 14.3, - 14.3, - 14.2, - 14.1, - 14.1, - 13.9, - 13.4, - 13.1, - 13, - 13, - 13.1, - 13, - 12.7, - 12.6, - 12.5, - 12.3, - 12.2, - 12.1, - 12.1, - 11.9, - 11.8, - 11.7, - 11.6, - 11.6, - 11.5, - 11.5, - 11.4, - 11.3, - 11.3, - 11.3, - 11.2, - 11, - 10.9, - 10.6, - 10.4, - 10.3, - 10.2, - 10.2, - 10.2, - 10, - 8.9, - 7.5, - 6.3, - 5.1, - 3.9, - 3, - 2.4, - 2.5, - 2.9, - 3.1, - 3.1, - 3.1, - 3.1, - 3.3, - 3.4, - 3.4, - 3.4, - 3.4, - 3.3, - 3.2, - 3.3, - 3.4, - 3.3, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.2, - 3.2, - 3.3, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3.1, - 3.2, - 3.4, - 3.5, - 3.5, - 3.4, - 2.4, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.5, - 3, - 3.3, - 3.4, - 3.4, - 3.5, - 3.4, - 66.9, - 66.8, - 66.4, - 66.1, - 65.8, - 65.8, - 66, - 66, - 66, - 66, - 65.9, - 65.8, - 65.4, - 65.1, - 64.7, - 64.4, - 63.9, - 63.5, - 63.1, - 63, - 62.8, - 62.3, - 62.2, - 62, - 61.8, - 61.3, - 60.3, - 59.3, - 59, - 58.6, - 58.1, - 57.6, - 57, - 56.2, - 55.6, - 55.2, - 54.7, - 54.3, - 54, - 53.7, - 53.3, - 52.7, - 52.1, - 51.7, - 51.2, - 50.7, - 50.4, - 50, - 49.6, - 49.1, - 48.6, - 48.3, - 48.1, - 47.9, - 47.7, - 47.3, - 47.1, - 46.6, - 46.1, - 45.7, - 45.2, - 44.7, - 44.1, - 43.7, - 43.7, - 43.4, - 43.3, - 43.4, - 43.6, - 43.5, - 43.3, - 42.9, - 42.7, - 42.4, - 42.3, - 42.3, - 41.9, - 41.3, - 40.8, - 40.2, - 39.7, - 39.3, - 38.8, - 38, - 37.8, - 37.7, - 37.2, - 35.9, - 35.1, - 34.3, - 34, - 33.2, - 30.6, - 29.5, - 29.6, - 29.7, - 29.6, - 28.5, - 25.4, - 24.8, - 24.7, - 24.6, - 24.5, - 24.5, - 24.5, - 24.5, - 24.5, - 23.2, - 22.3, - 21.7, - 21.2, - 20.7, - 20.1, - 19.5, - 18.9, - 18.4, - 17.9, - 17.6, - 17.4, - 17.1, - 16.8, - 16.5, - 16.3, - 16, - 15.8, - 15.4, - 14.8, - 14.4, - 14.4, - 14.3, - 14.2, - 14.2, - 14.1, - 14.1, - 13.8, - 13.2, - 13.1, - 13.1, - 13.1, - 13.1, - 12.9, - 12.7, - 12.5, - 12.4, - 12.2, - 12.1, - 12, - 12, - 11.8, - 11.7, - 11.6, - 11.6, - 11.5, - 11.4, - 11.4, - 11.3, - 11.2, - 11.2, - 11.1, - 10.9, - 10.8, - 10.6, - 10.4, - 10.4, - 10.3, - 10.3, - 10.2, - 9.7, - 8.5, - 7.2, - 5.9, - 4.8, - 4, - 3, - 2.5, - 2.6, - 2.9, - 3.1, - 3.2, - 3.3, - 3.3, - 3.5, - 3.7, - 3.7, - 3.7, - 3.6, - 3.5, - 3.5, - 3.6, - 3.6, - 3.4, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3.1, - 3, - 3, - 3.2, - 3.3, - 3.3, - 3.4, - 3.4, - 3.3, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.2, - 3.3, - 3.4, - 3.6, - 3.7, - 3.8, - 3.8, - 3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.5, - 3.2, - 3.4, - 3.4, - 3.5, - 3.4, - 66.9, - 66.6, - 66.3, - 65.9, - 65.7, - 65.7, - 65.7, - 65.9, - 66, - 65.9, - 65.9, - 65.8, - 65.5, - 65, - 64.4, - 64, - 63.7, - 63.4, - 63.1, - 63, - 62.9, - 62.4, - 62, - 61.9, - 61.5, - 61, - 60.3, - 59.6, - 59.1, - 58.6, - 58, - 57.6, - 57.1, - 56.6, - 56, - 55.4, - 55, - 54.5, - 54.2, - 53.9, - 53.6, - 53.3, - 52.9, - 52.1, - 51.5, - 51, - 50.6, - 50.2, - 49.7, - 49.2, - 48.7, - 48.4, - 48.4, - 48.1, - 47.9, - 47.5, - 47.2, - 46.9, - 46.3, - 45.8, - 45.1, - 44.7, - 44.2, - 43.7, - 43.7, - 43.5, - 43.3, - 43.6, - 43.7, - 43.6, - 43.3, - 43.1, - 43, - 42.9, - 42.7, - 42.4, - 42, - 41.5, - 41, - 40.5, - 39.9, - 39.4, - 39, - 38.4, - 38.1, - 37.8, - 37.4, - 36.5, - 35.4, - 34.7, - 34.3, - 33.4, - 31.2, - 29.6, - 29.8, - 29.8, - 29.8, - 28.5, - 25.4, - 24.9, - 24.8, - 24.7, - 24.6, - 24.6, - 24.6, - 24.6, - 24.4, - 23, - 22.2, - 21.5, - 21.1, - 20.7, - 20.1, - 19.4, - 18.9, - 18.5, - 18.1, - 17.8, - 17.4, - 17.2, - 16.9, - 16.6, - 16.4, - 16.1, - 15.8, - 15.4, - 15.2, - 15, - 14.7, - 14.4, - 14.3, - 14.3, - 14.2, - 14.1, - 14, - 13.7, - 13.3, - 13.1, - 13.1, - 13.2, - 13.1, - 12.9, - 12.7, - 12.5, - 12.3, - 12.2, - 12.1, - 12, - 11.9, - 11.8, - 11.6, - 11.6, - 11.5, - 11.4, - 11.4, - 11.3, - 11.2, - 11.1, - 11, - 10.9, - 10.7, - 10.6, - 10.5, - 10.5, - 10.4, - 10.4, - 10.2, - 9.3, - 8, - 6.7, - 5.5, - 4.4, - 3.6, - 3, - 2.6, - 2.8, - 3, - 3.2, - 3.3, - 3.5, - 3.5, - 3.6, - 3.8, - 3.9, - 3.8, - 3.7, - 3.6, - 3.6, - 3.7, - 3.5, - 3.4, - 3.3, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.3, - 3.4, - 3.4, - 3.5, - 3.5, - 3.4, - 3.4, - 3.4, - 3.4, - 3.3, - 3.2, - 3.2, - 3.3, - 3.4, - 3.6, - 3.7, - 3.8, - 3.9, - 4, - 3.7, - 2.5, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 3.1, - 3.3, - 3.4, - 3.5, - 3.5, - 66.8, - 66.5, - 66.1, - 65.7, - 65.6, - 65.5, - 65.5, - 65.7, - 65.9, - 65.8, - 65.7, - 65.6, - 65.2, - 64.6, - 64.1, - 63.8, - 63.4, - 63.2, - 63.1, - 63, - 62.9, - 62.6, - 62, - 61.7, - 61.2, - 60.7, - 60.3, - 59.7, - 59.4, - 58.7, - 58, - 57.5, - 57.2, - 56.8, - 56.2, - 55.7, - 55.1, - 54.7, - 54.2, - 53.8, - 53.7, - 53.6, - 53, - 52.4, - 51.8, - 51.3, - 50.9, - 50.4, - 49.8, - 49.3, - 49, - 48.7, - 48.4, - 48.1, - 48, - 47.6, - 47.3, - 47.1, - 46.7, - 45.9, - 45.1, - 44.5, - 44, - 43.7, - 43.7, - 43.5, - 43.5, - 43.7, - 43.9, - 43.8, - 43.6, - 43.4, - 43.3, - 43.2, - 43, - 42.5, - 42, - 41.4, - 40.9, - 40.4, - 39.9, - 39.5, - 39.2, - 38.7, - 38.4, - 38, - 37.7, - 37.1, - 35.9, - 35, - 34.5, - 33.3, - 31.1, - 29.9, - 29.9, - 30, - 29.9, - 29.1, - 25.5, - 25, - 24.9, - 24.8, - 24.8, - 24.7, - 24.8, - 24.8, - 24.2, - 22.6, - 22, - 21.4, - 20.9, - 20.5, - 20, - 19.4, - 19, - 18.6, - 18.3, - 17.9, - 17.5, - 17.2, - 16.9, - 16.7, - 16.4, - 16.1, - 15.8, - 15.6, - 15.4, - 15.3, - 15, - 14.5, - 14.3, - 14.4, - 14.3, - 14.3, - 14.1, - 13.8, - 13.3, - 13.2, - 13.2, - 13.2, - 13.1, - 13.1, - 12.9, - 12.7, - 12.5, - 12.3, - 12.2, - 12, - 11.9, - 11.9, - 11.7, - 11.6, - 11.5, - 11.5, - 11.4, - 11.3, - 11.2, - 11.1, - 11, - 10.9, - 10.8, - 10.7, - 10.6, - 10.6, - 10.6, - 10.5, - 9.9, - 8.8, - 7.5, - 6.2, - 5, - 4.1, - 3.4, - 2.8, - 2.8, - 3, - 3.2, - 3.4, - 3.5, - 3.6, - 3.7, - 3.7, - 3.9, - 3.9, - 3.9, - 3.9, - 3.9, - 3.8, - 3.7, - 3.6, - 3.4, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.3, - 3.5, - 3.6, - 3.6, - 3.6, - 3.6, - 3.6, - 3.6, - 3.5, - 3.5, - 3.4, - 3.3, - 3.3, - 3.5, - 3.6, - 3.8, - 3.9, - 3.9, - 3.9, - 3.8, - 3.1, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 3, - 3.2, - 3.3, - 3.4, - 3.5, - 66.7, - 66.3, - 66, - 65.6, - 65.4, - 65.3, - 65.3, - 65.5, - 65.6, - 65.4, - 65.4, - 65.4, - 64.9, - 64.2, - 63.7, - 63.4, - 63.1, - 63.1, - 63.2, - 63.2, - 62.8, - 62.7, - 62.2, - 61.7, - 61.2, - 60.8, - 60.4, - 59.9, - 59.5, - 58.8, - 57.9, - 57.4, - 57.2, - 56.8, - 56.2, - 55.6, - 55, - 54.6, - 54, - 53.6, - 53.5, - 53.6, - 53, - 52.4, - 51.9, - 51.3, - 50.9, - 50.5, - 50, - 49.5, - 49.4, - 49.1, - 48.5, - 48.2, - 48.1, - 47.8, - 47.5, - 47.1, - 46.7, - 45.8, - 45.1, - 44.4, - 44, - 43.7, - 43.6, - 43.5, - 43.7, - 43.9, - 44.2, - 44.2, - 44.1, - 43.7, - 43.6, - 43.6, - 43.2, - 42.6, - 42, - 41.5, - 41, - 40.5, - 40.1, - 39.8, - 39.3, - 39, - 38.7, - 38.1, - 37.7, - 37.5, - 36.7, - 35.2, - 34.8, - 33.6, - 31.5, - 30.1, - 30.1, - 30.1, - 30.1, - 29.4, - 25.7, - 25, - 25, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 23.8, - 22.4, - 21.7, - 21.2, - 20.7, - 20.3, - 19.8, - 19.3, - 18.9, - 18.5, - 18.2, - 17.9, - 17.6, - 17.2, - 16.8, - 16.6, - 16.4, - 16.2, - 16, - 15.9, - 15.6, - 15.4, - 15.2, - 15, - 14.5, - 14.3, - 14.3, - 14.3, - 14.2, - 13.9, - 13.5, - 13.3, - 13.2, - 13.2, - 13.1, - 13.1, - 13, - 12.8, - 12.6, - 12.4, - 12.2, - 12.1, - 12, - 11.9, - 11.9, - 11.6, - 11.6, - 11.7, - 11.6, - 11.3, - 11.2, - 11.1, - 11, - 10.9, - 10.9, - 10.8, - 10.7, - 10.7, - 10.7, - 10.3, - 9.2, - 8, - 6.8, - 5.7, - 4.6, - 4, - 3.4, - 3, - 2.9, - 3.1, - 3.4, - 3.6, - 3.7, - 3.8, - 3.8, - 3.9, - 4, - 4, - 4, - 4.1, - 4.1, - 4, - 3.8, - 3.6, - 3.4, - 3.2, - 3.2, - 3.2, - 3.2, - 3.3, - 3.3, - 3.2, - 3.2, - 3.2, - 3.2, - 3.3, - 3.3, - 3.6, - 3.7, - 3.8, - 3.8, - 3.8, - 3.9, - 3.9, - 3.8, - 3.7, - 3.6, - 3.5, - 3.5, - 3.6, - 3.8, - 3.9, - 4, - 4, - 3.9, - 3.8, - 3.7, - 2.6, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.7, - 3, - 3.1, - 3.3, - 3.5, - 66.6, - 66.1, - 65.6, - 65.5, - 65.2, - 65.2, - 65, - 65.2, - 65.2, - 65.1, - 65.2, - 65, - 64.2, - 63.8, - 63.4, - 63.1, - 63.1, - 63.2, - 63.3, - 63.1, - 62.8, - 62.4, - 62.1, - 61.7, - 61.3, - 60.9, - 60.5, - 60.1, - 59.7, - 59.1, - 57.9, - 57.5, - 57.1, - 56.4, - 55.9, - 55.3, - 54.8, - 54.3, - 53.8, - 53.4, - 53.1, - 53.2, - 52.8, - 52.3, - 51.8, - 51.2, - 50.9, - 50.6, - 50.1, - 49.8, - 49.6, - 49.3, - 48.7, - 48.3, - 48, - 47.7, - 47.5, - 47.1, - 46.6, - 45.8, - 45.1, - 44.5, - 44, - 43.8, - 43.6, - 43.6, - 44, - 44.4, - 44.6, - 44.6, - 44.6, - 44.1, - 43.8, - 43.7, - 43.2, - 42.8, - 42.3, - 41.9, - 41.4, - 40.9, - 40.6, - 40.2, - 39.6, - 39.1, - 38.7, - 38, - 37.7, - 37.7, - 37.3, - 35.7, - 35.4, - 34.5, - 31.7, - 30.2, - 30.2, - 30.3, - 30.2, - 30, - 26.4, - 25.1, - 25.1, - 25, - 25, - 25, - 25, - 25, - 23.1, - 22.1, - 21.5, - 21, - 20.5, - 20, - 19.6, - 19.2, - 18.8, - 18.4, - 18.1, - 17.8, - 17.5, - 17.2, - 16.9, - 16.6, - 16.4, - 16.2, - 16, - 15.9, - 15.7, - 15.5, - 15.3, - 15.1, - 14.8, - 14.3, - 14.3, - 14.2, - 14.2, - 14.1, - 13.8, - 13.5, - 13.4, - 13.2, - 13.1, - 13.1, - 13.1, - 13, - 12.8, - 12.5, - 12.4, - 12.2, - 12.2, - 12.1, - 11.9, - 11.7, - 11.7, - 11.7, - 11.6, - 11.5, - 11.3, - 11.2, - 11.1, - 11, - 10.9, - 10.9, - 10.9, - 10.9, - 10.7, - 10, - 8.6, - 7.2, - 6.1, - 5.3, - 4.7, - 4.2, - 3.5, - 3, - 3, - 3.2, - 3.5, - 3.8, - 3.9, - 3.9, - 3.9, - 4, - 4.1, - 4.1, - 4.2, - 4.3, - 4.2, - 4.1, - 3.9, - 3.7, - 3.4, - 3.3, - 3.3, - 3.3, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.5, - 3.7, - 3.9, - 4, - 4, - 4, - 4.3, - 4.3, - 4, - 3.9, - 3.8, - 3.7, - 3.6, - 3.7, - 3.9, - 4.1, - 4.1, - 4, - 3.9, - 3.9, - 3.7, - 3.4, - 2.7, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.5, - 2.9, - 3, - 3.1, - 3.3, - 66.3, - 65.9, - 65.4, - 65.1, - 65, - 64.9, - 64.8, - 64.8, - 64.7, - 64.8, - 64.7, - 64.3, - 63.7, - 63.4, - 63.2, - 63, - 63, - 63.2, - 63.2, - 62.9, - 62.6, - 62.4, - 62, - 61.7, - 61.3, - 61.1, - 60.6, - 60, - 59.6, - 59.3, - 58.7, - 58, - 57.1, - 56.5, - 55.9, - 55.3, - 54.7, - 54.2, - 53.8, - 53.3, - 52.9, - 53, - 52.8, - 52.3, - 51.8, - 51.3, - 51, - 50.7, - 50.5, - 50.1, - 49.8, - 49.4, - 48.8, - 48.3, - 47.9, - 47.7, - 47.5, - 47.1, - 46.6, - 45.8, - 45, - 44.5, - 43.9, - 43.7, - 43.6, - 44, - 44.5, - 44.9, - 44.9, - 44.8, - 44.7, - 44.2, - 44, - 43.8, - 43.2, - 42.9, - 42.5, - 42.1, - 41.9, - 41.3, - 41.1, - 40.7, - 39.9, - 39.3, - 38.8, - 38.2, - 38, - 37.8, - 37.5, - 36.1, - 35.6, - 34.8, - 31.9, - 30.3, - 30.4, - 30.4, - 30.4, - 30.3, - 28.2, - 25.7, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 23, - 21.8, - 21.4, - 21, - 20.3, - 19.8, - 19.4, - 19, - 18.6, - 18.3, - 18.1, - 17.8, - 17.6, - 17.3, - 17, - 16.7, - 16.5, - 16.4, - 16, - 15.8, - 15.6, - 15.5, - 15.2, - 15.1, - 14.8, - 14.6, - 14.3, - 14.2, - 14.1, - 14, - 14, - 13.8, - 13.5, - 13.5, - 13.3, - 13.1, - 13.1, - 13, - 12.9, - 12.7, - 12.5, - 12.4, - 12.3, - 12.1, - 11.9, - 11.8, - 11.8, - 11.7, - 11.6, - 11.5, - 11.5, - 11.3, - 11.1, - 11.1, - 11.1, - 11, - 11, - 11, - 10.8, - 9.7, - 8.3, - 7, - 5.9, - 5.2, - 4.8, - 4.2, - 3.4, - 3.1, - 3, - 3.1, - 3.5, - 3.9, - 4, - 4, - 4, - 4.1, - 4.1, - 4.2, - 4.4, - 4.4, - 4.3, - 4.2, - 4, - 3.7, - 3.5, - 3.5, - 3.5, - 3.5, - 3.6, - 3.6, - 3.6, - 3.6, - 3.5, - 3.5, - 3.5, - 3.5, - 3.6, - 3.8, - 4, - 4.1, - 4.1, - 4.2, - 4.3, - 4.4, - 4.3, - 4.2, - 4.1, - 3.9, - 3.9, - 3.9, - 4.1, - 4.2, - 4.2, - 4.1, - 4, - 3.9, - 3.9, - 3.9, - 3.7, - 2.8, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.7, - 2.9, - 3, - 3.2, - 66.2, - 65.6, - 65.1, - 64.8, - 64.7, - 64.7, - 64.5, - 64.4, - 64.4, - 64.3, - 64.2, - 63.8, - 63.4, - 63.1, - 63, - 63, - 62.9, - 63, - 63, - 62.7, - 62.4, - 62.2, - 62, - 61.5, - 61.3, - 61.1, - 60.7, - 60.2, - 60, - 59.6, - 59.1, - 58.5, - 57.7, - 56.8, - 55.9, - 55.4, - 54.7, - 54.1, - 53.7, - 53.3, - 52.9, - 52.8, - 52.7, - 52.4, - 51.8, - 51.3, - 51, - 50.8, - 50.7, - 50.3, - 49.8, - 49.4, - 49, - 48.5, - 48, - 47.6, - 47.4, - 47.2, - 46.7, - 45.7, - 45, - 44.5, - 44, - 43.9, - 43.9, - 44.3, - 44.8, - 45.2, - 45.1, - 44.9, - 44.8, - 44.5, - 44.1, - 43.7, - 43.3, - 42.9, - 42.6, - 42.4, - 42.2, - 41.9, - 41.3, - 40.9, - 40.3, - 39.4, - 38.9, - 38.4, - 38.2, - 37.9, - 37.4, - 36.4, - 35.5, - 34.2, - 31.2, - 30.4, - 30.5, - 30.5, - 30.5, - 30.4, - 29.1, - 26.4, - 25.4, - 25.2, - 25.2, - 25.3, - 25.4, - 25.2, - 22.3, - 21.7, - 21.2, - 20.7, - 20.2, - 19.9, - 19.4, - 19, - 18.6, - 18.4, - 18.1, - 17.8, - 17.6, - 17.3, - 17, - 16.8, - 16.5, - 16.4, - 16.2, - 16, - 15.8, - 15.6, - 15.2, - 14.9, - 14.8, - 14.7, - 14.5, - 14.3, - 14.1, - 14, - 13.9, - 13.8, - 13.7, - 13.6, - 13.5, - 13.1, - 13.1, - 13.1, - 13, - 12.8, - 12.7, - 12.5, - 12.3, - 12.1, - 11.9, - 11.9, - 11.8, - 11.7, - 11.6, - 11.5, - 11.5, - 11.4, - 11.1, - 11, - 11.1, - 11, - 11, - 10.9, - 10.8, - 9.8, - 8.6, - 7.3, - 5.9, - 5.2, - 4.7, - 4, - 3.3, - 3.1, - 3, - 3.2, - 3.7, - 4, - 4.1, - 4.2, - 4.2, - 4.2, - 4.3, - 4.4, - 4.5, - 4.5, - 4.5, - 4.3, - 4.1, - 3.8, - 3.7, - 3.7, - 3.7, - 3.8, - 3.8, - 3.8, - 3.8, - 3.8, - 3.8, - 3.7, - 3.7, - 3.7, - 3.8, - 3.9, - 4.1, - 4.2, - 4.3, - 4.4, - 4.5, - 4.6, - 4.6, - 4.5, - 4.4, - 4.2, - 4.1, - 4.1, - 4.3, - 4.4, - 4.3, - 4.2, - 4.1, - 4, - 4.1, - 4.1, - 4.1, - 3.3, - 2.7, - 2.5, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.5, - 2.8, - 3, - 3.1, - 65.9, - 65.3, - 64.9, - 64.5, - 64.4, - 64.4, - 64.3, - 64.2, - 64.1, - 64, - 63.9, - 63.5, - 63.2, - 63.1, - 63.1, - 62.9, - 62.9, - 62.8, - 62.8, - 62.6, - 62.5, - 62.4, - 62, - 61.5, - 61.4, - 61.2, - 60.8, - 60.4, - 60.1, - 59.7, - 59.1, - 58.5, - 57.9, - 57.1, - 56.3, - 55.6, - 54.7, - 54.2, - 53.8, - 53.4, - 52.9, - 52.7, - 52.7, - 52.7, - 52.1, - 51.4, - 51, - 50.7, - 50.6, - 50.2, - 49.7, - 49.4, - 48.9, - 48.3, - 47.9, - 47.6, - 47.3, - 46.9, - 46.4, - 45.6, - 45, - 44.6, - 44.2, - 44, - 44.1, - 44.6, - 44.9, - 45.1, - 44.9, - 44.8, - 44.8, - 44.6, - 44.1, - 43.7, - 43.4, - 43.1, - 42.9, - 42.7, - 42.5, - 42.3, - 41.7, - 41.1, - 40.5, - 39.5, - 39, - 38.5, - 38, - 37.9, - 37.6, - 36.5, - 35.5, - 34.5, - 31.3, - 30.5, - 30.7, - 30.7, - 30.7, - 30.5, - 29.4, - 27.6, - 26.4, - 25.9, - 25.6, - 25.6, - 25.6, - 24.6, - 22.4, - 21.7, - 21.1, - 20.7, - 20.1, - 19.7, - 19.4, - 19.1, - 18.7, - 18.4, - 18.1, - 17.9, - 17.7, - 17.3, - 17, - 16.8, - 16.5, - 16.3, - 16.1, - 16, - 15.9, - 15.7, - 15.4, - 15.2, - 14.9, - 14.6, - 14.5, - 14.3, - 14.1, - 14, - 13.8, - 13.7, - 13.7, - 13.5, - 13.2, - 13.1, - 13.1, - 13.1, - 13, - 12.9, - 12.8, - 12.4, - 12.1, - 12, - 11.9, - 11.8, - 11.7, - 11.6, - 11.6, - 11.5, - 11.4, - 11.4, - 11.1, - 11.1, - 11.2, - 11, - 10.9, - 10.9, - 10.7, - 10.1, - 9.1, - 7.7, - 6.3, - 5.2, - 4.5, - 4, - 3.5, - 3.1, - 3, - 3.2, - 3.8, - 4, - 4.1, - 4.2, - 4.3, - 4.3, - 4.4, - 4.6, - 4.7, - 4.6, - 4.5, - 4.4, - 4.2, - 4, - 3.9, - 3.9, - 3.9, - 4, - 4, - 4.1, - 4.1, - 4, - 4, - 3.9, - 3.9, - 3.9, - 4, - 4.1, - 4.3, - 4.4, - 4.5, - 4.6, - 4.7, - 4.8, - 4.9, - 4.9, - 4.7, - 4.5, - 4.4, - 4.4, - 4.5, - 4.5, - 4.5, - 4.3, - 4.2, - 4.2, - 4.3, - 4.3, - 4.3, - 3.7, - 3, - 2.7, - 2.7, - 2.6, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.6, - 2.8, - 3, - 65.6, - 65.2, - 64.7, - 64.3, - 64.2, - 64.1, - 64, - 64, - 64, - 64.1, - 64, - 63.6, - 63.4, - 63.1, - 63.1, - 62.8, - 62.8, - 62.7, - 62.6, - 62.5, - 62.5, - 62.4, - 62.2, - 61.7, - 61.3, - 61.2, - 61, - 60.5, - 60.1, - 59.7, - 59, - 58.5, - 58, - 57.3, - 56.7, - 55.9, - 55.1, - 54.7, - 54.3, - 53.7, - 53, - 52.6, - 52.6, - 52.6, - 52.1, - 51.6, - 51, - 50.6, - 50.4, - 50.1, - 49.9, - 49.5, - 48.8, - 48.3, - 47.9, - 47.4, - 47.1, - 46.5, - 46.1, - 45.7, - 45.3, - 44.9, - 44.6, - 44.3, - 44.3, - 44.9, - 45, - 45, - 44.9, - 44.8, - 44.7, - 44.6, - 44.1, - 43.8, - 43.5, - 43.3, - 43.2, - 42.9, - 42.7, - 42.4, - 42, - 41.3, - 40.5, - 39.6, - 39, - 38.4, - 37.8, - 37.6, - 37.5, - 36.5, - 35.5, - 34.9, - 31.8, - 30.6, - 30.8, - 30.8, - 30.8, - 30.6, - 29.6, - 27.9, - 26.8, - 26.4, - 26.1, - 26, - 25.9, - 24.4, - 22.6, - 21.9, - 21.3, - 20.9, - 20.2, - 19.8, - 19.4, - 19, - 18.7, - 18.4, - 18.2, - 17.9, - 17.7, - 17.3, - 17, - 16.7, - 16.5, - 16.4, - 16.1, - 16, - 15.8, - 15.6, - 15.5, - 15.2, - 15, - 14.8, - 14.6, - 14.5, - 14.2, - 14, - 13.8, - 13.5, - 13.4, - 13.2, - 13.1, - 13, - 13, - 13, - 12.9, - 12.8, - 12.7, - 12.2, - 12, - 12, - 11.9, - 11.8, - 11.7, - 11.6, - 11.5, - 11.4, - 11.3, - 11.3, - 11, - 11, - 11.1, - 10.9, - 10.9, - 10.8, - 10.7, - 10.3, - 9.3, - 8, - 6.7, - 5.8, - 5.2, - 4.6, - 3.7, - 3.4, - 3.2, - 3.4, - 3.9, - 4, - 4.2, - 4.3, - 4.4, - 4.5, - 4.6, - 4.7, - 4.7, - 4.7, - 4.6, - 4.5, - 4.4, - 4.2, - 4.1, - 4.1, - 4.2, - 4.3, - 4.3, - 4.3, - 4.3, - 4.3, - 4.2, - 4.2, - 4.2, - 4.2, - 4.2, - 4.3, - 4.4, - 4.6, - 4.7, - 4.9, - 5, - 5.1, - 5.1, - 5.1, - 5, - 4.8, - 4.6, - 4.6, - 4.6, - 4.6, - 4.5, - 4.4, - 4.3, - 4.4, - 4.5, - 4.5, - 4.6, - 4, - 3.3, - 2.9, - 2.8, - 3, - 2.9, - 2.7, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.7, - 3, - 65.3, - 65, - 64.6, - 64.3, - 64.1, - 64, - 63.8, - 63.9, - 64.2, - 64.4, - 64.2, - 63.5, - 63.4, - 63.3, - 63, - 62.7, - 62.6, - 62.5, - 62.5, - 62.4, - 62.4, - 62.4, - 62.2, - 61.5, - 61.3, - 61.2, - 61.1, - 60.5, - 60, - 59.6, - 59.1, - 58.5, - 57.9, - 57.5, - 56.8, - 56.4, - 55.9, - 55.2, - 54.6, - 54, - 53.3, - 52.8, - 52.5, - 52.2, - 51.9, - 51.7, - 51.2, - 50.6, - 50.3, - 50.1, - 50, - 49.5, - 48.9, - 48.4, - 47.9, - 47.5, - 46.8, - 46.3, - 45.9, - 45.7, - 45.5, - 45.1, - 44.8, - 44.5, - 44.6, - 45, - 45.1, - 45.1, - 45, - 44.8, - 44.8, - 44.6, - 44.1, - 43.8, - 43.7, - 43.6, - 43.3, - 43.1, - 42.8, - 42.4, - 42.1, - 41.4, - 40.7, - 39.6, - 39.1, - 38.4, - 37.8, - 37.4, - 37.2, - 36.4, - 35.6, - 34.6, - 31.7, - 30.8, - 30.9, - 31, - 31, - 30.8, - 29.9, - 28.7, - 27.5, - 26.7, - 26.4, - 26.2, - 26.1, - 24.6, - 23.1, - 22.3, - 21.5, - 20.9, - 20.4, - 20, - 19.5, - 19.1, - 18.7, - 18.4, - 18.2, - 17.9, - 17.7, - 17.4, - 17, - 16.7, - 16.6, - 16.4, - 16.2, - 16, - 15.8, - 15.6, - 15.4, - 15.2, - 15, - 14.8, - 14.7, - 14.5, - 14.4, - 14.1, - 13.7, - 13.5, - 13.3, - 13.2, - 13, - 12.9, - 12.8, - 12.7, - 12.7, - 12.6, - 12.4, - 12.2, - 12, - 12, - 11.9, - 11.7, - 11.6, - 11.6, - 11.5, - 11.3, - 11.2, - 11.2, - 10.9, - 10.9, - 11, - 10.9, - 10.7, - 10.7, - 10.6, - 10.4, - 9.6, - 8.5, - 7.5, - 6.6, - 5.8, - 4.8, - 4, - 3.5, - 3.4, - 3.6, - 4, - 4.1, - 4.3, - 4.4, - 4.5, - 4.6, - 4.7, - 4.7, - 4.8, - 4.8, - 4.7, - 4.7, - 4.6, - 4.5, - 4.4, - 4.4, - 4.6, - 4.6, - 4.6, - 4.6, - 4.6, - 4.5, - 4.5, - 4.5, - 4.6, - 4.5, - 4.5, - 4.5, - 4.6, - 4.8, - 4.9, - 5, - 5.2, - 5.2, - 5.3, - 5.3, - 5.2, - 5, - 4.9, - 4.8, - 4.8, - 4.7, - 4.6, - 4.6, - 4.5, - 4.6, - 4.7, - 4.8, - 4.8, - 4.3, - 3.5, - 3.1, - 2.9, - 3, - 3, - 3.1, - 3.1, - 3, - 2.8, - 2.6, - 2.3, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.3, - 2.4, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.4, - 2.8, - 65.1, - 64.7, - 64.5, - 64.3, - 63.9, - 63.7, - 63.7, - 63.8, - 64.3, - 64.4, - 64.1, - 63.6, - 63.4, - 63.3, - 62.9, - 62.6, - 62.4, - 62.4, - 62.4, - 62.2, - 62.2, - 62, - 61.7, - 61.4, - 61.2, - 61.1, - 61, - 60.3, - 59.8, - 59.3, - 58.8, - 58.1, - 57.4, - 57.1, - 56.6, - 56.4, - 56.1, - 55.4, - 55, - 54.4, - 53.6, - 53, - 52.5, - 52.2, - 51.8, - 51.6, - 51.2, - 50.6, - 50.4, - 49.9, - 49.7, - 49.5, - 49, - 48.4, - 47.9, - 47.5, - 47, - 46.4, - 46.1, - 45.9, - 45.7, - 45.3, - 44.9, - 44.6, - 44.6, - 45.1, - 45.3, - 45.3, - 45, - 44.8, - 44.9, - 44.4, - 44, - 43.9, - 43.8, - 43.9, - 43.5, - 43.2, - 43, - 42.7, - 42.3, - 41.5, - 40.5, - 39.7, - 39.2, - 38.4, - 37.9, - 37.5, - 36.9, - 36.4, - 35.7, - 34.6, - 32.2, - 31, - 31.1, - 31.1, - 31.1, - 30.9, - 29.9, - 29, - 27.9, - 27.1, - 26.6, - 26.4, - 26.2, - 24.9, - 23.5, - 22.9, - 22.1, - 21.4, - 20.8, - 20.2, - 19.6, - 19.1, - 18.8, - 18.5, - 18.2, - 17.9, - 17.7, - 17.3, - 17, - 16.7, - 16.6, - 16.4, - 16.2, - 16, - 15.9, - 15.6, - 15.3, - 15.2, - 15, - 14.8, - 14.7, - 14.5, - 14.3, - 14, - 13.8, - 13.5, - 13.2, - 13, - 12.9, - 12.8, - 12.7, - 12.5, - 12.5, - 12.4, - 12.3, - 12.1, - 12, - 11.9, - 11.8, - 11.7, - 11.5, - 11.4, - 11.4, - 11.3, - 11.2, - 11.1, - 10.9, - 10.9, - 11, - 10.9, - 10.7, - 10.6, - 10.5, - 10.4, - 10.1, - 9.1, - 8.1, - 7.1, - 6, - 4.9, - 4.2, - 3.8, - 3.7, - 3.9, - 4.1, - 4.2, - 4.4, - 4.5, - 4.6, - 4.6, - 4.7, - 4.8, - 4.9, - 4.9, - 5, - 5, - 4.9, - 4.8, - 4.7, - 4.8, - 4.8, - 4.9, - 4.9, - 4.9, - 4.8, - 4.8, - 4.7, - 4.9, - 5.1, - 4.8, - 4.7, - 4.8, - 4.9, - 5, - 5.1, - 5.2, - 5.3, - 5.4, - 5.4, - 5.5, - 5.4, - 5.3, - 5.1, - 5, - 4.9, - 4.8, - 4.8, - 4.7, - 4.7, - 4.7, - 4.9, - 5, - 5.1, - 4.6, - 3.7, - 3.2, - 3.1, - 3.2, - 3.3, - 3.2, - 3.2, - 3.1, - 3, - 3, - 2.8, - 2.8, - 2.5, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.5, - 2.5, - 2.5, - 2.4, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.5, - 64.8, - 64.6, - 64.3, - 64.1, - 63.8, - 63.7, - 63.7, - 63.8, - 64.2, - 64.2, - 64, - 63.7, - 63.6, - 63.2, - 62.8, - 62.5, - 62.4, - 62.4, - 62.4, - 62.2, - 62, - 61.9, - 61.5, - 61.2, - 61, - 60.9, - 60.8, - 60.1, - 59.6, - 59, - 58.4, - 57.7, - 57.2, - 56.8, - 56.6, - 56.3, - 55.9, - 55.6, - 55.3, - 54.8, - 54, - 53.4, - 52.5, - 52.1, - 51.6, - 51.2, - 51, - 50.6, - 50.3, - 49.8, - 49.6, - 49.4, - 49.1, - 48.5, - 48.1, - 47.7, - 47.5, - 46.9, - 46.4, - 46.1, - 45.8, - 45.3, - 44.9, - 44.6, - 44.5, - 45.2, - 45.7, - 45.5, - 45, - 44.9, - 44.8, - 44.3, - 44, - 44, - 44, - 44, - 43.7, - 43.4, - 43.2, - 42.8, - 42.4, - 41.8, - 40.6, - 39.9, - 39.2, - 38.5, - 38.2, - 37.7, - 36.7, - 36.1, - 35.6, - 34.8, - 32.8, - 31.2, - 31.2, - 31.3, - 31.2, - 31.1, - 30.2, - 29.2, - 28.2, - 27.4, - 26.9, - 26.7, - 26.1, - 24.9, - 24, - 23.5, - 22.9, - 22.2, - 21.6, - 20.9, - 20.1, - 19.4, - 18.9, - 18.6, - 18.2, - 17.9, - 17.7, - 17.4, - 17, - 16.7, - 16.6, - 16.4, - 16.2, - 16, - 15.8, - 15.6, - 15.3, - 15.1, - 15, - 14.8, - 14.7, - 14.5, - 14.3, - 14, - 13.8, - 13.5, - 13.3, - 12.9, - 12.8, - 12.7, - 12.6, - 12.5, - 12.4, - 12.2, - 12.1, - 12.1, - 12, - 11.9, - 11.8, - 11.5, - 11.4, - 11.3, - 11.2, - 11.2, - 11.1, - 11, - 10.8, - 10.9, - 10.9, - 10.8, - 10.7, - 10.6, - 10.5, - 10.3, - 10.2, - 9.7, - 8.6, - 7.5, - 6.4, - 5.4, - 4.7, - 4.2, - 4, - 4.2, - 4.3, - 4.6, - 4.9, - 4.9, - 4.8, - 4.9, - 5, - 5, - 5.1, - 5.2, - 5.2, - 5.2, - 5.2, - 5, - 5, - 5.1, - 5.1, - 5.2, - 5.2, - 5.1, - 5, - 5, - 5.1, - 5.4, - 5.7, - 5.2, - 5, - 5, - 5.1, - 5.2, - 5.2, - 5.3, - 5.4, - 5.5, - 5.5, - 5.5, - 5.5, - 5.4, - 5.3, - 5.2, - 5, - 5, - 4.9, - 4.9, - 4.9, - 4.9, - 5, - 5.2, - 5.3, - 5.1, - 4, - 3.4, - 3.2, - 3.3, - 3.4, - 3.4, - 3.3, - 3.1, - 3.1, - 3, - 2.9, - 2.9, - 2.8, - 2.8, - 2.6, - 2.4, - 2.6, - 2.6, - 2.4, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.4, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 64.8, - 64.5, - 64.2, - 63.9, - 63.7, - 63.7, - 63.8, - 63.9, - 64.1, - 64.2, - 64.1, - 63.8, - 63.5, - 63.1, - 62.8, - 62.7, - 62.6, - 62.5, - 62.4, - 62.2, - 62, - 61.8, - 61.3, - 61, - 60.9, - 60.7, - 60.4, - 59.9, - 59.2, - 58.5, - 58.1, - 57.6, - 57.3, - 57, - 56.6, - 56.4, - 56, - 55.6, - 55.2, - 54.7, - 54, - 53.3, - 52.5, - 52, - 51.5, - 51.2, - 51, - 50.6, - 50.1, - 49.8, - 49.6, - 49.5, - 49.2, - 48.8, - 48.4, - 48.1, - 47.8, - 47.2, - 46.8, - 46.3, - 45.9, - 45.5, - 45, - 44.7, - 44.6, - 45.1, - 45.7, - 45.5, - 45.1, - 44.8, - 44.6, - 44.2, - 44.1, - 44.1, - 44.1, - 44, - 43.8, - 43.5, - 43.3, - 43.1, - 42.6, - 41.9, - 41, - 40, - 39.3, - 38.8, - 38.5, - 37.7, - 36.6, - 35.8, - 35.1, - 34.6, - 32.2, - 31.2, - 31.3, - 31.4, - 31.4, - 31.2, - 30.6, - 29.6, - 28.5, - 27.6, - 27, - 27, - 26.6, - 25.6, - 24.7, - 24.1, - 23.5, - 22.9, - 22.3, - 21.5, - 20.6, - 19.7, - 19.1, - 18.7, - 18.2, - 17.9, - 17.6, - 17.4, - 17.1, - 16.8, - 16.6, - 16.5, - 16.2, - 16, - 15.8, - 15.6, - 15.3, - 15.1, - 15, - 14.8, - 14.6, - 14.4, - 14.2, - 14, - 13.8, - 13.5, - 13.1, - 12.8, - 12.7, - 12.6, - 12.5, - 12.4, - 12.3, - 12.3, - 12.1, - 12, - 11.9, - 11.8, - 11.6, - 11.5, - 11.4, - 11.3, - 11.2, - 11.1, - 11.1, - 11, - 10.8, - 10.9, - 10.8, - 10.7, - 10.6, - 10.4, - 10.3, - 10.2, - 10.2, - 10, - 9.3, - 8.1, - 7.3, - 6.4, - 5.2, - 4.7, - 4.5, - 4.5, - 4.5, - 5, - 5.4, - 5.3, - 5.1, - 5, - 5.2, - 5.3, - 5.4, - 5.4, - 5.4, - 5.4, - 5.4, - 5.3, - 5.4, - 5.3, - 5.3, - 5.4, - 5.4, - 5.3, - 5.3, - 5.3, - 5.5, - 5.8, - 5.9, - 5.4, - 5.2, - 5.3, - 5.3, - 5.3, - 5.4, - 5.4, - 5.5, - 5.5, - 5.6, - 5.6, - 5.6, - 5.5, - 5.4, - 5.2, - 5.2, - 5.1, - 5.1, - 5.1, - 5.1, - 5.1, - 5.2, - 5.3, - 5.4, - 5.4, - 4.5, - 3.7, - 3.4, - 3.5, - 3.6, - 3.5, - 3.4, - 3.2, - 3.2, - 3.1, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3, - 2.9, - 2.9, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.6, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 64.7, - 64.3, - 64.1, - 63.9, - 63.7, - 63.7, - 63.9, - 64, - 64.2, - 64.3, - 64.3, - 63.9, - 63.7, - 63.3, - 62.8, - 62.7, - 62.9, - 62.8, - 62.5, - 62.4, - 62.1, - 61.8, - 61.4, - 61, - 60.6, - 60.4, - 60.1, - 59.7, - 58.9, - 58.3, - 58.1, - 57.7, - 57.6, - 57.3, - 57, - 56.7, - 56.3, - 55.7, - 55.2, - 54.7, - 53.9, - 53.2, - 52.5, - 52.1, - 51.7, - 51.3, - 51.1, - 50.6, - 50.3, - 50, - 49.6, - 49.4, - 49.1, - 48.9, - 48.6, - 48.1, - 47.8, - 47.4, - 47, - 46.5, - 46.1, - 45.5, - 45, - 44.7, - 44.8, - 45.1, - 45.5, - 45.5, - 45.2, - 44.8, - 44.5, - 44.2, - 44.2, - 44.1, - 44, - 44, - 43.9, - 43.7, - 43.5, - 43.2, - 42.7, - 42, - 40.9, - 40, - 39.6, - 39.2, - 38.6, - 37.9, - 36.8, - 35.8, - 34.9, - 34.3, - 32.8, - 31.3, - 31.5, - 31.5, - 31.5, - 31.3, - 30.7, - 29.7, - 28.6, - 27.7, - 27.3, - 27.3, - 26.8, - 25.9, - 25, - 24.6, - 23.9, - 23.3, - 22.7, - 21.9, - 21.1, - 20.1, - 19.4, - 18.8, - 18.2, - 17.9, - 17.7, - 17.5, - 17.1, - 16.8, - 16.6, - 16.4, - 16.3, - 16, - 15.8, - 15.6, - 15.4, - 15.2, - 15, - 14.7, - 14.5, - 14.3, - 14.1, - 13.9, - 13.7, - 13.4, - 13, - 12.8, - 12.6, - 12.4, - 12.3, - 12.3, - 12.3, - 12.2, - 12.1, - 12, - 11.8, - 11.7, - 11.6, - 11.4, - 11.3, - 11.2, - 11.2, - 11.1, - 11.1, - 10.8, - 10.7, - 10.8, - 10.8, - 10.6, - 10.5, - 10.4, - 10.3, - 10.1, - 10.2, - 10, - 9.6, - 8.6, - 7.9, - 7.1, - 6, - 5.4, - 5, - 4.9, - 4.9, - 5.2, - 5.5, - 5.5, - 5.2, - 5.2, - 5.4, - 5.5, - 5.6, - 5.7, - 5.5, - 5.6, - 5.6, - 5.4, - 5.6, - 5.4, - 5.4, - 5.6, - 5.5, - 5.5, - 5.4, - 5.5, - 5.6, - 5.8, - 5.8, - 5.5, - 5.4, - 5.4, - 5.5, - 5.5, - 5.5, - 5.6, - 5.6, - 5.7, - 5.7, - 5.6, - 5.6, - 5.6, - 5.4, - 5.3, - 5.3, - 5.3, - 5.2, - 5.3, - 5.3, - 5.3, - 5.3, - 5.4, - 5.5, - 5.5, - 4.8, - 3.9, - 3.6, - 3.6, - 3.7, - 3.7, - 3.5, - 3.3, - 3.2, - 3.2, - 3.2, - 3.2, - 3.3, - 3.4, - 3.4, - 3.6, - 3.4, - 3.1, - 3.1, - 3, - 2.9, - 2.9, - 2.8, - 2.8, - 2.7, - 2.5, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 64.6, - 64.2, - 63.8, - 63.8, - 63.9, - 63.9, - 64.1, - 64.2, - 64.2, - 64.2, - 64.2, - 63.9, - 63.8, - 63.7, - 63.2, - 63, - 63.1, - 63, - 62.7, - 62.4, - 62.3, - 62, - 61.3, - 60.9, - 60.5, - 60.1, - 59.6, - 59.3, - 58.7, - 58.3, - 58, - 57.8, - 57.8, - 57.6, - 57.4, - 57, - 56.4, - 55.7, - 55.2, - 54.6, - 54, - 53.3, - 52.7, - 52.3, - 51.8, - 51.4, - 51.2, - 50.8, - 50.5, - 50.1, - 49.7, - 49.5, - 49.2, - 49.1, - 48.7, - 48.2, - 47.6, - 47.2, - 46.9, - 46.4, - 46, - 45.5, - 45.1, - 44.8, - 44.8, - 45.1, - 45.4, - 45.3, - 45.1, - 44.8, - 44.6, - 44.4, - 44.3, - 44.1, - 44, - 44, - 44.2, - 44, - 43.7, - 43.3, - 42.8, - 42.3, - 41.2, - 40.1, - 39.8, - 39.3, - 38.9, - 38.1, - 37, - 35.9, - 34.7, - 33.9, - 32.8, - 31.5, - 31.6, - 31.7, - 31.6, - 31.5, - 30.8, - 29.7, - 28.4, - 27.7, - 27.5, - 27.5, - 26.9, - 26, - 25.3, - 24.7, - 24.2, - 23.7, - 22.9, - 22.2, - 21.3, - 20.6, - 19.8, - 19.1, - 18.3, - 18, - 17.7, - 17.4, - 17.1, - 16.8, - 16.6, - 16.5, - 16.3, - 16, - 15.8, - 15.7, - 15.5, - 15.2, - 15, - 14.7, - 14.5, - 14.3, - 14.1, - 13.9, - 13.7, - 13.4, - 13, - 12.7, - 12.4, - 12.3, - 12.2, - 12.2, - 12.2, - 12.2, - 12.1, - 12.1, - 11.8, - 11.6, - 11.6, - 11.5, - 11.3, - 11.2, - 11.2, - 11.1, - 11, - 10.7, - 10.7, - 10.8, - 10.8, - 10.7, - 10.5, - 10.3, - 10.3, - 10.2, - 10.2, - 10, - 9.5, - 9, - 8.4, - 7.7, - 6.8, - 5.9, - 5.3, - 5.1, - 5.3, - 5.4, - 5.5, - 5.5, - 5.4, - 5.5, - 5.5, - 5.5, - 5.7, - 5.7, - 5.6, - 5.8, - 5.7, - 5.5, - 5.5, - 5.6, - 5.6, - 5.7, - 5.7, - 5.6, - 5.6, - 5.7, - 5.7, - 5.6, - 5.6, - 5.5, - 5.5, - 5.5, - 5.6, - 5.6, - 5.6, - 5.8, - 5.9, - 5.8, - 5.7, - 5.7, - 5.7, - 5.6, - 5.5, - 5.4, - 5.4, - 5.4, - 5.4, - 5.5, - 5.4, - 5.4, - 5.4, - 5.5, - 5.6, - 5.6, - 5.1, - 4.2, - 3.8, - 3.9, - 3.9, - 3.9, - 3.7, - 3.5, - 3.4, - 3.4, - 3.5, - 3.5, - 3.6, - 3.6, - 3.7, - 3.9, - 4, - 3.8, - 3.7, - 3.2, - 3.1, - 2.9, - 2.9, - 2.8, - 2.8, - 2.7, - 2.3, - 2.1, - 2.1, - 2.1, - 2.1, - 64.3, - 64.1, - 63.8, - 64, - 64.1, - 64.2, - 64.1, - 64.2, - 64.1, - 64.1, - 64.1, - 64, - 63.7, - 63.6, - 63.4, - 63.2, - 63.2, - 63.1, - 62.9, - 62.6, - 62.3, - 61.9, - 61.6, - 61, - 60.5, - 60, - 59.5, - 59.1, - 58.5, - 58.1, - 57.9, - 57.8, - 57.8, - 57.6, - 57.4, - 57, - 56.5, - 55.8, - 55.2, - 54.7, - 54.1, - 53.6, - 53.1, - 52.4, - 51.8, - 51.4, - 51.1, - 50.8, - 50.6, - 50.1, - 49.8, - 49.4, - 49.2, - 49, - 48.4, - 47.9, - 47.4, - 46.9, - 46.6, - 46.3, - 45.7, - 45.2, - 45, - 45, - 45.1, - 45.3, - 45.4, - 45.3, - 45, - 44.8, - 44.7, - 44.6, - 44.5, - 44.4, - 44.3, - 44.3, - 44.4, - 44.4, - 44.1, - 43.3, - 42.9, - 42.4, - 41.5, - 40.2, - 39.8, - 39.4, - 39, - 38.2, - 37, - 36, - 34.7, - 33.4, - 32.2, - 31.6, - 31.8, - 31.8, - 31.8, - 31.6, - 30.8, - 29.6, - 28.5, - 27.9, - 27.8, - 27.7, - 26.8, - 25.9, - 25.3, - 24.7, - 24.3, - 23.7, - 23, - 22.1, - 21.5, - 20.8, - 20, - 19.3, - 18.5, - 18, - 17.6, - 17.3, - 17.1, - 16.9, - 16.7, - 16.5, - 16.3, - 16.1, - 15.9, - 15.8, - 15.6, - 15.2, - 15, - 14.7, - 14.4, - 14.3, - 14.1, - 13.9, - 13.6, - 13.3, - 13, - 12.5, - 12.2, - 12.2, - 12.1, - 12.1, - 12.2, - 12.1, - 12.1, - 12, - 11.9, - 11.6, - 11.4, - 11.4, - 11.3, - 11.2, - 11.2, - 11.1, - 10.9, - 10.7, - 10.6, - 10.7, - 10.6, - 10.6, - 10.6, - 10.4, - 10.3, - 10.1, - 10, - 10, - 9.7, - 9.2, - 8.6, - 7.9, - 7.2, - 6.3, - 5.7, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.3, - 5.2, - 5.3, - 5.4, - 5.3, - 5.2, - 5.3, - 5.4, - 5.4, - 5.5, - 5.5, - 5.5, - 5.5, - 5.4, - 5.4, - 5.3, - 5.4, - 5.5, - 5.6, - 5.7, - 5.8, - 5.8, - 5.9, - 5.9, - 5.9, - 5.8, - 5.8, - 5.7, - 5.7, - 5.7, - 5.7, - 5.6, - 5.6, - 5.6, - 5.6, - 5.7, - 5.6, - 5.6, - 5.6, - 5.6, - 5.7, - 5.6, - 5, - 4.5, - 4.2, - 4.2, - 4.2, - 4.1, - 3.9, - 3.7, - 3.6, - 3.7, - 3.7, - 3.8, - 3.9, - 4, - 4.2, - 4.5, - 4.5, - 4.5, - 4.2, - 3.6, - 3.5, - 3.1, - 2.9, - 2.9, - 2.8, - 2.8, - 2.6, - 2.2, - 2.1, - 2.1, - 2.1, - 64, - 64, - 64.1, - 64.5, - 64.5, - 64.4, - 64.2, - 64.1, - 64, - 64.1, - 64, - 63.9, - 63.7, - 63.6, - 63.5, - 63.4, - 63.3, - 63.3, - 63, - 62.6, - 62.1, - 61.9, - 61.8, - 61.3, - 60.7, - 59.9, - 59.5, - 58.9, - 58.4, - 58.1, - 58, - 58, - 57.9, - 57.5, - 57.2, - 56.8, - 56.3, - 55.7, - 55.2, - 54.7, - 54.3, - 53.8, - 53.2, - 52.4, - 51.9, - 51.6, - 51.2, - 50.8, - 50.5, - 50.1, - 49.7, - 49.3, - 49, - 48.6, - 48.1, - 47.7, - 47.3, - 46.8, - 46.4, - 46, - 45.5, - 45.1, - 45.2, - 45.2, - 45.2, - 45.1, - 45.2, - 45.3, - 45.1, - 44.9, - 44.8, - 44.7, - 44.7, - 44.7, - 44.5, - 44.5, - 44.5, - 44.4, - 44, - 43.4, - 43.1, - 42.5, - 41.6, - 40.4, - 39.8, - 39.6, - 39, - 38, - 36.9, - 35.9, - 34.5, - 33.6, - 32.5, - 31.8, - 31.9, - 31.9, - 31.9, - 31.7, - 30.7, - 29.5, - 28.6, - 28.2, - 28.1, - 27.8, - 26.9, - 25.8, - 25.3, - 24.9, - 24.4, - 23.8, - 23, - 22.4, - 21.7, - 21, - 20.2, - 19.4, - 18.6, - 17.9, - 17.6, - 17.2, - 17, - 16.9, - 16.7, - 16.5, - 16.3, - 16.2, - 16, - 15.8, - 15.6, - 15.3, - 15, - 14.7, - 14.4, - 14.3, - 14.1, - 13.9, - 13.6, - 13.2, - 12.8, - 12.4, - 12.1, - 12.1, - 12, - 12, - 12, - 12.1, - 12, - 11.9, - 11.8, - 11.6, - 11.4, - 11.3, - 11.2, - 11.2, - 11.1, - 11, - 10.8, - 10.6, - 10.6, - 10.6, - 10.5, - 10.5, - 10.5, - 10.4, - 10.3, - 10.2, - 10, - 9.8, - 9.5, - 8.9, - 8.4, - 7.8, - 7.2, - 6.4, - 5.6, - 5, - 5, - 5, - 5.1, - 5.1, - 5.1, - 5.1, - 5.1, - 5.1, - 5.1, - 5.2, - 5.2, - 5.1, - 5.1, - 5.2, - 5.4, - 5.4, - 5.3, - 5.3, - 5.3, - 5.3, - 5.3, - 5.3, - 5.3, - 5.2, - 5.2, - 5.2, - 5.3, - 5.4, - 5.5, - 5.6, - 5.5, - 5.5, - 5.5, - 5.5, - 5.4, - 5.4, - 5.4, - 5.4, - 5.4, - 5.4, - 5.5, - 5.5, - 5.4, - 5.5, - 5.5, - 5.6, - 5.6, - 5.7, - 5.6, - 5.4, - 5.1, - 4.9, - 4.7, - 4.4, - 4.5, - 4.4, - 4.1, - 4.1, - 4.1, - 4.2, - 4.2, - 4.2, - 4.2, - 4.5, - 4.7, - 4.7, - 4.6, - 4.4, - 4.1, - 3.9, - 3.9, - 3.3, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.6, - 2.2, - 2.1, - 2.1, - 64.1, - 64.2, - 64.4, - 64.5, - 64.6, - 64.4, - 64.2, - 64.1, - 64, - 64.1, - 64, - 63.8, - 63.8, - 63.8, - 63.6, - 63.4, - 63.3, - 63.2, - 63, - 62.8, - 62.4, - 62.1, - 61.7, - 61.2, - 60.5, - 59.8, - 59.4, - 58.8, - 58.5, - 58.3, - 58.1, - 58.1, - 58, - 57.6, - 57.2, - 56.8, - 56.3, - 55.7, - 55.2, - 54.7, - 54.3, - 53.8, - 53.1, - 52.3, - 51.8, - 51.5, - 51.1, - 50.7, - 50.4, - 49.9, - 49.4, - 49.2, - 48.9, - 48.5, - 48.1, - 47.8, - 47.4, - 47, - 46.5, - 46, - 45.6, - 45.4, - 45.3, - 45.1, - 45.1, - 45.2, - 45.2, - 45.2, - 45.2, - 45, - 44.9, - 44.9, - 44.8, - 44.9, - 44.7, - 44.6, - 44.4, - 44.3, - 44.1, - 43.5, - 43, - 42.4, - 41.6, - 40.9, - 40.1, - 39.7, - 38.9, - 38, - 36.9, - 35.7, - 34.6, - 33.5, - 32.7, - 32, - 32, - 32.1, - 32, - 31.9, - 30.8, - 29.6, - 28.7, - 28.4, - 28.3, - 27.8, - 26.8, - 26, - 25.5, - 25, - 24.4, - 23.7, - 22.9, - 22.4, - 21.8, - 21, - 20.3, - 19.5, - 18.7, - 18.1, - 17.6, - 17.2, - 17, - 16.8, - 16.7, - 16.5, - 16.4, - 16.2, - 16, - 15.9, - 15.6, - 15.3, - 15, - 14.7, - 14.5, - 14.4, - 14.1, - 13.9, - 13.5, - 13.1, - 12.7, - 12.3, - 12, - 11.9, - 11.9, - 11.9, - 11.9, - 12, - 11.9, - 11.8, - 11.6, - 11.5, - 11.3, - 11.2, - 11.2, - 11, - 11, - 10.9, - 10.7, - 10.6, - 10.6, - 10.5, - 10.5, - 10.5, - 10.3, - 10.2, - 10.1, - 10.1, - 9.8, - 9.6, - 9.2, - 8.6, - 8.1, - 7.5, - 6.7, - 5.9, - 5.2, - 4.8, - 4.9, - 4.9, - 5, - 5, - 5, - 5.1, - 5.1, - 5, - 5.1, - 5.1, - 5.1, - 5.1, - 5.2, - 5.2, - 5.3, - 5.3, - 5.3, - 5.3, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.1, - 5.1, - 5.1, - 5.1, - 5.2, - 5.2, - 5.3, - 5.2, - 5.3, - 5.2, - 5.2, - 5.1, - 5.1, - 5.1, - 5.1, - 5.2, - 5.2, - 5.3, - 5.3, - 5.3, - 5.3, - 5.2, - 5.3, - 5.4, - 5.4, - 5.6, - 5.6, - 5.4, - 5.3, - 5.2, - 4.8, - 4.8, - 4.6, - 4.5, - 4.7, - 4.9, - 5, - 5.1, - 5, - 4.9, - 5, - 4.9, - 4.7, - 4.4, - 4.2, - 4.2, - 4.2, - 3.8, - 3.1, - 3, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.6, - 2.3, - 2.1, - 64.5, - 64.4, - 64.4, - 64.5, - 64.4, - 64.4, - 64.3, - 64.2, - 64.2, - 64.2, - 64, - 63.8, - 63.7, - 63.6, - 63.4, - 63.3, - 63.2, - 63.1, - 62.9, - 62.9, - 62.7, - 62.3, - 61.8, - 61, - 60.3, - 59.8, - 59.3, - 58.8, - 58.5, - 58.4, - 58.3, - 58.1, - 57.8, - 57.5, - 57.3, - 57, - 56.3, - 55.8, - 55.2, - 54.7, - 54.3, - 53.6, - 52.9, - 52.2, - 51.6, - 51.2, - 50.8, - 50.4, - 50.1, - 49.7, - 49.5, - 49.3, - 49, - 48.6, - 48.3, - 48.1, - 47.6, - 47.2, - 46.7, - 46, - 45.7, - 45.6, - 45.3, - 45.1, - 45.1, - 45.2, - 45.2, - 45.3, - 45.3, - 45.1, - 45, - 45, - 45, - 45, - 45, - 44.8, - 44.5, - 44.1, - 43.9, - 43.4, - 42.7, - 42.1, - 41.6, - 40.9, - 40.2, - 39.7, - 38.9, - 38, - 37.1, - 35.9, - 34.8, - 33.8, - 33, - 32.2, - 32.2, - 32.2, - 32.2, - 32, - 31, - 29.9, - 28.8, - 28.6, - 28.5, - 27.9, - 26.7, - 26.1, - 25.6, - 25.1, - 24.4, - 23.7, - 22.8, - 22.4, - 21.8, - 21.1, - 20.3, - 19.6, - 18.9, - 18.1, - 17.6, - 17.2, - 16.9, - 16.8, - 16.6, - 16.5, - 16.4, - 16.2, - 16, - 15.9, - 15.6, - 15.3, - 14.9, - 14.6, - 14.5, - 14.4, - 14.2, - 13.9, - 13.5, - 13.1, - 12.7, - 12.2, - 11.9, - 11.7, - 11.8, - 11.8, - 11.8, - 11.8, - 11.8, - 11.7, - 11.5, - 11.3, - 11.2, - 11.1, - 11.1, - 10.9, - 10.8, - 10.8, - 10.6, - 10.5, - 10.5, - 10.6, - 10.5, - 10.4, - 10.2, - 10, - 9.8, - 9.6, - 9.3, - 9, - 8.7, - 8.1, - 7.5, - 6.9, - 6, - 5.2, - 4.7, - 4.8, - 4.8, - 4.9, - 5, - 5, - 5, - 5, - 5.1, - 5.1, - 5.2, - 5.2, - 5.1, - 5.1, - 5.2, - 5.3, - 5.3, - 5.3, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.1, - 5, - 5.1, - 5.1, - 5.1, - 5.1, - 5.1, - 5.1, - 5.1, - 5.1, - 5.1, - 5, - 5, - 5.1, - 5.2, - 5.2, - 5.2, - 5.2, - 5.3, - 5.3, - 5.3, - 5.3, - 5.3, - 5.3, - 5.3, - 5.4, - 5.3, - 5.3, - 5.3, - 5.2, - 5.1, - 5.2, - 5.1, - 5.2, - 5.3, - 5.4, - 5.3, - 5.3, - 5.1, - 4.9, - 4.7, - 4.5, - 4.5, - 4.4, - 4.5, - 4.3, - 3.8, - 3.3, - 3.1, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.7, - 2.4, - 64.7, - 64.4, - 64.4, - 64.4, - 64.3, - 64.3, - 64.4, - 64.2, - 64.2, - 64.1, - 64.1, - 63.9, - 63.8, - 63.6, - 63.3, - 63.3, - 63.3, - 63.1, - 62.9, - 62.8, - 62.5, - 62.2, - 61.8, - 61, - 60.4, - 59.8, - 59.4, - 58.9, - 58.7, - 58.7, - 58.4, - 58, - 57.7, - 57.6, - 57.6, - 57.3, - 56.4, - 55.8, - 55.3, - 54.8, - 54.3, - 53.4, - 52.6, - 52, - 51.3, - 50.9, - 50.4, - 50.1, - 50, - 49.8, - 49.6, - 49.3, - 49.1, - 48.8, - 48.6, - 48.2, - 47.6, - 47.2, - 46.8, - 46.3, - 46, - 45.9, - 45.8, - 45.5, - 45.3, - 45.2, - 45.2, - 45.4, - 45.5, - 45.5, - 45.4, - 45.5, - 45.8, - 45.6, - 45.3, - 44.9, - 44.6, - 44.2, - 43.8, - 43.3, - 42.7, - 41.9, - 41.3, - 40.7, - 40.2, - 39.7, - 39, - 38.1, - 37.3, - 36.4, - 35.1, - 34.2, - 33.2, - 32.4, - 32.3, - 32.3, - 32.3, - 32.1, - 31.1, - 29.9, - 29, - 28.8, - 28.6, - 27.8, - 26.6, - 25.9, - 25.5, - 25, - 24.3, - 23.7, - 22.9, - 22.4, - 21.9, - 21.1, - 20.3, - 19.6, - 18.9, - 18.1, - 17.6, - 17.1, - 17, - 16.8, - 16.6, - 16.5, - 16.3, - 16.2, - 16, - 15.9, - 15.6, - 15.3, - 15, - 14.6, - 14.4, - 14.3, - 14.1, - 13.8, - 13.5, - 13.1, - 12.7, - 12.2, - 11.9, - 11.7, - 11.5, - 11.7, - 11.7, - 11.7, - 11.6, - 11.6, - 11.4, - 11.2, - 11.1, - 11, - 10.9, - 10.8, - 10.8, - 10.7, - 10.5, - 10.4, - 10.5, - 10.5, - 10.4, - 10.2, - 9.8, - 9.5, - 9.1, - 8.9, - 8.6, - 8.3, - 7.8, - 7.2, - 6.6, - 6, - 5.2, - 4.7, - 4.6, - 4.6, - 4.6, - 4.6, - 4.7, - 4.8, - 4.9, - 5, - 5.1, - 5.1, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.3, - 5.4, - 5.4, - 5.4, - 5.5, - 5.4, - 5.3, - 5.3, - 5.3, - 5.3, - 5.2, - 5.2, - 5.2, - 5.1, - 5.1, - 5.1, - 5.2, - 5.1, - 5.1, - 5.1, - 5.1, - 5, - 5.1, - 5.2, - 5.2, - 5.2, - 5.3, - 5.3, - 5.2, - 5.2, - 5.3, - 5.4, - 5.4, - 5.4, - 5.4, - 5.5, - 5.5, - 5.4, - 5.4, - 5.3, - 5.3, - 5.3, - 5.3, - 5.2, - 5.2, - 5.1, - 5, - 4.9, - 4.8, - 4.6, - 4.5, - 4.4, - 4.6, - 4.5, - 4.1, - 3.7, - 3.4, - 3.2, - 3.1, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 64.8, - 64.6, - 64.5, - 64.4, - 64.2, - 64.1, - 64, - 64, - 64, - 64, - 64.1, - 64.1, - 63.9, - 63.5, - 63.5, - 63.3, - 63.3, - 63.1, - 62.7, - 62.6, - 62.3, - 62, - 61.8, - 61.2, - 60.7, - 60, - 59.5, - 59, - 58.7, - 58.5, - 58.2, - 58, - 57.9, - 57.8, - 57.7, - 57.3, - 56.5, - 55.8, - 55.3, - 54.8, - 54.2, - 53.3, - 52.4, - 51.7, - 51.1, - 50.5, - 50.4, - 50.2, - 50.1, - 50, - 49.7, - 49.4, - 49.2, - 49, - 48.6, - 48.1, - 47.7, - 47.2, - 46.8, - 46.5, - 46.3, - 46.2, - 46.1, - 46, - 45.6, - 45.4, - 45.4, - 45.7, - 46.1, - 46.1, - 45.9, - 46, - 46.1, - 46, - 45.7, - 45.4, - 45, - 44.5, - 43.9, - 43.4, - 42.7, - 41.7, - 41, - 40.4, - 40.1, - 39.7, - 39.1, - 38.3, - 37.6, - 36.7, - 35.2, - 34.2, - 33.3, - 32.4, - 32.4, - 32.4, - 32.4, - 32.2, - 31.3, - 30, - 29.2, - 29, - 28.7, - 27.6, - 26.7, - 25.9, - 25.4, - 25, - 24.3, - 23.8, - 23.1, - 22.4, - 22, - 21, - 20.3, - 19.5, - 18.9, - 18.2, - 17.6, - 17.2, - 17, - 16.8, - 16.7, - 16.6, - 16.3, - 16.2, - 16, - 15.9, - 15.6, - 15.4, - 15, - 14.7, - 14.4, - 14.2, - 14, - 13.8, - 13.5, - 13.1, - 12.7, - 12.3, - 12, - 11.7, - 11.5, - 11.5, - 11.5, - 11.4, - 11.4, - 11.4, - 11.2, - 11.1, - 11, - 10.9, - 10.8, - 10.7, - 10.7, - 10.6, - 10.5, - 10.3, - 10.5, - 10.4, - 10.1, - 9.8, - 9.3, - 8.8, - 8.3, - 7.9, - 7.7, - 7.3, - 6.8, - 6.3, - 5.8, - 5, - 4.6, - 4.6, - 4.5, - 4.5, - 4.5, - 4.6, - 4.6, - 4.7, - 4.8, - 4.8, - 4.9, - 5, - 5, - 5, - 5.1, - 5.1, - 5.1, - 5.2, - 5.2, - 5.2, - 5.2, - 5.2, - 5.1, - 5.1, - 5.1, - 5.1, - 5, - 5, - 5.1, - 5.1, - 5.1, - 5.1, - 5.1, - 5, - 5, - 5.1, - 5.1, - 5, - 5, - 5, - 5.1, - 5.2, - 5.1, - 5.1, - 5.2, - 5.2, - 5.2, - 5.3, - 5.3, - 5.3, - 5.3, - 5.3, - 5.2, - 5.2, - 5.1, - 5.1, - 5, - 5, - 5, - 5, - 4.9, - 4.8, - 4.8, - 4.8, - 4.7, - 4.7, - 4.6, - 4.5, - 4.6, - 4.3, - 3.9, - 3.6, - 3.4, - 3.3, - 3.2, - 3.1, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 64.9, - 64.6, - 64.4, - 64.4, - 64.2, - 63.8, - 63.7, - 63.6, - 63.8, - 63.9, - 64, - 64.1, - 63.9, - 63.5, - 63.4, - 63.3, - 63.2, - 62.9, - 62.7, - 62.7, - 62.5, - 62.1, - 61.9, - 61.5, - 60.9, - 60.4, - 59.7, - 59.1, - 58.8, - 58.5, - 58.3, - 58.3, - 58.2, - 57.9, - 57.7, - 57.3, - 56.5, - 55.8, - 55.3, - 54.9, - 54.2, - 53, - 51.8, - 51.3, - 50.7, - 50.5, - 50.5, - 50.3, - 50.3, - 50, - 49.7, - 49.5, - 49.2, - 49, - 48.6, - 48.2, - 47.7, - 47.3, - 47, - 46.7, - 46.8, - 46.7, - 46.5, - 46.4, - 46.1, - 45.8, - 45.8, - 46, - 46.3, - 46.2, - 46.3, - 46.5, - 46.5, - 46.3, - 46.1, - 45.7, - 45.2, - 44.8, - 44.3, - 43.5, - 42.7, - 41.9, - 41.3, - 40.7, - 40.2, - 39.9, - 39.4, - 38.5, - 37.7, - 36.8, - 35.5, - 34, - 33.2, - 32.5, - 32.6, - 32.6, - 32.5, - 32.2, - 31.3, - 30, - 29.4, - 29.2, - 28.7, - 27.7, - 26.8, - 26.1, - 25.4, - 24.8, - 24.3, - 23.9, - 23.2, - 22.6, - 22, - 21.1, - 20.4, - 19.5, - 18.9, - 18.3, - 17.6, - 17.2, - 17, - 16.8, - 16.6, - 16.5, - 16.3, - 16.1, - 15.9, - 15.8, - 15.7, - 15.4, - 15, - 14.7, - 14.3, - 14.1, - 13.9, - 13.8, - 13.5, - 13.1, - 12.7, - 12.3, - 12, - 11.7, - 11.4, - 11.3, - 11.3, - 11.3, - 11.2, - 11.2, - 11.1, - 10.9, - 10.8, - 10.8, - 10.7, - 10.7, - 10.5, - 10.4, - 10.4, - 10.3, - 10.4, - 10, - 9.5, - 9, - 8.5, - 7.9, - 7.3, - 7, - 6.8, - 6.4, - 6, - 5.6, - 4.9, - 4.3, - 4.1, - 4.1, - 4, - 4.1, - 4.2, - 4.3, - 4.4, - 4.4, - 4.5, - 4.6, - 4.7, - 4.7, - 4.8, - 4.9, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 4.9, - 4.9, - 4.9, - 4.9, - 4.9, - 4.9, - 4.9, - 5, - 5, - 4.9, - 5, - 5, - 4.9, - 4.9, - 4.9, - 4.8, - 4.8, - 4.9, - 4.9, - 4.9, - 4.9, - 4.9, - 5, - 5.2, - 5.1, - 5.1, - 5.1, - 5, - 5, - 5, - 4.9, - 4.9, - 4.8, - 4.9, - 4.9, - 4.9, - 4.9, - 4.8, - 4.8, - 4.8, - 4.8, - 4.8, - 4.7, - 4.4, - 4.3, - 3.9, - 3.7, - 3.6, - 3.4, - 3.3, - 3.2, - 3.1, - 3.1, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 64.9, - 64.5, - 64.4, - 64.3, - 64.1, - 63.7, - 63.6, - 63.6, - 63.8, - 63.9, - 64.2, - 64, - 63.8, - 63.7, - 63.5, - 63.2, - 63.2, - 63, - 62.8, - 62.8, - 62.5, - 62.3, - 62.1, - 61.8, - 61, - 60.6, - 60, - 59.3, - 58.9, - 58.6, - 58.6, - 58.6, - 58.5, - 58.1, - 57.7, - 57.2, - 56.6, - 55.9, - 55.4, - 54.7, - 53.7, - 52.2, - 51.4, - 51, - 50.7, - 50.5, - 50.5, - 50.5, - 50.3, - 50.2, - 49.8, - 49.5, - 49.3, - 49, - 48.6, - 48.1, - 47.7, - 47.5, - 47.3, - 47, - 47.3, - 47.2, - 47.2, - 47, - 46.6, - 46.2, - 46.1, - 46.2, - 46.4, - 46.4, - 46.6, - 46.7, - 46.8, - 46.6, - 46.3, - 45.8, - 45.3, - 44.9, - 44.4, - 43.5, - 42.8, - 42.1, - 41.7, - 41, - 40.3, - 40, - 39.5, - 38.5, - 37.6, - 36.7, - 35.5, - 34.1, - 33.2, - 32.5, - 32.7, - 32.7, - 32.7, - 32.3, - 31.2, - 29.8, - 29.6, - 29.4, - 28.8, - 27.9, - 27, - 26.2, - 25.5, - 25, - 24.6, - 24, - 23.2, - 22.5, - 21.9, - 21.1, - 20.4, - 19.4, - 18.9, - 18.3, - 17.6, - 17.2, - 16.9, - 16.8, - 16.6, - 16.5, - 16.3, - 16.1, - 15.9, - 15.8, - 15.7, - 15.4, - 15, - 14.6, - 14.3, - 14.1, - 13.9, - 13.7, - 13.5, - 13.2, - 12.8, - 12.5, - 12.1, - 11.7, - 11.3, - 11.2, - 11.2, - 11.1, - 11.1, - 11, - 10.9, - 10.8, - 10.7, - 10.7, - 10.6, - 10.5, - 10.5, - 10.4, - 10.2, - 10.1, - 10.1, - 9.6, - 8.8, - 8.1, - 7.6, - 7, - 6.6, - 6.3, - 5.9, - 5.7, - 5.2, - 4.6, - 4, - 3.7, - 3.5, - 3.5, - 3.5, - 3.6, - 3.7, - 3.7, - 3.8, - 3.9, - 3.9, - 4, - 4.1, - 4.1, - 4.1, - 4.2, - 4.1, - 4.1, - 4.1, - 4.1, - 4, - 4, - 4.1, - 4.1, - 4.2, - 4.2, - 4.1, - 4.1, - 4.1, - 4.2, - 4.2, - 4.2, - 4.2, - 4.3, - 4.3, - 4.3, - 4.3, - 4.4, - 4.4, - 4.4, - 4.4, - 4.5, - 4.6, - 4.5, - 4.6, - 4.6, - 4.7, - 4.7, - 4.7, - 4.7, - 4.7, - 4.7, - 4.7, - 4.7, - 4.6, - 4.6, - 4.7, - 4.6, - 4.6, - 4.6, - 4.6, - 4.7, - 4.7, - 4.6, - 4.5, - 4.5, - 4.3, - 4.2, - 3.9, - 3.8, - 3.6, - 3.5, - 3.4, - 3.3, - 3.2, - 3.2, - 3.1, - 3.1, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 64.6, - 64.4, - 64.3, - 64.2, - 63.8, - 63.6, - 63.5, - 63.7, - 63.8, - 63.9, - 64.4, - 64.2, - 63.9, - 63.7, - 63.7, - 63.4, - 63.3, - 63.2, - 62.9, - 62.7, - 62.3, - 62.1, - 62, - 61.9, - 61.3, - 60.5, - 59.9, - 59.4, - 58.9, - 58.7, - 58.7, - 58.6, - 58.6, - 58.3, - 57.7, - 57.2, - 56.6, - 55.9, - 55.4, - 54.4, - 53, - 51.9, - 51.3, - 51, - 50.7, - 50.6, - 50.5, - 50.5, - 50.4, - 50.2, - 49.8, - 49.6, - 49.3, - 48.9, - 48.5, - 48, - 47.7, - 47.8, - 47.5, - 47.2, - 47.5, - 47.5, - 47.5, - 47.3, - 46.9, - 46.6, - 46.4, - 46.5, - 46.6, - 46.6, - 46.6, - 46.8, - 46.8, - 46.7, - 46.4, - 45.9, - 45.5, - 45.1, - 44.6, - 43.8, - 42.9, - 42.2, - 41.8, - 41.3, - 40.7, - 40.1, - 39.4, - 38.6, - 37.9, - 37, - 35.8, - 34.2, - 33.3, - 32.7, - 32.8, - 32.8, - 32.8, - 32.5, - 31.3, - 30.1, - 29.8, - 29.6, - 28.8, - 27.8, - 27, - 26.4, - 25.8, - 25.3, - 24.8, - 24.3, - 23.4, - 22.5, - 21.7, - 21, - 20.3, - 19.4, - 18.9, - 18.3, - 17.6, - 17.2, - 16.9, - 16.8, - 16.6, - 16.5, - 16.3, - 16.1, - 15.9, - 15.8, - 15.6, - 15.4, - 15, - 14.5, - 14.2, - 14, - 13.9, - 13.7, - 13.4, - 13.2, - 12.8, - 12.6, - 12.3, - 11.8, - 11.5, - 11.3, - 11.2, - 11.1, - 11, - 10.8, - 10.7, - 10.6, - 10.6, - 10.5, - 10.3, - 10, - 10.2, - 10.3, - 9.5, - 9.3, - 9, - 8.4, - 7.9, - 7.2, - 6.6, - 6.3, - 6, - 5.3, - 4.9, - 4.6, - 4.1, - 3.7, - 3.5, - 3.2, - 3.2, - 3.3, - 3.3, - 3.3, - 3.4, - 3.4, - 3.5, - 3.5, - 3.6, - 3.6, - 3.7, - 3.7, - 3.7, - 3.7, - 3.7, - 3.7, - 3.6, - 3.6, - 3.6, - 3.6, - 3.6, - 3.6, - 3.6, - 3.6, - 3.6, - 3.6, - 3.6, - 3.5, - 3.6, - 3.6, - 3.6, - 3.6, - 3.7, - 3.7, - 3.7, - 3.8, - 3.8, - 3.9, - 3.9, - 3.8, - 3.9, - 3.9, - 4, - 3.9, - 3.9, - 3.9, - 3.8, - 3.8, - 3.8, - 3.9, - 3.9, - 3.9, - 3.9, - 3.9, - 3.9, - 3.8, - 3.8, - 3.8, - 3.9, - 3.9, - 3.8, - 3.9, - 3.8, - 3.8, - 3.7, - 3.7, - 3.6, - 3.5, - 3.4, - 3.3, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 64.4, - 64.2, - 64.2, - 64, - 63.7, - 63.6, - 63.5, - 63.7, - 63.8, - 63.8, - 64.1, - 64.1, - 63.8, - 63.5, - 63.4, - 63, - 62.9, - 62.8, - 63, - 62.7, - 62.2, - 61.7, - 61.6, - 61.7, - 61.1, - 60.4, - 59.8, - 59.4, - 59.1, - 58.7, - 58.6, - 58.6, - 58.4, - 58.2, - 57.6, - 57.1, - 56.5, - 55.8, - 55.1, - 53.7, - 52.7, - 52.1, - 51.7, - 51.3, - 51, - 50.8, - 50.6, - 50.6, - 50.5, - 50.3, - 50.1, - 49.8, - 49.4, - 48.9, - 48.5, - 48.1, - 47.8, - 47.9, - 47.5, - 47.4, - 47.7, - 47.8, - 47.8, - 47.5, - 47.1, - 46.8, - 46.5, - 46.7, - 46.7, - 46.5, - 46.5, - 46.8, - 46.8, - 46.8, - 46.5, - 46.2, - 45.7, - 45.1, - 44.6, - 44, - 43.1, - 42.3, - 41.9, - 41.4, - 40.9, - 40.1, - 39.3, - 38.5, - 38.1, - 37.5, - 36.4, - 34.4, - 33.5, - 32.8, - 32.9, - 32.9, - 32.9, - 32.6, - 31.5, - 30.3, - 30, - 29.7, - 28.9, - 27.8, - 27.1, - 26.4, - 26, - 25.5, - 25.1, - 24.3, - 23.2, - 22.3, - 21.5, - 20.9, - 20.2, - 19.4, - 18.7, - 18.1, - 17.5, - 17.2, - 17, - 16.8, - 16.6, - 16.5, - 16.3, - 16.1, - 15.9, - 15.8, - 15.6, - 15.4, - 15, - 14.5, - 14.1, - 13.9, - 13.8, - 13.6, - 13.4, - 13.2, - 12.9, - 12.7, - 12.3, - 12, - 11.8, - 11.5, - 11.4, - 11.2, - 11, - 10.7, - 10.3, - 9.9, - 9.8, - 9.7, - 9.5, - 9.4, - 9.7, - 9.8, - 8.8, - 8.7, - 8.2, - 7.3, - 7, - 6.2, - 5.7, - 5.2, - 5, - 4.3, - 3.8, - 3.6, - 3.5, - 3.4, - 3.2, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.3, - 3.3, - 3.3, - 3.3, - 3.4, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.2, - 3.2, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.3, - 3.4, - 3.4, - 3.4, - 3.4, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.3, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.4, - 3.3, - 3.3, - 3.3, - 3.2, - 3.2, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 64.3, - 64.2, - 64.1, - 63.9, - 63.8, - 63.6, - 63.5, - 63.6, - 63.7, - 63.7, - 63.9, - 64, - 63.8, - 63.4, - 63, - 62.7, - 62.2, - 62.2, - 62.5, - 62.3, - 61.6, - 61.3, - 61.1, - 60.9, - 60.7, - 60.3, - 59.8, - 59.4, - 58.9, - 58.6, - 58.4, - 58.5, - 58.1, - 57.8, - 57.3, - 56.8, - 56.5, - 56, - 55.2, - 53.7, - 52.9, - 52.4, - 52, - 51.6, - 51.3, - 51.1, - 50.9, - 50.9, - 50.7, - 50.4, - 50.2, - 49.9, - 49.4, - 48.9, - 48.5, - 48.3, - 48, - 47.8, - 47.7, - 47.7, - 47.9, - 48.2, - 48, - 47.7, - 47.5, - 47.1, - 46.7, - 46.7, - 46.8, - 46.6, - 46.5, - 46.6, - 46.7, - 46.6, - 46.5, - 46.2, - 45.7, - 45, - 44.7, - 44, - 43.1, - 42.5, - 42, - 41.3, - 40.9, - 40.1, - 39.2, - 38.6, - 38, - 37.6, - 36.4, - 34.4, - 33.6, - 32.9, - 33, - 33.1, - 33, - 32.8, - 31.6, - 30.4, - 30.2, - 29.8, - 29, - 28.1, - 27.3, - 26.7, - 26.2, - 25.8, - 25.1, - 24.1, - 23, - 22.1, - 21.3, - 20.7, - 20, - 19.4, - 18.7, - 18.1, - 17.4, - 17.1, - 17, - 16.8, - 16.6, - 16.4, - 16.2, - 16, - 15.9, - 15.8, - 15.6, - 15.4, - 15, - 14.5, - 14.1, - 13.8, - 13.7, - 13.5, - 13.4, - 13.2, - 12.9, - 12.7, - 12.5, - 12.2, - 12, - 11.7, - 11.5, - 11.1, - 10.7, - 10.1, - 9.3, - 8.9, - 8.7, - 8.5, - 8.3, - 8.4, - 8.5, - 8.6, - 7.5, - 7, - 6.7, - 6.5, - 6.4, - 5.8, - 5.3, - 4.6, - 4.3, - 3.5, - 3.1, - 3.1, - 3.1, - 3.1, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 64.2, - 64.1, - 64.1, - 64, - 63.9, - 63.6, - 63.6, - 63.6, - 63.7, - 63.7, - 63.9, - 63.9, - 63.6, - 63, - 62.6, - 62.1, - 61.8, - 61.7, - 61.9, - 61.8, - 61.4, - 61, - 60.9, - 60.8, - 60.5, - 60.2, - 59.8, - 59.2, - 58.8, - 58.4, - 58.2, - 58.3, - 58, - 57.5, - 57, - 56.9, - 56.6, - 56.2, - 56, - 54.5, - 53.4, - 52.8, - 52.5, - 52, - 51.5, - 51.2, - 50.9, - 50.8, - 50.6, - 50.4, - 50.2, - 49.9, - 49.3, - 48.8, - 48.5, - 48.3, - 48.1, - 48, - 48, - 47.9, - 48.1, - 48.2, - 48, - 47.8, - 47.6, - 47.2, - 46.7, - 46.8, - 46.9, - 46.8, - 46.5, - 46.5, - 46.7, - 46.5, - 46.4, - 46, - 45.4, - 44.9, - 44.4, - 43.9, - 43.1, - 42.5, - 42.1, - 41.3, - 40.8, - 40, - 39.2, - 38.7, - 38.2, - 37.7, - 36.1, - 34.4, - 33.5, - 33.1, - 33.2, - 33.2, - 33.1, - 32.9, - 31.8, - 30.6, - 30.4, - 29.8, - 28.9, - 28.2, - 27.6, - 27, - 26.4, - 25.9, - 25, - 23.9, - 23, - 22.1, - 21.3, - 20.6, - 19.8, - 19.3, - 18.7, - 17.9, - 17.4, - 17.2, - 17, - 16.8, - 16.6, - 16.4, - 16.2, - 16, - 15.8, - 15.7, - 15.6, - 15.3, - 14.9, - 14.5, - 14, - 13.8, - 13.6, - 13.5, - 13.3, - 13.2, - 12.9, - 12.7, - 12.5, - 12.3, - 12, - 11.6, - 11.1, - 10.5, - 9.9, - 9, - 8.3, - 7.8, - 7.6, - 7.3, - 7.1, - 7.2, - 7.1, - 6.5, - 5.8, - 5.8, - 5.6, - 5.5, - 5.3, - 4.6, - 4.1, - 3.8, - 3.8, - 3.3, - 3, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 64.1, - 64.1, - 64.1, - 64, - 63.8, - 63.6, - 63.5, - 63.3, - 63.4, - 63.6, - 63.8, - 63.8, - 63.2, - 62.6, - 62, - 61.7, - 61.6, - 61.5, - 61.4, - 61.4, - 61.2, - 60.8, - 60.7, - 60.6, - 60.4, - 60.3, - 59.7, - 59.1, - 58.7, - 58.1, - 57.8, - 57.9, - 57.7, - 57.3, - 57, - 56.9, - 56.7, - 56.5, - 56.2, - 55.4, - 53.7, - 53.1, - 52.6, - 52, - 51.6, - 51.2, - 50.9, - 50.6, - 50.3, - 50.1, - 49.9, - 49.8, - 49.2, - 48.8, - 48.6, - 48.5, - 48.3, - 48.2, - 48.1, - 48.2, - 48, - 47.9, - 48.1, - 47.9, - 47.8, - 47.3, - 46.8, - 46.8, - 46.9, - 46.9, - 46.6, - 46.6, - 46.8, - 46.4, - 46.1, - 45.7, - 45.2, - 44.7, - 44.3, - 43.9, - 43.1, - 42.5, - 42.1, - 41.5, - 40.8, - 40, - 39.3, - 38.9, - 38.4, - 37.7, - 36.2, - 34.1, - 33.5, - 33.2, - 33.3, - 33.3, - 33.3, - 33, - 31.8, - 30.7, - 30.6, - 29.8, - 28.8, - 28.2, - 27.6, - 26.9, - 26.4, - 25.7, - 24.8, - 23.8, - 23, - 22, - 21.2, - 20.5, - 19.8, - 19.2, - 18.6, - 17.8, - 17.4, - 17.2, - 17, - 16.8, - 16.5, - 16.4, - 16.2, - 16, - 15.8, - 15.7, - 15.6, - 15.3, - 14.8, - 14.4, - 14, - 13.8, - 13.6, - 13.5, - 13.3, - 13.1, - 12.9, - 12.7, - 12.4, - 12, - 11.5, - 10.9, - 10, - 9.5, - 8.8, - 7.8, - 6.9, - 6.5, - 6.5, - 6.3, - 6.2, - 5.8, - 5.7, - 5.4, - 5.1, - 5, - 4.7, - 4.5, - 4.2, - 3.8, - 3.3, - 3.1, - 3.1, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 63.6, - 63.6, - 63.5, - 63.8, - 63.7, - 63.5, - 63.3, - 62.9, - 62.8, - 62.5, - 63.3, - 63.8, - 63, - 62.2, - 61.7, - 61.7, - 61.5, - 61.4, - 61.3, - 61, - 60.6, - 60.5, - 60.5, - 60.5, - 60.3, - 60.2, - 59.8, - 59.2, - 58.7, - 57.8, - 57.3, - 57.2, - 57.2, - 57.1, - 56.9, - 56.8, - 56.7, - 56.6, - 56.5, - 56.1, - 54.9, - 53.1, - 52.3, - 51.9, - 51.4, - 51.2, - 50.8, - 50.5, - 50.2, - 49.9, - 49.7, - 49.5, - 49.1, - 48.9, - 48.7, - 48.7, - 48.5, - 48.3, - 48.2, - 47.9, - 47.9, - 48, - 48.1, - 48, - 47.7, - 47.3, - 46.8, - 46.8, - 46.8, - 46.9, - 46.7, - 46.6, - 46.8, - 46.5, - 46.1, - 45.8, - 45.3, - 44.6, - 44.2, - 43.8, - 43.2, - 42.7, - 42.1, - 41.6, - 40.9, - 40.2, - 39.6, - 39.3, - 38.7, - 37.8, - 36.5, - 34.4, - 33.6, - 33.3, - 33.4, - 33.4, - 33.4, - 33.1, - 31.9, - 31, - 30.7, - 29.8, - 28.9, - 28.2, - 27.6, - 27, - 26.4, - 25.7, - 24.8, - 24, - 23.2, - 22, - 21.2, - 20.5, - 19.7, - 19, - 18.4, - 17.7, - 17.5, - 17.2, - 17, - 16.7, - 16.5, - 16.3, - 16.1, - 16, - 15.8, - 15.7, - 15.5, - 15.2, - 14.8, - 14.5, - 14.2, - 13.9, - 13.6, - 13.4, - 13.2, - 13, - 12.8, - 12.5, - 11.9, - 11.3, - 10.6, - 9.9, - 9.1, - 8.3, - 7.4, - 6.7, - 6.1, - 5.5, - 5.3, - 5.2, - 5, - 4.6, - 4.5, - 4.4, - 4.3, - 4.2, - 3.7, - 3.4, - 3.2, - 3, - 2.9, - 2.8, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.7, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 63.4, - 63.2, - 63.1, - 63.1, - 63, - 63, - 62.3, - 61.9, - 62, - 62, - 62.1, - 63, - 62.9, - 62.3, - 61.7, - 61.7, - 61.5, - 61.3, - 61.2, - 60.8, - 60.4, - 60.4, - 60.3, - 60.3, - 60.1, - 60, - 59.6, - 59, - 58.3, - 57.4, - 56.8, - 56.7, - 56.8, - 56.8, - 56.8, - 56.8, - 56.7, - 56.7, - 56.6, - 56, - 55.1, - 52.9, - 52.1, - 51.7, - 51.3, - 51, - 50.8, - 50.5, - 50.2, - 50.1, - 49.9, - 49.4, - 49.2, - 49.1, - 48.9, - 48.7, - 48.6, - 48.6, - 48.5, - 48.1, - 47.9, - 48.1, - 48.1, - 47.9, - 47.6, - 47.3, - 46.8, - 46.7, - 46.8, - 46.9, - 46.7, - 46.5, - 46.6, - 46.5, - 46.1, - 45.8, - 45.1, - 44.7, - 44.3, - 43.8, - 43.2, - 42.6, - 42.1, - 41.6, - 41.1, - 40.6, - 40.2, - 39.8, - 39.1, - 38.1, - 37.4, - 34.6, - 33.6, - 33.4, - 33.5, - 33.5, - 33.5, - 33.2, - 32.1, - 31.1, - 30.7, - 29.8, - 28.8, - 28.2, - 27.6, - 26.9, - 26.3, - 25.5, - 24.8, - 24, - 23.2, - 22.1, - 21.3, - 20.5, - 19.7, - 19, - 18.4, - 17.8, - 17.6, - 17.3, - 17, - 16.7, - 16.4, - 16.3, - 16.1, - 15.9, - 15.8, - 15.6, - 15.5, - 15.2, - 14.9, - 14.5, - 14.2, - 13.9, - 13.6, - 13.4, - 13.2, - 13, - 12.4, - 11.8, - 11, - 10.2, - 9.6, - 9.2, - 8.3, - 7.5, - 7, - 6.4, - 5.7, - 5.1, - 4.8, - 4.1, - 3.7, - 3.5, - 3.5, - 3.3, - 3.3, - 3.2, - 3, - 2.9, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 63.1, - 63, - 62.7, - 62.4, - 62, - 61.8, - 61.6, - 61.6, - 61.6, - 61.7, - 61.7, - 61.7, - 62.1, - 62, - 61.6, - 61.4, - 61.1, - 61.1, - 61.1, - 60.9, - 60.3, - 60.3, - 60.2, - 60, - 59.9, - 59.6, - 59.1, - 58.7, - 58, - 57.2, - 56.6, - 56.5, - 56.5, - 56.5, - 56.6, - 56.7, - 56.5, - 56.6, - 56.4, - 55.6, - 54, - 52.7, - 52.1, - 51.6, - 51.2, - 51, - 50.7, - 50.5, - 50.3, - 50.1, - 49.9, - 49.5, - 49.4, - 49.2, - 49, - 48.8, - 48.6, - 48.5, - 48.4, - 48.1, - 47.9, - 48.1, - 48, - 47.9, - 47.5, - 47.3, - 46.9, - 46.7, - 46.6, - 46.6, - 46.7, - 46.5, - 46.4, - 46.4, - 46.1, - 45.7, - 45.2, - 44.9, - 44.6, - 43.9, - 43.4, - 42.8, - 42.2, - 41.7, - 41.2, - 41.1, - 40.6, - 40.2, - 39.5, - 38.4, - 37.6, - 34.9, - 33.5, - 33.4, - 33.6, - 33.6, - 33.6, - 33.2, - 32.2, - 31.3, - 30.6, - 29.6, - 28.8, - 28.2, - 27.6, - 27, - 26.3, - 25.4, - 24.7, - 23.9, - 22.9, - 21.9, - 21.2, - 20.4, - 19.8, - 19.1, - 18.5, - 17.9, - 17.5, - 17.3, - 17, - 16.6, - 16.4, - 16.2, - 16, - 15.9, - 15.7, - 15.6, - 15.5, - 15.3, - 14.9, - 14.6, - 14.2, - 13.7, - 13.5, - 13.3, - 13.1, - 12.6, - 11.7, - 10.8, - 10.2, - 9.5, - 8.8, - 8.3, - 7.7, - 6.9, - 6.6, - 5.9, - 5, - 4.8, - 4.2, - 3.5, - 3.2, - 3.1, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 2.9, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.8, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 62.8, - 62.7, - 62.3, - 61.8, - 61.6, - 61.5, - 61.5, - 61.6, - 61.6, - 61.6, - 61.6, - 61.6, - 61.5, - 61.5, - 61.3, - 61.1, - 60.9, - 61, - 60.9, - 60.8, - 60.3, - 60.2, - 60.3, - 60.1, - 59.8, - 59.3, - 58.9, - 58.5, - 57.9, - 57.1, - 56.6, - 56.4, - 56.3, - 56.2, - 56.4, - 56.4, - 56.3, - 56.4, - 56.2, - 54.8, - 53.3, - 52.3, - 51.7, - 51.3, - 51.1, - 50.9, - 50.8, - 50.6, - 50.3, - 50.2, - 49.8, - 49.7, - 49.4, - 49.2, - 49, - 48.9, - 48.6, - 48.3, - 48.1, - 47.9, - 48, - 48.1, - 48, - 47.8, - 47.6, - 47.3, - 47, - 46.7, - 46.6, - 46.5, - 46.5, - 46.3, - 46.2, - 46.2, - 46, - 45.7, - 45.4, - 45.3, - 44.8, - 44.2, - 43.5, - 43, - 42.4, - 41.9, - 41.5, - 41.3, - 40.8, - 40.1, - 39.6, - 39, - 37.6, - 34.8, - 33.5, - 33.5, - 33.7, - 33.7, - 33.7, - 33.4, - 32.3, - 31.3, - 30.5, - 29.5, - 28.8, - 28.2, - 27.6, - 27.1, - 26.3, - 25.3, - 24.5, - 23.9, - 22.9, - 21.9, - 21.1, - 20.4, - 19.8, - 19.1, - 18.5, - 18, - 17.6, - 17.3, - 17, - 16.6, - 16.3, - 16.2, - 16, - 15.9, - 15.8, - 15.6, - 15.4, - 15.2, - 14.9, - 14.5, - 14.1, - 13.6, - 13.3, - 13.1, - 12.8, - 12.1, - 10.8, - 9.8, - 9.3, - 8.7, - 8.1, - 7.4, - 6.8, - 6.3, - 5.6, - 5, - 4, - 3.6, - 3.3, - 3.1, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 2.9, - 2.8, - 2.8, - 2.7, - 2.7, - 2.6, - 2.6, - 2.7, - 2.8, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 61.6, - 61.9, - 61.4, - 61.2, - 61.4, - 61.4, - 61.4, - 61.4, - 61.5, - 61.5, - 61.5, - 61.5, - 61.4, - 61.4, - 61.2, - 61.1, - 61, - 61, - 60.7, - 60.5, - 60.3, - 60.2, - 60.4, - 60.3, - 60.1, - 59.5, - 59.1, - 58.6, - 58, - 57.2, - 56.5, - 56.2, - 56.1, - 56.2, - 56.1, - 56.1, - 56.3, - 56.3, - 55.6, - 54.4, - 52.5, - 51.8, - 51.5, - 51.3, - 51.1, - 50.8, - 50.7, - 50.5, - 50.2, - 50.1, - 49.9, - 49.6, - 49.4, - 49.2, - 49, - 48.9, - 48.5, - 48.3, - 48.1, - 48, - 48.2, - 48.2, - 48.1, - 47.9, - 47.5, - 47.3, - 47, - 46.8, - 46.6, - 46.7, - 46.5, - 46.2, - 46.2, - 46.1, - 46, - 45.7, - 45.6, - 45.5, - 45, - 44.5, - 43.8, - 43.1, - 42.5, - 42.1, - 41.6, - 41.3, - 40.9, - 40.2, - 39.7, - 39.2, - 38, - 34.9, - 33.6, - 33.6, - 33.8, - 33.8, - 33.8, - 33.6, - 32.4, - 31.5, - 30.3, - 29.4, - 28.8, - 28.1, - 27.6, - 27, - 26.4, - 25.3, - 24.4, - 23.8, - 23, - 22.1, - 21.2, - 20.6, - 19.9, - 19.1, - 18.5, - 18.1, - 17.7, - 17.3, - 17, - 16.6, - 16.3, - 16.1, - 16, - 15.9, - 15.8, - 15.6, - 15.4, - 15.1, - 14.9, - 14.4, - 13.9, - 13.5, - 13.1, - 12.7, - 12.2, - 11.3, - 10.2, - 9.1, - 8.4, - 7.7, - 7.1, - 6.6, - 5.9, - 5.1, - 4.5, - 4, - 3.4, - 3.1, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3, - 2.9, - 2.8, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 60.4, - 60.9, - 60.7, - 60.8, - 61.1, - 61.2, - 61.2, - 61.3, - 61.5, - 61.5, - 61.5, - 61.5, - 61.4, - 61.3, - 61.3, - 61.1, - 60.9, - 61, - 60.7, - 60.5, - 60.3, - 60.3, - 60.3, - 60.2, - 60, - 59.6, - 59.1, - 58.5, - 58.1, - 57.3, - 56.6, - 56.3, - 56.1, - 56, - 56, - 55.7, - 55.2, - 55.1, - 54.4, - 53.3, - 52.5, - 51.8, - 51.4, - 51.2, - 50.9, - 50.7, - 50.6, - 50.5, - 50.2, - 50.1, - 49.9, - 49.6, - 49.4, - 49.2, - 49.1, - 48.8, - 48.7, - 48.4, - 48.2, - 48.1, - 48.1, - 48.2, - 48.1, - 48, - 47.7, - 47.4, - 47.1, - 46.8, - 46.8, - 46.7, - 46.4, - 46.1, - 46.1, - 46, - 45.8, - 45.7, - 45.5, - 45.4, - 45.1, - 44.6, - 44, - 43.2, - 42.6, - 42, - 41.4, - 41.2, - 41, - 40.5, - 40, - 39.4, - 38.2, - 35.3, - 33.7, - 33.7, - 33.9, - 33.9, - 33.9, - 33.7, - 32.5, - 31.8, - 30.9, - 30, - 28.7, - 28, - 27.4, - 27, - 26.4, - 25.4, - 24.4, - 23.7, - 23, - 22.2, - 21.4, - 20.6, - 19.9, - 19.1, - 18.6, - 18.2, - 17.8, - 17.3, - 17, - 16.6, - 16.3, - 16.1, - 16, - 15.9, - 15.8, - 15.6, - 15.4, - 15.1, - 14.8, - 14.2, - 13.6, - 13, - 12.5, - 11.9, - 11.4, - 10.7, - 9.7, - 8.5, - 7.6, - 6.8, - 6.1, - 5.6, - 4.5, - 3.9, - 3.5, - 3.2, - 3, - 2.9, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3, - 2.9, - 2.8, - 2.7, - 2.7, - 2.6, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 60.2, - 60.3, - 60.4, - 60.5, - 60.7, - 60.9, - 61.1, - 61.3, - 61.4, - 61.4, - 61.4, - 61.4, - 61.3, - 61.3, - 61.2, - 60.9, - 60.7, - 60.7, - 60.5, - 60.4, - 60.2, - 60.2, - 60.1, - 59.9, - 59.7, - 59.6, - 59.3, - 58.7, - 58.1, - 57.4, - 56.8, - 56.3, - 56, - 55.9, - 55.8, - 55, - 54.1, - 53.7, - 53.7, - 53, - 52.4, - 51.8, - 51.3, - 51, - 50.9, - 50.7, - 50.4, - 50.4, - 50.3, - 50.2, - 49.9, - 49.5, - 49.2, - 49.1, - 49, - 48.8, - 48.5, - 48.4, - 48.3, - 48.2, - 48.2, - 48.1, - 48.2, - 48.1, - 47.8, - 47.5, - 47.1, - 46.8, - 46.9, - 46.6, - 46.2, - 45.9, - 45.8, - 45.6, - 45.6, - 45.5, - 45.4, - 45.3, - 45, - 44.6, - 44.1, - 43.2, - 42.5, - 41.9, - 41.2, - 41, - 41, - 40.7, - 40.2, - 39.6, - 38.6, - 36.2, - 33.8, - 33.7, - 34, - 34, - 33.9, - 33.7, - 32.6, - 32, - 31.3, - 30.4, - 29.1, - 28.1, - 27.5, - 26.8, - 26.1, - 25.3, - 24.4, - 23.8, - 23, - 22.2, - 21.4, - 20.7, - 19.9, - 19.1, - 18.6, - 18.2, - 17.8, - 17.4, - 17, - 16.6, - 16.3, - 16.1, - 15.9, - 15.9, - 15.8, - 15.6, - 15.4, - 15, - 14.4, - 13.7, - 12.9, - 12.2, - 11.5, - 10.9, - 10.5, - 9.9, - 9.1, - 8, - 7, - 6, - 5, - 4.4, - 3.6, - 3.3, - 3.1, - 2.9, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 2.9, - 2.8, - 2.7, - 2.7, - 2.6, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 59.8, - 60.1, - 60.2, - 60.3, - 60.5, - 60.7, - 60.9, - 61, - 61.2, - 61.1, - 61.1, - 61.3, - 61.4, - 61.2, - 60.8, - 60.7, - 60.6, - 60.5, - 60.5, - 60.4, - 60.2, - 60, - 59.8, - 59.8, - 59.6, - 59.5, - 59.4, - 58.9, - 58.2, - 57.6, - 57.1, - 56.6, - 56, - 55.7, - 55.1, - 54.1, - 53.6, - 53.5, - 53.3, - 52.9, - 52.2, - 51.7, - 51.4, - 51.1, - 50.8, - 50.7, - 50.5, - 50.3, - 50.2, - 50, - 49.8, - 49.6, - 49.2, - 49, - 48.9, - 48.8, - 48.5, - 48.4, - 48.3, - 48.1, - 48, - 48.2, - 48.2, - 48, - 47.7, - 47.4, - 47, - 46.7, - 46.9, - 46.7, - 46.3, - 45.9, - 45.6, - 45.4, - 45.5, - 45.4, - 45.4, - 45.2, - 45, - 44.6, - 44.1, - 43.2, - 42.4, - 41.6, - 41.1, - 40.9, - 40.9, - 40.7, - 40.2, - 39.7, - 38.9, - 37.3, - 34.3, - 33.8, - 34.1, - 34.1, - 34, - 33.8, - 32.8, - 32.2, - 31.4, - 30.5, - 29.6, - 28.5, - 27.6, - 26.7, - 25.8, - 25.2, - 24.6, - 23.8, - 23.1, - 22.2, - 21.4, - 20.7, - 20, - 19.2, - 18.7, - 18.3, - 17.9, - 17.4, - 17, - 16.6, - 16.3, - 16.1, - 15.9, - 15.9, - 15.8, - 15.6, - 15.2, - 14.7, - 14, - 13.1, - 12.1, - 11.3, - 10.6, - 10, - 9.4, - 8.9, - 8.4, - 7.4, - 6.3, - 5.3, - 4.2, - 3.5, - 3.2, - 3.1, - 2.8, - 2.7, - 2.7, - 2.8, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.6, - 2.6, - 2.5, - 2.4, - 2.3, - 2.2, - 2.2, - 2.3, - 2.3, - 2.4, - 2.5, - 2.6, - 2.6, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.9, - 2.8, - 2.8, - 2.8, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.8, - 59.6, - 59.8, - 60, - 60.3, - 60.6, - 60.8, - 60.7, - 60.8, - 60.9, - 60.9, - 61, - 61.1, - 61.3, - 61, - 60.8, - 60.6, - 60.5, - 60.5, - 60.6, - 60.3, - 60.2, - 60, - 59.9, - 59.8, - 59.7, - 59.6, - 59.7, - 58.9, - 58.3, - 58, - 57.6, - 56.8, - 56, - 55.3, - 54.6, - 53.7, - 53.4, - 53.2, - 53, - 52.6, - 52.1, - 51.7, - 51.5, - 51.2, - 50.8, - 50.6, - 50.5, - 50.3, - 50.1, - 49.8, - 49.7, - 49.5, - 49.3, - 49.1, - 48.9, - 48.9, - 48.9, - 48.7, - 48.4, - 48, - 47.9, - 47.9, - 48, - 47.8, - 47.6, - 47.4, - 46.9, - 46.6, - 46.8, - 46.7, - 46.3, - 45.8, - 45.4, - 45.3, - 45.2, - 45.1, - 45.2, - 45.1, - 45, - 44.5, - 44.1, - 43.1, - 42.1, - 41.4, - 41, - 40.8, - 40.8, - 40.5, - 40.1, - 39.7, - 39.2, - 37.9, - 35, - 33.9, - 34.2, - 34.2, - 34.1, - 33.9, - 33, - 32.5, - 31.7, - 30.7, - 29.9, - 28.8, - 27.7, - 26.8, - 25.9, - 25.3, - 24.6, - 23.8, - 23.2, - 22.1, - 21.3, - 20.7, - 20, - 19.2, - 18.6, - 18.3, - 17.9, - 17.5, - 17.1, - 16.7, - 16.4, - 16, - 15.8, - 15.8, - 15.8, - 15.5, - 15.1, - 14.4, - 13.4, - 12.4, - 11.5, - 10.7, - 9.9, - 9.1, - 8.4, - 7.8, - 7.3, - 6.5, - 5.4, - 4.3, - 3.5, - 3.1, - 2.9, - 2.7, - 2.7, - 2.7, - 2.8, - 2.9, - 2.9, - 2.9, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.9, - 2.9, - 2.8, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.7, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 59.4, - 59.6, - 59.8, - 60.3, - 60.6, - 60.7, - 60.8, - 60.8, - 60.7, - 60.8, - 60.9, - 61, - 61.1, - 61, - 60.8, - 60.7, - 60.5, - 60.3, - 60.3, - 60.2, - 60.2, - 60.4, - 60.1, - 60, - 59.8, - 59.6, - 59.6, - 58.7, - 58.2, - 58, - 57.7, - 56.8, - 55.2, - 54.5, - 54.1, - 53.5, - 53.3, - 53.1, - 53, - 52.6, - 52.2, - 51.9, - 51.8, - 51.5, - 51, - 50.6, - 50.3, - 50.2, - 50, - 49.8, - 49.7, - 49.5, - 49.3, - 49.1, - 48.9, - 48.9, - 48.9, - 48.9, - 48.5, - 48.1, - 48, - 47.8, - 47.7, - 47.6, - 47.5, - 47.2, - 46.9, - 46.6, - 46.5, - 46.4, - 46, - 45.6, - 45.2, - 45, - 45, - 45, - 44.9, - 45, - 44.9, - 44.4, - 43.9, - 42.6, - 41.8, - 41.2, - 41, - 40.9, - 40.7, - 40.5, - 40.1, - 39.6, - 39.2, - 38.1, - 35.3, - 34, - 34.3, - 34.3, - 34.2, - 34, - 33.1, - 32.6, - 31.7, - 30.6, - 29.8, - 28.7, - 27.9, - 26.9, - 26.1, - 25.4, - 24.7, - 23.9, - 23.1, - 22.1, - 21.4, - 20.8, - 20, - 19.3, - 18.7, - 18.3, - 18, - 17.6, - 17.2, - 16.8, - 16.4, - 16, - 15.9, - 15.9, - 15.7, - 15.3, - 14.7, - 13.8, - 12.7, - 11.8, - 11, - 10.3, - 9.5, - 8.6, - 7.7, - 6.8, - 6, - 5.4, - 4.6, - 3.5, - 3.1, - 2.9, - 2.7, - 2.6, - 2.7, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3.1, - 3, - 2.9, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2, - 1.9, - 1.9, - 1.9, - 2, - 2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.5, - 59, - 59.2, - 59.7, - 60.3, - 60.5, - 60.7, - 60.9, - 60.9, - 60.7, - 60.6, - 60.6, - 60.8, - 61.2, - 61.1, - 60.8, - 60.7, - 60.6, - 60.4, - 60.2, - 60.3, - 60.3, - 60.3, - 60.2, - 60.2, - 60.1, - 59.6, - 59, - 58.5, - 58.2, - 57.9, - 57.8, - 56.8, - 54.9, - 53.9, - 53.5, - 53.3, - 53.2, - 53.1, - 53, - 52.8, - 52.4, - 52.1, - 52, - 51.7, - 51.1, - 50.5, - 50.2, - 50, - 49.9, - 50, - 49.9, - 49.6, - 49.5, - 49.1, - 48.9, - 48.8, - 48.7, - 48.7, - 48.3, - 48, - 47.8, - 47.6, - 47.6, - 47.6, - 47.3, - 47, - 46.7, - 46.4, - 46.4, - 46.1, - 45.7, - 45.4, - 45.1, - 44.9, - 44.8, - 44.8, - 44.8, - 44.9, - 44.8, - 44.5, - 44, - 42.4, - 41.4, - 41.1, - 40.9, - 40.8, - 40.6, - 40.4, - 40, - 39.6, - 39.1, - 38, - 35.4, - 34.2, - 34.4, - 34.4, - 34.3, - 34.1, - 33.2, - 32.7, - 31.6, - 30.4, - 29.4, - 28.6, - 28, - 27.1, - 26.3, - 25.5, - 24.9, - 24.2, - 23.3, - 22.5, - 21.5, - 20.7, - 19.9, - 19.2, - 18.7, - 18.3, - 18, - 17.6, - 17.2, - 16.8, - 16.4, - 16.2, - 16, - 15.8, - 15.5, - 15, - 14, - 13, - 12, - 11.1, - 10.3, - 9.8, - 9.2, - 8.4, - 7.2, - 6.1, - 5.3, - 4.6, - 4.1, - 3.3, - 2.9, - 2.6, - 2.6, - 2.6, - 2.7, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2.9, - 2.9, - 2.8, - 2.7, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2, - 2, - 2, - 2, - 1.9, - 1.9, - 1.9, - 1.9, - 1.9, - 1.9, - 1.9, - 1.8, - 1.8, - 1.7, - 1.7, - 1.7, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.9, - 1.9, - 1.9, - 1.9, - 1.9, - 1.9, - 1.9, - 1.9, - 1.9, - 1.9, - 2, - 2, - 2, - 2, - 2, - 2, - 2.1, - 2.1, - 2.1, - 2.2, - 2.2, - 2.2, - 2.2, - 2.2, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.4, - 2.4, - 2.4, - 2.3, - 58.9, - 59.1, - 59.6, - 60.2, - 60.5, - 60.7, - 60.9, - 60.8, - 60.7, - 60.6, - 60.6, - 60.7, - 60.8, - 61, - 60.8, - 60.6, - 60.5, - 60.5, - 60.3, - 60.3, - 60.3, - 60.3, - 60.1, - 60.2, - 60.2, - 59.8, - 59, - 58.5, - 58.3, - 58, - 57.7, - 56.4, - 54.7, - 53.7, - 53.4, - 53.2, - 53.2, - 53.1, - 53, - 52.9, - 52.5, - 52.1, - 51.8, - 51.6, - 51.3, - 50.6, - 50.2, - 50.1, - 50.1, - 50, - 49.7, - 49.6, - 49.4, - 49, - 48.7, - 48.6, - 48.4, - 48.4, - 48, - 47.8, - 47.5, - 47.3, - 47.4, - 47.3, - 47, - 46.9, - 46.7, - 46.3, - 46.2, - 45.9, - 45.7, - 45.3, - 45.1, - 44.9, - 44.9, - 45, - 44.8, - 44.8, - 44.7, - 44.6, - 43.9, - 42.1, - 41.4, - 41, - 40.7, - 40.5, - 40.6, - 40.3, - 39.9, - 39.6, - 39.1, - 38.2, - 35.8, - 34.3, - 34.5, - 34.5, - 34.4, - 34.2, - 33.5, - 32.8, - 31.4, - 29.9, - 29, - 28.5, - 27.8, - 27.1, - 26.5, - 25.8, - 25.1, - 24.4, - 23.5, - 22.8, - 21.7, - 20.7, - 19.9, - 19.2, - 18.7, - 18.3, - 18, - 17.5, - 17.2, - 16.8, - 16.5, - 16.3, - 16, - 15.8, - 15.3, - 14.3, - 13.2, - 12.1, - 11.2, - 10.3, - 9.7, - 9.1, - 8.5, - 7.8, - 6.7, - 5.7, - 4.8, - 4.2, - 3.7, - 3.2, - 2.9, - 2.6, - 2.6, - 2.6, - 2.8, - 2.9, - 2.9, - 3, - 3, - 3, - 2.9, - 2.9, - 2.8, - 2.8, - 2.8, - 2.8, - 2.8, - 2.7, - 2.7, - 2.6, - 2.5, - 2.5, - 2.4, - 2.4, - 2.4, - 2.3, - 2.3, - 2.3, - 2.2, - 2.2, - 2.2, - 2.1, - 2.1, - 2.1, - 2.1, - 2, - 2, - 2, - 1.9, - 1.9, - 1.8, - 1.8, - 1.8, - 1.8, - 1.7, - 1.7, - 1.7, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.7, - 1.7, - 1.7, - 1.7, - 1.7, - 1.7, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.9, - 1.9, - 1.9, - 1.9, - 1.9, - 2, - 2, - 2, - 2, - 2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 2, - 2, - 2, - 2.1, - 2.1, - 2.1, - 2.1, - 2.1, - 58.8, - 59.1, - 59.4, - 60, - 60.5, - 60.9, - 61, - 60.9, - 60.5, - 60.5, - 60.4, - 60.5, - 60.6, - 60.8, - 60.7, - 60.5, - 60.5, - 60.5, - 60.2, - 60.1, - 60.1, - 60, - 59.9, - 60, - 60.1, - 59.9, - 59.4, - 58.7, - 58.3, - 58, - 57.1, - 55.6, - 54.8, - 53.7, - 53.5, - 53.4, - 53.2, - 53.1, - 53, - 52.8, - 52.5, - 52.1, - 51.8, - 51.6, - 51.3, - 50.7, - 50.2, - 50, - 50, - 49.8, - 49.6, - 49.5, - 49.3, - 48.9, - 48.8, - 48.6, - 48.4, - 48.2, - 47.9, - 47.8, - 47.4, - 47.3, - 47.3, - 47, - 46.8, - 46.7, - 46.6, - 46.4, - 46.1, - 46, - 45.8, - 45.4, - 45.3, - 45, - 44.9, - 44.9, - 44.9, - 44.7, - 44.6, - 44.4, - 43.8, - 42.2, - 41.3, - 40.9, - 40.5, - 40.4, - 40.4, - 40.2, - 39.9, - 39.8, - 39.2, - 38.3, - 36.5, - 34.5, - 34.6, - 34.5, - 34.5, - 34.3, - 33.6, - 32.8, - 31.3, - 30, - 29.2, - 28.4, - 27.8, - 27.1, - 26.5, - 25.8, - 25.3, - 24.6, - 23.8, - 23, - 21.8, - 20.9, - 20.1, - 19.3, - 18.7, - 18.3, - 17.9, - 17.5, - 17.2, - 16.9, - 16.6, - 16.3, - 16.1, - 15.6, - 14.9, - 13.7, - 12.6, - 11.4, - 10.6, - 9.9, - 9.1, - 8.4, - 7.8, - 7, - 6, - 5.2, - 4.4, - 3.9, - 3.5, - 3, - 2.8, - 2.6, - 2.6, - 2.6, - 2.8, - 2.9, - 2.9, - 3, - 2.9, - 2.8, - 2.8, - 2.7, - 2.7, - 2.6, - 2.6, - 2.6, - 2.5, - 2.5, - 2.4, - 2.3, - 2.3, - 2.2, - 2.2, - 2.1, - 2.1, - 2, - 2, - 2, - 1.9, - 1.9, - 1.9, - 1.8, - 1.8, - 1.8, - 1.8, - 1.7, - 1.7, - 1.7, - 1.6, - 1.6, - 1.6, - 1.5, - 1.5, - 1.5, - 1.4, - 1.4, - 1.4, - 1.4, - 1.4, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.2, - 1.3, - 1.2, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.4, - 1.4, - 1.4, - 1.4, - 1.4, - 1.4, - 1.4, - 1.4, - 1.5, - 1.5, - 1.5, - 1.5, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.7, - 1.7, - 1.7, - 1.7, - 1.7, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.8, - 1.9, - 1.9, - 58.8, - 59, - 59.1, - 59.6, - 60.3, - 60.9, - 60.9, - 60.7, - 60.4, - 60.3, - 60.4, - 60.3, - 60.6, - 60.7, - 60.6, - 60.6, - 60.4, - 60.3, - 60.1, - 60.2, - 60.3, - 60.1, - 60, - 59.9, - 60, - 60, - 59.6, - 58.9, - 58.4, - 57.2, - 56, - 55.4, - 54.7, - 54.1, - 53.5, - 53.3, - 53.1, - 53, - 52.8, - 52.6, - 52.4, - 52, - 51.8, - 51.5, - 51.1, - 50.6, - 50.2, - 50, - 49.7, - 49.5, - 49.5, - 49.4, - 49.2, - 48.9, - 48.8, - 48.5, - 48.4, - 48.2, - 47.8, - 47.7, - 47.6, - 47.3, - 47.2, - 46.9, - 46.7, - 46.6, - 46.5, - 46.4, - 46.3, - 46.2, - 46, - 45.8, - 45.6, - 45.3, - 45.2, - 45.1, - 44.9, - 44.6, - 44.5, - 44.3, - 43.8, - 41.8, - 41.2, - 40.9, - 40.6, - 40.4, - 40.4, - 40.2, - 39.9, - 39.8, - 39.4, - 38.6, - 36.7, - 34.4, - 34.6, - 34.6, - 34.5, - 34.4, - 33.5, - 32.9, - 31.3, - 30.1, - 29.2, - 28.5, - 27.9, - 27.2, - 26.5, - 25.9, - 25.3, - 24.7, - 23.9, - 23, - 22.1, - 21, - 20.2, - 19.2, - 18.7, - 18.1, - 17.8, - 17.5, - 17.2, - 16.9, - 16.6, - 16.3, - 16, - 15.2, - 14.1, - 13, - 11.8, - 10.8, - 10.2, - 9.6, - 8.9, - 8.2, - 7.5, - 6.4, - 5.5, - 4.8, - 4.2, - 3.7, - 3.2, - 2.9, - 2.6, - 2.5, - 2.5, - 2.7, - 2.8, - 2.8, - 2.9, - 2.9, - 2.8, - 2.7, - 2.6, - 2.5, - 2.5, - 2.4, - 2.3, - 2.3, - 2.2, - 2.2, - 2.1, - 2, - 2, - 1.9, - 1.9, - 1.8, - 1.8, - 1.7, - 1.7, - 1.7, - 1.6, - 1.6, - 1.6, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.4, - 1.4, - 1.4, - 1.3, - 1.3, - 1.3, - 1.2, - 1.2, - 1.2, - 1.2, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.2, - 1.2, - 1.2, - 1.2, - 1.2, - 1.2, - 1.2, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.4, - 1.4, - 1.4, - 1.4, - 1.4, - 1.5, - 1.5, - 1.5, - 1.5, - 1.5, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 1.6, - 58.7, - 58.9, - 58.9, - 59.2, - 60.1, - 60.8, - 60.8, - 60.6, - 60.4, - 60.3, - 60.3, - 60.3, - 60.6, - 60.8, - 60.8, - 60.7, - 60.6, - 60.4, - 60.2, - 60.2, - 60.3, - 60.1, - 60.1, - 60, - 60.1, - 59.9, - 59.4, - 59.1, - 58.1, - 56.3, - 55.7, - 55.2, - 54.5, - 54.1, - 53.8, - 53.5, - 53.3, - 53.1, - 52.7, - 52.5, - 52.2, - 52, - 51.8, - 51.6, - 51.1, - 50.6, - 50.3, - 50, - 49.6, - 49.4, - 49.4, - 49.4, - 49.2, - 48.9, - 48.6, - 48.4, - 48.2, - 48, - 47.8, - 47.7, - 47.6, - 47.4, - 47.2, - 46.8, - 46.6, - 46.4, - 46.4, - 46.5, - 46.5, - 46.4, - 46.2, - 46.1, - 46, - 45.6, - 45.3, - 45, - 44.7, - 44.6, - 44.5, - 44.3, - 43.9, - 41.7, - 41.2, - 41, - 40.6, - 40.6, - 40.5, - 40.3, - 40.1, - 39.8, - 39.4, - 38.6, - 37.4, - 35.1, - 34.7, - 34.7, - 34.6, - 34.5, - 33.7, - 33, - 31.6, - 30.4, - 29.3, - 28.6, - 28.1, - 27.3, - 26.7, - 26, - 25.4, - 24.5, - 23.7, - 23, - 22.1, - 21.1, - 20.2, - 19.2, - 18.5, - 18.1, - 17.8, - 17.4, - 17.1, - 16.8, - 16.6, - 16.2, - 15.5, - 14.4, - 13.5, - 12.2, - 11.1, - 10.3, - 9.7, - 9.1, - 8.5, - 8.1, - 7.3, - 6.5, - 5.2, - 4.3, - 3.8, - 3.4, - 2.9, - 2.7, - 2.6, - 2.5, - 2.5, - 2.6, - 2.7, - 2.8, - 2.8, - 2.7, - 2.6, - 2.5, - 2.4, - 2.3, - 2.2, - 2.1, - 2.1, - 2, - 1.9, - 1.9, - 1.8, - 1.7, - 1.7, - 1.6, - 1.6, - 1.5, - 1.5, - 1.5, - 1.4, - 1.4, - 1.4, - 1.3, - 1.3, - 1.3, - 1.3, - 1.2, - 1.2, - 1.2, - 1.2, - 1.1, - 1.1, - 1.1, - 1.1, - 1, - 1, - 1, - 1, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 1, - 1, - 1, - 1, - 1, - 1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.2, - 1.2, - 1.2, - 1.2, - 1.2, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.3, - 1.4, - 1.4, - 1.4, - 1.4, - 1.4, - 1.4, - 58.7, - 58.7, - 58.8, - 59.1, - 59.8, - 60.6, - 60.7, - 60.5, - 60.1, - 60.1, - 60.2, - 60.3, - 60.3, - 60.8, - 60.9, - 60.7, - 60.6, - 60.5, - 60.3, - 60.2, - 60.3, - 60, - 59.9, - 60, - 60, - 59.6, - 59.4, - 59, - 57.4, - 55.8, - 55.4, - 54.9, - 54.3, - 54.1, - 53.8, - 53.6, - 53.3, - 53, - 52.7, - 52.5, - 52.4, - 52.3, - 52.1, - 51.8, - 51.4, - 50.8, - 50.2, - 49.9, - 49.6, - 49.4, - 49.4, - 49.2, - 49, - 48.8, - 48.5, - 48.3, - 48.2, - 48, - 47.8, - 47.7, - 47.5, - 47.3, - 47, - 46.8, - 46.6, - 46.4, - 46.4, - 46.4, - 46.4, - 46.5, - 46.5, - 46.1, - 46, - 45.8, - 45.5, - 45.1, - 44.8, - 44.5, - 44.5, - 44.3, - 43.8, - 42.3, - 41.5, - 41.1, - 40.7, - 40.7, - 40.6, - 40.5, - 40.2, - 39.7, - 39.1, - 38.6, - 37.7, - 35.2, - 34.7, - 34.7, - 34.7, - 34.5, - 33.9, - 33, - 31.8, - 30.5, - 29.3, - 28.7, - 28.1, - 27.4, - 26.6, - 25.9, - 25, - 24, - 23.3, - 22.7, - 21.9, - 21.1, - 20.2, - 19.4, - 18.6, - 18.1, - 17.7, - 17.5, - 17.2, - 17, - 16.6, - 16, - 15, - 14, - 12.8, - 11.5, - 10.6, - 10, - 9.4, - 8.8, - 8.4, - 7.9, - 7.3, - 6.3, - 5.1, - 4, - 3.6, - 3.2, - 2.8, - 2.6, - 2.5, - 2.5, - 2.5, - 2.5, - 2.6, - 2.6, - 2.7, - 2.6, - 2.5, - 2.3, - 2.2, - 2, - 2, - 1.9, - 1.8, - 1.7, - 1.6, - 1.6, - 1.5, - 1.5, - 1.4, - 1.4, - 1.3, - 1.3, - 1.3, - 1.2, - 1.2, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1, - 1, - 1, - 1, - 0.9, - 0.9, - 0.9, - 0.9, - 0.8, - 0.8, - 0.8, - 0.8, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 1, - 1, - 1, - 1, - 1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.1, - 1.2, - 1.2, - 1.2, - 58.7, - 58.7, - 58.8, - 59, - 59.4, - 60, - 60.3, - 60.2, - 59.9, - 59.8, - 60.3, - 60.3, - 60.2, - 60.5, - 60.7, - 60.7, - 60.5, - 60.4, - 60.3, - 60.1, - 59.9, - 59.7, - 59.8, - 60, - 59.9, - 59.6, - 59.4, - 58.8, - 56.8, - 55.8, - 55.3, - 54.6, - 54.2, - 54, - 53.7, - 53.5, - 53.1, - 52.9, - 52.7, - 52.7, - 52.7, - 52.5, - 52.4, - 52, - 51.6, - 50.8, - 50.2, - 49.9, - 49.6, - 49.4, - 49.4, - 49.4, - 49.1, - 49, - 48.6, - 48.3, - 48.1, - 48, - 47.9, - 47.7, - 47.4, - 47.2, - 46.9, - 46.8, - 46.6, - 46.3, - 46.3, - 46.4, - 46.3, - 46.6, - 46.5, - 46.2, - 46, - 45.9, - 45.5, - 45.2, - 44.9, - 44.6, - 44.4, - 44.2, - 43.5, - 42.3, - 41.6, - 41.1, - 40.7, - 40.8, - 40.7, - 40.7, - 40.2, - 39.7, - 39.1, - 38.7, - 38, - 35.4, - 34.8, - 34.8, - 34.7, - 34.6, - 34, - 33, - 31.8, - 30.7, - 29.5, - 28.7, - 28.2, - 27.5, - 26.7, - 25.6, - 24.6, - 23.7, - 23.1, - 22.5, - 21.9, - 21.1, - 20.3, - 19.6, - 18.8, - 18.3, - 17.9, - 17.6, - 17.3, - 17, - 16.4, - 15.7, - 14.7, - 13.6, - 12.4, - 11.3, - 10.3, - 9.7, - 9.1, - 8.5, - 8.1, - 7.7, - 6.7, - 5.8, - 4.7, - 3.8, - 3.3, - 3, - 2.8, - 2.6, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.5, - 2.4, - 2.3, - 2.2, - 2, - 1.9, - 1.8, - 1.7, - 1.6, - 1.5, - 1.4, - 1.4, - 1.3, - 1.2, - 1.2, - 1.1, - 1.1, - 1.1, - 1, - 1, - 1, - 1, - 0.9, - 0.9, - 0.9, - 0.9, - 0.8, - 0.8, - 0.8, - 0.8, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.6, - 0.6, - 0.6, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.4, - 0.5, - 0.5, - 0.5, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.8, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 0.9, - 1, - 58.7, - 58.6, - 58.8, - 59.1, - 59.4, - 59.6, - 59.7, - 59.7, - 59.8, - 59.8, - 60.1, - 60.3, - 60.1, - 60.3, - 60.6, - 60.5, - 60.3, - 60.2, - 60.2, - 60.1, - 59.7, - 59.6, - 59.6, - 59.6, - 59.5, - 59.4, - 59.1, - 58.2, - 56.4, - 55.5, - 55.2, - 54.7, - 54.2, - 54, - 53.7, - 53.3, - 53.1, - 52.9, - 52.7, - 52.8, - 52.8, - 52.8, - 52.4, - 52, - 51.2, - 50.7, - 50.3, - 49.8, - 49.4, - 49.1, - 49.2, - 49.3, - 49, - 48.7, - 48.6, - 48.4, - 48.1, - 47.9, - 47.8, - 47.7, - 47.4, - 47.2, - 46.9, - 46.6, - 46.4, - 46, - 46.1, - 46.2, - 46.3, - 46.4, - 46.2, - 46.2, - 46.1, - 45.8, - 45.4, - 45.3, - 45, - 44.6, - 44.5, - 44.2, - 43.5, - 42.6, - 41.7, - 41, - 40.7, - 40.9, - 41, - 40.8, - 40.1, - 39.6, - 39, - 38.7, - 37.9, - 35.6, - 34.8, - 34.9, - 34.8, - 34.7, - 34.1, - 33.1, - 31.9, - 30.9, - 29.8, - 28.8, - 28.2, - 27.6, - 26.6, - 25.4, - 24.4, - 23.6, - 23, - 22.4, - 21.9, - 21.2, - 20.4, - 19.7, - 18.9, - 18.5, - 18.1, - 17.7, - 17.4, - 17.1, - 16.5, - 15.8, - 14.8, - 13.6, - 12.3, - 11.2, - 10.2, - 9.4, - 8.7, - 8, - 7.7, - 6.9, - 5.9, - 5.1, - 4.1, - 3.6, - 3.1, - 2.9, - 2.8, - 2.6, - 2.4, - 2.3, - 2.3, - 2.3, - 2.3, - 2.3, - 2.2, - 2, - 1.9, - 1.7, - 1.6, - 1.5, - 1.4, - 1.3, - 1.3, - 1.2, - 1.1, - 1.1, - 1, - 1, - 0.9, - 0.9, - 0.9, - 0.8, - 0.8, - 0.8, - 0.8, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.6, - 0.6, - 0.6, - 0.6, - 0.5, - 0.5, - 0.5, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.6, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.8, - 0.7, - 58.7, - 58.9, - 58.8, - 58.9, - 59.2, - 59.4, - 59.4, - 59.5, - 59.7, - 59.8, - 60, - 60.1, - 59.9, - 60.1, - 60.4, - 60.4, - 60.2, - 60, - 60.2, - 59.9, - 59.3, - 59.4, - 59.5, - 59.4, - 59.2, - 59.1, - 58.5, - 56.9, - 55.8, - 55.2, - 55, - 54.6, - 54.2, - 54, - 53.6, - 53.3, - 53, - 52.8, - 52.7, - 52.9, - 52.8, - 52.7, - 52.3, - 51.9, - 51, - 50.7, - 50.4, - 50, - 49.5, - 49.1, - 49.2, - 49.3, - 49, - 48.8, - 48.4, - 48.2, - 47.9, - 47.8, - 47.7, - 47.5, - 47.2, - 47, - 46.8, - 46.5, - 46.3, - 46, - 45.9, - 45.9, - 46, - 46, - 45.9, - 45.9, - 45.9, - 45.8, - 45.4, - 45.3, - 45.1, - 44.7, - 44.6, - 44.2, - 43.4, - 42.6, - 41.8, - 41.1, - 40.9, - 41, - 41, - 40.5, - 39.7, - 39.3, - 38.8, - 38.3, - 37.2, - 35.2, - 34.8, - 34.9, - 34.8, - 34.7, - 34.3, - 33.3, - 32.1, - 31.1, - 30, - 28.7, - 28.1, - 27.4, - 26.4, - 25.4, - 24.5, - 23.6, - 23.1, - 22.4, - 21.8, - 21.1, - 20.4, - 19.6, - 19, - 18.6, - 18.2, - 17.8, - 17.5, - 17.2, - 16.7, - 16.1, - 15.1, - 13.9, - 12.5, - 11.3, - 10.2, - 9.2, - 8.3, - 7.5, - 7, - 5.9, - 5, - 4.4, - 3.9, - 3.6, - 3.2, - 2.9, - 2.7, - 2.6, - 2.4, - 2.3, - 2.2, - 2.2, - 2.1, - 2, - 1.9, - 1.7, - 1.6, - 1.5, - 1.4, - 1.3, - 1.2, - 1.1, - 1, - 1, - 0.9, - 0.9, - 0.8, - 0.8, - 0.7, - 0.7, - 0.7, - 0.6, - 0.6, - 0.6, - 0.6, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.4, - 0.4, - 0.4, - 0.4, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.6, - 58.9, - 59, - 59.1, - 58.9, - 58.9, - 59.1, - 59.2, - 59.2, - 59.2, - 59.4, - 59.8, - 59.9, - 59.8, - 60, - 60.1, - 60.1, - 59.9, - 59.7, - 60.2, - 59.7, - 59.2, - 59.2, - 59.3, - 59.4, - 59.1, - 58.6, - 57.2, - 55.9, - 55.3, - 55.1, - 55, - 54.5, - 54.2, - 53.9, - 53.5, - 53.1, - 53, - 52.8, - 52.8, - 52.9, - 52.6, - 52.4, - 52, - 51.8, - 51.1, - 50.7, - 50.5, - 50.1, - 49.4, - 49.1, - 49.1, - 49.2, - 49.2, - 48.8, - 48.4, - 48, - 47.8, - 47.7, - 47.6, - 47.4, - 47.3, - 46.9, - 46.7, - 46.5, - 46.3, - 46.1, - 45.9, - 45.7, - 45.5, - 45.6, - 45.5, - 45.4, - 45.7, - 45.5, - 45.3, - 45.2, - 45, - 44.8, - 44.7, - 44.1, - 43.7, - 42.7, - 41.9, - 41.4, - 41.2, - 41, - 40.6, - 40, - 39.6, - 39.1, - 38.5, - 37.9, - 37.2, - 35.4, - 34.8, - 35, - 34.9, - 34.8, - 34.5, - 33.5, - 32.4, - 31.2, - 30, - 28.7, - 28.2, - 27.6, - 26.7, - 25.5, - 24.6, - 23.6, - 22.9, - 22.4, - 21.6, - 20.9, - 20.2, - 19.6, - 19.1, - 18.7, - 18.3, - 17.9, - 17.6, - 17.3, - 16.8, - 16.2, - 15.3, - 14.1, - 12.7, - 11.3, - 10.1, - 9.1, - 8.1, - 7.1, - 6, - 4.8, - 4.2, - 4, - 3.8, - 3.5, - 3, - 2.7, - 2.6, - 2.5, - 2.4, - 2.3, - 2.1, - 2, - 2, - 1.8, - 1.6, - 1.5, - 1.4, - 1.3, - 1.1, - 1, - 0.9, - 0.9, - 0.8, - 0.8, - 0.7, - 0.7, - 0.6, - 0.6, - 0.5, - 0.5, - 0.5, - 0.5, - 0.4, - 0.4, - 0.4, - 0.4, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - -0.1, - -0.1, - -0.1, - 0, - -0.1, - -0.1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 0.4, - 58.7, - 58.6, - 58.8, - 58.8, - 58.8, - 59, - 59, - 59, - 58.9, - 59, - 59.4, - 59.5, - 59.4, - 59.7, - 59.7, - 59.6, - 59.4, - 59.4, - 59.5, - 59.3, - 59.1, - 59, - 59.2, - 59.2, - 58.4, - 57.4, - 56.1, - 55.5, - 55.3, - 55.1, - 54.8, - 54.6, - 54.2, - 53.8, - 53.5, - 53.1, - 52.8, - 52.8, - 52.9, - 52.8, - 52.3, - 52.1, - 51.9, - 51.6, - 51.1, - 50.8, - 50.5, - 50.3, - 49.5, - 49.2, - 49, - 48.9, - 48.9, - 48.8, - 48.4, - 48, - 47.8, - 47.6, - 47.5, - 47.5, - 47.3, - 46.9, - 46.6, - 46.4, - 46.3, - 46.1, - 45.8, - 45.6, - 45.4, - 45.3, - 45.3, - 45.3, - 45.2, - 45.1, - 45, - 45, - 44.7, - 44.8, - 44.7, - 44.3, - 43.9, - 43.2, - 42.2, - 41.7, - 41.4, - 40.9, - 40.5, - 40, - 39.4, - 38.9, - 38.4, - 37.7, - 37.1, - 35.5, - 34.8, - 35, - 34.9, - 34.9, - 34.7, - 33.9, - 32.8, - 31.4, - 30.2, - 29.1, - 28.6, - 27.9, - 27.2, - 26.3, - 25.5, - 23.8, - 22.9, - 22.2, - 21.6, - 20.8, - 20.1, - 19.7, - 19.2, - 18.8, - 18.5, - 18.1, - 17.7, - 17.3, - 16.8, - 16.3, - 15.6, - 14.4, - 12.9, - 11.5, - 10.2, - 9.4, - 8.2, - 6.8, - 5.3, - 4.2, - 3.8, - 3.4, - 3.4, - 3.1, - 2.8, - 2.6, - 2.4, - 2.3, - 2.2, - 2.1, - 2, - 1.9, - 1.7, - 1.6, - 1.4, - 1.3, - 1.2, - 1, - 0.9, - 0.8, - 0.7, - 0.7, - 0.6, - 0.6, - 0.6, - 0.5, - 0.4, - 0.4, - 0.4, - 0.3, - 0.3, - 0.3, - 0.3, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 58.5, - 58.5, - 58.6, - 58.6, - 58.7, - 58.7, - 58.8, - 58.8, - 58.6, - 58.7, - 58.9, - 59, - 58.7, - 58.8, - 59.2, - 59.4, - 59.1, - 59.1, - 59, - 59.1, - 59, - 59.1, - 59.3, - 58.8, - 57.3, - 56.8, - 55.9, - 55.4, - 55.3, - 55.1, - 54.7, - 54.4, - 54.1, - 53.7, - 53.4, - 53.2, - 52.8, - 52.7, - 52.8, - 52.5, - 52.2, - 52.1, - 51.9, - 51.7, - 51.4, - 50.9, - 50.6, - 50.4, - 49.8, - 49.4, - 49, - 48.8, - 48.7, - 48.6, - 48.4, - 48.1, - 48, - 47.7, - 47.4, - 47.3, - 47.1, - 46.9, - 46.6, - 46.4, - 46.1, - 46, - 45.8, - 45.6, - 45.4, - 45.3, - 45.1, - 45.2, - 45.1, - 44.9, - 44.8, - 44.8, - 44.6, - 44.5, - 44.7, - 44.5, - 44, - 43.4, - 42.8, - 42.1, - 41.5, - 41, - 40.6, - 39.9, - 39.4, - 38.9, - 38.3, - 37.5, - 36.9, - 35.6, - 34.8, - 35, - 35, - 34.9, - 34.8, - 34.2, - 33.1, - 31.7, - 30.5, - 29.4, - 28.8, - 28, - 27.3, - 26.7, - 26.1, - 24.9, - 23.6, - 22.5, - 21.6, - 20.9, - 20.3, - 19.8, - 19.4, - 19, - 18.6, - 18.3, - 17.8, - 17.3, - 16.8, - 16.4, - 15.7, - 14.7, - 13.5, - 12.3, - 11, - 9.9, - 8.7, - 7.3, - 5.4, - 4, - 3.5, - 3.2, - 2.9, - 2.8, - 2.7, - 2.4, - 2.2, - 2.1, - 2, - 2, - 1.9, - 1.7, - 1.5, - 1.4, - 1.3, - 1.1, - 1, - 0.9, - 0.7, - 0.6, - 0.6, - 0.5, - 0.5, - 0.5, - 0.4, - 0.3, - 0.3, - 0.3, - 0.2, - 0.2, - 0.2, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0, - 0, - 0, - 0, - 0, - 0, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 58.5, - 58.4, - 58.4, - 58.4, - 58.4, - 58.4, - 58.6, - 58.6, - 58.3, - 58.4, - 58.5, - 58.4, - 58.4, - 58.4, - 58.7, - 58.9, - 58.8, - 58.7, - 58.7, - 58.8, - 58.8, - 58.9, - 58.8, - 57.9, - 57.1, - 56.6, - 55.7, - 55.3, - 55.2, - 55, - 54.7, - 54.2, - 54, - 53.7, - 53.5, - 53.3, - 53.1, - 52.9, - 52.7, - 52.5, - 52.2, - 52, - 51.9, - 51.7, - 51.4, - 50.9, - 50.7, - 50.3, - 49.8, - 49.4, - 49.1, - 48.9, - 48.7, - 48.5, - 48.3, - 48.2, - 48, - 47.7, - 47.4, - 47.3, - 47, - 46.8, - 46.6, - 46.3, - 46.1, - 46, - 45.7, - 45.6, - 45.6, - 45.4, - 45.2, - 45, - 45.1, - 44.9, - 44.7, - 44.7, - 44.6, - 44.4, - 44.5, - 44.2, - 43.9, - 43.4, - 42.8, - 42.3, - 41.5, - 41, - 40.6, - 40.1, - 39.6, - 39, - 38.3, - 37.6, - 36.8, - 35.6, - 34.9, - 35, - 35, - 35, - 34.9, - 34.3, - 33.4, - 32.1, - 30.7, - 29.7, - 29.2, - 28.3, - 27.5, - 26.9, - 26.2, - 25.1, - 24, - 22.9, - 21.9, - 21.2, - 20.6, - 20.1, - 19.7, - 19.3, - 18.9, - 18.6, - 18.1, - 17.4, - 16.8, - 16.4, - 15.8, - 15.1, - 14.2, - 13.2, - 11.8, - 10.6, - 9.2, - 7.6, - 5.9, - 4.3, - 3.5, - 3, - 2.7, - 2.7, - 2.5, - 2.2, - 2.1, - 1.9, - 1.9, - 1.8, - 1.7, - 1.5, - 1.3, - 1.2, - 1.1, - 1, - 0.8, - 0.7, - 0.6, - 0.5, - 0.4, - 0.4, - 0.4, - 0.3, - 0.3, - 0.2, - 0.2, - 0.1, - 0.1, - 0, - 0, - 0, - 0, - 0, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - -0.1, - 58.5, - 58.4, - 58.4, - 58.2, - 58.1, - 58.1, - 58.3, - 58.1, - 58.1, - 58.1, - 58.3, - 58.2, - 58.1, - 58.1, - 58.2, - 58.3, - 58.4, - 58.3, - 58.3, - 58.4, - 58.4, - 58.3, - 57.9, - 57.3, - 56.7, - 56.1, - 55.3, - 55.1, - 55, - 54.8, - 54.5, - 54.1, - 54, - 53.7, - 53.6, - 53.5, - 53.2, - 53, - 53, - 52.8, - 52.5, - 52.1, - 51.7, - 51.6, - 51.4, - 50.8, - 50.6, - 50.2, - 49.8, - 49.6, - 49.4, - 49.1, - 48.9, - 48.6, - 48.4, - 48.3, - 48.1, - 47.8, - 47.5, - 47.3, - 47.1, - 47, - 46.7, - 46.2, - 46.1, - 45.9, - 45.6, - 45.5, - 45.5, - 45.3, - 45.2, - 45.1, - 45, - 45, - 44.9, - 44.8, - 44.8, - 44.5, - 44.5, - 44.2, - 43.7, - 43.3, - 42.8, - 42.2, - 41.3, - 40.9, - 40.5, - 40.2, - 39.5, - 39.1, - 38.4, - 37.4, - 36.5, - 35.6, - 35, - 35, - 35.1, - 35, - 34.9, - 34.6, - 34, - 32.9, - 30.9, - 29.9, - 29.4, - 28.8, - 28.1, - 27.4, - 26.6, - 25.4, - 24.4, - 23.3, - 22.4, - 21.7, - 21.2, - 20.6, - 20.1, - 20, - 19.7, - 19.4, - 18.8, - 18.2, - 17.5, - 16.7, - 16, - 15.2, - 14.5, - 13.6, - 12.3, - 10.9, - 9.4, - 8.1, - 6.6, - 5, - 3.8, - 3.4, - 3, - 2.7, - 2.4, - 2.1, - 1.9, - 1.8, - 1.8, - 1.6, - 1.5, - 1.3, - 1.1, - 1, - 0.9, - 0.8, - 0.7, - 0.6, - 0.5, - 0.4, - 0.4, - 0.3, - 0.2, - 0.2, - 0.2, - 0.1, - 0, - 0, - 0, - -0.1, - -0.1, - -0.1, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - -0.2, - 58.7, - 58.7, - 58.5, - 58.1, - 58, - 57.9, - 57.9, - 57.9, - 57.8, - 57.7, - 57.6, - 57.9, - 58.1, - 57.9, - 57.7, - 58, - 58, - 58, - 57.9, - 58, - 58, - 57.8, - 57.5, - 57.1, - 56.3, - 55.6, - 55.2, - 55, - 54.9, - 54.6, - 54.3, - 54.2, - 54.2, - 53.9, - 53.6, - 53.5, - 53.3, - 53.3, - 53.2, - 52.9, - 52.6, - 52.2, - 51.7, - 51.4, - 51.1, - 50.8, - 50.6, - 50.2, - 49.9, - 49.7, - 49.5, - 49.3, - 49.1, - 48.8, - 48.6, - 48.4, - 48.1, - 47.7, - 47.4, - 47.2, - 47, - 46.9, - 46.5, - 46.2, - 46.2, - 45.8, - 45.5, - 45.4, - 45.3, - 45.1, - 45.1, - 44.9, - 44.9, - 44.9, - 44.6, - 44.5, - 44.7, - 44.5, - 44.4, - 44.2, - 43.7, - 43.4, - 42.8, - 42.1, - 41.3, - 40.9, - 40.6, - 40.1, - 39.6, - 39, - 38, - 37.1, - 36.2, - 35.6, - 35.2, - 35, - 35.1, - 35.1, - 35, - 34.8, - 34.4, - 33.5, - 32.3, - 30.9, - 30.4, - 29.5, - 28.6, - 28.2, - 27, - 25.8, - 24.8, - 23.9, - 22.9, - 22.5, - 21.8, - 21.3, - 20.7, - 20.5, - 20.7, - 20.4, - 19.9, - 19.5, - 18.8, - 17.6, - 16.6, - 15.5, - 14.6, - 13.6, - 12.5, - 11, - 9.8, - 8.4, - 7.1, - 5.4, - 4.1, - 3.6, - 3, - 2.3, - 2.2, - 2, - 1.8, - 1.7, - 1.6, - 1.5, - 1.3, - 1.2, - 1, - 0.9, - 0.8, - 0.7, - 0.6, - 0.5, - 0.4, - 0.3, - 0.3, - 0.2, - 0.1, - 0.1, - 0, - 0, - -0.1, - -0.1, - -0.2, - -0.2, - -0.2, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - -0.3, - 58.5, - 58.7, - 58.6, - 58.2, - 57.9, - 57.9, - 57.7, - 57.6, - 57.5, - 57.3, - 57.2, - 57.2, - 57.4, - 57.3, - 57.2, - 57.7, - 57.7, - 57.7, - 57.5, - 57.5, - 57.6, - 57.6, - 57.4, - 57, - 56.1, - 55.5, - 55.2, - 55, - 54.8, - 54.6, - 54.3, - 54.3, - 54.2, - 53.8, - 53.6, - 53.5, - 53.3, - 53.3, - 53.2, - 52.9, - 52.6, - 52.2, - 51.7, - 51.4, - 51.2, - 50.9, - 50.6, - 50.2, - 49.9, - 49.6, - 49.4, - 49.3, - 49.1, - 48.9, - 48.9, - 48.4, - 48, - 47.6, - 47.3, - 47.1, - 46.9, - 46.7, - 46.5, - 46.2, - 46.1, - 45.7, - 45.4, - 45.3, - 45.3, - 45.1, - 44.9, - 45, - 45, - 44.8, - 44.6, - 44.3, - 44.2, - 44.2, - 44.1, - 44, - 43.8, - 43.6, - 42.9, - 42.2, - 41.6, - 41.1, - 40.8, - 40.1, - 39.5, - 38.8, - 37.9, - 37.2, - 36.1, - 35.6, - 35.4, - 35, - 35.1, - 35.1, - 35, - 35, - 34.6, - 34, - 33.2, - 32.9, - 32.1, - 30.8, - 29.3, - 28.5, - 27.5, - 26.5, - 25.4, - 24.6, - 23.7, - 23, - 22.3, - 21.7, - 21.2, - 21.2, - 21.2, - 20.9, - 20.6, - 20.2, - 19.4, - 18.4, - 17.2, - 15.8, - 14.8, - 13.7, - 12.7, - 11.4, - 10.1, - 8.6, - 7.1, - 5.4, - 4.3, - 3.8, - 3, - 2.3, - 2.1, - 2, - 1.8, - 1.6, - 1.6, - 1.5, - 1.2, - 1.1, - 0.9, - 0.8, - 0.7, - 0.6, - 0.5, - 0.4, - 0.3, - 0.2, - 0.1, - 0.1, - 0, - 0, - -0.1, - -0.2, - -0.2, - -0.3, - -0.3, - -0.3, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - -0.4, - 58.4, - 58.5, - 58.4, - 58.1, - 57.7, - 57.6, - 57.4, - 57.2, - 56.9, - 56.9, - 56.9, - 56.9, - 56.9, - 56.8, - 56.7, - 57.1, - 57.3, - 57.5, - 57.2, - 57, - 57, - 56.9, - 56.9, - 56.8, - 56.1, - 55.6, - 55.4, - 55.2, - 54.9, - 54.5, - 54.4, - 54.3, - 54, - 53.8, - 53.6, - 53.5, - 53.3, - 53.2, - 53.1, - 52.8, - 52.5, - 52, - 51.7, - 51.6, - 51.5, - 51.1, - 50.7, - 50.4, - 49.9, - 49.6, - 49.4, - 49.1, - 49, - 49, - 48.9, - 48.5, - 48, - 47.5, - 47.2, - 47, - 46.9, - 46.6, - 46.3, - 46, - 45.8, - 45.6, - 45.4, - 45.2, - 45.1, - 45, - 45, - 44.9, - 45, - 44.7, - 44.6, - 44.3, - 43.9, - 43.6, - 43.7, - 43.7, - 43.7, - 43.5, - 42.7, - 42.2, - 41.6, - 41, - 40.7, - 40.1, - 39.3, - 38.6, - 37.8, - 37, - 36.4, - 36, - 35.8, - 35.2, - 35.1, - 35.1, - 35.1, - 35, - 34.9, - 34.4, - 33.8, - 33.5, - 32.8, - 32, - 30.8, - 29.6, - 28.6, - 27.7, - 26.5, - 25.5, - 24.4, - 23.4, - 22.7, - 22, - 21.4, - 21.4, - 21.3, - 21, - 20.7, - 20.4, - 19.6, - 18.9, - 17.8, - 16.3, - 15, - 14.3, - 13.3, - 12.1, - 10.5, - 8.7, - 6.9, - 5.4, - 4.5, - 3.8, - 2.8, - 2.3, - 2, - 1.9, - 1.7, - 1.5, - 1.3, - 1.3, - 1.1, - 0.9, - 0.8, - 0.7, - 0.6, - 0.5, - 0.4, - 0.3, - 0.2, - 0.1, - 0, - 0, - -0.1, - -0.2, - -0.2, - -0.3, - -0.3, - -0.4, - -0.4, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - -0.5, - 58.4, - 58.4, - 58.3, - 58.1, - 57.8, - 57.5, - 57.3, - 57, - 56.6, - 56.6, - 56.6, - 56.6, - 56.7, - 56.7, - 56.6, - 56.7, - 57, - 57, - 56.9, - 56.9, - 56.8, - 56.7, - 56.8, - 56.8, - 56.3, - 55.8, - 55.4, - 55.3, - 55, - 54.5, - 54.3, - 54.1, - 54, - 54, - 53.6, - 53.5, - 53.3, - 53.1, - 53.1, - 52.7, - 52.4, - 52.1, - 51.8, - 51.4, - 51.5, - 51.2, - 50.8, - 50.3, - 49.8, - 49.6, - 49.3, - 49, - 48.9, - 48.9, - 48.8, - 48.5, - 47.9, - 47.5, - 47.3, - 47, - 46.8, - 46.5, - 46.2, - 46.1, - 46, - 45.6, - 45.4, - 45.2, - 45, - 45, - 44.9, - 44.9, - 44.9, - 44.7, - 44.5, - 44.4, - 44, - 43.5, - 43.4, - 43.4, - 43.4, - 43, - 42.5, - 42.1, - 41.5, - 41.1, - 40.4, - 39.7, - 39.1, - 38.4, - 37.8, - 37.3, - 36.8, - 36.5, - 36.3, - 35.5, - 35.1, - 35.1, - 35.1, - 35, - 35, - 34.7, - 34, - 33.7, - 33.1, - 32.6, - 31.8, - 30.8, - 30.2, - 29.2, - 27.4, - 26.2, - 25.1, - 24, - 23.1, - 22.3, - 21.8, - 21.6, - 21.3, - 21.1, - 20.8, - 20.3, - 19.6, - 19, - 18.1, - 16.7, - 15.5, - 14.8, - 14, - 12.6, - 10.4, - 8.8, - 7, - 5.6, - 4.5, - 3.4, - 2.8, - 2.4, - 1.8, - 1.5, - 1.3, - 1.2, - 1.1, - 1.1, - 1, - 0.8, - 0.6, - 0.5, - 0.5, - 0.4, - 0.3, - 0.3, - 0, - 0, - -0.1, - -0.2, - -0.2, - -0.3, - -0.4, - -0.4, - -0.5, - -0.5, - -0.5, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - -0.6, - 58.4, - 58.2, - 58.1, - 57.8, - 57.8, - 57.5, - 57.3, - 56.9, - 56.4, - 56.3, - 56.3, - 56.3, - 56.4, - 56.6, - 56.5, - 56.5, - 56.5, - 56.7, - 56.7, - 56.7, - 56.7, - 56.7, - 56.9, - 56.9, - 56.5, - 55.9, - 55.4, - 55.3, - 55.2, - 54.6, - 54.3, - 54.1, - 54, - 53.8, - 53.6, - 53.4, - 53.2, - 53.1, - 53.1, - 52.9, - 52.7, - 52.7, - 52, - 51.5, - 51.2, - 51.2, - 50.7, - 50.2, - 49.8, - 49.4, - 49.2, - 48.9, - 48.8, - 48.8, - 48.5, - 48.2, - 47.9, - 47.7, - 47.5, - 47, - 46.7, - 46.4, - 46.2, - 46, - 46, - 45.7, - 45.5, - 45.1, - 45, - 45, - 44.8, - 44.8, - 44.9, - 44.6, - 44.4, - 44.4, - 44, - 43.5, - 43.3, - 43.2, - 43.1, - 42.9, - 42.4, - 41.8, - 41.4, - 41, - 39.9, - 39.4, - 39, - 38.5, - 38, - 37.6, - 37.2, - 36.9, - 36.6, - 35.9, - 35.1, - 35.1, - 35.1, - 35.1, - 35, - 34.9, - 34.3, - 33.8, - 33.3, - 32.6, - 32.2, - 31.8, - 31.1, - 30.2, - 28.2, - 26.4, - 25.4, - 24.5, - 23.6, - 22.9, - 22.4, - 22, - 21.7, - 21.3, - 21, - 20.3, - 19.6, - 19, - 18.1, - 17, - 15.9, - 15.1, - 14.3, - 12.8, - 10.9, - 9, - 7.4, - 5.6, - 4.5, - 3.4, - 3, - 2.3, - 1.7, - 1.3, - 1.2, - 1.2, - 1, - 0.9, - 0.8, - 0.6, - 0.5, - 0.4, - 0.4, - 0.3, - 0.2, - 0.1, - 0, - -0.2, - -0.3, - -0.3, - -0.4, - -0.4, - -0.5, - -0.5, - -0.6, - -0.6, - -0.6, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - -0.7, - 58.5, - 58.3, - 57.9, - 57.6, - 57.5, - 57.4, - 57.1, - 56.6, - 56.3, - 56.1, - 56.1, - 56.1, - 56.1, - 56.4, - 56.3, - 56.3, - 56.3, - 56.5, - 56.6, - 56.6, - 56.6, - 56.7, - 56.8, - 56.8, - 56.6, - 55.9, - 55.5, - 55.4, - 55.2, - 54.7, - 54.4, - 54.3, - 54.2, - 54, - 53.7, - 53.5, - 53.4, - 53.3, - 53.2, - 53, - 52.8, - 52.8, - 52.4, - 51.9, - 51.5, - 51.1, - 50.6, - 50.1, - 49.5, - 49.2, - 49, - 49, - 48.9, - 48.9, - 48.5, - 48.1, - 47.9, - 47.8, - 47.4, - 47.1, - 46.8, - 46.7, - 46.2, - 45.9, - 45.8, - 45.7, - 45.6, - 45.3, - 44.9, - 44.8, - 44.7, - 44.5, - 44.6, - 44.6, - 44.4, - 44.4, - 43.9, - 43.6, - 43.3, - 43, - 43, - 42.9, - 42.4, - 41.7, - 41.1, - 40.6, - 40, - 39.6, - 39.1, - 38.7, - 38.6, - 38.2, - 37.6, - 37, - 36.5, - 35.9, - 35.1, - 35.1, - 35.1, - 35.1, - 35, - 35, - 34.5, - 33.9, - 33.2, - 32.5, - 32.2, - 31.9, - 31.3, - 30.7, - 29.6, - 27.7, - 26.2, - 25.3, - 24.5, - 23.8, - 23.1, - 22.4, - 21.9, - 21.3, - 21, - 20.3, - 19.7, - 19.1, - 18.4, - 17.4, - 16, - 15.3, - 14.4, - 13.1, - 11.2, - 9.4, - 7.7, - 5.7, - 4.5, - 3.4, - 3, - 2.3, - 1.6, - 1.2, - 1.2, - 1.3, - 0.8, - 0.6, - 0.5, - 0.5, - 0.5, - 0.3, - 0.2, - 0.1, - 0.2, - 0.2, - -0.1, - -0.3, - -0.3, - -0.5, - -0.5, - -0.5, - -0.6, - -0.6, - -0.7, - -0.7, - -0.7, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - -0.8, - 58.4, - 58.4, - 57.8, - 57.4, - 57.3, - 57.3, - 57, - 56.7, - 56.4, - 56.1, - 56.1, - 56, - 55.9, - 56, - 56.1, - 56, - 56, - 56.2, - 56.4, - 56.4, - 56.6, - 56.7, - 56.8, - 56.6, - 56.4, - 56, - 55.6, - 55.4, - 55.2, - 54.8, - 54.6, - 54.7, - 54.5, - 54.2, - 53.9, - 53.7, - 53.5, - 53.4, - 53.3, - 53, - 52.9, - 52.7, - 52.3, - 51.8, - 51.4, - 50.9, - 50.5, - 50, - 49.5, - 49.2, - 49.1, - 49.1, - 48.9, - 48.8, - 48.4, - 48.1, - 47.7, - 47.6, - 47.3, - 47, - 46.8, - 46.7, - 46.4, - 46, - 45.8, - 45.8, - 45.6, - 45.4, - 44.9, - 44.7, - 44.5, - 44.4, - 44.5, - 44.6, - 44.6, - 44.3, - 43.9, - 43.7, - 43.3, - 43.1, - 43.2, - 43.1, - 42.6, - 41.9, - 41, - 40.5, - 40, - 39.5, - 39.2, - 39.1, - 39, - 38.5, - 37.7, - 37.3, - 36.8, - 36.1, - 35.4, - 35.1, - 35.1, - 35.1, - 35.1, - 35, - 34.7, - 33.9, - 33.1, - 32.6, - 32.3, - 32, - 31.3, - 31, - 30.3, - 28.7, - 27.3, - 26.3, - 25.4, - 24.6, - 23.8, - 22.6, - 22, - 21.4, - 20.9, - 20.3, - 19.7, - 19.2, - 18.5, - 17.7, - 16.5, - 15.5, - 14.6, - 13.4, - 12.1, - 10.5, - 8.9, - 6.9, - 4.9, - 3.3, - 2.9, - 2.2, - 1.6, - 1.2, - 1, - 1, - 0.6, - 0.4, - 0.5, - 0.6, - 0.9, - 0.5, - 0, - 0, - 0, - 0, - -0.1, - -0.2, - -0.4, - -0.5, - -0.6, - -0.6, - -0.7, - -0.7, - -0.8, - -0.8, - -0.8, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - -0.9, - 58.1, - 58.1, - 57.7, - 57.4, - 57.3, - 57.2, - 57, - 56.8, - 55.9, - 55.8, - 55.8, - 55.7, - 55.6, - 55.7, - 55.8, - 55.9, - 55.9, - 55.9, - 56.2, - 56.4, - 56.7, - 56.8, - 56.8, - 56.5, - 56.3, - 56, - 55.7, - 55.5, - 55.3, - 55, - 54.8, - 54.7, - 54.5, - 54.2, - 53.9, - 53.7, - 53.5, - 53.4, - 53.5, - 53, - 52.9, - 52.6, - 52.2, - 51.8, - 51.3, - 50.8, - 50.3, - 49.9, - 49.6, - 49.4, - 49.2, - 49, - 48.9, - 48.8, - 48.3, - 47.8, - 47.4, - 47.3, - 47.1, - 47, - 46.8, - 46.6, - 46.3, - 46.1, - 46.1, - 46.2, - 45.8, - 45.2, - 44.7, - 44.6, - 44.6, - 44.4, - 44.4, - 44.6, - 44.6, - 44.3, - 43.9, - 43.6, - 43.3, - 43.1, - 43.1, - 43, - 42.5, - 41.8, - 41, - 40.4, - 39.8, - 39.4, - 39.3, - 39.3, - 39.1, - 38.6, - 38, - 37.7, - 37.1, - 36.6, - 35.7, - 35.1, - 35.1, - 35.1, - 35.1, - 35, - 34.8, - 33.7, - 33.1, - 32.8, - 32.5, - 32.3, - 31.5, - 30.9, - 30.3, - 29.2, - 28, - 27, - 26.2, - 25.2, - 24.1, - 23, - 22.1, - 21.4, - 20.8, - 20.3, - 19.5, - 19, - 18.5, - 17.8, - 17, - 16, - 14.9, - 13.9, - 12.8, - 11.5, - 10, - 8.5, - 6.6, - 4, - 2.7, - 2, - 1.5, - 0.9, - 0.6, - 0.6, - 0.5, - 0.4, - 0.6, - 0.7, - 0.6, - 0.5, - 0.2, - 0, - -0.1, - -0.1, - -0.1, - -0.1, - -0.3, - -0.6, - -0.7, - -0.7, - -0.8, - -0.8, - -0.9, - -0.9, - -0.9, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - -1, - 57.8, - 57.9, - 57.7, - 57.4, - 57.4, - 57.5, - 56.9, - 56.3, - 55.8, - 55.6, - 55.7, - 55.7, - 55.6, - 55.6, - 55.7, - 55.7, - 55.7, - 55.7, - 56, - 56.2, - 56.5, - 56.7, - 56.7, - 56.6, - 56.2, - 56, - 55.8, - 55.5, - 55.3, - 55.1, - 54.9, - 54.7, - 54.3, - 54.1, - 54, - 53.7, - 53.5, - 53.4, - 53.4, - 52.9, - 52.7, - 52.6, - 52.1, - 51.7, - 51.2, - 50.7, - 50.3, - 49.9, - 49.8, - 49.7, - 49.5, - 49.2, - 48.9, - 48.8, - 48.4, - 47.8, - 47.3, - 46.9, - 46.8, - 46.7, - 46.7, - 46.5, - 46.1, - 46, - 45.9, - 45.9, - 45.6, - 45.2, - 44.7, - 44.5, - 44.4, - 44.4, - 44.6, - 44.9, - 44.7, - 44.5, - 44.1, - 43.6, - 43.3, - 43, - 42.9, - 42.8, - 42.3, - 41.6, - 40.9, - 40.5, - 40, - 39.4, - 39.2, - 39.1, - 38.9, - 38.6, - 38.3, - 38, - 37.4, - 37, - 36.2, - 35.1, - 35, - 35.1, - 35.1, - 35, - 34.9, - 34.1, - 33.3, - 33, - 32.9, - 32.6, - 31.8, - 31, - 30.4, - 29.3, - 28.4, - 27.5, - 26.7, - 25.8, - 24.5, - 23.1, - 22.1, - 21.4, - 20.7, - 20, - 19.3, - 18.7, - 18.2, - 17.7, - 17.1, - 16.3, - 15.2, - 14.2, - 13.4, - 12.4, - 11.2, - 10.3, - 8.2, - 5.8, - 3.3, - 2.1, - 1.1, - 0.7, - 0.3, - 0.2, - 0.3, - 0.2, - 0.4, - 0.4, - 0.4, - 0.4, - 0.3, - 0.2, - -0.1, - -0.2, - -0.1, - -0.3, - -0.4, - -0.7, - -0.7, - -0.8, - -0.9, - -0.9, - -1, - -1, - -1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - -1.1, - 57.1, - 57.5, - 57.7, - 57.7, - 57.6, - 57.6, - 56.9, - 56.2, - 55.7, - 55.3, - 55.4, - 55.8, - 55.6, - 55.4, - 55.5, - 55.6, - 55.6, - 55.7, - 55.9, - 56, - 56.4, - 56.5, - 56.6, - 56.5, - 56.2, - 56, - 55.9, - 55.6, - 55.3, - 55.1, - 55.1, - 54.7, - 54.2, - 54.1, - 53.9, - 53.8, - 53.5, - 53.4, - 53.3, - 52.7, - 52.5, - 52.4, - 52, - 51.6, - 51.2, - 50.8, - 50.4, - 50, - 49.9, - 49.9, - 49.7, - 49.3, - 49, - 49, - 48.6, - 47.8, - 47.4, - 46.9, - 46.5, - 46.4, - 46.4, - 46.2, - 46, - 45.8, - 45.6, - 45.4, - 45.3, - 45, - 44.7, - 44.4, - 44.3, - 44.4, - 44.6, - 45, - 44.7, - 44.5, - 44.3, - 43.7, - 43.4, - 42.9, - 42.8, - 42.6, - 42.1, - 41.5, - 41.1, - 40.6, - 40.1, - 39.6, - 39.1, - 39, - 38.9, - 38.9, - 38.6, - 38.2, - 37.7, - 37.2, - 36.7, - 35.2, - 35, - 35, - 35, - 35, - 34.9, - 34.7, - 34.1, - 33.7, - 33.4, - 32.9, - 32, - 31.1, - 30.6, - 29.6, - 28.7, - 27.7, - 27, - 26.3, - 25.5, - 23.6, - 22.3, - 21.6, - 20.6, - 19.8, - 19, - 18.5, - 18.1, - 17.7, - 17.2, - 16.4, - 15.6, - 14.5, - 13.6, - 13, - 12.2, - 11.1, - 9.5, - 7.7, - 6.1, - 4.4, - 1.6, - 0.5, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.3, - 0.3, - 0.2, - 0.2, - 0.2, - 0, - -0.3, - -0.2, - -0.4, - -0.4, - -0.7, - -0.9, - -0.9, - -1, - -1, - -1.1, - -1.1, - -1.1, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - -1.2, - 56.7, - 57.2, - 57.5, - 57.5, - 57.5, - 57, - 56.6, - 56.2, - 55.6, - 55.1, - 55.2, - 55.5, - 55.5, - 55.4, - 55.2, - 55, - 55.3, - 55.6, - 55.8, - 55.8, - 56.2, - 56.4, - 56.3, - 56.3, - 56.1, - 55.9, - 55.9, - 55.7, - 55.4, - 55.1, - 55, - 54.7, - 54.4, - 54.3, - 54.2, - 54, - 53.7, - 53.4, - 53.3, - 52.7, - 52.3, - 52.2, - 52, - 51.6, - 51.2, - 50.8, - 50.4, - 50.1, - 50, - 50.1, - 49.9, - 49.5, - 49.2, - 49.1, - 48.7, - 47.9, - 47.5, - 47.1, - 46.5, - 46.4, - 46.2, - 46, - 45.8, - 45.5, - 45.4, - 45.2, - 45, - 44.8, - 44.7, - 44.4, - 44.5, - 44.4, - 44.5, - 44.9, - 44.7, - 44.6, - 44.3, - 43.8, - 43.4, - 43, - 43, - 42.6, - 41.9, - 41.6, - 41.2, - 40.7, - 40.1, - 39.6, - 39.3, - 39.2, - 39.1, - 39.2, - 39.1, - 38.6, - 38.1, - 37.5, - 37, - 35.4, - 34.9, - 35, - 35, - 35, - 34.9, - 34.8, - 34.6, - 34.4, - 33.9, - 33.1, - 32, - 31.1, - 30.7, - 29.9, - 29, - 28.1, - 27.4, - 26.8, - 26, - 24.1, - 22.6, - 21.6, - 20.6, - 20, - 19.2, - 18.7, - 18.3, - 17.8, - 17.4, - 16.5, - 15.4, - 14.6, - 13.7, - 13.3, - 12.6, - 11.7, - 10.4, - 8.9, - 7.6, - 5.7, - 2.5, - 0.5, - 0.2, - 0, - 0.1, - 0, - 0, - 0.1, - 0.3, - 0.2, - 0, - -0.1, - 0, - -0.2, - -0.4, - -0.5, - -0.6, - -0.8, - -1, - -1.1, - -1.1, - -1.1, - -1.2, - -1.2, - -1.2, - -1.2, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.6, - -1.6, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - -1.3, - 56.5, - 56.9, - 57.2, - 57.3, - 57.3, - 57.2, - 56.6, - 56, - 55.4, - 55.1, - 55, - 55.2, - 55.3, - 55.1, - 54.9, - 54.8, - 54.9, - 55.4, - 55.7, - 55.7, - 55.9, - 56.2, - 56.2, - 56.2, - 56.1, - 56, - 55.8, - 55.7, - 55.5, - 55.2, - 55.1, - 55, - 54.7, - 54.5, - 54.3, - 54.1, - 53.8, - 53.5, - 53.3, - 52.5, - 52.3, - 52.1, - 51.8, - 51.5, - 51.2, - 50.9, - 50.5, - 50.2, - 50.1, - 50.2, - 50, - 49.7, - 49.3, - 49.1, - 48.8, - 48.3, - 47.4, - 47, - 46.6, - 46.3, - 45.9, - 45.7, - 45.6, - 45.4, - 45.4, - 45.2, - 44.9, - 44.6, - 44.6, - 44.3, - 44.4, - 44.3, - 44.4, - 44.7, - 44.6, - 44.6, - 44.2, - 43.8, - 43.3, - 43.1, - 43, - 42.6, - 41.9, - 41.5, - 40.8, - 40.4, - 40.1, - 39.7, - 39.5, - 39.2, - 39.3, - 39.5, - 39.8, - 39.7, - 39, - 38.1, - 37.3, - 36.1, - 34.9, - 35, - 35, - 35, - 34.9, - 34.8, - 34.6, - 34.5, - 34.1, - 33.2, - 32, - 31.3, - 30.8, - 30.2, - 29.4, - 28.5, - 27.7, - 27, - 26.1, - 24.2, - 23, - 22, - 21, - 20.2, - 19.5, - 19.1, - 18.4, - 17.8, - 17.3, - 16.6, - 15.2, - 14.4, - 13.7, - 13.3, - 12.8, - 12, - 10.7, - 9.2, - 8.1, - 6.6, - 3.6, - 0.7, - 0.2, - 0.1, - 0.1, - 0.1, - 0.2, - 0.1, - 0.1, - 0, - -0.1, - -0.1, - 0, - -0.4, - -0.5, - -0.7, - -0.9, - -1, - -1.1, - -1.2, - -1.2, - -1.2, - -1.2, - -1.3, - -1.3, - -1.3, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.5, - -1.6, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - -1.4, - 56.4, - 56.8, - 57.1, - 57.1, - 57.1, - 57, - 56.3, - 55.8, - 55.3, - 55, - 54.8, - 54.8, - 54.9, - 54.7, - 54.7, - 54.6, - 54.9, - 55.2, - 55.5, - 55.6, - 55.8, - 56, - 56.1, - 56.2, - 56.2, - 56.1, - 55.9, - 55.7, - 55.5, - 55.3, - 55.2, - 55.1, - 54.9, - 54.6, - 54.3, - 54.1, - 53.8, - 53.6, - 53.1, - 52.5, - 52.3, - 52.1, - 51.8, - 51.5, - 51.3, - 50.9, - 50.5, - 50.2, - 50.1, - 50.1, - 49.8, - 49.6, - 49.3, - 49.1, - 48.9, - 48.4, - 47.4, - 47.1, - 46.5, - 46.3, - 45.9, - 45.7, - 45.5, - 45.2, - 45.2, - 45.3, - 44.8, - 44.5, - 44.4, - 44.2, - 44.2, - 44.2, - 44.4, - 44.6, - 44.6, - 44.4, - 44.2, - 43.8, - 43.3, - 43.1, - 42.9, - 42.6, - 41.9, - 41.5, - 41, - 40.6, - 40.2, - 39.8, - 39.6, - 39.3, - 39.5, - 39.6, - 40.1, - 40.2, - 40, - 39.1, - 37.4, - 36.5, - 35.1, - 34.9, - 34.9, - 35, - 34.9, - 34.9, - 34.6, - 34.3, - 34, - 33.2, - 32.1, - 31.4, - 30.9, - 30.4, - 29.6, - 28.7, - 27.9, - 27, - 26.3, - 25.2, - 23.7, - 22.5, - 21.4, - 20.7, - 19.9, - 19.2, - 18.5, - 17.7, - 17.1, - 16.4, - 15.3, - 14.5, - 13.7, - 13.4, - 12.9, - 12.2, - 11.1, - 9.7, - 8.2, - 6.7, - 4.2, - 1.1, - 0.4, - 0.3, - 0.2, - 0, - 0.2, - 0, - -0.2, - -0.3, - -0.2, - -0.2, - -0.4, - -0.6, - -0.7, - -0.9, - -1, - -1, - -1.1, - -1.3, - -1.3, - -1.3, - -1.3, - -1.4, - -1.4, - -1.4, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.5, - -1.6, - -1.6, - -1.6, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - -1.5, - 56.4, - 56.8, - 57, - 57.1, - 56.9, - 56.6, - 56.2, - 55.8, - 55.2, - 54.9, - 54.5, - 54.4, - 54.4, - 54.4, - 54.5, - 54.6, - 54.9, - 55.1, - 55.4, - 55.4, - 55.7, - 56, - 56.1, - 56.3, - 56.3, - 56.2, - 56, - 55.7, - 55.5, - 55.3, - 55.3, - 55.1, - 55, - 54.7, - 54.4, - 54.2, - 53.9, - 53.7, - 53.2, - 52.7, - 52.2, - 52, - 51.6, - 51.2, - 51.1, - 50.9, - 50.4, - 50.1, - 49.9, - 49.8, - 49.7, - 49.4, - 49.3, - 49.1, - 48.8, - 48.3, - 47.4, - 47, - 46.6, - 46.3, - 45.9, - 45.7, - 45.4, - 45.2, - 45.1, - 45, - 44.6, - 44.4, - 44.3, - 44.1, - 44.2, - 44.2, - 44.4, - 44.7, - 44.8, - 44.4, - 44.1, - 43.8, - 43.3, - 43.1, - 43, - 42.6, - 42.1, - 41.8, - 41.4, - 40.8, - 40.1, - 39.9, - 39.6, - 39.4, - 39.6, - 39.7, - 40, - 40.3, - 40.3, - 39.9, - 38.1, - 36.3, - 35.2, - 34.8, - 34.9, - 34.9, - 34.9, - 35, - 34.7, - 34.3, - 33.9, - 33.5, - 32.5, - 31.7, - 31.2, - 30.7, - 29.8, - 28.7, - 27.8, - 27, - 26.3, - 25.1, - 23.8, - 22.6, - 21.6, - 20.8, - 19.9, - 19.2, - 18.3, - 17.6, - 16.8, - 16, - 15.1, - 14.3, - 13.7, - 13.4, - 13, - 12.4, - 11.4, - 10, - 8.3, - 6.8, - 5, - 1.9, - 0.6, - 0.5, - 0.5, - 0.3, - 0, - 0, - -0.2, - -0.3, - -0.4, - -0.6, - -0.7, - -0.8, - -0.9, - -1, - -1.2, - -1.2, - -1.2, - -1.3, - -1.4, - -1.4, - -1.4, - -1.5, - -1.5, - -1.5, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.5, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - 56.3, - 56.7, - 57, - 57.1, - 57, - 56.5, - 56.2, - 55.7, - 55.1, - 54.7, - 54.5, - 54.4, - 54.4, - 54.4, - 54.3, - 54.6, - 54.9, - 54.9, - 55.2, - 55.4, - 55.6, - 55.9, - 56.2, - 56.3, - 56.2, - 56.1, - 55.9, - 55.6, - 55.4, - 55.3, - 55.2, - 55, - 54.9, - 54.8, - 54.5, - 54.2, - 53.9, - 53.7, - 53.3, - 52.7, - 52.1, - 51.7, - 51.4, - 50.9, - 50.7, - 50.5, - 50.3, - 50.1, - 49.7, - 49.6, - 49.5, - 49.3, - 49.3, - 49.1, - 48.8, - 48.3, - 47.6, - 47.1, - 46.7, - 46.2, - 45.9, - 45.7, - 45.4, - 45.2, - 45, - 44.9, - 44.7, - 44.5, - 44.5, - 44.3, - 44.2, - 44.3, - 44.5, - 44.8, - 44.8, - 44.3, - 44.1, - 43.7, - 43.3, - 43.1, - 43, - 42.7, - 42.6, - 42.1, - 41.4, - 40.6, - 40.2, - 39.9, - 39.5, - 39.4, - 39.6, - 39.7, - 39.7, - 39.8, - 40.1, - 40.2, - 38.6, - 36.8, - 36, - 34.8, - 34.8, - 34.9, - 34.9, - 34.9, - 34.7, - 34.4, - 33.9, - 33.5, - 33, - 32.1, - 31.5, - 31, - 30.1, - 28.9, - 27.8, - 27, - 26.2, - 25.2, - 23.8, - 22.6, - 21.7, - 20.7, - 19.7, - 18.9, - 18.1, - 17.3, - 16.4, - 15.5, - 14.8, - 14.1, - 13.7, - 13.3, - 13, - 12.5, - 11.4, - 10, - 8.5, - 6.9, - 6.1, - 4.5, - 2.7, - 1.8, - 1.2, - 0.4, - 0, - 0, - -0.1, - -0.2, - -0.3, - -0.5, - -0.8, - -1, - -1, - -1.1, - -1.2, - -1.3, - -1.3, - -1.4, - -1.4, - -1.5, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.6, - -1.7, - -1.7, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -2, - -2.2, - -2.4, - -2.5, - -2.3, - -2.3, - -2.1, - -2.1, - -2, - -1.9, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.7, - -1.7, - -1.7, - -1.8, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.6, - -1.6, - -1.6, - 56, - 56.3, - 56.7, - 56.9, - 56.9, - 56.5, - 56.1, - 55.4, - 54.9, - 54.6, - 54.5, - 54.3, - 54.2, - 54.2, - 54.1, - 54.5, - 54.8, - 54.8, - 55, - 55.2, - 55.4, - 55.5, - 56, - 56, - 56, - 55.9, - 55.9, - 55.6, - 55.3, - 55.2, - 55.2, - 55.1, - 54.9, - 54.7, - 54.5, - 54.2, - 53.9, - 53.6, - 53.2, - 52.8, - 52, - 51.7, - 51.4, - 51, - 50.7, - 50.4, - 50.2, - 49.9, - 49.5, - 49.4, - 49.3, - 49.2, - 49.3, - 49.1, - 48.9, - 48.5, - 48, - 47.2, - 46.6, - 46.3, - 46, - 45.7, - 45.3, - 45.2, - 45.2, - 45, - 44.9, - 44.8, - 44.8, - 44.6, - 44.3, - 44.4, - 44.5, - 44.7, - 44.5, - 44.3, - 44, - 43.7, - 43.5, - 43.2, - 43.1, - 42.9, - 42.8, - 42.3, - 41.4, - 40.6, - 40.1, - 39.8, - 39.6, - 39.7, - 39.7, - 39.6, - 39.6, - 39.6, - 39.6, - 39.7, - 38.7, - 36.9, - 36.3, - 34.9, - 34.7, - 34.8, - 34.8, - 34.8, - 34.7, - 34.5, - 34, - 33.4, - 33.1, - 32.4, - 31.6, - 31.1, - 30.5, - 29.1, - 28, - 27, - 26.2, - 25.3, - 23.7, - 22.7, - 21.8, - 20.7, - 19.6, - 18.6, - 17.7, - 17.1, - 16.1, - 15.2, - 14.6, - 14, - 13.6, - 13.2, - 12.9, - 12.4, - 11, - 9.7, - 8.4, - 7, - 6.3, - 5.2, - 3.8, - 3.2, - 2.1, - 0.5, - -0.1, - -0.1, - -0.1, - -0.1, - -0.3, - -0.5, - -0.7, - -1, - -1.2, - -1.1, - -1.2, - -1.3, - -1.5, - -1.6, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -1.9, - -2.3, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.5, - -2.5, - -2.5, - -2.4, - -2.4, - -2.4, - -2.3, - -2.3, - -2.3, - -2.3, - -2.2, - -2.2, - -2.2, - -2.3, - -2.3, - -2.3, - -2.3, - -2.4, - -2.4, - -2.4, - -2.4, - -2.4, - -2.4, - -2.4, - -2.4, - -2.3, - -2.4, - -2.4, - -2.3, - -2.2, - -2.2, - -2.2, - -2.2, - -2.2, - -2.1, - -2, - -2, - -2, - -1.9, - -1.8, - -1.7, - -1.7, - -1.7, - 55.8, - 55.8, - 56.2, - 56.7, - 56.7, - 56.5, - 56, - 54.9, - 54.6, - 54.3, - 54.3, - 54.2, - 54.1, - 54.1, - 54, - 54.5, - 54.7, - 54.8, - 54.9, - 55, - 55.2, - 55.3, - 55.6, - 55.8, - 55.7, - 55.8, - 55.8, - 55.6, - 55.3, - 55.3, - 55.4, - 55.3, - 55.1, - 54.8, - 54.6, - 54.3, - 54, - 53.5, - 53, - 52.6, - 52, - 51.6, - 51.3, - 50.9, - 50.6, - 50.3, - 50.1, - 49.8, - 49.5, - 49.3, - 49.2, - 49, - 48.9, - 48.9, - 48.7, - 48.2, - 47.8, - 47.2, - 46.7, - 46.3, - 46, - 45.6, - 45.3, - 45.3, - 45.3, - 45.2, - 45, - 45.1, - 44.9, - 44.5, - 44.2, - 44.2, - 44.4, - 44.6, - 44.4, - 44.2, - 44, - 43.7, - 43.6, - 43.4, - 43.2, - 43.1, - 42.9, - 42.5, - 41.5, - 40.7, - 40, - 39.9, - 40, - 40, - 40.1, - 39.7, - 39.5, - 39.6, - 39.3, - 39.2, - 38.6, - 37, - 36.4, - 35.1, - 34.6, - 34.7, - 34.8, - 34.8, - 34.7, - 34.5, - 34.1, - 33.4, - 32.9, - 32.6, - 31.8, - 31.2, - 30.5, - 29.4, - 28.1, - 27.3, - 26.2, - 25.2, - 23.8, - 22.6, - 21.9, - 20.9, - 19.4, - 18.4, - 17.4, - 16.7, - 15.9, - 14.9, - 14.3, - 13.9, - 13.5, - 13.1, - 12.6, - 11.5, - 10.2, - 9.2, - 8, - 6.9, - 6.1, - 5, - 4, - 3.3, - 1.8, - 0.6, - 0.1, - -0.2, - -0.1, - -0.1, - -0.3, - -0.6, - -0.8, - -1, - -1.2, - -1.2, - -1.3, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.9, - -2.1, - -2.5, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.4, - -2.3, - -2, - 55.6, - 55.6, - 55.9, - 56.4, - 56.6, - 56.5, - 55.8, - 54.3, - 54.1, - 54, - 53.8, - 53.9, - 54, - 54, - 53.8, - 54.5, - 54.8, - 54.7, - 54.8, - 55, - 55.2, - 55.2, - 55.5, - 55.7, - 55.6, - 55.6, - 55.7, - 55.5, - 55.2, - 55.3, - 55.5, - 55.5, - 55.3, - 54.9, - 54.6, - 54.2, - 53.9, - 53.5, - 53.1, - 52.5, - 52, - 51.5, - 51.1, - 50.8, - 50.5, - 50.3, - 50.1, - 49.8, - 49.4, - 49.1, - 49, - 48.9, - 48.7, - 48.6, - 48.5, - 48.1, - 47.6, - 47.2, - 46.5, - 46.1, - 45.7, - 45.5, - 45.3, - 45.3, - 45.3, - 45.4, - 45.2, - 45, - 44.8, - 44.3, - 44.1, - 44.3, - 44.5, - 44.6, - 44.4, - 44.2, - 43.8, - 43.6, - 43.6, - 43.4, - 43.2, - 43.2, - 42.9, - 42.7, - 41.6, - 40.5, - 40, - 40.1, - 40.1, - 40.1, - 40.3, - 39.6, - 39.3, - 39.4, - 39.2, - 38.9, - 38.3, - 36.9, - 36.5, - 35.5, - 34.6, - 34.6, - 34.7, - 34.7, - 34.7, - 34.5, - 34.2, - 33.5, - 32.9, - 32.6, - 32.1, - 31.3, - 30.6, - 29.8, - 28.6, - 27.6, - 26.5, - 25.2, - 24, - 22.7, - 21.9, - 20.8, - 19.5, - 18.2, - 17.1, - 16.4, - 15.6, - 14.8, - 14.3, - 13.8, - 13.4, - 13, - 12, - 10.9, - 9.6, - 8.8, - 7.9, - 6.9, - 5.9, - 4.9, - 3.9, - 2.9, - 1.9, - 0.8, - 0, - -0.3, - -0.1, - -0.3, - -0.4, - -0.8, - -0.9, - -1, - -1.3, - -1.3, - -1.4, - -1.4, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -2, - -2.1, - -2.2, - -2.3, - -2.4, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 55.4, - 55.5, - 55.5, - 55.9, - 56.1, - 56, - 54.9, - 54, - 53.8, - 53.6, - 53.5, - 53.6, - 53.8, - 53.8, - 53.7, - 54.3, - 54.6, - 54.7, - 54.7, - 54.8, - 55.1, - 55.2, - 55.4, - 55.6, - 55.5, - 55.4, - 55.5, - 55.4, - 55.1, - 55.3, - 55.4, - 55.4, - 55.2, - 54.8, - 54.3, - 54, - 53.7, - 53.3, - 53, - 52.5, - 51.9, - 51.5, - 51.1, - 50.7, - 50.5, - 50.4, - 50.2, - 49.8, - 49.3, - 49, - 48.9, - 48.8, - 48.5, - 48.4, - 48.3, - 48.1, - 47.8, - 47.4, - 46.5, - 45.9, - 45.5, - 45.3, - 45.3, - 45.3, - 45.4, - 45.4, - 45.2, - 45.1, - 44.9, - 44.4, - 44.3, - 44.5, - 44.6, - 44.5, - 44.3, - 44.1, - 43.8, - 43.6, - 43.6, - 43.3, - 43.2, - 43.2, - 43.1, - 42.9, - 41.7, - 40.4, - 40.1, - 40, - 40.1, - 40.4, - 40.3, - 39.8, - 39.3, - 39.1, - 38.9, - 38.6, - 37.9, - 36.8, - 36.6, - 36.1, - 34.7, - 34.5, - 34.6, - 34.6, - 34.6, - 34.5, - 34.2, - 33.6, - 33.1, - 32.6, - 32.2, - 31.4, - 30.6, - 29.8, - 29.1, - 28.2, - 27, - 25.7, - 24.1, - 23, - 22, - 20.5, - 19.4, - 18.2, - 17.3, - 16.5, - 15.6, - 14.9, - 14.3, - 13.9, - 13.4, - 13.1, - 12.2, - 10.9, - 10.2, - 9.5, - 8.5, - 7.4, - 6.1, - 4.9, - 3.8, - 2.7, - 1.6, - 0.6, - -0.3, - -0.4, - -0.3, - -0.7, - -0.8, - -1.1, - -1.1, - -1.2, - -1.1, - -1.3, - -1.5, - -1.5, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.7, - -1.8, - -1.9, - -2, - -2.3, - -2.3, - -2.4, - -2.5, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 55.3, - 55.4, - 55.4, - 55.6, - 55.3, - 54.8, - 54.4, - 53.9, - 53.6, - 53.4, - 53.3, - 53.3, - 53.3, - 53.5, - 53.7, - 54.1, - 54.6, - 54.7, - 54.7, - 54.7, - 54.9, - 55, - 55.2, - 55.4, - 55.3, - 55.3, - 55.4, - 55.4, - 55.1, - 55.1, - 55.1, - 55.2, - 55, - 54.4, - 53.9, - 53.8, - 53.4, - 53, - 52.8, - 52.4, - 52.1, - 51.6, - 51.1, - 50.7, - 50.5, - 50.4, - 50.1, - 49.7, - 49.1, - 48.8, - 48.7, - 48.6, - 48.3, - 48.2, - 47.9, - 47.8, - 47.7, - 47.4, - 46.6, - 46, - 45.4, - 45.3, - 45.3, - 45.3, - 45.5, - 45.7, - 45.4, - 45.3, - 45.1, - 44.6, - 44.2, - 44.6, - 44.7, - 44.5, - 44.2, - 44, - 43.9, - 43.6, - 43.6, - 43.4, - 43.3, - 43.2, - 43.2, - 43, - 41.9, - 40.5, - 40, - 40.2, - 40.7, - 40.8, - 40.6, - 40.1, - 39.3, - 39, - 38.7, - 38.3, - 37.7, - 36.8, - 36.5, - 36.3, - 35.2, - 34.4, - 34.5, - 34.5, - 34.6, - 34.5, - 34.3, - 33.8, - 33.3, - 32.6, - 32.1, - 31.6, - 30.6, - 30, - 29.3, - 28.2, - 27.5, - 26.3, - 24.3, - 23.3, - 21.8, - 20.6, - 19.6, - 18.5, - 17.4, - 16.9, - 16.4, - 15.6, - 15, - 14.3, - 13.5, - 13.1, - 12.7, - 11.7, - 11.1, - 10.4, - 9, - 7.7, - 6.5, - 4.8, - 3.5, - 2.2, - 1.2, - 0.4, - -0.6, - -0.9, - -1, - -0.9, - -1, - -1.2, - -1.1, - -1, - -0.9, - -1.4, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.9, - -2.1, - -2.2, - -2.4, - -2.5, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 55, - 55, - 55.3, - 55.3, - 55, - 54.6, - 54.4, - 53.8, - 53.3, - 53.1, - 52.9, - 53, - 53.1, - 53.2, - 53.5, - 53.7, - 54.5, - 54.7, - 54.6, - 54.4, - 54.7, - 54.8, - 55, - 55.1, - 55.1, - 55.1, - 55.3, - 55.4, - 55.1, - 55, - 55.1, - 55, - 54.5, - 54.1, - 53.8, - 53.6, - 53.2, - 52.9, - 52.8, - 52.5, - 52.1, - 51.7, - 51.1, - 50.6, - 50.4, - 50.2, - 49.9, - 49.4, - 49.1, - 48.8, - 48.6, - 48.4, - 48.2, - 48, - 47.6, - 47.2, - 47.1, - 47.1, - 46.6, - 46, - 45.4, - 45.2, - 45.2, - 45.2, - 45.3, - 45.5, - 45.3, - 45.3, - 45.2, - 44.6, - 44.2, - 44.4, - 44.7, - 44.4, - 44.2, - 44.2, - 44, - 43.8, - 43.6, - 43.4, - 43.4, - 43.2, - 43.1, - 42.9, - 41.6, - 40.5, - 40.2, - 40.6, - 41.4, - 41.6, - 41.1, - 40.4, - 39.7, - 39.1, - 38.7, - 38.1, - 37.7, - 36.8, - 36.5, - 36.3, - 35.7, - 34.4, - 34.4, - 34.5, - 34.5, - 34.4, - 34.3, - 33.9, - 33.4, - 32.7, - 32.1, - 31.8, - 31, - 30.2, - 29.5, - 28.7, - 27.7, - 26.8, - 25.1, - 23.4, - 22.2, - 20.8, - 19.5, - 18.8, - 18, - 17.2, - 16.6, - 15.9, - 15.2, - 14.5, - 13.7, - 13.3, - 13, - 12.3, - 11.4, - 10.7, - 9.8, - 8.2, - 6.7, - 4.5, - 3.2, - 1.8, - 0.7, - -0.1, - -0.8, - -1.2, - -1.2, - -1, - -1.1, - -1.3, - -1.3, - -1.3, - -1.3, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -1.8, - -2, - -2.1, - -2.2, - -2.4, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 54.8, - 54.8, - 54.9, - 55, - 54.9, - 54.6, - 54.3, - 53.7, - 53.1, - 52.9, - 52.6, - 52.6, - 52.8, - 52.9, - 53.1, - 53.4, - 54.3, - 54.5, - 54.4, - 54.1, - 54.4, - 54.7, - 54.8, - 54.9, - 54.9, - 55, - 55.1, - 55.2, - 55, - 54.9, - 55, - 54.7, - 54.4, - 54.1, - 53.8, - 53.5, - 53.1, - 52.8, - 52.6, - 52.4, - 52, - 51.5, - 51, - 50.5, - 50.3, - 50.1, - 49.6, - 49.4, - 49.2, - 48.9, - 48.5, - 48.2, - 48.1, - 47.9, - 47.4, - 47.1, - 46.9, - 46.8, - 46.4, - 45.9, - 45.4, - 45.2, - 45.1, - 45, - 45.1, - 45.2, - 45.3, - 45.4, - 45.1, - 44.6, - 44.1, - 44.2, - 44.6, - 44.4, - 44.6, - 44.8, - 44.5, - 44.1, - 43.8, - 43.4, - 43.3, - 43.2, - 43, - 42.7, - 41.7, - 40.8, - 40.4, - 41.2, - 41.9, - 42, - 41.4, - 40.5, - 39.8, - 39.2, - 38.8, - 38.1, - 37.5, - 36.8, - 36.3, - 36.2, - 35.6, - 34.3, - 34.3, - 34.4, - 34.4, - 34.4, - 34.3, - 34, - 33.5, - 32.8, - 32.2, - 31.8, - 31.2, - 30.1, - 29.5, - 28.8, - 27.9, - 27.2, - 25.4, - 23.8, - 22.5, - 21.2, - 20.1, - 19.2, - 18.3, - 17.6, - 16.8, - 16.1, - 15.3, - 14.5, - 13.8, - 13.6, - 13.3, - 12.6, - 11.7, - 11, - 10, - 8.6, - 6.7, - 5.4, - 3.4, - 1.2, - 0.1, - -0.7, - -1.1, - -1.4, - -1.4, - -1.3, - -1.3, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -1.8, - -1.8, - -1.8, - -1.9, - -2.1, - -2.3, - -2.4, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 54.9, - 54.7, - 54.6, - 54.5, - 54.5, - 54.2, - 53.8, - 53.3, - 52.9, - 52.6, - 52.4, - 52.3, - 52.5, - 52.7, - 52.8, - 53.1, - 54, - 54.1, - 53.8, - 53.6, - 54, - 54.5, - 54.6, - 54.7, - 54.8, - 54.9, - 55, - 54.9, - 54.8, - 54.7, - 54.7, - 54.5, - 54.3, - 54, - 53.7, - 53.5, - 53.1, - 52.8, - 52.5, - 52.2, - 51.9, - 51.4, - 51, - 50.6, - 50.2, - 49.9, - 49.5, - 49.3, - 49.2, - 48.7, - 48.3, - 48.1, - 47.9, - 47.6, - 47.1, - 47, - 46.7, - 46.8, - 46.2, - 45.8, - 45.3, - 45.3, - 45, - 44.8, - 44.9, - 45, - 45.4, - 45.6, - 45.2, - 44.6, - 44.3, - 44.1, - 44.4, - 44.6, - 44.9, - 45, - 44.8, - 44.3, - 43.9, - 43.5, - 43.4, - 43.2, - 43, - 42.8, - 42.2, - 41, - 40.5, - 41.5, - 42, - 42, - 41.5, - 40.7, - 40, - 39.5, - 39.1, - 38.2, - 37.2, - 36.6, - 36.2, - 36.1, - 35.6, - 34.5, - 34.2, - 34.3, - 34.3, - 34.3, - 34.3, - 34, - 33.3, - 32.6, - 32.1, - 31.6, - 31.3, - 30.3, - 29.5, - 28.8, - 28, - 27.2, - 26, - 24.2, - 23.2, - 21.7, - 20.6, - 19.6, - 18.6, - 17.9, - 17.2, - 16.2, - 15.2, - 14.5, - 14.1, - 13.9, - 13.4, - 12.6, - 11.7, - 11, - 10, - 8.7, - 7.1, - 5.4, - 3.6, - 1, - -0.2, - -1.1, - -1.4, - -1.4, - -1.5, - -1.5, - -1.5, - -1.5, - -1.6, - -1.5, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -2.1, - -2.4, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 54.6, - 54.5, - 54.1, - 53.9, - 53.8, - 53.7, - 53.4, - 53, - 52.7, - 52.5, - 52.3, - 52, - 52.1, - 52.4, - 52.6, - 53, - 53.8, - 54.1, - 53.5, - 53.5, - 53.8, - 54.2, - 54.4, - 54.4, - 54.6, - 54.8, - 54.8, - 54.8, - 54.6, - 54.5, - 54.4, - 54.3, - 54.1, - 53.9, - 53.6, - 53.5, - 53.2, - 53, - 52.7, - 52.4, - 52.1, - 51.6, - 51.1, - 50.7, - 50.3, - 50.1, - 49.6, - 49.1, - 49, - 48.6, - 48.2, - 48.1, - 47.8, - 47.5, - 47, - 46.7, - 46.6, - 46.7, - 46, - 45.6, - 45.4, - 45.3, - 44.9, - 44.8, - 44.9, - 44.9, - 45.5, - 45.6, - 45.2, - 44.8, - 44.4, - 44.2, - 44.5, - 44.7, - 44.9, - 44.9, - 44.8, - 44.4, - 44, - 43.7, - 43.6, - 43.3, - 43.1, - 42.8, - 42.2, - 41.1, - 40.8, - 41.5, - 42, - 41.8, - 41.5, - 41, - 40.3, - 39.9, - 39.2, - 38.2, - 37.1, - 36.4, - 35.9, - 36, - 35.7, - 34.9, - 34.1, - 34.1, - 34.2, - 34.3, - 34.2, - 33.9, - 33.1, - 32.5, - 31.9, - 31.5, - 31.3, - 30.5, - 29.7, - 29.1, - 28.2, - 27.2, - 26.1, - 25, - 24.1, - 22.5, - 21.1, - 20, - 19, - 18.4, - 17.6, - 16.7, - 15.6, - 14.8, - 14.3, - 14, - 13.2, - 12.5, - 11.7, - 11.1, - 9.8, - 8.5, - 7.9, - 6.1, - 3.4, - 0.8, - -0.3, - -0.9, - -1.4, - -1.5, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.8, - -1.7, - -1.7, - -1.7, - -1.7, - -2, - -2.4, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 54.1, - 54.2, - 53.9, - 53.7, - 53.5, - 53.4, - 53.1, - 52.8, - 52.5, - 52.3, - 52.1, - 51.8, - 51.9, - 52, - 52.6, - 53, - 53.8, - 53.9, - 53.6, - 53.6, - 53.8, - 54, - 54.1, - 54, - 54.2, - 54.5, - 54.6, - 54.6, - 54.4, - 54.2, - 54.3, - 54.2, - 54, - 53.7, - 53.4, - 53.1, - 52.9, - 52.8, - 52.5, - 52.3, - 52, - 51.5, - 51, - 50.6, - 50.4, - 50.2, - 49.6, - 49.1, - 49, - 48.8, - 48.2, - 48, - 47.8, - 47.4, - 46.9, - 46.6, - 46.5, - 46.5, - 46.2, - 45.7, - 45.4, - 45.2, - 44.9, - 44.8, - 44.9, - 45.1, - 45.6, - 45.6, - 45.2, - 44.9, - 44.6, - 44.2, - 44.4, - 44.7, - 44.8, - 44.7, - 44.7, - 44.4, - 44, - 43.8, - 43.7, - 43.3, - 43, - 42.6, - 41.8, - 41, - 40.8, - 41.5, - 41.9, - 41.8, - 41.7, - 41.3, - 40.5, - 39.9, - 39.1, - 38.3, - 37.2, - 36.3, - 35.7, - 35.7, - 35.5, - 34.8, - 34, - 34, - 34.1, - 34.2, - 34.1, - 33.8, - 33.2, - 32.6, - 31.9, - 31.3, - 31.2, - 30.7, - 29.9, - 29.2, - 28.2, - 27.3, - 26.3, - 25.2, - 24, - 23, - 21.9, - 20.7, - 19.5, - 18.9, - 18.1, - 17.1, - 16.2, - 15.4, - 14.6, - 13.9, - 13.2, - 12.5, - 11.7, - 10.9, - 10, - 8.8, - 8.1, - 6.9, - 4.2, - 1.6, - 0.3, - -0.4, - -1.2, - -1.5, - -1.5, - -1.5, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.9, - -2.3, - -2.5, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 53.9, - 53.9, - 53.7, - 53.5, - 53.4, - 53.2, - 53, - 52.6, - 52.2, - 51.9, - 51.8, - 51.7, - 51.6, - 51.9, - 52.3, - 52.6, - 53.4, - 53.5, - 53.5, - 53.5, - 53.7, - 53.9, - 53.9, - 53.9, - 54, - 54.2, - 54.3, - 54.3, - 54, - 54, - 54, - 54, - 53.9, - 53.6, - 53, - 52.6, - 52.4, - 52.1, - 51.9, - 51.7, - 51.5, - 51.3, - 50.9, - 50.6, - 50.3, - 50, - 49.3, - 48.9, - 48.9, - 48.7, - 48.1, - 47.7, - 47.6, - 47.2, - 46.9, - 46.5, - 46.5, - 46.4, - 46.2, - 45.9, - 45.5, - 45, - 44.8, - 44.7, - 45, - 45.2, - 45.6, - 45.6, - 45.2, - 44.9, - 44.4, - 44.1, - 44.3, - 44.6, - 44.7, - 44.7, - 44.6, - 44.5, - 44.2, - 43.9, - 43.6, - 43.1, - 42.8, - 42.3, - 41.6, - 41, - 40.6, - 41.3, - 41.9, - 41.8, - 41.6, - 41.2, - 40.6, - 40, - 39.3, - 38.5, - 37, - 35.9, - 35.5, - 35.5, - 35.1, - 34.6, - 33.9, - 33.9, - 34, - 34.1, - 34.1, - 33.7, - 33.3, - 32.6, - 32, - 31.3, - 31, - 30.8, - 30, - 29.2, - 28.3, - 27.5, - 26.5, - 25.3, - 24, - 23.1, - 22.2, - 21.3, - 20.1, - 19.2, - 18.4, - 17.6, - 16.6, - 15.6, - 14.7, - 14.2, - 13.5, - 12.6, - 11.6, - 10.8, - 10.4, - 9.2, - 8.1, - 7, - 4.7, - 2.1, - 0.6, - -0.6, - -1.4, - -1.5, - -1.5, - -1.5, - -1.5, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.7, - -1.9, - -2.2, - -2.5, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 53.9, - 53.7, - 53.6, - 53.3, - 53.1, - 52.9, - 52.5, - 52.1, - 51.8, - 51.6, - 51.5, - 51.4, - 51.5, - 51.7, - 51.9, - 52.2, - 52.9, - 53.4, - 53.3, - 53.3, - 53.5, - 53.7, - 53.6, - 53.7, - 53.8, - 54.1, - 54.2, - 54.1, - 54, - 53.9, - 53.8, - 53.9, - 53.8, - 53.6, - 52.9, - 52.3, - 52, - 51.9, - 51.7, - 51.4, - 51.1, - 51, - 50.8, - 50.4, - 50.2, - 49.8, - 49, - 48.7, - 48.6, - 48.5, - 48, - 47.7, - 47.5, - 47.1, - 46.9, - 46.5, - 46.4, - 46.3, - 46.1, - 45.7, - 45.4, - 44.9, - 44.7, - 44.6, - 45.2, - 45.4, - 45.5, - 45.5, - 45.1, - 44.8, - 44.2, - 43.8, - 44, - 44.4, - 44.6, - 44.7, - 44.5, - 44.5, - 44.1, - 43.9, - 43.6, - 43.1, - 42.5, - 41.9, - 41.5, - 40.9, - 40.2, - 41, - 41.8, - 41.8, - 41.5, - 41.3, - 41, - 40.1, - 39.4, - 38.6, - 36.9, - 35.8, - 35.3, - 35.2, - 34.9, - 34.5, - 34.1, - 33.8, - 33.9, - 33.9, - 34, - 33.8, - 33.2, - 32.6, - 32.1, - 31.3, - 31, - 30.8, - 29.9, - 29, - 28.2, - 27.6, - 26.5, - 25.1, - 24.1, - 23.2, - 22.5, - 21.7, - 20.3, - 19.4, - 18.4, - 17.6, - 16.7, - 15.9, - 15.2, - 14.8, - 14, - 13.1, - 12.1, - 11.1, - 10.6, - 9.4, - 8.1, - 7.1, - 5.1, - 2.4, - 0.4, - -0.8, - -1.4, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.6, - -1.8, - -2, - -2.4, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 54.2, - 53.7, - 53.5, - 52.9, - 52.4, - 52.3, - 51.9, - 51.7, - 51.4, - 51.3, - 51.2, - 51.1, - 51.3, - 51.6, - 51.7, - 51.8, - 52.2, - 53.1, - 53.1, - 53.1, - 53.3, - 53.5, - 53.3, - 53.4, - 53.4, - 53.8, - 54, - 54, - 53.8, - 53.8, - 53.7, - 53.7, - 53.7, - 53.6, - 53.2, - 52.3, - 51.9, - 51.6, - 51.5, - 51.1, - 50.9, - 50.8, - 50.5, - 50.3, - 50, - 49.5, - 49, - 48.7, - 48.5, - 48.2, - 47.7, - 47.5, - 47.3, - 47, - 46.7, - 46.4, - 46.1, - 46.1, - 45.9, - 45.6, - 45.3, - 44.9, - 44.6, - 44.6, - 45.4, - 45.6, - 45.6, - 45.5, - 45.1, - 44.7, - 44.2, - 43.7, - 43.8, - 44.3, - 44.6, - 44.6, - 44.4, - 44.3, - 44, - 43.8, - 43.5, - 43.1, - 42.5, - 42, - 41.5, - 40.7, - 39.8, - 40.8, - 41.5, - 41.8, - 41.7, - 41.4, - 40.9, - 40.2, - 39.5, - 38.8, - 37.3, - 35.8, - 35.3, - 35.1, - 34.9, - 34.5, - 34.1, - 33.7, - 33.7, - 33.8, - 33.9, - 33.7, - 33.2, - 32.6, - 31.9, - 31.2, - 30.8, - 30.7, - 29.8, - 28.9, - 28.2, - 27.5, - 26.1, - 25.1, - 24.2, - 23.4, - 22.7, - 21.9, - 20.8, - 19.7, - 18.5, - 17.4, - 16.9, - 16.2, - 15.6, - 15.1, - 14.2, - 13.4, - 12.6, - 11.3, - 10.4, - 9.7, - 9, - 7.6, - 5.4, - 2.9, - 0.6, - -0.8, - -1.4, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.7, - -1.7, - -1.7, - -1.9, - -2.3, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 54.1, - 53.7, - 53.1, - 52.4, - 52, - 51.8, - 51.5, - 51.4, - 51.2, - 51, - 50.9, - 50.8, - 51, - 51.2, - 51.3, - 51.4, - 51.7, - 52.2, - 52.6, - 52.7, - 52.9, - 53, - 53.1, - 52.8, - 52.8, - 53.2, - 53.6, - 53.8, - 53.7, - 53.6, - 53.6, - 53.6, - 53.6, - 53.5, - 53.4, - 52.8, - 51.9, - 51.5, - 51.3, - 51, - 50.7, - 50.5, - 50.4, - 50.2, - 49.7, - 49.4, - 49.1, - 48.7, - 48.4, - 48, - 47.5, - 47.3, - 47.2, - 46.7, - 46.4, - 46.2, - 46, - 45.9, - 45.8, - 45.4, - 45, - 44.6, - 44.5, - 44.4, - 45.4, - 45.7, - 45.8, - 45.6, - 45.2, - 44.7, - 44.1, - 43.5, - 43.6, - 44.2, - 44.5, - 44.5, - 44.3, - 44.2, - 44, - 43.7, - 43.4, - 43.1, - 42.8, - 42.3, - 41.5, - 40.5, - 39.7, - 40.5, - 41.4, - 41.7, - 41.8, - 41.6, - 41.2, - 40.5, - 39.8, - 38.9, - 37.3, - 35.8, - 35.4, - 35.1, - 35, - 34.5, - 34, - 33.5, - 33.6, - 33.7, - 33.8, - 33.7, - 33.2, - 32.5, - 31.9, - 31.2, - 30.6, - 30.4, - 29.6, - 28.9, - 28.1, - 27.2, - 26.1, - 25, - 24.3, - 23.5, - 23, - 22.2, - 20.9, - 19.8, - 18.7, - 17.7, - 16.9, - 16.1, - 15.6, - 15, - 14.1, - 13.3, - 12.9, - 11.9, - 11.1, - 10.5, - 9.6, - 8.3, - 6.2, - 3.3, - 1.1, - -0.9, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - -1.7, - -1.8, - -2.1, - -2.5, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 53.4, - 52.9, - 52, - 51.9, - 51.6, - 51.5, - 51.4, - 51.2, - 50.9, - 50.8, - 50.7, - 50.6, - 50.7, - 50.8, - 50.9, - 51.1, - 51.2, - 51.3, - 51.7, - 51.8, - 52.1, - 52.2, - 52.4, - 52.3, - 52.3, - 52.5, - 52.8, - 53.3, - 53.3, - 53.3, - 53.3, - 53.3, - 53.3, - 53.2, - 53.1, - 52.7, - 52, - 51.5, - 51.2, - 50.8, - 50.6, - 50.4, - 50.2, - 50, - 49.5, - 49.1, - 48.9, - 48.7, - 48.3, - 47.8, - 47.4, - 47.1, - 46.8, - 46.5, - 46.3, - 46.1, - 46, - 45.7, - 45.5, - 45.2, - 44.9, - 44.5, - 44.4, - 44.2, - 45.2, - 45.7, - 45.6, - 45.4, - 45.2, - 44.7, - 44.1, - 43.5, - 43.4, - 44.1, - 44.3, - 44.3, - 44.1, - 43.8, - 43.7, - 43.5, - 43.4, - 43.1, - 42.8, - 42.2, - 41.3, - 40.3, - 39.5, - 40.5, - 41.3, - 41.5, - 41.6, - 41.6, - 41.2, - 40.7, - 40.1, - 38.8, - 37.4, - 36.1, - 35.3, - 35.1, - 34.8, - 34.3, - 33.9, - 33.4, - 33.5, - 33.6, - 33.7, - 33.7, - 33.2, - 32.4, - 31.8, - 31.2, - 30.5, - 30.2, - 29.4, - 28.8, - 28, - 27.1, - 26.1, - 25.1, - 24.4, - 23.7, - 23.1, - 22.2, - 21, - 20, - 18.8, - 17.8, - 16.8, - 16.1, - 15.5, - 14.5, - 13.7, - 13.1, - 12.8, - 12.4, - 11.5, - 10.5, - 9.1, - 7.8, - 6, - 3.8, - 1.4, - -0.8, - -1.6, - -1.6, - -1.6, - -1.6, - -1.9, - -2.3, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 51.8, - 51.8, - 51.6, - 51.6, - 51.5, - 51.5, - 51.4, - 51.2, - 50.9, - 50.8, - 50.6, - 50.4, - 50.4, - 50.5, - 50.6, - 50.6, - 50.8, - 51, - 51, - 51, - 51.2, - 51.4, - 51.7, - 52, - 52, - 52, - 52, - 52.2, - 52.6, - 52.7, - 53, - 52.9, - 52.9, - 52.8, - 52.7, - 52.4, - 51.8, - 51.4, - 51.1, - 50.8, - 50.4, - 50.2, - 50, - 49.7, - 49.3, - 49.1, - 48.8, - 48.5, - 48.2, - 47.7, - 47.4, - 47, - 46.5, - 46.4, - 46.2, - 46, - 45.7, - 45.4, - 45.1, - 45, - 44.8, - 44.5, - 44.1, - 44, - 45.2, - 45.8, - 45.7, - 45.3, - 45, - 44.6, - 43.8, - 43.3, - 43.3, - 43.9, - 44.1, - 44.1, - 43.9, - 43.6, - 43.4, - 43.3, - 43.2, - 43, - 42.7, - 41.7, - 40.9, - 40.1, - 39.3, - 40.2, - 41.4, - 41.3, - 41.2, - 41.5, - 41.3, - 40.8, - 40.1, - 39, - 37.7, - 36.6, - 35.5, - 34.9, - 34.3, - 34, - 33.8, - 33.2, - 33.3, - 33.4, - 33.5, - 33.6, - 33.1, - 32.4, - 31.9, - 31.3, - 30.4, - 30.1, - 29.3, - 28.5, - 27.7, - 26.9, - 25.8, - 25, - 24.4, - 23.8, - 22.9, - 21.9, - 20.9, - 19.9, - 18.8, - 17.8, - 16.7, - 16.2, - 15, - 14, - 13.4, - 13.1, - 12.8, - 12.3, - 11.4, - 9.8, - 8.8, - 7.6, - 6.2, - 4.6, - 1.6, - 0.4, - -0.9, - -1.4, - -1.6, - -2.2, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 51.7, - 51.7, - 51.5, - 51.4, - 51.5, - 51.4, - 51.2, - 51.1, - 51, - 50.9, - 50.5, - 50.3, - 50.3, - 50.4, - 50.3, - 50.3, - 50.3, - 50.4, - 50.4, - 50.6, - 50.7, - 50.9, - 51.1, - 51.4, - 51.5, - 51.5, - 51.4, - 51.3, - 51.7, - 51.8, - 51.8, - 52.2, - 52.4, - 52.2, - 51.9, - 51.8, - 51.6, - 51.3, - 51.1, - 50.6, - 50.2, - 49.9, - 49.7, - 49.3, - 49.1, - 49, - 48.7, - 48.4, - 48, - 47.6, - 47.3, - 46.8, - 46.4, - 46.2, - 46.1, - 45.8, - 45.5, - 45.2, - 45, - 44.9, - 44.8, - 44.4, - 43.9, - 43.7, - 44.9, - 45.6, - 45.6, - 45.3, - 44.8, - 44.3, - 43.5, - 42.9, - 43, - 43.6, - 44, - 43.9, - 43.6, - 43.4, - 43.2, - 43, - 43, - 42.8, - 42.2, - 41.4, - 40.7, - 39.9, - 39.3, - 39.7, - 41.2, - 41.2, - 40.8, - 40.6, - 40.6, - 40.4, - 39.9, - 38.9, - 38.3, - 37.4, - 35.8, - 34.9, - 34.1, - 33.8, - 33.4, - 33, - 33.1, - 33.3, - 33.4, - 33.5, - 33.1, - 32.5, - 31.8, - 31, - 30.3, - 29.9, - 29, - 28.2, - 27.5, - 26.6, - 25.6, - 24.8, - 24.3, - 23.5, - 22.5, - 21.5, - 20.5, - 19.6, - 18.5, - 17.7, - 16.8, - 15.8, - 14.8, - 14.1, - 13.6, - 13.2, - 12.7, - 12.2, - 11.1, - 10, - 8.5, - 7.5, - 6, - 4, - 1.4, - 0.5, - -0.5, - -1.5, - -2.2, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 51.4, - 51.3, - 51.1, - 51.1, - 51, - 50.9, - 50.7, - 50.6, - 50.5, - 50.5, - 50.3, - 50.2, - 50.1, - 50.2, - 50.1, - 50, - 50, - 50.1, - 50.1, - 50.3, - 50.5, - 50.7, - 50.9, - 51.1, - 51.1, - 51.1, - 50.9, - 50.9, - 51, - 51.1, - 51, - 50.8, - 50.8, - 51.3, - 51.4, - 51.4, - 51.4, - 51.2, - 50.8, - 50.5, - 50, - 49.7, - 49.5, - 49.3, - 49, - 48.8, - 48.6, - 48.2, - 47.8, - 47.4, - 47.1, - 46.7, - 46.4, - 46.1, - 45.9, - 45.7, - 45.5, - 45.2, - 45, - 44.7, - 44.5, - 44.2, - 43.8, - 43.4, - 44.5, - 45.6, - 45.5, - 45.1, - 44.6, - 43.9, - 43.1, - 42.4, - 42.4, - 42.9, - 43.5, - 43.6, - 43.4, - 43.2, - 43, - 42.8, - 42.7, - 42.3, - 41.5, - 41.2, - 40.5, - 40, - 39.3, - 39.2, - 40.8, - 41.2, - 40.8, - 40.4, - 40.2, - 39.9, - 39.6, - 39, - 38.3, - 37.6, - 35.9, - 34.7, - 34.1, - 33.5, - 33.1, - 32.9, - 33, - 33.2, - 33.3, - 33.4, - 33.2, - 32.5, - 31.6, - 30.7, - 30, - 29.8, - 28.9, - 27.9, - 27.3, - 26.3, - 25.4, - 24.7, - 24.2, - 23.2, - 22.1, - 21.1, - 20.2, - 19.4, - 18.6, - 17.7, - 16.5, - 15.6, - 14.9, - 14.4, - 13.8, - 13.1, - 12.6, - 12.3, - 11.5, - 10.2, - 8, - 7.3, - 5.6, - 3.2, - 0.3, - -0.7, - -1.3, - -2.3, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 50.8, - 50.5, - 50.3, - 50.4, - 50.5, - 50.4, - 50.2, - 50.3, - 50.2, - 50.2, - 50, - 49.9, - 49.9, - 49.9, - 50, - 50, - 50, - 50, - 50, - 50, - 50.3, - 50.5, - 50.8, - 50.9, - 50.9, - 50.6, - 50.5, - 50.7, - 50.8, - 50.7, - 50.5, - 50.4, - 50.4, - 50.6, - 50.9, - 51.2, - 51.2, - 50.9, - 50.6, - 50.3, - 50, - 49.7, - 49.4, - 49.2, - 48.8, - 48.7, - 48.4, - 48, - 47.7, - 47.2, - 47, - 46.6, - 46.4, - 46.2, - 45.9, - 45.7, - 45.6, - 45.3, - 45, - 44.5, - 44.2, - 44, - 43.6, - 43.2, - 43.5, - 44.5, - 45.2, - 44.8, - 44.3, - 43.4, - 42.6, - 42.1, - 42, - 42.2, - 42.9, - 43.2, - 43.1, - 42.9, - 42.7, - 42.4, - 42.2, - 41.9, - 41.3, - 40.9, - 40.3, - 39.7, - 39.2, - 39.5, - 40.7, - 41.1, - 40.8, - 40.5, - 40.2, - 39.6, - 39, - 38.6, - 38.1, - 37.3, - 35.8, - 34.7, - 33.9, - 33.4, - 32.9, - 32.7, - 32.8, - 33, - 33.2, - 33.3, - 33.1, - 32.3, - 31.4, - 30.5, - 29.8, - 29.5, - 28.6, - 27.7, - 27, - 26.2, - 25.4, - 24.7, - 24.1, - 23, - 21.9, - 20.9, - 20.2, - 19.6, - 18.9, - 17.6, - 16.4, - 15.7, - 15, - 14.5, - 13.6, - 12.9, - 12.4, - 12.2, - 11.2, - 9.5, - 7.2, - 6.1, - 4.3, - 1.8, - 0.1, - -0.8, - -2.1, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 50.3, - 50.1, - 50.1, - 50.1, - 50.2, - 50, - 49.9, - 49.9, - 49.9, - 49.9, - 49.8, - 49.6, - 49.6, - 49.7, - 49.9, - 49.9, - 49.9, - 49.8, - 50, - 49.9, - 50, - 50.3, - 50.4, - 50.4, - 50.3, - 50.2, - 50.2, - 50.2, - 50.4, - 50.4, - 50.2, - 50.2, - 50.3, - 50.1, - 50.2, - 50.6, - 50.8, - 50.6, - 50.3, - 50.1, - 50, - 49.5, - 49.3, - 49, - 48.6, - 48.4, - 48.2, - 47.9, - 47.7, - 47.3, - 46.9, - 46.6, - 46.4, - 46.3, - 46.1, - 45.8, - 45.6, - 45.1, - 44.8, - 44.2, - 44, - 43.8, - 43.5, - 43.1, - 42.9, - 42.9, - 42.8, - 43.5, - 43.7, - 42.7, - 42.1, - 41.7, - 41.5, - 41.7, - 42.3, - 42.8, - 42.7, - 42.5, - 42.2, - 42.2, - 42, - 41.6, - 41, - 40.6, - 39.9, - 39.6, - 39.1, - 38.8, - 40.3, - 40.9, - 40.9, - 40.8, - 40.3, - 39.7, - 39, - 38.2, - 37.6, - 36.7, - 35.7, - 34.8, - 33.9, - 33.3, - 32.7, - 32.5, - 32.7, - 32.9, - 33, - 33.1, - 33, - 32.3, - 31.3, - 30.4, - 29.7, - 29.4, - 28.4, - 27.7, - 26.8, - 26, - 25.3, - 24.9, - 24, - 22.8, - 21.7, - 21, - 20.3, - 19.8, - 19.1, - 18.3, - 17.3, - 16.3, - 15.5, - 14.6, - 14, - 13.3, - 12.1, - 11.5, - 10.3, - 8.4, - 6.3, - 4.2, - 3.4, - 1.5, - 0.7, - -0.4, - -2.4, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 49.7, - 49.6, - 49.7, - 49.8, - 49.9, - 49.8, - 49.6, - 49.5, - 49.5, - 49.5, - 49.5, - 49.4, - 49.4, - 49.5, - 49.8, - 49.8, - 49.8, - 49.8, - 49.8, - 49.8, - 49.8, - 49.9, - 50, - 49.7, - 49.7, - 49.9, - 49.8, - 49.9, - 50, - 49.9, - 49.7, - 49.7, - 49.7, - 49.6, - 49.7, - 49.8, - 49.9, - 50, - 50.1, - 49.9, - 49.8, - 49.4, - 49.1, - 48.8, - 48.4, - 48.2, - 48, - 47.8, - 47.6, - 47.3, - 46.8, - 46.6, - 46.5, - 46.3, - 46, - 45.8, - 45.6, - 45.1, - 44.7, - 44.1, - 43.8, - 43.5, - 43.3, - 43.1, - 42.6, - 42.4, - 42.3, - 42.2, - 42.3, - 41.8, - 41.4, - 41.1, - 40.9, - 41.3, - 41.7, - 42, - 42, - 42.1, - 42, - 41.9, - 41.7, - 41.3, - 40.8, - 40.2, - 39.7, - 39.4, - 39, - 38.7, - 40, - 40.8, - 40.8, - 40.7, - 40.3, - 39.7, - 39.2, - 38.4, - 37.5, - 36.6, - 35.6, - 34.7, - 33.9, - 33.2, - 32.7, - 32.4, - 32.6, - 32.7, - 32.9, - 33, - 32.9, - 32.2, - 31.3, - 30.3, - 29.6, - 29.3, - 28.4, - 27.6, - 26.7, - 25.9, - 25.5, - 24.9, - 23.9, - 22.8, - 21.9, - 21.2, - 20.5, - 19.8, - 19.5, - 18.8, - 17.9, - 16.7, - 15.6, - 14.7, - 14.4, - 13.5, - 12.4, - 11.3, - 9.7, - 7.9, - 5.4, - 3.3, - 2.5, - 1.6, - 0.8, - -0.5, - -2.5, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 49.5, - 49.3, - 49.3, - 49.5, - 49.7, - 49.6, - 49.5, - 49.4, - 49.3, - 49.2, - 49.1, - 49.1, - 49, - 49, - 49.2, - 49.3, - 49.4, - 49.5, - 49.6, - 49.6, - 49.6, - 49.6, - 49.4, - 49.4, - 49.3, - 49.4, - 49.3, - 49.3, - 49.2, - 49, - 49.1, - 48.9, - 48.9, - 49.1, - 49.3, - 49.4, - 49.5, - 49.7, - 49.9, - 49.8, - 49.6, - 49.4, - 49, - 48.6, - 48.3, - 48, - 47.9, - 47.7, - 47.4, - 47.2, - 46.7, - 46.5, - 46.6, - 46.2, - 45.8, - 45.8, - 45.5, - 45, - 44.3, - 43.8, - 43.3, - 43, - 42.9, - 42.8, - 42.4, - 42.2, - 42, - 41.7, - 41.5, - 41.2, - 40.8, - 40.5, - 40.5, - 40.8, - 41.3, - 41.5, - 41.6, - 41.7, - 41.6, - 41.5, - 41.2, - 40.8, - 40.6, - 40.1, - 39.7, - 39.2, - 38.7, - 38.3, - 39.4, - 40.6, - 40.8, - 40.7, - 40.3, - 39.8, - 39.4, - 38.7, - 37.5, - 36.4, - 35.7, - 34.7, - 33.8, - 33.1, - 32.5, - 32.3, - 32.4, - 32.6, - 32.8, - 32.9, - 32.8, - 32.2, - 31.2, - 30.2, - 29.5, - 29.1, - 28.3, - 27.4, - 26.8, - 26.4, - 25.7, - 24.7, - 23.6, - 22.9, - 22.2, - 21.5, - 20.6, - 20.1, - 19.9, - 19.2, - 18.5, - 17, - 15.9, - 15, - 14.4, - 13.5, - 12.8, - 11.8, - 9.9, - 7.7, - 4.6, - 3.3, - 2.5, - 1.3, - 0.5, - -0.6, - -2.3, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 49.3, - 49.1, - 48.9, - 48.9, - 49.1, - 49.2, - 49.5, - 49.4, - 49.1, - 48.9, - 48.8, - 48.8, - 48.7, - 48.6, - 48.6, - 48.6, - 48.8, - 48.9, - 49.1, - 49.2, - 49.2, - 49.2, - 49, - 49.1, - 49, - 48.9, - 48.8, - 48.8, - 48.5, - 48.5, - 48.6, - 48.7, - 48.7, - 48.8, - 48.9, - 48.9, - 49.1, - 49.3, - 49.6, - 49.6, - 49.3, - 49, - 48.8, - 48.4, - 48.2, - 48, - 47.8, - 47.6, - 47.2, - 47.1, - 46.7, - 46.4, - 46.3, - 46, - 45.8, - 45.7, - 45.4, - 44.7, - 43.7, - 43.1, - 42.7, - 42.4, - 42.3, - 42.2, - 42.1, - 42, - 41.7, - 41.4, - 41.1, - 40.7, - 40.4, - 40.1, - 40.1, - 40.5, - 40.7, - 41.1, - 41.4, - 41.6, - 41.4, - 40.8, - 40.6, - 40.2, - 39.8, - 39.5, - 39.4, - 39.1, - 38.5, - 38, - 38.7, - 40.2, - 40.6, - 40.6, - 40.3, - 39.9, - 39.4, - 38.9, - 37.6, - 36.3, - 35.6, - 34.4, - 33.5, - 32.7, - 32.3, - 32, - 32.2, - 32.5, - 32.6, - 32.8, - 32.8, - 32, - 31.1, - 30.2, - 29.4, - 29, - 28.2, - 27.3, - 27, - 27, - 26.8, - 26, - 24.7, - 23.1, - 22.3, - 21.7, - 20.9, - 20.5, - 20.1, - 19.3, - 18.5, - 17.6, - 16.4, - 15.1, - 14.5, - 13.7, - 13, - 11.8, - 10.5, - 8, - 5.6, - 3.8, - 2.9, - 1.6, - 0.4, - -0.6, - -2.2, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 48.9, - 48.7, - 48.6, - 48.5, - 48.5, - 48.7, - 49.1, - 49.1, - 48.9, - 48.7, - 48.6, - 48.5, - 48.5, - 48.3, - 48.3, - 48.3, - 48.4, - 48.5, - 48.5, - 48.5, - 48.7, - 48.7, - 48.5, - 48.4, - 48.4, - 48.3, - 48.3, - 48.3, - 48.1, - 48.3, - 48.5, - 48.6, - 48.5, - 48.6, - 48.6, - 48.6, - 48.6, - 48.8, - 49.3, - 49.4, - 49, - 48.7, - 48.5, - 48.3, - 48.1, - 47.9, - 47.7, - 47.4, - 47.2, - 47, - 46.7, - 46.3, - 46, - 45.8, - 45.7, - 45.4, - 44.9, - 43.8, - 42.9, - 42.4, - 42, - 41.8, - 41.7, - 41.5, - 41.5, - 41.8, - 41.5, - 41.3, - 40.9, - 40.4, - 40, - 39.8, - 39.7, - 40, - 40.3, - 40.8, - 41.2, - 41.2, - 41, - 40.5, - 40.1, - 39.9, - 39.5, - 39.1, - 38.9, - 38.7, - 38.2, - 37.8, - 38.2, - 39.8, - 40.2, - 40.1, - 40, - 39.7, - 39.5, - 39, - 37.8, - 36.2, - 35.1, - 33.8, - 33, - 32.5, - 32.1, - 32, - 32.1, - 32.4, - 32.5, - 32.6, - 32.7, - 31.9, - 31, - 30.1, - 29.3, - 28.9, - 27.9, - 27.4, - 27.2, - 27.3, - 27.3, - 26.7, - 26.2, - 25, - 23.6, - 22.7, - 21.7, - 20.7, - 20.1, - 19.5, - 18.9, - 18.1, - 17.1, - 15.5, - 14.4, - 13.6, - 13, - 12, - 11, - 9.3, - 6.5, - 5.1, - 3.6, - 2.4, - 1.1, - -0.2, - -1.9, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 48.5, - 48.4, - 48.2, - 48.1, - 48.2, - 48.2, - 48.4, - 48.6, - 48.2, - 48.2, - 48, - 48, - 48.1, - 48, - 48.1, - 48.1, - 48.1, - 48.1, - 48.1, - 48.1, - 48.1, - 48, - 47.9, - 47.7, - 47.8, - 47.9, - 48, - 47.8, - 47.8, - 48, - 48.2, - 48.3, - 48.2, - 48.2, - 48.4, - 48.5, - 48.3, - 48.2, - 48.5, - 49, - 48.8, - 48.6, - 48.4, - 48.2, - 48, - 47.9, - 47.7, - 47.4, - 47.2, - 47, - 46.7, - 46.3, - 45.9, - 45.7, - 45.6, - 45.2, - 44.2, - 43, - 42.2, - 41.7, - 41.5, - 41.4, - 41.3, - 41.2, - 41.1, - 41.3, - 41.3, - 41.2, - 40.7, - 40.2, - 39.7, - 39.3, - 39.2, - 39.4, - 39.9, - 40.4, - 40.7, - 40.7, - 40.6, - 40.2, - 39.6, - 39.5, - 39.2, - 38.9, - 38.6, - 38.3, - 37.9, - 37.6, - 37.9, - 39.2, - 39.8, - 40, - 39.8, - 39.6, - 39.4, - 38.9, - 37.9, - 36.2, - 34.9, - 33.7, - 32.8, - 32.3, - 32.1, - 31.9, - 32, - 32.3, - 32.4, - 32.5, - 32.6, - 32.1, - 31.1, - 30, - 29.2, - 28.8, - 28, - 27.4, - 27.2, - 27.4, - 27.2, - 26.9, - 26.6, - 25.6, - 24.4, - 23.4, - 22.4, - 21.2, - 20.2, - 19.5, - 18.9, - 18.2, - 17.1, - 15.3, - 14.3, - 13.7, - 13.1, - 12, - 11, - 9.5, - 7.1, - 5.5, - 4.2, - 2.6, - 1.3, - 0.2, - -1.5, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 48.3, - 48.1, - 48, - 48, - 47.8, - 47.7, - 47.8, - 47.9, - 47.9, - 47.8, - 47.6, - 47.6, - 47.7, - 47.8, - 47.7, - 47.6, - 47.7, - 47.8, - 47.7, - 47.8, - 47.7, - 47.5, - 47.4, - 47.3, - 47.3, - 47.5, - 47.4, - 47.3, - 47.4, - 47.6, - 47.8, - 47.8, - 47.9, - 47.9, - 48.1, - 48.2, - 48.1, - 47.9, - 47.9, - 48.3, - 48.5, - 48.4, - 48.2, - 48.1, - 48, - 47.7, - 47.5, - 47.2, - 47.1, - 46.8, - 46.6, - 46.3, - 45.8, - 45.6, - 45.4, - 44.7, - 43.5, - 42.6, - 41.8, - 41.2, - 41.1, - 41, - 40.8, - 40.8, - 40.8, - 41.1, - 41, - 40.9, - 40.6, - 40.2, - 39.7, - 39.2, - 38.8, - 39.1, - 39.5, - 40, - 40.4, - 40.2, - 40, - 39.6, - 39.2, - 38.9, - 38.7, - 38.6, - 38.6, - 38.3, - 37.8, - 37.4, - 37.4, - 38.3, - 39.3, - 39.7, - 39.9, - 39.8, - 39.4, - 38.9, - 37.9, - 36.3, - 35, - 33.8, - 32.9, - 32.2, - 32.1, - 31.9, - 31.9, - 32.1, - 32.3, - 32.4, - 32.5, - 32.1, - 31.1, - 30.1, - 29.2, - 28.8, - 28.2, - 27.2, - 27.2, - 27.4, - 27.4, - 27.1, - 26.7, - 25.9, - 24.6, - 23.7, - 23, - 21.9, - 21, - 19.7, - 18.8, - 17.8, - 16.7, - 15.3, - 14.3, - 13.8, - 13, - 11.9, - 10.7, - 9.2, - 6.5, - 5.3, - 4.2, - 2.8, - 1.5, - 0.3, - -1.3, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 48.2, - 48, - 47.8, - 47.7, - 47.7, - 47.5, - 47.6, - 47.5, - 47.5, - 47.5, - 47.4, - 47.4, - 47.5, - 47.6, - 47.6, - 47.5, - 47.3, - 47.3, - 47.4, - 47.3, - 47.3, - 47.1, - 47, - 46.8, - 46.8, - 46.8, - 46.9, - 46.9, - 47.1, - 47.3, - 47.5, - 47.6, - 47.6, - 47.7, - 47.9, - 48, - 47.9, - 47.7, - 47.6, - 47.7, - 47.9, - 48.1, - 48, - 47.9, - 47.8, - 47.6, - 47.4, - 47.1, - 47, - 46.7, - 46.4, - 46, - 45.8, - 45.4, - 45, - 44.1, - 43, - 42.1, - 41.4, - 41, - 40.8, - 40.7, - 40.4, - 40.5, - 40.6, - 40.8, - 40.8, - 40.8, - 40.5, - 40.1, - 39.6, - 39, - 38.5, - 38.6, - 39, - 39.5, - 40, - 40, - 39.8, - 39.2, - 38.9, - 38.6, - 38.4, - 38.4, - 38.7, - 38.5, - 37.7, - 37.2, - 37.1, - 37.9, - 38.9, - 39.4, - 39.6, - 39.7, - 39.4, - 38.9, - 38.2, - 36.6, - 35, - 33.9, - 33.3, - 32.7, - 32.1, - 31.8, - 31.8, - 32, - 32.1, - 32.3, - 32.4, - 32, - 31, - 30, - 29.1, - 28.8, - 28.1, - 27.3, - 27.3, - 27.4, - 27.5, - 27.2, - 26.8, - 25.9, - 24.9, - 24.1, - 23.1, - 22.2, - 21.3, - 19.8, - 18.8, - 17.6, - 16.6, - 15.5, - 14.4, - 13.7, - 12.7, - 11.7, - 10.5, - 9.1, - 6.1, - 5.1, - 4.1, - 2.9, - 1.6, - 0.2, - -1.3, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - 47.7, - 47.7, - 47.6, - 47.4, - 47.3, - 47.4, - 47.4, - 47.4, - 47.4, - 47.4, - 47.3, - 47.3, - 47.3, - 47.6, - 47.6, - 47.4, - 47.2, - 47.1, - 47.2, - 47.2, - 47, - 46.9, - 46.7, - 46.5, - 46.3, - 46.2, - 46.4, - 46.7, - 46.9, - 47.1, - 47.2, - 47.4, - 47.5, - 47.5, - 47.8, - 47.9, - 47.6, - 47.5, - 47.3, - 47.4, - 47.3, - 47.5, - 47.7, - 47.7, - 47.6, - 47.5, - 47.3, - 47.1, - 46.8, - 46.5, - 46.2, - 45.9, - 45.5, - 45.2, - 44.4, - 43.2, - 42.4, - 41.7, - 41.3, - 41, - 40.6, - 40.4, - 40.2, - 40.2, - 40.2, - 40.3, - 40.3, - 40.4, - 40.3, - 39.9, - 39.4, - 38.8, - 38.1, - 38.2, - 38.7, - 39.2, - 39.5, - 39.7, - 39.5, - 39.1, - 38.6, - 38.3, - 38.2, - 38.3, - 38.5, - 38.4, - 37.8, - 37, - 36.6, - 37.4, - 38.4, - 39, - 39.2, - 39.1, - 39.1, - 38.8, - 38.3, - 36.5, - 35.1, - 34.1, - 33.6, - 32.9, - 32.2, - 31.7, - 31.6, - 31.9, - 32, - 32.2, - 32.3, - 31.8, - 30.8, - 29.7, - 29, - 28.7, - 28, - 27.3, - 27.3, - 27.4, - 27.3, - 27, - 26.7, - 25.9, - 25.4, - 24.7, - 23.5, - 22.4, - 21.2, - 19.9, - 18.7, - 17.7, - 16.4, - 15.3, - 14.3, - 13.4, - 12.6, - 11.5, - 10.6, - 9.2, - 6.3, - 5.2, - 4, - 2.7, - 1.5, - 0.1, - -1.3, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6, - -2.6 - ] + "raw16": "heightmap.r16", + "min": -2.6, + "max": 6550.9 } } }, diff --git a/packages/terrain/starter/coastal/sea.ts b/packages/terrain/starter/coastal/sea.ts index b2510106fc..a9cd4a6d56 100644 --- a/packages/terrain/starter/coastal/sea.ts +++ b/packages/terrain/starter/coastal/sea.ts @@ -21,6 +21,7 @@ import { clamp, color, float, + fwidth, mix, mx_fractal_noise_float, mx_noise_float, @@ -124,6 +125,11 @@ function createSeaMesh(ocean: SpectralOcean, data: ICoastalSeaData): Mesh { // through more than 2 m of water leaves sheltered lagoons out of the breaking-surf mask. const n = data.resolution; const cell = data.size / (n - 1); + // The surveyed deepest bed sets the tint range and the open-sea continuation. + const deepDepth = data.heights.reduce( + (deepest, height) => Math.max(deepest, level - height), + cell, + ); const distance = Float32Array.from(data.heights, (height) => (height >= level ? 0 : 1e4)); const exposed = new Uint8Array(n * n); const queue: number[] = []; @@ -207,12 +213,15 @@ function createSeaMesh(ocean: SpectralOcean, data: ICoastalSeaData): Mesh { roughness: 0.12, }); const shoreUV = (x: Node<"float">, z: Node<"float">) => - vec2(x.div(data.size).add(0.5), z.div(data.size).add(0.5)); + vec2(x.div(data.size).add(0.5), z.div(data.size).add(0.5)) + .mul((n - 1) / n) + .add(0.5 / n); + // Outside the surveyed bed, continue its edge depth before easing into open sea. const inside = (x: Node<"float">, z: Node<"float">) => - float(1).sub(smoothstep(data.size / 2 - 2, data.size / 2 + 2, x.abs().max(z.abs()))); + float(1).sub(smoothstep(data.size / 2, data.size * 0.75, x.abs().max(z.abs()))); const vertexLand = texture(heightTexture, shoreUV(positionLocal.x, positionLocal.z)).r; const vertexDepth = mix( - float(28), + float(deepDepth), float(level).sub(vertexLand), inside(positionLocal.x, positionLocal.z), ); @@ -246,7 +255,7 @@ function createSeaMesh(ocean: SpectralOcean, data: ICoastalSeaData): Mesh { ); const shore = texture(heightTexture, shoreUV(positionWorld.x, positionWorld.z)); const local = inside(positionWorld.x, positionWorld.z); - const depth = mix(float(28), positionWorld.y.sub(shore.r), local); + const depth = mix(float(deepDepth), float(level).sub(shore.r), local); const damp = smoothstep(0.1, 2.5, depth); const wind = mx_noise_float( vec3(positionWorld.x.mul(0.07).sub(time.mul(0.04)), positionWorld.z.mul(0.07), time.mul(0.015)), @@ -269,24 +278,48 @@ function createSeaMesh(ocean: SpectralOcean, data: ICoastalSeaData): Mesh { const bounced = view.negate().reflect(normal); // The standard material supplies Fresnel and the sun's specular lobe; the sky supplies the radiance. material.envNode = mix(color(0xb6cbd5), color(0x568fbd), clamp(bounced.y, 0, 1)).mul(1.15); - const water = mix(OCEAN_LOOK.shallow, OCEAN_LOOK.deep, smoothstep(0.4, 9, depth)); - const surf = mx_noise_float( - vec3(positionWorld.x.mul(0.9), positionWorld.z.mul(0.9), time.mul(0.45)), - ) + const water = mix( + OCEAN_LOOK.shallow, + OCEAN_LOOK.deep, + smoothstep(deepDepth * 0.25, deepDepth, depth).pow(2), + ); + const surfPoint = vec3(positionWorld.x, positionWorld.z, time.mul(0.45)); + const broadSurf = mx_noise_float(surfPoint.mul(vec3(0.18, 0.18, 0.3))) + .mul(0.5) + .add(0.5); + const surf = mx_noise_float(surfPoint.mul(vec3(0.9, 0.9, 1))) .mul(0.5) .add(0.5); + // A pixel covering a bubble fades it out; derivative-wide thresholds soften the remaining edges. + const footprint = fwidth(positionWorld.xz).length().mul(0.9); + const antialias = fwidth(surf).max(0.025); + const bubbles = smoothstep( + float(0.5).sub(broadSurf.mul(0.16)).sub(antialias), + float(0.65).sub(broadSurf.mul(0.16)).add(antialias), + surf, + ).mul(float(1).sub(smoothstep(0.35, 1, footprint))); + const shoreReach = float(1) + .sub(smoothstep(3, 12, shore.g)) + .mul(shore.b.mul(0.65).add(0.35)) + .mul(local); const shoreFoam = float(1) - .sub(smoothstep(0.25, 1.8, depth)) + .sub(smoothstep(0.25, 1.8, depth.add(time.mul(0.85).sin().mul(0.18)))) .mul(smoothstep(0.015, 0.08, depth)) - .mul(float(1).sub(smoothstep(1, 5.5, shore.g))) - .mul(smoothstep(0.25, 0.65, surf).mul(0.7).add(0.3)) - .mul(shore.b) - .mul(local) - .mul(0.8); - material.colorNode = mix(water, color(FOAM), shoreFoam); + .mul(bubbles) + .mul(broadSurf.mul(0.6).add(0.25)) + .mul(shoreReach); + const swash = float(1) + .sub(smoothstep(0.1, 2.5, depth)) + .mul(smoothstep(0, 0.12, depth)) + .mul(broadSurf.mul(0.5).add(0.5)) + .mul(shoreReach) + .mul(0.12); + material.colorNode = mix(water, color(FOAM), shoreFoam.add(swash)); // The surface runs to the fogged horizon, and fades into the sky there rather than ending on a hard band. const horizonFade = float(1).sub(smoothstep(1200, 3500, eyeDistance)); - material.opacityNode = smoothstep(-0.03, 0.35, depth).max(shoreFoam.mul(0.9)).mul(horizonFade); + material.opacityNode = smoothstep(0, Math.min(2.5, deepDepth), depth) + .max(shoreFoam.mul(0.9)) + .mul(horizonFade); material.roughnessNode = mix( float(0.12).add(smoothstep(70, 700, eyeDistance).mul(0.07)), float(0.7), diff --git a/packages/terrain/starter/coastal/world.ts b/packages/terrain/starter/coastal/world.ts index 9cdc3bbd4e..497eb3ef4b 100644 --- a/packages/terrain/starter/coastal/world.ts +++ b/packages/terrain/starter/coastal/world.ts @@ -1,31 +1,20 @@ -// The coastal starter world, as the game that copied it draws and collides with it. -// -// `bake.mjs` wrote a world package into `world/`: heightmap, splat masks, placements and the CC0 fir. -// The engine's `WorldCells` streams it. `loadTerrainSplat` textures its ground. -// Everything that decides how the world looks, or what stops the player, is in this folder for the game to edit. -import type { IComputeDriven, ICtx } from "@threenative/core"; +import type { ICtx } from "@threenative/core"; +import { type IWorldPackage, loadTerrainSplat } from "@threenative/core/world"; +import type { IPhysicsContext } from "@threenative/physics"; import { - Heightfield, - type IWorldCellsLoadOptions, - type IWorldPackage, - WorldCells, - heightSamplerFromHeightmap, - loadTerrainSplat, - loadWorldHeightmap, -} from "@threenative/core/world"; -import { CollisionShape3D, type IPhysicsContext, RigidBody3D } from "@threenative/physics"; + type IStarterWorld, + type IStarterWorldSpec, + PropColliders as StarterPropColliders, + addStarterWorld, +} from "@threenative/terrain/starter"; import { EquirectangularReflectionMapping, - type Material, type Mesh, type MeshStandardMaterial, - Object3D, + type Object3D, type Texture, - Vector3, } from "three"; -import { HDRLoader } from "three/addons/loaders/HDRLoader.js"; import { type ICoastalSea, type ICoastalSeaData, addCoastalSea } from "./sea.js"; - /** Where the game's asset source holds the baked `world/` folder. */ export const COASTAL_URL = "terrain/coastal/world.json"; @@ -43,24 +32,10 @@ export const PROP_SKY_LIGHT = 0.5; /** Metres around `follow` within which props collide. Beyond that, props only draw. */ export const PROP_COLLIDER_REACH = 60; -export interface ICoastalWorld { - readonly world: WorldCells; - /** One heightfield for the whole world, in place before `addCoastal` resolves. */ - readonly ground: RigidBody3D; - /** The prop colliders near `follow`. They stream with it (see `PROP_COLLIDER_REACH`). */ - readonly colliders: PropColliders; - /** The heightfield the ground stands on: `heightAt(x, z)` reads the terrain under any point. */ - readonly field: Heightfield; - /** Every placed prop's record, eight floats each, as the bake wrote it. */ - readonly placements: Float32Array; - /** The world's extent in metres, as the bake wrote it. */ - readonly extent: IWorldPackage["extent"]; - /** The sea's level in metres, as the bake wrote it. The scene keeps its spawn and its walk above it. */ +export interface ICoastalWorld extends IStarterWorld { readonly seaLevel: number; - /** The sea in the scene. `dispose` removes its mesh and releases its water surface. */ readonly sea: ICoastalSea; } - /** What `water.json` holds for the coastal kit: the sea, as one lake with a level. */ interface ICoastalWaterFile { readonly lakes: readonly { @@ -71,293 +46,57 @@ interface ICoastalWaterFile { readonly rivers: readonly unknown[]; } -/** - * Stream the coastal world around `follow` (usually the player or the camera) and give it collision. - * `whileCurrent` stops a departed scene's async load from attaching anything. Its resources are - * released on cancellation, failure and when the game's entity registry ends this scene. - */ +const spec: IStarterWorldSpec<{ dispose(): void; sea: ICoastalSea; seaLevel: number }> = { + name: "Coastal", + url: COASTAL_URL, + colliders: COLLIDERS, + colliderReach: PROP_COLLIDER_REACH, + loadSurface: (ctx, url) => loadTerrainSplat({ assets: ctx.assets, url }), + lighting: { sky: "sky.hdr", lods: false, dress: dressProp }, + streaming: { + ring: 5, + impostors: false, + gpuScene: false, + shadows: { cast: true, receive: true }, + budgets: { residentCells: 64, instances: 40_000, bytes: 64_000_000 }, + terrain: { streamRadius: 6 }, + }, + water: (ctx, file, _field, manifest, heightmap) => { + const seaLevel = (file as ICoastalWaterFile).lakes[0]?.level; + if (seaLevel === undefined) throw new Error("Coastal world: water.json names no sea level."); + const sea = addCoastalSea(ctx, coastalSeaData(manifest, heightmap, seaLevel)); + return { seaLevel, sea, dispose: () => sea.dispose() }; + }, +}; +export class PropColliders extends StarterPropColliders { + constructor( + ctx: ICtx, IPhysicsContext>, + follow: Object3D, + manifest: IWorldPackage, + records: Float32Array, + ) { + super(ctx, follow, manifest, records, COLLIDERS, PROP_COLLIDER_REACH, "Coastal"); + } +} export async function addCoastal( ctx: ICtx, IPhysicsContext>, follow: Object3D, url = COASTAL_URL, whileCurrent: () => boolean = () => true, ): Promise { - const assertCurrent = () => { - if (whileCurrent()) return; - const error = new Error("Coastal world: its scene is no longer current."); - error.name = "AbortError"; - throw error; - }; - let surface: Material | undefined; - let releaseLighting: (() => void) | undefined; - let world: WorldCells | undefined; - let ground: RigidBody3D | undefined; - let colliders: PropColliders | undefined; - let sea: ICoastalSea | undefined; - let released = false; - const dispose = () => { - if (released) return; - released = true; - colliders?.detach(); - colliders?.removeFromParent(); - ground?.dispose(); - ground?.object?.removeFromParent(); - sea?.dispose(); - world?.dispose(); - surface?.dispose(); - releaseLighting?.(); - }; - try { - assertCurrent(); - const manifest = (await (await fetchAsset(ctx, url, assertCurrent)).json()) as IWorldPackage; - assertCurrent(); - const base = url.slice(0, url.lastIndexOf("/") + 1); - const records = await ( - await fetchAsset(ctx, base + manifest.placements, assertCurrent) - ).arrayBuffer(); - assertCurrent(); - const [heightmapUrl] = await ctx.assets.resolve(base + manifest.terrain.heightmap); - assertCurrent(); - if (heightmapUrl === undefined) throw new Error("Coastal world: its heightmap is not served."); - const heightmap = await loadWorldHeightmap(heightmapUrl); - assertCurrent(); - surface = await loadTerrainSplat({ assets: ctx.assets, url }); - assertCurrent(); - releaseLighting = await lightProps(ctx, url, manifest, assertCurrent); - assertCurrent(); - const options: IWorldCellsLoadOptions = { - url, - assets: ctx.assets, - data: { manifest, placements: records, heightmap }, - surface, - follow, - ring: 5, - impostors: false, - gpuScene: false, - shadows: { cast: true, receive: true }, - budgets: { residentCells: 64, instances: 40_000, bytes: 64_000_000 }, - terrain: { streamRadius: 6 }, - }; - world = await WorldCells.load(options); - assertCurrent(); - const field = coastalField(manifest, heightmap); - ground = groundCollider(ctx, manifest, field); - const waterFile = (await ( - await fetchAsset(ctx, `${base}water.json`, assertCurrent) - ).json()) as ICoastalWaterFile; - assertCurrent(); - const seaLevel = waterFile.lakes[0]?.level; - if (seaLevel === undefined) throw new Error("Coastal world: water.json names no sea level."); - const ocean = addCoastalSea(ctx, coastalSeaData(manifest, heightmap, seaLevel)); - sea = ocean; - const props = new Float32Array(records); - colliders = new PropColliders(ctx, follow, manifest, props); - ctx.add(world); - if (ground.object !== undefined) ctx.add(ground.object); - ctx.add(colliders); - ctx.entities.add(`coastal-world.${world.uuid}`, { dispose }); - return { - world, - ground, - colliders, - field, - placements: props, - extent: manifest.extent, - seaLevel, - sea: ocean, - }; - } catch (error) { - dispose(); - throw error; - } + const result = await addStarterWorld(ctx, follow, spec, url, whileCurrent); + if (!result.extra) throw new Error("Coastal world: missing sea."); + return { ...result, ...result.extra }; } - -// Every prop gets the sky as its own envMap before WorldCells adopts the shared cached models. -// Without it, a fir lit by the sun alone draws flat. -async function lightProps( - ctx: ICtx, IPhysicsContext>, - url: string, - manifest: IWorldPackage, - assertCurrent: () => void, -): Promise<() => void> { - const base = url.slice(0, url.lastIndexOf("/") + 1); - const [skyUrl] = await ctx.assets.resolve(`${base}sky.hdr`); - assertCurrent(); - if (skyUrl === undefined) throw new Error("Coastal world: 'sky.hdr' is not served."); - const sky = await new HDRLoader().loadAsync(skyUrl); - const originals = new Map(); - const dispose = () => { - for (const [material, original] of originals) { - if (material.envMap !== sky) continue; - material.envMap = original.envMap; - material.envMapIntensity = original.intensity; - } - sky.dispose(); - }; - try { - assertCurrent(); - sky.mapping = EquirectangularReflectionMapping; - for (const asset of Object.values(manifest.assets)) { - const model = await ctx.assets.model<{ scene: Object3D }>(base + asset.glb); - assertCurrent(); - model.scene.traverse((object) => { - const mesh = object as Mesh; - for (const material of [mesh.material ?? []].flat() as MeshStandardMaterial[]) - if (!originals.has(material)) - originals.set(material, { - envMap: material.envMap, - intensity: material.envMapIntensity, - }); - dressProp(mesh, sky); - }); - } - return dispose; - } catch (error) { - dispose(); - throw error; - } -} - /** One prop mesh: the sky as its envMap. */ function dressProp(mesh: Mesh, sky: Texture): void { for (const material of [mesh.material ?? []].flat() as MeshStandardMaterial[]) { + sky.mapping = EquirectangularReflectionMapping; material.envMap = sky; material.envMapIntensity = PROP_SKY_LIGHT; } } -// The streamed terrain tiles arrive over several frames. A player spawned before its tile would -// fall through. One heightfield for the whole package avoids that, at a few hundred kilobytes. -/** The kit's one heightfield: the collider stands on it. */ -function coastalField(manifest: IWorldPackage, heightmap: Uint16Array): Heightfield { - const { extent, terrain } = manifest; - return Heightfield.fromSampler({ - columns: terrain.columns, - rows: terrain.rows, - width: extent.sizeX, - depth: extent.sizeZ, - origin: { x: extent.minX + extent.sizeX / 2, z: extent.minZ + extent.sizeZ / 2 }, - sampleHeight: heightSamplerFromHeightmap(terrain, extent, heightmap), - }); -} - -function groundCollider( - ctx: ICtx, IPhysicsContext>, - manifest: IWorldPackage, - field: Heightfield, -): RigidBody3D { - const { extent } = manifest; - const anchor = new Object3D(); - anchor.position.set(extent.minX + extent.sizeX / 2, 0, extent.minZ + extent.sizeZ / 2); - return new RigidBody3D({ - object: anchor, - physics: ctx.physics, - type: "fixed", - entity: "terrain", - shape: CollisionShape3D.heightfield(field.rows, field.columns, field.toColliderHeights(), { - x: extent.sizeX, - y: 1, - z: extent.sizeZ, - }), - }); -} - -/** - * Fixed bodies for the props within `PROP_COLLIDER_REACH` of `follow`, rebuilt when it has moved a few - * metres. Thousands of resident bodies cost Rapier about 0.5 s per 8 s (measured). A hundred do not. - */ -export class PropColliders extends Object3D implements IComputeDriven { - readonly warmupNodes: readonly unknown[] = []; - readonly processCadence = "render" as const; - readonly #live = new Map(); - readonly #runs: { asset: string; offset: number; count: number }[] = []; - readonly #at = { x: Number.NaN, z: Number.NaN }; - readonly #followPosition = new Vector3(); - #released = false; - - constructor( - private readonly ctx: ICtx, IPhysicsContext>, - private readonly follow: Object3D, - manifest: IWorldPackage, - private readonly records: Float32Array, - ) { - super(); - this.name = "coastal-prop-colliders"; - for (const cell of manifest.cells) - for (const run of cell.runs) if (COLLIDERS[run.asset]) this.#runs.push(run); - try { - this.#rebuild(); - } catch (error) { - this.detach(); - throw error; - } - } - - /** How many prop bodies exist right now. */ - get active(): number { - return this.#live.size; - } - - get released(): boolean { - return this.#released; - } - - attachRenderer(): void {} - - process(): void { - if (this.#released) return; - const p = this.follow.getWorldPosition(this.#followPosition); - if (Math.hypot(p.x - this.#at.x, p.z - this.#at.z) > 4 || Number.isNaN(this.#at.x)) - this.#rebuild(); - } - - detach(): void { - for (const body of this.#live.values()) body.dispose(); - this.#live.clear(); - this.#released = true; - } - - #rebuild(): void { - const { x: fx, z: fz } = this.follow.getWorldPosition(this.#followPosition); - this.#at.x = fx; - this.#at.z = fz; - const keep = new Set(); - for (const run of this.#runs) { - const shape = COLLIDERS[run.asset]; - if (!shape) continue; - for (let i = run.offset; i < run.offset + run.count; i++) { - const x = this.records[i * 8] as number; - const z = this.records[i * 8 + 2] as number; - if (Math.hypot(x - fx, z - fz) > PROP_COLLIDER_REACH) continue; - keep.add(i); - if (this.#live.has(i)) continue; - const collider = CollisionShape3D.sphere( - shape.sphere * (this.records[i * 8 + 7] as number), - ); - this.#live.set( - i, - new RigidBody3D({ - physics: this.ctx.physics, - type: "fixed", - entity: `${run.asset}.${String(i)}`, - position: { x, y: this.records[i * 8 + 1] as number, z }, - shape: collider, - }), - ); - } - } - for (const [i, body] of this.#live) - if (!keep.has(i)) { - body.dispose(); - this.#live.delete(i); - } - } -} - -/** - * The bake's heights on the world's grid, in metres, row-major by Z then X. The sea's shoreline, surf and - * depth tint are read from these, so they match the terrain the player stands on. - */ function coastalSeaData( manifest: IWorldPackage, heightmap: Uint16Array, @@ -370,17 +109,3 @@ function coastalSeaData( ); return { heights, resolution: terrain.columns, size: extent.sizeX, waterLevel: seaLevel }; } - -async function fetchAsset( - ctx: ICtx, IPhysicsContext>, - url: string, - assertCurrent: () => void, -): Promise { - const [resolved] = await ctx.assets.resolve(url); - assertCurrent(); - if (resolved === undefined) throw new Error(`Coastal world: '${url}' is not served.`); - const response = await fetch(resolved); - assertCurrent(); - if (!response.ok) throw new Error(`Coastal world: '${url}' answered ${String(response.status)}.`); - return response; -} diff --git a/packages/terrain/starter/desert/bake.mjs b/packages/terrain/starter/desert/bake.mjs index 1c1b93b163..70097ec850 100644 --- a/packages/terrain/starter/desert/bake.mjs +++ b/packages/terrain/starter/desert/bake.mjs @@ -1,118 +1,6 @@ -// Bakes this folder's recipe into a self-contained world package. Run it once from your game -// project, then serve the folder it writes: -// node src/terrain/desert/bake.mjs [--assets ] [--out ] -// The engine's `WorldCells` streams `world/world.json` and `loadTerrainSplat` textures its ground; -// the game never evaluates terrain at runtime. -import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { Terrain, applyPlacementOverrides, bakeWorldPackage } from "@threenative/terrain"; +// Run with [--assets ] [--out ]; defaults to installed assets and ./world. +import { bakeKit } from "@threenative/terrain/kit"; -const here = dirname(fileURLToPath(import.meta.url)); - -/** `--key value` after the script; `--assets` and `--out` default as noted below. */ -function flag(name, fallback) { - const at = process.argv.indexOf(`--${name}`); - return at === -1 ? fallback : resolve(process.argv[at + 1] ?? ""); -} - -// The installed kit ships its models and textures beside the package manifest, so a copied kit -// finds them without asking the game for a path. `--assets` overrides that for a local checkout. -const installed = dirname(fileURLToPath(import.meta.resolve("@threenative/terrain/package.json"))); -const assetsDir = flag("assets", join(installed, "starter-assets")); -const outDir = flag("out", join(here, "world")); - -const recipe = JSON.parse(await readFile(join(here, "recipe.json"), "utf8")); -const assetTable = JSON.parse(await readFile(join(here, "assets.json"), "utf8")); -const surface = JSON.parse(await readFile(join(here, "surface.json"), "utf8")); - -const state = applyPlacementOverrides( - Terrain.fromJSON(recipe.recipe).evaluate(), - recipe.placementOverrides ?? {}, -); - -const assets = Object.fromEntries( - Object.entries(assetTable).map(([id, spec]) => [ - id, - { - bounds: spec.bounds, - glb: `models/${spec.near.split("/").at(-1)}`, - ...(spec.mid - ? { lods: [{ distance: spec.midDistance, glb: `models/${spec.mid.split("/").at(-1)}` }] } - : {}), - maxDistance: spec.maxDistance, - }, - ]), -); - -const started = performance.now(); -const { manifest, splat, files } = bakeWorldPackage(state, { - assets, - layers: { table: "terrain-table.json" }, +await bakeKit(import.meta.url, { + sky: "hdri/kloofendal_48d_partly_cloudy_1k.hdr", }); -const written = new Map(); - -async function write(relative, bytes) { - await mkdir(join(outDir, dirname(relative)), { recursive: true }); - await writeFile(join(outDir, relative), bytes); - written.set(relative, bytes.length ?? bytes.byteLength); -} - -async function copy(from, to) { - await mkdir(join(outDir, dirname(to)), { recursive: true }); - await copyFile(join(assetsDir, from), join(outDir, to)); - written.set(to, (await readFile(join(outDir, to))).byteLength); -} - -await write("world.json", `${JSON.stringify(manifest, null, 2)}\n`); -// The lake and river the generator evaluated, so the game draws the water the bake shaped. -const round = (value) => Math.round(value * 1_000) / 1_000; -await write( - "water.json", - `${JSON.stringify( - { - lakes: state.waters.map((lake) => ({ - id: lake.id, - at: lake.at, - radius: lake.radius, - level: lake.level, - })), - rivers: state.rivers.map((river) => ({ - id: river.id, - width: round(river.width), - points: river.points.map((point) => point.map(round)), - })), - }, - null, - 2, - )}\n`, -); -for (const [name, bytes] of Object.entries(files)) await write(name, bytes); -for (const spec of Object.values(assetTable)) { - await copy(spec.near, `models/${spec.near.split("/").at(-1)}`); - if (spec.mid) await copy(spec.mid, `models/${spec.mid.split("/").at(-1)}`); -} -// The sky the props are lit by (CC0 Poly Haven Kloofendal); world.ts gives it to their materials. -await copy("hdri/kloofendal_48d_partly_cloudy_1k.hdr", "sky.hdr"); -for (const layer of [surface.base, ...surface.layers]) { - const source = surface.sources[layer.id]; - await copy(source.diff, `${surface.textures}/${layer.id}_diff.jpg`); - await copy(source.nrm, `${surface.textures}/${layer.id}_nrm.jpg`); -} -// `sources` is the bake's own lookup: the runtime reads only base/layers/splat/textures. -await write( - "terrain-table.json", - `${JSON.stringify({ ...surface, splat, textures: "textures" }, null, 2)}\n`, -); - -const perAsset = {}; -for (const cell of manifest.cells) - for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; -const total = [...written.values()].reduce((sum, bytes) => sum + bytes, 0); -console.log( - `${Object.entries(perAsset) - .map(([asset, count]) => `${asset} ${String(count)}`) - .join(", ")} placements; ${[...written] - .map(([name, bytes]) => `${name} ${String(bytes)} B`) - .join(", ")}; ${String(total)} B total; ${Math.round(performance.now() - started)} ms`, -); diff --git a/packages/terrain/starter/desert/heightmap.r16 b/packages/terrain/starter/desert/heightmap.r16 new file mode 100644 index 0000000000..9485861c44 Binary files /dev/null and b/packages/terrain/starter/desert/heightmap.r16 differ diff --git a/packages/terrain/starter/desert/recipe.json b/packages/terrain/starter/desert/recipe.json index a444319427..c020bfb709 100644 --- a/packages/terrain/starter/desert/recipe.json +++ b/packages/terrain/starter/desert/recipe.json @@ -17,66057 +17,9 @@ "data": { "width": 257, "height": 257, - "values": [ - 24.2, - 24.2, - 24.2, - 24.1, - 23.9, - 23.8, - 23.6, - 23.8, - 23.8, - 23.9, - 23.8, - 23.9, - 24.1, - 24.3, - 24.3, - 24.3, - 24.1, - 24.1, - 24.1, - 24.1, - 24.3, - 24.6, - 24.9, - 24.6, - 24.8, - 25.1, - 25.7, - 26, - 25.7, - 25.7, - 25.8, - 25.9, - 26.2, - 26.4, - 26.2, - 26.6, - 27.6, - 28.3, - 28, - 27.1, - 27, - 27.2, - 27.2, - 27.6, - 28.4, - 28.8, - 28.8, - 28.7, - 28.4, - 28.2, - 28.2, - 28.6, - 28.8, - 29.3, - 29.9, - 30.5, - 31.4, - 31.7, - 31.9, - 32.3, - 32.6, - 33, - 33.3, - 33.6, - 33.9, - 34.1, - 34.3, - 34.3, - 34.3, - 34.3, - 34.1, - 34, - 33.8, - 33.8, - 33.7, - 33.8, - 33.6, - 33.1, - 33, - 33.4, - 33.9, - 34.1, - 34.7, - 35.5, - 35.9, - 36.7, - 37.6, - 38.9, - 40.3, - 42.8, - 43.1, - 43.2, - 43.3, - 43.4, - 43.6, - 43.9, - 44.3, - 44.7, - 44.7, - 44.6, - 44.6, - 44.3, - 44.1, - 43.8, - 43.4, - 42.9, - 42.4, - 41.9, - 41.3, - 40.6, - 40.2, - 39.7, - 39.4, - 39.2, - 38.8, - 38.6, - 38.3, - 38.2, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.1, - 36.7, - 36.7, - 36.5, - 36.2, - 36, - 35.9, - 35.7, - 35.1, - 34.4, - 33.8, - 33.1, - 32.8, - 32.4, - 32.2, - 31.9, - 31.8, - 31.7, - 31.5, - 31.4, - 31.4, - 31.3, - 31.2, - 31.2, - 31.1, - 31, - 31, - 30.9, - 30.9, - 30.8, - 30.8, - 30.8, - 30.6, - 30.6, - 30.5, - 30.3, - 30.3, - 30.2, - 30.2, - 30.1, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.5, - 29.5, - 29.5, - 29.4, - 29.3, - 29.3, - 29.2, - 29.2, - 29.1, - 29.1, - 29.2, - 29.2, - 29.2, - 29.2, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.5, - 29.4, - 29.2, - 28.6, - 28.3, - 28.2, - 28.2, - 28.2, - 28.2, - 28.1, - 27.9, - 27.8, - 27.9, - 28.1, - 28.3, - 28.3, - 28.4, - 28.3, - 28.4, - 28.5, - 28.5, - 28.6, - 28.7, - 28.8, - 28.9, - 29, - 29.1, - 29.1, - 29.4, - 29.5, - 29.7, - 29.8, - 29.9, - 30.1, - 30.2, - 30.4, - 30.8, - 31, - 31.1, - 31.2, - 31.2, - 31.2, - 31.2, - 31.2, - 31.1, - 30.9, - 30.6, - 30.4, - 30.3, - 30.1, - 29.9, - 29.8, - 29.7, - 29.6, - 29.3, - 29.1, - 29, - 28.9, - 28.9, - 28.9, - 29.1, - 29.1, - 29, - 23.9, - 23.8, - 23.7, - 23.4, - 23.3, - 23.1, - 23.2, - 23.3, - 23.4, - 23.3, - 23.4, - 23.5, - 23.7, - 23.8, - 23.8, - 23.9, - 23.9, - 23.9, - 24.1, - 24.1, - 24.3, - 24.6, - 24.6, - 24.7, - 25.1, - 25.3, - 26, - 26.9, - 26.8, - 26.3, - 26.2, - 26.3, - 26.6, - 26.9, - 26.7, - 26.3, - 27, - 27.5, - 27.3, - 26.8, - 27.4, - 28, - 27.7, - 27.3, - 27.7, - 28, - 28.1, - 28, - 27.9, - 28.1, - 28.3, - 28.8, - 29.1, - 29.5, - 29.9, - 30.4, - 31.3, - 31.9, - 32.3, - 32.7, - 33, - 33.3, - 33.7, - 34, - 34.1, - 34.2, - 34.3, - 34.2, - 34.1, - 33.9, - 33.7, - 33.3, - 33.1, - 33.1, - 33.1, - 33.2, - 33.1, - 32.9, - 33, - 33.3, - 33.8, - 34.1, - 34.8, - 35.6, - 35.9, - 36.6, - 37.3, - 38.4, - 39.8, - 42.3, - 43.3, - 43.2, - 43.3, - 43.4, - 43.7, - 43.8, - 44.1, - 44.5, - 44.8, - 44.7, - 44.6, - 44.4, - 44.1, - 43.8, - 43.4, - 43, - 42.4, - 41.9, - 41.4, - 40.7, - 40.3, - 39.9, - 39.6, - 39.3, - 38.9, - 38.5, - 38.3, - 38.2, - 38, - 37.9, - 37.8, - 37.8, - 37.6, - 37.5, - 37.3, - 37.3, - 37.2, - 37.1, - 36.8, - 36.3, - 36, - 35.9, - 35.5, - 35, - 34.3, - 33.7, - 33.1, - 32.8, - 32.4, - 32.2, - 32, - 31.8, - 31.7, - 31.5, - 31.5, - 31.4, - 31.3, - 31.1, - 31.1, - 31, - 31, - 31, - 30.9, - 30.9, - 30.8, - 30.8, - 30.7, - 30.6, - 30.6, - 30.5, - 30.3, - 30.3, - 30.2, - 30.2, - 30.2, - 30, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.6, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.3, - 29.2, - 29.2, - 29, - 29.1, - 29, - 29, - 29.1, - 29.2, - 29.2, - 29.3, - 29.2, - 29.2, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.3, - 29, - 28.4, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 27.7, - 27.5, - 27.7, - 27.9, - 28.2, - 28.3, - 28.4, - 28, - 28.2, - 28.3, - 28.3, - 28.5, - 28.6, - 28.7, - 28.8, - 28.9, - 28.9, - 29.1, - 29.2, - 29.4, - 29.5, - 29.5, - 29.5, - 29.7, - 30, - 30.2, - 30.5, - 30.8, - 31, - 31, - 31.2, - 31.2, - 31.2, - 31.2, - 31.2, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 29.9, - 29.8, - 29.7, - 29.6, - 29.3, - 29.1, - 29, - 28.9, - 28.8, - 28.8, - 28.9, - 28.9, - 28.9, - 23.9, - 23.6, - 23.4, - 23, - 22.9, - 22.9, - 23, - 23.1, - 23.1, - 23.2, - 23.3, - 23.4, - 23.5, - 23.6, - 23.7, - 23.8, - 24, - 24.1, - 24.1, - 24.2, - 24.3, - 24.6, - 24.9, - 25.5, - 26.1, - 26.1, - 26.5, - 27.5, - 27.8, - 27.4, - 27.1, - 27, - 27, - 27.6, - 27.4, - 26.7, - 26.5, - 27, - 26.9, - 26.9, - 27.9, - 28.9, - 28.7, - 27.9, - 27.5, - 27.6, - 27.7, - 27.9, - 28.1, - 28.3, - 28.7, - 29.3, - 29.7, - 30.2, - 30.6, - 31, - 31.5, - 32, - 32.5, - 33, - 33.2, - 33.5, - 33.9, - 34.1, - 34.1, - 34.2, - 34.2, - 34.1, - 33.8, - 33.5, - 33.1, - 32.8, - 32.6, - 32.6, - 32.7, - 32.7, - 32.7, - 32.8, - 33, - 33.3, - 33.7, - 34.1, - 34.7, - 35.5, - 35.9, - 36.5, - 37.3, - 38.3, - 39.5, - 41.9, - 43.3, - 43.4, - 43.3, - 43.5, - 43.9, - 44, - 44.1, - 44.4, - 44.7, - 44.8, - 44.7, - 44.3, - 44, - 43.7, - 43.4, - 42.9, - 42.4, - 41.9, - 41.5, - 41, - 40.6, - 40.2, - 39.8, - 39.4, - 38.9, - 38.6, - 38.4, - 38.3, - 38.1, - 38, - 38, - 37.9, - 37.8, - 37.6, - 37.5, - 37.3, - 37.3, - 37.2, - 36.8, - 36.2, - 35.9, - 35.7, - 35.1, - 34.7, - 34.1, - 33.5, - 33, - 32.7, - 32.4, - 32.2, - 32, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.2, - 31.1, - 31.1, - 31, - 30.9, - 30.9, - 30.8, - 30.8, - 30.8, - 30.8, - 30.7, - 30.6, - 30.5, - 30.4, - 30.4, - 30.3, - 30.2, - 30.2, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.7, - 29.6, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.2, - 29, - 29, - 28.9, - 28.9, - 28.9, - 29.1, - 29.2, - 29.2, - 29.2, - 29.2, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 28.9, - 28.4, - 28.1, - 28, - 28, - 27.8, - 27.6, - 27.3, - 27.4, - 27.6, - 27.8, - 28, - 28, - 27.6, - 27.8, - 27.8, - 27.9, - 28.2, - 28.6, - 28.7, - 28.7, - 28.8, - 28.9, - 29, - 29.1, - 29.2, - 29.3, - 29.3, - 29.3, - 29.5, - 29.7, - 29.9, - 30.2, - 30.4, - 30.6, - 30.8, - 31.1, - 31.2, - 31.2, - 31.2, - 31.2, - 31.1, - 31, - 30.7, - 30.5, - 30.3, - 30.1, - 29.9, - 29.7, - 29.6, - 29.4, - 29.1, - 29, - 28.9, - 28.8, - 28.8, - 28.7, - 28.7, - 28.8, - 23.7, - 23.5, - 23, - 22.7, - 23, - 23, - 23, - 23.2, - 23.3, - 23.3, - 23.4, - 23.6, - 23.7, - 23.7, - 24, - 24.1, - 24.5, - 25.1, - 25.5, - 25.7, - 25.6, - 26.1, - 26.4, - 26.9, - 27.5, - 27.2, - 27.4, - 28.1, - 28.4, - 28.3, - 28.2, - 27.8, - 27.7, - 28.3, - 28.4, - 27.7, - 26.8, - 26.7, - 26.7, - 27.1, - 28.2, - 29.4, - 29.6, - 28.9, - 28.1, - 27.7, - 27.9, - 28.5, - 28.7, - 28.7, - 29.1, - 29.6, - 30.2, - 30.6, - 30.9, - 31.2, - 31.6, - 32, - 32.6, - 33, - 33.3, - 33.6, - 34, - 34.1, - 34.1, - 34.1, - 34.1, - 33.9, - 33.4, - 33, - 32.6, - 32.2, - 32, - 32.2, - 32.4, - 32.5, - 32.7, - 32.9, - 33.2, - 33.6, - 33.9, - 34.4, - 34.9, - 35.5, - 35.9, - 36.5, - 37.3, - 38.2, - 39.5, - 41.3, - 43.2, - 43.5, - 43.4, - 43.5, - 44, - 44, - 44, - 44.2, - 44.6, - 44.9, - 44.9, - 44.6, - 44.3, - 43.9, - 43.6, - 43.1, - 42.6, - 42.1, - 41.9, - 41.6, - 41.1, - 40.5, - 40.1, - 39.6, - 39.2, - 38.9, - 38.6, - 38.4, - 38.2, - 38.1, - 38, - 37.9, - 37.8, - 37.6, - 37.5, - 37.3, - 37.2, - 37, - 36.5, - 35.9, - 35.5, - 35.2, - 34.8, - 34.3, - 33.7, - 33.2, - 32.9, - 32.6, - 32.3, - 32.1, - 31.9, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.3, - 31.2, - 31.1, - 31, - 31, - 30.9, - 30.8, - 30.7, - 30.7, - 30.7, - 30.6, - 30.6, - 30.5, - 30.4, - 30.3, - 30.2, - 30.2, - 30.1, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.8, - 29.7, - 29.7, - 29.6, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.9, - 29, - 29.2, - 29.1, - 29.1, - 29.1, - 29.2, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.4, - 29.2, - 28.6, - 28.1, - 27.8, - 27.7, - 27.6, - 27.5, - 27.3, - 27.2, - 27.4, - 27.4, - 27.5, - 27.5, - 27.4, - 27.5, - 27.5, - 27.6, - 27.9, - 28.2, - 28.5, - 28.7, - 28.7, - 28.8, - 28.9, - 29, - 29, - 29.1, - 29.1, - 29.2, - 29.4, - 29.5, - 29.6, - 29.8, - 30, - 30.2, - 30.5, - 30.8, - 31.1, - 31.1, - 31.1, - 31.2, - 31.1, - 31.1, - 30.8, - 30.6, - 30.4, - 30.3, - 30.1, - 29.7, - 29.6, - 29.5, - 29.2, - 29, - 28.9, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 23.6, - 23.2, - 22.6, - 23, - 23.3, - 23.3, - 23.2, - 23.4, - 23.6, - 23.5, - 23.6, - 23.8, - 24, - 23.9, - 24.1, - 24.4, - 24.9, - 25.8, - 26.8, - 27.4, - 27.6, - 27.8, - 27.8, - 28.1, - 28.2, - 28.1, - 28.4, - 28.7, - 28.8, - 28.9, - 28.8, - 28.6, - 28.4, - 28.9, - 29.3, - 29.1, - 28.4, - 27.8, - 27.9, - 28.1, - 28.5, - 29.5, - 29.9, - 29.5, - 28.8, - 28.2, - 28.1, - 29, - 29.5, - 29.4, - 29.5, - 29.9, - 30.2, - 30.6, - 30.8, - 31.2, - 31.5, - 31.8, - 32.4, - 32.9, - 33.4, - 33.7, - 34, - 34.1, - 34.1, - 34, - 33.8, - 33.5, - 33, - 32.6, - 32.1, - 31.3, - 31.5, - 32.2, - 32.5, - 32.7, - 33, - 33.4, - 33.7, - 34, - 34.3, - 34.9, - 35.4, - 35.6, - 36, - 36.5, - 37.1, - 38.1, - 39.4, - 41.5, - 43, - 43.5, - 43.5, - 43.5, - 43.7, - 43.9, - 44, - 44.2, - 44.6, - 45, - 45.1, - 44.9, - 44.5, - 44.1, - 43.8, - 43.3, - 42.8, - 42.3, - 42.2, - 42, - 41.5, - 40.9, - 40.4, - 40, - 39.5, - 39.1, - 38.8, - 38.5, - 38.3, - 38.2, - 38, - 37.9, - 37.8, - 37.6, - 37.4, - 37.3, - 37.1, - 36.6, - 36, - 35.5, - 35, - 34.7, - 34.4, - 33.8, - 33.3, - 33, - 32.7, - 32.4, - 32.3, - 32.1, - 31.9, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.4, - 31.2, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.8, - 30.7, - 30.6, - 30.5, - 30.5, - 30.4, - 30.3, - 30.3, - 30.2, - 30.2, - 30.1, - 30.1, - 30, - 29.9, - 29.9, - 29.8, - 29.8, - 29.7, - 29.7, - 29.7, - 29.6, - 29.6, - 29.5, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29.1, - 28.9, - 28.8, - 28.8, - 28.9, - 29.1, - 29, - 29, - 29.1, - 29.2, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 28.7, - 28.1, - 27.7, - 27.4, - 27.5, - 27.4, - 27.3, - 27.1, - 27.2, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.4, - 27.6, - 27.8, - 28.1, - 28.4, - 28.5, - 28.7, - 28.8, - 28.8, - 28.8, - 28.9, - 29, - 29.1, - 29.2, - 29.3, - 29.3, - 29.4, - 29.7, - 29.9, - 30.2, - 30.5, - 30.8, - 31, - 31, - 31.1, - 31, - 30.9, - 30.7, - 30.5, - 30.4, - 30.4, - 30.3, - 29.8, - 29.6, - 29.4, - 29.2, - 29, - 28.9, - 28.9, - 28.8, - 28.7, - 28.5, - 28.6, - 23.3, - 22.8, - 22.5, - 23.3, - 23.5, - 23.4, - 23.3, - 23.4, - 23.5, - 23.6, - 23.7, - 23.9, - 24.1, - 24.1, - 24.3, - 24.8, - 25.7, - 26.5, - 27.4, - 27.9, - 28.2, - 28.3, - 28.5, - 28.6, - 28.6, - 28.6, - 28.8, - 29, - 29.2, - 29.3, - 29.3, - 29.2, - 29.1, - 29.3, - 29.7, - 29.9, - 29.9, - 29.6, - 29.5, - 29.3, - 29.3, - 29.9, - 30.2, - 30, - 29.6, - 28.9, - 28.5, - 28.6, - 29.4, - 29.8, - 30, - 30.2, - 30.5, - 30.6, - 30.6, - 30.7, - 31.1, - 31.5, - 32.1, - 32.7, - 33.3, - 33.6, - 33.9, - 34.1, - 34, - 33.7, - 33.3, - 32.9, - 32.5, - 31.7, - 31.1, - 30.9, - 31.8, - 32.3, - 32.7, - 33, - 33.4, - 33.8, - 34.1, - 34.4, - 34.8, - 35.3, - 35.7, - 35.9, - 36.2, - 36.6, - 37.2, - 38, - 39.4, - 41.5, - 42.7, - 43.4, - 43.5, - 43.6, - 43.7, - 43.8, - 44.1, - 44.3, - 44.7, - 45.3, - 45.6, - 45.3, - 44.9, - 44.5, - 44, - 43.7, - 43.2, - 42.7, - 42.4, - 42.1, - 41.8, - 41.2, - 40.8, - 40.6, - 40, - 39.5, - 38.9, - 38.5, - 38.3, - 38.2, - 38, - 37.8, - 37.7, - 37.5, - 37.4, - 37.3, - 37.1, - 36.4, - 35.7, - 35, - 34.5, - 34.1, - 33.6, - 33.1, - 32.9, - 32.7, - 32.6, - 32.4, - 32.2, - 32.1, - 32, - 31.9, - 31.7, - 31.6, - 31.5, - 31.4, - 31.4, - 31.3, - 31.2, - 31.1, - 31, - 30.9, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.5, - 30.5, - 30.4, - 30.3, - 30.2, - 30.2, - 30.1, - 30.1, - 30, - 30, - 30, - 29.9, - 29.9, - 29.8, - 29.8, - 29.8, - 29.6, - 29.6, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.8, - 28.8, - 28.9, - 28.9, - 29, - 29.1, - 29.2, - 29.2, - 29.3, - 29.3, - 29.4, - 29.3, - 29.3, - 29.1, - 28.7, - 28, - 27.7, - 27.5, - 27.3, - 27.3, - 27.3, - 27.2, - 27.1, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.4, - 27.6, - 27.7, - 27.8, - 28.1, - 28.4, - 28.6, - 28.6, - 28.5, - 28.6, - 28.8, - 28.9, - 29, - 29, - 29, - 29.1, - 29.3, - 29.5, - 29.8, - 30.1, - 30.5, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.4, - 30.3, - 30.4, - 30.3, - 30.2, - 29.7, - 29.6, - 29.3, - 29.1, - 28.9, - 28.8, - 28.8, - 28.8, - 28.7, - 28.4, - 28.5, - 22.9, - 22.5, - 22.7, - 23.3, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.7, - 23.9, - 24.1, - 24.2, - 24.5, - 25, - 25.5, - 26.4, - 27.2, - 27.7, - 28.1, - 28.3, - 28.6, - 28.7, - 28.8, - 28.9, - 28.9, - 29, - 29.2, - 29.4, - 29.5, - 29.6, - 29.6, - 29.7, - 29.8, - 30, - 30.2, - 30.3, - 30.4, - 30.3, - 30.1, - 30.1, - 30.4, - 30.5, - 30.5, - 30.3, - 29.8, - 29.3, - 28.8, - 28.9, - 29.4, - 30, - 30.3, - 30.4, - 30.4, - 30.3, - 30.4, - 30.7, - 31.3, - 31.9, - 32.5, - 33.2, - 33.6, - 33.7, - 33.9, - 33.7, - 33.2, - 32.8, - 32.5, - 31.8, - 30.5, - 29.8, - 30.4, - 32, - 32.4, - 32.9, - 33.5, - 33.8, - 34.1, - 34.5, - 34.9, - 35.3, - 35.6, - 35.8, - 36.1, - 36.3, - 36.7, - 37.2, - 38, - 39.2, - 40.9, - 42.3, - 43.1, - 43.5, - 43.6, - 43.7, - 43.8, - 44, - 44.3, - 44.8, - 45.6, - 46, - 45.6, - 45.3, - 44.8, - 44.3, - 44, - 43.6, - 43, - 42.6, - 42.3, - 41.9, - 41.6, - 41.4, - 41.3, - 40.7, - 40, - 39.1, - 38.5, - 38.2, - 38.1, - 37.9, - 37.8, - 37.6, - 37.4, - 37.3, - 37.2, - 37, - 36.1, - 35.3, - 34.8, - 34.3, - 33.5, - 33, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 31.6, - 31.6, - 31.5, - 31.4, - 31.3, - 31.2, - 31.1, - 31, - 30.9, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.5, - 30.4, - 30.4, - 30.3, - 30.3, - 30.3, - 30.3, - 30.2, - 30.1, - 30.1, - 30.1, - 30, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 28.9, - 28.8, - 28.8, - 28.7, - 28.7, - 28.7, - 28.8, - 29, - 29.1, - 29.2, - 29.2, - 29.3, - 29.3, - 29.3, - 29.1, - 28.9, - 28.4, - 27.9, - 27.7, - 27.6, - 27.4, - 27.4, - 27.3, - 27.2, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.2, - 27.2, - 27.3, - 27.4, - 27.5, - 27.7, - 27.9, - 28, - 28.1, - 28.1, - 28.2, - 28.3, - 28.5, - 28.5, - 28.6, - 28.6, - 28.8, - 29, - 29.2, - 29.4, - 29.7, - 30.1, - 30.5, - 30.4, - 30.1, - 30.1, - 30.1, - 30.1, - 30.1, - 30.2, - 30.2, - 29.9, - 29.6, - 29.4, - 29.2, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.5, - 28.3, - 28.4, - 22.2, - 22.3, - 22.8, - 23.3, - 23.4, - 23.5, - 23.6, - 23.6, - 23.8, - 24, - 24.2, - 24.5, - 24.7, - 25.2, - 26, - 26.5, - 27, - 27.6, - 27.9, - 28.2, - 28.4, - 28.6, - 28.7, - 28.9, - 29, - 29, - 29.1, - 29.3, - 29.4, - 29.6, - 29.7, - 29.8, - 30, - 30.1, - 30.2, - 30.4, - 30.5, - 30.5, - 30.6, - 30.5, - 30.6, - 30.7, - 30.8, - 30.8, - 30.7, - 30.5, - 30.2, - 29.7, - 29, - 29.1, - 29.4, - 29.8, - 30, - 30, - 29.9, - 30.1, - 30.4, - 31, - 31.8, - 32.4, - 33, - 33.3, - 33.3, - 33.3, - 33.1, - 32.8, - 32.4, - 32, - 31, - 29.9, - 29.4, - 30.5, - 32.1, - 32.6, - 33.2, - 33.8, - 34.1, - 34.4, - 34.8, - 35.3, - 35.5, - 35.7, - 35.9, - 36.1, - 36.4, - 36.7, - 37.2, - 37.9, - 38.9, - 40.3, - 41.7, - 42.8, - 43.3, - 43.6, - 43.6, - 43.8, - 44, - 44.3, - 44.8, - 45.6, - 46.1, - 45.9, - 45.6, - 45.2, - 44.6, - 44.1, - 43.9, - 43.3, - 42.8, - 42.5, - 42, - 41.8, - 41.8, - 41.7, - 41.2, - 40.2, - 39.3, - 38.6, - 38.2, - 38.1, - 37.9, - 37.7, - 37.5, - 37.4, - 37.2, - 37.1, - 36.9, - 36, - 35.2, - 34.6, - 34.2, - 33.6, - 33, - 32.8, - 32.6, - 32.4, - 32.3, - 32.2, - 32, - 32, - 31.9, - 31.8, - 31.7, - 31.6, - 31.6, - 31.5, - 31.4, - 31.2, - 31.2, - 31.1, - 31.1, - 30.9, - 30.8, - 30.8, - 30.7, - 30.7, - 30.6, - 30.5, - 30.5, - 30.5, - 30.4, - 30.5, - 30.5, - 30.4, - 30.3, - 30.3, - 30.3, - 30.1, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.6, - 29.5, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.8, - 28.8, - 28.8, - 28.7, - 28.6, - 28.6, - 28.6, - 28.8, - 28.9, - 29.1, - 29.2, - 29.3, - 29.3, - 29.1, - 28.7, - 28.2, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 27, - 27, - 27, - 27.1, - 27.1, - 27.1, - 27.2, - 27.2, - 27.4, - 27.5, - 27.6, - 27.7, - 27.8, - 27.8, - 27.9, - 27.9, - 28, - 28.1, - 28.2, - 28.5, - 28.8, - 28.9, - 29, - 29.3, - 29.6, - 29.9, - 29.9, - 29.7, - 29.7, - 29.8, - 29.7, - 29.7, - 29.9, - 29.7, - 29.5, - 29.4, - 29.2, - 29, - 28.8, - 28.8, - 28.7, - 28.6, - 28.6, - 28.4, - 28.2, - 28.3, - 21.1, - 22.3, - 22.9, - 23.2, - 23.4, - 23.5, - 23.7, - 23.9, - 24.2, - 24.5, - 24.8, - 25.1, - 25.7, - 26.3, - 27, - 27.3, - 27.5, - 27.8, - 28, - 28.1, - 28.3, - 28.5, - 28.6, - 28.7, - 28.9, - 29, - 29.2, - 29.3, - 29.4, - 29.6, - 29.8, - 29.9, - 30.1, - 30.2, - 30.3, - 30.5, - 30.6, - 30.7, - 30.7, - 30.7, - 30.7, - 30.8, - 30.9, - 30.9, - 30.9, - 30.8, - 30.6, - 30.1, - 29.6, - 29.5, - 29.3, - 29.5, - 29.6, - 29.9, - 30, - 30.1, - 30.5, - 31, - 31.7, - 32.3, - 32.8, - 32.9, - 32.9, - 32.8, - 32.7, - 32.4, - 32, - 31.2, - 30.2, - 29.4, - 29.6, - 30.5, - 32, - 32.6, - 33.2, - 33.9, - 34.1, - 34.4, - 34.8, - 35.3, - 35.5, - 35.8, - 36, - 36.2, - 36.4, - 36.7, - 37.2, - 37.7, - 38.7, - 39.8, - 41.3, - 42.4, - 43.2, - 43.5, - 43.6, - 43.7, - 43.9, - 44.2, - 44.7, - 45.6, - 46.1, - 46, - 45.8, - 45.5, - 44.9, - 44.3, - 44, - 43.6, - 43.1, - 42.7, - 42.4, - 42.1, - 42, - 41.7, - 41, - 40, - 39.3, - 38.8, - 38.4, - 38.2, - 38, - 37.7, - 37.5, - 37.4, - 37.2, - 37.1, - 36.7, - 36, - 35.4, - 34.8, - 34.5, - 34, - 33.5, - 33.1, - 32.9, - 32.7, - 32.5, - 32.3, - 32.1, - 31.9, - 31.8, - 31.7, - 31.6, - 31.6, - 31.6, - 31.5, - 31.3, - 31.2, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.8, - 30.8, - 30.8, - 30.7, - 30.7, - 30.7, - 30.6, - 30.5, - 30.6, - 30.6, - 30.5, - 30.5, - 30.4, - 30.3, - 30.1, - 30.1, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.5, - 29.5, - 29.4, - 29.4, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.8, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.5, - 28.8, - 29, - 29.2, - 29.2, - 29.1, - 28.6, - 28.1, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.4, - 27.4, - 27.3, - 27.3, - 27.3, - 27.2, - 27.1, - 27, - 27, - 27, - 27, - 27, - 27, - 27.1, - 27.2, - 27.3, - 27.4, - 27.5, - 27.5, - 27.6, - 27.6, - 27.7, - 27.8, - 27.8, - 27.9, - 28.2, - 28.4, - 28.6, - 28.6, - 28.9, - 29.1, - 29.3, - 29.4, - 29.3, - 29.3, - 29.3, - 29.2, - 29.1, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 28.5, - 28.3, - 28.1, - 28.2, - 21.1, - 22.6, - 23.1, - 23.3, - 23.4, - 23.6, - 23.8, - 24.3, - 24.8, - 25.3, - 25.7, - 26.3, - 26.9, - 27.4, - 27.6, - 27.6, - 27.7, - 27.8, - 27.9, - 28, - 28.1, - 28.3, - 28.5, - 28.7, - 28.9, - 29.1, - 29.2, - 29.3, - 29.5, - 29.7, - 29.8, - 29.9, - 30.1, - 30.3, - 30.4, - 30.6, - 30.7, - 30.7, - 30.8, - 30.8, - 30.8, - 30.9, - 31, - 31, - 31, - 30.9, - 30.7, - 30.4, - 30.2, - 30.2, - 30.1, - 30.3, - 30.3, - 30.5, - 30.6, - 30.6, - 30.9, - 31.3, - 31.7, - 32.1, - 32.4, - 32.4, - 32.4, - 32.4, - 32.3, - 31.9, - 31.4, - 30.8, - 29.9, - 29.1, - 29.7, - 30.6, - 32, - 32.5, - 33.1, - 33.8, - 34.1, - 34.2, - 34.5, - 35.1, - 35.5, - 35.7, - 35.9, - 36.2, - 36.4, - 36.8, - 37.2, - 37.6, - 38.4, - 39.4, - 41.2, - 42.4, - 43, - 43.3, - 43.6, - 43.7, - 43.8, - 44.1, - 44.6, - 45.3, - 45.9, - 46.1, - 46, - 45.7, - 45.2, - 44.7, - 44.3, - 43.7, - 43.3, - 43, - 42.8, - 42.4, - 42.1, - 41.8, - 40.8, - 40, - 39.4, - 38.9, - 38.5, - 38.2, - 38, - 37.8, - 37.6, - 37.5, - 37.2, - 37.1, - 36.7, - 36.2, - 35.8, - 35.3, - 35.1, - 34.7, - 34.2, - 33.7, - 33.4, - 33.1, - 32.9, - 32.6, - 32.2, - 32.1, - 31.9, - 31.8, - 31.7, - 31.5, - 31.4, - 31.4, - 31.3, - 31.2, - 31.1, - 31, - 31, - 30.9, - 31, - 30.9, - 30.9, - 30.8, - 30.8, - 30.8, - 30.7, - 30.7, - 30.6, - 30.7, - 30.7, - 30.6, - 30.5, - 30.4, - 30.2, - 30.1, - 30.1, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.8, - 28.9, - 29, - 29, - 28.8, - 28.6, - 28.4, - 28.4, - 28.8, - 29.1, - 29.2, - 29, - 28.4, - 27.9, - 27.8, - 27.8, - 27.6, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 27, - 27, - 26.9, - 26.9, - 26.9, - 27, - 27.1, - 27.3, - 27.3, - 27.4, - 27.4, - 27.5, - 27.5, - 27.6, - 27.5, - 27.7, - 27.9, - 28.1, - 28.2, - 28.2, - 28.5, - 28.7, - 28.8, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.7, - 28.8, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.7, - 28.6, - 28.6, - 28.5, - 28.3, - 28.1, - 28, - 22.4, - 22.8, - 23.1, - 23.3, - 23.4, - 23.7, - 24, - 24.7, - 25.7, - 26.4, - 26.8, - 27.2, - 27.5, - 27.7, - 27.8, - 27.7, - 27.7, - 27.7, - 27.7, - 27.9, - 28.2, - 28.5, - 28.7, - 28.9, - 29.1, - 29.2, - 29.4, - 29.5, - 29.6, - 29.7, - 29.8, - 30, - 30.2, - 30.3, - 30.5, - 30.6, - 30.7, - 30.7, - 30.8, - 30.8, - 30.8, - 30.9, - 30.9, - 30.9, - 31, - 31, - 30.9, - 30.8, - 30.7, - 30.8, - 30.8, - 31, - 31, - 31.1, - 31.1, - 31, - 31.1, - 31.3, - 31.5, - 31.7, - 31.9, - 31.8, - 31.8, - 31.9, - 31.8, - 31.5, - 31.1, - 30.5, - 29.7, - 29, - 29.7, - 30.8, - 32, - 32.3, - 32.8, - 33.4, - 33.9, - 34.1, - 34.3, - 34.8, - 35.4, - 35.7, - 35.9, - 36.2, - 36.5, - 36.7, - 37.1, - 37.5, - 38.2, - 39, - 40.9, - 42.4, - 42.7, - 42.9, - 43.3, - 43.7, - 43.8, - 44, - 44.4, - 45.1, - 45.8, - 46, - 46, - 45.8, - 45.3, - 44.9, - 44.6, - 44.1, - 43.5, - 43.2, - 43, - 42.8, - 42.3, - 41.8, - 41, - 40.1, - 39.5, - 38.9, - 38.4, - 38.2, - 38.1, - 37.8, - 37.6, - 37.4, - 37.3, - 37.1, - 37, - 36.5, - 35.9, - 35.7, - 35.6, - 35.3, - 34.7, - 34.2, - 33.8, - 33.6, - 33.3, - 32.9, - 32.5, - 32.2, - 32.1, - 31.9, - 31.7, - 31.6, - 31.5, - 31.3, - 31.3, - 31.2, - 31.2, - 31.1, - 31, - 31, - 31, - 31, - 31, - 30.9, - 30.8, - 30.9, - 30.8, - 30.8, - 30.8, - 30.7, - 30.7, - 30.6, - 30.5, - 30.4, - 30.2, - 30.2, - 30.1, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.3, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 28.9, - 29, - 29, - 29, - 28.8, - 28.6, - 28.3, - 28.4, - 28.8, - 28.9, - 28.6, - 28.2, - 27.9, - 27.9, - 27.7, - 27.5, - 27.5, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 27.1, - 27.1, - 27.2, - 27.3, - 27.3, - 27.4, - 27.4, - 27.4, - 27.6, - 27.7, - 27.8, - 28, - 28, - 28.2, - 28.3, - 28.4, - 28.5, - 28.6, - 28.5, - 28.5, - 28.5, - 28.5, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.4, - 28.1, - 27.9, - 23.2, - 23.3, - 23.3, - 23.4, - 23.6, - 23.9, - 24.4, - 25.4, - 26.5, - 27.1, - 27.3, - 27.5, - 27.6, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.6, - 28, - 28.4, - 28.6, - 28.9, - 29.1, - 29.2, - 29.4, - 29.5, - 29.6, - 29.7, - 29.8, - 29.9, - 30, - 30.2, - 30.3, - 30.5, - 30.6, - 30.7, - 30.7, - 30.8, - 30.8, - 30.8, - 30.9, - 30.8, - 30.9, - 31, - 31.1, - 31.2, - 31.1, - 31.1, - 31.1, - 31.2, - 31.3, - 31.3, - 31.2, - 31.1, - 31.1, - 31, - 31, - 31.2, - 31.4, - 31.3, - 31.1, - 31.1, - 31.4, - 31.5, - 31.2, - 30.8, - 30.5, - 29.5, - 28.9, - 29.9, - 30.7, - 31.7, - 32, - 32.4, - 32.8, - 33.3, - 33.9, - 34.2, - 34.7, - 35.4, - 35.7, - 35.9, - 36.1, - 36.4, - 36.7, - 36.9, - 37.2, - 37.9, - 38.7, - 40, - 41.7, - 42.3, - 42.6, - 42.9, - 43.6, - 43.8, - 44, - 44.3, - 44.9, - 45.6, - 46, - 46.1, - 45.9, - 45.6, - 45.1, - 44.8, - 44.3, - 43.7, - 43.4, - 43.2, - 43, - 42.5, - 42, - 41.4, - 40.4, - 39.6, - 39.1, - 38.7, - 38.4, - 38.2, - 37.9, - 37.7, - 37.5, - 37.4, - 37.3, - 37.1, - 36.7, - 36.2, - 36, - 35.9, - 35.7, - 35.2, - 34.7, - 34.4, - 34.1, - 33.7, - 33.3, - 32.8, - 32.5, - 32.3, - 32, - 31.8, - 31.6, - 31.5, - 31.4, - 31.3, - 31.2, - 31.2, - 31.1, - 31.1, - 31.1, - 31.1, - 31.1, - 31, - 30.9, - 30.9, - 30.9, - 30.9, - 30.9, - 30.8, - 30.8, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.2, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 29.3, - 29.2, - 28.9, - 28.9, - 28.9, - 29, - 29.1, - 29, - 29, - 28.8, - 28.5, - 28.3, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27, - 26.9, - 26.8, - 26.9, - 26.9, - 26.8, - 26.9, - 27, - 27.1, - 27.1, - 27.2, - 27.2, - 27.2, - 27.3, - 27.4, - 27.5, - 27.6, - 27.7, - 27.9, - 28, - 28.1, - 28.2, - 28.2, - 28.3, - 28.4, - 28.3, - 28.3, - 28.4, - 28.4, - 28.4, - 28.4, - 28.4, - 28.4, - 28.5, - 28.5, - 28.6, - 28.6, - 28.6, - 28.6, - 28.4, - 28.1, - 27.9, - 23.4, - 23.5, - 23.4, - 23.5, - 23.6, - 24, - 24.6, - 25.9, - 26.8, - 27.1, - 27.3, - 27.4, - 27.5, - 27.5, - 27.4, - 27.3, - 27.3, - 27.2, - 27.6, - 28.1, - 28.4, - 28.7, - 28.9, - 29.1, - 29.3, - 29.4, - 29.6, - 29.7, - 29.8, - 29.9, - 30, - 30.1, - 30.2, - 30.3, - 30.4, - 30.5, - 30.5, - 30.6, - 30.7, - 30.7, - 30.7, - 30.7, - 30.8, - 30.9, - 31, - 31.2, - 31.3, - 31.3, - 31.3, - 31.4, - 31.4, - 31.3, - 31.3, - 31.2, - 31, - 30.9, - 30.8, - 30.8, - 31, - 31.1, - 30.8, - 30.3, - 30.3, - 31.1, - 31.3, - 31, - 30.5, - 30.2, - 29.4, - 28.8, - 29.8, - 30.5, - 31.2, - 31.5, - 31.8, - 32.3, - 32.8, - 33.7, - 34.2, - 34.9, - 35.4, - 35.6, - 35.8, - 36.1, - 36.3, - 36.6, - 36.8, - 37, - 37.5, - 38.2, - 39.1, - 40.3, - 42, - 42.4, - 42.7, - 43.5, - 43.8, - 43.9, - 44.3, - 44.9, - 45.6, - 46.1, - 46.1, - 46, - 45.8, - 45.4, - 44.9, - 44.2, - 43.7, - 43.5, - 43.3, - 43.1, - 42.6, - 42.3, - 41.8, - 40.8, - 40.1, - 39.5, - 39, - 38.6, - 38.2, - 37.9, - 37.8, - 37.7, - 37.5, - 37.4, - 37.2, - 37, - 36.8, - 36.5, - 36.3, - 36.1, - 35.7, - 35.2, - 34.9, - 34.5, - 34, - 33.4, - 32.9, - 32.5, - 32.2, - 32, - 31.7, - 31.6, - 31.5, - 31.5, - 31.4, - 31.3, - 31.2, - 31.2, - 31.2, - 31.2, - 31.2, - 31.2, - 31.1, - 31, - 31, - 31, - 30.9, - 30.9, - 30.9, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 30, - 29.9, - 29.8, - 29.7, - 29.7, - 29.6, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.3, - 29.2, - 29, - 28.9, - 28.9, - 29, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.6, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28, - 28.1, - 27.9, - 27.7, - 27.6, - 27.6, - 27.6, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 27.1, - 27, - 27, - 27, - 26.9, - 26.9, - 26.8, - 26.8, - 26.9, - 26.8, - 26.8, - 26.9, - 27, - 27, - 27, - 27, - 27.2, - 27.3, - 27.3, - 27.4, - 27.5, - 27.7, - 27.8, - 27.9, - 27.9, - 28, - 28.1, - 28.2, - 28.1, - 28.2, - 28.3, - 28.3, - 28.3, - 28.2, - 28.2, - 28.3, - 28.4, - 28.5, - 28.5, - 28.5, - 28.5, - 28.5, - 28.3, - 28.1, - 27.9, - 23.5, - 23.6, - 23.5, - 23.6, - 23.7, - 24.1, - 24.9, - 26.2, - 26.7, - 26.9, - 27.1, - 27.2, - 27.3, - 27.2, - 27.1, - 27, - 27, - 27.3, - 27.8, - 28.2, - 28.5, - 28.7, - 28.9, - 29.1, - 29.3, - 29.4, - 29.6, - 29.7, - 29.8, - 29.9, - 30, - 30.1, - 30.1, - 30.2, - 30.2, - 30.3, - 30.4, - 30.4, - 30.5, - 30.5, - 30.6, - 30.7, - 30.8, - 30.9, - 31, - 31.2, - 31.4, - 31.4, - 31.4, - 31.4, - 31.4, - 31.3, - 31.2, - 31.1, - 30.9, - 30.7, - 30.5, - 30.7, - 30.9, - 30.7, - 30.2, - 29.7, - 30.1, - 30.7, - 30.9, - 30.6, - 30.4, - 30, - 28.9, - 28.7, - 29.6, - 29.8, - 30.2, - 31, - 31.4, - 32.1, - 32.7, - 33.8, - 34.3, - 34.9, - 35.4, - 35.6, - 35.8, - 36, - 36.3, - 36.5, - 36.7, - 36.9, - 37.1, - 37.6, - 38.3, - 39.3, - 40.9, - 42.3, - 42.7, - 43.4, - 43.9, - 44, - 44.3, - 45, - 45.6, - 46.1, - 46.2, - 46.2, - 45.9, - 45.6, - 45, - 44.3, - 43.8, - 43.6, - 43.4, - 43.2, - 42.9, - 42.6, - 42.1, - 41.5, - 40.7, - 40, - 39.3, - 38.8, - 38.3, - 38.1, - 38, - 37.8, - 37.7, - 37.5, - 37.3, - 37.3, - 37.3, - 37.2, - 36.9, - 36.5, - 36.1, - 35.6, - 35.1, - 34.6, - 34, - 33.4, - 32.8, - 32.3, - 32, - 31.8, - 31.6, - 31.6, - 31.6, - 31.5, - 31.4, - 31.3, - 31.3, - 31.3, - 31.4, - 31.3, - 31.3, - 31.3, - 31.2, - 31.1, - 31.1, - 31, - 31, - 31, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.4, - 30.3, - 30.2, - 30.1, - 30.1, - 30, - 29.9, - 29.9, - 29.8, - 29.8, - 29.6, - 29.5, - 29.5, - 29.6, - 29.5, - 29.5, - 29.5, - 29.4, - 29.3, - 29.1, - 29, - 28.9, - 29, - 29.1, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.5, - 28.3, - 28.3, - 28.2, - 28.2, - 28.2, - 28.2, - 28.1, - 27.9, - 27.8, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.3, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.9, - 26.8, - 26.8, - 26.8, - 26.8, - 26.7, - 26.7, - 26.8, - 26.8, - 26.8, - 26.8, - 26.9, - 27, - 27, - 27, - 27.1, - 27.3, - 27.4, - 27.6, - 27.6, - 27.7, - 27.8, - 27.9, - 28, - 27.9, - 28, - 28.1, - 28.1, - 28.1, - 28.1, - 28, - 28.1, - 28.2, - 28.3, - 28.4, - 28.5, - 28.5, - 28.4, - 28.2, - 28.1, - 27.9, - 23.6, - 23.6, - 23.5, - 23.7, - 23.9, - 24.4, - 25.5, - 26.2, - 26.5, - 26.6, - 26.7, - 26.8, - 26.8, - 26.8, - 26.5, - 26.6, - 26.9, - 27.4, - 27.9, - 28.2, - 28.5, - 28.7, - 28.9, - 29.1, - 29.3, - 29.4, - 29.5, - 29.7, - 29.8, - 29.9, - 29.9, - 30, - 30, - 30, - 29.9, - 30, - 30.1, - 30.2, - 30.2, - 30.3, - 30.4, - 30.6, - 30.8, - 30.9, - 31.1, - 31.2, - 31.4, - 31.4, - 31.5, - 31.5, - 31.5, - 31.3, - 31.2, - 31, - 30.7, - 30.3, - 30.3, - 30.7, - 30.8, - 30.4, - 29.7, - 29.2, - 29.9, - 30.2, - 30.3, - 30.3, - 30.2, - 29.8, - 28.6, - 28.6, - 29.1, - 29.2, - 29.7, - 31, - 31.5, - 32.2, - 32.8, - 33.6, - 34.1, - 34.6, - 35.3, - 35.5, - 35.6, - 35.8, - 36.2, - 36.4, - 36.5, - 36.6, - 36.7, - 37.1, - 37.8, - 39, - 40.2, - 42, - 42.6, - 43.3, - 43.9, - 44, - 44.3, - 45, - 45.7, - 46.1, - 46.2, - 46.2, - 46.1, - 45.7, - 45.3, - 44.5, - 44, - 43.9, - 43.6, - 43.4, - 43.2, - 42.8, - 42.5, - 42.3, - 41.5, - 40.5, - 39.6, - 39, - 38.6, - 38.3, - 38.1, - 37.9, - 37.7, - 37.6, - 37.5, - 37.5, - 37.5, - 37.5, - 37.3, - 36.8, - 36.2, - 35.7, - 35.1, - 34.6, - 33.9, - 33.3, - 32.7, - 32.2, - 31.9, - 31.8, - 31.8, - 31.7, - 31.7, - 31.6, - 31.5, - 31.4, - 31.4, - 31.5, - 31.5, - 31.4, - 31.4, - 31.3, - 31.3, - 31.2, - 31.2, - 31.1, - 31.1, - 31, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.4, - 30.3, - 30.2, - 30.1, - 30.1, - 30, - 30, - 29.9, - 29.9, - 29.8, - 29.6, - 29.6, - 29.6, - 29.6, - 29.6, - 29.6, - 29.5, - 29.5, - 29.4, - 29.2, - 29, - 28.9, - 28.9, - 29.1, - 29, - 29, - 29, - 28.9, - 28.7, - 28.6, - 28.4, - 28.3, - 28.3, - 28.3, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 27, - 26.8, - 26.8, - 26.8, - 26.7, - 26.7, - 26.6, - 26.7, - 26.7, - 26.7, - 26.7, - 26.7, - 26.8, - 26.8, - 26.8, - 26.9, - 27, - 27.1, - 27.3, - 27.4, - 27.4, - 27.5, - 27.6, - 27.7, - 27.7, - 27.8, - 27.8, - 27.8, - 27.8, - 27.8, - 27.6, - 27.8, - 28, - 28.1, - 28.3, - 28.4, - 28.4, - 28.3, - 28.2, - 28, - 27.8, - 23.6, - 23.6, - 23.6, - 23.7, - 24, - 24.7, - 25.7, - 26.1, - 26.2, - 26.2, - 26.2, - 26.1, - 26.2, - 26.2, - 26.4, - 26.8, - 27, - 27.4, - 27.8, - 28.2, - 28.5, - 28.7, - 28.9, - 29.1, - 29.2, - 29.4, - 29.5, - 29.6, - 29.6, - 29.7, - 29.7, - 29.8, - 29.8, - 29.6, - 29.5, - 29.6, - 29.7, - 29.9, - 30, - 30.2, - 30.4, - 30.6, - 30.8, - 30.9, - 31.1, - 31.2, - 31.3, - 31.4, - 31.4, - 31.5, - 31.5, - 31.4, - 31.2, - 31, - 30.6, - 30.1, - 30, - 30.6, - 30.6, - 30.1, - 29.3, - 29, - 29.7, - 29.9, - 29.9, - 30.1, - 30, - 29.4, - 28.3, - 28.8, - 29.4, - 29.8, - 30.3, - 31.2, - 31.6, - 32.1, - 32.6, - 33.3, - 33.8, - 34.3, - 35.1, - 35.3, - 35.2, - 35.5, - 35.9, - 36.2, - 36.3, - 36.3, - 36.4, - 36.9, - 37.4, - 38.7, - 39.9, - 41.3, - 42.6, - 43.1, - 43.9, - 44, - 44.3, - 44.8, - 45.6, - 46, - 46.2, - 46.3, - 46.2, - 45.9, - 45.5, - 44.8, - 44.3, - 44.1, - 43.8, - 43.6, - 43.4, - 43.1, - 42.9, - 42.6, - 41.8, - 40.7, - 39.8, - 39.2, - 38.6, - 38.3, - 38.2, - 38.1, - 37.9, - 37.7, - 37.7, - 37.7, - 37.6, - 37.5, - 37.4, - 36.9, - 36.2, - 35.6, - 35, - 34.3, - 33.6, - 33, - 32.5, - 32, - 31.9, - 31.9, - 31.9, - 31.8, - 31.7, - 31.6, - 31.6, - 31.5, - 31.6, - 31.6, - 31.5, - 31.5, - 31.5, - 31.5, - 31.4, - 31.3, - 31.3, - 31.2, - 31.1, - 31, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.6, - 30.5, - 30.3, - 30.2, - 30.2, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.7, - 29.7, - 29.7, - 29.6, - 29.6, - 29.6, - 29.5, - 29.4, - 29.3, - 29, - 28.9, - 28.9, - 29, - 28.9, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.5, - 28.6, - 28.6, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28.1, - 28.1, - 28, - 27.8, - 27.6, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.8, - 26.8, - 26.7, - 26.5, - 26.5, - 26.6, - 26.5, - 26.5, - 26.6, - 26.6, - 26.6, - 26.6, - 26.7, - 26.7, - 26.8, - 26.9, - 27, - 27.1, - 27.2, - 27.3, - 27.3, - 27.4, - 27.5, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.5, - 27.8, - 27.9, - 28.1, - 28.2, - 28.2, - 28.1, - 28, - 27.8, - 27.7, - 23.6, - 23.7, - 23.7, - 23.7, - 24, - 24.6, - 25.3, - 25.6, - 25.7, - 25.5, - 25.5, - 25.6, - 26, - 26.3, - 26.8, - 27.2, - 27.4, - 27.6, - 27.9, - 28.2, - 28.5, - 28.7, - 28.9, - 29.1, - 29.2, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29.3, - 29.6, - 29.8, - 30, - 30.2, - 30.4, - 30.6, - 30.7, - 30.8, - 31, - 31.1, - 31.2, - 31.3, - 31.3, - 31.4, - 31.5, - 31.5, - 31.4, - 31.2, - 30.9, - 30.5, - 29.8, - 29.7, - 30.5, - 30.4, - 29.6, - 28.6, - 29, - 29.5, - 29.7, - 29.9, - 30, - 29.6, - 28.9, - 28.1, - 29.1, - 29.7, - 30.3, - 30.8, - 31.2, - 31.6, - 31.9, - 32.3, - 32.8, - 33.4, - 34, - 34.4, - 34.5, - 34.5, - 34.7, - 35.1, - 35.5, - 35.7, - 35.8, - 36, - 36.5, - 37.2, - 38.2, - 39.6, - 40.7, - 42.5, - 43.1, - 43.8, - 44.1, - 44.3, - 44.7, - 45.5, - 45.9, - 46.2, - 46.3, - 46.3, - 46.1, - 45.7, - 45.3, - 44.7, - 44.3, - 44, - 43.8, - 43.6, - 43.4, - 43.1, - 42.7, - 41.9, - 40.8, - 40, - 39.3, - 38.7, - 38.5, - 38.3, - 38.2, - 38, - 37.9, - 37.9, - 37.9, - 37.7, - 37.6, - 37.3, - 36.8, - 36, - 35.3, - 34.7, - 33.9, - 33.3, - 32.6, - 32.3, - 32.1, - 32.1, - 32, - 31.9, - 31.8, - 31.8, - 31.7, - 31.6, - 31.6, - 31.7, - 31.7, - 31.6, - 31.6, - 31.6, - 31.5, - 31.5, - 31.4, - 31.3, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.7, - 30.6, - 30.5, - 30.4, - 30.4, - 30.3, - 30.3, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.9, - 29.8, - 29.8, - 29.7, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.1, - 29, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 28.7, - 28.6, - 28.6, - 28.8, - 29.1, - 29.1, - 28.8, - 28.6, - 28.4, - 28.3, - 28.2, - 28.2, - 28.2, - 28.2, - 28.2, - 28.1, - 27.9, - 27.6, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.7, - 26.7, - 26.5, - 26.4, - 26.4, - 26.4, - 26.4, - 26.4, - 26.4, - 26.4, - 26.4, - 26.5, - 26.5, - 26.6, - 26.7, - 26.7, - 26.8, - 26.9, - 26.9, - 26.9, - 27, - 27.2, - 27.2, - 27.1, - 27, - 27, - 27, - 27.1, - 27.4, - 27.7, - 27.8, - 27.9, - 28, - 27.9, - 27.8, - 27.6, - 27.5, - 23.6, - 23.7, - 23.7, - 23.8, - 23.8, - 24.2, - 24.6, - 24.8, - 24.9, - 24.8, - 25.4, - 26, - 26.5, - 26.9, - 27.2, - 27.6, - 27.8, - 28, - 28.2, - 28.4, - 28.6, - 28.8, - 28.9, - 29, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.8, - 28.9, - 29.2, - 29.5, - 29.8, - 30.1, - 30.3, - 30.4, - 30.5, - 30.6, - 30.8, - 30.9, - 31, - 31.1, - 31.2, - 31.3, - 31.3, - 31.4, - 31.5, - 31.5, - 31.4, - 31.2, - 30.9, - 30.4, - 29.4, - 29.5, - 30.3, - 30.3, - 29.4, - 28.3, - 28.8, - 29.3, - 29.6, - 29.7, - 29.4, - 28.6, - 28.1, - 28.3, - 29.3, - 29.9, - 30.3, - 30.7, - 31.1, - 31.4, - 31.7, - 32, - 32.5, - 32.9, - 33.4, - 33.6, - 33.8, - 33.9, - 34, - 34.2, - 34.5, - 35.1, - 35.5, - 35.8, - 36.2, - 37, - 38.1, - 39.2, - 40.6, - 42.4, - 43.1, - 43.7, - 44, - 44.2, - 44.6, - 45.2, - 45.7, - 46.1, - 46.3, - 46.3, - 46.3, - 46, - 45.6, - 45, - 44.5, - 44.2, - 44, - 43.8, - 43.7, - 43.4, - 42.8, - 42.1, - 41, - 40.1, - 39.6, - 39, - 38.7, - 38.4, - 38.2, - 38.1, - 38, - 38.1, - 38, - 37.8, - 37.6, - 37.2, - 36.5, - 35.8, - 35.1, - 34.3, - 33.5, - 32.9, - 32.4, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 32, - 32, - 31.9, - 31.8, - 31.8, - 31.8, - 31.7, - 31.7, - 31.6, - 31.6, - 31.6, - 31.5, - 31.4, - 31.4, - 31.2, - 31.1, - 31, - 30.9, - 30.9, - 30.8, - 30.7, - 30.6, - 30.6, - 30.6, - 30.6, - 30.6, - 30.6, - 30.4, - 30.3, - 30.3, - 30.2, - 30, - 29.9, - 29.9, - 30, - 29.9, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.1, - 29, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.8, - 28.9, - 28.9, - 28.8, - 28.9, - 29.1, - 29.3, - 29.3, - 29.3, - 29.1, - 28.7, - 28.4, - 28.4, - 28.3, - 28.3, - 28.3, - 28.3, - 28.2, - 28.1, - 27.9, - 27.6, - 27.4, - 27.3, - 27.2, - 27.1, - 27.1, - 27, - 26.8, - 26.8, - 26.8, - 26.7, - 26.7, - 26.5, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.4, - 26.5, - 26.5, - 26.6, - 26.6, - 26.7, - 26.7, - 26.7, - 26.8, - 26.9, - 26.8, - 26.7, - 26.8, - 26.8, - 26.9, - 27.1, - 27.3, - 27.6, - 27.7, - 27.7, - 27.6, - 27.6, - 27.5, - 27.3, - 23.6, - 23.7, - 23.7, - 23.8, - 23.9, - 24, - 24.1, - 24.3, - 24.4, - 25, - 26.1, - 26.5, - 26.9, - 27.3, - 27.6, - 27.8, - 28, - 28.1, - 28.3, - 28.5, - 28.6, - 28.8, - 28.9, - 29, - 29, - 28.8, - 28.6, - 28.3, - 28.2, - 28.3, - 28.6, - 28.9, - 29.2, - 29.5, - 29.8, - 30, - 30.2, - 30.4, - 30.5, - 30.6, - 30.7, - 30.8, - 30.9, - 31, - 31.1, - 31.2, - 31.3, - 31.3, - 31.4, - 31.4, - 31.4, - 31.4, - 31.2, - 30.9, - 30.3, - 29.3, - 29, - 30, - 30, - 29.1, - 28.1, - 28.6, - 28.9, - 29, - 28.9, - 28.4, - 27.7, - 28.2, - 29.2, - 29.5, - 29.8, - 30.1, - 30.4, - 30.7, - 31, - 31.3, - 31.5, - 32.1, - 32.5, - 32.7, - 32.8, - 33.2, - 33.5, - 33.8, - 33.9, - 34.2, - 34.9, - 35.5, - 35.8, - 36.3, - 37, - 37.9, - 39.1, - 40.6, - 42.1, - 42.8, - 43.4, - 43.8, - 44.1, - 44.5, - 44.9, - 45.4, - 46, - 46.3, - 46.4, - 46.4, - 46.2, - 45.8, - 45.3, - 44.9, - 44.4, - 44.2, - 44, - 43.8, - 43.4, - 42.9, - 42.4, - 41.4, - 40.6, - 40, - 39.4, - 38.9, - 38.6, - 38.3, - 38.2, - 38.1, - 38.1, - 38, - 37.9, - 37.5, - 36.9, - 36.2, - 35.5, - 34.8, - 33.9, - 33.2, - 32.8, - 32.7, - 32.6, - 32.4, - 32.3, - 32.3, - 32.1, - 32.2, - 32.2, - 32.1, - 31.9, - 32, - 31.9, - 31.9, - 31.7, - 31.8, - 31.7, - 31.6, - 31.6, - 31.5, - 31.4, - 31.2, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.8, - 30.8, - 30.7, - 30.7, - 30.8, - 30.8, - 30.7, - 30.5, - 30.6, - 30.6, - 30.4, - 30.3, - 30.1, - 30, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29, - 29, - 28.9, - 29, - 28.9, - 28.8, - 29, - 29, - 29, - 28.9, - 29.1, - 29.2, - 29.3, - 29.4, - 29.4, - 29.4, - 29.2, - 28.7, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 27.9, - 27.6, - 27.5, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 27.1, - 27.1, - 27.1, - 27, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26.2, - 26.3, - 26.3, - 26.4, - 26.4, - 26.5, - 26.5, - 26.5, - 26.6, - 26.7, - 26.6, - 26.6, - 26.6, - 26.7, - 26.8, - 26.9, - 27.1, - 27.2, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.3, - 23.6, - 23.7, - 23.7, - 23.8, - 23.9, - 24, - 24.1, - 24.3, - 24.7, - 25.6, - 26.5, - 26.8, - 27.2, - 27.5, - 27.7, - 27.9, - 28, - 28.2, - 28.3, - 28.5, - 28.6, - 28.8, - 28.9, - 28.9, - 28.8, - 28.5, - 28.1, - 27.8, - 28.3, - 28.7, - 29, - 29.3, - 29.5, - 29.8, - 29.9, - 30.1, - 30.3, - 30.4, - 30.5, - 30.6, - 30.7, - 30.7, - 30.9, - 31, - 31.1, - 31.1, - 31.2, - 31.3, - 31.4, - 31.4, - 31.4, - 31.3, - 31.2, - 30.8, - 30.2, - 29.2, - 28.6, - 29.7, - 29.5, - 28.6, - 27.7, - 28, - 28.2, - 28.1, - 28, - 27.6, - 27.6, - 28.6, - 29.2, - 29.2, - 29.5, - 29.9, - 30.1, - 30.3, - 30.4, - 30.6, - 31, - 31.5, - 32, - 32.1, - 32.5, - 33.1, - 33.6, - 33.9, - 34.2, - 34.5, - 35.1, - 35.6, - 36, - 36.5, - 37.3, - 38.3, - 39.4, - 40.9, - 42.1, - 42.7, - 43.2, - 43.8, - 44.1, - 44.4, - 44.8, - 45.4, - 46, - 46.3, - 46.4, - 46.5, - 46.4, - 46.1, - 45.6, - 45.2, - 44.7, - 44.4, - 44.2, - 43.9, - 43.5, - 43.1, - 42.7, - 42.1, - 41, - 40.3, - 39.7, - 39.2, - 38.7, - 38.5, - 38.3, - 38.2, - 38.1, - 38, - 37.8, - 37.4, - 36.7, - 36, - 35.2, - 34.5, - 33.7, - 33.2, - 33.1, - 33.3, - 33, - 32.8, - 32.7, - 32.7, - 32.5, - 32.4, - 32.4, - 32.2, - 32.1, - 32.1, - 32.1, - 31.9, - 31.8, - 31.9, - 31.8, - 31.7, - 31.6, - 31.5, - 31.3, - 31.3, - 31.2, - 31.1, - 31, - 31, - 31.1, - 31, - 31, - 30.9, - 31, - 31, - 30.9, - 30.7, - 30.7, - 30.9, - 30.7, - 30.5, - 30.3, - 30.1, - 30.1, - 30.1, - 30.1, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.3, - 29.1, - 29, - 29, - 29, - 28.9, - 28.9, - 29, - 29.1, - 29.1, - 29.1, - 29.2, - 29.3, - 29.3, - 29.4, - 29.4, - 29.5, - 29.4, - 29.1, - 29.2, - 29.1, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.2, - 27.9, - 27.6, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.4, - 27.3, - 27.3, - 27.2, - 27.1, - 27, - 26.7, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26.2, - 26.2, - 26.2, - 26.3, - 26.3, - 26.3, - 26.3, - 26.4, - 26.4, - 26.4, - 26.4, - 26.4, - 26.5, - 26.6, - 26.8, - 26.9, - 27, - 27.1, - 27.1, - 27.2, - 27.1, - 27.2, - 27.2, - 23.6, - 23.7, - 23.8, - 23.8, - 23.9, - 24, - 24.2, - 24.5, - 25, - 25.9, - 26.6, - 27, - 27.3, - 27.6, - 27.8, - 27.9, - 28, - 28.2, - 28.3, - 28.4, - 28.6, - 28.7, - 28.7, - 28.7, - 28.4, - 28, - 27.6, - 28, - 28.8, - 29.1, - 29.3, - 29.5, - 29.7, - 29.8, - 30, - 30.1, - 30.2, - 30.3, - 30.4, - 30.5, - 30.6, - 30.7, - 30.8, - 30.9, - 31, - 31.1, - 31.2, - 31.3, - 31.3, - 31.4, - 31.4, - 31.3, - 31.1, - 30.7, - 29.9, - 28.7, - 28.4, - 29.4, - 29.1, - 28.2, - 27.5, - 27.5, - 27.6, - 27.6, - 27.5, - 27.2, - 27.6, - 28.2, - 28.6, - 29.1, - 29.5, - 29.7, - 29.8, - 30, - 30.2, - 30.4, - 30.7, - 31.2, - 31.8, - 32.1, - 32.7, - 33.2, - 33.7, - 34.1, - 34.5, - 35, - 35.5, - 35.9, - 36.3, - 36.8, - 37.5, - 38.7, - 40.1, - 41.7, - 42.4, - 43, - 43.4, - 43.9, - 44.1, - 44.5, - 45, - 45.5, - 46.1, - 46.3, - 46.5, - 46.6, - 46.6, - 46.3, - 45.9, - 45.5, - 45, - 44.5, - 44.3, - 44, - 43.7, - 43.3, - 42.9, - 42.4, - 41.4, - 40.6, - 39.9, - 39.5, - 38.9, - 38.6, - 38.4, - 38.3, - 38.1, - 38, - 37.8, - 37.3, - 36.5, - 35.7, - 35, - 34.6, - 34.1, - 33.9, - 33.8, - 33.8, - 33.6, - 33.4, - 33.2, - 33.1, - 32.8, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32, - 31.9, - 32, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.4, - 31.4, - 31.2, - 31.1, - 31.3, - 31.3, - 31.2, - 31.3, - 31.2, - 31.1, - 31, - 30.9, - 30.7, - 30.8, - 30.8, - 30.6, - 30.4, - 30.3, - 30.3, - 30.3, - 30.2, - 30.1, - 30, - 29.8, - 29.7, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.1, - 29.1, - 29, - 29, - 29, - 29, - 29, - 29.1, - 29.1, - 29.2, - 29.2, - 29.3, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.4, - 29.6, - 29.5, - 29.3, - 28.9, - 28.7, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 27.8, - 27.5, - 27.4, - 27.3, - 27.4, - 27.4, - 27.4, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 26.9, - 26.7, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26.1, - 26.1, - 26.2, - 26.2, - 26.2, - 26.1, - 26.1, - 26.2, - 26.2, - 26.1, - 26.1, - 26.3, - 26.5, - 26.6, - 26.8, - 26.8, - 26.9, - 27, - 27, - 27.1, - 27.1, - 27.2, - 23.7, - 23.7, - 23.7, - 23.8, - 23.9, - 24.1, - 24.5, - 25.2, - 25.7, - 26.3, - 26.7, - 27, - 27.3, - 27.6, - 27.8, - 27.9, - 28, - 28.1, - 28.2, - 28.4, - 28.5, - 28.5, - 28.5, - 28.3, - 27.9, - 27.5, - 27.6, - 28.5, - 29, - 29.3, - 29.4, - 29.6, - 29.7, - 29.8, - 29.9, - 30, - 30, - 30.1, - 30.2, - 30.4, - 30.5, - 30.6, - 30.7, - 30.8, - 30.9, - 31, - 31.1, - 31.2, - 31.3, - 31.3, - 31.3, - 31.2, - 31, - 30.7, - 30.1, - 28.9, - 27.9, - 28.5, - 28.5, - 27.8, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 27.3, - 27.7, - 28.3, - 28.7, - 29.1, - 29.4, - 29.5, - 29.7, - 30, - 30.3, - 30.7, - 31, - 31.5, - 32.1, - 32.5, - 33, - 33.4, - 33.9, - 34.4, - 34.9, - 35.4, - 35.8, - 36.2, - 36.5, - 37.1, - 38, - 38.8, - 40.3, - 42.1, - 42.6, - 43.3, - 43.6, - 44, - 44.2, - 44.7, - 45.2, - 45.7, - 46, - 46.4, - 46.5, - 46.6, - 46.7, - 46.5, - 46.2, - 45.8, - 45.3, - 44.7, - 44.5, - 44.2, - 43.9, - 43.5, - 43.1, - 42.5, - 41.8, - 41, - 40.2, - 39.7, - 39.1, - 38.7, - 38.5, - 38.3, - 38.2, - 38, - 37.7, - 37.2, - 36.3, - 35.7, - 35.3, - 35.1, - 34.9, - 34.7, - 34.4, - 34.2, - 34.1, - 33.9, - 33.7, - 33.4, - 33.2, - 33.1, - 32.8, - 32.7, - 32.5, - 32.4, - 32.3, - 32.1, - 32.1, - 32, - 31.9, - 31.8, - 31.6, - 31.5, - 31.5, - 31.5, - 31.5, - 31.3, - 31.4, - 31.4, - 31.4, - 31.4, - 31.4, - 31.3, - 31.2, - 31.1, - 31, - 30.8, - 30.8, - 30.7, - 30.6, - 30.5, - 30.5, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.2, - 29.1, - 29.1, - 29.1, - 29.1, - 29, - 29.1, - 29.2, - 29.2, - 29.2, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.6, - 29.6, - 29.6, - 29.6, - 29.3, - 28.9, - 28.6, - 28.5, - 28.4, - 28.3, - 28.3, - 27.8, - 27.6, - 27.4, - 27.4, - 27.4, - 27.5, - 27.5, - 27.6, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.2, - 26.9, - 26.6, - 26.6, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 26.1, - 26.2, - 26.1, - 26, - 25.8, - 25.8, - 25.9, - 25.9, - 25.9, - 26, - 26.1, - 26.3, - 26.6, - 26.7, - 26.8, - 26.9, - 27, - 27, - 27.1, - 27.2, - 23.7, - 23.7, - 23.8, - 23.9, - 24, - 24.4, - 24.9, - 25.8, - 26.3, - 26.5, - 26.8, - 27, - 27.3, - 27.6, - 27.7, - 27.8, - 27.9, - 28, - 28.2, - 28.3, - 28.3, - 28.2, - 27.9, - 27.6, - 27.2, - 27.3, - 28.1, - 28.7, - 29.1, - 29.3, - 29.4, - 29.5, - 29.6, - 29.6, - 29.7, - 29.8, - 29.8, - 29.8, - 30, - 30.2, - 30.3, - 30.3, - 30.5, - 30.7, - 30.8, - 31, - 31.1, - 31.1, - 31.1, - 31.2, - 31.2, - 31.1, - 31, - 30.8, - 30.2, - 29.1, - 27.9, - 27.6, - 27.6, - 27.2, - 27, - 26.9, - 26.9, - 27, - 27.2, - 27.6, - 28.2, - 28.8, - 29.1, - 29.2, - 29.2, - 29.4, - 29.7, - 30.2, - 30.6, - 31, - 31.5, - 31.9, - 32.4, - 32.8, - 33.2, - 33.5, - 33.9, - 34.4, - 34.9, - 35.5, - 36, - 36.3, - 36.6, - 37.1, - 37.9, - 38.7, - 40.8, - 42.4, - 42.8, - 43.4, - 43.7, - 43.9, - 44.2, - 44.9, - 45.3, - 45.8, - 46.3, - 46.5, - 46.5, - 46.5, - 46.6, - 46.6, - 46.4, - 46, - 45.6, - 45, - 44.5, - 44.2, - 44, - 43.6, - 43.2, - 42.7, - 42.1, - 41.3, - 40.4, - 39.8, - 39.2, - 38.8, - 38.5, - 38.3, - 38.1, - 37.9, - 37.6, - 37.3, - 36.5, - 36.1, - 35.8, - 35.8, - 35.6, - 35.3, - 34.9, - 34.6, - 34.4, - 34.2, - 34.1, - 33.7, - 33.4, - 33.2, - 33, - 32.8, - 32.6, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 31.7, - 31.6, - 31.6, - 31.7, - 31.6, - 31.4, - 31.5, - 31.5, - 31.5, - 31.5, - 31.4, - 31.4, - 31.3, - 31.2, - 31, - 30.9, - 30.8, - 30.8, - 30.7, - 30.7, - 30.6, - 30.4, - 30.3, - 30.2, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.5, - 29.3, - 29.2, - 29.1, - 29.1, - 29.2, - 29.1, - 29.1, - 29.2, - 29.2, - 29.3, - 29.3, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.6, - 29.6, - 29.6, - 29.6, - 29.5, - 29, - 28.7, - 28.5, - 28.4, - 28.4, - 28.3, - 27.9, - 27.5, - 27.4, - 27.4, - 27.4, - 27.5, - 27.6, - 27.6, - 27.6, - 27.7, - 27.6, - 27.6, - 27.5, - 27.3, - 27.2, - 26.8, - 26.6, - 26.4, - 26.3, - 26.3, - 26.2, - 26.2, - 26.1, - 26.1, - 26.1, - 25.8, - 25.8, - 25.7, - 25.7, - 25.8, - 25.9, - 25.9, - 25.9, - 26, - 26.2, - 26.4, - 26.7, - 26.9, - 27, - 27, - 27.1, - 27.2, - 23.7, - 23.8, - 23.8, - 23.9, - 24.2, - 24.7, - 25.4, - 26.1, - 26.4, - 26.6, - 26.8, - 27.1, - 27.3, - 27.5, - 27.6, - 27.8, - 27.9, - 28, - 28, - 28.1, - 28, - 27.7, - 27.3, - 26.8, - 27.1, - 27.8, - 28.4, - 28.8, - 29, - 29.2, - 29.2, - 29.2, - 29.2, - 29.3, - 29.4, - 29.4, - 29.4, - 29.5, - 29.7, - 29.9, - 30, - 30.1, - 30.4, - 30.6, - 30.7, - 30.9, - 31, - 31, - 31, - 31.1, - 31.1, - 31, - 31, - 30.8, - 30.2, - 29.2, - 28, - 27.2, - 27, - 26.8, - 26.7, - 26.7, - 26.9, - 27.3, - 27.7, - 28.2, - 28.7, - 29, - 29, - 28.8, - 28.9, - 29.2, - 29.7, - 30.2, - 30.7, - 31.2, - 31.7, - 32.1, - 32.5, - 33, - 33.3, - 33.5, - 33.8, - 34.2, - 34.7, - 35.4, - 35.8, - 36.2, - 36.5, - 37, - 37.6, - 38.8, - 41, - 42.5, - 42.9, - 43.4, - 43.7, - 43.8, - 44.2, - 45, - 45.4, - 45.8, - 46.5, - 46.6, - 46.5, - 46.5, - 46.5, - 46.6, - 46.4, - 46.1, - 45.7, - 45.3, - 44.6, - 44.1, - 43.9, - 43.5, - 43.1, - 42.6, - 42.2, - 41.5, - 40.4, - 39.8, - 39.2, - 38.7, - 38.4, - 38.2, - 38.1, - 37.8, - 37.6, - 37.5, - 37.1, - 36.9, - 36.6, - 36.6, - 36.2, - 35.8, - 35.3, - 34.8, - 34.5, - 34.3, - 34.1, - 33.7, - 33.4, - 33.2, - 33, - 32.8, - 32.6, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 31.8, - 31.8, - 31.9, - 31.8, - 31.6, - 31.5, - 31.6, - 31.7, - 31.6, - 31.6, - 31.5, - 31.4, - 31.4, - 31.1, - 31, - 31, - 30.9, - 30.8, - 30.8, - 30.7, - 30.5, - 30.4, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29.3, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.6, - 29.6, - 29.6, - 29.6, - 29.5, - 29.1, - 28.7, - 28.6, - 28.4, - 28.4, - 28.2, - 27.7, - 27.4, - 27.3, - 27.3, - 27.4, - 27.4, - 27.5, - 27.6, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.4, - 27.2, - 27, - 26.6, - 26.4, - 26.3, - 26.3, - 26.2, - 26.1, - 26.1, - 26, - 26, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 26.1, - 26.3, - 26.5, - 26.8, - 27, - 27.1, - 27.1, - 23.9, - 23.9, - 23.9, - 24, - 24.2, - 24.9, - 25.7, - 26.1, - 26.4, - 26.6, - 26.8, - 27.1, - 27.3, - 27.4, - 27.6, - 27.7, - 27.8, - 27.8, - 27.8, - 27.8, - 27.5, - 27.1, - 26.6, - 26.7, - 27.6, - 28.1, - 28.4, - 28.6, - 28.8, - 28.8, - 28.8, - 28.7, - 28.6, - 28.7, - 28.8, - 28.9, - 29, - 29.2, - 29.4, - 29.7, - 29.8, - 30, - 30.3, - 30.5, - 30.7, - 30.8, - 30.9, - 30.9, - 30.8, - 30.8, - 30.9, - 30.9, - 30.8, - 30.5, - 29.8, - 28.9, - 27.9, - 27, - 26.7, - 26.6, - 26.6, - 26.9, - 27.2, - 27.5, - 28, - 28.4, - 28.7, - 28.8, - 28.6, - 28.3, - 28.6, - 29, - 29.5, - 30.1, - 30.7, - 31.2, - 31.7, - 32, - 32.3, - 32.8, - 33.1, - 33.3, - 33.6, - 33.8, - 34.4, - 35.3, - 35.7, - 36.1, - 36.4, - 36.9, - 37.5, - 38.3, - 39.4, - 41.5, - 42.7, - 43.2, - 43.5, - 43.7, - 44.2, - 45, - 45.4, - 45.8, - 46.5, - 46.7, - 46.6, - 46.6, - 46.5, - 46.4, - 46.3, - 46.1, - 45.7, - 45.3, - 44.6, - 44, - 43.7, - 43.4, - 43.1, - 42.5, - 41.9, - 41.1, - 40.1, - 39.6, - 38.9, - 38.5, - 38.3, - 38.2, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 36.7, - 36, - 35.5, - 34.8, - 34.3, - 33.9, - 33.7, - 33.5, - 33.3, - 33.1, - 33, - 32.8, - 32.6, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.9, - 32, - 32, - 32, - 31.9, - 31.6, - 31.6, - 31.7, - 31.8, - 31.7, - 31.7, - 31.6, - 31.5, - 31.4, - 31.1, - 31.1, - 31, - 31, - 31, - 30.8, - 30.7, - 30.6, - 30.6, - 30.6, - 30.6, - 30.4, - 30.3, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.6, - 29.5, - 29.3, - 29.2, - 29.2, - 29.3, - 29.3, - 29.2, - 29.2, - 29.2, - 29.3, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.6, - 29.6, - 29.4, - 29, - 28.7, - 28.6, - 28.5, - 28.4, - 28, - 27.5, - 27.3, - 27.3, - 27.3, - 27.3, - 27.4, - 27.5, - 27.6, - 27.7, - 27.7, - 27.7, - 27.7, - 27.6, - 27.4, - 27.2, - 27, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26.1, - 25.8, - 25.7, - 25.6, - 25.6, - 25.6, - 25.7, - 25.5, - 25.6, - 25.7, - 25.7, - 25.8, - 25.7, - 25.8, - 25.9, - 26, - 26.3, - 26.7, - 26.9, - 27, - 24.1, - 24.2, - 24.1, - 24.2, - 24.3, - 25, - 25.7, - 25.9, - 26.2, - 26.5, - 26.8, - 27, - 27.2, - 27.3, - 27.5, - 27.5, - 27.5, - 27.4, - 27.2, - 27.1, - 26.9, - 26.5, - 26.2, - 27, - 27.6, - 27.9, - 28.1, - 28.1, - 28.1, - 28.1, - 28.1, - 28.1, - 28.2, - 28.5, - 28.6, - 28.8, - 29, - 29.2, - 29.4, - 29.7, - 29.8, - 30, - 30.3, - 30.5, - 30.7, - 30.8, - 30.8, - 30.7, - 30.5, - 30.4, - 30.7, - 30.7, - 30.5, - 29.9, - 29.1, - 28.4, - 27.5, - 26.8, - 26.6, - 26.4, - 26.7, - 27, - 27.3, - 27.7, - 28.1, - 28.3, - 28.4, - 28.5, - 28.3, - 28.2, - 28.5, - 28.9, - 29.3, - 30, - 30.6, - 31.1, - 31.6, - 32, - 32.3, - 32.6, - 32.8, - 33.1, - 33.3, - 33.6, - 34.2, - 35.2, - 35.6, - 36, - 36.3, - 36.7, - 37.4, - 37.9, - 38.6, - 40.4, - 42.3, - 42.9, - 43.4, - 43.7, - 44.1, - 44.8, - 45.3, - 45.8, - 46.5, - 46.7, - 46.7, - 46.6, - 46.5, - 46.3, - 46, - 45.8, - 45.5, - 45.1, - 44.6, - 44, - 43.6, - 43.3, - 42.8, - 42.3, - 41.4, - 40.3, - 39.6, - 39.2, - 38.7, - 38.5, - 38.3, - 38.1, - 38, - 37.9, - 37.7, - 37.6, - 37.6, - 37.5, - 37.5, - 37.4, - 36.7, - 35.9, - 35.2, - 34.5, - 33.9, - 33.6, - 33.3, - 33.2, - 33.1, - 33, - 32.9, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 32, - 32, - 32.2, - 32.2, - 32.1, - 31.8, - 31.7, - 31.8, - 31.9, - 31.8, - 31.8, - 31.7, - 31.7, - 31.6, - 31.4, - 31.3, - 31.3, - 31.2, - 31.2, - 31.1, - 30.8, - 30.7, - 30.8, - 31, - 31.1, - 31.1, - 31, - 30.9, - 30.7, - 30.4, - 30.1, - 29.8, - 29.7, - 29.6, - 29.6, - 29.5, - 29.3, - 29.3, - 29.2, - 29.3, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.5, - 29.6, - 29.6, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 28.9, - 28.7, - 28.5, - 28.5, - 28.4, - 27.9, - 27.4, - 27.2, - 27.2, - 27.2, - 27.3, - 27.4, - 27.5, - 27.6, - 27.7, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.3, - 27, - 26.8, - 26.6, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 25.8, - 25.5, - 25.4, - 25.5, - 25.5, - 25.5, - 25.6, - 25.4, - 25.5, - 25.6, - 25.6, - 25.6, - 25.7, - 25.6, - 25.7, - 25.9, - 26.2, - 26.5, - 26.9, - 24.4, - 24.5, - 24.5, - 24.5, - 24.6, - 25, - 25.5, - 25.8, - 26.2, - 26.5, - 26.7, - 26.9, - 27.1, - 27.2, - 27.3, - 27.3, - 27.1, - 26.7, - 26.4, - 26.3, - 26.1, - 25.9, - 26, - 26.6, - 27, - 27.1, - 27.3, - 27.4, - 27.5, - 27.6, - 27.9, - 28.2, - 28.5, - 28.8, - 29, - 29.2, - 29.4, - 29.6, - 29.8, - 29.9, - 30, - 30.2, - 30.4, - 30.5, - 30.7, - 30.7, - 30.7, - 30.5, - 30, - 29.7, - 30.2, - 30.2, - 29.7, - 29.1, - 28.3, - 27.6, - 26.9, - 26.5, - 26.4, - 26.5, - 26.7, - 27.1, - 27.5, - 27.9, - 28.1, - 28, - 28, - 28, - 27.8, - 28, - 28.3, - 28.6, - 29.1, - 29.9, - 30.5, - 30.8, - 31.2, - 31.7, - 32.1, - 32.4, - 32.5, - 32.7, - 32.9, - 33.3, - 34, - 35, - 35.5, - 35.9, - 36.2, - 36.5, - 36.9, - 37.3, - 37.9, - 39.1, - 42, - 42.8, - 43.4, - 43.6, - 44.1, - 44.8, - 45.3, - 45.7, - 46.4, - 46.7, - 46.7, - 46.7, - 46.5, - 46.2, - 45.8, - 45.5, - 45.1, - 44.9, - 44.4, - 43.8, - 43.5, - 43.1, - 42.5, - 41.8, - 40.7, - 39.8, - 39.4, - 39, - 38.7, - 38.5, - 38.3, - 38.2, - 38, - 37.9, - 37.8, - 37.7, - 37.7, - 37.6, - 37.5, - 37.2, - 36.4, - 35.6, - 34.8, - 34, - 33.6, - 33.4, - 33.2, - 33.1, - 32.9, - 32.8, - 32.8, - 32.8, - 32.7, - 32.6, - 32.4, - 32.2, - 32.2, - 32.2, - 32.1, - 32.1, - 32.2, - 32.3, - 32.1, - 31.8, - 31.9, - 32, - 32, - 31.9, - 31.8, - 31.8, - 31.7, - 31.6, - 31.5, - 31.5, - 31.4, - 31.3, - 31.2, - 31.1, - 30.8, - 30.9, - 31.3, - 31.6, - 31.7, - 31.6, - 31.5, - 31.5, - 31.3, - 31.1, - 30.4, - 29.9, - 29.7, - 29.6, - 29.6, - 29.5, - 29.4, - 29.3, - 29.3, - 29.3, - 29.5, - 29.5, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.5, - 29.6, - 29.6, - 29.6, - 29.5, - 29.5, - 29.4, - 29.5, - 29.4, - 29.2, - 28.8, - 28.6, - 28.5, - 28.5, - 28.2, - 27.8, - 27.4, - 27.1, - 27.1, - 27.1, - 27.2, - 27.3, - 27.4, - 27.6, - 27.6, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.3, - 27.1, - 26.9, - 26.7, - 26.5, - 26.3, - 26.3, - 26.2, - 26.1, - 26, - 25.6, - 25.3, - 25.2, - 25.2, - 25.2, - 25.3, - 25.4, - 25.5, - 25.5, - 25.5, - 25.5, - 25.6, - 25.5, - 25.7, - 25.8, - 25.9, - 26.1, - 26.5, - 24.2, - 24.4, - 24.4, - 24.4, - 24.5, - 24.9, - 25.4, - 25.8, - 26.2, - 26.4, - 26.6, - 26.7, - 26.8, - 27, - 27, - 26.8, - 26.3, - 26, - 25.6, - 25.7, - 26, - 26.1, - 26.1, - 26.3, - 26.5, - 26.8, - 27, - 27.4, - 27.7, - 28, - 28.3, - 28.6, - 28.9, - 29.1, - 29.3, - 29.5, - 29.7, - 29.8, - 29.9, - 30.1, - 30.2, - 30.3, - 30.4, - 30.6, - 30.6, - 30.6, - 30.5, - 30.2, - 29.4, - 28.9, - 29.4, - 29.1, - 28.7, - 28.1, - 27.4, - 26.8, - 26.4, - 26.3, - 26.4, - 26.6, - 26.8, - 27, - 27.5, - 27.9, - 27.8, - 27.6, - 27.5, - 27.5, - 27.5, - 27.7, - 28, - 28.3, - 28.8, - 29.8, - 30.1, - 30.4, - 30.7, - 31.2, - 31.8, - 32, - 32.2, - 32.3, - 32.5, - 32.9, - 33.7, - 34.7, - 35.4, - 35.7, - 35.9, - 36.2, - 36.5, - 36.8, - 37.5, - 38.6, - 41.3, - 42.7, - 43.3, - 43.6, - 43.9, - 44.6, - 45.2, - 45.5, - 46.1, - 46.6, - 46.7, - 46.7, - 46.6, - 46.4, - 46, - 45.5, - 45, - 44.5, - 44, - 43.6, - 43.3, - 42.8, - 42.2, - 41.4, - 40.5, - 40, - 39.6, - 39.1, - 38.8, - 38.5, - 38.4, - 38.2, - 38.1, - 38, - 37.8, - 37.7, - 37.7, - 37.6, - 37.4, - 36.8, - 35.9, - 35, - 34.1, - 33.7, - 33.7, - 33.5, - 33.4, - 33.2, - 33.1, - 33.1, - 33.2, - 33.2, - 33, - 32.7, - 32.6, - 32.3, - 32.4, - 32.4, - 32.3, - 32.4, - 32.3, - 32.3, - 32.2, - 32, - 32, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 31.7, - 31.7, - 31.7, - 31.5, - 31.4, - 31.2, - 31.1, - 30.9, - 30.9, - 31.2, - 31.6, - 31.9, - 31.9, - 31.9, - 31.8, - 31.7, - 31.6, - 31.4, - 30.6, - 30, - 29.7, - 29.6, - 29.6, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.7, - 29.7, - 29.7, - 29.6, - 29.4, - 29.3, - 29.3, - 29.2, - 28.9, - 28.6, - 28.5, - 28.4, - 28.3, - 27.9, - 27.5, - 27.2, - 27.1, - 27, - 27, - 27.2, - 27.3, - 27.4, - 27.5, - 27.6, - 27.6, - 27.7, - 27.6, - 27.6, - 27.5, - 27.3, - 27.2, - 27, - 26.7, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26.2, - 26.1, - 26, - 25.8, - 25.5, - 25.2, - 25.1, - 25.3, - 25.4, - 25.5, - 25.5, - 25.4, - 25.5, - 25.5, - 25.6, - 25.7, - 25.8, - 25.9, - 26.1, - 23.6, - 23.8, - 23.8, - 23.8, - 24, - 24.3, - 24.6, - 25, - 25.5, - 25.9, - 26, - 26.1, - 26.2, - 26.4, - 26.3, - 25.9, - 25.4, - 25.1, - 25.4, - 26.2, - 26.6, - 26.8, - 26.8, - 27, - 27.2, - 27.4, - 27.7, - 28, - 28.3, - 28.5, - 28.7, - 28.9, - 29.2, - 29.3, - 29.5, - 29.6, - 29.7, - 29.8, - 29.9, - 30.1, - 30.2, - 30.3, - 30.4, - 30.5, - 30.5, - 30.5, - 30.3, - 29.8, - 28.7, - 28.1, - 28.3, - 27.9, - 27.6, - 27.1, - 26.6, - 26.3, - 26.2, - 26.3, - 26.4, - 26.6, - 26.8, - 27.1, - 27.4, - 27.6, - 27.4, - 27.2, - 27.2, - 27.3, - 27.4, - 27.6, - 27.8, - 28, - 28.2, - 28.8, - 29, - 29.2, - 29.7, - 30.3, - 31.3, - 31.5, - 31.7, - 31.8, - 32.3, - 32.8, - 33.5, - 34.4, - 35.2, - 35.5, - 35.7, - 35.9, - 36.2, - 36.7, - 37.5, - 38.6, - 40.4, - 42.4, - 43.3, - 43.5, - 43.8, - 44.3, - 44.9, - 45.3, - 46, - 46.5, - 46.6, - 46.7, - 46.6, - 46.4, - 46.1, - 45.5, - 45.1, - 44.7, - 44.2, - 43.6, - 43.3, - 42.6, - 42.2, - 41.8, - 41.2, - 40.5, - 39.9, - 39.3, - 38.9, - 38.6, - 38.4, - 38.3, - 38.2, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.2, - 36.3, - 35.4, - 34.7, - 34.3, - 34.3, - 34.2, - 33.9, - 33.8, - 33.7, - 33.8, - 33.7, - 33.7, - 33.4, - 33.1, - 32.8, - 32.6, - 32.3, - 32.4, - 32.5, - 32.5, - 32.5, - 32.5, - 32.4, - 32.3, - 32.3, - 32.1, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.9, - 31.8, - 31.7, - 31.5, - 31.4, - 31.3, - 31.2, - 31, - 31.1, - 31.3, - 31.8, - 32.1, - 32.1, - 32, - 31.9, - 31.8, - 31.6, - 31.4, - 30.7, - 30, - 29.8, - 29.7, - 29.6, - 29.6, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.7, - 29.7, - 29.7, - 29.6, - 29.3, - 29.1, - 28.9, - 28.8, - 28.6, - 28.4, - 28.4, - 28.2, - 28, - 27.6, - 27.3, - 27.1, - 27, - 26.9, - 27, - 27.1, - 27.3, - 27.4, - 27.5, - 27.6, - 27.6, - 27.6, - 27.6, - 27.5, - 27.5, - 27.3, - 27.2, - 27, - 26.8, - 26.6, - 26.4, - 26.3, - 26.3, - 26.2, - 26.2, - 26.2, - 26.2, - 26.1, - 26, - 25.6, - 25.2, - 25, - 25.4, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.5, - 25.6, - 25.7, - 25.8, - 25.9, - 23.2, - 23.3, - 23.4, - 23.4, - 23.6, - 23.7, - 23.9, - 24.2, - 24.5, - 24.8, - 25, - 25, - 25.2, - 25.5, - 25.4, - 24.9, - 24.8, - 25.4, - 26.2, - 26.6, - 27, - 27.2, - 27.4, - 27.5, - 27.7, - 28, - 28.2, - 28.4, - 28.6, - 28.8, - 28.9, - 29.1, - 29.3, - 29.4, - 29.5, - 29.6, - 29.6, - 29.7, - 29.9, - 30, - 30.1, - 30.2, - 30.3, - 30.4, - 30.4, - 30.3, - 29.9, - 29.1, - 28, - 27.2, - 27.2, - 26.9, - 26.6, - 26.4, - 26.1, - 26, - 26.2, - 26.3, - 26.4, - 26.6, - 26.9, - 27.1, - 27.3, - 27.2, - 27, - 27, - 27.2, - 27.4, - 27.5, - 27.6, - 27.7, - 27.9, - 28, - 28.2, - 28.4, - 28.9, - 29.3, - 29.8, - 30.7, - 30.9, - 31, - 31.4, - 32.3, - 32.9, - 33.5, - 34.5, - 35.2, - 35.4, - 35.6, - 35.9, - 36.3, - 36.7, - 37.6, - 38.6, - 40, - 42, - 43.2, - 43.5, - 43.7, - 44.2, - 44.8, - 45.4, - 45.9, - 46.4, - 46.6, - 46.6, - 46.6, - 46.5, - 46.2, - 45.7, - 45.3, - 44.9, - 44.4, - 43.9, - 43.4, - 42.9, - 42.8, - 42.4, - 41.8, - 41, - 40.2, - 39.6, - 39, - 38.7, - 38.5, - 38.4, - 38.2, - 38, - 37.9, - 37.8, - 37.7, - 37.6, - 37.4, - 36.6, - 35.6, - 35.3, - 35.2, - 35.1, - 34.8, - 34.4, - 34.4, - 34.4, - 34.4, - 34.3, - 34, - 33.6, - 33.2, - 32.8, - 32.6, - 32.5, - 32.4, - 32.6, - 32.6, - 32.6, - 32.6, - 32.5, - 32.5, - 32.5, - 32.3, - 32.5, - 32.4, - 32.3, - 32.1, - 32.1, - 31.9, - 31.8, - 31.6, - 31.6, - 31.5, - 31.4, - 31.2, - 31.2, - 31.3, - 31.7, - 32.1, - 32.2, - 32.1, - 32.1, - 32, - 31.8, - 31.7, - 31.4, - 30.6, - 30.1, - 29.9, - 29.7, - 29.6, - 29.6, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.6, - 29.6, - 29.7, - 29.6, - 29.4, - 29, - 28.7, - 28.5, - 28.4, - 28.3, - 28.2, - 27.7, - 27.4, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 27, - 27.1, - 27.2, - 27.3, - 27.4, - 27.5, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27, - 26.9, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.2, - 26.2, - 26.2, - 26.1, - 25.7, - 25.3, - 25, - 25.4, - 25.5, - 25.5, - 25.4, - 25.4, - 25.3, - 25.4, - 25.5, - 25.6, - 25.7, - 25.9, - 23.1, - 23.2, - 23.3, - 23.3, - 23.4, - 23.5, - 23.6, - 23.7, - 23.9, - 24, - 24.2, - 24.3, - 24.5, - 24.6, - 24.6, - 24.6, - 25.4, - 26.3, - 26.8, - 27, - 27.3, - 27.4, - 27.7, - 27.9, - 28.1, - 28.3, - 28.4, - 28.6, - 28.8, - 28.9, - 29.1, - 29.2, - 29.3, - 29.4, - 29.4, - 29.4, - 29.4, - 29.6, - 29.7, - 29.7, - 29.8, - 29.9, - 30.1, - 30.2, - 30.2, - 29.9, - 29.1, - 28.1, - 27.1, - 26.4, - 26.3, - 26.2, - 26, - 25.9, - 25.8, - 25.9, - 26.1, - 26.2, - 26.4, - 26.6, - 26.8, - 26.9, - 26.9, - 26.9, - 26.9, - 27.2, - 27.4, - 27.6, - 27.7, - 27.8, - 27.9, - 28.1, - 28.3, - 28.5, - 28.8, - 29.6, - 30.1, - 30.5, - 30.9, - 30.9, - 30.9, - 31.2, - 31.9, - 32.8, - 33.5, - 34.5, - 35.2, - 35.5, - 35.8, - 36.2, - 36.4, - 36.8, - 37.6, - 38.6, - 40, - 41.8, - 43.1, - 43.4, - 43.8, - 44.3, - 45, - 45.4, - 45.9, - 46.4, - 46.6, - 46.6, - 46.6, - 46.5, - 46.3, - 46, - 45.6, - 45.2, - 44.7, - 44.1, - 43.7, - 43.6, - 43.3, - 42.7, - 42.1, - 41.4, - 40.4, - 39.7, - 39.1, - 38.8, - 38.5, - 38.3, - 38.2, - 38.1, - 38, - 37.9, - 37.8, - 37.7, - 37.5, - 37.1, - 36.5, - 36.2, - 36, - 35.6, - 35.2, - 35, - 34.9, - 34.9, - 34.9, - 34.7, - 34.3, - 33.9, - 33.4, - 33, - 32.8, - 32.7, - 32.5, - 32.7, - 32.8, - 32.8, - 32.7, - 32.7, - 32.7, - 32.6, - 32.5, - 32.6, - 32.5, - 32.3, - 32.3, - 32.1, - 31.9, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.4, - 31.4, - 31.7, - 32.1, - 32.3, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.7, - 31.2, - 30.5, - 30.1, - 29.9, - 29.8, - 29.7, - 29.6, - 29.6, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.6, - 29.6, - 29.5, - 29.4, - 29, - 28.7, - 28.5, - 28.3, - 28.1, - 27.7, - 27.3, - 27, - 27, - 26.9, - 26.9, - 26.8, - 26.8, - 26.9, - 27, - 27.1, - 27.3, - 27.4, - 27.5, - 27.5, - 27.5, - 27.5, - 27.5, - 27.4, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26.3, - 26.2, - 26.1, - 26.1, - 25.9, - 25.6, - 25.3, - 25, - 25.4, - 25.5, - 25.5, - 25.4, - 25.4, - 25.3, - 25.3, - 25.4, - 25.5, - 25.6, - 25.9, - 23.1, - 23.2, - 23.3, - 23.3, - 23.4, - 23.4, - 23.5, - 23.6, - 23.7, - 23.8, - 23.9, - 24, - 24.2, - 24.3, - 24.6, - 25.2, - 26.3, - 26.9, - 27.2, - 27.4, - 27.5, - 27.7, - 27.9, - 28, - 28.2, - 28.4, - 28.6, - 28.7, - 28.8, - 29, - 29.1, - 29.3, - 29.3, - 29.3, - 29.2, - 29, - 28.9, - 29.2, - 29.2, - 29.2, - 29.3, - 29.4, - 29.6, - 29.7, - 29.5, - 28.8, - 27.9, - 26.9, - 26.1, - 25.9, - 25.8, - 25.7, - 25.7, - 25.8, - 26, - 26, - 26.1, - 26.1, - 26.2, - 26.3, - 26.5, - 26.6, - 26.7, - 26.8, - 27, - 27.3, - 27.6, - 28, - 28.2, - 28.3, - 28.5, - 28.6, - 28.9, - 29, - 29.3, - 30, - 30.7, - 31.2, - 31.5, - 31.6, - 31.5, - 31.5, - 31.8, - 32.6, - 33.5, - 34.4, - 35.2, - 35.6, - 35.8, - 36.2, - 36.5, - 36.9, - 37.5, - 38.6, - 40, - 41.9, - 43.1, - 43.4, - 43.7, - 44.3, - 44.8, - 45.2, - 45.7, - 46.2, - 46.4, - 46.5, - 46.5, - 46.5, - 46.4, - 46.3, - 45.9, - 45.3, - 44.9, - 44.5, - 44.2, - 44, - 43.5, - 42.8, - 42.1, - 41.4, - 40.5, - 39.7, - 39.2, - 38.8, - 38.5, - 38.4, - 38.3, - 38.2, - 38.1, - 38.1, - 37.9, - 37.8, - 37.6, - 37.5, - 37.3, - 37, - 36.5, - 36.1, - 35.6, - 35.5, - 35.5, - 35.4, - 35.3, - 35, - 34.5, - 34.1, - 33.6, - 33.2, - 33, - 32.9, - 32.7, - 32.8, - 32.9, - 32.9, - 32.9, - 33, - 32.9, - 32.8, - 32.7, - 32.7, - 32.6, - 32.4, - 32.3, - 32.1, - 31.9, - 31.9, - 31.8, - 31.6, - 31.7, - 31.6, - 31.6, - 31.8, - 32.2, - 32.3, - 32.4, - 32.3, - 32.3, - 32.2, - 32, - 31.8, - 31.4, - 30.9, - 30.4, - 30.1, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.3, - 28.9, - 28.5, - 28.4, - 28.2, - 27.7, - 27.2, - 27.1, - 26.9, - 26.8, - 26.8, - 26.8, - 26.7, - 26.7, - 26.8, - 26.9, - 27.1, - 27.2, - 27.3, - 27.4, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.2, - 26.1, - 25.9, - 25.6, - 25.5, - 25.2, - 25, - 25.4, - 25.5, - 25.5, - 25.4, - 25.4, - 25.3, - 25.2, - 25.3, - 25.4, - 25.6, - 25.8, - 23.1, - 23.2, - 23.3, - 23.4, - 23.5, - 23.5, - 23.6, - 23.7, - 23.9, - 23.9, - 24.1, - 24.4, - 24.8, - 25.4, - 26, - 26.5, - 26.9, - 27.2, - 27.4, - 27.5, - 27.7, - 27.8, - 28, - 28.1, - 28.3, - 28.4, - 28.6, - 28.7, - 28.9, - 29, - 29.2, - 29.3, - 29.3, - 29.2, - 29, - 28.4, - 28.2, - 28.6, - 28.4, - 28.4, - 28.5, - 28.6, - 28.9, - 28.9, - 28.4, - 27.5, - 26.6, - 25.9, - 25.6, - 25.6, - 25.6, - 25.7, - 25.8, - 26, - 26, - 26.1, - 26.2, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 26.8, - 27, - 27.1, - 27.3, - 27.8, - 28.4, - 28.9, - 28.9, - 29.1, - 29.4, - 29.6, - 29.8, - 29.8, - 30.2, - 30.9, - 31.5, - 31.9, - 32.1, - 32.1, - 32.2, - 32.4, - 32.9, - 33.7, - 34.5, - 35.2, - 35.5, - 35.7, - 36, - 36.4, - 36.8, - 37.6, - 38.5, - 39.6, - 41.6, - 42.6, - 43.2, - 43.4, - 43.6, - 44.1, - 44.6, - 45.2, - 45.7, - 45.9, - 46.2, - 46.5, - 46.6, - 46.6, - 46.5, - 46.1, - 45.6, - 45.3, - 45, - 44.7, - 44.2, - 43.6, - 42.9, - 42.1, - 41.2, - 40.4, - 39.8, - 39.2, - 38.8, - 38.6, - 38.5, - 38.4, - 38.3, - 38.3, - 38.1, - 38, - 37.8, - 37.7, - 37.7, - 37.7, - 37.4, - 36.9, - 36.5, - 36.3, - 36.2, - 36.1, - 35.9, - 35.6, - 35.2, - 34.8, - 34.3, - 33.8, - 33.4, - 33.2, - 33, - 32.8, - 32.9, - 33.1, - 33.1, - 33.1, - 33.1, - 33, - 32.9, - 32.9, - 32.9, - 32.7, - 32.4, - 32.3, - 32.1, - 32, - 31.9, - 31.8, - 31.8, - 31.8, - 31.6, - 31.8, - 32.1, - 32.4, - 32.5, - 32.5, - 32.4, - 32.3, - 32.2, - 32, - 31.7, - 31.2, - 30.8, - 30.3, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.4, - 29.4, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.2, - 28.9, - 28.6, - 28.4, - 28.2, - 27.7, - 27.2, - 26.9, - 26.8, - 26.8, - 26.7, - 26.7, - 26.6, - 26.6, - 26.7, - 26.8, - 27, - 27.1, - 27.2, - 27.3, - 27.4, - 27.4, - 27.4, - 27.3, - 27.1, - 27, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 25.9, - 25.8, - 25.5, - 25.5, - 25.1, - 25.1, - 25.4, - 25.5, - 25.5, - 25.4, - 25.3, - 25.3, - 25.2, - 25.2, - 25.3, - 25.5, - 25.6, - 23.3, - 23.4, - 23.5, - 23.6, - 23.7, - 23.9, - 24, - 24.2, - 24.5, - 24.7, - 24.9, - 25.3, - 25.8, - 26.3, - 26.6, - 26.9, - 27.1, - 27.3, - 27.5, - 27.6, - 27.8, - 27.9, - 28, - 28.1, - 28.3, - 28.4, - 28.6, - 28.7, - 28.8, - 29, - 29.1, - 29.2, - 29.2, - 29.1, - 28.6, - 27.7, - 27.3, - 27.7, - 27.5, - 27.4, - 27.5, - 27.6, - 27.9, - 27.7, - 27.2, - 26.3, - 25.7, - 25.4, - 25.4, - 25.6, - 26.1, - 26.4, - 26.5, - 26.4, - 26.3, - 26.2, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 26.8, - 27, - 27.1, - 27.2, - 27.6, - 27.9, - 28.5, - 29.2, - 29.5, - 29.8, - 30, - 30.2, - 30.3, - 30.4, - 30.5, - 31, - 31.6, - 32.1, - 32.4, - 32.6, - 32.8, - 33.2, - 33.7, - 34.1, - 34.8, - 35.3, - 35.5, - 35.7, - 35.9, - 36.2, - 36.7, - 37.5, - 38.3, - 39.2, - 40.8, - 41.9, - 42.5, - 42.9, - 43.3, - 43.8, - 44.4, - 44.9, - 45.4, - 45.6, - 46, - 46.5, - 46.7, - 46.7, - 46.6, - 46.4, - 46.1, - 45.8, - 45.4, - 44.9, - 44.4, - 43.6, - 42.9, - 42.2, - 41.3, - 40.5, - 39.8, - 39.4, - 39, - 38.8, - 38.7, - 38.5, - 38.4, - 38.3, - 38.1, - 38, - 38, - 38, - 38, - 37.8, - 37.7, - 37.3, - 37, - 37, - 36.9, - 36.6, - 36.4, - 36, - 35.5, - 34.9, - 34.4, - 33.9, - 33.4, - 33.2, - 33, - 32.9, - 33.1, - 33.3, - 33.3, - 33.3, - 33.2, - 33.1, - 33, - 33, - 33, - 32.7, - 32.4, - 32.3, - 32.1, - 32, - 32, - 32, - 32, - 31.8, - 31.7, - 31.8, - 32.2, - 32.5, - 32.6, - 32.6, - 32.5, - 32.4, - 32.2, - 32, - 31.6, - 31.2, - 30.8, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 29.8, - 29.7, - 29.5, - 29.4, - 29.4, - 29.4, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 28.9, - 28.5, - 28.3, - 28, - 27.5, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.5, - 26.6, - 26.7, - 26.9, - 27, - 27.1, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 26.9, - 26.9, - 26.9, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.6, - 25.4, - 25.4, - 25, - 25.1, - 25.4, - 25.4, - 25.5, - 25.4, - 25.3, - 25.3, - 25.2, - 25.2, - 25.3, - 25.3, - 25.4, - 23.7, - 23.9, - 24, - 24.1, - 24.4, - 24.6, - 24.7, - 25.1, - 25.3, - 25.4, - 25.6, - 25.9, - 26.1, - 26.4, - 26.7, - 27, - 27.2, - 27.4, - 27.5, - 27.7, - 27.9, - 28, - 28, - 28.2, - 28.3, - 28.4, - 28.5, - 28.7, - 28.8, - 29, - 29.1, - 29.1, - 29.1, - 28.9, - 28.2, - 27.1, - 26.4, - 26.6, - 26.4, - 26.1, - 26.3, - 26.5, - 26.6, - 26.5, - 26.1, - 25.5, - 25.3, - 25.4, - 25.7, - 26.3, - 27.1, - 27.5, - 27.5, - 27.3, - 26.7, - 26.4, - 26.4, - 26.5, - 26.5, - 26.6, - 26.8, - 27, - 27.2, - 27.4, - 27.5, - 27.7, - 28.2, - 28.7, - 29.4, - 29.9, - 30.3, - 30.5, - 30.6, - 30.8, - 31, - 31.1, - 31.3, - 31.9, - 32.2, - 32.6, - 33.1, - 33.5, - 33.9, - 34.2, - 34.6, - 35.1, - 35.4, - 35.6, - 35.8, - 35.9, - 36.1, - 36.6, - 37.1, - 37.8, - 38.9, - 40.5, - 41.8, - 42.4, - 43, - 43.5, - 44.2, - 44.8, - 45.3, - 45.6, - 45.7, - 45.9, - 46.4, - 46.8, - 46.9, - 46.8, - 46.7, - 46.4, - 46, - 45.6, - 44.9, - 44.3, - 43.4, - 42.8, - 42.2, - 41.4, - 40.4, - 39.8, - 39.5, - 39.1, - 39, - 38.8, - 38.6, - 38.5, - 38.4, - 38.3, - 38.2, - 38.3, - 38.3, - 38.2, - 38, - 37.8, - 37.6, - 37.4, - 37.5, - 37.4, - 37.1, - 36.7, - 36.3, - 35.7, - 35.1, - 34.6, - 34, - 33.5, - 33.3, - 33.2, - 33.2, - 33.3, - 33.5, - 33.5, - 33.4, - 33.3, - 33.2, - 33.1, - 33.1, - 33, - 32.7, - 32.5, - 32.3, - 32.1, - 32.2, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 31.9, - 32.3, - 32.6, - 32.7, - 32.7, - 32.6, - 32.4, - 32.2, - 32, - 31.7, - 31.3, - 31.1, - 30.9, - 30.7, - 30.7, - 30.5, - 30.2, - 29.9, - 29.7, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.2, - 28.8, - 28.5, - 28.4, - 28, - 27.5, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.6, - 26.7, - 26.9, - 27, - 27, - 27.1, - 27, - 27, - 26.9, - 26.8, - 26.7, - 26.7, - 26.6, - 26.5, - 26.4, - 26.4, - 26.3, - 26.2, - 26.2, - 26, - 25.9, - 25.6, - 25.5, - 25.4, - 25.3, - 24.9, - 25.1, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.3, - 25.2, - 25.1, - 25.2, - 25.2, - 25.3, - 24.4, - 24.5, - 24.6, - 24.8, - 25.1, - 25.2, - 25.3, - 25.5, - 25.5, - 25.6, - 25.8, - 26, - 26.2, - 26.5, - 26.7, - 27, - 27.2, - 27.4, - 27.5, - 27.7, - 27.8, - 27.9, - 28, - 28.1, - 28.2, - 28.4, - 28.5, - 28.7, - 28.8, - 28.9, - 29, - 29, - 29, - 28.5, - 27.5, - 26.4, - 25.7, - 25.5, - 25.4, - 25.4, - 25.5, - 25.6, - 25.6, - 25.6, - 25.3, - 25.3, - 25.4, - 25.6, - 26.1, - 27, - 27.7, - 27.9, - 27.7, - 27.4, - 27, - 26.7, - 26.6, - 26.6, - 26.7, - 26.8, - 27, - 27.2, - 27.5, - 27.6, - 27.7, - 27.9, - 28.4, - 29, - 29.6, - 30, - 30.5, - 30.9, - 31, - 31.3, - 31.4, - 31.6, - 31.8, - 32.1, - 32.5, - 33, - 33.5, - 33.9, - 34.4, - 34.8, - 35.1, - 35.3, - 35.5, - 35.8, - 36, - 36.2, - 36.3, - 36.6, - 36.9, - 37.5, - 38.8, - 40.8, - 42.2, - 42.8, - 43.4, - 43.8, - 44.5, - 45.1, - 45.7, - 45.9, - 45.9, - 46.1, - 46.5, - 46.8, - 47, - 47, - 46.9, - 46.6, - 46.2, - 45.6, - 44.9, - 44.2, - 43.2, - 42.3, - 41.8, - 41, - 40.2, - 39.9, - 39.6, - 39.3, - 39.1, - 38.8, - 38.7, - 38.6, - 38.5, - 38.5, - 38.5, - 38.5, - 38.5, - 38.5, - 38.3, - 38.1, - 37.8, - 37.7, - 37.7, - 37.7, - 37.5, - 37.1, - 36.5, - 35.9, - 35.3, - 34.8, - 34.2, - 33.7, - 33.7, - 33.6, - 33.6, - 33.5, - 33.7, - 33.7, - 33.6, - 33.4, - 33.3, - 33.2, - 33.2, - 33.1, - 32.8, - 32.5, - 32.4, - 32.4, - 32.3, - 32.3, - 32.2, - 32.1, - 31.9, - 32, - 32, - 32.1, - 32.5, - 32.7, - 32.7, - 32.6, - 32.4, - 32.3, - 32.1, - 31.9, - 31.7, - 31.4, - 31.2, - 31.1, - 31.1, - 30.9, - 30.4, - 30.1, - 29.8, - 29.6, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.2, - 28.8, - 28.5, - 28.3, - 27.8, - 27.3, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.4, - 26.4, - 26.6, - 26.7, - 26.8, - 26.9, - 26.9, - 26.9, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.2, - 26.1, - 26.1, - 26, - 25.8, - 25.4, - 25.4, - 25.3, - 25.2, - 24.9, - 25.1, - 25.3, - 25.3, - 25.4, - 25.4, - 25.2, - 25.2, - 25.2, - 25.1, - 25.2, - 25.2, - 25.3, - 24.8, - 24.9, - 25, - 25.1, - 25.2, - 25.2, - 25.3, - 25.3, - 25.4, - 25.5, - 25.7, - 25.9, - 26.2, - 26.4, - 26.7, - 27, - 27.2, - 27.4, - 27.5, - 27.6, - 27.8, - 27.9, - 28, - 28.1, - 28.2, - 28.4, - 28.5, - 28.6, - 28.7, - 28.8, - 28.9, - 28.8, - 28.6, - 27.7, - 26.5, - 25.7, - 25.2, - 25.1, - 25.1, - 25.1, - 25.2, - 25.2, - 25.2, - 25.2, - 25.2, - 25.3, - 25.4, - 25.7, - 26.3, - 27.1, - 27.5, - 27.4, - 27.1, - 26.9, - 26.7, - 26.6, - 26.7, - 26.8, - 26.9, - 27, - 27.2, - 27.4, - 27.6, - 27.7, - 27.8, - 28, - 28.4, - 29.1, - 29.5, - 29.8, - 30.3, - 30.7, - 31.2, - 31.5, - 31.8, - 32.1, - 32.2, - 32.5, - 32.9, - 33.4, - 33.8, - 34.4, - 35, - 35.2, - 35.4, - 35.5, - 35.7, - 36, - 36.2, - 36.5, - 36.7, - 37, - 37.6, - 38, - 38.9, - 41.2, - 42.4, - 43.1, - 43.7, - 44.2, - 44.9, - 45.4, - 46, - 46.2, - 46.2, - 46.5, - 46.8, - 47, - 47.2, - 47.2, - 47.1, - 46.8, - 46.4, - 45.8, - 45, - 44, - 43, - 42.3, - 41.9, - 41.4, - 40.9, - 40.4, - 40, - 39.7, - 39.3, - 39, - 38.9, - 38.7, - 38.8, - 38.8, - 38.7, - 38.7, - 38.8, - 38.7, - 38.5, - 38.2, - 38.1, - 38, - 38, - 37.9, - 37.8, - 37.4, - 36.8, - 36.1, - 35.6, - 35.1, - 34.7, - 34.4, - 34.3, - 34.1, - 33.9, - 33.6, - 33.9, - 33.9, - 33.7, - 33.5, - 33.4, - 33.4, - 33.3, - 33, - 32.7, - 32.6, - 32.6, - 32.6, - 32.5, - 32.4, - 32.3, - 32.1, - 32.1, - 32.1, - 32, - 32, - 32.3, - 32.6, - 32.7, - 32.6, - 32.5, - 32.3, - 32.2, - 31.9, - 31.7, - 31.4, - 31.2, - 31.1, - 31.1, - 31, - 30.6, - 30.2, - 29.9, - 29.6, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 28.9, - 28.6, - 28.4, - 27.8, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.4, - 26.5, - 26.7, - 26.7, - 26.7, - 26.7, - 26.6, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 26.1, - 26.1, - 26.1, - 26, - 25.9, - 25.6, - 25.4, - 25.3, - 25.2, - 25.2, - 24.9, - 25.1, - 25.3, - 25.3, - 25.3, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 25.2, - 25.2, - 24.9, - 25, - 25, - 25.1, - 25.1, - 25.1, - 24.9, - 24.6, - 24.7, - 24.7, - 25.3, - 25.8, - 26.1, - 26.4, - 26.7, - 26.9, - 27.1, - 27.3, - 27.4, - 27.6, - 27.7, - 27.9, - 28, - 28.1, - 28.2, - 28.3, - 28.4, - 28.5, - 28.6, - 28.7, - 28.7, - 28.5, - 28, - 26.8, - 25.7, - 25.1, - 24.8, - 24.8, - 24.9, - 24.9, - 25, - 25, - 25, - 25.1, - 25.2, - 25.3, - 25.4, - 25.7, - 26.3, - 26.8, - 26.7, - 26.5, - 26.4, - 26.4, - 26.3, - 26.4, - 26.6, - 26.8, - 27, - 27.1, - 27.2, - 27.4, - 27.5, - 27.5, - 27.6, - 27.8, - 28.2, - 28.7, - 29.1, - 29.5, - 30.1, - 30.6, - 31.2, - 31.7, - 32.1, - 32.5, - 32.7, - 33, - 33.4, - 33.7, - 34.2, - 34.9, - 35.2, - 35.4, - 35.6, - 35.8, - 35.9, - 36.1, - 36.3, - 36.6, - 37, - 37.5, - 38.2, - 39, - 39.9, - 41.6, - 42.4, - 43.1, - 43.6, - 44.3, - 45, - 45.4, - 46.1, - 46.4, - 46.5, - 46.8, - 47.1, - 47.3, - 47.5, - 47.5, - 47.3, - 47, - 46.6, - 45.9, - 44.8, - 43.7, - 43.4, - 43, - 42.6, - 42.1, - 41.6, - 41.1, - 40.5, - 39.9, - 39.5, - 39.2, - 38.9, - 39.1, - 39.2, - 39.2, - 39.1, - 39.1, - 39.1, - 39, - 38.7, - 38.5, - 38.4, - 38.3, - 38.2, - 38.1, - 37.9, - 37.7, - 37.1, - 36.5, - 35.9, - 35.3, - 35.1, - 35.1, - 35, - 34.6, - 34.2, - 33.9, - 33.9, - 33.9, - 33.8, - 33.7, - 33.6, - 33.4, - 33.1, - 32.9, - 32.7, - 32.8, - 32.8, - 32.7, - 32.6, - 32.4, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 32, - 32.3, - 32.6, - 32.6, - 32.5, - 32.4, - 32.2, - 31.9, - 31.4, - 31.2, - 31, - 30.9, - 30.9, - 30.8, - 30.5, - 30.2, - 29.9, - 29.7, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29, - 28.7, - 28.3, - 27.5, - 27.2, - 27, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.3, - 26.3, - 26.4, - 26.5, - 26.6, - 26.6, - 26.4, - 26.3, - 26.3, - 26.3, - 26.2, - 26.2, - 26.1, - 26, - 26, - 26, - 26, - 26, - 25.9, - 25.6, - 25.4, - 25.3, - 25.2, - 25.2, - 25.2, - 24.8, - 25.1, - 25.2, - 25.3, - 25.3, - 25.2, - 25.2, - 25.2, - 25.1, - 25.2, - 25.1, - 25.2, - 25.2, - 24.9, - 25, - 25, - 25, - 24.9, - 24.5, - 23.9, - 23.5, - 23.4, - 23.9, - 25.1, - 25.7, - 26.1, - 26.3, - 26.6, - 26.8, - 27, - 27.1, - 27.3, - 27.5, - 27.6, - 27.7, - 27.9, - 27.9, - 28.1, - 28.2, - 28.3, - 28.4, - 28.4, - 28.4, - 28.3, - 28, - 27.2, - 25.7, - 24.9, - 24.7, - 24.7, - 24.8, - 24.8, - 24.9, - 24.9, - 25, - 25.2, - 25.3, - 25.2, - 25.4, - 25.4, - 25.7, - 26.2, - 26.4, - 26.1, - 26, - 26.1, - 26.1, - 26.2, - 26.3, - 26.4, - 26.7, - 26.9, - 27.1, - 27.1, - 27.2, - 27.2, - 27.3, - 27.5, - 27.6, - 27.9, - 28.3, - 28.8, - 29.5, - 30.1, - 30.6, - 31.2, - 31.8, - 32.2, - 32.6, - 32.9, - 33.3, - 33.8, - 34.2, - 34.9, - 35.2, - 35.4, - 35.7, - 35.8, - 36, - 36.1, - 36.3, - 36.5, - 36.8, - 37.3, - 38, - 39, - 40.1, - 41.5, - 42.3, - 42.7, - 43.1, - 43.5, - 44, - 44.7, - 45.2, - 45.9, - 46.4, - 46.7, - 47.1, - 47.4, - 47.7, - 47.7, - 47.7, - 47.5, - 47.3, - 46.8, - 45.9, - 44.8, - 44.4, - 44.3, - 43.7, - 43.1, - 42.6, - 42.1, - 41.7, - 41, - 40.1, - 39.6, - 39.3, - 39.3, - 39.6, - 39.6, - 39.7, - 39.7, - 39.7, - 39.6, - 39.5, - 39.3, - 38.9, - 38.7, - 38.5, - 38.3, - 38.2, - 38, - 37.9, - 37.5, - 36.8, - 36.1, - 35.6, - 35.5, - 35.5, - 35.3, - 35, - 34.5, - 34.1, - 34, - 33.9, - 33.8, - 33.6, - 33.4, - 33.1, - 32.9, - 32.9, - 32.9, - 32.9, - 32.9, - 32.8, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32, - 31.8, - 31.7, - 31.8, - 32.2, - 32.4, - 32.4, - 32.3, - 32.1, - 31.7, - 31.2, - 31, - 30.8, - 30.7, - 30.6, - 30.5, - 30.4, - 30.1, - 29.9, - 29.7, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.4, - 29, - 28.6, - 28.2, - 27.4, - 27, - 26.9, - 26.9, - 26.7, - 26.7, - 26.6, - 26.4, - 26.3, - 26.2, - 26.2, - 26.3, - 26.4, - 26.3, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26, - 26, - 25.9, - 25.9, - 25.9, - 25.7, - 25.5, - 25.3, - 25.2, - 25.2, - 25.1, - 25.2, - 25.1, - 24.8, - 25.1, - 25.2, - 25.2, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25.2, - 24.8, - 24.9, - 24.9, - 24.9, - 24.5, - 23.7, - 23.1, - 22.7, - 22.7, - 23.4, - 25, - 25.8, - 26, - 26.3, - 26.5, - 26.7, - 26.8, - 27, - 27.2, - 27.3, - 27.4, - 27.5, - 27.5, - 27.6, - 27.7, - 27.9, - 28.1, - 28.2, - 28.2, - 28, - 27.7, - 27.1, - 26.1, - 24.7, - 24.6, - 24.6, - 24.7, - 24.7, - 24.8, - 24.8, - 25.1, - 25.8, - 26.2, - 26.1, - 25.6, - 25.3, - 25.4, - 25.7, - 25.8, - 25.8, - 25.9, - 26, - 26.1, - 26.2, - 26.4, - 26.4, - 26.4, - 26.5, - 26.7, - 26.9, - 26.9, - 27, - 27.2, - 27.3, - 27.5, - 27.8, - 28, - 28.5, - 29, - 29.4, - 30, - 30.5, - 31.2, - 31.7, - 32.2, - 32.6, - 33.1, - 33.5, - 33.8, - 34.4, - 35.1, - 35.3, - 35.6, - 35.9, - 36.1, - 36.2, - 36.3, - 36.5, - 36.7, - 37, - 37.7, - 38.5, - 39.9, - 41.1, - 42.5, - 42.9, - 43.3, - 43.5, - 44, - 44.5, - 45, - 45.3, - 45.8, - 46.3, - 46.8, - 47.3, - 47.7, - 48, - 48, - 47.9, - 47.7, - 47.3, - 46.8, - 45.8, - 45.3, - 45.3, - 45, - 44.2, - 43.6, - 43.1, - 42.5, - 42, - 41.2, - 40.3, - 39.7, - 39.5, - 39.7, - 40, - 40.1, - 40.2, - 40.3, - 40.4, - 40.3, - 40.2, - 40, - 39.6, - 39, - 38.6, - 38.5, - 38.2, - 38.1, - 37.9, - 37.6, - 36.9, - 36.4, - 36.1, - 35.9, - 35.7, - 35.4, - 35, - 34.6, - 34.1, - 33.9, - 33.7, - 33.5, - 33.4, - 33.2, - 33.1, - 33.2, - 33.2, - 33.1, - 33, - 33, - 32.9, - 32.8, - 32.7, - 32.6, - 32.5, - 32.4, - 32.2, - 32, - 31.9, - 31.7, - 31.6, - 31.7, - 32.1, - 32.1, - 32.1, - 31.8, - 31.4, - 31.1, - 30.9, - 30.7, - 30.5, - 30.4, - 30.3, - 30.2, - 30, - 29.9, - 29.8, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.3, - 29.3, - 29.4, - 29.4, - 29.3, - 28.8, - 28.5, - 28, - 27.3, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26.1, - 26, - 26, - 26, - 25.9, - 25.9, - 25.9, - 25.9, - 26, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 25, - 24.8, - 25.1, - 25.1, - 25.2, - 25.2, - 25.2, - 25.1, - 25.1, - 25, - 25.1, - 25.1, - 25.1, - 25.2, - 24.7, - 24.8, - 24.8, - 24.6, - 24, - 23.2, - 22.7, - 22.4, - 22.4, - 23.4, - 25, - 25.8, - 26, - 26.2, - 26.3, - 26.5, - 26.7, - 26.9, - 27, - 27, - 27, - 26.9, - 26.8, - 26.8, - 26.9, - 27.4, - 27.7, - 27.8, - 27.7, - 27.4, - 26.9, - 26.1, - 24.9, - 24.4, - 24.5, - 24.6, - 24.6, - 24.7, - 24.7, - 24.9, - 25.7, - 26.8, - 27.4, - 27.2, - 26.3, - 25.6, - 25.5, - 25.6, - 25.6, - 25.7, - 25.9, - 26.1, - 26.1, - 26.2, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 26.8, - 26.9, - 27.1, - 27.3, - 27.5, - 27.7, - 28.1, - 28.5, - 29, - 29.4, - 29.7, - 30.1, - 30.5, - 31.1, - 31.6, - 32.2, - 32.6, - 33.1, - 33.4, - 33.6, - 34.1, - 35, - 35.3, - 35.6, - 35.9, - 36.1, - 36.2, - 36.4, - 36.6, - 37, - 37.4, - 38.1, - 39.1, - 40.4, - 41.6, - 42.8, - 43.3, - 43.6, - 44.1, - 44.5, - 45.1, - 45.5, - 45.8, - 46, - 46.3, - 46.9, - 47.5, - 48, - 48.2, - 48.1, - 48, - 47.7, - 47.3, - 46.6, - 46.1, - 46, - 45.9, - 45.4, - 44.7, - 44, - 43.2, - 42.6, - 41.9, - 41.2, - 40.4, - 40, - 39.8, - 40, - 40.2, - 40.6, - 40.8, - 41.1, - 41.2, - 41.1, - 40.9, - 40.5, - 40, - 39.4, - 38.8, - 38.5, - 38.3, - 38.1, - 37.9, - 37.7, - 37.2, - 36.7, - 36.5, - 36.2, - 35.8, - 35.5, - 34.9, - 34.4, - 34, - 33.7, - 33.5, - 33.4, - 33.4, - 33.3, - 33.4, - 33.5, - 33.3, - 33.2, - 33.2, - 33.1, - 33.1, - 33, - 32.9, - 32.8, - 32.6, - 32.4, - 32.3, - 32.1, - 31.9, - 31.7, - 31.6, - 31.4, - 31.6, - 31.8, - 31.7, - 31.4, - 31.1, - 30.9, - 30.7, - 30.5, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.7, - 29.5, - 29.3, - 29.3, - 29.4, - 29.4, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.2, - 29.2, - 29.3, - 29.3, - 29, - 28.6, - 28.2, - 27.6, - 27.1, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.4, - 26.4, - 26.4, - 26.3, - 26.2, - 26.1, - 25.9, - 25.9, - 25.8, - 25.8, - 25.7, - 25.7, - 25.8, - 25.8, - 25.8, - 25.6, - 25.5, - 25.4, - 25.3, - 25.3, - 25.2, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25, - 24.8, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25, - 25.1, - 25, - 25, - 25.1, - 25.1, - 25.1, - 24.6, - 24.6, - 24.6, - 24.1, - 23.4, - 22.8, - 22.5, - 22.2, - 22.3, - 23.3, - 24.9, - 25.7, - 26, - 26, - 26, - 26.3, - 26.5, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.7, - 25.7, - 25.9, - 26.5, - 26.9, - 27.1, - 26.9, - 26.4, - 25.8, - 24.8, - 24.3, - 24.4, - 24.5, - 24.6, - 24.7, - 24.7, - 24.7, - 25, - 25.7, - 27, - 27.7, - 27.8, - 27.3, - 26.5, - 25.9, - 25.8, - 25.8, - 25.7, - 25.8, - 26, - 26, - 26.2, - 26.4, - 26.4, - 26.6, - 26.6, - 26.8, - 26.8, - 27, - 27.2, - 27.4, - 27.6, - 27.8, - 28.2, - 28.7, - 29.3, - 29.5, - 29.8, - 30.2, - 30.6, - 31, - 31.5, - 31.9, - 32.2, - 32.6, - 32.8, - 33.2, - 33.8, - 34.9, - 35.2, - 35.5, - 35.9, - 36.1, - 36.2, - 36.4, - 36.7, - 37.1, - 37.8, - 38.6, - 39.8, - 40.9, - 41.9, - 43, - 43.4, - 44, - 44.6, - 45, - 45.5, - 45.9, - 46.3, - 46.5, - 46.7, - 47.1, - 47.8, - 48.2, - 48.4, - 48.3, - 48.1, - 47.7, - 47.2, - 46.7, - 46.7, - 46.5, - 46.1, - 45.6, - 44.8, - 44, - 43.2, - 42.6, - 41.9, - 41.4, - 40.9, - 40.4, - 40.1, - 40.3, - 40.6, - 41.1, - 41.6, - 41.9, - 42, - 42.1, - 41.8, - 41.1, - 40.3, - 39.7, - 39, - 38.6, - 38.3, - 38.1, - 37.9, - 37.7, - 37.4, - 37.1, - 36.7, - 36.2, - 35.8, - 35.3, - 34.7, - 34.2, - 33.8, - 33.7, - 33.7, - 33.7, - 33.7, - 33.6, - 33.6, - 33.5, - 33.4, - 33.4, - 33.4, - 33.3, - 33.2, - 33.1, - 33, - 32.8, - 32.7, - 32.5, - 32.3, - 32.1, - 31.9, - 31.7, - 31.6, - 31.3, - 31.3, - 31.4, - 31.4, - 31.2, - 30.9, - 30.7, - 30.6, - 30.5, - 30.4, - 30.3, - 30.2, - 30, - 29.9, - 29.9, - 29.7, - 29.5, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 29.3, - 29.1, - 29.1, - 29.2, - 29.2, - 29, - 28.6, - 28.3, - 27.7, - 27.1, - 26.9, - 26.7, - 26.6, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.4, - 26.3, - 26, - 25.8, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.5, - 25.4, - 25.4, - 25.3, - 25.3, - 25.2, - 25.1, - 25.1, - 25, - 25, - 25, - 25, - 25, - 24.8, - 25.1, - 25.1, - 25, - 25.1, - 25, - 25, - 25, - 25, - 25, - 25.1, - 25.1, - 25.1, - 24.4, - 24.2, - 24, - 23.5, - 23, - 22.6, - 22.3, - 22.1, - 22.2, - 22.9, - 24.4, - 25.3, - 25.8, - 25.8, - 25.7, - 26, - 26.1, - 26, - 25.4, - 24.6, - 24.4, - 24.4, - 24.2, - 24.3, - 24.6, - 25.3, - 25.6, - 25.8, - 25.6, - 25.1, - 24.6, - 24.2, - 24.2, - 24.4, - 24.5, - 24.6, - 24.6, - 24.6, - 24.7, - 24.9, - 25.7, - 27, - 27.7, - 28, - 28, - 27.6, - 27.2, - 26.8, - 26.5, - 26.2, - 26, - 25.9, - 26.1, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 26.8, - 27, - 27, - 27.1, - 27.3, - 27.6, - 27.8, - 28, - 28.3, - 28.8, - 29, - 29.3, - 29.8, - 30.4, - 30.7, - 30.9, - 31.1, - 31.6, - 31.9, - 32.3, - 33, - 33.8, - 34.7, - 35.1, - 35.4, - 35.7, - 35.9, - 36, - 36.3, - 36.7, - 37.2, - 38.1, - 39.1, - 40.6, - 41.5, - 42.2, - 43.2, - 43.6, - 44.2, - 44.9, - 45.4, - 45.7, - 46, - 46.5, - 46.9, - 47.2, - 47.5, - 48, - 48.4, - 48.5, - 48.5, - 48.2, - 47.8, - 47.5, - 47.2, - 47.2, - 46.8, - 46.3, - 45.7, - 44.9, - 44.2, - 43.5, - 42.8, - 42.4, - 42, - 41.5, - 40.9, - 40.5, - 40.8, - 41.2, - 41.7, - 42, - 42.4, - 42.5, - 42.6, - 42.4, - 41.5, - 40.6, - 39.8, - 39.1, - 38.6, - 38.3, - 38.1, - 37.9, - 37.7, - 37.6, - 37.2, - 36.7, - 36.1, - 35.7, - 35, - 34.4, - 34, - 33.9, - 34.1, - 34.1, - 34.1, - 34.1, - 33.9, - 33.7, - 33.6, - 33.6, - 33.6, - 33.5, - 33.4, - 33.4, - 33.2, - 33.1, - 32.9, - 32.7, - 32.5, - 32.3, - 32.1, - 31.8, - 31.6, - 31.5, - 31.3, - 31.2, - 31.1, - 31.1, - 31.1, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.6, - 29.5, - 29.3, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 28.7, - 28.2, - 27.8, - 27.2, - 26.9, - 26.7, - 26.7, - 26.7, - 26.6, - 26.6, - 26.6, - 26.7, - 26.7, - 26.7, - 26.7, - 26.7, - 26.6, - 26.6, - 26.3, - 25.9, - 25.7, - 25.7, - 25.6, - 25.5, - 25.4, - 25.3, - 25.2, - 25.2, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25, - 25, - 25, - 25, - 25, - 24.7, - 25, - 25, - 25, - 25, - 25, - 25, - 25, - 25, - 25, - 25, - 25.1, - 25.1, - 24, - 23.7, - 23.3, - 23.1, - 22.9, - 22.6, - 22.2, - 21.9, - 22.1, - 22.4, - 23.6, - 24.7, - 25.3, - 25.3, - 25, - 25.2, - 25.2, - 24.7, - 23.7, - 23.2, - 23.2, - 23.4, - 23.5, - 23.7, - 23.9, - 24.2, - 24.4, - 24.5, - 24.3, - 24.1, - 24, - 24, - 24.1, - 24.3, - 24.4, - 24.5, - 24.5, - 24.6, - 24.7, - 24.9, - 25.8, - 27.1, - 27.8, - 28.2, - 28.3, - 28.3, - 28.2, - 27.8, - 27.4, - 26.9, - 26.4, - 26.1, - 26.3, - 26.4, - 26.4, - 26.5, - 26.8, - 27, - 26.9, - 27, - 27.2, - 27.2, - 27.3, - 27.5, - 27.6, - 27.8, - 28, - 28.1, - 28.4, - 28.6, - 29.1, - 29.6, - 29.8, - 29.8, - 30.2, - 30.6, - 31.4, - 32.1, - 32.8, - 33.7, - 34.6, - 35, - 35.3, - 35.5, - 35.8, - 35.9, - 36.2, - 36.6, - 37.2, - 38.3, - 39.6, - 41.3, - 41.9, - 42.4, - 43.2, - 43.7, - 44.3, - 45.1, - 45.6, - 45.9, - 46.2, - 46.7, - 47.1, - 47.5, - 47.9, - 48.3, - 48.5, - 48.6, - 48.6, - 48.4, - 48.1, - 48, - 47.7, - 47.5, - 47.1, - 46.5, - 46, - 45.3, - 44.7, - 44.1, - 43.5, - 43, - 42.5, - 41.9, - 41.4, - 41.1, - 41.4, - 41.8, - 42.2, - 42.4, - 42.7, - 42.9, - 43, - 42.7, - 41.9, - 40.8, - 39.9, - 39.1, - 38.6, - 38.2, - 38, - 37.9, - 37.7, - 37.5, - 36.9, - 36.4, - 35.9, - 35.4, - 34.8, - 34.2, - 34.2, - 34.2, - 34.4, - 34.5, - 34.5, - 34.3, - 34.1, - 33.8, - 33.7, - 33.7, - 33.6, - 33.5, - 33.4, - 33.4, - 33.3, - 33.1, - 32.9, - 32.7, - 32.5, - 32.3, - 32.1, - 31.9, - 31.7, - 31.5, - 31.4, - 31.2, - 31, - 30.9, - 30.9, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.5, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.3, - 29.3, - 29.2, - 29.1, - 29, - 28.8, - 28.6, - 28.4, - 27.8, - 27.3, - 26.9, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.9, - 26.8, - 26.8, - 26.8, - 26.7, - 26.6, - 26.3, - 25.8, - 25.7, - 25.7, - 25.6, - 25.5, - 25.3, - 25.2, - 25.1, - 25.2, - 25.2, - 25.2, - 25.1, - 25, - 25, - 24.9, - 25, - 24.9, - 25, - 24.7, - 24.9, - 25, - 25, - 25, - 24.9, - 25, - 25, - 25, - 24.9, - 25, - 25, - 25.1, - 23.5, - 23.2, - 23, - 23, - 22.8, - 22.5, - 22.2, - 21.9, - 21.8, - 22, - 22.7, - 23.7, - 24.5, - 24.2, - 23.3, - 23.5, - 23.6, - 23.1, - 22.7, - 22.5, - 22.8, - 23.1, - 23.3, - 23.4, - 23.5, - 23.6, - 23.8, - 23.7, - 23.7, - 23.7, - 23.8, - 23.9, - 24, - 24.2, - 24.4, - 24.4, - 24.6, - 24.9, - 25.1, - 25.1, - 25.9, - 27.1, - 27.8, - 28.1, - 28.3, - 28.4, - 28.3, - 28, - 27.5, - 27.1, - 26.6, - 26.4, - 26.6, - 26.7, - 26.4, - 26.6, - 26.9, - 27.3, - 27.2, - 27.1, - 27.2, - 27.3, - 27.3, - 27.4, - 27.5, - 27.7, - 27.8, - 27.9, - 28, - 28.2, - 28.4, - 28.6, - 28.7, - 29, - 29.3, - 29.8, - 31, - 31.7, - 32.4, - 33.3, - 34.1, - 34.8, - 35.1, - 35.3, - 35.6, - 35.7, - 36, - 36.4, - 37, - 38, - 39.1, - 41.1, - 41.9, - 42.3, - 43.1, - 43.7, - 44.5, - 45.2, - 45.7, - 45.9, - 46.2, - 46.7, - 47.2, - 47.6, - 47.9, - 48.4, - 48.7, - 48.7, - 48.7, - 48.6, - 48.5, - 48.4, - 48.1, - 47.8, - 47.2, - 46.8, - 46.4, - 45.8, - 45.3, - 44.7, - 44.1, - 43.5, - 42.9, - 42.2, - 41.8, - 41.7, - 41.9, - 42.2, - 42.6, - 42.8, - 43, - 43.2, - 43.3, - 42.9, - 41.9, - 40.8, - 39.8, - 39, - 38.4, - 38.1, - 37.9, - 37.7, - 37.5, - 37, - 36.5, - 36.1, - 35.6, - 35.3, - 34.7, - 34.6, - 34.7, - 34.6, - 34.7, - 35, - 34.9, - 34.6, - 34.1, - 33.9, - 33.9, - 33.7, - 33.5, - 33.4, - 33.3, - 33.2, - 33.2, - 33.1, - 32.9, - 32.8, - 32.6, - 32.4, - 32.2, - 32.1, - 31.8, - 31.6, - 31.4, - 31.2, - 31.1, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.9, - 29.7, - 29.5, - 29.5, - 29.4, - 29.2, - 29.2, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.1, - 29, - 28.7, - 28.4, - 27.7, - 27.4, - 27.1, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 27, - 27, - 27, - 26.9, - 26.9, - 26.8, - 26.4, - 25.9, - 25.8, - 25.8, - 25.7, - 25.7, - 25.6, - 25.2, - 25.1, - 25.1, - 25.1, - 25.1, - 25, - 25, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.7, - 24.8, - 25, - 24.9, - 24.9, - 24.9, - 25, - 24.9, - 24.9, - 24.9, - 25, - 25, - 25.1, - 23.2, - 23, - 22.9, - 22.9, - 22.8, - 22.5, - 22.3, - 22, - 21.6, - 21.7, - 22.1, - 22.8, - 23.3, - 23, - 22.5, - 22.4, - 22.5, - 22.4, - 22.2, - 22.2, - 22.5, - 22.8, - 23.1, - 23.2, - 23.2, - 23.3, - 23.5, - 23.4, - 23.5, - 23.6, - 23.7, - 23.8, - 24, - 24.2, - 24.3, - 24.5, - 25, - 25.5, - 26.1, - 26.1, - 26.1, - 26.9, - 27.6, - 28, - 28.3, - 28.3, - 28.3, - 28.1, - 27.6, - 27.2, - 26.7, - 26.7, - 26.9, - 27, - 26.6, - 26.7, - 27, - 27.4, - 27.6, - 27.4, - 27.3, - 27.3, - 27.5, - 27.6, - 27.6, - 27.7, - 27.8, - 27.9, - 28.1, - 28.3, - 28.4, - 28.5, - 29, - 29.6, - 29.8, - 29.8, - 30.4, - 30.9, - 31.9, - 32.6, - 33.4, - 34.1, - 34.7, - 35.1, - 35.3, - 35.5, - 35.8, - 36.1, - 36.6, - 37.5, - 38.7, - 40.4, - 41.9, - 42.2, - 42.9, - 43.6, - 44.4, - 45.1, - 45.5, - 45.7, - 45.8, - 46.4, - 46.9, - 47.4, - 47.9, - 48.4, - 48.7, - 48.8, - 48.8, - 48.8, - 48.8, - 48.7, - 48.5, - 48.1, - 47.6, - 47.2, - 46.7, - 46.3, - 45.7, - 45.1, - 44.4, - 43.6, - 42.9, - 42.2, - 42.1, - 42.1, - 42.3, - 42.7, - 43, - 43.4, - 43.6, - 43.7, - 43.6, - 43, - 41.9, - 40.7, - 39.7, - 38.8, - 38.3, - 37.9, - 37.7, - 37.5, - 37, - 36.5, - 36.1, - 35.7, - 35.3, - 35.1, - 34.8, - 35, - 35.1, - 35, - 35.1, - 35.2, - 35, - 34.6, - 34.2, - 34, - 33.8, - 33.7, - 33.6, - 33.5, - 33.2, - 33, - 32.9, - 32.9, - 32.8, - 32.7, - 32.5, - 32.3, - 32.2, - 32, - 31.8, - 31.7, - 31.5, - 31.3, - 31.1, - 31, - 30.8, - 30.7, - 30.6, - 30.5, - 30.3, - 30.3, - 30.1, - 30, - 29.9, - 29.8, - 29.6, - 29.5, - 29.5, - 29.4, - 29.2, - 29.2, - 29.2, - 29.3, - 29.4, - 29.4, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29.1, - 28.9, - 28.6, - 28.3, - 27.9, - 27.5, - 27.2, - 27.1, - 27, - 27, - 27, - 27, - 27, - 27, - 27, - 27.1, - 27.1, - 27.1, - 27, - 26.9, - 26.8, - 26.4, - 26.1, - 26, - 25.9, - 25.8, - 25.8, - 25.7, - 25.5, - 25.2, - 25, - 25, - 25, - 25, - 25, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.7, - 24.8, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 25, - 25, - 25, - 22.9, - 22.8, - 22.8, - 22.9, - 22.8, - 22.6, - 22.3, - 22, - 21.6, - 21.5, - 21.8, - 22.1, - 22.4, - 22.2, - 21.9, - 22, - 22, - 22, - 21.9, - 22, - 22.1, - 22.3, - 22.6, - 22.5, - 22.6, - 23.2, - 23.3, - 23.4, - 23.5, - 23.6, - 23.7, - 23.9, - 24, - 24.2, - 24.5, - 25.1, - 25.8, - 26.6, - 27, - 26.9, - 26.7, - 27, - 27.5, - 28, - 28.2, - 28.4, - 28.4, - 28.2, - 27.8, - 27.4, - 26.9, - 27, - 27.3, - 27.3, - 26.7, - 26.7, - 27, - 27.5, - 27.8, - 27.9, - 27.7, - 27.6, - 27.8, - 28.1, - 28.3, - 28.1, - 28.1, - 28.2, - 28.7, - 28.9, - 29.1, - 29, - 29.4, - 30.3, - 30.8, - 30.7, - 30.8, - 31, - 31.4, - 32, - 32.6, - 33.4, - 34.2, - 34.9, - 35.1, - 35.3, - 35.6, - 36, - 36.5, - 37.3, - 38.4, - 40.1, - 41.7, - 42.1, - 42.7, - 43.5, - 44.2, - 44.7, - 44.9, - 45, - 45.3, - 45.9, - 46.4, - 47.1, - 47.7, - 48.2, - 48.6, - 48.8, - 48.9, - 49, - 49.1, - 49, - 48.9, - 48.5, - 48.2, - 47.7, - 47.1, - 46.6, - 46, - 45.3, - 44.5, - 43.6, - 42.8, - 42.5, - 42.7, - 42.7, - 42.9, - 43.4, - 43.7, - 44, - 44.1, - 44.1, - 43.8, - 43, - 41.8, - 40.6, - 39.6, - 38.7, - 38.2, - 37.8, - 37.5, - 37.2, - 36.7, - 36.3, - 36, - 35.9, - 35.5, - 35.1, - 35, - 35.2, - 35.4, - 35.3, - 35.4, - 35.3, - 34.9, - 34.5, - 34.3, - 34.1, - 33.9, - 34, - 34.3, - 34.1, - 33.6, - 33.1, - 32.8, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.7, - 31.5, - 31.4, - 31.2, - 31.1, - 31, - 30.8, - 30.7, - 30.5, - 30.4, - 30.2, - 30.1, - 30, - 29.9, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29.2, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.7, - 28.4, - 28, - 27.5, - 27.3, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 26.8, - 26.5, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.8, - 25.7, - 25.5, - 25, - 24.9, - 24.9, - 25, - 24.9, - 24.9, - 24.9, - 24.9, - 24.8, - 24.8, - 24.7, - 24.7, - 24.9, - 24.8, - 24.8, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 25, - 25, - 22.8, - 22.7, - 22.8, - 22.8, - 22.8, - 22.6, - 22.4, - 22.1, - 21.6, - 21.4, - 21.5, - 21.7, - 21.8, - 21.7, - 21.6, - 21.7, - 21.7, - 21.9, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 21.8, - 21.9, - 22.9, - 23.1, - 23.4, - 23.5, - 23.6, - 23.8, - 23.9, - 24.1, - 24.5, - 25.3, - 26.2, - 26.8, - 27.2, - 27.4, - 27.4, - 27.3, - 27.2, - 27.6, - 28, - 28.2, - 28.4, - 28.5, - 28.3, - 28.1, - 27.8, - 27.4, - 27.4, - 27.6, - 27.6, - 27.1, - 26.9, - 27, - 27.4, - 27.8, - 28.1, - 28.1, - 28, - 28.2, - 28.5, - 28.9, - 28.8, - 28.7, - 28.7, - 29.1, - 29.5, - 29.8, - 29.8, - 29.8, - 30.6, - 31.4, - 31.5, - 31.6, - 31.9, - 32.2, - 32.4, - 32.7, - 33.4, - 34, - 34.8, - 35, - 35.2, - 35.5, - 35.9, - 36.4, - 37.1, - 38.1, - 39.2, - 41.1, - 41.8, - 42.4, - 42.9, - 43.4, - 43.7, - 44.1, - 44.9, - 45.7, - 46.4, - 46.6, - 46.9, - 47.3, - 47.8, - 48.2, - 48.7, - 48.9, - 49.1, - 49.2, - 49.3, - 49.2, - 48.9, - 48.6, - 48.1, - 47.4, - 46.7, - 45.9, - 45, - 44.3, - 43.6, - 43.1, - 43, - 43.4, - 43.5, - 43.6, - 44, - 44.4, - 44.7, - 44.6, - 44.3, - 43.8, - 43.1, - 42.1, - 40.9, - 40, - 39, - 38.2, - 37.9, - 37.6, - 37.2, - 36.9, - 36.7, - 36.3, - 36.1, - 35.8, - 35.5, - 35.2, - 35.5, - 35.6, - 35.5, - 35.5, - 35.2, - 34.9, - 34.6, - 34.5, - 34.5, - 34.2, - 34.4, - 34.9, - 34.7, - 34.1, - 33.4, - 33.2, - 32.9, - 32.6, - 32.4, - 32.2, - 31.9, - 31.8, - 31.9, - 31.8, - 31.7, - 31.5, - 31.4, - 31.3, - 31.1, - 30.9, - 30.8, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.6, - 29.5, - 29.5, - 29.4, - 29.2, - 29.1, - 29.1, - 29.1, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 28.7, - 28.4, - 28, - 27.6, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 26.8, - 26.5, - 26.3, - 26.1, - 26, - 25.9, - 25.8, - 25.8, - 25.7, - 25.2, - 25, - 24.9, - 24.8, - 24.9, - 24.9, - 24.8, - 24.8, - 24.8, - 24.8, - 24.7, - 24.7, - 24.8, - 24.8, - 24.8, - 24.8, - 24.9, - 24.9, - 24.8, - 24.9, - 24.9, - 25, - 25, - 22.7, - 22.7, - 22.7, - 22.8, - 22.7, - 22.6, - 22.4, - 22.1, - 21.7, - 21.3, - 21.4, - 21.4, - 21.5, - 21.5, - 21.4, - 21.5, - 21.5, - 21.7, - 21.5, - 21.5, - 21.6, - 21.7, - 21.6, - 21.4, - 21.4, - 22.6, - 23.2, - 23.5, - 23.6, - 23.7, - 23.8, - 23.9, - 24.2, - 25, - 26.2, - 26.9, - 27.2, - 27.4, - 27.6, - 27.6, - 27.6, - 27.6, - 27.7, - 28, - 28.3, - 28.4, - 28.5, - 28.5, - 28.4, - 28.2, - 27.8, - 27.8, - 27.9, - 27.9, - 27.7, - 27.3, - 27, - 27.3, - 27.7, - 28.1, - 28.4, - 28.3, - 28.5, - 28.8, - 29.2, - 29.3, - 29.3, - 29.2, - 29.5, - 30, - 30.4, - 30.5, - 30.3, - 30.8, - 31.8, - 32.1, - 32.3, - 32.7, - 33.1, - 33.3, - 33.6, - 34.1, - 34.6, - 34.9, - 35.1, - 35.2, - 35.4, - 35.8, - 36.2, - 36.6, - 37, - 37.8, - 39.4, - 41.3, - 42, - 42.4, - 42.8, - 43.2, - 44.1, - 45.3, - 46.2, - 46.9, - 47.3, - 47.3, - 47.3, - 47.6, - 48.2, - 48.8, - 49.1, - 49.3, - 49.4, - 49.5, - 49.4, - 49.2, - 48.9, - 48.3, - 47.5, - 46.6, - 45.6, - 44.7, - 44.3, - 44, - 43.6, - 43.5, - 44, - 44.2, - 44.4, - 44.7, - 45.1, - 45.3, - 45.1, - 44.6, - 43.9, - 43.4, - 42.6, - 41.4, - 40.4, - 39.4, - 38.6, - 38.1, - 37.7, - 37.5, - 37.3, - 37, - 36.7, - 36.3, - 36, - 35.6, - 35.5, - 35.7, - 35.8, - 35.7, - 35.5, - 35.2, - 35, - 34.8, - 34.7, - 34.5, - 34.3, - 34.7, - 35.2, - 35, - 34.4, - 33.8, - 33.6, - 33.4, - 33, - 32.7, - 32.4, - 32, - 31.8, - 31.6, - 31.5, - 31.6, - 31.5, - 31.3, - 31.2, - 31, - 30.9, - 30.8, - 30.6, - 30.5, - 30.3, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 29.1, - 29.3, - 29.3, - 29.3, - 29.4, - 29.3, - 29.3, - 29.4, - 29.4, - 29.2, - 29.2, - 29.2, - 29.2, - 29, - 28.6, - 28.4, - 27.9, - 27.5, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 26.7, - 26.6, - 26.3, - 26.1, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 25, - 24.9, - 24.8, - 24.8, - 24.8, - 24.8, - 24.7, - 24.7, - 24.8, - 24.8, - 24.6, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.9, - 24.9, - 24.9, - 25, - 22.7, - 22.7, - 22.7, - 22.7, - 22.7, - 22.6, - 22.3, - 22, - 21.6, - 21.2, - 21.2, - 21.2, - 21.2, - 21.2, - 21.2, - 21.3, - 21.3, - 21.4, - 21.3, - 21.3, - 21.4, - 21.5, - 21.3, - 21.3, - 21.7, - 22.8, - 23.3, - 23.6, - 23.7, - 24, - 24.2, - 24.2, - 24.3, - 25.3, - 26.5, - 27, - 27.3, - 27.5, - 27.7, - 27.8, - 27.8, - 27.8, - 27.9, - 28, - 28.2, - 28.4, - 28.5, - 28.5, - 28.4, - 28.3, - 28, - 28, - 28.1, - 28.2, - 28.1, - 27.8, - 27.3, - 27.5, - 27.9, - 28.3, - 28.5, - 28.6, - 28.8, - 29.2, - 29.5, - 29.7, - 29.8, - 29.8, - 30, - 30.5, - 31, - 31.2, - 31, - 31.2, - 31.9, - 32.4, - 32.9, - 33.3, - 33.7, - 34.1, - 34.4, - 34.9, - 35, - 35.1, - 35.2, - 35.3, - 35.4, - 35.6, - 35.9, - 36.1, - 36.5, - 37.4, - 38.6, - 41.1, - 42.1, - 42.7, - 43.3, - 43.9, - 44.4, - 45.2, - 46.1, - 47, - 47.5, - 47.7, - 47.6, - 47.9, - 48.4, - 49, - 49.3, - 49.5, - 49.6, - 49.7, - 49.5, - 49.4, - 49.1, - 48.3, - 47.4, - 46.4, - 45.6, - 45.2, - 45, - 44.6, - 44.1, - 44.4, - 44.8, - 45, - 45.1, - 45.4, - 45.7, - 45.7, - 45.4, - 44.9, - 44.3, - 43.7, - 43.1, - 41.8, - 40.7, - 39.8, - 38.9, - 38.4, - 38.2, - 37.9, - 37.6, - 37.3, - 36.8, - 36.4, - 36.1, - 35.8, - 35.8, - 35.9, - 35.8, - 35.6, - 35.4, - 35.3, - 35.2, - 35, - 34.7, - 34.6, - 34.6, - 34.9, - 35.2, - 35, - 34.5, - 34.2, - 34.1, - 33.8, - 33.4, - 32.9, - 32.6, - 32.3, - 32, - 31.7, - 31.4, - 31.2, - 31.4, - 31.3, - 31.1, - 31, - 30.9, - 30.8, - 30.6, - 30.5, - 30.3, - 30.1, - 29.9, - 29.8, - 29.7, - 29.6, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 29.1, - 29.1, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29, - 29.1, - 29.2, - 29.2, - 29.1, - 28.8, - 28.5, - 28.1, - 27.7, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 26.8, - 26.6, - 26.3, - 26.1, - 25.9, - 25.9, - 25.8, - 25.6, - 25.2, - 24.9, - 24.8, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.8, - 24.8, - 24.6, - 24.8, - 24.8, - 24.7, - 24.8, - 24.8, - 24.8, - 24.8, - 24.9, - 24.9, - 24.9, - 24.9, - 22.6, - 22.6, - 22.7, - 22.7, - 22.7, - 22.6, - 22.3, - 22, - 21.6, - 21.1, - 21, - 21, - 21, - 21.1, - 21.1, - 21.1, - 21.1, - 21, - 20.9, - 21, - 21.1, - 21.1, - 21.4, - 21.8, - 22.3, - 22.8, - 23.3, - 23.6, - 24, - 24.6, - 24.9, - 24.8, - 24.8, - 25.6, - 26.5, - 27, - 27.2, - 27.5, - 27.6, - 27.8, - 27.9, - 28, - 28, - 28.1, - 28.2, - 28.3, - 28.4, - 28.4, - 28.3, - 28, - 27.9, - 27.9, - 28.1, - 28.3, - 28.4, - 28.2, - 27.8, - 27.8, - 28.2, - 28.5, - 28.8, - 29, - 29.2, - 29.6, - 29.8, - 30, - 30.2, - 30.3, - 30.4, - 30.8, - 31.4, - 31.9, - 31.8, - 31.7, - 32.2, - 32.7, - 33.2, - 33.7, - 34.4, - 34.8, - 35, - 35.2, - 35.3, - 35.4, - 35.5, - 35.6, - 35.8, - 35.9, - 36, - 36.2, - 36.8, - 37.7, - 38.9, - 41.6, - 42.3, - 43.2, - 44, - 44.8, - 45.4, - 45.8, - 46.1, - 46.9, - 47.6, - 48.1, - 48.1, - 48.3, - 48.8, - 49.2, - 49.5, - 49.8, - 49.8, - 49.8, - 49.7, - 49.5, - 49.1, - 48.3, - 47.3, - 46.4, - 46.2, - 46.1, - 45.6, - 45, - 44.6, - 45.1, - 45.7, - 45.8, - 45.9, - 46.1, - 46.2, - 46.1, - 45.8, - 45.4, - 44.8, - 44.2, - 43.5, - 42.2, - 41.1, - 40.1, - 39.6, - 39.4, - 39, - 38.3, - 37.8, - 37.4, - 36.9, - 36.5, - 36.2, - 36, - 36, - 36, - 35.8, - 35.6, - 35.5, - 35.4, - 35.2, - 35.1, - 34.9, - 34.8, - 35.1, - 35.2, - 35.2, - 35.1, - 34.8, - 34.6, - 34.5, - 34.1, - 33.6, - 33.1, - 32.8, - 32.5, - 32.1, - 31.8, - 31.5, - 31.2, - 31.1, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.4, - 30.3, - 30.1, - 30, - 29.8, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.8, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.7, - 28.9, - 29, - 29.1, - 29, - 28.8, - 28.6, - 28.1, - 27.7, - 27.4, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.1, - 27, - 26.8, - 26.5, - 26.2, - 26, - 25.9, - 25.9, - 25.7, - 25.2, - 24.9, - 24.8, - 24.7, - 24.7, - 24.6, - 24.7, - 24.7, - 24.8, - 24.8, - 24.6, - 24.7, - 24.7, - 24.7, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.9, - 24.9, - 24.9, - 22.6, - 22.6, - 22.6, - 22.7, - 22.7, - 22.6, - 22.3, - 22, - 21.6, - 21.2, - 20.9, - 21, - 20.9, - 20.9, - 21, - 21, - 20.9, - 20.8, - 20.9, - 21.1, - 21.2, - 21.6, - 22, - 22.4, - 22.7, - 23, - 23.4, - 24, - 24.8, - 25.7, - 25.9, - 25.9, - 25.7, - 26, - 26.5, - 27, - 27.2, - 27.4, - 27.6, - 27.8, - 27.9, - 28, - 28.1, - 28.2, - 28.2, - 28.2, - 28.2, - 28, - 27.8, - 27.7, - 27.5, - 27.6, - 27.9, - 28.1, - 28.5, - 28.5, - 28.2, - 28.1, - 28.4, - 28.7, - 29, - 29.3, - 29.6, - 29.9, - 30.1, - 30.3, - 30.6, - 30.8, - 30.9, - 31.3, - 31.9, - 32.4, - 32.4, - 32.2, - 32.5, - 32.9, - 33.3, - 33.8, - 34.7, - 35, - 35.2, - 35.4, - 35.6, - 35.8, - 36, - 36.2, - 36.5, - 36.9, - 36.8, - 36.6, - 36.9, - 37.5, - 38.9, - 41.8, - 42.4, - 43.3, - 44.4, - 45.2, - 45.9, - 46.5, - 46.7, - 47.1, - 47.6, - 48.3, - 48.6, - 48.7, - 49.1, - 49.4, - 49.7, - 49.9, - 50, - 50, - 49.8, - 49.5, - 49.1, - 48.3, - 47.3, - 47, - 46.9, - 46.7, - 46.2, - 45.5, - 45.1, - 45.8, - 46.4, - 46.6, - 46.6, - 46.7, - 46.6, - 46.5, - 46.2, - 45.9, - 45.3, - 44.5, - 43.7, - 42.5, - 41.5, - 40.8, - 40.8, - 40.5, - 39.7, - 38.7, - 37.9, - 37.4, - 37, - 36.7, - 36.3, - 36.2, - 36.2, - 36, - 35.8, - 35.7, - 35.7, - 35.5, - 35.4, - 35.3, - 35.3, - 35.3, - 35.4, - 35.4, - 35.3, - 35.2, - 35, - 34.8, - 34.8, - 34.3, - 33.7, - 33.3, - 32.9, - 32.5, - 32, - 31.7, - 31.4, - 31.3, - 31, - 30.9, - 30.9, - 30.8, - 30.7, - 30.7, - 30.5, - 30.4, - 30.3, - 30.1, - 29.8, - 29.6, - 29.5, - 29.4, - 29.3, - 29.3, - 29.2, - 29.1, - 29, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.5, - 28.6, - 28.6, - 28.7, - 28.6, - 28.4, - 27.9, - 27.5, - 27.3, - 27.2, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.2, - 27.2, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.1, - 26.9, - 26.6, - 26.3, - 26.1, - 26, - 25.9, - 25.7, - 25.3, - 24.9, - 24.8, - 24.7, - 24.6, - 24.6, - 24.6, - 24.7, - 24.7, - 24.8, - 24.6, - 24.6, - 24.7, - 24.7, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.9, - 24.9, - 24.9, - 22.6, - 22.6, - 22.6, - 22.7, - 22.7, - 22.5, - 22.2, - 21.9, - 21.4, - 21.1, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.6, - 20.8, - 21.2, - 21.4, - 21.6, - 21.8, - 22.3, - 22.7, - 23, - 23.2, - 24.1, - 25.2, - 25.9, - 26.4, - 26.5, - 26.5, - 26.5, - 26.6, - 26.8, - 27.1, - 27.3, - 27.4, - 27.5, - 27.6, - 27.8, - 27.9, - 28.1, - 28.1, - 28, - 27.7, - 27.4, - 27.1, - 27.2, - 27.2, - 27, - 27.1, - 27.6, - 27.9, - 28.3, - 28.6, - 28.5, - 28.4, - 28.6, - 28.8, - 29.1, - 29.5, - 29.8, - 30.1, - 30.3, - 30.6, - 31, - 31.2, - 31.5, - 31.8, - 32.4, - 32.8, - 33, - 32.9, - 32.9, - 33.1, - 33.4, - 33.8, - 34.7, - 35, - 35.3, - 35.6, - 35.8, - 36.1, - 36.4, - 37, - 37.6, - 38.1, - 38.1, - 37.9, - 37.6, - 37.8, - 38.8, - 41.4, - 42.3, - 43.2, - 44.2, - 45.3, - 46, - 46.7, - 47.1, - 47.6, - 48, - 48.4, - 48.9, - 49.1, - 49.4, - 49.7, - 50, - 50.3, - 50.3, - 50.2, - 49.9, - 49.5, - 49, - 48.3, - 47.9, - 47.8, - 47.5, - 47.1, - 46.4, - 45.8, - 45.9, - 46.5, - 47, - 47.2, - 47.2, - 47.1, - 47, - 46.8, - 46.5, - 46.2, - 45.5, - 44.7, - 44, - 43, - 42.2, - 41.9, - 42, - 41.6, - 40.4, - 39.2, - 38.2, - 37.5, - 37.1, - 36.8, - 36.5, - 36.4, - 36.3, - 36, - 35.9, - 35.9, - 35.8, - 35.8, - 35.7, - 35.5, - 35.6, - 35.6, - 35.6, - 35.5, - 35.4, - 35.3, - 35, - 34.7, - 34.5, - 34.2, - 33.8, - 33.1, - 32.6, - 32.1, - 31.6, - 31.4, - 31.2, - 31.2, - 31.1, - 30.8, - 30.7, - 30.6, - 30.6, - 30.5, - 30.4, - 30.1, - 30, - 29.8, - 29.7, - 29.6, - 29.5, - 29.5, - 29.3, - 29.2, - 29.1, - 29.1, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 27.9, - 27.9, - 27.9, - 27.9, - 27.9, - 28, - 28, - 27.9, - 27.6, - 27.3, - 27.2, - 27.1, - 27, - 27, - 27, - 27, - 27, - 27.1, - 27.1, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 26.9, - 26.6, - 26.3, - 26.1, - 26, - 25.9, - 25.7, - 25.4, - 25, - 24.7, - 24.7, - 24.6, - 24.6, - 24.6, - 24.7, - 24.7, - 24.7, - 24.6, - 24.6, - 24.6, - 24.7, - 24.8, - 24.8, - 24.7, - 24.8, - 24.8, - 24.8, - 24.8, - 24.9, - 22.5, - 22.6, - 22.6, - 22.7, - 22.7, - 22.6, - 22.2, - 21.8, - 21.3, - 21, - 20.7, - 20.6, - 20.6, - 20.7, - 20.7, - 20.6, - 20.7, - 21.1, - 21.4, - 21.5, - 21.7, - 21.9, - 22.2, - 22.8, - 23.3, - 23.9, - 25.3, - 26.1, - 26.4, - 26.6, - 26.8, - 26.8, - 26.9, - 27, - 27.1, - 27.2, - 27.4, - 27.4, - 27.4, - 27.4, - 27.5, - 27.8, - 28, - 28, - 27.6, - 27, - 26.5, - 26.4, - 26.5, - 26.5, - 26.5, - 26.7, - 27.3, - 27.8, - 28.1, - 28.5, - 28.7, - 28.7, - 28.7, - 28.9, - 29.2, - 29.5, - 29.9, - 30.2, - 30.4, - 30.7, - 31.1, - 31.4, - 31.8, - 32.3, - 32.7, - 33, - 33.2, - 33.3, - 33.3, - 33.5, - 33.7, - 34.1, - 34.8, - 35, - 35.3, - 35.7, - 36, - 36.4, - 36.9, - 37.7, - 38.8, - 39.6, - 39.8, - 39.4, - 39.1, - 39.1, - 39.5, - 41.6, - 42.4, - 43.3, - 44.5, - 45.4, - 46.1, - 46.8, - 47.3, - 47.9, - 48.4, - 48.8, - 49.1, - 49.4, - 49.6, - 50, - 50.4, - 50.7, - 50.9, - 50.6, - 50.1, - 49.5, - 48.9, - 48.7, - 48.6, - 48.4, - 48, - 47.2, - 46.6, - 46.4, - 46.7, - 47.2, - 47.6, - 47.7, - 47.7, - 47.5, - 47.3, - 47, - 46.7, - 46.2, - 45.4, - 44.6, - 44, - 43.6, - 43.2, - 43.1, - 43, - 42.3, - 40.8, - 39.5, - 38.4, - 37.7, - 37.3, - 37, - 36.7, - 36.6, - 36.4, - 36.1, - 36.1, - 36.2, - 36.2, - 36.1, - 36.1, - 35.9, - 35.8, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 34.9, - 34.5, - 34.1, - 33.6, - 33.2, - 32.7, - 32.1, - 31.7, - 31.4, - 31.3, - 31.2, - 31, - 30.9, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.1, - 30, - 30, - 29.9, - 29.9, - 29.8, - 29.6, - 29.5, - 29.5, - 29.4, - 29.2, - 29.2, - 29.1, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.3, - 28.2, - 27.9, - 27.5, - 27.5, - 27.4, - 27.4, - 27.4, - 27.4, - 27.4, - 27.3, - 27.2, - 27.1, - 27.1, - 27, - 26.9, - 26.9, - 26.8, - 26.9, - 26.9, - 27, - 27, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27, - 26.8, - 26.5, - 26.2, - 26.1, - 26, - 25.9, - 25.7, - 25.5, - 25, - 24.7, - 24.6, - 24.6, - 24.6, - 24.5, - 24.7, - 24.7, - 24.7, - 24.6, - 24.5, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.8, - 24.8, - 24.8, - 24.9, - 22.5, - 22.6, - 22.7, - 22.8, - 22.8, - 22.6, - 22.1, - 21.7, - 21.3, - 21, - 20.6, - 20.4, - 20.5, - 20.5, - 20.5, - 20.6, - 21, - 21.3, - 21.4, - 21.5, - 21.7, - 21.9, - 22.2, - 22.8, - 23.6, - 25.3, - 26.1, - 26.4, - 26.6, - 26.8, - 26.9, - 27.1, - 27.1, - 27.2, - 27.3, - 27.4, - 27.4, - 27.3, - 27.1, - 26.8, - 26.8, - 27.3, - 27.5, - 27.5, - 27.2, - 26.5, - 25.8, - 25.8, - 25.9, - 26, - 26.3, - 26.8, - 27.2, - 27.7, - 28.1, - 28.4, - 28.7, - 28.7, - 28.7, - 28.8, - 29.1, - 29.4, - 29.8, - 30.1, - 30.4, - 30.6, - 30.8, - 31.2, - 31.7, - 32.3, - 32.8, - 33, - 33.3, - 33.5, - 33.7, - 33.9, - 34.2, - 34.7, - 34.9, - 35.1, - 35.5, - 35.8, - 36.1, - 36.6, - 37.2, - 38.3, - 39.5, - 40.5, - 40.9, - 40.8, - 40.8, - 41.1, - 41.1, - 41.9, - 42.4, - 43.2, - 44.5, - 45.4, - 46.3, - 46.8, - 47.4, - 48.1, - 48.6, - 48.9, - 49.3, - 49.6, - 49.8, - 50.2, - 50.7, - 51.4, - 51.7, - 51, - 50.3, - 49.8, - 49.3, - 49.3, - 49.2, - 48.8, - 48.2, - 47.3, - 46.9, - 47.1, - 47.5, - 47.9, - 48.3, - 48.4, - 48.2, - 47.9, - 47.5, - 47.1, - 46.6, - 45.9, - 45.2, - 44.7, - 44.4, - 44.3, - 44.3, - 44, - 43.6, - 42.5, - 41, - 39.7, - 38.4, - 37.8, - 37.4, - 37.1, - 36.9, - 36.7, - 36.4, - 36.2, - 36.5, - 36.6, - 36.6, - 36.6, - 36.6, - 36.4, - 36.2, - 36, - 35.8, - 35.7, - 35.6, - 35.4, - 35, - 34.5, - 33.6, - 32.7, - 32.6, - 32.2, - 31.8, - 31.6, - 31.5, - 31.5, - 31.4, - 31.2, - 31, - 30.9, - 30.8, - 30.6, - 30.4, - 30.2, - 30.2, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.3, - 28.2, - 28, - 27.7, - 27.5, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.8, - 26.7, - 26.8, - 26.8, - 26.8, - 26.9, - 26.9, - 26.9, - 27, - 27, - 27, - 26.9, - 26.8, - 26.5, - 26.2, - 26, - 25.9, - 25.9, - 25.8, - 25.5, - 25, - 24.7, - 24.6, - 24.5, - 24.6, - 24.5, - 24.6, - 24.6, - 24.7, - 24.6, - 24.5, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.8, - 24.8, - 24.9, - 22.5, - 22.6, - 22.7, - 22.8, - 22.8, - 22.6, - 22.1, - 21.6, - 21.2, - 20.7, - 20.3, - 20.3, - 20.4, - 20.4, - 20.6, - 20.9, - 21, - 21.2, - 21.4, - 21.5, - 21.7, - 21.9, - 22.3, - 23, - 24.6, - 26, - 26.4, - 26.5, - 26.7, - 26.9, - 27, - 27.1, - 27.2, - 27.3, - 27.3, - 27.3, - 27.1, - 26.9, - 26.5, - 26, - 26, - 26.5, - 26.6, - 26.7, - 26.4, - 25.9, - 25.4, - 25.5, - 25.8, - 26.2, - 26.8, - 27.4, - 27.7, - 27.9, - 28.1, - 28.4, - 28.6, - 28.5, - 28.4, - 28.5, - 28.7, - 29, - 29.5, - 29.9, - 30.1, - 30.2, - 30.4, - 30.9, - 31.6, - 32.2, - 32.7, - 33.1, - 33.5, - 33.9, - 34.3, - 34.5, - 34.7, - 34.9, - 35.1, - 35.3, - 35.6, - 35.9, - 36.1, - 36.6, - 37.4, - 38.4, - 39.6, - 40.6, - 41.1, - 41.3, - 41.5, - 41.9, - 42.3, - 42.5, - 42.9, - 43.2, - 44.1, - 45.1, - 46.1, - 46.6, - 47.2, - 48, - 48.5, - 48.9, - 49.4, - 50, - 50.3, - 50.4, - 51, - 51.9, - 52.2, - 51.5, - 50.6, - 50.2, - 49.9, - 49.9, - 49.6, - 49.1, - 48.3, - 47.6, - 47.7, - 47.9, - 48.4, - 48.6, - 49, - 48.9, - 48.6, - 48.1, - 47.5, - 46.9, - 46.3, - 45.8, - 45.5, - 45.3, - 45.2, - 45, - 44.8, - 44.4, - 43.7, - 42.4, - 41, - 39.7, - 38.6, - 37.9, - 37.5, - 37.2, - 37, - 36.7, - 36.5, - 36.4, - 36.9, - 37.1, - 37.2, - 37.3, - 37.2, - 36.9, - 36.5, - 36.2, - 36, - 35.9, - 35.7, - 35.5, - 35.1, - 34.5, - 33.6, - 32.7, - 32.2, - 32, - 31.8, - 31.7, - 31.7, - 31.7, - 31.5, - 31.3, - 31.1, - 31, - 30.9, - 30.7, - 30.4, - 30.5, - 30.4, - 30.4, - 30.3, - 30.1, - 30, - 29.9, - 29.9, - 29.8, - 29.7, - 29.5, - 29.5, - 29.5, - 29.3, - 29.2, - 29, - 28.9, - 28.8, - 28.8, - 28.8, - 28.7, - 28.6, - 28.4, - 28.2, - 28, - 27.9, - 27.6, - 27.5, - 27.4, - 27.3, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.8, - 26.8, - 26.7, - 26.6, - 26.6, - 26.7, - 26.7, - 26.7, - 26.8, - 26.7, - 26.8, - 26.9, - 26.9, - 26.9, - 26.7, - 26.4, - 26.1, - 26, - 25.9, - 25.8, - 25.8, - 25.5, - 25, - 24.7, - 24.6, - 24.5, - 24.5, - 24.5, - 24.5, - 24.6, - 24.6, - 24.5, - 24.5, - 24.6, - 24.6, - 24.6, - 24.7, - 24.7, - 24.7, - 24.7, - 24.8, - 24.8, - 24.8, - 22.5, - 22.7, - 22.7, - 22.8, - 22.8, - 22.5, - 22.1, - 21.6, - 21, - 20.3, - 20.2, - 20.3, - 20.4, - 20.6, - 20.8, - 20.9, - 21, - 21.2, - 21.3, - 21.5, - 21.8, - 22.1, - 22.5, - 23.6, - 25.5, - 26.2, - 26.4, - 26.5, - 26.6, - 26.7, - 26.8, - 27, - 27.1, - 27, - 26.8, - 26.6, - 26.3, - 25.9, - 25.5, - 25.1, - 25.3, - 25.6, - 25.8, - 25.9, - 25.7, - 25.4, - 25.3, - 25.7, - 26.1, - 26.5, - 27.1, - 27.5, - 27.8, - 28, - 27.9, - 28.2, - 28.3, - 28.1, - 28, - 28, - 28.1, - 28.5, - 29, - 29.4, - 29.6, - 29.7, - 30.1, - 30.6, - 31.4, - 32.1, - 32.7, - 33.1, - 33.8, - 34.4, - 34.7, - 34.8, - 34.9, - 35, - 35.1, - 35.3, - 35.6, - 35.8, - 36, - 36.4, - 37, - 37.7, - 38.7, - 40, - 40.9, - 41.4, - 41.9, - 42.4, - 43, - 43.3, - 43.7, - 43.9, - 44.1, - 44.8, - 45.9, - 46.7, - 47.5, - 48.1, - 48.6, - 49.2, - 49.8, - 50.5, - 51, - 51, - 51.3, - 52.1, - 52.4, - 52, - 51.3, - 50.7, - 50.5, - 50.4, - 49.9, - 49.2, - 48.5, - 48.3, - 48.6, - 48.8, - 49.1, - 49.3, - 49.5, - 49.2, - 48.7, - 48.1, - 47.4, - 46.9, - 46.5, - 46.3, - 46.2, - 46, - 45.7, - 45.3, - 44.9, - 44.4, - 43.5, - 42.1, - 40.8, - 39.5, - 38.5, - 37.9, - 37.5, - 37.2, - 37.1, - 37, - 36.6, - 36.7, - 37.4, - 37.7, - 37.7, - 37.8, - 37.7, - 37.4, - 36.9, - 36.5, - 36.2, - 36, - 35.7, - 35.4, - 35, - 34.3, - 33.1, - 32.6, - 32.6, - 32.5, - 32.2, - 32.1, - 31.9, - 31.8, - 31.6, - 31.4, - 31.2, - 31.1, - 30.9, - 30.6, - 30.6, - 30.7, - 30.6, - 30.6, - 30.4, - 30.2, - 30.1, - 30.2, - 30, - 29.9, - 29.8, - 29.6, - 29.7, - 29.6, - 29.4, - 29.3, - 29.1, - 29.1, - 29, - 29, - 29.1, - 29, - 28.9, - 28.8, - 28.6, - 28.3, - 28.2, - 28, - 28, - 27.9, - 27.9, - 27.8, - 27.4, - 27.2, - 27, - 26.8, - 26.7, - 26.7, - 26.7, - 26.6, - 26.5, - 26.6, - 26.6, - 26.6, - 26.6, - 26.5, - 26.6, - 26.6, - 26.7, - 26.7, - 26.5, - 26.2, - 26, - 25.9, - 25.8, - 25.8, - 25.7, - 25.5, - 25, - 24.7, - 24.5, - 24.4, - 24.4, - 24.5, - 24.5, - 24.6, - 24.6, - 24.4, - 24.5, - 24.6, - 24.6, - 24.6, - 24.6, - 24.7, - 24.7, - 24.7, - 24.8, - 24.8, - 24.8, - 22.4, - 22.6, - 22.7, - 22.8, - 22.7, - 22.5, - 22.1, - 21.5, - 20.6, - 20.1, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 21, - 21.1, - 21.4, - 21.5, - 21.8, - 22.2, - 22.9, - 24.3, - 25.6, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26, - 26.2, - 26.3, - 26, - 25.6, - 25.3, - 25.1, - 24.7, - 24.4, - 24.4, - 24.6, - 24.8, - 25, - 25.2, - 25.2, - 25.1, - 25.3, - 25.8, - 26.2, - 26.6, - 26.9, - 27.2, - 27.4, - 27.6, - 27.5, - 27.9, - 27.9, - 27.6, - 27.4, - 27.5, - 27.6, - 27.9, - 28.3, - 28.7, - 29.1, - 29.5, - 30, - 30.5, - 31.2, - 32, - 32.6, - 33.2, - 33.9, - 34.5, - 34.7, - 34.8, - 34.9, - 35, - 35.1, - 35.3, - 35.5, - 35.7, - 35.9, - 36.1, - 36.4, - 36.8, - 37.6, - 39, - 40.6, - 41.3, - 42, - 42.5, - 43.2, - 44, - 44.5, - 45, - 45, - 45, - 46.1, - 47, - 48, - 48.4, - 48.8, - 49.5, - 50.3, - 51.2, - 51.8, - 51.8, - 51.9, - 52.3, - 52.6, - 52.6, - 52.1, - 51.5, - 51.2, - 50.8, - 50.1, - 49.4, - 49.2, - 49.2, - 49.5, - 49.6, - 49.9, - 49.9, - 49.8, - 49.4, - 48.8, - 48.2, - 47.8, - 47.5, - 47.1, - 47, - 46.7, - 46.3, - 46, - 45.4, - 44.7, - 44, - 42.7, - 41.3, - 40.2, - 39.1, - 38.3, - 37.9, - 37.6, - 37.5, - 37.5, - 37.3, - 36.8, - 37.1, - 37.7, - 37.9, - 38, - 38.1, - 38, - 37.7, - 37.1, - 36.7, - 36.3, - 36.1, - 35.8, - 35.4, - 34.6, - 33.8, - 33.2, - 32.9, - 33.1, - 32.9, - 32.5, - 32.4, - 32.1, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.8, - 30.7, - 30.8, - 30.8, - 30.8, - 30.7, - 30.5, - 30.4, - 30.4, - 30.3, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.6, - 29.5, - 29.4, - 29.2, - 29.1, - 29.1, - 29.1, - 29.2, - 29.2, - 29.3, - 29.2, - 28.9, - 28.5, - 28.3, - 28.3, - 28.3, - 28.3, - 28.4, - 28.4, - 28.1, - 27.8, - 27.3, - 27, - 26.7, - 26.6, - 26.6, - 26.5, - 26.4, - 26.4, - 26.4, - 26.4, - 26.3, - 26.2, - 26.2, - 26.3, - 26.4, - 26.4, - 26.2, - 25.9, - 25.8, - 25.8, - 25.7, - 25.7, - 25.7, - 25.5, - 25.1, - 24.7, - 24.5, - 24.4, - 24.4, - 24.5, - 24.4, - 24.5, - 24.5, - 24.4, - 24.5, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.7, - 24.7, - 24.7, - 24.8, - 24.8, - 22.3, - 22.5, - 22.6, - 22.7, - 22.6, - 22.4, - 21.9, - 21, - 20.2, - 20.1, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21.1, - 21.4, - 21.6, - 21.9, - 22.4, - 23.3, - 24.7, - 25.3, - 25.3, - 25, - 25, - 24.8, - 24.7, - 24.7, - 24.9, - 24.8, - 24.7, - 24.4, - 24.2, - 24, - 24, - 24.1, - 24.2, - 24.3, - 24.5, - 24.6, - 24.8, - 24.9, - 25.1, - 25.5, - 25.8, - 26.1, - 26.2, - 26.4, - 26.6, - 26.8, - 26.9, - 27.2, - 27.6, - 27.5, - 27.1, - 27.1, - 27.3, - 27.4, - 27.7, - 27.9, - 28.1, - 28.8, - 29.4, - 29.8, - 30.4, - 31, - 31.8, - 32.5, - 33.1, - 33.8, - 34.5, - 34.6, - 34.7, - 34.7, - 34.8, - 35, - 35.1, - 35.4, - 35.5, - 35.7, - 35.9, - 36.3, - 36.8, - 37.5, - 38.9, - 40.3, - 40.9, - 41.6, - 42.2, - 42.9, - 43.9, - 44.8, - 45.6, - 45.9, - 45.9, - 46.4, - 47.1, - 47.9, - 48.6, - 49, - 49.5, - 50.3, - 51.4, - 52.2, - 52.6, - 52.5, - 52.6, - 53, - 53.1, - 52.8, - 52.5, - 51.9, - 51.1, - 50.3, - 50, - 50, - 50, - 50.2, - 50.2, - 50.3, - 50.1, - 49.8, - 49.4, - 49, - 48.8, - 48.4, - 48, - 47.6, - 47.2, - 46.7, - 46.3, - 45.7, - 44.9, - 44, - 42.8, - 41.6, - 40.5, - 39.4, - 38.7, - 38.3, - 38, - 37.8, - 38, - 38, - 37.7, - 37.3, - 37.4, - 37.8, - 38, - 38.2, - 38.3, - 38.3, - 37.8, - 37.3, - 36.8, - 36.5, - 36.1, - 35.8, - 35.4, - 34.7, - 34.3, - 33.9, - 33.5, - 33.3, - 33, - 32.6, - 32.3, - 32.1, - 31.9, - 31.7, - 31.5, - 31.3, - 31.1, - 30.9, - 30.9, - 30.9, - 30.9, - 30.9, - 30.8, - 30.6, - 30.6, - 30.5, - 30.4, - 30.3, - 30.2, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.2, - 29.2, - 29.2, - 29.3, - 29.4, - 29.4, - 29.4, - 29, - 28.6, - 28.4, - 28.4, - 28.5, - 28.5, - 28.7, - 28.8, - 28.8, - 28.4, - 27.8, - 27.2, - 26.9, - 26.7, - 26.6, - 26.4, - 26.4, - 26.3, - 26.2, - 26.1, - 26.1, - 26, - 25.9, - 25.9, - 25.9, - 25.9, - 25.8, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.6, - 25.5, - 25, - 24.6, - 24.5, - 24.4, - 24.4, - 24.4, - 24.4, - 24.5, - 24.4, - 24.4, - 24.5, - 24.5, - 24.5, - 24.6, - 24.6, - 24.6, - 24.7, - 24.7, - 24.7, - 24.7, - 24.8, - 22.2, - 22.3, - 22.4, - 22.4, - 22.3, - 21.9, - 21.2, - 20.6, - 20, - 20.1, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.9, - 21, - 21.1, - 21.3, - 21.5, - 21.9, - 22.5, - 23.3, - 24, - 24.1, - 23.8, - 23.7, - 23.6, - 23.7, - 23.6, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.8, - 23.9, - 24.2, - 24.4, - 24.6, - 24.5, - 24.6, - 24.7, - 24.9, - 25.1, - 25.4, - 25.6, - 25.7, - 25.8, - 25.9, - 26.1, - 26.2, - 26.4, - 26.9, - 27.2, - 27, - 26.9, - 27.1, - 27.4, - 27.6, - 27.8, - 27.9, - 27.9, - 28.2, - 28.6, - 29.2, - 29.9, - 30.4, - 31.4, - 32.2, - 32.7, - 33.2, - 33.9, - 34, - 34, - 34.1, - 34.5, - 34.8, - 34.9, - 35.1, - 35.2, - 35.5, - 36, - 36.4, - 37.3, - 38.3, - 40.1, - 40.7, - 40.9, - 41.3, - 42, - 42.7, - 43.5, - 44.5, - 45.5, - 46.1, - 46.5, - 46.9, - 47.3, - 48, - 48.7, - 49.2, - 49.7, - 50.3, - 51.3, - 52.2, - 52.9, - 53.3, - 53.3, - 53.6, - 53.7, - 53.4, - 53.1, - 52.4, - 51.5, - 50.9, - 50.9, - 50.8, - 50.7, - 50.7, - 50.6, - 50.5, - 50.2, - 49.9, - 49.6, - 49.3, - 49, - 48.6, - 48.1, - 47.5, - 47, - 46.4, - 45.7, - 44.9, - 44, - 42.9, - 41.6, - 40.6, - 39.8, - 39.3, - 39.2, - 39, - 38.6, - 38.3, - 38.4, - 38.6, - 38, - 37.7, - 37.7, - 37.8, - 38.1, - 38.2, - 38.3, - 38.3, - 37.9, - 37.4, - 36.9, - 36.5, - 36.2, - 35.8, - 35.6, - 35.1, - 34.9, - 34.5, - 33.9, - 33.3, - 32.9, - 32.6, - 32.3, - 32.3, - 32.1, - 31.9, - 31.7, - 31.5, - 31.2, - 31, - 31.1, - 31.1, - 31.1, - 31, - 30.9, - 30.8, - 30.8, - 30.7, - 30.6, - 30.4, - 30.3, - 30.1, - 30.1, - 30, - 29.8, - 29.7, - 29.6, - 29.5, - 29.3, - 29.2, - 29.3, - 29.3, - 29.5, - 29.5, - 29.4, - 29, - 28.6, - 28.5, - 28.6, - 28.6, - 28.7, - 28.9, - 29, - 29.1, - 28.8, - 28.2, - 27.5, - 27.1, - 26.9, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 25.9, - 25.9, - 25.8, - 25.8, - 25.7, - 25.6, - 25.6, - 25.6, - 25.6, - 25.5, - 25.5, - 25.5, - 25.5, - 25.2, - 24.8, - 24.5, - 24.4, - 24.4, - 24.3, - 24.4, - 24.3, - 24.4, - 24.3, - 24.4, - 24.4, - 24.4, - 24.5, - 24.5, - 24.5, - 24.6, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 22, - 22.1, - 22.1, - 22, - 21.4, - 20.7, - 20.3, - 20, - 19.9, - 20, - 20.2, - 20.3, - 20.5, - 20.6, - 20.7, - 20.9, - 21.1, - 21.1, - 21.2, - 21.3, - 21.5, - 22, - 22.5, - 22.8, - 22.8, - 23, - 23.1, - 23.1, - 23.2, - 23.3, - 23.3, - 23.2, - 23.2, - 23.2, - 23.3, - 23.5, - 23.8, - 24.3, - 24.8, - 25.3, - 25.5, - 24.9, - 24.7, - 24.7, - 24.9, - 25, - 25.2, - 25.3, - 25.4, - 25.6, - 25.8, - 25.9, - 26.1, - 26.4, - 26.7, - 26.7, - 26.7, - 27, - 27.4, - 27.8, - 28.2, - 28.5, - 28.6, - 28.6, - 28.6, - 28.5, - 28.6, - 29.1, - 29.8, - 30.7, - 31.6, - 32, - 32.5, - 32.9, - 32.9, - 32.9, - 33.1, - 33.7, - 34.4, - 34.7, - 34.9, - 35.2, - 35.6, - 36, - 36.6, - 37.4, - 39, - 40.8, - 41.3, - 41.5, - 41.6, - 42.3, - 43.1, - 44.1, - 45.2, - 45.8, - 46.3, - 46.7, - 47.2, - 47.8, - 48.5, - 49, - 49.4, - 50, - 50.6, - 51.5, - 52.4, - 53.3, - 54, - 54.3, - 54.4, - 54.4, - 54.1, - 53.7, - 53, - 52.3, - 51.9, - 51.8, - 51.6, - 51.3, - 51.1, - 50.9, - 50.5, - 50.3, - 50, - 49.6, - 49.2, - 48.7, - 48.2, - 47.6, - 47.1, - 46.5, - 45.9, - 45.1, - 44.3, - 43.3, - 41.8, - 41, - 40.6, - 40.5, - 40.3, - 40.3, - 40, - 39.4, - 38.8, - 38.6, - 38.6, - 38, - 37.9, - 37.9, - 37.9, - 38.1, - 38.2, - 38.3, - 38.3, - 38, - 37.6, - 37.1, - 36.6, - 36.3, - 35.9, - 35.6, - 35.4, - 34.9, - 34.3, - 33.7, - 33.2, - 32.8, - 32.7, - 32.6, - 32.4, - 32.2, - 32, - 31.7, - 31.5, - 31.3, - 31.1, - 31.3, - 31.3, - 31.2, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.4, - 30.2, - 30.2, - 30.1, - 30, - 29.8, - 29.6, - 29.5, - 29.3, - 29.2, - 29.3, - 29.3, - 29.4, - 29.5, - 29.3, - 29, - 28.6, - 28.6, - 28.7, - 28.8, - 28.9, - 29, - 29.1, - 29.1, - 28.9, - 28.4, - 27.7, - 27.2, - 26.8, - 26.7, - 26.7, - 26.6, - 26.6, - 26.5, - 26.4, - 26.3, - 26.1, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.5, - 25.5, - 25.4, - 25.3, - 25.2, - 25.1, - 24.9, - 24.6, - 24.5, - 24.4, - 24.4, - 24.3, - 24.3, - 24.3, - 24.3, - 24.3, - 24.4, - 24.4, - 24.4, - 24.3, - 24.3, - 24.2, - 24.5, - 24.6, - 24.7, - 24.7, - 24.7, - 24.7, - 21.8, - 21.6, - 21.3, - 20.8, - 20.3, - 20, - 19.8, - 19.9, - 20.1, - 20.2, - 20.2, - 20.2, - 20.3, - 20.5, - 20.7, - 20.8, - 21, - 21, - 21, - 21.1, - 21.3, - 21.4, - 21.7, - 21.9, - 22.1, - 22.5, - 22.6, - 22.7, - 22.8, - 22.9, - 23, - 23, - 22.9, - 23.1, - 23.3, - 23.6, - 24.2, - 25.1, - 25.9, - 26.5, - 26.7, - 25.8, - 25.1, - 24.9, - 24.9, - 25.1, - 25.3, - 25.4, - 25.5, - 25.6, - 25.7, - 25.9, - 26.1, - 26.4, - 26.4, - 26.5, - 26.7, - 26.9, - 27.4, - 27.8, - 28.3, - 28.7, - 29.2, - 29.5, - 29.6, - 29.4, - 29.1, - 28.8, - 29.1, - 29.9, - 30.7, - 31.5, - 31.8, - 31.9, - 31.8, - 32, - 32.4, - 33.1, - 34.1, - 34.7, - 35.1, - 35.5, - 35.9, - 36.7, - 37.5, - 38.5, - 39.9, - 41, - 41.7, - 42.2, - 42.4, - 42.6, - 43.2, - 44.5, - 45.8, - 46.3, - 46.6, - 46.9, - 47.5, - 48.2, - 49, - 49.6, - 49.8, - 50.4, - 51.1, - 52.1, - 52.9, - 53.8, - 54.6, - 55.2, - 55.3, - 55.2, - 54.9, - 54.4, - 53.8, - 53.2, - 53, - 52.7, - 52.3, - 51.8, - 51.3, - 50.9, - 50.4, - 50, - 49.7, - 49.3, - 48.8, - 48.3, - 47.7, - 47.1, - 46.7, - 45.9, - 45.3, - 44.7, - 44.3, - 43.8, - 42.6, - 42.3, - 42.2, - 42.2, - 41.8, - 41.6, - 41, - 40.2, - 39.4, - 38.9, - 38.4, - 38.3, - 38.3, - 38.2, - 38.1, - 38.1, - 38.2, - 38.3, - 38.3, - 37.9, - 37.5, - 37.1, - 36.6, - 36.3, - 35.9, - 35.6, - 35.4, - 34.6, - 33.8, - 33.3, - 33.1, - 32.9, - 32.8, - 32.6, - 32.4, - 32.2, - 31.9, - 31.7, - 31.5, - 31.4, - 31.4, - 31.5, - 31.5, - 31.4, - 31.4, - 31.3, - 31.2, - 31.1, - 30.9, - 30.8, - 30.7, - 30.5, - 30.4, - 30.5, - 30.6, - 30.5, - 30.1, - 29.8, - 29.6, - 29.4, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.2, - 28.9, - 28.8, - 28.8, - 29, - 29.2, - 29.3, - 29.1, - 29.1, - 29.1, - 28.8, - 28.4, - 27.8, - 27.1, - 26.9, - 26.9, - 26.8, - 26.8, - 26.8, - 26.8, - 26.7, - 26.6, - 26.4, - 26.2, - 26, - 25.9, - 25.8, - 25.6, - 25.5, - 25.4, - 25.1, - 24.8, - 24.8, - 24.7, - 24.7, - 24.5, - 24.5, - 24.4, - 24.4, - 24.2, - 24.2, - 24.2, - 24.3, - 24.3, - 24.3, - 24.3, - 24.2, - 24, - 23.9, - 23.9, - 24.4, - 24.6, - 24.6, - 24.5, - 24.6, - 24.6, - 21.3, - 20.9, - 20.3, - 19.9, - 19.7, - 19.7, - 19.9, - 20.2, - 20.5, - 20.7, - 20.5, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.2, - 21.4, - 21.4, - 21.5, - 21.6, - 21.8, - 22, - 22.2, - 22.4, - 22.4, - 22.5, - 22.7, - 22.8, - 23, - 23.2, - 23.7, - 24.8, - 25.6, - 26.5, - 27.2, - 27.4, - 27.3, - 26.9, - 26, - 25.4, - 25.2, - 25.3, - 25.4, - 25.7, - 26, - 26.2, - 26, - 26, - 26, - 26.1, - 26.3, - 26.5, - 26.7, - 26.9, - 27.2, - 27.6, - 28, - 28.5, - 29.2, - 29.8, - 30.3, - 30.3, - 30.1, - 29.6, - 29.1, - 29.3, - 29.8, - 30.6, - 31.1, - 31, - 31.3, - 31.9, - 32.6, - 33.3, - 34.3, - 34.8, - 35.2, - 35.7, - 36.2, - 37.1, - 38.3, - 40, - 40.4, - 41.1, - 42, - 42.6, - 43.2, - 43.5, - 43.7, - 44.5, - 45.8, - 46.5, - 47, - 47.4, - 48, - 48.6, - 49.4, - 50.2, - 50.4, - 50.9, - 51.8, - 52.7, - 53.5, - 54.3, - 55.4, - 56.3, - 56.4, - 56.2, - 55.8, - 55.3, - 54.7, - 54.2, - 53.8, - 53.3, - 52.9, - 52.2, - 51.4, - 50.9, - 50.3, - 49.9, - 49.5, - 49, - 48.5, - 48, - 47.6, - 47.1, - 46.6, - 46, - 45.6, - 45.2, - 44.9, - 44.4, - 43.9, - 43.9, - 43.8, - 43.5, - 43, - 42.5, - 41.8, - 40.6, - 39.6, - 39, - 38.6, - 38.8, - 38.8, - 38.4, - 38.3, - 38.3, - 38.2, - 38.3, - 38.3, - 37.8, - 37.4, - 36.9, - 36.5, - 36.2, - 35.8, - 35.6, - 35.1, - 34.1, - 33.5, - 33.5, - 33.3, - 33.1, - 32.8, - 32.6, - 32.5, - 32.2, - 32, - 31.8, - 31.7, - 31.5, - 31.6, - 31.7, - 31.6, - 31.5, - 31.5, - 31.5, - 31.3, - 31.2, - 31.1, - 30.9, - 30.8, - 30.6, - 30.8, - 31.1, - 31.3, - 31.3, - 30.8, - 30.1, - 29.7, - 29.6, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29, - 29, - 29.2, - 29.3, - 29.5, - 29.5, - 29.2, - 29.1, - 29.1, - 28.8, - 28.4, - 27.8, - 27.2, - 27.1, - 27.1, - 27, - 26.9, - 26.9, - 26.9, - 26.8, - 26.7, - 26.6, - 26.3, - 26.1, - 25.9, - 25.9, - 25.7, - 25.6, - 25.4, - 25.2, - 24.9, - 24.7, - 24.6, - 24.6, - 24.5, - 24.5, - 24.4, - 24.3, - 24.1, - 24.2, - 24.2, - 24.3, - 24.3, - 24.1, - 24, - 23.8, - 23.5, - 23.5, - 23.5, - 23.8, - 24.2, - 24.2, - 24, - 24.1, - 24.4, - 20.5, - 20.1, - 19.7, - 19.6, - 19.6, - 19.9, - 20.4, - 20.7, - 21, - 21, - 20.9, - 20.5, - 20.5, - 20.6, - 20.6, - 20.7, - 20.9, - 21.1, - 21.2, - 21.2, - 21.4, - 21.5, - 21.6, - 21.7, - 21.7, - 21.9, - 22.1, - 22.1, - 22.2, - 22.3, - 22.6, - 23, - 23.3, - 23.6, - 24.7, - 26.1, - 27, - 27.5, - 27.6, - 27.6, - 27.5, - 27.4, - 26.9, - 26.2, - 25.7, - 25.6, - 25.9, - 26.3, - 26.7, - 27, - 26.6, - 26.5, - 26.6, - 26.5, - 26.5, - 26.8, - 27.1, - 27.2, - 27.3, - 27.5, - 27.9, - 28.3, - 29, - 29.8, - 30.6, - 30.9, - 30.9, - 30.6, - 30, - 29.6, - 29.7, - 29.9, - 30.5, - 30.9, - 31.5, - 32.2, - 32.8, - 33.5, - 34.4, - 34.9, - 35.3, - 35.8, - 36.4, - 37.4, - 38.9, - 40.7, - 41.1, - 41.3, - 41.9, - 42.7, - 43.5, - 44.4, - 44.9, - 45.3, - 45.9, - 46.6, - 47.3, - 48, - 48.5, - 48.9, - 49.5, - 50.4, - 50.8, - 51.3, - 52.3, - 53.4, - 54.3, - 55, - 56.2, - 57.2, - 57.7, - 57.3, - 56.9, - 56.3, - 55.7, - 55.1, - 54.4, - 53.8, - 53.2, - 52.5, - 51.6, - 51, - 50.5, - 50.1, - 49.6, - 49, - 48.3, - 48, - 47.6, - 47.1, - 46.7, - 46.6, - 46.3, - 45.9, - 45.5, - 45, - 44.8, - 44.8, - 44.5, - 44.3, - 43.7, - 42.8, - 41.8, - 40.7, - 39.7, - 39.1, - 38.9, - 38.9, - 38.8, - 38.5, - 38.5, - 38.5, - 38.4, - 38.4, - 38.4, - 37.7, - 37.2, - 36.6, - 36.3, - 35.9, - 35.6, - 35.3, - 34.4, - 33.8, - 33.8, - 33.8, - 33.5, - 33.2, - 33.1, - 32.8, - 32.7, - 32.4, - 32.1, - 32, - 31.8, - 31.7, - 31.8, - 31.9, - 31.7, - 31.7, - 31.7, - 31.6, - 31.4, - 31.3, - 31.2, - 31, - 30.9, - 30.7, - 31.1, - 31.6, - 31.8, - 31.8, - 31.3, - 30.5, - 29.9, - 29.7, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29.2, - 29.2, - 29.4, - 29.4, - 29.5, - 29.5, - 29.3, - 29.2, - 29.1, - 28.8, - 28.4, - 28, - 27.8, - 27.6, - 27.5, - 27.2, - 27.1, - 27, - 26.9, - 26.9, - 26.8, - 26.7, - 26.5, - 26.2, - 26, - 25.9, - 25.7, - 25.7, - 25.5, - 25.4, - 25.3, - 24.9, - 24.6, - 24.5, - 24.4, - 24.4, - 24.4, - 24.2, - 24.1, - 24.1, - 24.2, - 24.1, - 24.1, - 23.9, - 23.6, - 22.8, - 22.4, - 22.8, - 22.6, - 23.1, - 23.4, - 23.5, - 23.4, - 23.5, - 23.9, - 19.7, - 19.5, - 19.5, - 19.6, - 20, - 20.5, - 21, - 21.3, - 21.4, - 21.3, - 21.1, - 20.8, - 20.6, - 20.7, - 20.8, - 20.8, - 21, - 21.1, - 21.3, - 21.3, - 21.4, - 21.5, - 21.6, - 21.8, - 21.9, - 21.9, - 22, - 22.2, - 22.2, - 22.4, - 22.7, - 23, - 23.4, - 23.6, - 24.9, - 26.3, - 27.2, - 27.5, - 27.6, - 27.6, - 27.6, - 27.5, - 27.2, - 26.8, - 26.3, - 26, - 26.3, - 26.6, - 26.9, - 27.2, - 27, - 26.9, - 27.1, - 27.2, - 27.2, - 27.4, - 27.5, - 27.5, - 27.5, - 27.6, - 27.9, - 28.3, - 29, - 29.8, - 30.7, - 31.3, - 31.6, - 31.5, - 31.2, - 30.8, - 31, - 31.1, - 30.9, - 30.9, - 31.6, - 32.3, - 33, - 33.7, - 34.5, - 34.9, - 35.4, - 36, - 36.7, - 37.7, - 39.1, - 41, - 41.9, - 42.1, - 42.4, - 42.7, - 43.6, - 44.9, - 45.7, - 46.2, - 46.5, - 46.9, - 47.5, - 48.2, - 48.9, - 49.3, - 49.8, - 50.6, - 51.2, - 51.9, - 52.9, - 54, - 55, - 56, - 57, - 58.1, - 58.6, - 58.4, - 58.1, - 57.6, - 56.8, - 55.9, - 55, - 54.3, - 53.5, - 52.7, - 52, - 51.4, - 50.7, - 50.1, - 49.6, - 49, - 48.6, - 48.5, - 48.1, - 47.6, - 47.5, - 47.3, - 47, - 46.4, - 46, - 45.5, - 45.4, - 45.2, - 45, - 44.6, - 43.8, - 42.7, - 41.4, - 40.4, - 39.6, - 39.2, - 39.1, - 39, - 38.8, - 38.7, - 38.7, - 38.7, - 38.6, - 38.5, - 38.3, - 37.6, - 36.9, - 36.4, - 36.1, - 35.7, - 35.3, - 34.8, - 34.3, - 34.1, - 34.1, - 33.8, - 33.6, - 33.5, - 33.2, - 33, - 32.8, - 32.5, - 32.3, - 32.1, - 32, - 31.9, - 32.1, - 32, - 31.8, - 31.9, - 31.8, - 31.7, - 31.5, - 31.4, - 31.3, - 31.2, - 31, - 30.9, - 31.2, - 31.7, - 32, - 31.9, - 31.7, - 30.9, - 30, - 29.8, - 29.6, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.3, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.4, - 29.2, - 29.1, - 28.8, - 28.5, - 28.4, - 28.3, - 28.2, - 28, - 27.6, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.3, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.6, - 25.5, - 25.3, - 24.9, - 24.6, - 24.4, - 24.3, - 24.3, - 24.1, - 24, - 24.1, - 24, - 23.8, - 23.7, - 23.4, - 21.6, - 19.1, - 18.4, - 19.1, - 18.5, - 20.3, - 20.9, - 21.5, - 21.9, - 22, - 22.9, - 19.3, - 19.4, - 19.8, - 20.1, - 20.6, - 21, - 21.4, - 21.6, - 21.7, - 21.6, - 21.4, - 21, - 20.9, - 20.9, - 20.9, - 20.9, - 21, - 21.1, - 21.3, - 21.5, - 21.6, - 21.7, - 21.8, - 22, - 22.2, - 22.3, - 22.3, - 22.5, - 22.6, - 22.7, - 22.8, - 23, - 23.2, - 23.5, - 24.4, - 25.8, - 26.6, - 26.8, - 26.8, - 27, - 27.2, - 27.4, - 27.3, - 27, - 26.6, - 26.3, - 26.5, - 26.7, - 26.9, - 27.2, - 27.2, - 27.2, - 27.3, - 27.4, - 27.6, - 27.8, - 27.8, - 27.9, - 27.9, - 28, - 28.1, - 28.4, - 29, - 29.8, - 30.8, - 31.6, - 31.9, - 32, - 31.9, - 31.8, - 31.8, - 31.8, - 31.6, - 31.3, - 31.5, - 32.2, - 33.1, - 33.9, - 34.6, - 35.1, - 35.5, - 36.1, - 37.1, - 38.3, - 39.7, - 41.3, - 42.3, - 42.7, - 43.3, - 43.5, - 43.9, - 45, - 45.9, - 46.5, - 47.1, - 47.4, - 47.8, - 48.5, - 49.1, - 49.6, - 50.2, - 50.8, - 51.7, - 52.6, - 53.5, - 54.6, - 55.7, - 57.3, - 61.3, - 62.2, - 59.7, - 59.5, - 59.4, - 58.8, - 57.8, - 56.7, - 55.6, - 54.7, - 53.8, - 53, - 52.3, - 51.6, - 51, - 50.4, - 49.8, - 49.4, - 49.3, - 48.9, - 48.5, - 48.3, - 48.2, - 47.8, - 47.4, - 46.9, - 46.5, - 46.1, - 45.9, - 45.6, - 45.2, - 44.6, - 43.6, - 42.3, - 41.2, - 40.5, - 39.7, - 39.5, - 39.3, - 39.2, - 39, - 38.7, - 38.6, - 38.8, - 39.1, - 38.3, - 37.8, - 37.2, - 36.6, - 36.3, - 36.1, - 35.7, - 35.4, - 35.2, - 34.9, - 34.6, - 34.1, - 34.1, - 33.9, - 33.8, - 33.4, - 33.1, - 32.9, - 32.6, - 32.5, - 32.3, - 32.2, - 32.1, - 32.2, - 32.1, - 32, - 32.1, - 32, - 31.8, - 31.7, - 31.6, - 31.5, - 31.3, - 31.1, - 31.1, - 31.2, - 31.8, - 32, - 32, - 31.8, - 31.1, - 30.2, - 29.9, - 29.8, - 29.4, - 29.4, - 29.3, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.2, - 29.1, - 28.9, - 28.7, - 28.7, - 28.7, - 28.6, - 28.4, - 28.1, - 27.5, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.4, - 26.1, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.6, - 25.5, - 25.3, - 24.9, - 24.5, - 24.3, - 24.2, - 24.1, - 23.9, - 23.7, - 23.5, - 23.2, - 23.1, - 22.8, - 18.1, - 16.6, - 16.1, - 16.1, - 15.8, - 16.8, - 17.1, - 17.1, - 17.7, - 18.4, - 19.5, - 19.4, - 19.7, - 20.2, - 20.7, - 21.2, - 21.5, - 21.7, - 21.8, - 21.8, - 21.7, - 21.5, - 21.2, - 21, - 21, - 21, - 21.1, - 21.1, - 21.2, - 21.3, - 21.5, - 21.7, - 21.8, - 22, - 22.3, - 22.5, - 22.8, - 22.9, - 23, - 23.3, - 23.5, - 23.6, - 23.5, - 23.4, - 23.3, - 23.9, - 25, - 25.4, - 25.5, - 25.4, - 25.6, - 26.1, - 26.8, - 27.1, - 26.9, - 26.6, - 26.5, - 26.6, - 26.6, - 26.9, - 27.2, - 27.2, - 27.2, - 27.2, - 27.3, - 27.5, - 27.8, - 28, - 28.2, - 28.3, - 28.4, - 28.5, - 28.7, - 29.2, - 29.9, - 31, - 31.8, - 32.2, - 32.4, - 32.3, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 32, - 32.9, - 33.9, - 34.6, - 35, - 35.5, - 36.2, - 37.4, - 38.9, - 41.3, - 42.2, - 42.8, - 43.3, - 44.3, - 44.7, - 44.9, - 45.1, - 45.7, - 46.6, - 47.4, - 48, - 48.3, - 48.7, - 49.2, - 49.8, - 50.5, - 51.2, - 52.2, - 53.2, - 54.2, - 55.2, - 56.4, - 62.4, - 67.4, - 67.9, - 62.1, - 61.1, - 60.7, - 59.9, - 58.8, - 57.4, - 56.1, - 55, - 54.1, - 53.3, - 52.7, - 52.1, - 51.4, - 50.7, - 50.1, - 50, - 49.8, - 49.4, - 49.1, - 48.9, - 48.6, - 48.3, - 47.8, - 47.6, - 47, - 46.4, - 46.2, - 45.7, - 45.2, - 44.5, - 43.4, - 42.2, - 41.4, - 40.6, - 39.8, - 39.7, - 39.5, - 39.5, - 39.1, - 38.6, - 38.4, - 38.5, - 38.6, - 37.8, - 37.4, - 37, - 36.6, - 36.4, - 36.2, - 36, - 35.8, - 35.5, - 35.2, - 34.7, - 34.6, - 34.7, - 34.5, - 34.4, - 34.1, - 33.7, - 33.2, - 32.9, - 32.8, - 32.6, - 32.3, - 32.4, - 32.5, - 32.3, - 32.1, - 32.2, - 32.1, - 32, - 31.9, - 31.7, - 31.5, - 31.3, - 31.2, - 31.4, - 31.4, - 31.9, - 32, - 31.9, - 31.7, - 31.1, - 30.3, - 30, - 30, - 29.5, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.3, - 29.1, - 29, - 29, - 29.1, - 29.2, - 29.1, - 28.8, - 28.4, - 27.8, - 27.4, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.2, - 26, - 25.9, - 25.8, - 25.6, - 25.6, - 25.5, - 25.5, - 25.4, - 25, - 24.5, - 24.3, - 24.1, - 24, - 23.7, - 23.2, - 23, - 22.7, - 22.6, - 20.8, - 16.3, - 15.6, - 15.2, - 15.2, - 15, - 15.4, - 15.7, - 15.7, - 16, - 16.7, - 17.4, - 19.7, - 20.2, - 20.7, - 21.2, - 21.5, - 21.8, - 21.8, - 21.8, - 21.7, - 21.6, - 21.5, - 21.4, - 21.3, - 21.2, - 21.2, - 21.2, - 21.3, - 21.3, - 21.4, - 21.6, - 21.8, - 22, - 22.2, - 22.6, - 23.2, - 23.5, - 23.9, - 24.1, - 24.4, - 25.1, - 25.2, - 24.5, - 23.9, - 23.5, - 23.6, - 24.1, - 24.3, - 24.4, - 24.4, - 24.5, - 25, - 25.9, - 26.5, - 26.5, - 26.2, - 26.1, - 26.4, - 26.4, - 26.6, - 26.9, - 26.9, - 26.9, - 26.9, - 27, - 27.2, - 27.6, - 28.1, - 28.4, - 28.7, - 28.9, - 29.1, - 29.3, - 29.7, - 30.3, - 31.2, - 31.9, - 32.5, - 32.8, - 32.9, - 33, - 33, - 32.8, - 32.7, - 32.8, - 32.6, - 32.3, - 32.7, - 33.5, - 34.3, - 34.8, - 35.4, - 36.2, - 37.4, - 39.5, - 42.2, - 43.1, - 43.7, - 44.1, - 44.8, - 45.4, - 45.8, - 45.8, - 45.9, - 46.5, - 47.4, - 48.1, - 48.6, - 49.1, - 49.6, - 50, - 50.9, - 51.7, - 52.9, - 53.9, - 54.9, - 56, - 57.4, - 66.9, - 71.9, - 73, - 67.9, - 63.9, - 61.8, - 60.7, - 59.2, - 57.8, - 56.6, - 55.6, - 54.8, - 54, - 53.2, - 52.4, - 51.6, - 50.9, - 50.6, - 50.6, - 50.4, - 50, - 49.7, - 49.5, - 49.2, - 48.8, - 48.5, - 48.1, - 47.2, - 46.6, - 46.3, - 45.8, - 45, - 44.1, - 42.9, - 42.1, - 41.2, - 40.6, - 40.2, - 40.1, - 40, - 39.6, - 38.9, - 38.6, - 38.4, - 38.2, - 38, - 37.8, - 37.7, - 37.5, - 37.2, - 36.9, - 36.6, - 36.4, - 36.1, - 35.7, - 35.2, - 34.9, - 35.1, - 35.2, - 35.2, - 35.1, - 34.9, - 34.4, - 33.8, - 33.4, - 33.1, - 32.7, - 32.6, - 32.9, - 33, - 32.5, - 32.3, - 32.4, - 32.2, - 32.1, - 32, - 31.8, - 31.6, - 31.5, - 31.4, - 31.5, - 31.5, - 31.8, - 31.9, - 31.8, - 31.5, - 30.9, - 30.3, - 30.2, - 30.1, - 29.7, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.3, - 29.2, - 29.1, - 29.2, - 29.3, - 29.4, - 29.4, - 29, - 28.6, - 28, - 27.4, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.3, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.5, - 25.4, - 25.1, - 24.8, - 24.4, - 24.3, - 24.1, - 24, - 23.7, - 23.1, - 22.7, - 22.4, - 22.1, - 18.5, - 15.7, - 14.9, - 14.8, - 14.6, - 14.2, - 14.3, - 14.5, - 14.7, - 15, - 15.6, - 16.4, - 20, - 20.7, - 21.2, - 21.5, - 21.7, - 21.8, - 21.7, - 21.6, - 21.5, - 21.5, - 21.5, - 21.4, - 21.4, - 21.3, - 21.3, - 21.3, - 21.4, - 21.4, - 21.5, - 21.7, - 22.1, - 22.5, - 22.9, - 23.6, - 24.3, - 24.7, - 25.3, - 25.8, - 26, - 26.3, - 26.1, - 25.4, - 24.7, - 24.2, - 23.8, - 23.8, - 24.1, - 25.1, - 25.5, - 25.3, - 24.8, - 25, - 25.6, - 25.6, - 25.5, - 25.5, - 25.8, - 25.8, - 26, - 26.3, - 26.4, - 26.5, - 26.5, - 26.7, - 27, - 27.5, - 27.9, - 28.4, - 28.9, - 29.3, - 29.7, - 30, - 30.7, - 31.1, - 31.5, - 32, - 32.6, - 33.2, - 33.6, - 34, - 34, - 33.6, - 33.5, - 33.7, - 33.3, - 33, - 33.1, - 33.3, - 34.2, - 34.8, - 35.3, - 36.1, - 37.5, - 39.5, - 42.4, - 43.5, - 44.2, - 44.7, - 45.2, - 45.7, - 46.2, - 46.6, - 46.7, - 47.1, - 47.8, - 48.4, - 48.9, - 49.5, - 50.2, - 50.6, - 51.5, - 52.1, - 53.2, - 54.3, - 55.5, - 56.7, - 58, - 68.2, - 75.2, - 76.2, - 72, - 66.6, - 63.4, - 61.8, - 59.8, - 58.5, - 57.6, - 56.7, - 55.8, - 54.7, - 53.8, - 52.9, - 52.3, - 51.7, - 51.5, - 51.3, - 50.9, - 50.6, - 50.3, - 50, - 49.7, - 49.6, - 49.3, - 48.5, - 47.5, - 46.9, - 46.4, - 45.6, - 44.8, - 44, - 42.7, - 42, - 41.5, - 41.4, - 41.3, - 41, - 40.4, - 39.7, - 39.2, - 39, - 38.7, - 38.6, - 38.5, - 38.4, - 38.3, - 38.1, - 37.8, - 37.5, - 37.1, - 36.7, - 36.3, - 35.8, - 35.3, - 35.3, - 35.5, - 35.6, - 35.6, - 35.7, - 35.5, - 35.1, - 34.5, - 33.8, - 33.4, - 33, - 33, - 33.5, - 33.2, - 32.6, - 32.5, - 32.5, - 32.4, - 32.2, - 32, - 31.9, - 31.8, - 31.6, - 31.6, - 31.6, - 31.5, - 31.5, - 31.5, - 31.4, - 31.1, - 30.7, - 30.3, - 30.2, - 30.1, - 29.9, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.4, - 29.5, - 29.4, - 29.1, - 28.6, - 28, - 27.4, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.3, - 26, - 25.8, - 25.7, - 25.6, - 25.5, - 25.4, - 25, - 24.6, - 24.4, - 24.3, - 24.2, - 24.1, - 24, - 23.9, - 23.5, - 23, - 22.8, - 22, - 18, - 16, - 15.1, - 14.7, - 14.3, - 13.9, - 13.8, - 13.8, - 13.8, - 13.9, - 14.5, - 15.4, - 20.4, - 21, - 21.4, - 21.7, - 21.8, - 21.6, - 21.4, - 21.3, - 21.2, - 21.2, - 21.3, - 21.4, - 21.4, - 21.5, - 21.4, - 21.4, - 21.5, - 21.6, - 21.7, - 21.9, - 22.4, - 23.3, - 24.1, - 25.1, - 25.5, - 25.9, - 26.3, - 26.6, - 26.8, - 26.8, - 26.6, - 26.1, - 25.8, - 25.3, - 25, - 24.6, - 24.8, - 26.1, - 26.5, - 26.2, - 25.6, - 24.9, - 25.1, - 25.3, - 25.7, - 25.9, - 25.8, - 25.9, - 26.1, - 26.2, - 26.3, - 26.3, - 26.3, - 26.6, - 26.9, - 27.3, - 27.8, - 28.3, - 28.9, - 29.5, - 30.1, - 30.8, - 31.4, - 31.8, - 32, - 32.3, - 32.7, - 33.3, - 33.9, - 34.3, - 34.4, - 34.3, - 34.2, - 34.3, - 34.1, - 34, - 33.8, - 34.1, - 34.5, - 35, - 35.5, - 36.1, - 37.1, - 38.7, - 41.7, - 43, - 44, - 44.7, - 45.4, - 45.9, - 46.5, - 47, - 47.5, - 47.8, - 48.2, - 48.8, - 49.5, - 50.1, - 50.8, - 51.2, - 52, - 53, - 53.7, - 54.9, - 56, - 57.3, - 58.4, - 66.5, - 75.3, - 76.8, - 74.8, - 68.5, - 67, - 64.5, - 60.8, - 59.5, - 58.7, - 57.8, - 56.7, - 55.5, - 54.6, - 53.7, - 53.3, - 52.7, - 52.4, - 51.9, - 51.5, - 51.1, - 50.8, - 50.4, - 50.2, - 50, - 49.6, - 48.5, - 47.6, - 47, - 46.3, - 45.3, - 44.7, - 44.2, - 43.1, - 42.7, - 42.9, - 42.8, - 42.4, - 41.7, - 40.8, - 39.9, - 39.8, - 39.6, - 39.2, - 39.2, - 39.3, - 39.2, - 39.1, - 38.8, - 38.5, - 38.1, - 37.6, - 37.1, - 36.5, - 36.1, - 35.7, - 35.5, - 35.9, - 36.1, - 36.2, - 36.1, - 35.9, - 35.5, - 35.1, - 34.2, - 33.7, - 33.2, - 33.2, - 33.5, - 33.2, - 32.7, - 32.8, - 32.7, - 32.5, - 32.3, - 32.1, - 32.1, - 31.9, - 31.7, - 31.7, - 31.6, - 31.5, - 31.3, - 31.2, - 31, - 30.8, - 30.6, - 30.4, - 30.4, - 30.3, - 30.1, - 29.6, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.4, - 29.3, - 29.4, - 29.4, - 29.3, - 28.9, - 28.5, - 27.9, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.4, - 26.2, - 25.9, - 25.7, - 25.6, - 25.5, - 25.4, - 25.3, - 24.7, - 24.4, - 24.4, - 24.4, - 24.2, - 24.1, - 24.1, - 24.1, - 24, - 23.7, - 23.3, - 22, - 18, - 16.8, - 16.1, - 15.6, - 15.1, - 14.3, - 14.1, - 13.9, - 13.7, - 13.5, - 13.6, - 14.2, - 20.4, - 21, - 21.4, - 21.6, - 21.5, - 21.2, - 21, - 21, - 21.1, - 21.1, - 21.2, - 21.4, - 21.6, - 21.7, - 21.7, - 21.6, - 21.8, - 21.9, - 21.9, - 22, - 22.7, - 24, - 25.1, - 25.9, - 26.1, - 26.4, - 26.7, - 26.9, - 27, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.3, - 25.8, - 25.3, - 26.5, - 27, - 26.8, - 26.3, - 25.7, - 26, - 26.3, - 26.7, - 26.8, - 26.7, - 26.4, - 26.8, - 27, - 26.9, - 26.6, - 26.5, - 26.8, - 27.1, - 27.5, - 28, - 28.6, - 29.1, - 29.7, - 30.4, - 31.4, - 32.1, - 32.4, - 32.6, - 32.7, - 32.9, - 33.5, - 34, - 34.4, - 34.6, - 34.5, - 34.5, - 34.6, - 34.6, - 34.5, - 34.4, - 34.5, - 34.7, - 35.2, - 35.6, - 36.4, - 37.1, - 38.1, - 40.3, - 42.2, - 43.1, - 44, - 45.1, - 45.8, - 46.6, - 47.2, - 47.8, - 48.3, - 48.5, - 49.1, - 49.8, - 50.6, - 51.4, - 52, - 52.6, - 53.7, - 54.7, - 55.8, - 56.8, - 58, - 59.8, - 69.1, - 76.2, - 77.4, - 76.9, - 71.7, - 68.7, - 66.8, - 62.1, - 60.6, - 59.6, - 58.6, - 57.4, - 56.3, - 55.3, - 54.7, - 54.2, - 53.5, - 53.1, - 52.5, - 51.9, - 51.5, - 51.2, - 50.8, - 50.3, - 50, - 49.3, - 48.1, - 47.3, - 46.7, - 46, - 45.2, - 45, - 44.7, - 43.9, - 44, - 44.2, - 44.1, - 43.5, - 42.4, - 41.6, - 40.8, - 40.7, - 40.4, - 39.9, - 40, - 40.2, - 40.2, - 40.1, - 39.7, - 39.3, - 38.6, - 38, - 37.6, - 37.1, - 36.5, - 36, - 35.9, - 36.4, - 36.6, - 36.6, - 36.6, - 36.1, - 35.6, - 35.2, - 34.5, - 33.9, - 33.5, - 33.4, - 33.3, - 33, - 33, - 33.1, - 32.9, - 32.6, - 32.4, - 32.2, - 32.1, - 31.9, - 31.9, - 31.8, - 31.7, - 31.5, - 31.3, - 31.1, - 30.9, - 30.8, - 30.7, - 30.7, - 30.6, - 30.4, - 30.2, - 29.8, - 29.5, - 29.4, - 29.4, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.3, - 29, - 28.6, - 28.2, - 27.6, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.4, - 26.2, - 26, - 25.8, - 25.6, - 25.5, - 25.4, - 25.4, - 25.1, - 24.6, - 24.5, - 24.5, - 24.5, - 24.4, - 24.3, - 24.2, - 24.2, - 24.2, - 24.1, - 23.7, - 22.9, - 19.5, - 17.9, - 17.3, - 16.8, - 16, - 15.2, - 14.6, - 14.1, - 13.6, - 13.3, - 13.4, - 13.5, - 20.1, - 20.7, - 21.1, - 21, - 20.8, - 20.7, - 20.9, - 21.2, - 21.3, - 21.2, - 21.2, - 21.3, - 21.7, - 21.8, - 21.8, - 21.8, - 22.1, - 22.5, - 22.6, - 22.4, - 23.2, - 24.7, - 25.6, - 26, - 26.3, - 26.5, - 26.7, - 26.9, - 27.1, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27, - 26.5, - 25.9, - 26.8, - 27.4, - 27.2, - 27, - 26.7, - 27, - 27.2, - 27.3, - 27.3, - 27.2, - 27, - 27.3, - 27.5, - 27.4, - 27, - 26.8, - 27.2, - 27.5, - 28, - 28.5, - 28.9, - 29.4, - 30, - 30.8, - 31.7, - 32.5, - 33, - 33.2, - 33.2, - 33.4, - 33.9, - 34.2, - 34.5, - 34.7, - 34.8, - 34.8, - 34.8, - 34.8, - 34.8, - 34.7, - 34.8, - 35, - 35.4, - 35.9, - 36.8, - 37.7, - 39, - 41.3, - 42.4, - 42.9, - 43.9, - 44.6, - 45.5, - 46.5, - 47.2, - 47.9, - 48.3, - 48.8, - 49.3, - 50.1, - 51, - 51.9, - 52.8, - 53.7, - 54.6, - 55.6, - 56.8, - 57.8, - 58.8, - 61.9, - 73.2, - 77, - 77.7, - 77.6, - 73.9, - 69.8, - 67.8, - 64.9, - 61.7, - 60.6, - 59.2, - 58.1, - 57, - 56.5, - 55.9, - 55.1, - 54.3, - 53.6, - 52.9, - 52.2, - 51.5, - 51, - 50.5, - 50, - 49.5, - 48.7, - 47.9, - 47.1, - 46.5, - 45.9, - 45.7, - 45.8, - 45, - 44.5, - 44.8, - 45, - 44.8, - 44.2, - 43.4, - 42.5, - 42.1, - 42, - 41.4, - 40.8, - 40.9, - 41.1, - 41.5, - 41.4, - 40.8, - 40, - 39.2, - 38.5, - 38, - 37.6, - 36.7, - 36.3, - 36.5, - 36.9, - 36.9, - 36.8, - 36.7, - 36.3, - 35.7, - 35.3, - 34.8, - 34.1, - 33.8, - 33.6, - 33.4, - 33.1, - 33.3, - 33.2, - 32.9, - 32.7, - 32.5, - 32.3, - 32.2, - 32.2, - 32, - 31.9, - 31.7, - 31.5, - 31.3, - 31.2, - 31.1, - 31, - 30.9, - 30.7, - 30.6, - 30.4, - 30.3, - 30, - 29.5, - 29.4, - 29.4, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.3, - 29.4, - 29.4, - 29.5, - 29.5, - 29.3, - 29.2, - 29, - 28.6, - 28.3, - 27.7, - 27.3, - 27, - 26.9, - 26.8, - 26.6, - 26.4, - 26.1, - 25.9, - 25.7, - 25.6, - 25.5, - 25.4, - 25.4, - 25.3, - 25.1, - 24.8, - 24.7, - 24.7, - 24.6, - 24.5, - 24.4, - 24.3, - 24.2, - 24.2, - 24.2, - 24, - 23.4, - 21.8, - 19.1, - 18.5, - 17.7, - 16.9, - 16.1, - 15.2, - 14.5, - 13.8, - 13.2, - 13, - 13.2, - 19.7, - 20, - 20.3, - 20.2, - 20.5, - 20.9, - 21.3, - 21.6, - 21.7, - 21.6, - 21.3, - 21.4, - 21.8, - 22, - 22, - 22.1, - 22.5, - 23.3, - 23.5, - 23.2, - 23.8, - 24.8, - 25.5, - 26, - 26.2, - 26.5, - 26.7, - 26.9, - 27.1, - 27.2, - 27.3, - 27.3, - 27.4, - 27.5, - 27.4, - 27.1, - 26.6, - 27, - 27.5, - 27.6, - 27.6, - 27.5, - 27.6, - 27.8, - 27.8, - 27.8, - 27.7, - 27.7, - 27.8, - 28, - 27.9, - 27.5, - 27, - 27.3, - 27.7, - 28.3, - 28.9, - 29.2, - 29.8, - 30.4, - 31.2, - 31.9, - 32.7, - 33.3, - 33.7, - 33.8, - 34, - 34.2, - 34.4, - 34.6, - 34.7, - 34.8, - 34.8, - 34.9, - 35, - 35, - 35, - 35.1, - 35.4, - 35.9, - 36.6, - 37.6, - 38.6, - 41.2, - 43.4, - 44, - 44.4, - 45, - 45.2, - 45.6, - 46.4, - 47, - 47.8, - 48.3, - 48.8, - 49.4, - 50.3, - 51.4, - 52.4, - 53.3, - 54.4, - 55.3, - 56.4, - 57.8, - 58.9, - 59.9, - 62.2, - 70.9, - 77.1, - 77.9, - 77.8, - 76.4, - 71.5, - 68.4, - 67.1, - 64, - 61.8, - 60, - 58.7, - 58.3, - 57.6, - 56.7, - 55.9, - 54.9, - 53.9, - 53.1, - 52.4, - 51.7, - 50.8, - 50.3, - 49.6, - 48.9, - 48.3, - 47.8, - 47.3, - 46.7, - 46.3, - 46.3, - 45.9, - 45.2, - 44.9, - 45.5, - 45.7, - 45.1, - 44.6, - 44.1, - 43.6, - 43.6, - 43.5, - 42.7, - 42.1, - 42, - 42.3, - 42.7, - 42.7, - 41.9, - 40.6, - 39.8, - 39, - 38.3, - 37.7, - 37.1, - 37.1, - 37.2, - 37.2, - 37.1, - 36.9, - 36.7, - 36.3, - 35.7, - 35.3, - 35, - 34.4, - 34, - 33.8, - 33.5, - 33.4, - 33.3, - 33.1, - 33, - 32.8, - 32.6, - 32.5, - 32.4, - 32.3, - 32.1, - 31.9, - 31.7, - 31.6, - 31.4, - 31.3, - 31.2, - 31, - 30.9, - 30.8, - 30.6, - 30.5, - 30.3, - 30.2, - 29.6, - 29.5, - 29.4, - 29.5, - 29.5, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.1, - 28.8, - 28.5, - 28.1, - 27.7, - 27.3, - 27.1, - 26.9, - 26.8, - 26.6, - 26.4, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.5, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 24.9, - 24.7, - 24.5, - 24.4, - 24.3, - 24.2, - 24.1, - 24.1, - 23.7, - 22.8, - 20.9, - 19.4, - 18.6, - 17.8, - 16.9, - 16.1, - 15.3, - 14.5, - 13.2, - 12.6, - 12.7, - 19.6, - 19.7, - 20, - 20.4, - 21, - 21.4, - 21.7, - 21.9, - 22, - 21.9, - 21.5, - 21.6, - 22, - 22.1, - 22.3, - 22.7, - 23.6, - 24.5, - 24.5, - 24.5, - 24.7, - 25, - 25.5, - 25.9, - 26.1, - 26.3, - 26.6, - 26.9, - 27.1, - 27.2, - 27.3, - 27.4, - 27.5, - 27.6, - 27.7, - 27.5, - 27.2, - 27.2, - 27.7, - 27.8, - 28, - 28, - 28.2, - 28.3, - 28.4, - 28.4, - 28.3, - 28.3, - 28.4, - 28.6, - 28.6, - 28.2, - 27.6, - 27.3, - 27.8, - 28.5, - 29.1, - 29.5, - 30.2, - 31, - 31.7, - 32.3, - 33, - 33.6, - 33.8, - 34, - 34, - 34.2, - 34.3, - 34.5, - 34.7, - 34.6, - 34.6, - 34.8, - 35, - 35.1, - 35.2, - 35.3, - 35.7, - 36.2, - 37.2, - 38.3, - 39.6, - 43.1, - 44.4, - 45, - 45.3, - 45.6, - 46.2, - 46.5, - 46.8, - 47, - 47.6, - 48.4, - 48.8, - 49.5, - 50.7, - 51.9, - 52.8, - 53.9, - 54.9, - 55.9, - 57.1, - 58.4, - 59.6, - 61, - 63.3, - 69.5, - 75.7, - 78.1, - 78.1, - 77.4, - 72.4, - 69.4, - 68.3, - 66.6, - 63.3, - 61.7, - 60.1, - 59.5, - 58.5, - 57.4, - 56.3, - 55.1, - 54.1, - 53.3, - 52.6, - 51.8, - 51, - 50.1, - 49.4, - 48.9, - 48.5, - 48, - 47.5, - 47, - 46.9, - 46.4, - 45.9, - 45.6, - 45.5, - 46.2, - 46.3, - 45.7, - 45.1, - 44.7, - 44.6, - 44.6, - 44.4, - 43.9, - 43.6, - 43.2, - 43.2, - 43.5, - 43.4, - 42.8, - 41.4, - 40.2, - 39.4, - 38.7, - 38.1, - 37.6, - 37.5, - 37.6, - 37.5, - 37.3, - 37, - 36.7, - 36.3, - 35.7, - 35.3, - 35, - 34.6, - 34.2, - 33.9, - 33.7, - 33.6, - 33.3, - 33.3, - 33.1, - 32.9, - 32.8, - 32.6, - 32.5, - 32.3, - 32.1, - 32, - 31.8, - 31.6, - 31.5, - 31.4, - 31.3, - 31.1, - 31, - 30.9, - 30.7, - 30.6, - 30.4, - 30.2, - 29.7, - 29.5, - 29.4, - 29.5, - 29.5, - 29.4, - 29.3, - 29.3, - 29.2, - 29.1, - 29.2, - 29.2, - 29.1, - 28.7, - 28.2, - 27.8, - 27.5, - 27.1, - 27, - 26.9, - 26.7, - 26.6, - 26.4, - 26.2, - 26.1, - 26.2, - 26.3, - 26.3, - 26.1, - 26, - 25.9, - 25.7, - 25.6, - 25.6, - 25.5, - 25.5, - 25.5, - 25.4, - 25.1, - 24.7, - 24.5, - 24.3, - 24.2, - 24.1, - 24.1, - 23.8, - 23.3, - 22.6, - 20.4, - 19.4, - 18.4, - 17.7, - 17, - 16.2, - 15.1, - 13.5, - 12.4, - 12.5, - 20, - 20.1, - 20.6, - 21, - 21.4, - 21.8, - 22.1, - 22.3, - 22.2, - 22.1, - 21.7, - 21.8, - 22, - 22.2, - 22.6, - 23.3, - 24.5, - 25.1, - 25.1, - 25.2, - 25.3, - 25.5, - 25.8, - 26, - 26.1, - 26.3, - 26.6, - 26.9, - 27.1, - 27.3, - 27.3, - 27.5, - 27.6, - 27.8, - 27.9, - 27.8, - 27.7, - 27.7, - 27.9, - 28.1, - 28.3, - 28.5, - 28.6, - 28.8, - 28.9, - 28.9, - 28.9, - 28.9, - 29, - 29.3, - 29.3, - 29.1, - 28.4, - 27.9, - 28.1, - 28.7, - 29.2, - 29.7, - 30.4, - 31.4, - 32.1, - 32.6, - 33.1, - 33.6, - 33.7, - 33.8, - 33.7, - 33.7, - 33.7, - 34, - 34.1, - 34.3, - 34.4, - 34.6, - 34.8, - 35.1, - 35.3, - 35.4, - 35.8, - 36.6, - 37.6, - 38.7, - 40.1, - 43.1, - 44.6, - 45.3, - 45.8, - 46.2, - 46.7, - 47.2, - 47.4, - 47.5, - 48, - 48.6, - 49.1, - 49.8, - 50.7, - 52, - 53.1, - 54.1, - 55.3, - 56.5, - 57.7, - 59, - 60.2, - 62, - 65.2, - 69.4, - 73.9, - 78.2, - 78.2, - 77.7, - 75.1, - 70.4, - 69.1, - 67.8, - 65.4, - 63.4, - 61.7, - 60.3, - 59, - 57.7, - 56.6, - 55.6, - 54.5, - 53.5, - 52.6, - 51.8, - 51.1, - 50.4, - 49.8, - 49.3, - 49, - 48.4, - 48, - 47.6, - 47.1, - 46.6, - 46.4, - 45.8, - 46.3, - 46.8, - 46.8, - 46.5, - 45.9, - 45.4, - 45.2, - 45.2, - 45.1, - 44.7, - 44.5, - 44.1, - 44, - 44, - 43.7, - 43.1, - 41.9, - 40.7, - 39.8, - 39.3, - 38.6, - 37.9, - 37.8, - 37.9, - 37.8, - 37.6, - 37.2, - 36.7, - 36.3, - 35.8, - 35.3, - 35, - 34.7, - 34.3, - 34.1, - 33.9, - 33.6, - 33.6, - 33.5, - 33.3, - 33.1, - 33, - 32.8, - 32.5, - 32.3, - 32.1, - 32, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.2, - 31.1, - 30.9, - 30.8, - 30.6, - 30.4, - 30.2, - 29.9, - 29.5, - 29.4, - 29.4, - 29.5, - 29.5, - 29.4, - 29.2, - 29.1, - 29, - 28.8, - 28.8, - 28.7, - 28.3, - 27.6, - 27.2, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.3, - 26.2, - 26.3, - 26.5, - 26.6, - 26.6, - 26.5, - 26.5, - 26.5, - 26.2, - 25.9, - 25.8, - 25.7, - 25.6, - 25.6, - 25.6, - 25.5, - 25, - 24.6, - 24.4, - 24.2, - 24.1, - 24, - 24, - 23.8, - 23.2, - 22.2, - 20.3, - 19.2, - 18.5, - 17.8, - 16.8, - 15.4, - 13.7, - 12.2, - 12.3, - 20.7, - 20.8, - 21.1, - 21.4, - 21.8, - 22.1, - 22.3, - 22.3, - 22.3, - 22.2, - 21.9, - 21.8, - 22, - 22.2, - 22.6, - 23.5, - 24.7, - 25.3, - 25.4, - 25.6, - 25.7, - 25.8, - 26, - 26.1, - 26.3, - 26.4, - 26.6, - 26.8, - 27.1, - 27.2, - 27.4, - 27.5, - 27.8, - 27.9, - 28.1, - 28.1, - 28, - 28, - 28.2, - 28.4, - 28.6, - 28.8, - 29, - 29.2, - 29.3, - 29.4, - 29.4, - 29.5, - 29.6, - 29.7, - 29.7, - 29.5, - 29.1, - 28.5, - 28.6, - 29, - 29.3, - 29.8, - 30.5, - 31.3, - 32.1, - 32.4, - 32.9, - 33.2, - 33.1, - 33.1, - 32.7, - 32.6, - 32.3, - 32.5, - 32.7, - 33.3, - 33.9, - 34.3, - 34.7, - 34.9, - 35.2, - 35.4, - 35.7, - 36.5, - 37.5, - 38.7, - 40, - 42.7, - 44.6, - 45.4, - 46, - 46.7, - 47.1, - 47.5, - 47.7, - 47.9, - 48.2, - 48.9, - 49.5, - 50, - 50.6, - 51.6, - 52.9, - 54, - 55.4, - 56.8, - 58, - 59.3, - 60.7, - 64.4, - 67.8, - 69.9, - 74, - 78, - 78.2, - 77.8, - 76.7, - 72.5, - 70, - 68.4, - 66.7, - 65.3, - 63, - 61.1, - 59.5, - 58.4, - 57.3, - 56.1, - 54.9, - 53.9, - 53.1, - 52.3, - 51.6, - 51.1, - 50.4, - 49.8, - 49.4, - 49, - 48.5, - 48.1, - 47.6, - 47.3, - 46.8, - 46.2, - 46.9, - 47.3, - 47.1, - 46.8, - 46.5, - 46, - 45.6, - 45.6, - 45.5, - 45.3, - 45.1, - 44.7, - 44.4, - 44.2, - 43.8, - 43.1, - 42.1, - 41, - 40.2, - 39.6, - 38.7, - 38.1, - 38.2, - 38.1, - 37.9, - 37.7, - 37.2, - 36.6, - 36.2, - 35.7, - 35.2, - 34.9, - 34.6, - 34.3, - 34, - 33.9, - 33.8, - 33.8, - 33.6, - 33.4, - 33.2, - 33, - 32.8, - 32.6, - 32.4, - 32.2, - 32, - 31.9, - 31.8, - 31.7, - 31.5, - 31.4, - 31.3, - 31.1, - 30.9, - 30.8, - 30.6, - 30.4, - 30.2, - 30, - 29.5, - 29.4, - 29.4, - 29.5, - 29.6, - 29.4, - 29.3, - 29.2, - 29.1, - 28.8, - 28.6, - 28.5, - 28, - 27.4, - 27.1, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.4, - 26.6, - 26.7, - 26.8, - 26.8, - 26.8, - 26.9, - 26.8, - 26.6, - 26.3, - 26, - 25.8, - 25.7, - 25.7, - 25.7, - 25.6, - 25.2, - 24.7, - 24.5, - 24.2, - 24.1, - 24.1, - 24.1, - 24, - 23.8, - 23.3, - 22.5, - 20.7, - 19.5, - 18.4, - 17, - 15.8, - 14.3, - 12, - 12.1, - 21.3, - 21.4, - 21.5, - 21.8, - 22.1, - 22.3, - 22.3, - 22.4, - 22.3, - 22.3, - 22.1, - 22, - 22, - 22.2, - 22.5, - 23.4, - 24.6, - 25.3, - 25.5, - 25.6, - 25.7, - 25.8, - 25.9, - 26.1, - 26.2, - 26.3, - 26.5, - 26.7, - 26.9, - 27.1, - 27.3, - 27.6, - 27.8, - 28, - 28.2, - 28.2, - 28.2, - 28.3, - 28.4, - 28.6, - 28.8, - 29, - 29.2, - 29.3, - 29.5, - 29.6, - 29.7, - 29.8, - 29.8, - 29.7, - 29.6, - 29.4, - 29.2, - 29, - 29, - 29.1, - 29.4, - 29.8, - 30.4, - 31.1, - 31.6, - 32, - 32.3, - 32.4, - 32.3, - 32, - 31.6, - 31.4, - 31.5, - 31.7, - 32, - 32.3, - 33, - 34.1, - 34.5, - 34.8, - 35, - 35.2, - 35.6, - 36.3, - 37.3, - 38.4, - 39.7, - 42, - 44.6, - 45.4, - 45.9, - 46.6, - 47.1, - 47.5, - 47.8, - 48.1, - 48.6, - 49.3, - 49.9, - 50.4, - 51.1, - 51.7, - 52.6, - 53.7, - 54.9, - 56.3, - 57.7, - 59.4, - 61.8, - 65.7, - 68.4, - 70.9, - 75.1, - 77.8, - 78.1, - 78, - 77.4, - 75, - 71.2, - 69.1, - 67.6, - 66.5, - 64.7, - 62.5, - 60.5, - 59.3, - 58, - 56.6, - 55.5, - 54.6, - 53.8, - 53, - 52.3, - 51.6, - 50.9, - 50.4, - 49.8, - 49.4, - 48.9, - 48.5, - 48.2, - 47.7, - 47.2, - 46.7, - 47.5, - 47.5, - 47.1, - 46.8, - 46.5, - 46.2, - 46, - 46, - 46, - 45.7, - 45.6, - 45.2, - 44.8, - 44.5, - 43.9, - 43.2, - 42.1, - 40.9, - 40, - 39.1, - 38.5, - 38.5, - 38.5, - 38.2, - 37.9, - 37.6, - 37, - 36.5, - 35.9, - 35.5, - 35.1, - 35, - 34.9, - 34.8, - 34.5, - 34.2, - 33.9, - 33.7, - 33.6, - 33.4, - 33.2, - 33, - 32.8, - 32.6, - 32.4, - 32.2, - 32, - 31.9, - 31.8, - 31.6, - 31.4, - 31.3, - 31.2, - 31.1, - 30.9, - 30.7, - 30.6, - 30.4, - 30.3, - 30.1, - 29.7, - 29.4, - 29.4, - 29.4, - 29.6, - 29.5, - 29.3, - 29.3, - 29.2, - 29, - 28.6, - 28.5, - 28.3, - 27.8, - 27.5, - 27.1, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.5, - 26.6, - 26.7, - 26.8, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.8, - 26.5, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.1, - 24.7, - 24.4, - 24.3, - 24.2, - 24.1, - 24.1, - 24.1, - 24, - 23.8, - 23.3, - 22.6, - 21, - 19.1, - 17.5, - 16.4, - 15.2, - 12.5, - 11.9, - 21.5, - 21.6, - 21.8, - 22, - 22.2, - 22.3, - 22.3, - 22.4, - 22.3, - 22.4, - 22.3, - 22.2, - 22.2, - 22.2, - 22.4, - 23.1, - 24.2, - 25, - 25.3, - 25.4, - 25.5, - 25.6, - 25.6, - 25.7, - 25.8, - 26, - 26.2, - 26.5, - 26.7, - 27, - 27.2, - 27.5, - 27.8, - 28, - 28.2, - 28.3, - 28.3, - 28.4, - 28.5, - 28.7, - 29, - 29.1, - 29.3, - 29.4, - 29.5, - 29.6, - 29.7, - 29.8, - 29.7, - 29.4, - 29.2, - 29.1, - 29, - 29, - 29, - 29.1, - 29.3, - 29.7, - 30.1, - 30.8, - 31.1, - 31.4, - 31.6, - 31.6, - 31.6, - 31.2, - 30.8, - 31.1, - 31.4, - 31.9, - 32.5, - 32.8, - 32.7, - 33.8, - 34.3, - 34.5, - 34.8, - 35.3, - 35.7, - 36.1, - 37, - 38.2, - 39.6, - 41.5, - 44.3, - 45, - 45.6, - 46.4, - 47, - 47.4, - 47.8, - 48.2, - 48.9, - 49.6, - 50.3, - 51, - 51.5, - 52.3, - 53.1, - 54.2, - 55.2, - 56.4, - 59.9, - 63.7, - 65.5, - 67.4, - 69, - 72.6, - 77.1, - 77.9, - 78.3, - 78.3, - 77.9, - 76.7, - 74.6, - 71, - 68.7, - 67.7, - 66.6, - 65.3, - 62.7, - 60.2, - 58.6, - 57.3, - 56.7, - 55.6, - 54.5, - 53.8, - 53, - 52.2, - 51.5, - 51, - 50.4, - 49.9, - 49.2, - 49.2, - 49, - 48.4, - 47.9, - 47.3, - 48, - 47.7, - 47.2, - 46.9, - 46.6, - 46.4, - 46.5, - 46.3, - 46.3, - 46.1, - 46, - 45.7, - 45.2, - 44.7, - 44.1, - 43.3, - 42, - 40.8, - 39.8, - 39, - 39, - 38.9, - 38.6, - 38.2, - 37.8, - 37.4, - 37, - 36.5, - 35.9, - 35.4, - 35.3, - 35.2, - 35.2, - 35.1, - 34.9, - 34.4, - 33.9, - 33.6, - 33.5, - 33.3, - 33.2, - 33, - 32.8, - 32.5, - 32.3, - 32.2, - 32, - 31.9, - 31.7, - 31.5, - 31.4, - 31.2, - 31.1, - 30.9, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.1, - 29.8, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.4, - 29.3, - 29.3, - 29.1, - 28.7, - 28.6, - 28.5, - 28.3, - 27.8, - 27.3, - 26.9, - 26.7, - 26.6, - 26.5, - 26.5, - 26.7, - 26.7, - 26.8, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.7, - 26.6, - 26.4, - 26.1, - 25.9, - 25.8, - 25.7, - 25.5, - 24.9, - 24.6, - 24.5, - 24.4, - 24.2, - 24.2, - 24.2, - 24.1, - 24.1, - 24, - 23.7, - 23.2, - 22.7, - 20.1, - 18, - 16.9, - 15.9, - 13.9, - 12, - 21.5, - 21.7, - 21.9, - 22.1, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.4, - 22.4, - 22.6, - 23.6, - 24.5, - 24.9, - 25, - 25, - 25.1, - 25.1, - 25.2, - 25.4, - 25.8, - 26, - 26.3, - 26.6, - 26.8, - 27.1, - 27.4, - 27.6, - 27.9, - 28, - 28.2, - 28.3, - 28.5, - 28.7, - 28.8, - 29, - 29.2, - 29.3, - 29.4, - 29.4, - 29.4, - 29.5, - 29.4, - 29.3, - 29, - 28.7, - 28.7, - 28.7, - 28.7, - 28.8, - 28.9, - 29.2, - 29.4, - 29.6, - 30.2, - 30.5, - 30.7, - 30.8, - 30.8, - 30.7, - 30.6, - 30.8, - 31.2, - 31.5, - 32.1, - 33.3, - 33.9, - 33.7, - 34, - 34.2, - 34.5, - 34.7, - 35.2, - 35.7, - 36.1, - 36.7, - 38, - 39.1, - 40.7, - 43.6, - 44.6, - 45.3, - 46, - 46.6, - 47.2, - 47.6, - 48.3, - 49.1, - 49.9, - 50.6, - 51.5, - 52.1, - 52.9, - 53.7, - 54.7, - 55.9, - 57, - 61.6, - 66.2, - 67.6, - 68.8, - 70.1, - 73.1, - 77.6, - 78.3, - 78.8, - 78.9, - 78.6, - 77.8, - 76.9, - 74.7, - 72.5, - 71.8, - 70.6, - 67.7, - 65.6, - 61.6, - 59.7, - 58.5, - 57.6, - 56.4, - 55.6, - 54.7, - 53.8, - 52.9, - 52.2, - 51.5, - 51, - 50.4, - 49.7, - 49.8, - 49.6, - 48.9, - 48.3, - 48.1, - 48.4, - 47.9, - 47.4, - 47.1, - 47, - 46.9, - 46.9, - 46.7, - 46.6, - 46.5, - 46.3, - 46, - 45.3, - 44.7, - 44.1, - 43.2, - 42.1, - 40.7, - 39.6, - 39.4, - 39.4, - 39.5, - 38.9, - 38.1, - 37.6, - 37.3, - 37, - 36.5, - 36, - 35.6, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 34.6, - 33.9, - 33.5, - 33.1, - 33.2, - 33.2, - 33, - 32.9, - 32.5, - 32.2, - 32.2, - 32.1, - 31.9, - 31.7, - 31.5, - 31.3, - 31.1, - 31, - 30.8, - 30.8, - 30.6, - 30.5, - 30.4, - 30.3, - 30.1, - 29.8, - 29.5, - 29.4, - 29.3, - 29.4, - 29.4, - 29.4, - 29.3, - 29.3, - 29.2, - 28.9, - 28.7, - 28.6, - 28.6, - 28, - 27.4, - 26.9, - 26.8, - 26.7, - 26.6, - 26.7, - 26.7, - 26.8, - 26.8, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.8, - 26.7, - 26.6, - 26.3, - 26, - 25.8, - 25.7, - 25.5, - 25.1, - 24.7, - 24.5, - 24.4, - 24.3, - 24.2, - 24.2, - 24.2, - 24.1, - 24, - 24, - 23.5, - 22.9, - 21, - 18.7, - 17.4, - 16.3, - 15, - 12.9, - 21.6, - 21.8, - 22, - 22.1, - 22.2, - 22.3, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.6, - 23, - 23, - 22.8, - 22.7, - 23.1, - 23.9, - 24.3, - 24.4, - 24.5, - 24.7, - 24.8, - 25.2, - 25.7, - 26.1, - 26.3, - 26.5, - 26.7, - 26.9, - 27.1, - 27.3, - 27.5, - 27.7, - 27.9, - 28.1, - 28.3, - 28.5, - 28.7, - 28.9, - 29, - 29.2, - 29.2, - 29.2, - 29.1, - 28.9, - 28.9, - 28.8, - 28.7, - 28.5, - 28.3, - 28.3, - 28.4, - 28.5, - 28.6, - 28.8, - 29, - 29.3, - 29.5, - 29.8, - 30, - 30.1, - 30.1, - 30.2, - 30.4, - 30.8, - 31.3, - 31.7, - 32, - 32.3, - 33.4, - 34.3, - 34.5, - 34.4, - 34.5, - 34.7, - 34.8, - 35, - 35.5, - 36.1, - 36.7, - 37.7, - 38.4, - 39.1, - 41.8, - 43.9, - 44.7, - 45.3, - 46.2, - 46.9, - 47.6, - 48.2, - 49, - 49.9, - 50.8, - 51.8, - 52.7, - 53.5, - 54.4, - 55.4, - 56.6, - 57.6, - 60.9, - 66.2, - 68.1, - 69.7, - 71.2, - 75, - 77.7, - 78.7, - 79.3, - 79.5, - 79.3, - 78.8, - 78, - 77.1, - 76.2, - 75.8, - 75.5, - 71.3, - 67.3, - 63.3, - 61.2, - 59.7, - 58.4, - 57.5, - 56.6, - 55.6, - 54.6, - 53.6, - 53, - 52.1, - 51.7, - 51, - 50.4, - 50.4, - 50, - 49.3, - 48.8, - 48.9, - 48.9, - 48.1, - 47.8, - 47.6, - 47.4, - 47.5, - 47.4, - 47.2, - 46.9, - 46.8, - 46.6, - 46, - 45.3, - 44.7, - 44.1, - 43, - 41.8, - 40.4, - 39.9, - 39.8, - 39.8, - 39.8, - 39.2, - 38.1, - 37.4, - 37, - 36.6, - 36.4, - 36.2, - 36.1, - 36, - 35.9, - 35.6, - 35.4, - 35.2, - 34.8, - 34, - 33.4, - 32.9, - 32.8, - 32.8, - 32.7, - 32.6, - 32.4, - 32.1, - 32, - 32, - 31.9, - 31.7, - 31.6, - 31.4, - 31.2, - 31.1, - 30.9, - 30.6, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 29.9, - 29.5, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.3, - 29.3, - 29.2, - 29.1, - 28.8, - 28.6, - 28.5, - 28.2, - 27.6, - 27.1, - 26.9, - 26.8, - 26.7, - 26.7, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.9, - 26.8, - 26.8, - 26.8, - 26.9, - 26.8, - 26.6, - 26.4, - 26.1, - 25.9, - 25.7, - 25.6, - 25.4, - 24.9, - 24.6, - 24.4, - 24.3, - 24.2, - 24.2, - 24.1, - 24.1, - 24.1, - 24.1, - 23.6, - 22.8, - 20.7, - 19.2, - 17.9, - 16.6, - 15.4, - 14.1, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.4, - 22.7, - 23.5, - 23.7, - 23.7, - 23.5, - 23.4, - 23.6, - 24, - 24.4, - 24.8, - 24.9, - 25, - 25.3, - 25.9, - 26.3, - 26.5, - 26.7, - 26.9, - 27.1, - 27.2, - 27.3, - 27.5, - 27.7, - 27.9, - 28.1, - 28.3, - 28.5, - 28.7, - 28.8, - 29, - 29.1, - 29.1, - 28.8, - 28.5, - 28.3, - 28.2, - 28.2, - 28, - 27.9, - 28, - 28.2, - 28.4, - 28.5, - 28.7, - 28.9, - 29, - 29.2, - 29.4, - 29.6, - 29.7, - 29.9, - 30.3, - 30.7, - 31.1, - 31.5, - 31.9, - 32.5, - 33.2, - 33, - 33.4, - 34.3, - 34.8, - 34.9, - 35, - 35.1, - 35.2, - 35.3, - 36, - 36.8, - 37.3, - 38.1, - 38.6, - 39.2, - 41.2, - 43.4, - 44.2, - 44.8, - 45.8, - 46.8, - 47.5, - 48.2, - 49, - 49.9, - 51, - 51.9, - 53.2, - 53.9, - 54.8, - 55.9, - 57.2, - 58.1, - 60.2, - 65.7, - 68.1, - 69.8, - 73.4, - 76.6, - 77.9, - 78.9, - 79.9, - 80.7, - 80.1, - 79.7, - 79, - 78.3, - 77.7, - 77.5, - 77.1, - 74.8, - 69, - 65.2, - 62.2, - 60.8, - 59.4, - 58.7, - 57.6, - 56.4, - 55.3, - 54.6, - 53.9, - 53, - 52.7, - 51.9, - 51.2, - 51, - 50.4, - 49.8, - 49.6, - 49.8, - 49.4, - 48.5, - 48.5, - 48.4, - 48, - 48.3, - 48, - 47.6, - 47.2, - 47.1, - 46.8, - 46.2, - 45.4, - 44.8, - 44.3, - 42.9, - 41.3, - 40.4, - 40.3, - 40, - 39.7, - 39.4, - 38.9, - 38.1, - 37.2, - 36.8, - 36.6, - 36.5, - 36.5, - 36.4, - 36.3, - 36.2, - 35.8, - 35.5, - 35.3, - 35, - 34.1, - 33.5, - 32.9, - 32.7, - 32.5, - 32.3, - 32.2, - 32.1, - 31.9, - 31.9, - 31.8, - 31.7, - 31.6, - 31.5, - 31.5, - 31.3, - 31.1, - 30.9, - 30.5, - 30.4, - 30.4, - 30.2, - 30.1, - 30, - 29.8, - 29.6, - 29.3, - 29.3, - 29.2, - 29.3, - 29.3, - 29.3, - 29.3, - 29.4, - 29.2, - 29, - 28.7, - 28.5, - 28.3, - 27.7, - 27.3, - 27.1, - 26.9, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.9, - 26.8, - 26.7, - 26.5, - 26.2, - 25.9, - 25.8, - 25.6, - 25.5, - 25, - 24.6, - 24.4, - 24.3, - 24.2, - 24.2, - 24.1, - 24.1, - 24.1, - 24.1, - 23.5, - 22.8, - 21.1, - 19.1, - 17.7, - 16.6, - 15.5, - 14.5, - 21.9, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.4, - 22.8, - 23.8, - 24.2, - 24.3, - 24.3, - 24.2, - 24.2, - 24.6, - 25, - 25.3, - 25.5, - 25.5, - 25.6, - 25.9, - 26.2, - 26.6, - 26.8, - 26.9, - 27.1, - 27.3, - 27.4, - 27.5, - 27.7, - 27.9, - 28, - 28.3, - 28.5, - 28.7, - 28.8, - 28.9, - 29, - 28.9, - 28.5, - 28, - 27.8, - 27.7, - 27.6, - 27.7, - 27.8, - 28, - 28.2, - 28.4, - 28.8, - 29, - 29.1, - 29.3, - 29.6, - 29.8, - 30, - 30.2, - 30.5, - 31, - 31.5, - 31.9, - 32.3, - 32.8, - 33.5, - 34.2, - 34.2, - 33.9, - 34.3, - 34.9, - 35.1, - 35.3, - 35.6, - 35.7, - 35.8, - 36.5, - 37.5, - 38.1, - 39.2, - 39.9, - 41.1, - 43.3, - 44.4, - 44.6, - 45, - 45.9, - 46.9, - 47.6, - 48.2, - 49.1, - 50.1, - 50.9, - 51.8, - 52.9, - 53.9, - 54.9, - 56.1, - 57.4, - 58.6, - 60.1, - 65.1, - 67.7, - 69.3, - 74.6, - 76.8, - 78.1, - 79.2, - 80.4, - 81.8, - 81.5, - 80.7, - 80.1, - 79.5, - 78.9, - 78.7, - 77.9, - 76.3, - 70.9, - 67.2, - 63.7, - 61.8, - 60.8, - 59.7, - 58.5, - 57.2, - 56.2, - 55.5, - 54.5, - 53.8, - 53.2, - 52.5, - 51.9, - 51.6, - 50.9, - 50.7, - 50.8, - 50.5, - 49.6, - 49.1, - 49.1, - 49, - 48.7, - 49.1, - 48.6, - 48.1, - 47.6, - 47.3, - 47, - 46.2, - 45.4, - 44.8, - 44.2, - 42.5, - 41.7, - 41.2, - 40.5, - 39.9, - 39.4, - 39, - 38.6, - 37.7, - 37.3, - 37, - 37, - 36.9, - 36.9, - 36.7, - 36.4, - 36.3, - 36, - 35.7, - 35.4, - 35.1, - 34.3, - 33.7, - 33.2, - 33, - 32.6, - 32.2, - 32, - 31.9, - 31.7, - 31.7, - 31.6, - 31.4, - 31.2, - 31.2, - 31.3, - 31.2, - 31.1, - 30.9, - 30.5, - 30.3, - 30.3, - 30.1, - 30, - 29.9, - 29.7, - 29.5, - 29.3, - 29.3, - 29.2, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.1, - 28.7, - 28.6, - 28.4, - 28.1, - 27.6, - 27.3, - 27, - 26.9, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.7, - 26.7, - 26.7, - 26.7, - 26.8, - 26.8, - 26.8, - 26.7, - 26.5, - 26.2, - 25.9, - 25.8, - 25.6, - 25.5, - 25.1, - 24.6, - 24.4, - 24.3, - 24.1, - 24.2, - 24.1, - 24.1, - 24, - 24, - 23.3, - 22.7, - 20.8, - 19, - 17.6, - 16.4, - 15.5, - 14.6, - 21.8, - 21.9, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.2, - 22.3, - 22.3, - 22.4, - 22.7, - 23.6, - 24.3, - 24.6, - 24.7, - 24.6, - 24.7, - 24.9, - 25.3, - 25.6, - 25.8, - 25.9, - 25.9, - 26, - 26.2, - 26.5, - 26.7, - 26.9, - 27.1, - 27.3, - 27.4, - 27.6, - 27.8, - 27.9, - 28.1, - 28.3, - 28.5, - 28.6, - 28.6, - 28.7, - 28.8, - 28.8, - 28.4, - 27.6, - 27.4, - 27.4, - 27.5, - 27.6, - 27.8, - 27.9, - 28.2, - 28.6, - 29.3, - 29.8, - 29.7, - 29.8, - 30.1, - 30.5, - 30.9, - 31.2, - 31.3, - 31.6, - 32.4, - 32.9, - 33.4, - 33.9, - 34.3, - 34.8, - 34.8, - 34.4, - 34.3, - 34.9, - 35.3, - 35.7, - 36.2, - 36.4, - 36.5, - 37.1, - 38.1, - 39.5, - 42.4, - 42.9, - 43.5, - 44.7, - 45.6, - 45.8, - 45.9, - 46.3, - 47.1, - 47.7, - 48.3, - 49.1, - 50, - 50.8, - 51.7, - 52.8, - 53.9, - 55.1, - 56.3, - 57.8, - 59.4, - 61.6, - 66, - 68.3, - 70.6, - 75.2, - 77.1, - 78.5, - 79.6, - 81.3, - 84.9, - 84.5, - 82.6, - 81.5, - 80.7, - 80.1, - 79.4, - 78.1, - 76.4, - 72.1, - 68, - 65.3, - 63.1, - 61.8, - 60.4, - 59.2, - 58.2, - 57.1, - 56.3, - 55.3, - 54.7, - 53.7, - 53.3, - 52.8, - 52.2, - 51.7, - 51.9, - 51.9, - 51.1, - 50.4, - 50.2, - 49.7, - 49.5, - 49.6, - 49.8, - 49.4, - 48.7, - 48, - 47.5, - 47, - 45.8, - 45.2, - 44.7, - 44.1, - 43.3, - 42.9, - 42, - 40.5, - 39.8, - 39.3, - 39, - 38.6, - 37.9, - 37.8, - 37.8, - 37.6, - 37.3, - 37.3, - 37.1, - 36.6, - 36.4, - 36.2, - 35.9, - 35.4, - 35.1, - 34.6, - 34, - 33.5, - 33.2, - 32.8, - 32.4, - 32.2, - 32, - 31.8, - 31.6, - 31.5, - 31.3, - 31.2, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.4, - 30.3, - 30.2, - 30, - 29.9, - 29.7, - 29.6, - 29.5, - 29.3, - 29.2, - 29.2, - 29.1, - 29.3, - 29.3, - 29.4, - 29.3, - 29.3, - 29.1, - 28.8, - 28.6, - 28.5, - 28.4, - 27.8, - 27.4, - 27, - 26.9, - 26.8, - 26.8, - 26.8, - 26.7, - 26.7, - 26.7, - 26.7, - 26.7, - 26.6, - 26.7, - 26.7, - 26.7, - 26.7, - 26.5, - 26.2, - 25.9, - 25.7, - 25.6, - 25.5, - 25.1, - 24.5, - 24.4, - 24.3, - 24.1, - 24.1, - 24.1, - 24, - 24, - 23.7, - 23.1, - 22.6, - 20.5, - 18.6, - 17.3, - 16.2, - 15.4, - 14.6, - 21.4, - 21.7, - 21.9, - 22, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.7, - 23.3, - 24.2, - 24.6, - 24.8, - 24.8, - 24.9, - 25.2, - 25.5, - 25.7, - 25.9, - 26, - 26.1, - 26.2, - 26.3, - 26.5, - 26.7, - 26.9, - 27.1, - 27.3, - 27.4, - 27.6, - 27.7, - 27.9, - 28.1, - 28.2, - 28.4, - 28.4, - 28.3, - 28.4, - 28.7, - 28.8, - 28.2, - 27.2, - 27.1, - 27.2, - 27.4, - 27.5, - 27.6, - 27.9, - 28.3, - 28.8, - 29.7, - 30.5, - 30.4, - 30.3, - 30.7, - 31.1, - 31.5, - 31.8, - 32, - 32.2, - 33.1, - 33.8, - 34.4, - 34.8, - 35.1, - 35.2, - 35.1, - 34.9, - 34.8, - 35, - 35.4, - 36, - 36.6, - 37, - 37.2, - 37.7, - 38.9, - 40.6, - 43.8, - 44.5, - 44.4, - 44.9, - 46.1, - 46.7, - 46.8, - 46.7, - 47.3, - 47.9, - 48.5, - 49.3, - 50.3, - 51.4, - 52.4, - 53.5, - 54.8, - 56, - 56.9, - 58.2, - 60.4, - 63.2, - 67, - 69.4, - 74.3, - 76.4, - 77.7, - 79, - 80.2, - 82.6, - 88.8, - 89.2, - 87, - 85.1, - 82.4, - 81.1, - 79.8, - 78.4, - 77.1, - 73.7, - 69, - 66.6, - 64.5, - 62.5, - 61.4, - 60.1, - 59, - 57.8, - 57, - 56, - 55.3, - 54.5, - 54.4, - 53.6, - 53.1, - 53, - 52.8, - 52.4, - 51.8, - 51.6, - 51.2, - 50.3, - 50.1, - 50.4, - 50.4, - 49.8, - 48.9, - 47.9, - 47.2, - 46.4, - 45.8, - 45.6, - 45.1, - 44.7, - 44.3, - 43.6, - 42.1, - 40.6, - 39.9, - 39.5, - 39, - 38.8, - 38.7, - 38.8, - 38.5, - 38.1, - 37.8, - 37.5, - 37.2, - 36.7, - 36.5, - 36.2, - 36, - 35.5, - 35.1, - 34.7, - 34.2, - 33.7, - 33.3, - 33, - 32.6, - 32.4, - 32.1, - 32, - 31.8, - 31.6, - 31.4, - 31.3, - 31, - 30.9, - 30.7, - 30.6, - 30.4, - 30.3, - 30.2, - 30.1, - 29.9, - 29.8, - 29.8, - 29.7, - 29.5, - 29.4, - 29.2, - 29.1, - 29.1, - 29.1, - 29.3, - 29.4, - 29.3, - 29.3, - 29.1, - 28.9, - 28.6, - 28.5, - 28.5, - 28, - 27.4, - 27.1, - 26.9, - 26.8, - 26.8, - 26.7, - 26.7, - 26.6, - 26.6, - 26.5, - 26.5, - 26.5, - 26.6, - 26.6, - 26.6, - 26.6, - 26.4, - 26.1, - 25.8, - 25.7, - 25.6, - 25.6, - 25.3, - 24.7, - 24.3, - 24.2, - 24.1, - 24, - 24, - 24, - 23.9, - 23.4, - 22.9, - 22.3, - 20.2, - 18, - 17, - 16.1, - 15.2, - 14.6, - 21.6, - 21.7, - 21.7, - 21.8, - 21.8, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.4, - 22.5, - 22.9, - 23.7, - 24.4, - 24.8, - 24.9, - 25, - 25.2, - 25.5, - 25.7, - 25.9, - 26, - 26.1, - 26.2, - 26.4, - 26.6, - 26.8, - 27, - 27.2, - 27.3, - 27.5, - 27.6, - 27.7, - 27.9, - 28, - 28.2, - 28.3, - 28.2, - 28.1, - 27.9, - 28.2, - 28.2, - 27.6, - 26.9, - 27, - 27.1, - 27.3, - 27.3, - 27.6, - 28, - 28.4, - 28.8, - 29.6, - 30.5, - 30.8, - 30.8, - 31, - 31.4, - 31.9, - 32.2, - 32.6, - 32.9, - 33.5, - 34.2, - 34.7, - 35.2, - 35.5, - 35.6, - 35.6, - 35.4, - 35.4, - 35.2, - 35.4, - 36.3, - 36.9, - 37.4, - 37.9, - 38.3, - 39.5, - 41, - 44.1, - 45.1, - 45.5, - 45.7, - 46.2, - 47, - 47.3, - 47.3, - 47.7, - 48.2, - 48.8, - 49.7, - 50.7, - 51.8, - 53.1, - 54.3, - 55.6, - 57, - 58.2, - 59.5, - 61.3, - 64.8, - 67.1, - 69.5, - 75.7, - 77.6, - 78.5, - 79.6, - 80.9, - 83.7, - 90.8, - 92.3, - 91, - 87.9, - 84.8, - 82.3, - 80.3, - 79.1, - 78.2, - 75.7, - 69.6, - 67.7, - 65.9, - 64.3, - 62.5, - 61, - 59.9, - 58.7, - 57.8, - 56.7, - 55.7, - 55.5, - 55.2, - 54.3, - 54.4, - 54.2, - 53.6, - 53, - 52.4, - 52.2, - 51.6, - 51.1, - 50.9, - 51.1, - 50.9, - 49.7, - 48.6, - 47.7, - 47.1, - 46.6, - 46.3, - 45.9, - 45.6, - 45.2, - 44.8, - 43.9, - 42.1, - 40.7, - 40.2, - 40, - 40, - 40.2, - 40.2, - 39.9, - 39.2, - 38.5, - 38.1, - 37.7, - 37.1, - 36.7, - 36.5, - 36.2, - 36, - 35.4, - 35, - 34.3, - 33.9, - 33.6, - 33.3, - 33.1, - 32.8, - 32.4, - 32.2, - 32.1, - 31.9, - 31.7, - 31.5, - 31.3, - 31.1, - 31, - 30.8, - 30.6, - 30.5, - 30.3, - 30.1, - 30, - 30, - 29.9, - 29.8, - 29.7, - 29.5, - 29.4, - 29.2, - 29.1, - 29, - 29, - 29.2, - 29.3, - 29.3, - 29.3, - 29, - 28.8, - 28.6, - 28.5, - 28.4, - 28.2, - 27.6, - 27.2, - 26.9, - 26.8, - 26.7, - 26.6, - 26.6, - 26.5, - 26.4, - 26.4, - 26.3, - 26.3, - 26.4, - 26.4, - 26.5, - 26.4, - 26.2, - 25.9, - 25.7, - 25.6, - 25.6, - 25.5, - 25.4, - 24.8, - 24.4, - 24.2, - 24, - 24, - 24, - 23.9, - 23.7, - 23.2, - 22.7, - 22, - 19.7, - 17.9, - 16.8, - 16, - 15.3, - 14.6, - 21.9, - 21.9, - 21.8, - 21.9, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.4, - 22.5, - 22.7, - 23.3, - 24, - 24.6, - 24.8, - 24.9, - 25.1, - 25.4, - 25.5, - 25.6, - 25.8, - 26, - 26.2, - 26.5, - 26.7, - 26.9, - 27.1, - 27.2, - 27.3, - 27.5, - 27.6, - 27.7, - 27.8, - 28, - 28.1, - 28.1, - 28, - 27.8, - 27.5, - 27.4, - 27.4, - 27, - 26.7, - 26.9, - 27, - 27, - 27.3, - 27.7, - 28.1, - 28.4, - 28.9, - 29.5, - 30.4, - 30.8, - 30.9, - 31.3, - 31.6, - 31.9, - 32.3, - 32.8, - 33.4, - 33.8, - 34.2, - 34.6, - 35.1, - 35.6, - 35.9, - 36.3, - 36.2, - 36.1, - 36, - 35.6, - 36.3, - 37, - 37.8, - 38.4, - 39.2, - 40.1, - 41.9, - 44.4, - 45.4, - 46.2, - 46.7, - 47, - 47.3, - 47.8, - 47.9, - 48.2, - 48.8, - 49.3, - 50.1, - 51.1, - 52.1, - 53.4, - 54.5, - 55.9, - 57.3, - 58.8, - 60.9, - 62.6, - 66.3, - 67.7, - 71.4, - 76.2, - 78.2, - 79.3, - 80.4, - 81.6, - 85.2, - 90.8, - 93.1, - 93.1, - 91.9, - 90.1, - 86.4, - 81.3, - 80.1, - 79, - 77.2, - 71.1, - 68.6, - 67.3, - 66.4, - 64.2, - 61.8, - 60.7, - 59.6, - 58.6, - 57.5, - 56.5, - 56.6, - 55.9, - 55.4, - 55.6, - 55.1, - 54.3, - 53.6, - 53.1, - 52.8, - 52.4, - 51.9, - 51.6, - 51.4, - 50.8, - 49.6, - 48.8, - 47.9, - 47.8, - 47.6, - 47.1, - 46.5, - 45.9, - 45.5, - 44.9, - 44.2, - 43.1, - 41.8, - 41.6, - 41.5, - 41.6, - 41.9, - 41.5, - 40.7, - 39.8, - 39, - 38.3, - 37.9, - 37.3, - 36.7, - 36.5, - 36.2, - 35.8, - 35.4, - 35, - 34.2, - 33.7, - 33.4, - 33.1, - 32.9, - 32.8, - 32.5, - 32.3, - 32.1, - 31.9, - 31.7, - 31.5, - 31.4, - 31.2, - 31.1, - 30.9, - 30.7, - 30.5, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.5, - 29.4, - 29.2, - 29.1, - 29, - 28.9, - 29.1, - 29.3, - 29.3, - 29.2, - 28.9, - 28.6, - 28.5, - 28.5, - 28.5, - 28.4, - 27.8, - 27.4, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26.2, - 26.2, - 26.2, - 26.2, - 26.2, - 25.9, - 25.7, - 25.6, - 25.5, - 25.5, - 25.4, - 25.2, - 24.7, - 24.3, - 24.2, - 24, - 24, - 23.9, - 23.8, - 23.4, - 22.9, - 22.5, - 21, - 19.3, - 17.6, - 16.6, - 16, - 15.3, - 14.7, - 22, - 22.1, - 22, - 22, - 22, - 22.1, - 22.2, - 22.4, - 22.5, - 22.6, - 22.6, - 22.6, - 22.6, - 23, - 23.5, - 24.1, - 24.5, - 24.7, - 24.9, - 25, - 25.1, - 25.3, - 25.7, - 26, - 26.2, - 26.4, - 26.7, - 26.9, - 27, - 27.2, - 27.3, - 27.4, - 27.5, - 27.7, - 27.8, - 27.9, - 27.9, - 27.9, - 27.7, - 27.5, - 27.1, - 26.9, - 26.8, - 26.6, - 26.6, - 26.8, - 26.8, - 26.9, - 27.3, - 27.7, - 28, - 28.4, - 28.8, - 29.3, - 30, - 30.5, - 30.8, - 31.1, - 31.4, - 31.7, - 32.2, - 32.8, - 33.6, - 34, - 34.2, - 34.5, - 35, - 35.6, - 36.1, - 36.6, - 36.8, - 36.7, - 36.6, - 36.5, - 36.5, - 37, - 38.1, - 39.1, - 40, - 41, - 43, - 44.9, - 45.7, - 46.6, - 47.3, - 47.8, - 48, - 48.4, - 48.6, - 48.9, - 49.5, - 50, - 50.7, - 51.8, - 52.7, - 53.8, - 54.9, - 56.1, - 57.5, - 59.3, - 62.1, - 64.7, - 68.3, - 71.1, - 75.2, - 77.1, - 78.5, - 79.8, - 81, - 83.2, - 86.5, - 91.2, - 93.3, - 93.5, - 93.4, - 92.9, - 90.4, - 83.8, - 81, - 79.5, - 78.1, - 74.7, - 70.7, - 68.8, - 67.5, - 66.1, - 64, - 62.7, - 61.2, - 59.6, - 58.5, - 57.8, - 57.5, - 56.7, - 56.8, - 56.7, - 56, - 55.1, - 54.4, - 54, - 53.7, - 53.2, - 52.7, - 52.2, - 51.6, - 50.8, - 49.4, - 49, - 49, - 49, - 48.5, - 47.7, - 46.9, - 46.1, - 45.6, - 45.3, - 44.8, - 44.8, - 43.6, - 43.8, - 43.8, - 43.5, - 43, - 42.3, - 41.4, - 40.3, - 39.4, - 38.6, - 38.1, - 37.5, - 36.8, - 36.5, - 36.1, - 35.8, - 35.5, - 35.1, - 34.4, - 33.6, - 33.2, - 33, - 32.8, - 32.6, - 32.4, - 32.2, - 32.1, - 31.9, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.9, - 30.7, - 30.5, - 30.3, - 30.8, - 30.7, - 30.2, - 29.9, - 29.7, - 29.6, - 29.5, - 29.4, - 29.2, - 29, - 29, - 28.9, - 28.9, - 29.2, - 29.2, - 29.1, - 28.8, - 28.4, - 28.4, - 28.4, - 28.3, - 28.4, - 27.9, - 27.4, - 27, - 26.8, - 26.6, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 26, - 26, - 26, - 26, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.4, - 25.4, - 25.3, - 25, - 24.5, - 24.2, - 24.1, - 24, - 23.9, - 23.8, - 23.8, - 23.3, - 22.7, - 22.3, - 20.1, - 19, - 17.5, - 16.7, - 16, - 15.3, - 14.7, - 22.1, - 22.1, - 22, - 22, - 22.1, - 22.1, - 22.3, - 22.6, - 22.9, - 23.2, - 23.2, - 23.2, - 23, - 23, - 23.1, - 23.6, - 23.9, - 24.2, - 24.5, - 24.6, - 24.7, - 25, - 25.5, - 25.8, - 26.1, - 26.3, - 26.5, - 26.8, - 26.9, - 27.1, - 27.3, - 27.4, - 27.5, - 27.6, - 27.7, - 27.7, - 27.6, - 27.5, - 27.3, - 27.1, - 26.8, - 26.6, - 26.5, - 26.4, - 26.5, - 26.6, - 26.6, - 26.8, - 27.1, - 27.4, - 27.6, - 28.1, - 28.4, - 28.8, - 29.2, - 29.8, - 30.5, - 30.9, - 31.2, - 31.5, - 32, - 32.6, - 33.3, - 33.8, - 34.1, - 34.5, - 35, - 35.6, - 36.1, - 36.8, - 36.9, - 36.9, - 37.3, - 37.5, - 37.1, - 37.1, - 38.3, - 39.6, - 40.8, - 42.2, - 44.1, - 45.4, - 46.3, - 47, - 47.6, - 48.3, - 48.8, - 49.3, - 49.5, - 49.8, - 50.2, - 50.7, - 51.3, - 52.6, - 53.4, - 54.4, - 55.6, - 56.8, - 58.1, - 60.4, - 64.1, - 68.5, - 73.1, - 75.7, - 76.9, - 77.9, - 78.9, - 80.1, - 81.4, - 83.8, - 86.8, - 91.7, - 93.2, - 93.4, - 93.7, - 93.6, - 92.9, - 90.3, - 84.4, - 80.5, - 78.9, - 77.5, - 72.7, - 69.8, - 68.5, - 67.7, - 66.9, - 66, - 64, - 61.3, - 59.4, - 59, - 58.3, - 58, - 58.2, - 57.8, - 56.9, - 55.9, - 55.2, - 54.9, - 54.6, - 54.1, - 53.5, - 52.9, - 51.9, - 50.6, - 49.5, - 49.8, - 50.4, - 50.2, - 49.3, - 48.2, - 47.4, - 46.6, - 46.1, - 45.7, - 45.5, - 45.7, - 45.5, - 45.3, - 45, - 44.6, - 43.5, - 42.7, - 42, - 40.7, - 39.7, - 38.9, - 38.2, - 37.7, - 36.8, - 36.5, - 36.2, - 36, - 35.6, - 35.2, - 34.7, - 33.7, - 33.3, - 33.3, - 33.1, - 32.9, - 32.6, - 32.3, - 32.1, - 31.9, - 31.7, - 31.5, - 31.4, - 31.2, - 31, - 30.8, - 30.6, - 30.5, - 30.4, - 32.3, - 32.8, - 32.2, - 31.6, - 29.8, - 29.6, - 29.5, - 29.4, - 29.4, - 29, - 28.9, - 28.8, - 28.8, - 29, - 29.1, - 28.9, - 28.6, - 28.2, - 28.2, - 28.3, - 28.3, - 28.2, - 27.7, - 27.1, - 26.9, - 26.7, - 26.6, - 26.5, - 26.5, - 26.4, - 26.3, - 26.1, - 25.9, - 25.8, - 25.8, - 25.8, - 25.8, - 25.7, - 25.5, - 25.5, - 25.4, - 25.3, - 25.3, - 25.1, - 24.7, - 24.3, - 24.2, - 24, - 23.9, - 23.9, - 23.8, - 23.7, - 23.1, - 22.6, - 22, - 19.9, - 18.7, - 17.5, - 16.8, - 16.1, - 15.5, - 14.9, - 22, - 22.1, - 22, - 22, - 22, - 22.1, - 22.4, - 22.9, - 23.3, - 23.7, - 23.8, - 23.8, - 23.7, - 23.6, - 23.4, - 23.5, - 23.7, - 23.9, - 24.2, - 24.4, - 24.7, - 25, - 25.3, - 25.6, - 25.9, - 26.2, - 26.4, - 26.7, - 26.9, - 27.1, - 27.2, - 27.4, - 27.4, - 27.5, - 27.5, - 27.5, - 27.3, - 27.1, - 26.8, - 26.7, - 26.6, - 26.5, - 26.3, - 26.3, - 26.3, - 26.4, - 26.5, - 26.6, - 26.9, - 27.1, - 27.3, - 27.8, - 28.1, - 28.5, - 29, - 29.8, - 30.5, - 30.9, - 31.4, - 31.6, - 31.8, - 32.2, - 32.7, - 33.3, - 33.9, - 34.5, - 35.1, - 35.7, - 36.1, - 36.5, - 36.8, - 37.2, - 37.6, - 37.9, - 37.7, - 37.5, - 38.4, - 39.8, - 41.5, - 43.9, - 44.8, - 45.8, - 46.7, - 47.3, - 47.8, - 48.6, - 49.4, - 50.1, - 50.5, - 50.8, - 50.9, - 51.3, - 52.2, - 53.3, - 54.2, - 55.3, - 56.4, - 57.6, - 59, - 61.3, - 65.4, - 71.2, - 76.4, - 77.6, - 78.2, - 78.8, - 79.6, - 80.8, - 81.8, - 84, - 87.3, - 91.9, - 93, - 93.3, - 93.7, - 93.8, - 93.7, - 93.1, - 89.8, - 83.9, - 79.7, - 78.6, - 74.6, - 71.7, - 70.3, - 69, - 68.3, - 67.2, - 65.3, - 62.7, - 60.9, - 60.3, - 59.7, - 59.5, - 59.5, - 58.8, - 57.8, - 56.7, - 56.2, - 55.8, - 55.2, - 54.5, - 53.8, - 52.9, - 51.8, - 51, - 50.5, - 51, - 51.2, - 50.8, - 50, - 48.8, - 48.1, - 47.1, - 46.6, - 46.6, - 46.6, - 46.6, - 46.4, - 46, - 45.7, - 45.6, - 44, - 43.1, - 42.5, - 41.2, - 39.9, - 39, - 38.3, - 37.7, - 36.9, - 36.8, - 36.5, - 36.3, - 35.9, - 35.4, - 34.9, - 33.9, - 33.6, - 33.9, - 33.7, - 33.4, - 32.9, - 32.4, - 32.1, - 31.9, - 31.6, - 31.4, - 31.3, - 31.1, - 31, - 30.8, - 30.7, - 30.7, - 30.8, - 32.3, - 33.5, - 33.6, - 33.3, - 30.1, - 29.7, - 29.5, - 29.4, - 29.3, - 29, - 28.9, - 28.8, - 28.7, - 28.8, - 28.8, - 28.7, - 28.5, - 28.2, - 27.9, - 28.1, - 28.3, - 28.1, - 27.5, - 26.9, - 26.7, - 26.6, - 26.5, - 26.5, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 25.8, - 25.7, - 25.6, - 25.6, - 25.5, - 25.5, - 25.4, - 25.3, - 25.2, - 25.1, - 24.7, - 24.4, - 24.2, - 24.1, - 24, - 23.9, - 23.9, - 23.8, - 23.7, - 23.1, - 22.5, - 21.5, - 19.7, - 18.6, - 17.5, - 16.8, - 16.1, - 15.5, - 15, - 22, - 22, - 21.9, - 21.9, - 21.9, - 22, - 22.5, - 23.4, - 23.8, - 24, - 24.2, - 24.2, - 24.2, - 24.1, - 24.1, - 24.1, - 24.2, - 24.4, - 24.6, - 24.8, - 25.1, - 25.4, - 25.6, - 25.8, - 26.1, - 26.3, - 26.6, - 26.7, - 27, - 27.1, - 27.2, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27, - 26.7, - 26.5, - 26.4, - 26.4, - 26.3, - 26.3, - 26.2, - 26.2, - 26.2, - 26.4, - 26.7, - 27.1, - 27.5, - 27.8, - 28.3, - 28.7, - 29.3, - 29.8, - 30.5, - 30.9, - 31.3, - 31.7, - 31.9, - 31.9, - 32.2, - 32.4, - 33.2, - 34, - 34.7, - 35.2, - 35.7, - 36.1, - 36.5, - 37, - 37.3, - 37.5, - 37.8, - 38.1, - 37.9, - 38.5, - 39.8, - 41.8, - 44.4, - 45.1, - 46, - 46.9, - 47.5, - 48.1, - 48.7, - 49.8, - 50.6, - 51.4, - 51.9, - 51.9, - 52.3, - 53.1, - 54.3, - 55.3, - 56.3, - 57.4, - 58.6, - 59.7, - 62.5, - 66.4, - 71.3, - 77, - 78.3, - 79.1, - 80, - 80.8, - 82.1, - 83.7, - 85.6, - 88, - 91.9, - 92.8, - 93.3, - 93.7, - 94, - 93.9, - 93.7, - 91.8, - 86.6, - 80.5, - 79.4, - 77.9, - 76.3, - 75.2, - 70.3, - 68.9, - 67.3, - 65.5, - 63.6, - 62.5, - 61.9, - 61.8, - 61.1, - 60.6, - 59.7, - 58.6, - 57.8, - 57.3, - 56.6, - 55.7, - 54.9, - 53.7, - 52.5, - 51.8, - 51.7, - 51.6, - 52.2, - 51.9, - 51.3, - 50.6, - 49.6, - 48.7, - 47.7, - 47, - 47.7, - 47.8, - 47.5, - 47.1, - 46.7, - 46.3, - 45.5, - 44.5, - 43.4, - 42.7, - 41.3, - 39.9, - 38.9, - 38.2, - 37.7, - 37.3, - 37, - 36.7, - 36.4, - 36, - 35.5, - 35.1, - 34.3, - 34.4, - 34.7, - 34.4, - 33.8, - 33.2, - 32.5, - 32.1, - 31.9, - 31.7, - 31.6, - 31.4, - 31.3, - 31.1, - 31.1, - 31.1, - 30.9, - 30.9, - 31.3, - 32.7, - 33, - 32.2, - 30, - 29.7, - 29.5, - 29.3, - 29.2, - 29, - 28.8, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.4, - 28.1, - 27.6, - 27.5, - 27.9, - 27.9, - 27.4, - 26.8, - 26.6, - 26.5, - 26.5, - 26.4, - 26.4, - 26.4, - 26.3, - 26.2, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.4, - 25.4, - 25.3, - 25.2, - 25.1, - 24.7, - 24.4, - 24.2, - 24.2, - 24, - 23.9, - 23.8, - 23.8, - 23.8, - 23.6, - 23, - 22.4, - 20.6, - 19.4, - 18.4, - 17.4, - 16.8, - 16.2, - 15.6, - 15, - 21.9, - 21.8, - 21.8, - 21.8, - 21.9, - 22.1, - 22.8, - 23.7, - 24.1, - 24.2, - 24.4, - 24.5, - 24.5, - 24.5, - 24.5, - 24.6, - 24.7, - 24.9, - 25.1, - 25.2, - 25.4, - 25.7, - 25.9, - 26.1, - 26.3, - 26.5, - 26.7, - 26.9, - 27, - 27.1, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 26.9, - 26.7, - 26.4, - 26.2, - 26.1, - 26.1, - 26, - 26, - 26.1, - 26, - 26.1, - 26.6, - 27, - 27.4, - 27.9, - 28.4, - 28.9, - 29.5, - 30, - 30.5, - 30.8, - 31.2, - 31.6, - 32, - 32.3, - 32.6, - 32.8, - 33, - 33.4, - 33.9, - 34.6, - 35, - 35.3, - 35.9, - 36.5, - 37.1, - 37.3, - 37.6, - 38.1, - 38.7, - 38.8, - 38.7, - 39.7, - 42.3, - 44.2, - 45.1, - 46, - 47.1, - 47.5, - 48.2, - 48.9, - 50.3, - 51.3, - 52.5, - 53.1, - 53.2, - 53.3, - 53.9, - 54.8, - 55.9, - 57, - 58.2, - 59.3, - 60.3, - 63.1, - 66.5, - 70.3, - 76.7, - 78.4, - 79.5, - 80.9, - 81.8, - 83.5, - 86.3, - 87.2, - 88.8, - 91.5, - 92.6, - 93.3, - 93.9, - 94.1, - 94, - 93.6, - 92.8, - 88.8, - 81.4, - 80.3, - 79, - 77.9, - 76.6, - 70.5, - 68.8, - 67.4, - 65.9, - 64.8, - 64.2, - 63.4, - 63.2, - 62.4, - 61.6, - 60.8, - 59.5, - 58.8, - 58.1, - 57.2, - 56.1, - 55.1, - 53.8, - 52.6, - 52.3, - 52.4, - 52.5, - 52.9, - 52.4, - 51.9, - 51.5, - 50.6, - 49.4, - 48.1, - 47.8, - 48.3, - 48.7, - 48.4, - 48, - 47.5, - 47.1, - 46.7, - 45.4, - 43.7, - 42.7, - 41.2, - 39.9, - 38.9, - 38.3, - 38.1, - 37.5, - 37.1, - 36.6, - 36.2, - 35.8, - 35.4, - 35.2, - 35.2, - 35.3, - 35.2, - 34.7, - 33.9, - 33.2, - 32.5, - 32.2, - 32.1, - 31.9, - 31.8, - 31.6, - 31.5, - 31.4, - 31.4, - 31.2, - 31.1, - 30.9, - 30.7, - 30.8, - 31.1, - 30.3, - 30, - 29.8, - 29.5, - 29.3, - 29.1, - 29, - 28.8, - 28.7, - 28.6, - 28.6, - 28.6, - 28.5, - 28.1, - 27.3, - 26.8, - 26.9, - 27.3, - 27.3, - 27.1, - 26.8, - 26.4, - 26.4, - 26.4, - 26.4, - 26.4, - 26.4, - 26.3, - 26.2, - 26, - 25.9, - 25.8, - 25.6, - 25.5, - 25.4, - 25.3, - 25.2, - 25.2, - 24.9, - 24.5, - 24.1, - 24.1, - 24, - 24, - 23.8, - 23.8, - 23.8, - 23.8, - 23.4, - 22.8, - 22.2, - 19.9, - 19.1, - 18.2, - 17.4, - 16.8, - 16.3, - 15.6, - 15, - 21.8, - 21.7, - 21.7, - 21.7, - 21.8, - 22.1, - 23, - 23.8, - 24.2, - 24.4, - 24.5, - 24.6, - 24.7, - 24.7, - 24.8, - 24.9, - 25, - 25.2, - 25.4, - 25.5, - 25.7, - 25.9, - 26.1, - 26.3, - 26.5, - 26.7, - 26.8, - 26.9, - 27, - 27.1, - 27.1, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.3, - 26.1, - 25.8, - 25.7, - 25.8, - 25.8, - 25.8, - 25.9, - 25.9, - 26.2, - 26.6, - 27.1, - 27.6, - 28.1, - 28.6, - 29.1, - 29.7, - 30.3, - 30.7, - 31, - 31.5, - 31.9, - 32.3, - 32.8, - 33.2, - 33.5, - 33.8, - 34.1, - 34, - 34.4, - 34.8, - 35.2, - 35.9, - 36.4, - 36.9, - 37.3, - 37.7, - 38.3, - 39.1, - 39.7, - 39.9, - 40.8, - 43.1, - 44.2, - 45.1, - 46.1, - 47.4, - 47.9, - 48.7, - 49.4, - 50.6, - 51.7, - 52.8, - 53.7, - 54.2, - 54.4, - 54.7, - 55.4, - 56.5, - 57.6, - 58.9, - 59.9, - 62, - 65, - 67, - 69.4, - 76.1, - 78.5, - 79.9, - 81, - 82.3, - 84.9, - 87, - 88.3, - 90.3, - 91.6, - 92.5, - 93.4, - 94.1, - 94.4, - 94, - 93.5, - 91.9, - 88.1, - 82.1, - 80.9, - 79.5, - 78.3, - 76.7, - 72.9, - 70.1, - 68.7, - 67.2, - 66.5, - 65.7, - 64.6, - 64.3, - 63.5, - 62.6, - 61.7, - 60.6, - 59.6, - 58.6, - 57.5, - 56.2, - 54.9, - 53.9, - 53.3, - 53.5, - 53.3, - 53.2, - 53.4, - 52.5, - 52, - 51.5, - 50.7, - 49.8, - 49.1, - 48.7, - 48.5, - 49.3, - 49.2, - 48.8, - 48.9, - 49.5, - 49.6, - 47.8, - 44.7, - 43, - 41.3, - 39.9, - 39, - 38.7, - 38.5, - 37.8, - 37.3, - 36.7, - 36.2, - 35.9, - 35.8, - 35.9, - 36, - 35.9, - 35.4, - 34.8, - 33.9, - 33.1, - 32.6, - 32.4, - 32.3, - 32.1, - 32, - 31.9, - 31.7, - 31.7, - 31.5, - 31.4, - 31.2, - 30.9, - 30.7, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.5, - 29.4, - 29.2, - 29, - 28.8, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 27.8, - 27, - 26.6, - 26.5, - 26.8, - 26.8, - 26.7, - 26.6, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.5, - 25.3, - 25.3, - 25.2, - 25.1, - 24.6, - 24.2, - 24, - 23.9, - 23.9, - 23.9, - 23.8, - 23.8, - 23.7, - 23.7, - 23.3, - 22.6, - 21.3, - 19.5, - 18.8, - 17.9, - 17.1, - 16.6, - 15.9, - 15.3, - 14.6, - 21.7, - 21.7, - 21.6, - 21.7, - 21.8, - 22.1, - 23, - 23.9, - 24.3, - 24.5, - 24.5, - 24.6, - 24.8, - 24.9, - 25, - 25.1, - 25.2, - 25.4, - 25.5, - 25.7, - 25.8, - 26, - 26.2, - 26.4, - 26.5, - 26.7, - 26.8, - 26.9, - 26.9, - 26.9, - 26.9, - 26.9, - 26.8, - 26.6, - 26.4, - 26.3, - 26.1, - 25.8, - 25.6, - 25.5, - 25.6, - 25.6, - 25.7, - 25.7, - 25.8, - 26.1, - 26.5, - 26.9, - 27.3, - 27.8, - 28.2, - 28.5, - 29.2, - 30.1, - 30.7, - 31, - 31.4, - 31.9, - 32.4, - 33, - 33.4, - 34, - 34.4, - 34.7, - 34.6, - 34.6, - 35, - 35.5, - 36, - 36.5, - 36.8, - 37.3, - 37.8, - 38.4, - 39.1, - 40.1, - 40.9, - 41.7, - 43.5, - 44.7, - 45.7, - 47.1, - 48, - 48.4, - 48.9, - 49.8, - 50.9, - 51.9, - 53, - 53.9, - 54.7, - 55.1, - 55.5, - 56.1, - 57.2, - 58.2, - 59.4, - 60.5, - 64.6, - 66.6, - 67.7, - 69.3, - 75.7, - 78.4, - 79.7, - 81, - 83.2, - 86.3, - 87.9, - 90.2, - 91.4, - 92.4, - 93.2, - 93.8, - 94.5, - 94.5, - 94, - 93.3, - 89.2, - 86.1, - 82.8, - 81.6, - 80.1, - 78.8, - 77.7, - 76.8, - 74.6, - 71.4, - 68.9, - 68, - 66.9, - 66, - 65.5, - 64.7, - 63.6, - 62.4, - 61.3, - 60.1, - 58.7, - 57.4, - 56.2, - 55, - 54.6, - 55, - 55, - 54.2, - 53.7, - 53.3, - 52.6, - 52.2, - 51.4, - 50.7, - 50.2, - 49.7, - 49.1, - 49.3, - 49.9, - 49.7, - 49.4, - 50, - 50.8, - 50.5, - 48.3, - 45.5, - 43.5, - 42.1, - 40.5, - 39.8, - 39.7, - 39, - 38.2, - 37.7, - 37, - 36.5, - 36.2, - 36.4, - 36.7, - 36.7, - 36.3, - 35.4, - 34.7, - 33.8, - 33.2, - 32.9, - 32.6, - 32.5, - 32.3, - 32.2, - 32.1, - 31.9, - 31.8, - 31.6, - 31.5, - 31.2, - 31, - 30.7, - 30.5, - 30.3, - 30.2, - 29.9, - 29.8, - 29.5, - 29.4, - 29.2, - 29.1, - 29, - 28.7, - 28.6, - 28.5, - 28.4, - 28, - 27.5, - 26.8, - 26.4, - 26.3, - 26.4, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.2, - 26.2, - 26.2, - 26.2, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 25.4, - 25.3, - 25.3, - 25.2, - 25.1, - 24.6, - 24.1, - 23.9, - 23.9, - 23.8, - 23.8, - 23.7, - 23.7, - 23.7, - 23.6, - 23.1, - 22.5, - 20.3, - 19.2, - 18.3, - 17.5, - 16.8, - 16.1, - 15.5, - 14.9, - 14.2, - 21.6, - 21.6, - 21.5, - 21.6, - 21.7, - 22, - 22.9, - 23.9, - 24.3, - 24.5, - 24.5, - 24.7, - 24.8, - 24.9, - 25.1, - 25.2, - 25.2, - 25.4, - 25.6, - 25.7, - 25.9, - 26, - 26.2, - 26.4, - 26.5, - 26.6, - 26.7, - 26.8, - 26.8, - 26.8, - 26.7, - 26.6, - 26.5, - 26.2, - 26, - 25.9, - 25.8, - 25.4, - 25.3, - 25.4, - 25.4, - 25.5, - 25.6, - 25.9, - 26.1, - 26.2, - 26.5, - 26.9, - 27.1, - 27.4, - 27.8, - 28.2, - 28.9, - 29.7, - 30.5, - 30.8, - 31.2, - 31.7, - 32.2, - 33, - 33.5, - 34.2, - 34.8, - 35.1, - 35.3, - 35.1, - 35.3, - 35.6, - 35.9, - 36.4, - 36.8, - 37.3, - 37.8, - 38.4, - 39.3, - 40.5, - 41.8, - 42.8, - 44.2, - 45.1, - 46.3, - 48.2, - 48.9, - 49.4, - 49.8, - 50.2, - 51, - 52, - 53.3, - 54.3, - 55.3, - 56.1, - 56.4, - 56.8, - 57.7, - 58.9, - 60.3, - 62, - 65.5, - 67.2, - 68.4, - 69.6, - 75.7, - 78.3, - 79.6, - 81, - 83.2, - 86.9, - 88.5, - 90.9, - 92.1, - 93.2, - 93.9, - 94.4, - 94.7, - 94.6, - 94.1, - 93.3, - 89.2, - 86.5, - 84, - 82.3, - 81, - 79.8, - 78.9, - 78.1, - 76.6, - 73.6, - 70.4, - 69.1, - 67.8, - 67.2, - 66.9, - 65.9, - 64.6, - 63.2, - 62, - 60.8, - 59.1, - 58, - 57, - 55.8, - 55.8, - 55.9, - 55.6, - 54.8, - 54, - 53.4, - 53.3, - 52.8, - 51.7, - 51, - 50.2, - 49.7, - 49.9, - 50.3, - 50.4, - 50.1, - 49.5, - 50, - 50.5, - 50.5, - 47.3, - 45.2, - 43.8, - 43.3, - 42.4, - 41.4, - 40.6, - 39.5, - 38.6, - 38.1, - 37.3, - 36.8, - 36.8, - 37.1, - 37.2, - 37.2, - 36.6, - 35.4, - 34.7, - 33.9, - 33.6, - 33.3, - 32.9, - 32.7, - 32.6, - 32.4, - 32.2, - 32.1, - 32, - 31.8, - 31.5, - 31.3, - 31.1, - 30.9, - 30.7, - 30.4, - 30.2, - 29.9, - 29.7, - 29.5, - 29.3, - 29.2, - 29.1, - 28.9, - 28.7, - 28.6, - 28.6, - 28.6, - 28.1, - 27.2, - 26.6, - 26.3, - 26.2, - 26.1, - 26.1, - 26.2, - 26.2, - 26.1, - 26, - 26, - 26.1, - 26.1, - 26, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 25.4, - 25.3, - 25.2, - 25.2, - 25.2, - 25.1, - 24.9, - 24.2, - 23.9, - 23.9, - 23.8, - 23.7, - 23.7, - 23.7, - 23.7, - 23.5, - 22.9, - 21.8, - 19.6, - 18.5, - 17.4, - 16.8, - 16.3, - 15.7, - 15.1, - 14.5, - 13.9, - 21.5, - 21.5, - 21.5, - 21.5, - 21.7, - 21.9, - 22.7, - 23.7, - 24.2, - 24.3, - 24.4, - 24.7, - 24.8, - 25, - 25.1, - 25.2, - 25.3, - 25.5, - 25.6, - 25.8, - 25.9, - 26, - 26.2, - 26.3, - 26.4, - 26.5, - 26.6, - 26.6, - 26.6, - 26.5, - 26.4, - 26.3, - 26.1, - 25.8, - 25.7, - 25.5, - 25.3, - 25, - 25.1, - 25.2, - 25.3, - 25.4, - 25.7, - 26.1, - 26.4, - 26.5, - 26.8, - 27.1, - 27.4, - 27.8, - 28.4, - 29.1, - 29.6, - 30.1, - 30.6, - 31, - 31.4, - 31.7, - 32.1, - 33, - 33.6, - 34.3, - 35, - 35.5, - 35.7, - 35.5, - 35.5, - 35.8, - 36, - 36.8, - 37.1, - 37.6, - 38.1, - 38.7, - 39.5, - 41, - 42.8, - 44.1, - 44.8, - 45.6, - 46.9, - 48.2, - 49.2, - 50, - 50.8, - 51.4, - 51.7, - 52.2, - 53.5, - 54.9, - 55.8, - 56.7, - 57.4, - 57.9, - 58.7, - 60.1, - 61.7, - 64.2, - 66.1, - 67.7, - 68.9, - 70.9, - 76, - 78.3, - 79.9, - 81.1, - 82.9, - 86.9, - 89.3, - 91.1, - 92.3, - 93.3, - 94.3, - 94.7, - 94.8, - 94.7, - 94.1, - 93.3, - 90.4, - 86.8, - 84.9, - 83.4, - 82.2, - 80.9, - 79.8, - 78.6, - 77.2, - 74.8, - 71.4, - 70, - 68.9, - 68.7, - 67.9, - 66.7, - 65.4, - 64.1, - 63.1, - 62, - 60.4, - 59.1, - 58, - 56.9, - 56.5, - 56.5, - 55.9, - 55.1, - 54.3, - 54.1, - 53.9, - 52.9, - 51.6, - 51, - 50.5, - 50.5, - 50.7, - 51, - 50.7, - 50.2, - 49.3, - 49, - 48.2, - 47.6, - 46, - 45.3, - 44.2, - 44.4, - 43.9, - 42.5, - 41.3, - 40.1, - 39.2, - 38.7, - 37.9, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 36.6, - 35.6, - 35, - 34.5, - 34.1, - 33.8, - 33.2, - 33, - 32.8, - 32.5, - 32.4, - 32.2, - 32, - 31.8, - 31.5, - 31.3, - 31.1, - 30.9, - 30.7, - 30.5, - 30.2, - 29.9, - 29.8, - 29.6, - 29.4, - 29.3, - 29.1, - 28.9, - 28.7, - 28.6, - 28.6, - 28.6, - 28.1, - 27.2, - 26.7, - 26.4, - 26.3, - 26.3, - 26.1, - 25.9, - 26, - 26, - 25.9, - 25.9, - 25.9, - 25.9, - 25.9, - 25.8, - 25.8, - 25.7, - 25.5, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 24.9, - 24.3, - 23.9, - 23.8, - 23.7, - 23.7, - 23.6, - 23.6, - 23.6, - 23.2, - 22.6, - 20.8, - 19.1, - 17.7, - 16.7, - 16.1, - 15.5, - 14.9, - 14.5, - 14, - 13.4, - 21.2, - 21.3, - 21.4, - 21.5, - 21.7, - 21.9, - 22.4, - 23.3, - 23.9, - 24, - 24.3, - 24.6, - 24.8, - 25, - 25.2, - 25.3, - 25.4, - 25.5, - 25.7, - 25.8, - 25.9, - 26, - 26.1, - 26.2, - 26.3, - 26.4, - 26.4, - 26.4, - 26.3, - 26.2, - 26, - 25.8, - 25.7, - 25.4, - 25.4, - 25.1, - 24.9, - 24.8, - 25, - 25.1, - 25.2, - 25.5, - 25.8, - 26.2, - 26.5, - 26.7, - 27, - 27.5, - 27.9, - 28.4, - 29, - 29.6, - 30.1, - 30.4, - 30.7, - 31.3, - 32, - 32.4, - 32.4, - 33, - 33.6, - 34.4, - 35.2, - 35.7, - 35.8, - 35.8, - 35.7, - 35.9, - 36.1, - 37.5, - 37.6, - 37.9, - 38.6, - 39.3, - 40, - 41.4, - 43.7, - 44.9, - 45.6, - 46, - 46.9, - 48.2, - 49.1, - 50, - 50.8, - 51.9, - 52.6, - 53.3, - 54, - 55.3, - 56.3, - 57.4, - 58.3, - 59.1, - 59.7, - 60.8, - 62.4, - 64.5, - 66.1, - 68.4, - 70, - 74.3, - 77.3, - 78.4, - 79.9, - 81.2, - 84, - 87.1, - 89, - 91, - 92.1, - 93.3, - 94.3, - 94.9, - 95.1, - 94.9, - 94.1, - 93.1, - 90.8, - 87.2, - 85.7, - 84.5, - 83.2, - 81.9, - 80.3, - 79, - 77.8, - 76.4, - 73.5, - 70.9, - 70.2, - 69.8, - 68.7, - 67.4, - 66.2, - 65.5, - 64.6, - 63.1, - 61.4, - 60, - 59, - 57.8, - 57.4, - 57, - 56.2, - 55.6, - 55.1, - 54.7, - 53.9, - 53, - 51.8, - 51.3, - 51.1, - 51.2, - 51.1, - 51.3, - 50.9, - 50.3, - 49.4, - 48.2, - 47.4, - 46.9, - 46.1, - 45.5, - 45.2, - 45.1, - 44.7, - 43.4, - 42, - 40.8, - 39.8, - 39.2, - 38.5, - 38.5, - 38.2, - 37.9, - 37.6, - 37.4, - 36.8, - 36, - 35.5, - 35, - 34.5, - 34.1, - 33.5, - 33.2, - 33, - 32.7, - 32.5, - 32.3, - 32.1, - 31.9, - 31.6, - 31.4, - 31.2, - 30.9, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.5, - 29.4, - 29.2, - 29, - 28.8, - 28.6, - 28.5, - 28.5, - 28.3, - 27.6, - 26.9, - 26.5, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.9, - 25.9, - 25.8, - 25.7, - 25.7, - 25.6, - 25.6, - 25.6, - 25.5, - 25.4, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 25, - 24.9, - 24.6, - 24.1, - 23.9, - 23.8, - 23.7, - 23.6, - 23.6, - 23.6, - 23.5, - 22.9, - 21.9, - 19.7, - 18.5, - 17.2, - 16.2, - 15.4, - 14.8, - 14.3, - 13.8, - 13.4, - 12.9, - 21.1, - 21.3, - 21.4, - 21.5, - 21.7, - 21.8, - 22.1, - 22.9, - 23.5, - 23.6, - 24.1, - 24.5, - 24.9, - 25.1, - 25.3, - 25.4, - 25.5, - 25.6, - 25.7, - 25.8, - 25.9, - 26, - 26, - 26.1, - 26.2, - 26.3, - 26.3, - 26.2, - 26, - 25.7, - 25.4, - 25.2, - 25.2, - 25, - 25.1, - 24.7, - 24.7, - 24.8, - 24.9, - 25, - 25.2, - 25.6, - 25.9, - 26.2, - 26.5, - 26.7, - 27, - 27.5, - 28, - 28.5, - 29.1, - 29.8, - 30.1, - 30.4, - 30.8, - 31.6, - 32.6, - 33, - 33.1, - 33.2, - 33.5, - 34.2, - 35, - 35.7, - 35.7, - 35.8, - 35.9, - 36, - 36.3, - 37.4, - 38, - 38.2, - 38.9, - 39.9, - 40.8, - 41.7, - 43.7, - 45, - 46.1, - 46.8, - 47.2, - 48.1, - 48.9, - 49.8, - 50.8, - 52, - 53.2, - 54.1, - 55.1, - 56.2, - 57, - 57.9, - 59.1, - 60.2, - 60.7, - 61.6, - 62.9, - 64.9, - 66.3, - 68.2, - 71, - 76, - 77.6, - 78.7, - 79.9, - 81.6, - 85.5, - 87.4, - 89.3, - 91, - 92.1, - 93.3, - 94.4, - 95.2, - 95.6, - 95.1, - 94.1, - 93.1, - 91.2, - 87.8, - 86.5, - 85.4, - 84, - 82.6, - 81, - 79.6, - 78.3, - 77.1, - 74.5, - 72.1, - 71.6, - 70.8, - 69.7, - 68.5, - 67.6, - 66.7, - 65.4, - 63.7, - 62, - 60.8, - 59.7, - 58.7, - 58.2, - 57.3, - 56.7, - 56.3, - 55.6, - 54.7, - 53.8, - 53.3, - 52.4, - 51.8, - 52, - 51.7, - 51.5, - 51.6, - 51.2, - 50.4, - 49.5, - 48.5, - 47.6, - 47.2, - 46.4, - 46.2, - 45.9, - 45.6, - 45.3, - 44.3, - 42.9, - 41.4, - 40.2, - 39.4, - 39, - 38.8, - 38.5, - 38.2, - 37.8, - 37.5, - 37.2, - 36.6, - 36, - 35.4, - 34.8, - 34.1, - 33.6, - 33.3, - 33.1, - 32.9, - 32.7, - 32.4, - 32.1, - 31.9, - 31.7, - 31.4, - 31.1, - 30.9, - 30.7, - 30.5, - 30.3, - 30.1, - 29.9, - 29.7, - 29.6, - 29.5, - 29.3, - 29.1, - 28.9, - 28.6, - 28.5, - 28.5, - 28.6, - 28, - 27.3, - 26.9, - 26.4, - 26.2, - 26.1, - 26, - 25.9, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.4, - 25.5, - 25.4, - 25.3, - 25.2, - 25.1, - 25.1, - 25, - 25, - 24.9, - 24.8, - 24.5, - 24.2, - 23.9, - 23.8, - 23.7, - 23.6, - 23.6, - 23.5, - 23.5, - 23.3, - 22.7, - 20.9, - 19, - 18, - 16.8, - 15.9, - 15, - 14.3, - 13.8, - 13.4, - 13.2, - 12.9, - 21.1, - 21.3, - 21.5, - 21.6, - 21.7, - 21.8, - 22, - 22.4, - 23, - 23.4, - 24, - 24.5, - 24.9, - 25.1, - 25.3, - 25.4, - 25.5, - 25.6, - 25.6, - 25.7, - 25.8, - 25.9, - 26, - 26, - 26.1, - 26.1, - 26.1, - 25.9, - 25.6, - 25.1, - 24.8, - 24.7, - 24.8, - 24.7, - 24.8, - 24.5, - 24.6, - 24.7, - 24.8, - 24.9, - 25.3, - 25.6, - 25.9, - 26.1, - 26.3, - 26.5, - 26.9, - 27.3, - 27.7, - 28.2, - 28.9, - 29.6, - 29.9, - 30.3, - 30.7, - 31.4, - 32.4, - 33.1, - 33.4, - 33.6, - 33.8, - 34.2, - 34.7, - 35.2, - 35.3, - 35.6, - 35.9, - 36.1, - 36.5, - 37.1, - 37.8, - 38.2, - 39, - 40.2, - 41.4, - 42.5, - 43.9, - 45.1, - 46.3, - 47.4, - 48.1, - 48.8, - 49.3, - 50, - 50.9, - 52, - 53.1, - 54.2, - 55.3, - 56.4, - 57.5, - 58.5, - 59.5, - 60.7, - 61.6, - 62.4, - 63.6, - 65.2, - 66.7, - 68.7, - 71.8, - 76.2, - 77.6, - 78.6, - 79.9, - 83.1, - 86.9, - 88.2, - 90.2, - 91.4, - 92.4, - 93.4, - 94.6, - 95.6, - 96, - 95.2, - 94.3, - 93.3, - 91.7, - 88.5, - 87.2, - 86.1, - 84.5, - 83, - 81.4, - 80, - 78.5, - 77.1, - 75.6, - 74.1, - 73.1, - 71.9, - 70.9, - 69.8, - 68.6, - 67, - 65.4, - 63.7, - 62.3, - 61.1, - 60.2, - 59.5, - 58.7, - 58, - 57.4, - 56.8, - 55.8, - 54.9, - 54, - 53.9, - 53.4, - 52.9, - 52.9, - 52.5, - 52.1, - 52, - 51.6, - 50.8, - 49.7, - 48.7, - 48.1, - 47.5, - 46.9, - 46.8, - 46.6, - 46.4, - 45.9, - 45.1, - 43.9, - 41.6, - 40.4, - 39.9, - 39.7, - 39.3, - 38.8, - 38.6, - 38.1, - 37.6, - 37.3, - 37, - 36.5, - 35.8, - 34.9, - 34.1, - 33.6, - 33.4, - 33.3, - 33, - 32.8, - 32.5, - 32.2, - 32, - 31.7, - 31.5, - 31.3, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.5, - 29.4, - 29.1, - 28.9, - 28.6, - 28.5, - 28.4, - 28.5, - 28.2, - 27.9, - 27.3, - 26.6, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.6, - 25.5, - 25.5, - 25.4, - 25.2, - 25.3, - 25.2, - 25.1, - 25.1, - 25, - 24.9, - 24.9, - 24.7, - 24.4, - 24.1, - 23.9, - 23.8, - 23.7, - 23.7, - 23.6, - 23.6, - 23.5, - 23.4, - 23.1, - 22.6, - 20.2, - 18.4, - 17.6, - 16.3, - 15.4, - 14.8, - 14.1, - 13.8, - 13.5, - 13.3, - 13, - 21.2, - 21.3, - 21.5, - 21.6, - 21.7, - 21.8, - 22.1, - 22.4, - 23, - 23.6, - 24.1, - 24.5, - 24.8, - 25, - 25.2, - 25.3, - 25.4, - 25.5, - 25.6, - 25.7, - 25.8, - 25.8, - 25.9, - 25.9, - 26, - 26, - 25.9, - 25.7, - 25.2, - 24.6, - 24.3, - 24.5, - 24.6, - 24.5, - 24.4, - 24.4, - 24.5, - 24.6, - 24.7, - 25, - 25.4, - 25.6, - 25.8, - 25.9, - 26.1, - 26.3, - 26.6, - 26.8, - 27.3, - 27.9, - 28.5, - 29, - 29.4, - 29.9, - 30.4, - 31, - 31.8, - 32.6, - 33.2, - 33.7, - 34.1, - 34.3, - 34.6, - 34.9, - 35.1, - 35.4, - 35.8, - 36.1, - 36.5, - 37.1, - 37.6, - 38.1, - 38.9, - 40, - 41.2, - 42.5, - 44.1, - 45.4, - 46.5, - 47.4, - 48.1, - 49, - 49.7, - 50.5, - 51.5, - 52.3, - 53.1, - 54.1, - 55.2, - 56.3, - 57.6, - 58.8, - 59.9, - 60.9, - 62.2, - 62.9, - 64.3, - 65.8, - 67.5, - 69.4, - 73, - 76.4, - 77.8, - 78.8, - 80.2, - 84.8, - 87.4, - 88.7, - 90.8, - 92, - 93, - 94, - 95.1, - 96.2, - 96.7, - 95.7, - 94.8, - 93.8, - 92.6, - 89.9, - 88.2, - 86.5, - 85.1, - 83.2, - 81.9, - 80.5, - 79.1, - 78.2, - 77.5, - 76, - 74.6, - 73.3, - 71.7, - 70.1, - 68.6, - 66.9, - 65.5, - 64.2, - 62.8, - 61.9, - 61.2, - 60.5, - 59.8, - 58.8, - 58, - 57.2, - 56.2, - 55.2, - 54.5, - 54.7, - 54.3, - 53.9, - 53.8, - 53.3, - 53, - 52.9, - 52.6, - 51.6, - 50.2, - 49.1, - 48.5, - 47.9, - 47.5, - 47.3, - 47, - 46.8, - 46.3, - 45.4, - 44.2, - 41.9, - 41.3, - 40.9, - 40.5, - 40, - 39.3, - 39, - 38.5, - 37.9, - 37.5, - 37.2, - 36.7, - 35.6, - 34.6, - 33.9, - 33.7, - 33.5, - 33.3, - 33.1, - 32.8, - 32.5, - 32.3, - 32.1, - 31.8, - 31.5, - 31.4, - 31.1, - 30.8, - 30.6, - 30.4, - 30.2, - 30, - 29.7, - 29.6, - 29.4, - 29.2, - 29.1, - 28.9, - 28.6, - 28.5, - 28.4, - 28.4, - 28.2, - 28.1, - 27.5, - 26.7, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.5, - 25.3, - 25.2, - 25.1, - 25.1, - 25.1, - 25.2, - 25.1, - 25.1, - 25, - 24.9, - 24.5, - 24.1, - 24, - 23.9, - 23.8, - 23.7, - 23.7, - 23.6, - 23.5, - 23.5, - 23.3, - 22.9, - 22.4, - 20.9, - 17.7, - 17, - 16, - 15.3, - 14.9, - 14.6, - 14.5, - 14.2, - 14, - 13.7, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 22.4, - 23.1, - 23.5, - 23.9, - 24.2, - 24.5, - 24.8, - 25, - 25.1, - 25.2, - 25.2, - 25.4, - 25.5, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.3, - 24.9, - 24.4, - 24.1, - 24.2, - 24.3, - 24.2, - 24.2, - 24.3, - 24.4, - 24.5, - 24.8, - 25, - 25.2, - 25.4, - 25.6, - 25.7, - 25.9, - 26.1, - 26.4, - 26.6, - 27, - 27.7, - 28.1, - 28.5, - 28.8, - 29.4, - 29.9, - 30.5, - 31.4, - 32.1, - 32.8, - 33.5, - 34.1, - 34.5, - 35, - 35.3, - 35.4, - 35.5, - 35.7, - 36.1, - 36.4, - 36.8, - 37.2, - 38, - 38.9, - 39.8, - 40.9, - 42.2, - 43.8, - 45.2, - 46.2, - 47.1, - 47.7, - 48.6, - 49.4, - 50.4, - 51.6, - 52.7, - 53.8, - 54.7, - 55.6, - 56.7, - 57.8, - 59, - 60.1, - 61.2, - 62.6, - 63.5, - 64.9, - 66.3, - 67.7, - 69.5, - 73.5, - 76.7, - 77.9, - 79.1, - 80.8, - 85.4, - 87.5, - 89.2, - 91.4, - 92.7, - 93.7, - 94.7, - 95.8, - 97, - 97.4, - 96.7, - 95.6, - 94.6, - 93.8, - 92.8, - 90.2, - 87.4, - 86.3, - 84.2, - 82.7, - 81.4, - 80.3, - 79.5, - 78.6, - 77.4, - 75.8, - 74.1, - 72, - 70.1, - 69, - 67.8, - 66.7, - 65.6, - 64.1, - 63.2, - 62.3, - 61.8, - 61.1, - 59.7, - 58.6, - 57.8, - 56.4, - 55.6, - 55.4, - 55.7, - 55.3, - 54.8, - 54.5, - 54, - 53.9, - 53.6, - 53.1, - 52, - 50.6, - 49.5, - 48.9, - 48.3, - 47.9, - 47.9, - 47.3, - 47, - 46.4, - 45.5, - 44.8, - 43.8, - 43.4, - 42.5, - 41.5, - 40.6, - 39.7, - 38.9, - 38.4, - 37.9, - 37.4, - 37.1, - 36.5, - 35.3, - 34.5, - 34.1, - 34, - 33.7, - 33.4, - 33.1, - 32.8, - 32.5, - 32.3, - 32.1, - 31.8, - 31.6, - 31.3, - 31.1, - 30.8, - 30.6, - 30.4, - 30.2, - 30, - 29.7, - 29.5, - 29.3, - 29.1, - 29, - 28.8, - 28.6, - 28.5, - 28.4, - 28.4, - 28.2, - 28.1, - 27.8, - 27.1, - 26.4, - 26.1, - 25.9, - 25.8, - 25.7, - 25.7, - 25.5, - 25.4, - 25.4, - 25.2, - 25.1, - 25, - 25.1, - 25.1, - 25.1, - 25, - 25, - 24.9, - 24.4, - 24.1, - 23.9, - 23.9, - 23.8, - 23.7, - 23.6, - 23.6, - 23.5, - 23.5, - 23.4, - 22.9, - 22.4, - 21.8, - 18.3, - 16.7, - 16.2, - 15.6, - 15.6, - 15.4, - 15.4, - 15.4, - 15.2, - 14.8, - 21.4, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 22.5, - 23.5, - 23.9, - 24.1, - 24.4, - 24.6, - 24.7, - 24.8, - 24.9, - 25, - 25, - 25.3, - 25.5, - 25.6, - 25.6, - 25.6, - 25.5, - 25.4, - 25.2, - 25.1, - 25, - 24.7, - 24.5, - 24.2, - 23.9, - 24, - 24, - 24, - 24.1, - 24.2, - 24.3, - 24.6, - 24.8, - 24.9, - 25, - 25.3, - 25.5, - 25.8, - 26, - 26.3, - 26.5, - 26.7, - 27.1, - 27.6, - 28, - 28.3, - 28.7, - 29.3, - 29.8, - 30.4, - 31.1, - 31.9, - 32.9, - 33.6, - 34.1, - 34.6, - 35.4, - 35.9, - 35.6, - 35.8, - 36, - 36.3, - 36.6, - 36.7, - 37.1, - 37.8, - 39.4, - 40.1, - 41, - 42.1, - 43.5, - 44.5, - 45.9, - 46.6, - 47.2, - 48.1, - 49, - 50.1, - 51.2, - 52.5, - 54.1, - 54.9, - 55.9, - 56.9, - 58, - 59.3, - 60.4, - 61.6, - 62.9, - 63.9, - 65.2, - 66.6, - 67.7, - 69.3, - 73.1, - 76.7, - 78.1, - 79.3, - 81.1, - 85.3, - 87.5, - 89.4, - 91.6, - 93, - 94.2, - 95.5, - 96.7, - 97.8, - 98.5, - 97.9, - 96.7, - 95.6, - 94.9, - 94.2, - 92.3, - 88.8, - 87.2, - 85.7, - 83.8, - 82.5, - 81.6, - 80.4, - 79.3, - 77.9, - 76.3, - 74.4, - 72.8, - 71.2, - 70.3, - 69.4, - 68.1, - 66.8, - 65.5, - 64.8, - 63.9, - 63.4, - 62.4, - 61.3, - 59.6, - 58.3, - 57, - 56.4, - 56.6, - 56.9, - 56.6, - 56, - 55.5, - 54.9, - 54.5, - 54, - 53, - 51.8, - 50.8, - 50, - 49.4, - 48.7, - 48.6, - 48.3, - 47.7, - 47.1, - 46.3, - 45.8, - 45.4, - 45, - 44.4, - 43.6, - 42.4, - 40.9, - 39.9, - 39, - 38.4, - 37.9, - 37.4, - 37, - 36.1, - 35.6, - 35.1, - 34.8, - 34.4, - 34, - 33.4, - 33, - 32.6, - 32.4, - 32.2, - 32, - 31.7, - 31.5, - 31.3, - 31, - 30.8, - 30.6, - 30.3, - 30.1, - 29.9, - 29.7, - 29.5, - 29.3, - 29, - 28.9, - 28.8, - 28.6, - 28.5, - 28.4, - 28.4, - 28.2, - 28.2, - 28, - 27.4, - 26.4, - 26, - 26, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 25.4, - 25.2, - 25.1, - 24.9, - 24.9, - 25.1, - 25, - 25, - 24.9, - 24.9, - 24.4, - 24.1, - 24, - 23.9, - 23.8, - 23.8, - 23.7, - 23.7, - 23.6, - 23.5, - 23.5, - 23.1, - 22.3, - 22, - 19.7, - 17.5, - 17.2, - 16.9, - 16.9, - 16.8, - 16.6, - 16.7, - 16.5, - 16.1, - 21.4, - 21.5, - 21.6, - 21.7, - 21.7, - 21.9, - 22.5, - 23.5, - 24, - 24.2, - 24.3, - 24.5, - 24.6, - 24.6, - 24.6, - 24.7, - 24.9, - 25.2, - 25.3, - 25.4, - 25.5, - 25.4, - 25.3, - 25, - 24.6, - 24.4, - 24.4, - 24.3, - 24.2, - 24, - 23.8, - 23.9, - 23.8, - 23.9, - 24, - 24.2, - 24.5, - 24.5, - 24.6, - 24.7, - 24.9, - 25.6, - 25.7, - 25.9, - 26.1, - 26.4, - 26.7, - 27, - 27.4, - 27.8, - 28.1, - 28.4, - 28.8, - 29.5, - 30.1, - 30.6, - 31.3, - 32.2, - 33.1, - 33.7, - 34.1, - 34.6, - 35.4, - 35.9, - 35.8, - 36.1, - 36.3, - 36.7, - 37, - 37.1, - 37.7, - 38.5, - 40.5, - 41.1, - 41.7, - 42.5, - 43.5, - 44.1, - 45, - 46.1, - 46.9, - 47.7, - 48.7, - 49.8, - 50.9, - 52.1, - 53.4, - 54.7, - 55.7, - 56.9, - 58, - 59.2, - 60.4, - 61.5, - 62.7, - 63.8, - 65.3, - 66.7, - 67.9, - 69.2, - 73.3, - 76.6, - 78.1, - 79.3, - 81.1, - 84.3, - 87.2, - 89.4, - 91.7, - 93.2, - 94.7, - 96, - 97.4, - 98.6, - 99.7, - 99.2, - 98, - 96.8, - 95.9, - 95, - 93.8, - 91.7, - 88.2, - 86.8, - 85.1, - 83.6, - 82.4, - 81.2, - 79.8, - 78.4, - 76.8, - 75.5, - 74.8, - 73.5, - 71.8, - 70.5, - 69.1, - 67.9, - 67.5, - 66.8, - 66.4, - 65.5, - 64.1, - 62.5, - 60.8, - 59.4, - 58.4, - 57.4, - 57.8, - 58.2, - 57.9, - 57.3, - 56.5, - 55.6, - 54.8, - 54.1, - 53, - 51.9, - 51.3, - 50.6, - 49.9, - 49.4, - 49.2, - 48.6, - 48.1, - 47.7, - 46.8, - 46.4, - 46, - 45.4, - 44.8, - 44, - 43, - 41.5, - 40.2, - 39.2, - 38.6, - 38, - 37.4, - 36.9, - 36.2, - 36.1, - 35.8, - 35.6, - 35, - 34.3, - 33.5, - 32.9, - 32.4, - 32.1, - 32, - 31.9, - 31.7, - 31.4, - 31.2, - 31, - 30.7, - 30.5, - 30.3, - 30, - 29.8, - 29.6, - 29.5, - 29.3, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.5, - 28.2, - 28, - 27.7, - 27.3, - 26.5, - 26.2, - 26.1, - 26.1, - 26, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.3, - 25, - 24.8, - 24.9, - 24.9, - 24.9, - 24.9, - 24.8, - 24.5, - 24.1, - 24, - 24.1, - 23.9, - 23.9, - 23.8, - 23.7, - 23.6, - 23.5, - 23.5, - 23, - 22.3, - 22, - 21.9, - 20.9, - 20.1, - 19.7, - 19.9, - 19.9, - 18.4, - 18.3, - 18, - 17.3, - 21.5, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 22.5, - 23.5, - 24, - 24.1, - 24.2, - 24.2, - 24.3, - 24.3, - 24.2, - 24.5, - 24.8, - 25, - 25.2, - 25.3, - 25.3, - 25.3, - 25.1, - 24.7, - 24.2, - 23.8, - 23.8, - 23.9, - 23.8, - 23.7, - 23.7, - 23.7, - 23.6, - 23.8, - 24, - 24.2, - 24.3, - 24.3, - 24.4, - 24.6, - 25, - 25.8, - 25.8, - 25.9, - 26.1, - 26.5, - 26.8, - 27.1, - 27.4, - 27.8, - 28.1, - 28.5, - 28.9, - 29.6, - 30.2, - 30.7, - 31.5, - 32.4, - 33.1, - 33.7, - 34.1, - 34.6, - 35.3, - 35.8, - 35.9, - 36.2, - 36.5, - 36.7, - 37.1, - 37.7, - 38.3, - 38.9, - 40.3, - 41.2, - 41.7, - 42.5, - 43.4, - 44, - 44.8, - 45.9, - 46.9, - 47.8, - 48.8, - 49.9, - 50.9, - 52.1, - 53.5, - 54.6, - 55.7, - 56.7, - 57.7, - 58.9, - 60.1, - 61, - 62, - 63.5, - 65.3, - 66.9, - 68.7, - 69.9, - 74.3, - 76.9, - 78.2, - 79.5, - 81, - 83.5, - 86.9, - 89.1, - 91.6, - 93.1, - 94.7, - 96.4, - 97.8, - 99.2, - 100.4, - 100.2, - 99.1, - 97.9, - 97, - 96, - 94.9, - 93.8, - 91.3, - 87.9, - 86.3, - 84.6, - 83.1, - 82, - 80.7, - 79.1, - 77.8, - 77.4, - 77.2, - 76.3, - 74.1, - 71.4, - 70.1, - 69.5, - 69.5, - 68.8, - 68.7, - 67.2, - 65.3, - 63.4, - 62.2, - 61.9, - 60.6, - 59.3, - 60, - 59.7, - 59.1, - 58.3, - 57.4, - 56.1, - 54.9, - 54, - 53.2, - 52.3, - 51.7, - 51, - 50.3, - 50, - 49.5, - 48.9, - 48.4, - 47.9, - 47.4, - 46.9, - 46.5, - 45.9, - 45.2, - 44.3, - 43.4, - 42.1, - 40.7, - 39.5, - 38.6, - 37.9, - 37.4, - 37.1, - 37, - 37.1, - 36.7, - 36.4, - 35.5, - 34.6, - 33.7, - 32.9, - 32.4, - 31.8, - 31.7, - 31.7, - 31.6, - 31.3, - 31.1, - 30.9, - 30.7, - 30.4, - 30.2, - 30, - 29.7, - 29.5, - 29.4, - 29.2, - 29.1, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 28.7, - 28.4, - 27.9, - 27.7, - 27.4, - 27, - 26.7, - 26.5, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.2, - 24.8, - 24.7, - 24.8, - 24.8, - 24.8, - 24.8, - 24.6, - 24.3, - 24.1, - 24.1, - 24, - 23.8, - 23.7, - 23.6, - 23.6, - 23.5, - 23.4, - 23.1, - 22.6, - 22.6, - 22.8, - 22.7, - 22.3, - 22.4, - 22.4, - 22.1, - 21.3, - 20.7, - 19.6, - 18.8, - 21.5, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 22.5, - 23.4, - 23.8, - 23.9, - 23.9, - 23.9, - 23.9, - 23.8, - 24, - 24.3, - 24.6, - 24.8, - 25, - 25.1, - 25.2, - 25.2, - 25, - 24.7, - 24, - 23.4, - 23.4, - 23.4, - 23.5, - 23.6, - 23.6, - 23.5, - 23.5, - 23.7, - 23.9, - 24, - 24.1, - 24.3, - 24.5, - 24.7, - 25, - 25.6, - 25.7, - 25.9, - 26.2, - 26.5, - 26.9, - 27.2, - 27.4, - 27.8, - 28.1, - 28.4, - 28.9, - 29.4, - 30, - 30.6, - 31.3, - 32.3, - 33, - 33.6, - 34.1, - 34.5, - 35.2, - 35.6, - 35.8, - 36.2, - 36.6, - 36.7, - 37, - 37.7, - 38.5, - 39.1, - 39.9, - 40.6, - 41.3, - 42.1, - 42.8, - 43.7, - 44.7, - 45.6, - 46.8, - 48, - 49, - 50, - 51.1, - 52.2, - 53.3, - 54.4, - 55.4, - 56.2, - 57.1, - 58.4, - 59.5, - 60.3, - 61.4, - 63.4, - 65.7, - 67.7, - 69.6, - 71.6, - 75.7, - 77.5, - 78.7, - 80.1, - 81.4, - 84.3, - 86.9, - 89.4, - 91.6, - 93.1, - 94.6, - 96.5, - 98, - 99.5, - 100.6, - 101.1, - 100, - 99, - 98, - 96.9, - 95.7, - 94.6, - 93.4, - 89.6, - 87.1, - 85.6, - 84, - 82.7, - 81.2, - 79.7, - 79.3, - 79, - 78.7, - 78, - 76.8, - 74.3, - 73.1, - 71.4, - 70.9, - 70.3, - 69.9, - 68.3, - 66.2, - 64.3, - 64.2, - 63.9, - 62.5, - 61.7, - 62, - 61.6, - 60.4, - 58.8, - 57.6, - 56.1, - 54.8, - 53.8, - 53.1, - 52.4, - 51.8, - 51.2, - 50.9, - 50.7, - 50, - 49.5, - 49, - 48.5, - 48.1, - 47.3, - 46.8, - 46.1, - 45.4, - 44.5, - 43.6, - 42, - 40.6, - 39.6, - 38.6, - 37.9, - 37.7, - 37.7, - 37.7, - 37.7, - 37.4, - 36.8, - 35.9, - 34.9, - 34.1, - 33.3, - 32.5, - 31.9, - 31.5, - 31.4, - 31.3, - 31.2, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.5, - 29.3, - 29.2, - 29.1, - 28.9, - 28.8, - 28.7, - 28.6, - 28.7, - 28.7, - 28.5, - 28, - 27.7, - 27.8, - 27.8, - 27.4, - 26.9, - 26.6, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 24.9, - 24.7, - 24.6, - 24.7, - 24.7, - 24.6, - 24.5, - 24.2, - 24.2, - 24, - 23.9, - 23.8, - 23.7, - 23.7, - 23.6, - 23.5, - 23.4, - 23.2, - 23, - 23.3, - 23.3, - 23.2, - 23, - 23.2, - 23.2, - 22.9, - 22.4, - 22.1, - 21.4, - 20.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 22.2, - 23, - 23.4, - 23.6, - 23.6, - 23.6, - 23.5, - 23.5, - 23.8, - 24.2, - 24.5, - 24.7, - 25, - 25.1, - 25.1, - 25.1, - 24.9, - 24.4, - 23.7, - 23.2, - 23.1, - 23.3, - 23.5, - 23.5, - 23.4, - 23.3, - 23.5, - 23.8, - 24, - 24.2, - 24.3, - 24.5, - 24.7, - 24.8, - 25.2, - 25.5, - 25.7, - 25.9, - 26, - 26.3, - 26.8, - 27.1, - 27.3, - 27.7, - 27.9, - 28.3, - 28.7, - 28.9, - 29.4, - 30, - 30.6, - 31.6, - 33.1, - 33.7, - 34.1, - 34.4, - 34.7, - 35.1, - 35.4, - 35.8, - 36.2, - 36.4, - 36.9, - 37.7, - 38.6, - 39.2, - 39.7, - 40.1, - 40.8, - 41.6, - 42.4, - 43.3, - 44.1, - 45.1, - 46.1, - 47.5, - 48.6, - 49.7, - 50.7, - 51.7, - 52.5, - 53.6, - 54.6, - 55.8, - 56.9, - 58, - 59, - 60, - 61.2, - 63.3, - 66, - 68.1, - 70.4, - 73.4, - 76.3, - 77.7, - 79, - 80.7, - 82.6, - 86.1, - 87.6, - 90.2, - 91.8, - 93.4, - 95.1, - 96.9, - 98.6, - 100, - 101.1, - 101.8, - 101.1, - 100.1, - 99.1, - 97.9, - 96.5, - 95.4, - 94.2, - 92.2, - 87.9, - 86.3, - 84.9, - 83.5, - 81.9, - 81, - 80.9, - 80.5, - 80.1, - 79.2, - 78.2, - 77.2, - 76, - 74, - 72.6, - 71.5, - 70.4, - 68.8, - 67.3, - 66.3, - 66.3, - 65.5, - 63.9, - 63.5, - 63.5, - 62.6, - 60.9, - 59, - 57.5, - 56.1, - 54.9, - 53.9, - 53.2, - 52.7, - 52.1, - 51.6, - 51.6, - 51.4, - 51, - 50.6, - 50.2, - 50.6, - 50.8, - 49.3, - 46.7, - 45.9, - 45.1, - 44.2, - 43.2, - 41.4, - 40, - 39.2, - 38.6, - 38.2, - 38, - 38.1, - 38.1, - 38, - 37.6, - 37, - 36, - 35.4, - 34.8, - 34.1, - 33, - 32.2, - 31.7, - 31.3, - 31.1, - 31, - 30.8, - 30.7, - 30.6, - 30.4, - 30.2, - 30, - 29.9, - 29.6, - 29.4, - 29.2, - 29.1, - 28.8, - 28.8, - 28.6, - 28.6, - 28.6, - 28.6, - 28.3, - 28, - 27.9, - 28, - 28, - 27.8, - 27.3, - 26.9, - 26.5, - 26.3, - 26.1, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 25.1, - 24.7, - 24.6, - 24.5, - 24.4, - 24.2, - 24.2, - 24, - 24, - 23.9, - 23.9, - 23.9, - 23.9, - 23.8, - 23.7, - 23.6, - 23.5, - 23.4, - 23.4, - 23.5, - 23.4, - 23.4, - 23.4, - 23.5, - 23.5, - 23.4, - 23, - 22.6, - 22.1, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 21.7, - 21.9, - 22.4, - 22.8, - 23, - 23.1, - 23.1, - 23.2, - 23.6, - 24, - 24.3, - 24.6, - 24.8, - 25, - 25.1, - 25.1, - 25, - 24.5, - 23.8, - 23.2, - 22.9, - 23.1, - 23.3, - 23.2, - 23.2, - 23.2, - 23.3, - 23.7, - 23.9, - 24.1, - 24.3, - 24.5, - 24.6, - 24.8, - 25, - 25.2, - 25.3, - 25.5, - 25.7, - 25.9, - 26.2, - 26.7, - 26.9, - 27.1, - 27.4, - 27.6, - 28.1, - 28.4, - 28.7, - 29.2, - 29.8, - 30.3, - 31, - 32.9, - 33.5, - 33.9, - 34.2, - 34.5, - 34.8, - 35.1, - 35.5, - 36.1, - 36.4, - 36.7, - 37.7, - 38.2, - 39.1, - 39.4, - 39.8, - 40.5, - 41, - 41.8, - 42.4, - 43.3, - 44.5, - 45.6, - 46.9, - 48.1, - 49.1, - 50, - 50.7, - 51.7, - 52.9, - 54.2, - 55.7, - 56.7, - 57.8, - 58.8, - 59.7, - 61.2, - 64.1, - 66.4, - 68.9, - 71.7, - 74.5, - 76.7, - 77.9, - 79.1, - 80.8, - 84, - 86.8, - 88.8, - 90.8, - 92.3, - 93.8, - 95.4, - 97.3, - 99.2, - 100.6, - 101.9, - 102.6, - 102.1, - 101.3, - 101.1, - 100.3, - 97.5, - 96.1, - 94.8, - 93.2, - 89.9, - 87.5, - 86.5, - 86.1, - 84.9, - 83.4, - 83, - 81.9, - 81.3, - 80.3, - 79.3, - 78.4, - 77.5, - 75.9, - 74.3, - 72.5, - 70.9, - 69.5, - 68.9, - 68.2, - 67.6, - 66.7, - 65.5, - 65.2, - 64.7, - 63.3, - 61.3, - 59.4, - 57.9, - 56.6, - 55.4, - 54.6, - 54, - 53.6, - 52.7, - 52.4, - 52.3, - 52, - 51.7, - 51.5, - 52.2, - 54, - 53.2, - 51.8, - 47.4, - 45.8, - 44.9, - 44, - 42.9, - 41.1, - 39.8, - 38.8, - 38.9, - 38.8, - 38.7, - 38.5, - 38.4, - 38.2, - 37.8, - 37.3, - 36.7, - 36.1, - 35.5, - 34.7, - 33.6, - 32.6, - 32, - 31.4, - 31, - 30.8, - 30.5, - 30.4, - 30.3, - 30.2, - 30, - 29.9, - 29.8, - 29.6, - 29.4, - 29.2, - 29, - 28.8, - 28.8, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.3, - 26.9, - 26.5, - 26.3, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.4, - 25, - 24.7, - 24.6, - 24.5, - 24.4, - 24.3, - 24.2, - 24.1, - 24.2, - 24.1, - 24.1, - 24, - 23.8, - 23.7, - 23.6, - 23.6, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.6, - 23.6, - 23.5, - 23.1, - 22.5, - 21.9, - 21.5, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 22, - 22.2, - 22.5, - 22.7, - 23.1, - 23.5, - 23.9, - 24.2, - 24.5, - 24.7, - 24.9, - 25, - 25, - 25, - 24.7, - 24, - 23.3, - 22.8, - 22.9, - 23.1, - 23.1, - 23, - 23, - 23.2, - 23.6, - 23.9, - 24.1, - 24.3, - 24.4, - 24.5, - 24.7, - 24.8, - 25, - 25.2, - 25.4, - 25.5, - 25.7, - 26, - 26.3, - 26.7, - 26.9, - 27.3, - 27.4, - 27.6, - 28, - 28.6, - 29.3, - 29.8, - 30.3, - 30.7, - 31.2, - 32.8, - 33.3, - 33.7, - 34.2, - 34.7, - 34.8, - 35, - 35.4, - 36, - 36.1, - 36.5, - 37.4, - 38.2, - 39.2, - 39.1, - 39.6, - 40.3, - 40.7, - 41.2, - 41.7, - 43, - 44.7, - 45.9, - 46.9, - 47.7, - 48.8, - 49.8, - 50.5, - 51.7, - 53, - 54.1, - 55.1, - 56.1, - 57.2, - 58.5, - 59.6, - 61.4, - 64.8, - 66.6, - 69.4, - 73.3, - 75.4, - 77.2, - 78.5, - 80.1, - 81.8, - 85.6, - 88.4, - 90.1, - 91.3, - 92.6, - 94.3, - 96.1, - 97.9, - 99.9, - 101.4, - 102.7, - 103.8, - 103.4, - 104.2, - 111.8, - 114.9, - 105.6, - 100.9, - 95.3, - 93.9, - 92.8, - 91.8, - 90.5, - 89.4, - 88.2, - 87, - 86.1, - 83.4, - 82.4, - 81.2, - 80.2, - 79.2, - 78.4, - 77.2, - 75.4, - 73.2, - 71.6, - 70.8, - 70.4, - 69.7, - 68.5, - 67.4, - 66.5, - 66.3, - 65.3, - 63.6, - 61.9, - 60, - 58.4, - 57.5, - 56.6, - 55.7, - 54.9, - 54.3, - 53.7, - 53.3, - 53, - 52.6, - 52.2, - 51.8, - 54.6, - 54.8, - 53.8, - 52.3, - 48.4, - 46.2, - 45.4, - 44.5, - 43, - 41.1, - 39.8, - 39.7, - 40.1, - 39.9, - 39.6, - 39.2, - 38.8, - 38.4, - 38.1, - 37.7, - 37.3, - 36.7, - 35.9, - 34.9, - 33.9, - 32.9, - 32.1, - 31.5, - 31.1, - 30.8, - 30.6, - 30.3, - 30.1, - 30, - 29.8, - 29.7, - 29.7, - 29.5, - 29.3, - 29.1, - 29, - 28.8, - 28.8, - 28.5, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 28, - 27.7, - 27.3, - 27.3, - 27, - 26.7, - 26.5, - 26.2, - 26.1, - 25.8, - 25.7, - 25.6, - 25.4, - 25.2, - 24.8, - 24.7, - 24.7, - 24.7, - 24.6, - 24.5, - 24.4, - 24.3, - 24.3, - 24.2, - 24.1, - 24, - 23.8, - 23.7, - 23.6, - 23.7, - 23.6, - 23.6, - 23.6, - 23.6, - 23.6, - 23.5, - 23.6, - 23.6, - 23.6, - 23.7, - 23.7, - 23.5, - 22.8, - 22.3, - 21.3, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 21.9, - 22.4, - 22.8, - 23.1, - 23.5, - 23.9, - 24.1, - 24.4, - 24.6, - 24.8, - 24.8, - 24.9, - 24.9, - 24.9, - 24.5, - 23.8, - 23, - 22.7, - 22.9, - 22.9, - 22.9, - 22.9, - 23, - 23.4, - 23.7, - 23.9, - 24.1, - 24.3, - 24.4, - 24.6, - 24.7, - 24.9, - 25, - 25.2, - 25.4, - 25.5, - 25.8, - 26.1, - 26.3, - 26.7, - 27, - 27.4, - 27.6, - 27.9, - 28.5, - 29.2, - 29.8, - 30.2, - 30.7, - 31.4, - 32.3, - 33.1, - 33.5, - 33.8, - 34.2, - 34.7, - 34.9, - 35, - 35.3, - 35.6, - 35.9, - 36.6, - 38.1, - 39.1, - 39.4, - 39.2, - 39.4, - 40, - 40.4, - 40.8, - 41.6, - 43.3, - 45.2, - 46.3, - 47, - 47.4, - 48.5, - 49.7, - 50.5, - 51.6, - 52.7, - 53.8, - 54.8, - 56, - 57.2, - 58.7, - 59.8, - 62.3, - 65.1, - 66.8, - 69.4, - 73.7, - 75.9, - 77.3, - 78.7, - 80.5, - 83.2, - 87.1, - 90.7, - 91.3, - 92.1, - 93.3, - 94.7, - 96.3, - 98.2, - 100, - 101.8, - 104, - 105.3, - 107.5, - 115.6, - 121.4, - 122.8, - 121.1, - 117.4, - 102.5, - 94.9, - 94.3, - 93.8, - 93.5, - 93.4, - 92.6, - 92.1, - 89.8, - 86.4, - 83.9, - 82.1, - 81, - 79.9, - 79.2, - 78.2, - 77.2, - 75.1, - 73.8, - 72.6, - 71.8, - 70.7, - 69.5, - 68.4, - 67.6, - 67, - 65.5, - 63.9, - 62.7, - 61.4, - 60.2, - 58.8, - 57.8, - 56.7, - 55.8, - 55.1, - 54.5, - 54, - 53.5, - 52.9, - 52.3, - 51.8, - 53.3, - 53.7, - 53.7, - 52.9, - 48.9, - 46.5, - 45.7, - 44.7, - 42.8, - 40.8, - 40.7, - 41.9, - 41.9, - 41.1, - 40.5, - 39.8, - 39.1, - 38.5, - 38.2, - 37.9, - 37.5, - 37, - 36.1, - 34.9, - 33.9, - 32.9, - 32.1, - 31.6, - 31.2, - 30.9, - 30.6, - 30.4, - 30.3, - 30.1, - 29.9, - 29.6, - 29.4, - 29.3, - 29.1, - 29, - 28.8, - 28.7, - 28.7, - 28.6, - 28.4, - 28.2, - 28, - 28, - 27.8, - 27.6, - 26.9, - 26.6, - 26.6, - 26.6, - 26.5, - 26.3, - 26.1, - 26, - 25.8, - 25.6, - 25.5, - 25.3, - 24.9, - 24.9, - 24.9, - 24.8, - 24.8, - 24.7, - 24.6, - 24.5, - 24.4, - 24.3, - 24.2, - 24, - 23.9, - 23.8, - 23.8, - 23.8, - 23.8, - 23.8, - 23.6, - 23.6, - 23.7, - 23.6, - 23.6, - 23.6, - 23.6, - 23.7, - 23.7, - 23.7, - 23.6, - 23.2, - 22.7, - 21.3, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.8, - 22.1, - 22.8, - 23.4, - 23.6, - 23.8, - 24.1, - 24.3, - 24.5, - 24.6, - 24.7, - 24.7, - 24.7, - 24.7, - 24.6, - 24.2, - 23.5, - 22.8, - 22.6, - 22.7, - 22.8, - 22.8, - 22.8, - 23.1, - 23.4, - 23.6, - 23.8, - 23.9, - 24.1, - 24.4, - 24.6, - 24.7, - 24.9, - 25.1, - 25.2, - 25.4, - 25.6, - 25.8, - 26.2, - 26.3, - 26.6, - 27, - 27.5, - 27.8, - 28.2, - 28.8, - 29.3, - 29.8, - 30.2, - 30.5, - 31.1, - 31.9, - 32.9, - 33.4, - 33.8, - 34.2, - 34.7, - 34.9, - 35.1, - 35.3, - 35.6, - 36.4, - 37.2, - 38.7, - 40.9, - 40.5, - 39.4, - 39.1, - 39.6, - 40.1, - 40.7, - 41.4, - 42.9, - 44.9, - 45.9, - 46.3, - 47, - 48.2, - 49.4, - 50.6, - 51.7, - 52.7, - 53.8, - 55, - 56.3, - 57.4, - 58.7, - 60, - 62.6, - 64.7, - 66.6, - 68.8, - 73.1, - 76, - 77.4, - 78.8, - 80.8, - 84.8, - 88.8, - 91.5, - 92.5, - 93.2, - 94.3, - 95.6, - 96.9, - 98.9, - 100.4, - 103.3, - 109.6, - 112.2, - 115.2, - 123.3, - 125, - 124.8, - 123.2, - 122.2, - 116.8, - 98.6, - 95.6, - 95.2, - 94.7, - 94.6, - 94.7, - 94.5, - 93.1, - 88.6, - 85.2, - 83, - 81.8, - 81.1, - 80.1, - 79.1, - 78.1, - 77.2, - 76.1, - 74.4, - 73.2, - 71.6, - 70.5, - 69.7, - 69.1, - 67.9, - 66.3, - 65.3, - 64.6, - 63.1, - 62, - 60.4, - 58.8, - 57.8, - 57, - 55.9, - 55, - 54.3, - 53.6, - 52.7, - 52, - 51.5, - 51.2, - 50.9, - 51.5, - 51.6, - 48, - 46.7, - 45.7, - 44.4, - 42.7, - 42.3, - 43.1, - 43.7, - 43.4, - 42.5, - 41.2, - 40.1, - 39.2, - 38.5, - 38.2, - 37.9, - 37.5, - 36.9, - 36, - 34.8, - 33.7, - 32.8, - 32.1, - 31.6, - 31.2, - 30.9, - 30.7, - 30.5, - 30.4, - 30.2, - 30, - 29.7, - 29.6, - 29.4, - 29.3, - 29.1, - 29, - 28.9, - 28.8, - 28.6, - 28.4, - 28.2, - 28, - 28.1, - 27.8, - 27.4, - 26.7, - 26.4, - 26.2, - 26.3, - 26.4, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.5, - 25.3, - 25, - 25, - 25, - 24.9, - 24.8, - 24.7, - 24.6, - 24.5, - 24.4, - 24.2, - 24.1, - 24, - 23.9, - 23.9, - 23.9, - 23.9, - 23.8, - 23.8, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.5, - 23.1, - 21.3, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.8, - 22.1, - 22.9, - 23.5, - 23.8, - 24, - 24.2, - 24.3, - 24.4, - 24.5, - 24.5, - 24.4, - 24.2, - 24.2, - 24.2, - 23.7, - 22.9, - 22.5, - 22.5, - 22.6, - 22.7, - 22.7, - 22.9, - 23, - 23.2, - 23.4, - 23.6, - 23.9, - 24.1, - 24.3, - 24.5, - 24.7, - 24.9, - 25.3, - 25.4, - 25.5, - 25.7, - 25.9, - 26.2, - 26.3, - 26.7, - 27, - 27.4, - 27.9, - 28.3, - 28.6, - 28.9, - 29.3, - 29.7, - 30.2, - 30.6, - 31.3, - 32.6, - 33.2, - 33.7, - 34.3, - 34.8, - 35, - 35.2, - 35.6, - 35.8, - 37.1, - 38.8, - 40.7, - 41.3, - 39.9, - 39, - 38.9, - 39.7, - 40.2, - 40.9, - 41.8, - 42.7, - 44.1, - 44.9, - 45.7, - 46.8, - 47.9, - 49.1, - 50.4, - 51.4, - 52.5, - 53.7, - 54.7, - 55.9, - 57.2, - 58.9, - 60.1, - 62.5, - 64.4, - 66.3, - 68.6, - 73, - 76.5, - 77.9, - 79.1, - 82.2, - 86.2, - 90.1, - 92, - 93.4, - 94.4, - 95.5, - 96.6, - 98.4, - 99.9, - 101.2, - 105.7, - 111.6, - 115.1, - 119.7, - 125.5, - 126, - 125.5, - 124.1, - 122.5, - 120.6, - 106, - 96.8, - 96.3, - 95.6, - 95.5, - 95.8, - 95.5, - 94.6, - 91.2, - 86.8, - 84.6, - 83, - 82.1, - 81.1, - 79.9, - 78.9, - 78.2, - 77.5, - 75.9, - 74.3, - 72.7, - 71.2, - 70.7, - 70.2, - 68.8, - 67.7, - 67.3, - 66.2, - 64.7, - 63, - 61.6, - 60, - 58.6, - 57.5, - 56.1, - 55, - 54.2, - 53.3, - 52.3, - 51.6, - 51, - 50.5, - 49.9, - 49.4, - 48.7, - 47.3, - 46.4, - 45.2, - 43.5, - 43.2, - 43.9, - 44.3, - 44.2, - 43.7, - 42.9, - 41.5, - 40.1, - 39.1, - 38.4, - 38, - 37.7, - 37.3, - 36.7, - 35.8, - 34.6, - 33.5, - 32.7, - 32.1, - 31.6, - 31.3, - 31, - 30.7, - 30.6, - 30.5, - 30.3, - 30.1, - 30, - 29.9, - 29.7, - 29.5, - 29.3, - 29.2, - 29, - 28.8, - 28.6, - 28.3, - 28.2, - 28, - 28.1, - 28, - 27.6, - 27, - 26.7, - 26.2, - 26.1, - 26.3, - 26.2, - 26.1, - 25.9, - 25.8, - 25.6, - 25.3, - 25.1, - 25.1, - 25.2, - 25.2, - 25.1, - 24.9, - 24.8, - 24.7, - 24.6, - 24.5, - 24.2, - 24.2, - 24.1, - 24.1, - 24, - 24, - 24, - 23.9, - 23.8, - 23.8, - 23.8, - 23.8, - 23.8, - 23.8, - 23.7, - 23.7, - 23.8, - 23.8, - 23.8, - 23.7, - 23.7, - 23.5, - 21.4, - 21.4, - 21.5, - 21.6, - 21.6, - 21.7, - 21.9, - 22.2, - 23, - 23.6, - 23.9, - 24.1, - 24.2, - 24.3, - 24.4, - 24.4, - 24.3, - 24, - 23.5, - 23.3, - 23.3, - 22.9, - 22.4, - 22.4, - 22.5, - 22.6, - 22.6, - 22.7, - 23, - 23.2, - 23.4, - 23.5, - 23.6, - 23.9, - 24.1, - 24.3, - 24.5, - 24.8, - 25.1, - 25.6, - 25.8, - 25.7, - 25.8, - 26, - 26.2, - 26.3, - 26.6, - 26.8, - 27.3, - 27.7, - 28, - 28, - 28.2, - 28.4, - 29.1, - 30.3, - 30.8, - 31.8, - 32.9, - 33.4, - 33.7, - 34.1, - 34.7, - 35.2, - 35.4, - 36, - 36.1, - 37.4, - 40.3, - 40.5, - 38.8, - 38.1, - 38.4, - 38.8, - 39.6, - 40, - 40.9, - 41.9, - 42.7, - 43.7, - 44.6, - 45.6, - 46.6, - 47.7, - 48.9, - 50.1, - 51, - 52.1, - 53.4, - 54.6, - 55.8, - 57.2, - 59.3, - 60.9, - 63.6, - 65, - 66.7, - 69.1, - 73.9, - 77, - 78.4, - 80.4, - 86.3, - 89.7, - 91.1, - 92.5, - 94, - 95.5, - 96.7, - 98, - 99.5, - 100.7, - 102, - 105.8, - 111.3, - 114.4, - 120.3, - 125.9, - 126.3, - 125.7, - 124.9, - 122.6, - 120.5, - 113.8, - 98.9, - 97.2, - 96.5, - 96.6, - 96.8, - 96.4, - 95.3, - 94, - 90.7, - 86.3, - 84.2, - 82.8, - 81.7, - 80.4, - 79.6, - 79, - 78.2, - 77, - 75.2, - 73.8, - 72.8, - 72, - 70.7, - 69.6, - 69.1, - 68.6, - 67.2, - 65.6, - 63.8, - 62, - 60.5, - 58.6, - 57.1, - 55.7, - 54.6, - 53.7, - 52.8, - 52.1, - 51.5, - 50.9, - 50.3, - 49.3, - 48.5, - 47.7, - 46.9, - 45.8, - 44.8, - 44.3, - 44.7, - 45, - 44.8, - 44.4, - 43.8, - 42.8, - 41.3, - 40, - 39, - 38.3, - 37.8, - 37.3, - 36.9, - 36.3, - 35.4, - 34.5, - 33.7, - 32.8, - 32.1, - 31.7, - 31.3, - 31, - 30.9, - 30.8, - 30.7, - 30.5, - 30.5, - 30.3, - 30.1, - 29.9, - 29.6, - 29.4, - 29.2, - 29.1, - 28.9, - 28.6, - 28.3, - 28.2, - 28.1, - 28.2, - 28.1, - 27.7, - 27.1, - 26.6, - 26.3, - 26, - 26, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 25.2, - 25.2, - 25.3, - 25.4, - 25.4, - 25.3, - 25, - 24.8, - 24.7, - 24.6, - 24.5, - 24.4, - 24.4, - 24.4, - 24.4, - 24.4, - 24.3, - 24.1, - 24, - 23.9, - 23.8, - 23.8, - 23.8, - 23.9, - 23.9, - 23.9, - 23.8, - 23.8, - 23.8, - 23.8, - 23.8, - 23.7, - 23.7, - 21.5, - 21.6, - 21.7, - 21.7, - 21.7, - 21.8, - 21.9, - 22.2, - 22.9, - 23.5, - 23.9, - 24.1, - 24.2, - 24.3, - 24.3, - 24.2, - 24, - 23.5, - 22.7, - 22.5, - 22.5, - 22.3, - 22.2, - 22.3, - 22.4, - 22.5, - 22.6, - 22.9, - 23.1, - 23.3, - 23.5, - 23.7, - 23.8, - 24, - 24.1, - 24.3, - 24.5, - 24.8, - 25.1, - 25.5, - 25.8, - 25.8, - 25.9, - 26.1, - 26.2, - 26.5, - 26.6, - 26.8, - 27.1, - 27.4, - 27.5, - 27.6, - 28, - 28.6, - 29.6, - 30.5, - 31.2, - 32.5, - 33.2, - 33.4, - 33.6, - 34, - 34.5, - 35.2, - 35.7, - 36.2, - 36.3, - 37, - 38.4, - 38.1, - 37.6, - 37.9, - 38.3, - 38.7, - 39.4, - 40.1, - 41, - 41.8, - 42.4, - 43.7, - 44.8, - 45.7, - 46.7, - 47.9, - 49.1, - 50.3, - 51.4, - 52.5, - 53.8, - 55.1, - 56.1, - 57.4, - 59.3, - 62.3, - 64.7, - 65.8, - 67.3, - 71.3, - 75.6, - 77.3, - 78.6, - 81.7, - 89.6, - 91.7, - 92.4, - 93.3, - 94.5, - 96.2, - 97.7, - 99, - 100.6, - 101.8, - 103, - 106.2, - 111.2, - 115.8, - 119.4, - 125.5, - 125.9, - 125.6, - 124.7, - 122.5, - 120.7, - 120, - 109.5, - 98, - 97.4, - 97.8, - 97.8, - 97.1, - 96.1, - 95.1, - 94.2, - 89.5, - 85.5, - 84, - 82.3, - 81.4, - 80.6, - 79.6, - 78.7, - 77.7, - 76.3, - 74.9, - 74.2, - 73, - 71.2, - 70.7, - 70.2, - 69.3, - 68, - 66.1, - 63.9, - 61.7, - 60, - 58.3, - 56.7, - 55.3, - 54.1, - 53.4, - 52.6, - 52, - 51.3, - 50.8, - 50.1, - 49.3, - 48.3, - 47.2, - 46.4, - 45.7, - 45.4, - 45.7, - 45.8, - 45.5, - 45.1, - 44.5, - 43.8, - 42.6, - 41.1, - 39.9, - 38.9, - 38.2, - 37.7, - 37.2, - 36.8, - 36.2, - 35.5, - 34.8, - 34.1, - 33.2, - 32.4, - 31.9, - 31.5, - 31.1, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.4, - 30.1, - 29.9, - 29.7, - 29.4, - 29.3, - 29.1, - 28.9, - 28.7, - 28.3, - 28.2, - 28.1, - 28.3, - 28.1, - 27.8, - 27.3, - 26.8, - 26.5, - 26.2, - 25.9, - 25.9, - 25.8, - 25.6, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.5, - 25.5, - 25.4, - 25.2, - 24.9, - 24.7, - 24.6, - 24.6, - 24.5, - 24.6, - 24.6, - 24.6, - 24.7, - 24.6, - 24.5, - 24.2, - 24, - 23.9, - 23.9, - 23.9, - 23.9, - 24, - 24, - 23.9, - 23.9, - 23.9, - 23.8, - 23.8, - 23.8, - 23.8, - 21.6, - 21.8, - 21.9, - 22, - 22.1, - 22.1, - 22.2, - 22.5, - 22.9, - 23.5, - 23.8, - 24, - 24.2, - 24.3, - 24.3, - 24.1, - 23.7, - 23, - 22.3, - 22, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.6, - 22.9, - 23.1, - 23.1, - 23.2, - 23.5, - 23.7, - 23.9, - 24.1, - 24.3, - 24.5, - 24.6, - 24.8, - 25, - 25.3, - 25.5, - 25.7, - 25.9, - 26.1, - 26.3, - 26.5, - 26.6, - 26.7, - 26.8, - 27.1, - 27.3, - 27.6, - 28, - 28.8, - 29.8, - 30.4, - 31.1, - 32.2, - 32.6, - 32.6, - 33, - 33.7, - 34.1, - 34.9, - 36.1, - 36.2, - 36.3, - 36.5, - 36.8, - 37.1, - 37.4, - 37.7, - 38.1, - 38.5, - 39.3, - 40, - 40.5, - 41.1, - 42, - 43.9, - 45.2, - 46.1, - 47.1, - 48.2, - 49.6, - 50.7, - 51.9, - 52.9, - 54, - 55.2, - 56.3, - 57.4, - 59.8, - 63.2, - 65.4, - 66.7, - 69.2, - 74, - 76.6, - 77.6, - 78.9, - 82.1, - 90.2, - 92.3, - 93.3, - 94.7, - 95.7, - 96.8, - 98.3, - 99.6, - 101.6, - 102.8, - 103.9, - 105.4, - 110, - 117.5, - 120.8, - 124.8, - 125.4, - 125.3, - 124.7, - 122.8, - 121.3, - 120.4, - 119.1, - 104.6, - 100.1, - 98.8, - 98.6, - 97.7, - 96.8, - 95.8, - 94.8, - 92.8, - 88.7, - 86.3, - 84.2, - 82.5, - 81.6, - 80.4, - 79.4, - 78.3, - 77.4, - 76.3, - 75.2, - 74.1, - 73.6, - 71.9, - 70.8, - 69.8, - 68.5, - 66.2, - 63.3, - 61.2, - 59.5, - 57.8, - 56.2, - 55, - 54.2, - 53.4, - 52.7, - 51.8, - 51.1, - 50.6, - 49.8, - 49.1, - 48.3, - 47.2, - 46.8, - 46.5, - 46.8, - 46.9, - 46.5, - 46, - 45.4, - 44.6, - 43.7, - 42.3, - 40.9, - 39.8, - 38.8, - 38.2, - 37.8, - 37.4, - 36.9, - 36.4, - 35.7, - 35.1, - 34.4, - 33.5, - 32.7, - 32, - 31.6, - 31.4, - 31.4, - 31.3, - 31.1, - 30.9, - 30.7, - 30.4, - 30.2, - 29.9, - 29.7, - 29.5, - 29.4, - 29.1, - 29, - 28.8, - 28.4, - 28.3, - 28.2, - 28.3, - 28.1, - 27.9, - 27.6, - 27.2, - 26.8, - 26.4, - 26.2, - 26.1, - 26, - 25.8, - 25.6, - 25.5, - 25.5, - 25.5, - 25.6, - 25.6, - 25.5, - 25.4, - 25.3, - 24.9, - 24.8, - 24.7, - 24.5, - 24.6, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.5, - 24.2, - 24, - 23.9, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 23.9, - 23.9, - 23.9, - 23.9, - 21.6, - 21.9, - 22.3, - 22.5, - 22.6, - 22.7, - 22.8, - 23.1, - 23.3, - 23.6, - 23.9, - 24, - 24.2, - 24.2, - 24.2, - 23.9, - 23.4, - 22.5, - 21.9, - 21.8, - 22, - 22.1, - 22.1, - 22.2, - 22.5, - 22.8, - 23, - 23.2, - 23.3, - 23.4, - 23.6, - 23.7, - 24, - 24.2, - 24.4, - 24.6, - 24.8, - 24.9, - 25.1, - 25.3, - 25.5, - 25.7, - 25.8, - 26, - 26.2, - 26.3, - 26.4, - 26.5, - 26.7, - 26.9, - 27.1, - 27.4, - 27.8, - 28.5, - 29.6, - 30, - 30.4, - 30.8, - 30.9, - 31.1, - 32.1, - 33.5, - 34, - 34.3, - 35.4, - 35.9, - 36, - 36.4, - 36.5, - 36.9, - 37.2, - 37.5, - 37.8, - 38.2, - 39, - 39.6, - 40.1, - 40.7, - 42, - 44.5, - 45.7, - 46.6, - 47.4, - 48.4, - 49.6, - 50.6, - 51.7, - 52.8, - 53.9, - 55.2, - 56.4, - 57.7, - 60.9, - 64.5, - 66.6, - 69.4, - 72.2, - 75.7, - 77.3, - 78.4, - 80.2, - 85.1, - 91.2, - 92.5, - 93.8, - 95.3, - 96.5, - 97.4, - 98.5, - 99.8, - 101.6, - 102.7, - 104, - 104.9, - 107, - 116.5, - 121.8, - 123.9, - 124.7, - 125, - 124.9, - 123.5, - 122.3, - 121, - 120.3, - 118.6, - 111.3, - 99.9, - 99.3, - 98.4, - 97.4, - 96.2, - 95.2, - 94.2, - 92.9, - 89.4, - 86.1, - 84.1, - 82.5, - 81.3, - 79.9, - 78.8, - 78.2, - 77.4, - 76.5, - 75.9, - 75, - 72.8, - 71.1, - 69.8, - 68.4, - 66.4, - 63.9, - 61.2, - 59.5, - 57.9, - 56.8, - 55.7, - 54.7, - 53.7, - 52.9, - 52.1, - 51.4, - 50.8, - 50.2, - 49.7, - 48.9, - 48, - 47.9, - 47.8, - 47.8, - 47.5, - 47, - 46.3, - 45.5, - 44.6, - 43.5, - 42.1, - 40.7, - 39.6, - 38.6, - 38.1, - 37.7, - 37.3, - 36.8, - 36.2, - 35.6, - 35.1, - 34.3, - 33.5, - 32.8, - 32.2, - 31.8, - 31.7, - 31.5, - 31.4, - 31.2, - 31, - 30.7, - 30.5, - 30.2, - 30, - 29.7, - 29.6, - 29.4, - 29.2, - 29.1, - 28.7, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28, - 27.9, - 27.5, - 27, - 26.5, - 26.3, - 26.2, - 26, - 25.8, - 25.8, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.5, - 25.4, - 25.3, - 25, - 24.8, - 24.7, - 24.6, - 24.7, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.7, - 24.4, - 24.1, - 24, - 24.1, - 24.1, - 24.1, - 24.1, - 24, - 24, - 24, - 24, - 24, - 23.9, - 23.9, - 21.6, - 22, - 22.7, - 23, - 23.2, - 23.3, - 23.4, - 23.5, - 23.7, - 23.8, - 23.9, - 24, - 24.1, - 24.2, - 24.1, - 23.5, - 22.6, - 21.8, - 21.7, - 21.9, - 21.9, - 22, - 22.2, - 22.5, - 22.7, - 22.9, - 23, - 23.2, - 23.3, - 23.4, - 23.6, - 23.8, - 24, - 24.2, - 24.4, - 24.6, - 24.8, - 24.9, - 25.1, - 25.3, - 25.4, - 25.6, - 25.7, - 25.8, - 25.9, - 26, - 26.5, - 26.8, - 27, - 26.9, - 27, - 27.2, - 27.5, - 27.7, - 28.6, - 29.1, - 29.3, - 29.6, - 30, - 30.8, - 32.2, - 33.5, - 34, - 34.3, - 34.9, - 35.4, - 35.7, - 36.1, - 36.2, - 36.7, - 37, - 37.3, - 37.6, - 38, - 38.9, - 39.5, - 40.1, - 40.7, - 42.1, - 44.7, - 45.8, - 46.8, - 47.5, - 48.4, - 49.6, - 50.5, - 51.6, - 52.7, - 53.9, - 55.2, - 56.4, - 58.2, - 61.9, - 65.5, - 69.1, - 73.7, - 76, - 77.1, - 78.1, - 80.2, - 84.9, - 90.2, - 92.3, - 93.2, - 94.2, - 95.3, - 96.5, - 97.5, - 98.6, - 99.6, - 101, - 102.3, - 103.4, - 104.4, - 105, - 114.3, - 121.5, - 122.3, - 123.7, - 124.1, - 123.8, - 123.5, - 122.7, - 122.1, - 120.5, - 120.2, - 117.8, - 106, - 99.7, - 99, - 97.7, - 96.7, - 95.8, - 94.7, - 94.1, - 92.2, - 87.2, - 85.4, - 83.4, - 81.8, - 80.7, - 79.7, - 79.1, - 78.4, - 77.9, - 77.5, - 75.2, - 72.7, - 70.8, - 69.6, - 68.3, - 67.3, - 66.1, - 62.9, - 60.4, - 58.7, - 57.7, - 56.6, - 55.5, - 54.3, - 53.3, - 52.5, - 51.7, - 51.2, - 51, - 50.3, - 49.3, - 48.9, - 49.1, - 48.8, - 48.3, - 47.7, - 46.9, - 46.1, - 45.3, - 44.2, - 43.2, - 41.9, - 40.3, - 39.3, - 38.5, - 37.9, - 37.4, - 37.1, - 36.3, - 35.6, - 35.1, - 34.6, - 33.9, - 33.2, - 32.7, - 32.2, - 32, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.8, - 30.5, - 30.3, - 30.1, - 29.9, - 29.7, - 29.5, - 29.3, - 29.1, - 28.7, - 28.4, - 28.4, - 28.3, - 28.4, - 28.2, - 28, - 27.9, - 27.7, - 27.1, - 26.7, - 26.4, - 26.2, - 26, - 26, - 26, - 25.8, - 25.8, - 25.7, - 25.7, - 25.6, - 25.4, - 25.3, - 25.2, - 25, - 24.9, - 24.7, - 24.7, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.8, - 24.6, - 24.2, - 24, - 24.1, - 24.1, - 24.1, - 24.2, - 24.2, - 24.1, - 24.1, - 24.1, - 24, - 24, - 23.9, - 21.6, - 22.1, - 22.9, - 23.4, - 23.5, - 23.6, - 23.7, - 23.7, - 23.8, - 23.9, - 24, - 24.1, - 24.1, - 24, - 23.7, - 22.8, - 21.8, - 21.6, - 21.7, - 21.8, - 21.9, - 22.1, - 22.3, - 22.5, - 22.7, - 22.8, - 22.9, - 23, - 23.2, - 23.4, - 23.6, - 23.8, - 24, - 24.2, - 24.4, - 24.6, - 24.7, - 24.9, - 25, - 25.1, - 25.3, - 25.5, - 25.5, - 25.5, - 25.8, - 26.2, - 26.8, - 27.3, - 27.5, - 27.3, - 27.4, - 27.6, - 27.9, - 28, - 28.3, - 28.5, - 28.5, - 29.1, - 30.2, - 31.4, - 33, - 33.7, - 34, - 34.5, - 35, - 34.8, - 35.6, - 35.6, - 35.9, - 36.3, - 36.8, - 37.2, - 37.5, - 38.2, - 38.8, - 39.5, - 40.2, - 41, - 42.1, - 44.4, - 45.5, - 46.6, - 47.5, - 48.4, - 49.5, - 50.5, - 51.7, - 52.9, - 54.2, - 55.3, - 56.4, - 58.4, - 62, - 66.6, - 72.9, - 76.1, - 77.1, - 78, - 79, - 82.7, - 89.1, - 91.9, - 93.1, - 93.9, - 94.8, - 95.6, - 96.5, - 97.6, - 98.6, - 99.4, - 100.7, - 101.7, - 102.6, - 103.6, - 104, - 108.9, - 116.9, - 121.3, - 122.7, - 123.2, - 123.1, - 123.1, - 122.9, - 122.4, - 121.4, - 120.4, - 119.7, - 114.7, - 101.1, - 99.3, - 98, - 97, - 96.1, - 95.4, - 94.8, - 92.9, - 88.3, - 86.2, - 84.4, - 82.6, - 81.8, - 80.8, - 80.2, - 79.4, - 78.8, - 78.2, - 75.7, - 72.7, - 70.7, - 69.9, - 69.1, - 68.5, - 67.5, - 65.2, - 62.1, - 60, - 58.6, - 57.4, - 56.2, - 54.9, - 53.8, - 53, - 52.4, - 51.9, - 51.2, - 50.5, - 50.1, - 50, - 49.7, - 49, - 48.2, - 47.3, - 46.3, - 45.4, - 44.5, - 43.6, - 43, - 41.7, - 40.1, - 39.1, - 38.3, - 37.8, - 37.2, - 36.9, - 35.8, - 35.1, - 34.5, - 34.1, - 33.6, - 33, - 32.5, - 32.2, - 31.9, - 31.7, - 31.5, - 31.3, - 31.2, - 31.1, - 30.9, - 30.5, - 30.4, - 30.2, - 30, - 29.7, - 29.6, - 29.4, - 29.3, - 28.9, - 28.5, - 28.4, - 28.4, - 28.4, - 28.3, - 28.1, - 28, - 27.8, - 27.4, - 26.9, - 26.6, - 26.3, - 26.2, - 26.2, - 26.1, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.3, - 25.2, - 25.1, - 25, - 24.8, - 24.8, - 24.9, - 25, - 25.1, - 25.2, - 25.3, - 25.3, - 25.1, - 25, - 24.9, - 24.8, - 24.4, - 24.1, - 24.1, - 24.2, - 24.2, - 24.2, - 24.2, - 24.2, - 24.1, - 24.1, - 24.1, - 24, - 24, - 21.6, - 22.2, - 23.1, - 23.6, - 23.7, - 23.8, - 23.8, - 23.9, - 24, - 24, - 24, - 24, - 23.9, - 23.6, - 23, - 22, - 21.5, - 21.5, - 21.6, - 21.7, - 21.9, - 22.1, - 22.3, - 22.4, - 22.5, - 22.7, - 22.8, - 22.9, - 23.1, - 23.3, - 23.6, - 23.8, - 24, - 24.2, - 24.3, - 24.5, - 24.6, - 24.8, - 24.9, - 25, - 25.1, - 25.2, - 25.3, - 25.5, - 25.8, - 26.2, - 26.7, - 27.3, - 27.6, - 27.7, - 27.8, - 28.1, - 28.4, - 28.7, - 29.1, - 29.3, - 29.5, - 29.6, - 30.1, - 31.2, - 33, - 33.7, - 34, - 34.3, - 34.5, - 34.5, - 35.2, - 35.7, - 35.9, - 36.4, - 36.8, - 37.2, - 37.6, - 38.3, - 38.9, - 39.6, - 40.3, - 41.1, - 42, - 43.4, - 45, - 46.5, - 47.6, - 48.5, - 49.7, - 50.9, - 52, - 53.4, - 54.7, - 55.9, - 56.9, - 58.9, - 62.2, - 67.4, - 73.9, - 76, - 77.2, - 78.6, - 81, - 87.6, - 91.2, - 92.8, - 93.7, - 94.3, - 94.9, - 95.7, - 96.5, - 97.6, - 98.4, - 99.1, - 100.1, - 101.1, - 101.8, - 102.7, - 103.1, - 103.6, - 111.6, - 119.2, - 122.1, - 122.6, - 122.7, - 122.8, - 122.8, - 122.6, - 122, - 121.1, - 120.7, - 117.6, - 106.7, - 99.7, - 98.6, - 97.6, - 97.1, - 96.3, - 95.1, - 94, - 91.6, - 87.5, - 86.1, - 84.4, - 83.1, - 82.2, - 81.4, - 80.3, - 79.5, - 78.5, - 76.3, - 73.1, - 71.3, - 70.9, - 70.4, - 69.6, - 68.7, - 67.3, - 64.7, - 62.1, - 59.8, - 58, - 56.8, - 55.6, - 54.6, - 54, - 53.5, - 52.6, - 51.6, - 51.4, - 50.9, - 50.5, - 49.9, - 48.8, - 48, - 46.9, - 45.9, - 44.9, - 44, - 43.5, - 43, - 42.1, - 40.6, - 39.5, - 38.5, - 37.8, - 37.3, - 36.8, - 35.7, - 34.9, - 34.2, - 34, - 33.6, - 32.8, - 32.5, - 32.3, - 32.1, - 31.9, - 31.8, - 31.2, - 31.1, - 31, - 30.8, - 30.6, - 30.4, - 30.1, - 30, - 29.8, - 29.6, - 29.5, - 29.3, - 29.2, - 28.5, - 28.4, - 28.4, - 28.5, - 28.4, - 28.1, - 27.9, - 27.8, - 27.5, - 27, - 26.6, - 26.3, - 26.4, - 26.3, - 26.1, - 26, - 25.9, - 25.7, - 25.6, - 25.4, - 25.3, - 25.2, - 25.1, - 24.9, - 24.9, - 24.9, - 25.1, - 25.2, - 25.4, - 25.6, - 25.6, - 25.7, - 25.4, - 25.1, - 25, - 24.9, - 24.7, - 24.4, - 24.2, - 24.2, - 24.3, - 24.3, - 24.3, - 24.3, - 24.2, - 24.1, - 24.1, - 24.1, - 24, - 21.6, - 22.3, - 23.2, - 23.7, - 23.8, - 23.9, - 23.9, - 24, - 24, - 24, - 23.9, - 23.8, - 23.5, - 22.9, - 22.2, - 21.5, - 21.3, - 21.4, - 21.6, - 21.7, - 21.9, - 22, - 22.1, - 22.2, - 22.4, - 22.5, - 22.7, - 22.8, - 23.1, - 23.4, - 23.6, - 23.8, - 24, - 24.1, - 24.3, - 24.4, - 24.5, - 24.7, - 24.8, - 24.9, - 25, - 25.1, - 25.3, - 25.4, - 25.5, - 25.9, - 26.4, - 27, - 27.6, - 28.1, - 28.4, - 28.8, - 29.1, - 29.5, - 30.1, - 30.3, - 30.6, - 30.7, - 31, - 32.1, - 33.1, - 33.5, - 33.8, - 34, - 34.3, - 34.7, - 35.3, - 35.8, - 36, - 36.3, - 36.6, - 37, - 37.4, - 37.9, - 38.5, - 39.4, - 40.5, - 41.1, - 42.2, - 43.5, - 44.9, - 46.4, - 47.5, - 48.9, - 49.9, - 51.1, - 52.4, - 53.8, - 55.2, - 56.5, - 57.9, - 59.8, - 63.1, - 67.9, - 74, - 76, - 77.4, - 79, - 85.2, - 90.6, - 92.8, - 93.7, - 93.9, - 94.2, - 94.7, - 95.5, - 96.4, - 97, - 97.8, - 98.7, - 99.4, - 100.5, - 101.1, - 101.7, - 102.3, - 102.1, - 105.5, - 114.8, - 119.8, - 122.3, - 122.3, - 122.3, - 122.5, - 122.5, - 121.7, - 121.9, - 121.7, - 120.4, - 110.8, - 100.4, - 99.8, - 99.2, - 98.1, - 96.9, - 95.3, - 94.4, - 93.7, - 91.2, - 88.2, - 85.9, - 84.9, - 83.4, - 82, - 80.9, - 79.9, - 78.7, - 77.3, - 75.6, - 73.8, - 72.4, - 71.5, - 70.4, - 69.5, - 68.4, - 66.1, - 63.3, - 61, - 58.6, - 57.4, - 56.5, - 55.7, - 55, - 54, - 53.1, - 52.4, - 52.1, - 51.5, - 50.9, - 50.2, - 49.3, - 48.3, - 47.2, - 46.2, - 45.2, - 44.3, - 43.9, - 43.3, - 42.3, - 41.2, - 39.8, - 38.6, - 37.9, - 37.5, - 36.8, - 35.7, - 35.2, - 34.8, - 34.4, - 33.8, - 33.1, - 33, - 32.9, - 32.7, - 32.5, - 32, - 31.4, - 31.1, - 30.9, - 30.7, - 30.6, - 30.3, - 30.1, - 29.9, - 29.7, - 29.5, - 29.4, - 29.2, - 29, - 28.6, - 28.4, - 28.4, - 28.5, - 28.4, - 28.1, - 27.9, - 27.8, - 27.3, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.3, - 25.3, - 25.1, - 25, - 25.1, - 25.2, - 25.3, - 25.5, - 25.6, - 25.7, - 25.9, - 25.8, - 25.7, - 25.2, - 25, - 24.9, - 24.8, - 24.6, - 24.3, - 24.2, - 24.3, - 24.4, - 24.4, - 24.4, - 24.3, - 24.2, - 24.2, - 24.1, - 24.1, - 21.6, - 22.3, - 23.2, - 23.7, - 23.9, - 23.9, - 24, - 24, - 24, - 23.9, - 23.7, - 23.4, - 22.8, - 22.1, - 21.6, - 21.3, - 21.2, - 21.5, - 21.6, - 21.6, - 21.8, - 21.8, - 21.9, - 22.1, - 22.2, - 22.4, - 22.6, - 22.8, - 23, - 23.3, - 23.5, - 23.6, - 23.8, - 24, - 24.2, - 24.3, - 24.4, - 24.5, - 24.6, - 24.7, - 24.9, - 25, - 25.1, - 25.2, - 25.4, - 25.9, - 26.3, - 26.9, - 27.7, - 28.4, - 28.9, - 29.4, - 29.7, - 30.2, - 30.8, - 31.3, - 31.6, - 32, - 32.5, - 33, - 33.4, - 33.7, - 33.9, - 34.2, - 34.5, - 34.9, - 35.2, - 35.5, - 35.8, - 36.1, - 36.5, - 36.7, - 37.1, - 37.4, - 38.3, - 39.3, - 40.6, - 41.7, - 43.1, - 44.7, - 45.6, - 46.5, - 47.6, - 48.9, - 50, - 51.3, - 52.7, - 54.2, - 55.5, - 56.9, - 58.8, - 61.5, - 66.1, - 70.8, - 75.1, - 76.2, - 77.3, - 79.8, - 87, - 91.1, - 92.5, - 93.4, - 93.5, - 93.9, - 94.5, - 95.1, - 95.8, - 96.5, - 97.3, - 98.6, - 99.6, - 100, - 100.6, - 101.4, - 101.9, - 101.7, - 101.8, - 110.7, - 116.3, - 120.9, - 121.1, - 120.9, - 121, - 121.1, - 121.7, - 122.2, - 121.7, - 120.7, - 117.4, - 102.7, - 101.4, - 100.1, - 98.7, - 97.3, - 96, - 95.2, - 94.3, - 93.4, - 91.1, - 87, - 86.2, - 85, - 83.4, - 81.6, - 80.7, - 79.4, - 78.6, - 78, - 77.2, - 75.8, - 73.6, - 71.2, - 70.2, - 69, - 66.4, - 63.7, - 61.6, - 59.5, - 58.4, - 57.5, - 56.6, - 55.4, - 54.5, - 53.9, - 53.4, - 52.7, - 52.1, - 51.5, - 50.4, - 49.4, - 48.4, - 47.5, - 46.6, - 45.7, - 44.8, - 44.1, - 43.4, - 42.3, - 40.8, - 39.5, - 38.3, - 37.8, - 37.4, - 36.9, - 36.5, - 36.1, - 35.7, - 34.9, - 34, - 33.5, - 33.7, - 33.7, - 33.6, - 33, - 32.2, - 31.7, - 31.3, - 30.9, - 30.7, - 30.5, - 30.3, - 30, - 29.8, - 29.6, - 29.4, - 29.3, - 29.2, - 29, - 28.6, - 28.4, - 28.4, - 28.5, - 28.3, - 28.1, - 27.9, - 27.7, - 27.2, - 26.9, - 26.8, - 26.7, - 26.4, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.5, - 25.3, - 25.2, - 25.1, - 25.1, - 25.3, - 25.4, - 25.5, - 25.6, - 25.8, - 25.9, - 26, - 26, - 25.8, - 25.4, - 25.2, - 25, - 24.9, - 24.8, - 24.5, - 24.3, - 24.4, - 24.4, - 24.4, - 24.4, - 24.4, - 24.3, - 24.3, - 24.2, - 24.1, - 21.5, - 22.3, - 23.2, - 23.6, - 23.8, - 23.9, - 23.9, - 23.9, - 23.8, - 23.6, - 23.2, - 22.6, - 21.9, - 21.4, - 21.2, - 21.1, - 21.2, - 21.4, - 21.4, - 21.5, - 21.6, - 21.8, - 21.9, - 22, - 22.2, - 22.3, - 22.6, - 22.8, - 23, - 23.2, - 23.4, - 23.5, - 23.6, - 23.9, - 24, - 24.1, - 24.3, - 24.4, - 24.5, - 24.6, - 24.7, - 24.8, - 24.9, - 25, - 25.3, - 25.8, - 26.2, - 26.7, - 27.5, - 28.2, - 29, - 29.4, - 29.9, - 30.6, - 31.4, - 31.8, - 32.1, - 32.5, - 32.9, - 33.1, - 33.4, - 33.7, - 33.9, - 34.1, - 34.4, - 34.8, - 35, - 35.4, - 35.7, - 36, - 36.3, - 36.6, - 37, - 37.6, - 38.4, - 39.4, - 40.6, - 42.1, - 43.9, - 45.6, - 46.7, - 47, - 47.8, - 49, - 50.3, - 51.7, - 53.1, - 54.5, - 55.7, - 57.6, - 60.7, - 65.2, - 72.5, - 74.5, - 75.9, - 76.9, - 77.8, - 79.7, - 87.5, - 90.5, - 92, - 92.5, - 93.1, - 93.6, - 94.2, - 94.6, - 95.2, - 95.9, - 96.6, - 98.2, - 99.6, - 99.8, - 100.5, - 101.7, - 102.5, - 101.8, - 101.2, - 103.8, - 108.7, - 115.1, - 117.3, - 117.9, - 117.7, - 118.5, - 120.3, - 122.1, - 121.8, - 120.9, - 120.5, - 112.3, - 106.8, - 100.5, - 99.2, - 98, - 96.9, - 95.7, - 94.6, - 93.7, - 92.1, - 88.5, - 87.6, - 86.8, - 86, - 83.1, - 81.8, - 80.6, - 79.7, - 79.4, - 78.6, - 77.7, - 75.6, - 72.3, - 70.5, - 69.2, - 66.8, - 64.4, - 62.7, - 61, - 59.5, - 58.3, - 56.9, - 55.8, - 55.4, - 54.8, - 54.1, - 53.2, - 52.2, - 51.2, - 50.3, - 49.3, - 48.2, - 47.3, - 46.5, - 45.7, - 44.9, - 44.2, - 43.2, - 41.3, - 39.9, - 38.9, - 38.4, - 37.8, - 37.6, - 37.4, - 37.1, - 36.8, - 36.3, - 35.2, - 34.4, - 34.3, - 34.6, - 34.7, - 34.4, - 33.6, - 32.7, - 32, - 31.4, - 31, - 30.7, - 30.4, - 30.3, - 30.1, - 29.9, - 29.7, - 29.5, - 29.3, - 29.2, - 29, - 28.6, - 28.4, - 28.4, - 28.5, - 28.4, - 28.3, - 28.2, - 27.8, - 27.4, - 27.3, - 27.2, - 27, - 26.5, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 25.4, - 25.3, - 25.2, - 25.1, - 25.3, - 25.5, - 25.6, - 25.7, - 25.8, - 25.9, - 26.1, - 26.1, - 26.1, - 25.9, - 25.6, - 25.3, - 25.1, - 24.9, - 24.8, - 24.6, - 24.4, - 24.5, - 24.5, - 24.5, - 24.5, - 24.4, - 24.4, - 24.3, - 24.2, - 24.2, - 21.5, - 22.2, - 23.1, - 23.6, - 23.8, - 23.9, - 23.9, - 23.8, - 23.6, - 23.2, - 22.5, - 21.8, - 21.3, - 21.2, - 21, - 21.1, - 21.1, - 21.3, - 21.3, - 21.5, - 21.6, - 21.8, - 21.9, - 22.1, - 22.2, - 22.4, - 22.6, - 22.8, - 23, - 23.2, - 23.3, - 23.3, - 23.5, - 23.7, - 23.8, - 24, - 24.2, - 24.3, - 24.4, - 24.5, - 24.6, - 24.7, - 24.8, - 25, - 25.2, - 25.5, - 26, - 26.6, - 27.2, - 27.9, - 28.8, - 29.4, - 29.8, - 30.5, - 31.2, - 31.5, - 31.7, - 32.2, - 32.5, - 32.8, - 33.2, - 33.5, - 33.8, - 34.1, - 34.4, - 34.8, - 35.1, - 35.5, - 35.8, - 36.1, - 36.4, - 36.8, - 37.2, - 37.7, - 38.5, - 39.3, - 40.5, - 42, - 43.5, - 45.7, - 46.9, - 47.5, - 48.3, - 49.3, - 50.6, - 52, - 53.4, - 54.7, - 57.1, - 61.5, - 64.2, - 69, - 74.6, - 75.6, - 76.6, - 77.5, - 79.8, - 82.4, - 88.1, - 90.9, - 92, - 92.4, - 92.9, - 93.3, - 93.8, - 94.2, - 94.7, - 95.3, - 96.1, - 97.2, - 98.3, - 99.2, - 100.7, - 101.9, - 102.3, - 101.6, - 100.8, - 100.6, - 101.4, - 103.5, - 105.4, - 109.3, - 111, - 114, - 117.4, - 122, - 122.1, - 121.3, - 121.2, - 118.8, - 112.4, - 102.8, - 101.5, - 99.5, - 97.1, - 96, - 94.9, - 94.1, - 93.6, - 91.9, - 91.1, - 89.7, - 88.7, - 87, - 84.9, - 82.1, - 81.1, - 80.4, - 79.5, - 78.4, - 76.3, - 72.9, - 71, - 69.8, - 68, - 65.6, - 63.7, - 62.2, - 60.6, - 58.5, - 57.4, - 56.8, - 56.2, - 55.3, - 54.4, - 53.3, - 52.2, - 51.1, - 50.1, - 49, - 47.7, - 46.9, - 46.1, - 45.5, - 44.9, - 44.1, - 42.8, - 40.5, - 39.7, - 39.4, - 39.1, - 38.5, - 38.2, - 37.8, - 37.4, - 37, - 36.4, - 35.6, - 35.4, - 35.5, - 35.6, - 35.5, - 35, - 34.1, - 33.2, - 32.4, - 31.6, - 31.1, - 30.7, - 30.4, - 30.2, - 30.1, - 29.9, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 28.6, - 28.4, - 28.4, - 28.5, - 28.5, - 28.5, - 28.5, - 28.3, - 27.7, - 27.6, - 27.5, - 27.1, - 26.6, - 26.2, - 26, - 25.8, - 25.8, - 25.7, - 25.5, - 25.4, - 25.3, - 25.3, - 25.3, - 25.4, - 25.6, - 25.8, - 25.9, - 26, - 26.1, - 26.1, - 26.2, - 26.1, - 25.9, - 25.6, - 25.3, - 25.1, - 24.9, - 24.8, - 24.6, - 24.4, - 24.5, - 24.6, - 24.7, - 24.5, - 24.5, - 24.4, - 24.3, - 24.3, - 24.2, - 21.4, - 21.9, - 22.9, - 23.5, - 23.8, - 23.8, - 23.8, - 23.7, - 23.4, - 22.7, - 21.8, - 21.3, - 21.1, - 21, - 20.9, - 21, - 21, - 21.1, - 21.3, - 21.4, - 21.6, - 21.8, - 21.9, - 22.1, - 22.2, - 22.4, - 22.6, - 22.8, - 23, - 23.1, - 23.2, - 23.3, - 23.5, - 23.6, - 23.8, - 24, - 24.1, - 24.1, - 24.2, - 24.4, - 24.5, - 24.7, - 24.8, - 25, - 25.2, - 25.4, - 25.8, - 26.3, - 26.8, - 27.4, - 28.1, - 28.9, - 29.5, - 29.9, - 30.4, - 30.7, - 30.9, - 31.1, - 31.4, - 32.3, - 33.1, - 33.6, - 33.9, - 34.2, - 34.4, - 34.8, - 35.1, - 35.4, - 35.8, - 36.1, - 36.5, - 36.9, - 37.3, - 37.8, - 38.5, - 39.3, - 40.3, - 41.5, - 43, - 45.5, - 46.8, - 47.7, - 48.5, - 49.7, - 50.8, - 52, - 53.5, - 54.9, - 59.4, - 65.1, - 69, - 72.3, - 75.3, - 76.3, - 77.3, - 78.2, - 81.2, - 87, - 89.3, - 91, - 92, - 92.5, - 92.7, - 92.9, - 93.3, - 93.7, - 94, - 94.4, - 95.6, - 96.6, - 97.4, - 98.4, - 101.7, - 103, - 102.3, - 101, - 99.9, - 100, - 100.7, - 101.2, - 101.6, - 102.5, - 103.7, - 109.4, - 115.1, - 120.7, - 122, - 121.9, - 122.4, - 121.6, - 117.8, - 115.5, - 115.4, - 112.6, - 103.9, - 96.8, - 95.6, - 94.7, - 94.7, - 94.3, - 94, - 93, - 92.5, - 92, - 90.9, - 87.4, - 83.6, - 81.3, - 79.8, - 78.7, - 77.2, - 73.8, - 71.5, - 70.1, - 68.5, - 66.4, - 64.4, - 62.9, - 61.3, - 59.1, - 58.2, - 57.7, - 56.9, - 56, - 54.9, - 53.9, - 52.7, - 51.4, - 50.1, - 48.8, - 47.7, - 47.1, - 46.2, - 45.6, - 45, - 44.2, - 43, - 41.4, - 40.7, - 40.2, - 39.8, - 39.1, - 38.4, - 37.8, - 37.4, - 37.1, - 36.8, - 36.5, - 36.3, - 36.3, - 36.3, - 36, - 35.5, - 34.6, - 33.7, - 32.7, - 31.8, - 31.3, - 30.8, - 30.5, - 30.2, - 29.9, - 29.8, - 29.8, - 29.6, - 29.4, - 29.3, - 29.1, - 28.6, - 28.5, - 28.4, - 28.5, - 28.5, - 28.5, - 28.5, - 28.3, - 27.8, - 27.7, - 27.5, - 27.1, - 26.6, - 26.2, - 26, - 25.8, - 25.7, - 25.6, - 25.5, - 25.4, - 25.4, - 25.5, - 25.6, - 25.6, - 25.7, - 25.9, - 26, - 26.1, - 26.1, - 26.2, - 26.2, - 26.1, - 25.9, - 25.6, - 25.3, - 25.1, - 24.9, - 24.8, - 24.7, - 24.6, - 24.7, - 24.8, - 24.7, - 24.6, - 24.5, - 24.5, - 24.4, - 24.3, - 24.3, - 21.3, - 21.8, - 22.7, - 23.4, - 23.7, - 23.7, - 23.7, - 23.5, - 23, - 22.1, - 21.4, - 21.1, - 20.9, - 20.9, - 20.9, - 20.9, - 20.9, - 21, - 21.2, - 21.4, - 21.6, - 21.8, - 21.9, - 22.1, - 22.2, - 22.3, - 22.5, - 22.7, - 22.8, - 22.9, - 23.1, - 23.3, - 23.4, - 23.6, - 23.8, - 23.9, - 23.9, - 24, - 24.1, - 24.2, - 24.4, - 24.6, - 24.7, - 24.9, - 25.1, - 25.4, - 25.6, - 25.8, - 26.2, - 26.6, - 27.1, - 27.8, - 28.5, - 29.4, - 29.7, - 30, - 30.2, - 30.5, - 31.1, - 32.7, - 33.2, - 33.5, - 33.9, - 34.2, - 34.5, - 34.8, - 35.1, - 35.4, - 35.7, - 36, - 36.4, - 36.8, - 37.3, - 37.8, - 38.5, - 39.3, - 40.3, - 41.5, - 42.9, - 45.9, - 47.1, - 47.8, - 48.7, - 49.9, - 51, - 52.3, - 53.7, - 55.1, - 59.9, - 68.1, - 73.3, - 75, - 75.8, - 76.7, - 77.7, - 79, - 81.1, - 87, - 89.3, - 90.7, - 91.5, - 92.2, - 92.3, - 92.6, - 93, - 93.1, - 92.9, - 93.2, - 94.7, - 95.7, - 96.7, - 97.9, - 100.2, - 101.2, - 102.1, - 100.8, - 99, - 99.4, - 100.1, - 100.6, - 101.3, - 101.8, - 102.2, - 104.6, - 113.5, - 120.1, - 122.5, - 122.9, - 123, - 122.6, - 122.5, - 122.2, - 121.6, - 121.4, - 117.9, - 98.7, - 96.2, - 95.7, - 95.9, - 95.5, - 95.1, - 94.6, - 94.4, - 93.9, - 93.5, - 92.4, - 90.6, - 84.9, - 80.4, - 79, - 77.7, - 74.9, - 72.3, - 70.2, - 68.5, - 66.8, - 64.9, - 63.3, - 61.9, - 60.4, - 59.2, - 58.4, - 57.4, - 56.5, - 55.3, - 54.1, - 52.8, - 51.3, - 49.9, - 48.8, - 48.3, - 47.5, - 46.5, - 45.8, - 45.1, - 44.6, - 44, - 43, - 42.1, - 41.3, - 40.2, - 39.2, - 38.5, - 38.2, - 37.8, - 37.6, - 37.4, - 37.1, - 37, - 36.8, - 36.8, - 36.5, - 35.8, - 35, - 34, - 32.8, - 31.9, - 31.3, - 30.9, - 30.6, - 30.3, - 30, - 29.7, - 29.6, - 29.5, - 29.3, - 29.2, - 29, - 28.6, - 28.5, - 28.4, - 28.5, - 28.5, - 28.4, - 28.3, - 28, - 27.8, - 27.7, - 27.3, - 26.9, - 26.5, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.5, - 25.5, - 25.6, - 25.6, - 25.7, - 25.8, - 25.9, - 26, - 26.1, - 26.2, - 26.2, - 26.2, - 26.2, - 26.1, - 25.8, - 25.5, - 25.2, - 25.1, - 25, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.8, - 24.7, - 24.6, - 24.5, - 24.4, - 24.4, - 24.4, - 21.2, - 21.6, - 22.5, - 23.2, - 23.5, - 23.6, - 23.5, - 23, - 22.1, - 21.5, - 21.2, - 21, - 20.8, - 20.8, - 20.7, - 20.8, - 20.8, - 21, - 21.2, - 21.4, - 21.6, - 21.8, - 21.9, - 22, - 22.1, - 22.3, - 22.5, - 22.6, - 22.6, - 22.8, - 23.1, - 23.2, - 23.3, - 23.5, - 23.6, - 23.7, - 23.8, - 23.9, - 23.9, - 24.1, - 24.3, - 24.5, - 24.7, - 24.9, - 25, - 25.3, - 25.5, - 25.6, - 25.9, - 26.3, - 26.7, - 27.3, - 28.1, - 29.1, - 29.6, - 29.9, - 30.4, - 30.9, - 31.6, - 32.9, - 33.2, - 33.5, - 33.9, - 34.2, - 34.4, - 34.7, - 34.9, - 35.3, - 35.7, - 36, - 36.4, - 36.7, - 37.3, - 37.8, - 38.6, - 39.5, - 40.4, - 41.9, - 43.6, - 45.9, - 47, - 47.9, - 48.9, - 50.1, - 51.3, - 52.5, - 53.8, - 55.7, - 62.2, - 71.5, - 74.3, - 75.5, - 76.3, - 76.9, - 77.9, - 79.2, - 80.3, - 85.3, - 88.5, - 89.6, - 90.5, - 91.1, - 91.3, - 92, - 92.4, - 92.1, - 91.6, - 91.9, - 93.4, - 94.6, - 95.7, - 96.7, - 97.7, - 98.2, - 99.4, - 98.6, - 98.1, - 98.6, - 99.2, - 100.1, - 100.9, - 101.2, - 101.3, - 101.5, - 111.3, - 120.4, - 122.8, - 123.4, - 123, - 122.8, - 122.8, - 122.8, - 122.6, - 122.3, - 123.1, - 108, - 96.7, - 96.9, - 96.9, - 96.4, - 96.1, - 95.8, - 95.5, - 95, - 94.5, - 93.9, - 92.9, - 89.6, - 82.1, - 79.3, - 78.3, - 76.7, - 73.4, - 70.5, - 69, - 67.2, - 65.5, - 64, - 62.4, - 61.2, - 59.9, - 58.9, - 57.8, - 56.6, - 55.4, - 54.2, - 52.7, - 51.4, - 50.4, - 49.7, - 49, - 48, - 47.1, - 46.3, - 45.7, - 45.4, - 44.8, - 44, - 43.2, - 42.2, - 40.6, - 39.6, - 39.4, - 38.9, - 38.3, - 38, - 37.7, - 37.5, - 37.2, - 37.1, - 37, - 36.7, - 35.8, - 34.9, - 33.8, - 32.6, - 31.8, - 31.2, - 30.9, - 30.6, - 30.2, - 30, - 29.8, - 29.6, - 29.4, - 29.3, - 29.1, - 29, - 28.5, - 28.4, - 28.4, - 28.5, - 28.5, - 28.3, - 28.1, - 28, - 27.8, - 27.6, - 27.1, - 26.8, - 26.4, - 26.1, - 25.9, - 25.8, - 25.7, - 25.6, - 25.6, - 25.6, - 25.7, - 25.8, - 26, - 26.1, - 26.2, - 26.2, - 26.3, - 26.3, - 26.2, - 26.2, - 26.2, - 26, - 25.8, - 25.5, - 25.3, - 25.2, - 25.1, - 25.1, - 25, - 25.1, - 25, - 25, - 25, - 24.8, - 24.7, - 24.6, - 24.5, - 24.5, - 24.4, - 21.1, - 21.4, - 22.2, - 22.9, - 23.2, - 23.2, - 22.9, - 22.1, - 21.4, - 21.1, - 21, - 20.8, - 20.6, - 20.6, - 20.6, - 20.7, - 20.9, - 21.1, - 21.3, - 21.4, - 21.5, - 21.7, - 21.8, - 21.9, - 22.1, - 22.2, - 22.3, - 22.5, - 22.6, - 22.8, - 23, - 23.1, - 23.3, - 23.4, - 23.5, - 23.6, - 23.6, - 23.8, - 24, - 24.2, - 24.3, - 24.5, - 24.7, - 24.9, - 25.3, - 25.5, - 25.7, - 26, - 26.4, - 26.9, - 27.5, - 28.1, - 28.9, - 29.4, - 29.8, - 30.4, - 30.9, - 31.5, - 32.3, - 32.9, - 33.3, - 33.6, - 33.9, - 34.1, - 34.3, - 34.6, - 34.9, - 35.2, - 35.6, - 35.9, - 36.3, - 36.7, - 37.2, - 37.9, - 38.9, - 40, - 41.4, - 43.6, - 45.1, - 46.2, - 47.1, - 48.1, - 49.1, - 50.3, - 51.5, - 52.8, - 54.9, - 59.4, - 65.4, - 72.3, - 74.7, - 75.7, - 76.4, - 77, - 77.8, - 78.7, - 79.4, - 84.3, - 86.8, - 87.7, - 88.5, - 88.8, - 89.3, - 90.5, - 90.8, - 90.2, - 89.6, - 90.4, - 92.4, - 93.6, - 94.5, - 95.3, - 96.2, - 96.6, - 96.5, - 96.4, - 96.5, - 97.9, - 98.5, - 99.5, - 100.4, - 100.6, - 100.6, - 100.5, - 109.2, - 120, - 122.2, - 122.8, - 122.7, - 122.7, - 122.8, - 123, - 123, - 122.9, - 122.9, - 116, - 97.9, - 98, - 97.8, - 97.5, - 97.3, - 97, - 96.7, - 96.2, - 95.6, - 94.8, - 93.7, - 92.1, - 86.1, - 80.2, - 79, - 77.7, - 74.8, - 70.9, - 69.2, - 67.3, - 65.8, - 64.4, - 63.1, - 61.7, - 60.2, - 59, - 57.9, - 56.7, - 55.5, - 54.4, - 53.1, - 52.2, - 51.5, - 50.7, - 49.7, - 48.7, - 47.8, - 47, - 46.6, - 46.1, - 45.4, - 44.7, - 44.1, - 42.7, - 41.5, - 41, - 40.5, - 39.8, - 38.9, - 38.3, - 37.9, - 37.7, - 37.4, - 37.2, - 37, - 36.5, - 35.7, - 34.8, - 33.5, - 32.3, - 31.5, - 31, - 30.7, - 30.4, - 30.1, - 30, - 29.7, - 29.5, - 29.4, - 29.3, - 29.1, - 28.9, - 28.5, - 28.4, - 28.4, - 28.5, - 28.4, - 28.2, - 28.1, - 27.9, - 27.8, - 27.3, - 26.8, - 26.5, - 26.3, - 26, - 25.9, - 25.8, - 25.7, - 25.7, - 25.7, - 25.8, - 25.9, - 26.1, - 26.3, - 26.4, - 26.6, - 26.5, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26, - 25.7, - 25.5, - 25.4, - 25.4, - 25.3, - 25.2, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 24.9, - 24.8, - 24.7, - 24.6, - 24.5, - 24.5, - 21.1, - 21.2, - 21.7, - 22.3, - 22.5, - 22.5, - 22, - 21.4, - 21.1, - 20.9, - 20.8, - 20.6, - 20.4, - 20.5, - 20.5, - 20.7, - 20.9, - 21.1, - 21.2, - 21.3, - 21.4, - 21.6, - 21.7, - 21.9, - 22.1, - 22.2, - 22.3, - 22.5, - 22.7, - 22.8, - 23, - 23.1, - 23.2, - 23.2, - 23.3, - 23.4, - 23.6, - 23.7, - 23.9, - 24.1, - 24.2, - 24.4, - 24.6, - 25, - 25.6, - 25.9, - 26.2, - 26.6, - 27, - 27.5, - 28, - 28.6, - 29.2, - 29.4, - 29.9, - 30.5, - 31, - 31.6, - 32.4, - 33, - 33.3, - 33.6, - 33.8, - 34, - 34.2, - 34.5, - 34.8, - 35.1, - 35.4, - 35.8, - 36.1, - 36.4, - 37, - 37.9, - 39, - 40.2, - 41.7, - 43.9, - 45.6, - 46.7, - 47.4, - 48.3, - 49.5, - 50.5, - 51.7, - 53.2, - 58.1, - 64.6, - 70.3, - 73.6, - 75, - 75.8, - 76.3, - 76.8, - 77.6, - 77.9, - 78.4, - 80.6, - 81.4, - 82.8, - 83.4, - 83.4, - 85.2, - 87, - 87.1, - 86.4, - 86.1, - 87.6, - 90.1, - 91.9, - 92.9, - 93.7, - 94.7, - 95.2, - 95, - 95.1, - 95.3, - 96.9, - 97.7, - 98.6, - 99.4, - 99.8, - 99.9, - 99.5, - 100.9, - 113.4, - 120.5, - 122, - 122.1, - 122.7, - 122.9, - 123.2, - 122.9, - 123.4, - 123, - 122.2, - 103.1, - 98.9, - 98.9, - 98.7, - 98.5, - 98.3, - 98, - 97.4, - 96.5, - 95.6, - 94.5, - 93.6, - 91.2, - 82.7, - 79.7, - 78.5, - 77.1, - 73.9, - 70.5, - 67.7, - 66.1, - 64.8, - 63.5, - 61.7, - 60.2, - 58.9, - 57.8, - 56.7, - 55.7, - 54.8, - 54.1, - 53.3, - 52.6, - 51.6, - 50.5, - 49.5, - 48.6, - 47.9, - 47.3, - 46.6, - 45.8, - 45.2, - 44.5, - 43.1, - 42.9, - 42.4, - 41.6, - 40.6, - 39.3, - 38.5, - 38, - 37.8, - 37.4, - 37.1, - 36.6, - 35.9, - 35.3, - 34.6, - 32.9, - 31.9, - 31.4, - 31.2, - 30.8, - 30.6, - 30.3, - 30.1, - 29.7, - 29.5, - 29.3, - 29.2, - 29.1, - 28.9, - 28.4, - 28.4, - 28.4, - 28.5, - 28.4, - 28.2, - 28, - 27.9, - 27.7, - 27.1, - 26.6, - 26.3, - 26.2, - 26.1, - 25.9, - 25.9, - 25.8, - 25.8, - 25.9, - 26, - 26.1, - 26.4, - 26.8, - 27, - 27.2, - 27, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 25.9, - 25.7, - 25.7, - 25.6, - 25.4, - 25.4, - 25.3, - 25.3, - 25.2, - 25.2, - 25.1, - 25, - 24.9, - 24.8, - 24.7, - 24.6, - 24.5, - 21, - 21, - 21.2, - 21.5, - 21.6, - 21.5, - 21.2, - 21, - 20.9, - 20.7, - 20.6, - 20.3, - 20.3, - 20.4, - 20.5, - 20.7, - 20.8, - 21, - 21.1, - 21.3, - 21.5, - 21.6, - 21.8, - 21.9, - 22.1, - 22.2, - 22.3, - 22.5, - 22.7, - 22.8, - 22.9, - 23, - 23, - 23.1, - 23.2, - 23.4, - 23.5, - 23.6, - 23.8, - 24, - 24.1, - 24.3, - 24.5, - 25, - 25.7, - 26, - 26.2, - 26.6, - 26.9, - 27.1, - 27.4, - 27.9, - 28.5, - 29, - 29.6, - 29.9, - 30.4, - 31.2, - 32.3, - 32.8, - 33, - 33.4, - 33.7, - 33.9, - 34.2, - 34.4, - 34.7, - 35, - 35.3, - 35.6, - 36, - 36.4, - 37, - 37.9, - 38.9, - 39.9, - 41.4, - 43.4, - 45.4, - 46.8, - 47.5, - 48.5, - 49.8, - 50.9, - 52, - 54.7, - 62, - 68.9, - 72.9, - 74.4, - 75.2, - 75.8, - 76.1, - 76.4, - 76.8, - 77, - 77.5, - 78.4, - 79.1, - 79.6, - 79.7, - 80.2, - 81.2, - 81.8, - 82.9, - 83.2, - 83.3, - 83.7, - 84.9, - 88.6, - 90.4, - 92.3, - 93.6, - 93.9, - 93.7, - 93.9, - 94.3, - 95.4, - 97.5, - 98.2, - 98.4, - 98.4, - 98.9, - 98.8, - 99, - 103.8, - 117.3, - 120.1, - 120.3, - 122.5, - 123.1, - 123.9, - 123.8, - 123.3, - 123.4, - 122.9, - 109.5, - 100, - 100, - 99.9, - 99.9, - 99.9, - 99.4, - 98.6, - 97.6, - 96.4, - 95.2, - 94.3, - 93.4, - 88.2, - 81.6, - 79.3, - 78.1, - 77, - 74.9, - 69.9, - 67.5, - 65.8, - 64.7, - 62.2, - 60.4, - 58.8, - 57.8, - 56.9, - 56.2, - 55.6, - 55, - 54.1, - 53.1, - 52, - 51.1, - 50.2, - 49.4, - 48.5, - 47.6, - 46.8, - 46, - 45.3, - 44.7, - 44.3, - 44, - 43.2, - 42.2, - 40.9, - 39.6, - 38.7, - 38, - 37.7, - 37.3, - 36.9, - 36, - 35.2, - 34.9, - 34.5, - 32.9, - 32.2, - 32.1, - 31.8, - 31.4, - 31, - 30.6, - 30.1, - 29.7, - 29.4, - 29.3, - 29, - 29, - 28.7, - 28.4, - 28.3, - 28.3, - 28.4, - 28.3, - 28.1, - 27.9, - 27.8, - 27.6, - 27, - 26.5, - 26.3, - 26.2, - 26, - 25.9, - 25.9, - 25.9, - 26, - 26.1, - 26.3, - 26.5, - 26.9, - 27.3, - 27.6, - 27.6, - 27.3, - 26.9, - 26.6, - 26.5, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.6, - 25.6, - 25.4, - 25.4, - 25.4, - 25.3, - 25.2, - 25.1, - 24.9, - 24.8, - 24.7, - 24.6, - 24.5, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21, - 20.9, - 20.7, - 20.6, - 20.4, - 20.2, - 20.1, - 20.2, - 20.5, - 20.6, - 20.8, - 20.9, - 21.1, - 21.2, - 21.4, - 21.5, - 21.7, - 21.8, - 21.9, - 22, - 22.2, - 22.3, - 22.5, - 22.6, - 22.7, - 22.8, - 22.8, - 22.9, - 23.1, - 23.2, - 23.3, - 23.4, - 23.5, - 23.6, - 23.9, - 24, - 24.1, - 24.3, - 24.9, - 25.6, - 25.9, - 26, - 26.1, - 26.1, - 26.1, - 26.4, - 26.7, - 27.2, - 28.1, - 28.9, - 29.3, - 29.8, - 30.5, - 31.5, - 32.2, - 32.6, - 33.3, - 33.7, - 33.9, - 34.1, - 34.4, - 34.7, - 34.9, - 35.1, - 35.5, - 35.9, - 36.5, - 37, - 37.7, - 38.5, - 39.6, - 40.8, - 42.7, - 45.1, - 46.6, - 47.4, - 48.5, - 49.8, - 51.2, - 52.2, - 57.6, - 64, - 70.3, - 73.5, - 74.7, - 75.2, - 75.5, - 75.7, - 75.9, - 76, - 76.4, - 76.9, - 77.5, - 78, - 78.4, - 78.8, - 79.4, - 79.2, - 79.5, - 80.5, - 81.4, - 81.5, - 81.4, - 81.9, - 83.5, - 86, - 90.6, - 92.3, - 92.7, - 92.7, - 92.8, - 93.4, - 95.6, - 97, - 97.5, - 97.4, - 96.9, - 97.7, - 98.1, - 98.4, - 99.4, - 106.4, - 114.9, - 116.5, - 119.9, - 123.9, - 124.6, - 124.7, - 124.8, - 125.3, - 125, - 120, - 101.1, - 101.2, - 101.2, - 101.4, - 101.2, - 100.5, - 99.4, - 98.3, - 97, - 95.9, - 94.9, - 94.1, - 92.3, - 85.7, - 80.2, - 79.1, - 78.4, - 77.6, - 75.9, - 74, - 69.4, - 65.8, - 63.3, - 61.2, - 58.9, - 58.2, - 57.6, - 57, - 56.4, - 55.5, - 54.6, - 53.6, - 52.4, - 51.4, - 50.5, - 49.7, - 48.6, - 47.5, - 46.8, - 46.3, - 45.7, - 45.5, - 45.2, - 44.6, - 43.7, - 42.6, - 41.1, - 39.8, - 38.7, - 38, - 37.6, - 37.2, - 36.9, - 36.3, - 35.7, - 35.4, - 35, - 34.2, - 33.4, - 33.3, - 32.9, - 32.1, - 31.3, - 30.7, - 30.2, - 29.7, - 29.3, - 29.1, - 28.9, - 28.7, - 28.5, - 28.3, - 28.3, - 28.3, - 28.4, - 28.2, - 28, - 27.8, - 27.7, - 27.5, - 26.9, - 26.5, - 26.3, - 26.2, - 26, - 25.9, - 26, - 26, - 26.1, - 26.3, - 26.6, - 27, - 27.4, - 27.7, - 27.9, - 27.7, - 27.4, - 27, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 26.1, - 26, - 25.8, - 25.8, - 25.6, - 25.5, - 25.5, - 25.4, - 25.3, - 25.2, - 25, - 24.8, - 24.7, - 24.6, - 24.6, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.7, - 20.4, - 20.2, - 20, - 20, - 20.1, - 20.3, - 20.4, - 20.6, - 20.8, - 20.9, - 21.1, - 21.2, - 21.4, - 21.5, - 21.7, - 21.7, - 21.8, - 22, - 22.1, - 22.3, - 22.4, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 23, - 23.1, - 23.1, - 23.3, - 23.3, - 23.5, - 23.7, - 23.8, - 24, - 24.2, - 24.7, - 25.2, - 25.5, - 25.4, - 25.3, - 25.3, - 25.5, - 25.9, - 26.4, - 27.2, - 27.9, - 28.7, - 29.4, - 29.8, - 30.2, - 31, - 32, - 32.6, - 33.3, - 33.6, - 33.8, - 34, - 34.4, - 34.7, - 34.9, - 35.1, - 35.5, - 35.9, - 36.4, - 36.7, - 37.4, - 38.1, - 38.9, - 40.1, - 42.5, - 44.6, - 46.3, - 47.2, - 48.3, - 49.7, - 51, - 52.1, - 58.9, - 64.9, - 70.9, - 73.9, - 74.6, - 74.9, - 75, - 75.1, - 75.2, - 75.5, - 76, - 76.4, - 76.7, - 76.8, - 77.3, - 77.9, - 78.4, - 77.8, - 78.4, - 79.1, - 79.8, - 79.9, - 79.9, - 80.5, - 80.9, - 81.5, - 84.3, - 87.1, - 89.9, - 91.1, - 90.6, - 91.2, - 93.2, - 94.5, - 95.2, - 95.7, - 95.9, - 96.6, - 97.2, - 97.9, - 98.6, - 99.7, - 102, - 104.1, - 114.4, - 122.4, - 124.8, - 125.2, - 125.7, - 125.8, - 125.6, - 125.7, - 109, - 102.4, - 102.5, - 102.6, - 102.2, - 101.4, - 100.3, - 99.1, - 97.9, - 96.7, - 95.7, - 94.6, - 93.5, - 88.2, - 81.6, - 80.4, - 79.5, - 78.8, - 78, - 77.3, - 74.1, - 66.9, - 63.9, - 61.5, - 59.5, - 59, - 58.4, - 57.7, - 56.9, - 55.8, - 54.7, - 53.4, - 52.1, - 51.1, - 50.3, - 49.5, - 48.4, - 47.6, - 47.3, - 46.8, - 46.3, - 46, - 45.6, - 44.9, - 43.9, - 42.6, - 41.1, - 39.7, - 38.6, - 37.9, - 37.6, - 37.3, - 37.1, - 37, - 36.6, - 36.2, - 35.7, - 35.1, - 34.6, - 34.2, - 33.6, - 32.7, - 31.5, - 30.8, - 30.2, - 29.7, - 29.3, - 29, - 28.8, - 28.5, - 28.3, - 28.2, - 28.2, - 28.2, - 28.4, - 28.3, - 28.1, - 27.9, - 27.7, - 27.6, - 27, - 26.6, - 26.5, - 26.2, - 25.9, - 25.8, - 25.9, - 26, - 26.2, - 26.5, - 26.9, - 27.3, - 27.7, - 28, - 28.1, - 27.8, - 27.4, - 27, - 26.7, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.2, - 26.2, - 26.1, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.3, - 25.2, - 25, - 24.9, - 24.8, - 24.7, - 24.7, - 20.7, - 20.7, - 20.7, - 20.7, - 20.6, - 20.6, - 20.4, - 20.1, - 19.9, - 19.9, - 20, - 20.1, - 20.3, - 20.6, - 20.7, - 20.8, - 20.9, - 21.1, - 21.2, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.2, - 22.2, - 22.4, - 22.5, - 22.5, - 22.6, - 22.8, - 22.9, - 22.9, - 23, - 23.1, - 23.2, - 23.3, - 23.5, - 23.6, - 23.8, - 24, - 24.4, - 24.8, - 24.9, - 24.8, - 25, - 25.3, - 25.8, - 26.4, - 27.1, - 28.1, - 28.9, - 29.5, - 30, - 30.4, - 30.9, - 31.6, - 32.3, - 32.8, - 33.2, - 33.5, - 33.7, - 34, - 34.4, - 34.7, - 34.9, - 35.1, - 35.4, - 35.7, - 36.1, - 36.5, - 37.1, - 37.8, - 38.8, - 40, - 42.7, - 44.5, - 46.2, - 47.1, - 48.2, - 49.6, - 50.9, - 51.9, - 59.9, - 64.8, - 68.3, - 73.4, - 74.1, - 74, - 74.1, - 74.3, - 74.6, - 75.3, - 75.7, - 75.8, - 75.9, - 75.8, - 76.2, - 76.8, - 77.2, - 76.8, - 77.3, - 77.6, - 77.9, - 78.3, - 78.5, - 79.1, - 79.5, - 79.1, - 79.2, - 80.6, - 84.2, - 86.5, - 85.9, - 86.3, - 89.1, - 91.7, - 93.1, - 94.2, - 95.1, - 95.9, - 96.8, - 97.6, - 98.3, - 99.2, - 100.2, - 101.1, - 107.2, - 119.9, - 125.7, - 126.1, - 126, - 126.1, - 126.1, - 126.1, - 119.4, - 106.7, - 106.7, - 106.2, - 103.9, - 102.2, - 101, - 99.8, - 98.6, - 97.3, - 96, - 94.7, - 93.7, - 90.1, - 84.3, - 81.4, - 80.3, - 79.3, - 78.2, - 77, - 74.3, - 66.4, - 63.9, - 61.8, - 61, - 59.9, - 59.1, - 58.2, - 56.9, - 55.5, - 54.2, - 52.8, - 51.8, - 50.9, - 50.4, - 49.6, - 48.9, - 48.5, - 48, - 47.4, - 46.8, - 46.3, - 45.7, - 44.9, - 43.9, - 42.4, - 40.4, - 39.5, - 38.6, - 38, - 37.8, - 37.6, - 37.5, - 37.3, - 37, - 36.8, - 36.4, - 35.8, - 35.3, - 34.8, - 33.9, - 32.8, - 31.6, - 30.8, - 30.2, - 29.7, - 29.3, - 28.9, - 28.7, - 28.5, - 28.2, - 28.1, - 28.1, - 28.2, - 28.4, - 28.3, - 28.2, - 28, - 27.7, - 27.5, - 27.3, - 26.9, - 26.7, - 26.2, - 25.8, - 25.7, - 25.9, - 26, - 26.3, - 26.6, - 27, - 27.4, - 27.9, - 28.1, - 28.1, - 27.7, - 27.3, - 27, - 26.6, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.5, - 25.4, - 25.3, - 25.1, - 25, - 24.9, - 24.8, - 24.7, - 20.6, - 20.6, - 20.5, - 20.5, - 20.3, - 20.2, - 19.9, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.4, - 20.6, - 20.7, - 20.8, - 20.9, - 21.1, - 21.2, - 21.3, - 21.4, - 21.5, - 21.6, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.3, - 22.3, - 22.5, - 22.6, - 22.8, - 22.8, - 22.9, - 23, - 23, - 23.2, - 23.3, - 23.5, - 23.7, - 23.9, - 24.2, - 24.3, - 24.4, - 24.7, - 25.2, - 25.7, - 26.2, - 26.9, - 27.7, - 28.6, - 29.4, - 30.1, - 30.8, - 31.3, - 31.8, - 32.2, - 32.4, - 32.8, - 33.1, - 33.4, - 33.7, - 33.9, - 34.1, - 34.5, - 34.7, - 34.9, - 35.1, - 35.3, - 35.7, - 36.2, - 36.8, - 37.6, - 38.7, - 39.7, - 42.2, - 43.9, - 45.8, - 47.1, - 48.2, - 49.4, - 50.6, - 51.3, - 59.4, - 64.4, - 66.4, - 69.6, - 70.7, - 70.5, - 71.6, - 72.8, - 74.2, - 74.9, - 75.2, - 75.2, - 75.1, - 75, - 75.3, - 75.8, - 76.1, - 75.9, - 76.2, - 76.4, - 76.5, - 76.7, - 77, - 77.7, - 78, - 77.8, - 77.7, - 78.4, - 79, - 79.4, - 80, - 81.4, - 84.6, - 89.3, - 92.2, - 93.5, - 94.2, - 95.3, - 96.5, - 97.5, - 98.4, - 99.1, - 100, - 100.8, - 103.6, - 117, - 126.1, - 126.6, - 126.7, - 126.8, - 126.8, - 126.8, - 124.4, - 118.8, - 118, - 119.7, - 113.9, - 102.3, - 101.1, - 99.9, - 98.3, - 96.9, - 95.6, - 94.5, - 93.5, - 90.1, - 85.4, - 81.6, - 80.3, - 79.1, - 77.9, - 76.7, - 72.3, - 66.4, - 63.8, - 62.6, - 61.7, - 60.5, - 58.9, - 57.7, - 56, - 54.6, - 53.6, - 52.7, - 52.1, - 51.5, - 50.9, - 50.3, - 49.6, - 49, - 48.4, - 47.7, - 47, - 46.4, - 45.7, - 44.9, - 43.8, - 42.1, - 40.2, - 39.5, - 38.8, - 38.3, - 38.1, - 37.8, - 37.7, - 37.6, - 37.4, - 37.1, - 36.8, - 36.4, - 35.7, - 35, - 34.2, - 33, - 31.7, - 30.6, - 30, - 29.6, - 29.1, - 28.8, - 28.5, - 28.3, - 28.1, - 28.1, - 28.1, - 28.2, - 28.3, - 28.3, - 28.3, - 28.2, - 27.9, - 27.6, - 27.4, - 27.1, - 26.7, - 26.2, - 25.7, - 25.6, - 25.8, - 25.9, - 26.2, - 26.7, - 27.2, - 27.5, - 28, - 28.1, - 28, - 27.7, - 27.2, - 26.9, - 26.5, - 26.3, - 26.2, - 26.2, - 26.3, - 26.3, - 26.3, - 26.4, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.6, - 25.4, - 25.3, - 25.2, - 25.1, - 25, - 24.9, - 24.8, - 20.4, - 20.3, - 20.2, - 20.2, - 20, - 19.7, - 19.6, - 19.7, - 19.8, - 19.9, - 20.1, - 20.3, - 20.4, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.2, - 21.3, - 21.3, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.5, - 22.6, - 22.6, - 22.7, - 22.8, - 22.8, - 23, - 23.1, - 23.3, - 23.4, - 23.6, - 23.8, - 24, - 24.1, - 24.2, - 24.7, - 25.4, - 25.9, - 26.4, - 27.1, - 27.9, - 28.8, - 29.6, - 30.5, - 31.4, - 31.9, - 32.1, - 32.2, - 32.4, - 32.8, - 33.1, - 33.3, - 33.5, - 33.6, - 33.7, - 34, - 34.3, - 34.5, - 34.8, - 35.1, - 35.6, - 36.1, - 36.7, - 37.5, - 38.5, - 39.6, - 41.2, - 43.2, - 45.3, - 46.9, - 47.9, - 48.9, - 49.9, - 50.7, - 56.2, - 63.7, - 65.4, - 66.3, - 66.6, - 66.9, - 67.7, - 70.3, - 72.8, - 74.3, - 74.6, - 74.2, - 73.9, - 73.9, - 74.3, - 74.7, - 74.9, - 74.9, - 75.1, - 75.1, - 75.1, - 75.2, - 75.8, - 76.4, - 76.6, - 76.5, - 76.6, - 77.2, - 77.2, - 77.6, - 78.4, - 79.9, - 80.7, - 84.7, - 90.6, - 92.6, - 93.9, - 95.4, - 96.7, - 97.7, - 99, - 99.8, - 100.2, - 100.9, - 101.9, - 111.6, - 125.6, - 126.8, - 127, - 127.1, - 127.3, - 127.4, - 127.2, - 126.4, - 126.2, - 127, - 125.2, - 102.1, - 101, - 99.8, - 97.6, - 96.2, - 95, - 94, - 92.9, - 88.4, - 84.2, - 81.9, - 80.6, - 79.1, - 78.1, - 76.9, - 72.4, - 67.1, - 64.9, - 63.2, - 62, - 60.9, - 58.9, - 57.7, - 56, - 54.7, - 53.8, - 53.5, - 53.1, - 52.3, - 51.6, - 50.9, - 50, - 49.1, - 48.4, - 47.7, - 47, - 46.4, - 45.8, - 45.1, - 44, - 42.9, - 40.9, - 40, - 39.5, - 39, - 38.5, - 38.1, - 37.9, - 37.8, - 37.6, - 37.3, - 36.9, - 36.6, - 36, - 35, - 34.1, - 33.1, - 31.6, - 30.2, - 29.6, - 29.2, - 28.8, - 28.6, - 28.4, - 28.1, - 28, - 28, - 28, - 28.2, - 28.2, - 28.3, - 28.3, - 28.3, - 28.2, - 27.7, - 27.4, - 27.2, - 26.6, - 26.1, - 25.6, - 25.5, - 25.7, - 25.8, - 26.2, - 26.8, - 27.3, - 27.7, - 28, - 28.1, - 28, - 27.5, - 27.1, - 26.7, - 26.4, - 26.2, - 26.1, - 26.1, - 26.2, - 26.2, - 26.3, - 26.4, - 26.4, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 25.7, - 25.5, - 25.3, - 25.2, - 25.2, - 25.1, - 24.9, - 24.8, - 20.2, - 20.1, - 20, - 19.8, - 19.5, - 19.4, - 19.5, - 19.6, - 19.9, - 20.2, - 20.4, - 20.6, - 20.7, - 20.7, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 22, - 22.2, - 22.3, - 22.4, - 22.5, - 22.5, - 22.6, - 22.7, - 22.8, - 22.9, - 23.1, - 23.3, - 23.4, - 23.6, - 23.7, - 23.8, - 23.9, - 24.2, - 24.7, - 25.3, - 25.9, - 26.5, - 27.2, - 27.9, - 28.9, - 29.6, - 30.6, - 31.4, - 31.9, - 31.9, - 32, - 32.3, - 32.7, - 33, - 33.2, - 33.3, - 33.3, - 33.4, - 33.7, - 34.1, - 34.4, - 34.8, - 35.3, - 35.7, - 36.2, - 36.7, - 37.4, - 38.3, - 39.2, - 40.5, - 42.6, - 44.8, - 46.4, - 47.4, - 48.3, - 49.2, - 49.9, - 51.6, - 58.3, - 61.2, - 62.6, - 62.8, - 63.8, - 65.8, - 67.6, - 70.6, - 73.1, - 73.4, - 72.5, - 69.9, - 70.1, - 71.8, - 72.4, - 72.5, - 72.8, - 73, - 72, - 71.5, - 73.3, - 74.5, - 75.3, - 75.3, - 75.2, - 75.2, - 75.8, - 75.9, - 76.6, - 77.4, - 78.6, - 79.8, - 81.5, - 88.5, - 92.3, - 94, - 95.6, - 96.9, - 98.1, - 99.3, - 100.3, - 100.8, - 101.3, - 102, - 108.4, - 122.9, - 126.9, - 127.1, - 127.4, - 127.7, - 128.1, - 128.4, - 128.7, - 128.5, - 127.9, - 127.2, - 102.2, - 101.1, - 99.9, - 97.7, - 96.3, - 95, - 93.9, - 92.4, - 87.3, - 84, - 82.4, - 81.1, - 79.7, - 78.5, - 77.3, - 74.7, - 68.8, - 65.6, - 63.9, - 62.5, - 61.3, - 59.4, - 58.1, - 56.8, - 55.6, - 54.7, - 54.4, - 53.8, - 52.8, - 51.9, - 50.7, - 49.7, - 49, - 48.4, - 47.6, - 46.8, - 46.2, - 45.7, - 45.2, - 44.2, - 43.3, - 42.3, - 41.3, - 40.3, - 39.7, - 39.1, - 38.5, - 38.1, - 37.9, - 37.7, - 37.4, - 37, - 36.6, - 35.9, - 35, - 34, - 33.1, - 31.5, - 29.8, - 29.2, - 28.8, - 28.6, - 28.3, - 28.2, - 28, - 27.9, - 27.9, - 28, - 28.1, - 28.2, - 28.2, - 28.3, - 28.3, - 28.3, - 28, - 27.5, - 27.2, - 26.5, - 26, - 25.5, - 25.4, - 25.7, - 25.9, - 26.3, - 26.8, - 27.3, - 27.8, - 28, - 28, - 27.9, - 27.4, - 26.9, - 26.5, - 26.3, - 26.1, - 26, - 26, - 26.1, - 26.2, - 26.3, - 26.4, - 26.4, - 26.4, - 26.4, - 26.3, - 26.1, - 26, - 25.7, - 25.5, - 25.4, - 25.3, - 25.2, - 25.1, - 25, - 24.9, - 20, - 19.8, - 19.7, - 19.4, - 19.3, - 19.4, - 19.6, - 19.9, - 20.2, - 20.5, - 20.7, - 20.8, - 20.9, - 20.9, - 20.8, - 20.9, - 21, - 21.1, - 21.1, - 21.2, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.4, - 22.5, - 22.6, - 22.7, - 22.8, - 23, - 23.2, - 23.3, - 23.5, - 23.6, - 23.6, - 23.8, - 24.1, - 24.5, - 25.1, - 25.8, - 26.4, - 27.1, - 27.9, - 28.8, - 29.6, - 30.4, - 31, - 31.2, - 31.2, - 31.6, - 31.8, - 32.4, - 32.8, - 32.9, - 33, - 33.1, - 33.4, - 33.8, - 34.2, - 34.5, - 35, - 35.4, - 35.7, - 36.2, - 36.6, - 37.2, - 37.9, - 38.7, - 39.8, - 42, - 44.3, - 45.8, - 46.8, - 47.7, - 48.3, - 49.2, - 50, - 51.4, - 52.6, - 55.1, - 55.9, - 59.5, - 62.6, - 65, - 68.3, - 69.8, - 68.6, - 67.4, - 63.8, - 63.8, - 66.6, - 67.5, - 67.9, - 67.7, - 68.3, - 67.3, - 66.5, - 68.3, - 71.5, - 73.6, - 73, - 72.9, - 72.9, - 74.1, - 74.7, - 75.6, - 76.8, - 78.3, - 79.7, - 80.6, - 88.3, - 92.6, - 94.2, - 95.7, - 97.1, - 98.2, - 99.2, - 100.4, - 101.1, - 101.8, - 102.5, - 106.3, - 118.8, - 126, - 127.1, - 127.6, - 128.5, - 130.6, - 130.2, - 130.3, - 131.3, - 130.4, - 128.5, - 102.3, - 101.2, - 99.9, - 98, - 96.6, - 95.4, - 94.1, - 92.3, - 87.6, - 84.4, - 82.4, - 81.3, - 79.9, - 78.8, - 77.7, - 76.2, - 71.2, - 66.5, - 64.9, - 63.3, - 61.4, - 60, - 58.5, - 57.5, - 56.4, - 55.2, - 54.4, - 53.5, - 52.6, - 51.7, - 50.8, - 49.9, - 49.1, - 48.4, - 47.6, - 47, - 46.4, - 45.9, - 45.5, - 45.1, - 44.5, - 44, - 43.1, - 41.4, - 40.3, - 39.6, - 38.8, - 38.2, - 37.9, - 37.7, - 37.5, - 37.1, - 36.7, - 36.1, - 35, - 34.1, - 33.2, - 31.3, - 29.7, - 28.9, - 28.6, - 28.4, - 28.2, - 28.1, - 27.9, - 27.8, - 27.8, - 27.9, - 28.1, - 28.2, - 28.2, - 28.3, - 28.3, - 28.3, - 28, - 27.5, - 27.1, - 26.4, - 25.7, - 25.4, - 25.4, - 25.7, - 25.9, - 26.3, - 26.9, - 27.3, - 27.9, - 28, - 28, - 27.9, - 27.3, - 26.7, - 26.3, - 26.1, - 26, - 25.8, - 25.8, - 26, - 26.2, - 26.3, - 26.3, - 26.4, - 26.4, - 26.4, - 26.3, - 26.2, - 26, - 25.8, - 25.5, - 25.3, - 25.2, - 25.2, - 25.2, - 25.1, - 25, - 19.5, - 18.9, - 18.8, - 19.2, - 19.4, - 19.7, - 19.9, - 20.1, - 20.4, - 20.7, - 20.8, - 20.9, - 20.9, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.4, - 21.4, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 22, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.4, - 22.5, - 22.6, - 22.8, - 22.9, - 23.1, - 23.3, - 23.3, - 23.4, - 23.5, - 23.8, - 24.1, - 24.5, - 25.2, - 25.8, - 26.4, - 27.1, - 27.8, - 28.6, - 29.3, - 29.9, - 30.2, - 30.3, - 30.4, - 30.9, - 31.1, - 31.8, - 32.3, - 32.6, - 32.6, - 33.1, - 33.5, - 34, - 34.4, - 34.6, - 34.9, - 35.2, - 35.6, - 36, - 36.3, - 36.8, - 37.2, - 38.2, - 39.4, - 40.8, - 43.3, - 45.2, - 46.3, - 47.1, - 47.6, - 48.5, - 49.5, - 50.4, - 51.5, - 52.3, - 52.7, - 53.1, - 54, - 56.2, - 58.6, - 59, - 58.3, - 58.2, - 57, - 60.9, - 64.1, - 64.5, - 64.7, - 64.4, - 65.1, - 65.2, - 64.6, - 64.8, - 66.9, - 68.4, - 67.5, - 67, - 67.5, - 71.4, - 73.3, - 75, - 76.6, - 78, - 79.2, - 81, - 89.3, - 92.7, - 94.1, - 95.5, - 96.8, - 97.9, - 99.1, - 100.3, - 101.4, - 102.2, - 103.2, - 105.1, - 116.2, - 122.9, - 127, - 127.7, - 129.7, - 131.9, - 131.8, - 132, - 133.5, - 134, - 133.4, - 107.2, - 101.3, - 100.1, - 98.4, - 96.8, - 95.5, - 94.1, - 92.7, - 87.3, - 84.1, - 82.3, - 81.3, - 80.1, - 79, - 78, - 76.8, - 72.4, - 68, - 65.2, - 63.2, - 61.6, - 60.5, - 58.5, - 57.5, - 56.2, - 55, - 54, - 53.5, - 52.9, - 52.3, - 51.3, - 50.2, - 49.3, - 48.6, - 48, - 47.5, - 46.9, - 46.6, - 46.4, - 46, - 45.4, - 44.9, - 44, - 42.4, - 40.7, - 39.7, - 38.8, - 38.2, - 37.9, - 37.7, - 37.4, - 37.1, - 36.7, - 36.1, - 35.2, - 34.4, - 33.3, - 31.2, - 29.8, - 29, - 28.5, - 28.2, - 28.1, - 27.9, - 27.8, - 27.7, - 27.6, - 27.8, - 28.1, - 28.2, - 28.2, - 28.3, - 28.3, - 28.3, - 28, - 27.4, - 26.9, - 26.2, - 25.5, - 25.3, - 25.3, - 25.5, - 25.9, - 26.3, - 26.9, - 27.4, - 27.9, - 28, - 28, - 27.7, - 27.1, - 26.5, - 26.1, - 25.9, - 25.9, - 25.7, - 25.6, - 25.9, - 26.1, - 26.2, - 26.3, - 26.3, - 26.3, - 26.3, - 26.2, - 26.1, - 25.9, - 25.7, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 18.3, - 18, - 18.1, - 19.1, - 19.8, - 20, - 20.2, - 20.3, - 20.6, - 20.7, - 20.8, - 20.9, - 20.9, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.1, - 21.2, - 21.3, - 21.2, - 21.2, - 21.3, - 21.4, - 21.5, - 21.6, - 21.8, - 21.9, - 22, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.5, - 22.6, - 22.8, - 22.9, - 23, - 23.1, - 23.2, - 23.2, - 23.5, - 23.8, - 24.1, - 24.6, - 25.2, - 25.7, - 26.3, - 27, - 27.7, - 28.5, - 29, - 29.3, - 29.6, - 29.7, - 29.8, - 30, - 30.2, - 31.1, - 31.8, - 32.2, - 32.6, - 33.1, - 33.6, - 34.1, - 34.4, - 34.5, - 34.8, - 35, - 35.3, - 35.7, - 36.1, - 36.3, - 36.7, - 37.5, - 38.6, - 39.6, - 41.4, - 44.4, - 45.9, - 46.6, - 47.2, - 47.8, - 49, - 50.1, - 51, - 51.7, - 51.9, - 51.5, - 51.3, - 51.5, - 51.8, - 52.5, - 53.3, - 54, - 53.9, - 56, - 60.6, - 62, - 61.5, - 63.5, - 64.2, - 63.6, - 62.3, - 62.8, - 64.4, - 64.8, - 63.8, - 63.6, - 64, - 67.7, - 72.5, - 74.6, - 76.1, - 77.2, - 78.5, - 81, - 88.5, - 92.1, - 93.7, - 94.8, - 96, - 97.5, - 99.2, - 100.2, - 101.5, - 101.9, - 103.1, - 104.1, - 112.4, - 122.4, - 127.1, - 127.9, - 130.6, - 132.2, - 132.2, - 132.9, - 133.9, - 134.7, - 133.5, - 116.7, - 101.1, - 100, - 98.4, - 96.8, - 95.3, - 94, - 92.5, - 87.4, - 83.8, - 82.1, - 81.2, - 80, - 78.9, - 78, - 77, - 73.3, - 68.9, - 65.1, - 63.1, - 61.7, - 60.4, - 58.5, - 57.4, - 56, - 55, - 54.6, - 54.3, - 53.6, - 52.8, - 51.8, - 50.8, - 49.9, - 49.2, - 48.5, - 48, - 47.4, - 47.2, - 47.1, - 46.3, - 45.7, - 45.1, - 44, - 42.1, - 40.6, - 39.6, - 38.8, - 38.2, - 37.9, - 37.6, - 37.3, - 37, - 36.7, - 36.2, - 35.4, - 34.4, - 33.3, - 30.8, - 29.6, - 28.9, - 28.4, - 28.1, - 28, - 27.9, - 27.7, - 27.6, - 27.5, - 27.8, - 28, - 28.2, - 28.2, - 28.2, - 28.3, - 28.3, - 28.1, - 27.4, - 26.8, - 26.1, - 25.5, - 25.1, - 25.2, - 25.5, - 25.8, - 26.3, - 26.9, - 27.5, - 27.9, - 28, - 27.9, - 27.5, - 26.9, - 26.4, - 26.1, - 25.9, - 25.7, - 25.5, - 25.4, - 25.7, - 26, - 26.1, - 26.2, - 26.2, - 26.2, - 26.1, - 26.1, - 26, - 25.8, - 25.5, - 25.3, - 25.2, - 25.2, - 25.2, - 25.1, - 25.2, - 25.2, - 17.8, - 17.8, - 18.4, - 19.5, - 20, - 20.2, - 20.4, - 20.5, - 20.7, - 20.7, - 20.8, - 20.9, - 20.9, - 20.9, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.1, - 21.1, - 21.1, - 21.1, - 21.2, - 21.3, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.6, - 22.7, - 22.9, - 22.9, - 22.9, - 23, - 23.2, - 23.6, - 24.1, - 24.3, - 24.6, - 25.2, - 25.7, - 26.3, - 26.8, - 27.4, - 28.1, - 28.4, - 28.8, - 29, - 29.1, - 29.2, - 29.3, - 30, - 31, - 31.9, - 32.4, - 32.8, - 33.2, - 33.7, - 34, - 34.2, - 34.4, - 34.5, - 34.7, - 35.2, - 35.5, - 35.6, - 35.8, - 36.2, - 36.9, - 37.6, - 38.8, - 40.1, - 42.9, - 45.3, - 46.1, - 46.8, - 47.4, - 48.6, - 49.6, - 50.3, - 50.7, - 50.9, - 50.6, - 50.4, - 50.5, - 50.8, - 51.4, - 52.1, - 52.7, - 52.8, - 52.6, - 53.5, - 54.3, - 55.7, - 60.4, - 62.8, - 61, - 59.2, - 60.1, - 61.6, - 62.3, - 61.9, - 62.3, - 63.7, - 66.3, - 71.5, - 74.3, - 75.5, - 76.5, - 77.8, - 78.8, - 83.5, - 90.3, - 93.1, - 94.1, - 95.5, - 97.2, - 99, - 99.7, - 100.4, - 101.2, - 102.1, - 103.4, - 111.6, - 123.1, - 127.3, - 128.3, - 129.7, - 131.5, - 131.8, - 133.2, - 134.3, - 134.9, - 134.8, - 121.8, - 100.8, - 99.7, - 98.3, - 96.8, - 95.3, - 93.9, - 92, - 86.3, - 83.6, - 81.9, - 80.9, - 79.4, - 78.5, - 77.6, - 76.1, - 72.8, - 68.8, - 64.8, - 62.9, - 61.6, - 60.5, - 58.4, - 57.5, - 56.8, - 56, - 55.5, - 54.8, - 54.2, - 53.4, - 52.6, - 51.7, - 50.6, - 49.6, - 48.9, - 48.4, - 47.8, - 47.5, - 47.1, - 46.4, - 45.7, - 44.8, - 43.6, - 41.7, - 40.4, - 39.5, - 38.6, - 38.1, - 37.8, - 37.5, - 37.1, - 36.8, - 36.6, - 36.1, - 35.2, - 34.2, - 32.8, - 30.3, - 29.3, - 28.7, - 28.4, - 28.2, - 28.1, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.7, - 27.9, - 28.1, - 28.1, - 28.1, - 28.2, - 28, - 27.5, - 27, - 26.1, - 25.4, - 25, - 25.2, - 25.5, - 25.9, - 26.4, - 27.1, - 27.7, - 27.9, - 27.9, - 27.8, - 27.2, - 26.7, - 26.3, - 26, - 25.8, - 25.6, - 25.3, - 25.2, - 25.5, - 25.8, - 25.9, - 26, - 26.1, - 26, - 26, - 25.9, - 25.7, - 25.5, - 25.3, - 25.2, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25.2, - 17.6, - 18.2, - 19.1, - 20, - 20.2, - 20.3, - 20.5, - 20.6, - 20.7, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.9, - 20.9, - 20.9, - 21, - 21, - 21, - 20.9, - 21, - 20.9, - 21.1, - 21.3, - 21.4, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 23.1, - 23.5, - 23.9, - 24.3, - 24.6, - 24.9, - 25.3, - 25.7, - 26.1, - 26.5, - 27, - 27.4, - 27.8, - 28.3, - 28.6, - 28.4, - 28.7, - 29.5, - 30.5, - 31.5, - 32.1, - 32.6, - 33, - 33.3, - 33.7, - 33.8, - 33.9, - 34.1, - 34.3, - 34.6, - 35, - 35.2, - 35, - 35.1, - 35.5, - 36.2, - 36.9, - 38.1, - 39.2, - 40.9, - 44.3, - 45.5, - 46.5, - 47.3, - 48.1, - 49, - 49.7, - 50, - 50, - 49.8, - 49.6, - 49.6, - 49.8, - 50.4, - 50.8, - 51.2, - 51.4, - 51.3, - 51.3, - 52, - 52.9, - 54.4, - 57, - 56.6, - 57.5, - 58.6, - 59.5, - 60.2, - 60.6, - 61.5, - 63.5, - 65.4, - 68.4, - 72.4, - 74.4, - 75.9, - 77, - 78, - 80.3, - 89.4, - 92.8, - 93.9, - 95.1, - 96.6, - 98, - 98.7, - 99.4, - 100.3, - 101.3, - 102.5, - 109.8, - 120.4, - 126.8, - 127.8, - 128.3, - 130, - 130.5, - 131, - 132.4, - 134.1, - 133.9, - 123, - 100.7, - 99.7, - 98.3, - 97, - 95.6, - 94.3, - 90.7, - 85.1, - 82.8, - 81.5, - 80.3, - 79.1, - 78.2, - 77.2, - 74.9, - 70.9, - 67.6, - 64.2, - 62.8, - 61.6, - 60.7, - 58.9, - 58.1, - 57.4, - 56.6, - 55.8, - 55.1, - 54.5, - 53.9, - 53.1, - 52.1, - 50.9, - 49.7, - 49, - 48.5, - 47.8, - 47.5, - 47.1, - 46.3, - 45.5, - 44.6, - 43.4, - 41.7, - 40.3, - 39.3, - 38.5, - 38, - 37.7, - 37.4, - 37, - 36.8, - 36.6, - 36, - 35.3, - 34.3, - 32.7, - 30.3, - 29.4, - 28.9, - 28.5, - 28.2, - 28.1, - 27.9, - 27.7, - 27.6, - 27.5, - 27.3, - 27.2, - 27.4, - 27.8, - 27.9, - 28, - 28.1, - 27.9, - 27.7, - 27.3, - 26.3, - 25.5, - 25, - 25.1, - 25.4, - 26, - 26.6, - 27.3, - 27.8, - 27.8, - 27.9, - 27.5, - 26.9, - 26.4, - 26.1, - 25.9, - 25.7, - 25.5, - 25.2, - 25, - 25.3, - 25.5, - 25.7, - 25.8, - 25.8, - 25.9, - 25.8, - 25.7, - 25.5, - 25.3, - 25.2, - 25.1, - 25.1, - 25, - 25.1, - 25.1, - 25.2, - 25.2, - 17.8, - 18.6, - 19.7, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.7, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.9, - 20.9, - 20.9, - 20.9, - 20.8, - 20.8, - 20.8, - 20.8, - 21.1, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 21.9, - 22, - 22.1, - 22.3, - 22.3, - 22.4, - 22.5, - 22.5, - 22.6, - 22.8, - 23.2, - 23.7, - 24, - 24.4, - 24.7, - 25, - 25.4, - 25.6, - 25.9, - 26.1, - 26.4, - 27, - 27.5, - 28.1, - 27.8, - 28, - 28.9, - 29.7, - 30.7, - 31.5, - 32.1, - 32.6, - 32.9, - 33.2, - 33.5, - 33.6, - 33.7, - 33.9, - 34.2, - 34.5, - 34.8, - 34.6, - 34.2, - 34.2, - 34.8, - 35.6, - 36.4, - 37.1, - 37.7, - 39, - 43, - 45.1, - 46.3, - 47.1, - 47.7, - 48.3, - 48.8, - 49, - 49, - 48.9, - 48.8, - 48.8, - 48.9, - 49.2, - 49.4, - 49.8, - 50, - 50.2, - 50.6, - 51.4, - 52, - 52.8, - 54.1, - 55.5, - 56.8, - 57.7, - 58.5, - 59.2, - 59.6, - 60.6, - 62.5, - 63.6, - 65.6, - 70.4, - 73.4, - 75.3, - 76.4, - 77.5, - 80.2, - 90, - 92.7, - 93.7, - 94.8, - 95.8, - 97, - 97.9, - 98.7, - 99.6, - 100.5, - 101.4, - 103.7, - 109.5, - 117.9, - 124.3, - 127.2, - 128.4, - 128.8, - 128.6, - 128.1, - 129.4, - 127.5, - 110.9, - 100.6, - 99.4, - 98, - 96.6, - 95.5, - 94.7, - 90.5, - 84.5, - 82.1, - 81, - 79.8, - 78.7, - 77.8, - 76.7, - 74.5, - 70.5, - 66.5, - 64.3, - 62.9, - 61.8, - 60.9, - 59.3, - 58.4, - 57.7, - 56.8, - 55.9, - 55.1, - 54.5, - 53.8, - 53.2, - 52.1, - 50.8, - 49.7, - 48.7, - 48.3, - 47.7, - 47.4, - 47.1, - 46.5, - 45.8, - 44.8, - 43.4, - 41.8, - 40.4, - 39.4, - 38.7, - 38.1, - 37.7, - 37.5, - 37.1, - 36.9, - 36.7, - 36.3, - 35.5, - 34.7, - 33.5, - 31.5, - 30.1, - 29.4, - 28.8, - 28.4, - 28.2, - 27.9, - 27.7, - 27.6, - 27.6, - 27.2, - 27.1, - 26.9, - 27.3, - 27.7, - 27.8, - 27.8, - 27.7, - 27.4, - 27, - 26.2, - 25.4, - 24.9, - 25.1, - 25.5, - 26.2, - 26.9, - 27.6, - 27.8, - 27.8, - 27.8, - 27.4, - 26.7, - 26.2, - 26, - 25.8, - 25.6, - 25.4, - 25, - 24.8, - 25, - 25.3, - 25.4, - 25.5, - 25.5, - 25.5, - 25.5, - 25.4, - 25.3, - 25.2, - 25.1, - 25, - 25, - 25, - 25, - 25.1, - 25.2, - 25.3, - 18, - 18.9, - 19.8, - 20.1, - 20.2, - 20.3, - 20.5, - 20.6, - 20.7, - 20.7, - 20.7, - 20.7, - 20.7, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.7, - 20.6, - 20.6, - 20.8, - 21.1, - 21.2, - 21.3, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.4, - 22.5, - 22.6, - 22.8, - 23.2, - 23.7, - 24.1, - 24.4, - 24.7, - 24.9, - 25.2, - 25.4, - 25.6, - 25.7, - 26.1, - 26.9, - 27.3, - 27.2, - 27.2, - 28.2, - 29.1, - 29.9, - 30.8, - 31.6, - 32.1, - 32.5, - 32.7, - 33, - 33, - 33.2, - 33.2, - 33.7, - 34, - 34.2, - 34.3, - 34.1, - 33.8, - 33.7, - 34.2, - 34.9, - 35.6, - 36, - 36.5, - 37.5, - 41.5, - 44.7, - 45.8, - 46.6, - 47.1, - 47.6, - 47.8, - 47.8, - 48, - 47.9, - 47.9, - 47.9, - 48, - 48.1, - 48.3, - 48.5, - 48.7, - 49.4, - 50, - 50.6, - 51.4, - 52.4, - 53.6, - 54.9, - 55.7, - 56.6, - 57.4, - 58, - 58.8, - 59.8, - 60.9, - 61.7, - 63.4, - 67.5, - 72.2, - 74.5, - 75.8, - 76.8, - 79.8, - 89.4, - 92.1, - 93.2, - 94.1, - 95.6, - 96.7, - 97.4, - 98, - 98.8, - 99.6, - 100.3, - 101.3, - 102.6, - 104.6, - 111.6, - 121.2, - 124.1, - 126.3, - 126.3, - 123.2, - 115.9, - 109.4, - 101.5, - 100.2, - 99, - 97.5, - 96.1, - 94.9, - 93.9, - 91, - 83.7, - 81.6, - 80.4, - 79.1, - 78.3, - 77.3, - 76, - 73.8, - 68.5, - 65.9, - 64.1, - 62.8, - 61.7, - 60.9, - 59, - 58.2, - 57.6, - 56.7, - 55.8, - 55, - 54.2, - 53.4, - 52.7, - 52.2, - 51, - 49.8, - 48.8, - 48, - 47.4, - 47.1, - 46.7, - 46.2, - 45.6, - 44.9, - 43.9, - 41.9, - 40.6, - 39.7, - 38.8, - 38.1, - 37.8, - 37.6, - 37.3, - 37.1, - 36.8, - 36.5, - 35.9, - 35.2, - 34.1, - 33, - 31.3, - 29.9, - 29.1, - 28.6, - 28.3, - 27.8, - 27.7, - 27.7, - 27.6, - 27.2, - 26.9, - 26.3, - 26, - 26.9, - 27.2, - 27.1, - 27, - 26.6, - 26.2, - 25.7, - 25.2, - 24.8, - 25.1, - 25.7, - 26.4, - 27.1, - 27.7, - 27.8, - 27.8, - 27.7, - 27.3, - 26.5, - 26.1, - 25.9, - 25.7, - 25.5, - 25.2, - 24.9, - 24.7, - 24.8, - 24.9, - 25.1, - 25.2, - 25.1, - 25.2, - 25.3, - 25.2, - 25.1, - 25, - 25, - 24.9, - 24.9, - 24.9, - 25.1, - 25.1, - 25.2, - 25.3, - 18.1, - 19, - 19.9, - 20, - 20.1, - 20.2, - 20.4, - 20.5, - 20.6, - 20.6, - 20.6, - 20.6, - 20.7, - 20.7, - 20.7, - 20.7, - 20.7, - 20.6, - 20.6, - 20.5, - 20.5, - 20.5, - 20.7, - 20.9, - 21.1, - 21.3, - 21.5, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 21.9, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.4, - 22.5, - 22.8, - 23.2, - 23.6, - 24, - 24.3, - 24.4, - 24.6, - 24.7, - 24.8, - 25.1, - 25.4, - 26, - 26.7, - 26.9, - 26.4, - 27.1, - 28.3, - 29.2, - 30, - 30.9, - 31.6, - 31.9, - 32.2, - 32.4, - 32.6, - 32.6, - 32.7, - 32.8, - 33.3, - 33.8, - 33.9, - 33.9, - 33.7, - 33.6, - 33.4, - 33.8, - 34.3, - 34.9, - 35.2, - 35.9, - 36.8, - 39.8, - 43.7, - 45, - 45.9, - 46.6, - 46.9, - 46.9, - 47, - 47.1, - 46.9, - 46.8, - 46.9, - 47.3, - 47.3, - 47.4, - 47.7, - 48, - 48.7, - 49.4, - 50.1, - 50.8, - 51.9, - 52.9, - 53.9, - 54.5, - 55.3, - 56.2, - 57.1, - 58.2, - 58.9, - 59.6, - 60.8, - 62.9, - 66.1, - 71.7, - 74.3, - 75.8, - 76.8, - 78, - 84.8, - 90.6, - 92.1, - 93.3, - 94.8, - 96.1, - 96.8, - 97.3, - 97.9, - 98.8, - 99.9, - 101.1, - 102.2, - 103, - 103.9, - 106, - 109.5, - 114.8, - 115.9, - 109.7, - 103.4, - 101.9, - 100.8, - 99.7, - 98.3, - 96.9, - 95.6, - 94.5, - 93.2, - 89.8, - 83.2, - 81, - 79.6, - 78.5, - 77.5, - 76.6, - 75.4, - 73.2, - 67.7, - 65.7, - 64.2, - 62.7, - 61.6, - 60.8, - 59, - 58, - 57.2, - 56.3, - 55.5, - 54.7, - 53.9, - 53, - 52.1, - 51.4, - 50.7, - 49.9, - 49.1, - 48, - 47.3, - 46.8, - 46.3, - 45.7, - 45.3, - 44.9, - 44.3, - 42.1, - 40.4, - 39.6, - 38.7, - 38.1, - 37.8, - 37.7, - 37.5, - 37.2, - 37, - 36.7, - 36.3, - 35.7, - 34.7, - 33.8, - 32.1, - 30.1, - 29.3, - 28.7, - 28.3, - 27.8, - 27.7, - 27.7, - 27.6, - 27.1, - 26.5, - 25.7, - 25, - 25.4, - 25.8, - 26, - 26.1, - 25.9, - 25.5, - 25.3, - 24.9, - 24.8, - 25.2, - 25.7, - 26.4, - 27.2, - 27.7, - 27.8, - 27.8, - 27.6, - 27, - 26.4, - 26, - 25.8, - 25.7, - 25.4, - 25.1, - 24.8, - 24.6, - 24.6, - 24.7, - 24.7, - 24.8, - 24.9, - 24.9, - 25, - 25, - 25, - 24.9, - 24.9, - 24.8, - 24.9, - 24.9, - 25.1, - 25.2, - 25.3, - 25.4, - 18.2, - 19.1, - 19.8, - 19.8, - 19.8, - 20, - 20.2, - 20.4, - 20.5, - 20.5, - 20.5, - 20.6, - 20.6, - 20.6, - 20.6, - 20.6, - 20.5, - 20.4, - 20.3, - 20.3, - 20.3, - 20.4, - 20.7, - 20.9, - 21.2, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 21.9, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.5, - 22.7, - 23, - 23.5, - 23.8, - 23.9, - 24, - 24.3, - 24.3, - 24.3, - 24.6, - 25.2, - 25.8, - 26.2, - 26.1, - 26, - 27.1, - 28.2, - 29.2, - 30, - 30.8, - 31.2, - 31.4, - 31.4, - 31.6, - 32, - 32.2, - 32.5, - 32.8, - 33.2, - 33.5, - 33.6, - 33.6, - 33.5, - 33.4, - 33.2, - 33.5, - 33.8, - 34, - 34.4, - 35.3, - 36.2, - 37.4, - 40.4, - 44.3, - 45.4, - 46.1, - 46.3, - 46.2, - 46.3, - 46.1, - 46, - 46.1, - 46.3, - 46.5, - 46.4, - 46.5, - 46.9, - 47.5, - 48.2, - 48.9, - 49.6, - 50.4, - 51.3, - 52.2, - 53.1, - 53.5, - 54.5, - 55.7, - 56.6, - 57.3, - 57.7, - 58.3, - 59.8, - 61.9, - 66.5, - 72.7, - 75, - 76.1, - 77, - 77.8, - 80.3, - 87.8, - 91.2, - 92.8, - 94, - 95.2, - 96, - 96.6, - 97.6, - 98.5, - 99.7, - 100.7, - 101.6, - 102.3, - 102.7, - 103, - 103.3, - 104, - 105.5, - 102.7, - 102.3, - 101.3, - 100.3, - 99, - 97.3, - 96.1, - 94.9, - 93.8, - 92.7, - 87.7, - 81.8, - 80.5, - 79.1, - 78.2, - 76.8, - 75.2, - 72.6, - 70.8, - 67.4, - 65.6, - 64.3, - 62.6, - 61.3, - 60.3, - 58.7, - 57.8, - 57, - 56.2, - 55.4, - 54.5, - 53.7, - 53, - 52.2, - 51, - 50.3, - 49.5, - 48.7, - 47.9, - 47.2, - 46.9, - 46.4, - 45.8, - 45.1, - 44.5, - 43.1, - 40.6, - 39.5, - 38.9, - 38.3, - 38, - 37.8, - 37.7, - 37.6, - 37.4, - 37.1, - 36.8, - 36.5, - 35.9, - 35, - 34.1, - 33, - 30.7, - 29.3, - 28.7, - 28.2, - 27.8, - 27.8, - 27.7, - 27.7, - 27.1, - 26.6, - 25.6, - 24.9, - 24.7, - 25, - 25.3, - 25.4, - 25.4, - 25.1, - 24.9, - 24.7, - 24.8, - 25.1, - 25.7, - 26.3, - 27, - 27.6, - 27.8, - 27.8, - 27.5, - 27, - 26.4, - 26, - 25.9, - 25.7, - 25.5, - 25.1, - 24.9, - 24.6, - 24.5, - 24.5, - 24.5, - 24.6, - 24.7, - 24.7, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.7, - 24.8, - 25, - 25.1, - 25.2, - 25.4, - 25.5, - 18, - 18.9, - 19.4, - 19.4, - 19.3, - 19.7, - 20, - 20.2, - 20.3, - 20.4, - 20.4, - 20.4, - 20.5, - 20.5, - 20.5, - 20.4, - 20.4, - 20.2, - 20.2, - 20.2, - 20.3, - 20.5, - 20.8, - 21, - 21.2, - 21.3, - 21.4, - 21.5, - 21.6, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.5, - 22.7, - 22.9, - 23.3, - 23.5, - 23.5, - 23.6, - 23.9, - 23.9, - 24.1, - 24.4, - 24.8, - 25.3, - 25.3, - 25.4, - 25.8, - 26.9, - 28.1, - 29.1, - 29.8, - 30.4, - 30.5, - 30.4, - 30.4, - 31.1, - 31.8, - 32.3, - 32.6, - 32.8, - 33.1, - 33.3, - 33.4, - 33.4, - 33.3, - 33.1, - 33, - 33.3, - 33.6, - 33.6, - 33.8, - 34.6, - 35.3, - 36.1, - 38, - 43.4, - 44.9, - 45.6, - 45.6, - 45.5, - 45.3, - 45.2, - 45.3, - 45.3, - 45.5, - 45.7, - 45.4, - 45.5, - 45.9, - 46.8, - 47.6, - 48.4, - 49.1, - 50, - 50.8, - 51.6, - 52.3, - 52.7, - 54, - 54.9, - 55.7, - 56.2, - 56.5, - 57.5, - 59.1, - 60.1, - 65, - 72.5, - 75.1, - 75.9, - 76.7, - 77.6, - 78.5, - 82.6, - 88.9, - 92.6, - 93.7, - 94.5, - 95.2, - 96, - 97.1, - 98.2, - 99.2, - 99.9, - 100.7, - 101.4, - 101.8, - 102.2, - 102.4, - 102.5, - 102.1, - 101.8, - 101.4, - 100.6, - 99.8, - 98, - 96.6, - 95.6, - 94.5, - 93.3, - 91.3, - 85.4, - 81.5, - 80.2, - 78.8, - 77.7, - 76.6, - 74.3, - 70, - 68.6, - 66.8, - 65.2, - 63.7, - 62.2, - 61, - 59.7, - 58.5, - 57.6, - 56.7, - 55.8, - 54.9, - 54, - 53.1, - 52.4, - 51.9, - 50.9, - 50.1, - 49, - 48.2, - 47.6, - 47.1, - 46.7, - 46.2, - 45.5, - 44.9, - 44.3, - 41.2, - 39.5, - 38.6, - 38.1, - 37.8, - 37.7, - 37.7, - 37.6, - 37.6, - 37.5, - 37.4, - 36.9, - 36.5, - 36.1, - 35.2, - 34.2, - 33.6, - 31.9, - 29.5, - 28.7, - 28.1, - 27.8, - 27.8, - 27.7, - 27.6, - 27, - 26.6, - 25.6, - 25, - 24.6, - 24.6, - 24.8, - 25, - 25.1, - 24.9, - 24.7, - 24.5, - 24.7, - 25.1, - 25.6, - 26.3, - 26.9, - 27.5, - 27.7, - 27.7, - 27.4, - 27.1, - 26.8, - 26, - 25.9, - 25.7, - 25.5, - 25.3, - 24.9, - 24.5, - 24.4, - 24.4, - 24.4, - 24.5, - 24.5, - 24.5, - 24.6, - 24.6, - 24.7, - 24.7, - 24.7, - 24.7, - 24.8, - 25, - 25.1, - 25.3, - 25.5, - 25.7, - 17.7, - 18.3, - 18.7, - 18.8, - 18.8, - 19.2, - 19.7, - 20, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.2, - 20.1, - 20.2, - 20, - 20.1, - 20.2, - 20.4, - 20.6, - 20.8, - 21, - 21.1, - 21.3, - 21.4, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.3, - 22.4, - 22.5, - 22.6, - 22.7, - 22.9, - 23.1, - 23.2, - 23.3, - 23.5, - 23.7, - 23.9, - 24.1, - 24.2, - 24.5, - 24.7, - 24.9, - 25.2, - 25.9, - 26.9, - 28, - 29, - 29.5, - 29.7, - 29.5, - 29.6, - 30.2, - 31.4, - 32.1, - 32.4, - 32.6, - 32.8, - 33, - 33.2, - 33.3, - 33.3, - 33.2, - 32.8, - 32.7, - 33.1, - 33.3, - 33.3, - 33.4, - 33.8, - 34.4, - 35.3, - 36.7, - 42.3, - 44.3, - 44.9, - 44.8, - 44.7, - 44.6, - 44.6, - 44.7, - 44.7, - 44.9, - 44.8, - 44.5, - 44.7, - 45.4, - 46.3, - 47.2, - 48, - 48.6, - 49.5, - 50.2, - 50.9, - 51.4, - 52, - 53.1, - 53.8, - 54.4, - 54.9, - 55.7, - 57, - 58, - 59.3, - 63, - 70.7, - 74.2, - 75.4, - 76.2, - 77.2, - 77.9, - 78.8, - 83.3, - 92, - 94.1, - 94.3, - 94.4, - 95.1, - 96, - 97.3, - 98.2, - 98.9, - 99.6, - 100.3, - 100.8, - 101.3, - 101.7, - 101.8, - 101.1, - 100.7, - 100.3, - 99.6, - 98.6, - 97.1, - 95.9, - 95, - 94.1, - 93, - 89.2, - 83.4, - 81.1, - 79.8, - 78.7, - 77.5, - 76.4, - 74.1, - 69.7, - 68.1, - 66.5, - 64.8, - 63, - 61.6, - 60.5, - 59.3, - 58.4, - 57.5, - 56.6, - 55.7, - 54.8, - 53.8, - 52.8, - 51.8, - 51.2, - 50.4, - 49.7, - 48.8, - 48.1, - 47.5, - 47.1, - 46.7, - 46.2, - 45.5, - 44.8, - 44.2, - 40.9, - 39.3, - 38.5, - 37.7, - 37.4, - 37.4, - 37.5, - 37.5, - 37.5, - 37.4, - 37.2, - 36.9, - 36.4, - 35.9, - 35, - 34.1, - 33.4, - 31.3, - 29.5, - 28.7, - 28.1, - 27.8, - 27.7, - 27.7, - 27.4, - 27, - 26.4, - 25.5, - 24.9, - 24.6, - 24.4, - 24.5, - 24.6, - 24.8, - 24.7, - 24.5, - 24.5, - 24.7, - 25.1, - 25.6, - 26.2, - 26.9, - 27.5, - 27.6, - 27.6, - 27.3, - 26.9, - 26.6, - 25.9, - 25.8, - 25.7, - 25.5, - 25.2, - 24.8, - 24.4, - 24.3, - 24.3, - 24.3, - 24.3, - 24.4, - 24.4, - 24.4, - 24.5, - 24.6, - 24.6, - 24.6, - 24.7, - 24.9, - 25, - 25.2, - 25.4, - 25.7, - 26, - 17.5, - 17.7, - 18, - 18.2, - 18.6, - 19, - 19.5, - 19.9, - 20.2, - 20.2, - 20.2, - 20.2, - 20.1, - 20.1, - 20, - 19.9, - 19.9, - 19.9, - 20.1, - 20.2, - 20.4, - 20.5, - 20.7, - 20.9, - 21, - 21.2, - 21.2, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.3, - 22.4, - 22.5, - 22.6, - 22.7, - 22.8, - 23, - 23.2, - 23.4, - 23.6, - 23.7, - 23.9, - 24.1, - 24.3, - 24.5, - 24.7, - 25.1, - 25.6, - 26.4, - 27.3, - 28.1, - 28.7, - 28.8, - 28.5, - 28.8, - 29.5, - 30.4, - 31.5, - 32.1, - 32.3, - 32.6, - 32.8, - 33, - 33.1, - 33.1, - 33.1, - 32.9, - 32.5, - 32.5, - 32.8, - 32.9, - 32.9, - 33, - 33.3, - 33.9, - 34.8, - 35.9, - 39.2, - 43.2, - 44.2, - 44.2, - 44, - 43.9, - 43.9, - 44.1, - 44.3, - 44.3, - 43.9, - 43.8, - 44.4, - 45.5, - 46.1, - 46.8, - 47.2, - 48, - 49, - 49.7, - 50.3, - 50.6, - 51.5, - 52.4, - 52.7, - 53.2, - 53.8, - 55.1, - 56, - 57.1, - 58.3, - 60.6, - 66.8, - 71.6, - 74.1, - 75.4, - 76.3, - 77.2, - 77.9, - 80, - 90.9, - 93.5, - 93.3, - 93.5, - 94.3, - 95.2, - 96, - 96.9, - 97.8, - 98.5, - 99.2, - 100, - 100.4, - 100.8, - 100.9, - 100, - 99.2, - 98.7, - 98, - 97.1, - 96.2, - 95.2, - 94.3, - 93.6, - 92.8, - 88.3, - 82.9, - 80.8, - 79.3, - 78.4, - 77.2, - 75.5, - 72.4, - 69.6, - 68, - 66.5, - 64.8, - 62.7, - 61, - 59.8, - 58.3, - 57.7, - 57, - 56.3, - 55.5, - 54.7, - 53.9, - 53, - 52, - 51.2, - 50.6, - 50, - 49.2, - 48.3, - 47.7, - 47.2, - 46.8, - 46.1, - 45.4, - 44.6, - 43.9, - 40.4, - 39, - 38.3, - 37.6, - 37.2, - 37, - 37.3, - 37.3, - 37.3, - 37.1, - 36.9, - 36.5, - 36, - 35.4, - 34.5, - 33.8, - 32.6, - 30.5, - 29.5, - 28.7, - 28, - 27.8, - 27.8, - 27.7, - 27.4, - 27, - 26.5, - 25.6, - 25, - 24.6, - 24.4, - 24.3, - 24.4, - 24.6, - 24.5, - 24.4, - 24.6, - 24.8, - 25.1, - 25.5, - 26.2, - 26.8, - 27.4, - 27.2, - 27.1, - 26.8, - 26.4, - 26, - 25.8, - 25.8, - 25.7, - 25.4, - 25, - 24.6, - 24.4, - 24.2, - 24.2, - 24.2, - 24.2, - 24.3, - 24.3, - 24.4, - 24.5, - 24.5, - 24.5, - 24.7, - 24.8, - 24.9, - 25.1, - 25.3, - 25.6, - 25.9, - 26.2, - 17.4, - 17.7, - 17.9, - 18.5, - 18.9, - 19.3, - 19.7, - 20, - 20.1, - 20.1, - 20.1, - 20, - 19.9, - 19.9, - 19.8, - 19.7, - 19.8, - 20, - 20.1, - 20.2, - 20.3, - 20.5, - 20.6, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.4, - 22.5, - 22.6, - 22.7, - 22.9, - 23, - 23.2, - 23.4, - 23.6, - 23.7, - 23.9, - 24.1, - 24.3, - 24.6, - 25.1, - 25.6, - 26.2, - 27, - 27.7, - 28.2, - 28.1, - 27.5, - 27.6, - 28.7, - 29.6, - 30.5, - 31.4, - 31.9, - 32, - 32.5, - 32.8, - 32.9, - 32.9, - 32.9, - 32.8, - 32.7, - 32.2, - 32.3, - 32.5, - 32.4, - 32.5, - 32.6, - 33.1, - 33.6, - 34.3, - 35.4, - 36.7, - 40.8, - 43.4, - 42.9, - 42.5, - 42.6, - 42.7, - 43.3, - 43.6, - 43.5, - 43.1, - 43.2, - 44.3, - 45.3, - 45.6, - 46.1, - 46.6, - 47.5, - 48.2, - 49, - 49.6, - 50, - 50.9, - 51.3, - 51.5, - 52, - 53.2, - 54.3, - 55.3, - 56.5, - 57.3, - 58.2, - 62, - 66.3, - 71.5, - 74.1, - 75.6, - 76.7, - 77.4, - 78.3, - 86.2, - 90.8, - 92.2, - 93.1, - 93.8, - 94.3, - 95.1, - 96.2, - 97, - 97.8, - 98.4, - 99.4, - 99.4, - 99.6, - 99.8, - 98.9, - 98.1, - 97.3, - 96.7, - 96.1, - 95.2, - 94.4, - 93.6, - 93.1, - 92.5, - 87.2, - 82, - 80.8, - 79.2, - 78.1, - 76.9, - 74.7, - 70.8, - 68.8, - 67.2, - 65.6, - 64.1, - 62.4, - 60.9, - 59.4, - 57.8, - 56.7, - 56, - 55.6, - 55, - 54.3, - 53.7, - 53, - 52.3, - 51.7, - 51, - 50.4, - 49.6, - 48.8, - 47.9, - 47.2, - 46.6, - 46, - 45.3, - 44.6, - 43.9, - 40.2, - 38.8, - 37.9, - 37.4, - 37, - 36.5, - 36.7, - 37, - 37, - 36.8, - 36.6, - 36.1, - 35.5, - 34.7, - 34, - 33.2, - 31.2, - 29.9, - 29.2, - 28.5, - 27.9, - 27.8, - 27.8, - 27.8, - 27.4, - 27, - 26.6, - 25.7, - 25.1, - 24.6, - 24.3, - 24.2, - 24.2, - 24.4, - 24.3, - 24.3, - 24.6, - 24.9, - 25.2, - 25.6, - 26.2, - 26.8, - 27, - 26.7, - 26.4, - 26.2, - 25.9, - 25.8, - 25.7, - 25.7, - 25.6, - 25.4, - 25, - 24.6, - 24.3, - 24.1, - 24.1, - 24.1, - 24.1, - 24.2, - 24.2, - 24.3, - 24.4, - 24.4, - 24.6, - 24.7, - 24.9, - 25.1, - 25.2, - 25.4, - 25.8, - 26.1, - 26.3, - 17.6, - 18.1, - 18.5, - 19, - 19.3, - 19.6, - 20, - 20.1, - 20.1, - 20, - 19.9, - 19.8, - 19.7, - 19.6, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.3, - 20.4, - 20.5, - 20.7, - 20.8, - 21, - 21.1, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.4, - 22.5, - 22.5, - 22.7, - 22.8, - 22.9, - 23.1, - 23.3, - 23.5, - 23.6, - 23.8, - 24.1, - 24.4, - 24.8, - 25.4, - 26, - 26.7, - 27.4, - 27.7, - 27.6, - 26.9, - 26.9, - 27.7, - 28.9, - 29.8, - 30.5, - 31.1, - 31.4, - 31.7, - 32.3, - 32.7, - 32.6, - 32.5, - 32.4, - 32.5, - 32.3, - 31.7, - 31.6, - 31.9, - 31.7, - 32.1, - 32.5, - 33, - 33.4, - 34, - 34.9, - 35.6, - 37.5, - 40.5, - 40, - 39.3, - 39.7, - 40.1, - 41, - 42.7, - 42.5, - 42.2, - 42.6, - 43.8, - 44.6, - 45, - 45.6, - 46.3, - 46.9, - 47.1, - 48.2, - 48.8, - 49.3, - 50, - 50.1, - 50.4, - 51.4, - 52.5, - 53.7, - 54.9, - 55.6, - 56.3, - 56.9, - 58.9, - 62.8, - 67.5, - 72.6, - 75, - 75.9, - 76.4, - 77.1, - 79.9, - 85.9, - 90.6, - 92.7, - 93.3, - 93.8, - 94.7, - 95.4, - 96, - 96.6, - 97.6, - 98.6, - 98.5, - 98.4, - 98.3, - 97.6, - 97, - 96.2, - 95.6, - 95, - 94.3, - 93.6, - 92.8, - 92.2, - 91.4, - 86.8, - 81.6, - 80.7, - 79.3, - 78.1, - 76.9, - 75.3, - 71.8, - 68.4, - 66.3, - 64.3, - 62.8, - 61.4, - 60.1, - 58.6, - 57.3, - 56.1, - 54.9, - 54.7, - 54.2, - 53.7, - 53.1, - 52.5, - 52.1, - 51.6, - 51.1, - 50.3, - 49.6, - 49, - 48, - 47.3, - 46.7, - 46.2, - 45.5, - 44.7, - 43.8, - 40.6, - 38.8, - 37.8, - 37.3, - 36.8, - 36.2, - 36.2, - 36.4, - 36.5, - 36.5, - 36.3, - 35.5, - 34.9, - 34, - 33.4, - 32.2, - 30.2, - 29.6, - 28.9, - 28.2, - 27.9, - 27.8, - 27.8, - 27.7, - 27.3, - 27, - 26.6, - 25.8, - 25.1, - 24.6, - 24.3, - 24.2, - 24.1, - 24.3, - 24.2, - 24.3, - 24.6, - 24.9, - 25.3, - 25.7, - 26.3, - 26.7, - 26.7, - 26.4, - 26, - 25.8, - 25.7, - 25.7, - 25.7, - 25.6, - 25.5, - 25.3, - 24.9, - 24.6, - 24.3, - 24.1, - 24, - 23.9, - 24, - 23.9, - 23.8, - 24, - 24.2, - 24.4, - 24.6, - 24.7, - 24.9, - 25.2, - 25.3, - 25.6, - 25.8, - 26.1, - 26.4, - 18.2, - 18.8, - 19.1, - 19.4, - 19.6, - 20, - 20.1, - 20.1, - 20, - 19.9, - 19.7, - 19.6, - 19.5, - 19.5, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20.2, - 20.3, - 20.4, - 20.6, - 20.7, - 20.9, - 21, - 21.2, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.4, - 22.5, - 22.6, - 22.8, - 23.1, - 23.3, - 23.4, - 23.6, - 23.9, - 24.3, - 24.6, - 25.1, - 25.7, - 26.3, - 26.8, - 27.2, - 27.1, - 26.5, - 26.1, - 26.7, - 27.8, - 29.1, - 29.9, - 30.4, - 30.5, - 30.8, - 31.4, - 32, - 32.5, - 32.3, - 32, - 31.9, - 32.1, - 32, - 31.2, - 30.7, - 30.8, - 30.9, - 32.1, - 32.5, - 32.8, - 33.2, - 33.6, - 34.2, - 34.9, - 35.7, - 36.8, - 37.5, - 37.4, - 38.2, - 38.6, - 38.5, - 40.4, - 40.7, - 41.1, - 42.4, - 43.6, - 44.4, - 44.8, - 45, - 45.4, - 45.9, - 46.5, - 47.5, - 48.1, - 48.6, - 49, - 49.3, - 49.9, - 51, - 52.2, - 53.2, - 54, - 54.7, - 55.3, - 56.5, - 58.1, - 60.9, - 64, - 69.3, - 73.4, - 74.7, - 75.8, - 76.9, - 78, - 81.1, - 87.4, - 91.7, - 92.7, - 93.4, - 94.2, - 94.7, - 95.2, - 95.8, - 96.6, - 97.4, - 97.7, - 97.5, - 97.2, - 96.7, - 95.9, - 95.3, - 94.9, - 94.2, - 93.5, - 92.6, - 90.3, - 88.7, - 87.4, - 84.2, - 80.9, - 80.2, - 79.2, - 78.2, - 77, - 75.9, - 73.1, - 68.7, - 66.3, - 64.1, - 61.9, - 60.5, - 58.8, - 57.8, - 56.8, - 55.8, - 54.4, - 53.8, - 53.3, - 52.7, - 52.2, - 51.7, - 51.3, - 51, - 50.6, - 50.1, - 49.4, - 48.5, - 47.7, - 47.1, - 46.6, - 46.2, - 45.5, - 44.6, - 43.2, - 40.5, - 39.1, - 38, - 37.3, - 36.7, - 36.1, - 35.3, - 35.4, - 35.6, - 35.6, - 35.7, - 35.2, - 34.4, - 33.6, - 32.8, - 31, - 29.9, - 29.3, - 28.8, - 28, - 27.8, - 27.8, - 27.7, - 27.5, - 27.2, - 27, - 26.5, - 25.7, - 24.9, - 24.5, - 24.3, - 24.1, - 24.1, - 24.1, - 24.1, - 24.3, - 24.7, - 25, - 25.3, - 25.8, - 26.4, - 26.6, - 26.3, - 26, - 25.8, - 25.6, - 25.5, - 25.5, - 25.5, - 25.5, - 25.3, - 25.1, - 24.7, - 24.5, - 24.3, - 24.1, - 23.9, - 23.7, - 23.7, - 23.5, - 23.5, - 23.6, - 23.9, - 24.4, - 24.6, - 24.8, - 24.9, - 25.2, - 25.4, - 25.7, - 25.9, - 26.2, - 26.4, - 18.6, - 19.2, - 19.5, - 19.8, - 19.9, - 20, - 20, - 20, - 19.8, - 19.7, - 19.5, - 19.4, - 19.4, - 19.5, - 19.6, - 19.7, - 19.9, - 20.1, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.8, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 21.8, - 21.9, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.5, - 22.6, - 22.8, - 23, - 23.2, - 23.4, - 23.7, - 24.1, - 24.5, - 24.9, - 25.4, - 25.9, - 26.3, - 26.6, - 26.8, - 26.5, - 25.8, - 25.9, - 26.6, - 27.8, - 29.1, - 29.8, - 30.1, - 29.9, - 30.4, - 31, - 31.8, - 32.2, - 31.9, - 31.5, - 31.1, - 31.6, - 31.8, - 31.1, - 30.2, - 30, - 30.5, - 31.8, - 32.3, - 32.6, - 33, - 33.3, - 33.8, - 34.4, - 35, - 35.6, - 36.1, - 36.3, - 37.1, - 37.5, - 37.2, - 37.5, - 37.3, - 39.3, - 42.8, - 43.7, - 44, - 43.8, - 44.3, - 45.1, - 46, - 46.9, - 47.3, - 47.6, - 47.8, - 48.2, - 48.7, - 49.5, - 50.6, - 51.7, - 52.4, - 52.8, - 53.6, - 54.8, - 56, - 57.5, - 59.5, - 61.3, - 64.4, - 70.1, - 73.7, - 75.7, - 76.7, - 77.7, - 78.4, - 83.2, - 88.5, - 91.5, - 92.7, - 93.6, - 94, - 94.6, - 95.2, - 95.8, - 96.4, - 97.1, - 96.9, - 96.5, - 96, - 95, - 94.4, - 94.1, - 93.6, - 93, - 91.2, - 87.1, - 85.7, - 83.4, - 81.5, - 80.4, - 79.5, - 78.6, - 77.8, - 76.9, - 76.1, - 73, - 68.7, - 66.6, - 64.6, - 61.8, - 59.6, - 57.9, - 57, - 56.1, - 55.3, - 54.5, - 53.8, - 53, - 51.9, - 51.3, - 50.8, - 50.4, - 50.3, - 50, - 49.4, - 48.8, - 48.1, - 47.3, - 46.8, - 46.4, - 46.1, - 45.3, - 44.6, - 43.3, - 40.8, - 39.3, - 38.1, - 37.4, - 36.8, - 36.1, - 35, - 34.3, - 34.3, - 34.5, - 34.9, - 34.8, - 34.2, - 33.5, - 32.4, - 30.4, - 29.6, - 29, - 28.3, - 27.8, - 27.7, - 27.7, - 27.7, - 27.2, - 27.1, - 26.9, - 26.4, - 25.4, - 24.7, - 24.5, - 24.2, - 24.1, - 24, - 23.9, - 24, - 24.3, - 24.7, - 25, - 25.3, - 25.7, - 26.2, - 26.2, - 26, - 25.8, - 25.7, - 25.5, - 25.4, - 25.2, - 25.3, - 25.3, - 25.1, - 24.8, - 24.5, - 24.4, - 24.2, - 24.1, - 23.8, - 23.6, - 23.5, - 23.4, - 23.4, - 23.4, - 23.8, - 24.3, - 24.5, - 24.7, - 24.9, - 25.1, - 25.3, - 25.6, - 25.9, - 26.2, - 26.4, - 19, - 19.4, - 19.7, - 19.9, - 19.9, - 20, - 19.9, - 19.8, - 19.6, - 19.4, - 19.3, - 19.2, - 19.3, - 19.5, - 19.7, - 19.8, - 20, - 20.1, - 20.2, - 20.3, - 20.4, - 20.5, - 20.7, - 20.8, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.5, - 22.7, - 22.8, - 22.9, - 23.1, - 23.3, - 23.7, - 24.1, - 24.5, - 24.9, - 25.4, - 25.9, - 26.1, - 26.3, - 26.4, - 25.9, - 25.3, - 25.7, - 26.6, - 27.6, - 28.9, - 29.3, - 29.3, - 29.3, - 30.1, - 30.9, - 31.6, - 31.8, - 31.5, - 30.8, - 30.4, - 31.2, - 31.3, - 30.6, - 29.8, - 30, - 30.7, - 31.8, - 32.3, - 32.6, - 32.9, - 33.3, - 33.7, - 34.1, - 34.4, - 34.8, - 34.9, - 35.4, - 35.9, - 36.2, - 36.1, - 36.4, - 36, - 38.3, - 42.6, - 43.2, - 42.9, - 42.9, - 44.2, - 45, - 45.9, - 46.5, - 46.7, - 47, - 47.1, - 47.6, - 48.4, - 49.4, - 50.6, - 51.3, - 51.8, - 52.1, - 52.9, - 54.3, - 55.5, - 56.8, - 58.6, - 59.7, - 61.8, - 66.5, - 72.8, - 75.1, - 76, - 76.8, - 77.5, - 79.2, - 83.9, - 87.8, - 91.2, - 92.7, - 93.4, - 94.1, - 94.7, - 95, - 95.7, - 96.3, - 96.2, - 95.8, - 95, - 94.3, - 93.6, - 93.2, - 93, - 92.2, - 88.8, - 85.8, - 84.1, - 81.6, - 80.8, - 79.6, - 78.7, - 77.9, - 77.1, - 76.2, - 75, - 71.4, - 68.4, - 66.5, - 64.5, - 61.3, - 58.7, - 57.4, - 56.2, - 55.2, - 54.5, - 54.1, - 53.5, - 53.1, - 52.1, - 51.2, - 50.5, - 49.9, - 49.4, - 49, - 48.7, - 48.1, - 47.6, - 46.9, - 46.3, - 46, - 45.7, - 45.2, - 44.6, - 43.8, - 41.4, - 39.4, - 38.3, - 37.6, - 37, - 36.3, - 35.1, - 33.8, - 33.1, - 33.2, - 33.9, - 34, - 33.7, - 33.1, - 31.5, - 29.7, - 29.2, - 28.5, - 27.8, - 27.6, - 27.7, - 27.7, - 27.6, - 27.2, - 26.9, - 26.8, - 26.3, - 25.3, - 24.6, - 24.3, - 24.2, - 24, - 23.9, - 23.8, - 24, - 24.3, - 24.7, - 24.9, - 25.2, - 25.5, - 25.7, - 25.8, - 25.7, - 25.7, - 25.6, - 25.5, - 25.2, - 25, - 25, - 25, - 24.8, - 24.6, - 24.4, - 24.3, - 24.1, - 24, - 23.8, - 23.6, - 23.5, - 23.4, - 23.3, - 23.4, - 23.6, - 24, - 24.5, - 24.7, - 24.8, - 25, - 25.3, - 25.6, - 25.9, - 26.2, - 26.4, - 19.2, - 19.6, - 19.8, - 19.9, - 19.9, - 19.8, - 19.7, - 19.5, - 19.3, - 19.2, - 19.2, - 19.3, - 19.5, - 19.6, - 19.7, - 19.8, - 20, - 20.1, - 20.2, - 20.3, - 20.4, - 20.6, - 20.7, - 20.9, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.6, - 22.6, - 22.8, - 23, - 23.2, - 23.6, - 24, - 24.4, - 24.8, - 25.3, - 25.6, - 25.7, - 25.9, - 25.8, - 25.3, - 25, - 25.8, - 26.5, - 27.1, - 28, - 28.3, - 28.4, - 29, - 29.9, - 30.8, - 31.4, - 31.4, - 31, - 30.1, - 29.9, - 30.5, - 30.3, - 29.6, - 29.2, - 30.1, - 31.1, - 32, - 32.5, - 32.8, - 33.1, - 33.4, - 33.6, - 33.9, - 34, - 34.2, - 34.5, - 34.9, - 35, - 35, - 35, - 35.5, - 35.7, - 37.1, - 39.8, - 41.5, - 41.4, - 42.5, - 43.7, - 44.5, - 45.3, - 45.6, - 46.1, - 46.4, - 46.9, - 47.7, - 48.3, - 49.1, - 50.1, - 50.9, - 51.2, - 51.5, - 52.6, - 53.8, - 55, - 56.1, - 57.3, - 59.2, - 61.2, - 63.9, - 69.5, - 73, - 74.9, - 76, - 77.2, - 78.4, - 80.1, - 82.9, - 88.7, - 91.6, - 92.8, - 93.5, - 93.9, - 94.2, - 95, - 95.6, - 95.4, - 94.7, - 94.1, - 93.5, - 93, - 91.9, - 91.1, - 89.2, - 86.2, - 84.9, - 82, - 80.9, - 80, - 78.8, - 78, - 77.1, - 76.4, - 75.3, - 72.2, - 69.1, - 67.4, - 65.6, - 63, - 60.4, - 58.4, - 57, - 55.7, - 54.4, - 53.6, - 53.3, - 52.5, - 52.3, - 51.8, - 51.2, - 50.4, - 49.6, - 48.7, - 48.1, - 47.9, - 47.4, - 47.1, - 46.5, - 46, - 45.7, - 45.2, - 44.7, - 44.3, - 43.3, - 40.8, - 39.4, - 38.4, - 37.7, - 37.2, - 36.5, - 35.3, - 34.1, - 33, - 32, - 32.7, - 32.9, - 32.4, - 31.4, - 29.9, - 28.9, - 28.5, - 27.9, - 27.5, - 27.6, - 27.6, - 27.7, - 27.6, - 27.3, - 26.9, - 26.6, - 26, - 25.1, - 24.5, - 24.3, - 24.2, - 24.1, - 23.9, - 23.8, - 23.9, - 24.3, - 24.6, - 24.9, - 25.1, - 25.3, - 25.4, - 25.5, - 25.5, - 25.5, - 25.5, - 25.4, - 25.2, - 24.9, - 24.7, - 24.7, - 24.5, - 24.4, - 24.2, - 24.1, - 24, - 23.9, - 23.7, - 23.5, - 23.5, - 23.3, - 23.3, - 23.3, - 23.5, - 23.9, - 24.5, - 24.7, - 24.8, - 25, - 25.2, - 25.5, - 25.8, - 26.1, - 26.4, - 19.4, - 19.7, - 19.8, - 19.8, - 19.7, - 19.6, - 19.4, - 19.2, - 19, - 19.1, - 19.2, - 19.4, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 21.8, - 22, - 22.1, - 22.3, - 22.4, - 22.5, - 22.5, - 22.5, - 22.8, - 23.1, - 23.5, - 23.8, - 24.3, - 24.7, - 25, - 25.3, - 25.3, - 25.4, - 25.3, - 24.9, - 24.8, - 25.7, - 26.3, - 26.7, - 27, - 27.3, - 27.7, - 28.7, - 29.8, - 30.6, - 31, - 30.8, - 30.3, - 29.6, - 29.4, - 29.7, - 29.3, - 28.3, - 29, - 30.1, - 31.4, - 32.1, - 32.5, - 32.9, - 33.2, - 33.4, - 33.7, - 33.8, - 33.7, - 33.9, - 34.3, - 34.9, - 34.3, - 34.1, - 34.3, - 34.5, - 35.2, - 36, - 36.8, - 38.2, - 39.1, - 41.8, - 43.1, - 44.2, - 44.5, - 44.8, - 45.4, - 46.3, - 47, - 47.6, - 48.2, - 48.9, - 49.5, - 50.2, - 50.7, - 51.4, - 52.3, - 53.5, - 54.6, - 55.7, - 56.8, - 59, - 61.1, - 63, - 64.9, - 68.6, - 73.6, - 75.4, - 76.7, - 78, - 78.9, - 79.9, - 85.3, - 89.7, - 92.2, - 92.9, - 93.2, - 93.7, - 94.5, - 94.8, - 94.5, - 94, - 93.4, - 93, - 92.4, - 89.3, - 87.6, - 86.4, - 85.4, - 83.1, - 80.7, - 80, - 79, - 78.2, - 77.4, - 76.5, - 75.5, - 73.9, - 70.1, - 68, - 66, - 64, - 61.1, - 59.5, - 57.8, - 56.7, - 55.2, - 53.9, - 52.8, - 52.3, - 51.8, - 51.2, - 51, - 50.8, - 50.1, - 49.2, - 48.2, - 47.7, - 47.3, - 46.9, - 46.5, - 46.2, - 45.8, - 45.4, - 44.8, - 44.2, - 43, - 41.8, - 40.2, - 39.1, - 38.2, - 37.6, - 37.2, - 36.5, - 35.5, - 34.3, - 33.1, - 31.4, - 30.5, - 30.3, - 29.8, - 29.3, - 28.6, - 28.3, - 27.9, - 27.5, - 27.5, - 27.5, - 27.6, - 27.7, - 27.6, - 27.4, - 26.8, - 26.5, - 25.8, - 25, - 24.5, - 24.4, - 24.4, - 24.2, - 23.9, - 23.8, - 23.8, - 24.2, - 24.6, - 24.8, - 25, - 25.2, - 25.3, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.1, - 24.8, - 24.5, - 24.4, - 24.2, - 24.2, - 24.1, - 24, - 23.9, - 23.8, - 23.6, - 23.5, - 23.4, - 23.3, - 23.2, - 23.3, - 23.4, - 23.8, - 24.3, - 24.6, - 24.8, - 25, - 25.2, - 25.4, - 25.8, - 26.1, - 26.3, - 19.5, - 19.7, - 19.7, - 19.7, - 19.6, - 19.3, - 19, - 18.9, - 19, - 19.2, - 19.4, - 19.5, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.4, - 22.4, - 22.7, - 23.1, - 23.4, - 23.7, - 24, - 24.4, - 24.7, - 24.9, - 24.9, - 25, - 24.8, - 24.5, - 24.6, - 25.4, - 25.8, - 26.1, - 26.4, - 26.9, - 27.6, - 28.6, - 29.6, - 30.2, - 30.3, - 30.1, - 29.6, - 29, - 29, - 29.2, - 28.1, - 28, - 29, - 30.1, - 31.2, - 32.1, - 32.4, - 32.8, - 33.2, - 33.4, - 33.6, - 33.8, - 33.5, - 33.6, - 34.3, - 34.7, - 33.9, - 33.5, - 33.6, - 34, - 34.6, - 35.3, - 35.8, - 36.4, - 37.2, - 40.4, - 42.5, - 43.3, - 43.6, - 44.2, - 45.1, - 46.2, - 46.7, - 47.3, - 48, - 48.4, - 49, - 49.8, - 50.8, - 51.8, - 52.5, - 53.3, - 54.4, - 55.3, - 56.4, - 58.8, - 60.4, - 61.6, - 62.6, - 65.8, - 71.3, - 74.3, - 76, - 77.1, - 77.9, - 78.6, - 81.8, - 87.8, - 91.5, - 92.5, - 92.9, - 93.6, - 94.1, - 94.1, - 93.8, - 93.4, - 93, - 92.3, - 90.1, - 87.2, - 86.2, - 85.4, - 84.2, - 81.1, - 80.1, - 79, - 78.1, - 77.5, - 76.7, - 76.1, - 74.5, - 72.1, - 69.4, - 66.9, - 65.1, - 62.6, - 60.1, - 58.4, - 57.3, - 56.3, - 54.8, - 53.6, - 52.5, - 51.6, - 51.2, - 50.7, - 50.1, - 50, - 49.6, - 48.8, - 48, - 47.4, - 46.9, - 46.6, - 46.1, - 45.8, - 45.3, - 45, - 44.6, - 43.7, - 41.7, - 40.6, - 39.6, - 38.7, - 37.9, - 37.3, - 36.8, - 36.2, - 35.4, - 34.4, - 33.2, - 31.2, - 29.3, - 28.7, - 28.3, - 28.1, - 28, - 27.8, - 27.4, - 27.4, - 27.4, - 27.5, - 27.5, - 27.5, - 27.5, - 27.2, - 26.8, - 26.3, - 25.6, - 25, - 24.7, - 24.6, - 24.4, - 24.3, - 24.1, - 23.8, - 23.7, - 24.1, - 24.5, - 24.7, - 24.9, - 25, - 25.1, - 25.2, - 25.3, - 25.3, - 25.3, - 25.2, - 25, - 24.7, - 24.5, - 24.3, - 24.1, - 24, - 24, - 23.9, - 23.8, - 23.6, - 23.4, - 23.4, - 23.3, - 23.2, - 23.1, - 23.2, - 23.3, - 23.6, - 24.1, - 24.6, - 24.8, - 25, - 25.2, - 25.4, - 25.7, - 25.9, - 26.2, - 19.5, - 19.6, - 19.6, - 19.5, - 19.1, - 18.2, - 18.7, - 18.9, - 19.2, - 19.4, - 19.5, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21.1, - 21.2, - 21.2, - 21.3, - 21.4, - 21.5, - 21.5, - 21.5, - 21.5, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.2, - 22.4, - 22.6, - 23, - 23.2, - 23.6, - 23.8, - 24.1, - 24.4, - 24.5, - 24.6, - 24.5, - 24.4, - 24.2, - 24.4, - 25, - 25.3, - 25.6, - 26.1, - 26.7, - 27.5, - 28.5, - 29.4, - 29.7, - 29.8, - 29.4, - 28.8, - 28.3, - 28.8, - 28.5, - 27.5, - 28, - 28.9, - 29.9, - 30.8, - 31.9, - 32.3, - 32.7, - 33, - 33.3, - 33.4, - 33.6, - 33.5, - 33.1, - 33.7, - 33.8, - 33.3, - 33.2, - 33.8, - 34.2, - 34.6, - 35, - 35.1, - 35.6, - 36.1, - 37.4, - 40.9, - 41.8, - 42.8, - 43.8, - 44.5, - 45.5, - 46, - 46.8, - 47.6, - 47.8, - 48.7, - 49.7, - 50.8, - 51.8, - 52.2, - 52.7, - 53.9, - 55, - 56.2, - 57.6, - 59.1, - 59.8, - 61.2, - 64.4, - 68.6, - 72.8, - 75, - 76.1, - 77.2, - 78.1, - 79.1, - 85.6, - 90.8, - 92.8, - 92.8, - 93.3, - 93.6, - 93.5, - 93.4, - 93.1, - 92.7, - 90.5, - 86.8, - 85.9, - 85.1, - 83.5, - 81.7, - 80.4, - 79.3, - 78.2, - 77.3, - 76.5, - 75.8, - 75, - 72.5, - 70.5, - 69.2, - 66.1, - 64.1, - 61.6, - 59.4, - 57.6, - 56.6, - 55.7, - 54.6, - 53.5, - 52.5, - 51.3, - 50.7, - 50.2, - 49.5, - 49.2, - 49, - 48.4, - 47.8, - 47.3, - 46.8, - 46.4, - 45.9, - 45.4, - 45, - 44.5, - 44, - 42.8, - 40.9, - 39.8, - 39, - 38.1, - 37.4, - 36.9, - 36.1, - 35.5, - 34.9, - 34.2, - 33.3, - 31.3, - 29.6, - 28.8, - 28, - 27.7, - 27.6, - 27.4, - 27.3, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.2, - 26.9, - 26.6, - 26, - 25.5, - 25.1, - 24.8, - 24.6, - 24.5, - 24.3, - 24.1, - 23.8, - 23.6, - 23.9, - 24.4, - 24.6, - 24.8, - 24.9, - 25, - 25.1, - 25.2, - 25.3, - 25.2, - 25.1, - 24.8, - 24.6, - 24.4, - 24.3, - 24.1, - 24, - 23.9, - 23.8, - 23.6, - 23.4, - 23.4, - 23.3, - 23.2, - 23.2, - 23.1, - 23.2, - 23.3, - 23.5, - 24, - 24.5, - 24.7, - 24.9, - 25, - 25.2, - 25.4, - 25.8, - 26.1, - 19.5, - 19.5, - 19.3, - 18.8, - 17.4, - 17, - 18.5, - 19.2, - 19.4, - 19.6, - 19.7, - 19.7, - 19.9, - 19.9, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.4, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.4, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.5, - 22.8, - 23.2, - 23.4, - 23.6, - 23.8, - 24, - 24.1, - 24.2, - 24.1, - 24, - 24.1, - 24.4, - 24.7, - 25, - 25.4, - 26, - 26.7, - 27.6, - 28.4, - 29.2, - 29.3, - 29.3, - 28.8, - 28.2, - 27.8, - 28.3, - 27.8, - 27.1, - 27.9, - 29, - 29.9, - 30.7, - 31.6, - 32.1, - 32.5, - 33.1, - 33.3, - 33.3, - 33.4, - 33.5, - 32.9, - 32.9, - 32.9, - 32.7, - 33, - 33.8, - 34.6, - 34.9, - 34.8, - 34.6, - 35, - 35.9, - 37.5, - 40.9, - 42.3, - 43, - 43.9, - 44.4, - 45, - 45.9, - 46.5, - 46.9, - 47.5, - 48.6, - 49.5, - 50.4, - 51.1, - 51.3, - 52, - 53.4, - 54.7, - 55.6, - 56.4, - 57.3, - 58.6, - 59.6, - 61.6, - 65.4, - 70.7, - 73.4, - 75.5, - 76.6, - 77.6, - 78.5, - 81.9, - 89.6, - 92.4, - 92.6, - 92.8, - 93, - 93.1, - 93.1, - 92.9, - 92.2, - 88.1, - 85.9, - 84.8, - 83.3, - 81.5, - 80.2, - 79.4, - 78.4, - 77.6, - 76.4, - 74.9, - 73.7, - 72.6, - 69.8, - 69, - 68.2, - 65.4, - 63.3, - 60.8, - 58.8, - 57.2, - 55.7, - 54.8, - 54.1, - 53.2, - 52.4, - 51.4, - 50.6, - 50, - 49.2, - 48.6, - 48.4, - 48.1, - 47.7, - 47.3, - 46.9, - 46.6, - 46.1, - 45.7, - 45.3, - 44.5, - 43.2, - 41.5, - 40, - 39.2, - 38.2, - 37.4, - 36.8, - 36.2, - 35.4, - 34.7, - 34.2, - 33.7, - 33.2, - 31.4, - 30, - 28.9, - 27.9, - 27.5, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27, - 26.9, - 26.9, - 26.8, - 26.7, - 26.4, - 25.8, - 25.3, - 25, - 24.8, - 24.6, - 24.5, - 24.3, - 24.1, - 23.8, - 23.5, - 23.7, - 24.1, - 24.4, - 24.5, - 24.6, - 24.7, - 24.9, - 25, - 25.1, - 25.1, - 24.8, - 24.6, - 24.3, - 24.2, - 24.1, - 23.9, - 23.9, - 23.8, - 23.5, - 23.4, - 23.3, - 23.3, - 23.3, - 23.2, - 23.1, - 23.1, - 23.2, - 23.3, - 23.5, - 23.9, - 24.5, - 24.7, - 24.8, - 25, - 25.2, - 25.4, - 25.7, - 26, - 19.3, - 19.1, - 18.3, - 17.6, - 16.7, - 17.1, - 18.4, - 19.3, - 19.5, - 19.6, - 19.7, - 19.8, - 19.9, - 19.9, - 20, - 20.1, - 20.1, - 20.2, - 20.2, - 20.3, - 20.4, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.4, - 21.4, - 21.4, - 21.5, - 21.7, - 21.8, - 21.8, - 21.9, - 22, - 22, - 22.1, - 22.3, - 22.5, - 22.8, - 23.1, - 23.3, - 23.5, - 23.6, - 23.6, - 23.7, - 23.8, - 23.8, - 23.8, - 24, - 24.3, - 24.5, - 24.9, - 25.5, - 26.2, - 26.9, - 27.6, - 28.5, - 28.9, - 28.9, - 28.7, - 28.2, - 27.7, - 27.5, - 27.8, - 26.9, - 26.5, - 27.9, - 29.1, - 29.8, - 30.3, - 30.9, - 31.8, - 32.3, - 32.8, - 33.1, - 33.2, - 33.2, - 33, - 32.5, - 32.1, - 32.4, - 32.4, - 32.7, - 33.4, - 34.2, - 34.7, - 34.6, - 34.2, - 34.6, - 35.8, - 38.1, - 41.9, - 43, - 43.6, - 44.1, - 44.6, - 45.1, - 45.7, - 46, - 46.5, - 47.6, - 48.5, - 49.2, - 49.7, - 50, - 50.6, - 51.6, - 52.8, - 54.2, - 54.8, - 55.4, - 56.2, - 57.4, - 58.5, - 59.5, - 61.8, - 67.6, - 72.3, - 75, - 76.2, - 77.2, - 78, - 80.2, - 87.2, - 90.9, - 92, - 92.3, - 92.3, - 92.6, - 92.8, - 92.7, - 91.8, - 87.2, - 85.3, - 83.6, - 81.5, - 80.3, - 79.2, - 78.3, - 77.6, - 77, - 76, - 73.7, - 71.3, - 70.1, - 68.9, - 67.5, - 66.1, - 64, - 62.3, - 60.1, - 57.9, - 56.7, - 55.2, - 53.8, - 53.3, - 52.6, - 51.8, - 51.1, - 50.5, - 49.8, - 49, - 48.3, - 47.9, - 47.8, - 47.4, - 47, - 46.7, - 46.6, - 46.2, - 45.8, - 45.4, - 44.6, - 43.3, - 40.8, - 39.4, - 38.3, - 37.7, - 37, - 35.7, - 35.2, - 34.6, - 33.8, - 33.1, - 32.9, - 32.5, - 30.6, - 29.6, - 28.7, - 27.7, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 26.9, - 26.7, - 26.7, - 26.6, - 26.5, - 26.4, - 26, - 25.4, - 25.1, - 24.9, - 24.7, - 24.6, - 24.5, - 24.4, - 24.2, - 23.8, - 23.5, - 23.5, - 23.7, - 23.9, - 24.1, - 24.2, - 24.3, - 24.6, - 24.7, - 24.8, - 24.7, - 24.5, - 24.3, - 24.1, - 24.1, - 24, - 23.8, - 23.7, - 23.6, - 23.4, - 23.3, - 23.3, - 23.2, - 23.2, - 23.1, - 23, - 23, - 23.2, - 23.3, - 23.6, - 24.1, - 24.4, - 24.6, - 24.8, - 25, - 25.3, - 25.5, - 25.8, - 26, - 18.7, - 17.9, - 17.2, - 16.7, - 16.5, - 17, - 18.1, - 19.2, - 19.4, - 19.5, - 19.7, - 19.8, - 19.8, - 19.9, - 19.9, - 20, - 20, - 20.1, - 20.2, - 20.3, - 20.3, - 20.4, - 20.5, - 20.5, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.2, - 21.2, - 21.3, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 21.8, - 21.9, - 21.9, - 22, - 22.2, - 22.4, - 22.7, - 23, - 23.2, - 23.3, - 23.4, - 23.4, - 23.5, - 23.5, - 23.6, - 23.8, - 24, - 24.3, - 24.6, - 25.2, - 25.8, - 26.4, - 27, - 27.6, - 28.2, - 28.4, - 28.3, - 28, - 27.5, - 27, - 27.2, - 27.1, - 25.8, - 25.9, - 27.7, - 28.8, - 29.3, - 29.7, - 30.5, - 31.3, - 31.9, - 32.5, - 32.9, - 33.1, - 33, - 32.7, - 32, - 31.3, - 31.9, - 32, - 32.4, - 32.8, - 33.3, - 33.9, - 34.1, - 33.7, - 34.5, - 35.8, - 37.5, - 41.7, - 43.1, - 43.3, - 43.9, - 44.5, - 45, - 45.5, - 46.1, - 46.9, - 47.8, - 48.3, - 48.6, - 48.9, - 49.6, - 50.5, - 51.4, - 52.2, - 53.1, - 53.8, - 54.6, - 55.5, - 56.4, - 57.2, - 57.9, - 59.1, - 63.5, - 70.8, - 74, - 75.8, - 77, - 77.9, - 79.1, - 81.9, - 85.6, - 89.3, - 90.6, - 90.8, - 91.7, - 92.4, - 92.4, - 91.8, - 86.8, - 84.8, - 82.4, - 80.8, - 80, - 78.8, - 77.8, - 76.9, - 76.1, - 74.7, - 72.1, - 69.7, - 69, - 67.6, - 65.9, - 64.8, - 62.9, - 61, - 59.4, - 57.1, - 56, - 54.8, - 53.5, - 52.4, - 51.7, - 51, - 50.7, - 50.2, - 49.3, - 48.6, - 48.1, - 47.7, - 47.2, - 47.1, - 46.6, - 46.1, - 46.1, - 45.9, - 45.6, - 45.1, - 44.4, - 43.1, - 40.4, - 39.1, - 37.8, - 37, - 35.9, - 34.6, - 34, - 33.7, - 33.2, - 32.1, - 31.4, - 30.9, - 29.8, - 29, - 28.1, - 27.5, - 27.3, - 27.2, - 27.1, - 27, - 26.8, - 26.5, - 26.2, - 26.3, - 26.2, - 25.9, - 25.7, - 25.4, - 25.1, - 24.8, - 24.7, - 24.7, - 24.6, - 24.6, - 24.5, - 24.3, - 24, - 23.5, - 23.4, - 23.4, - 23.5, - 23.7, - 23.8, - 24, - 24.2, - 24.3, - 24.3, - 24.2, - 24.1, - 24, - 23.9, - 23.9, - 23.8, - 23.7, - 23.6, - 23.5, - 23.4, - 23.3, - 23.2, - 23.1, - 23.1, - 23.1, - 23, - 23, - 23.1, - 23.4, - 23.7, - 24.3, - 24.5, - 24.6, - 24.8, - 25, - 25.2, - 25.4, - 25.6, - 25.8, - 17.5, - 16.9, - 16.4, - 16.3, - 16.6, - 16.9, - 17.7, - 18.8, - 19.2, - 19.4, - 19.5, - 19.6, - 19.7, - 19.7, - 19.8, - 19.9, - 20, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.1, - 21.1, - 21.2, - 21.3, - 21.4, - 21.5, - 21.6, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 22.2, - 22.4, - 22.6, - 23, - 23, - 23.1, - 23.2, - 23.3, - 23.3, - 23.4, - 23.6, - 23.8, - 24, - 24.3, - 24.7, - 25.3, - 25.9, - 26.5, - 27, - 27.4, - 27.7, - 27.9, - 27.8, - 27.5, - 26.9, - 26.2, - 26.5, - 26, - 25, - 25.9, - 27.2, - 28.1, - 28.6, - 29.2, - 30, - 30.7, - 31.7, - 32.4, - 32.9, - 33.1, - 32.9, - 32.4, - 31.4, - 30.9, - 31.3, - 31.6, - 32.2, - 32.3, - 32.4, - 33.1, - 33.1, - 33.5, - 34.7, - 35.8, - 36.2, - 39, - 42.3, - 43.4, - 44.3, - 44.9, - 45.5, - 46.2, - 46.8, - 47.3, - 47.8, - 47.8, - 48.1, - 48.6, - 49.4, - 50.2, - 51, - 51.3, - 51.9, - 52.5, - 53.6, - 54.6, - 55.4, - 56.1, - 56.5, - 57.7, - 59.9, - 68.3, - 73.8, - 75.6, - 76.7, - 77.6, - 78.6, - 79.6, - 81.2, - 85, - 86.7, - 87.4, - 88.5, - 90.5, - 91.7, - 90.9, - 86, - 84.1, - 81.5, - 80.1, - 79.2, - 78.3, - 77.6, - 76.7, - 75.5, - 72.9, - 70, - 68.8, - 67.9, - 66.5, - 65.3, - 64, - 62.1, - 60.3, - 58.2, - 56.6, - 55.3, - 54.1, - 53.1, - 51.9, - 51, - 50.4, - 50, - 49.5, - 48.9, - 48.3, - 47.8, - 47.5, - 46.9, - 46.4, - 45.8, - 45.4, - 45.4, - 45.3, - 45.1, - 44.8, - 44.3, - 43.1, - 40.3, - 38.8, - 37.7, - 36.5, - 35, - 34.2, - 32.9, - 32.3, - 32.3, - 31.3, - 30, - 29.3, - 28.8, - 28.2, - 27.6, - 27.4, - 27.3, - 27.2, - 27, - 26.7, - 26.4, - 25.9, - 25.2, - 25, - 25.3, - 25.3, - 25.2, - 24.9, - 24.7, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.4, - 24.2, - 23.9, - 23.4, - 23.3, - 23.3, - 23.4, - 23.5, - 23.7, - 23.8, - 23.9, - 23.9, - 23.8, - 23.8, - 23.8, - 23.7, - 23.7, - 23.7, - 23.6, - 23.5, - 23.4, - 23.3, - 23.2, - 23.1, - 23.1, - 23, - 23, - 22.9, - 23, - 23.1, - 23.3, - 23.7, - 24.3, - 24.5, - 24.6, - 24.8, - 25, - 25.1, - 25.3, - 25.5, - 25.6, - 16.6, - 16.1, - 16.2, - 16.6, - 17, - 17.6, - 18.5, - 19, - 19.2, - 19.3, - 19.4, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.4, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 22.1, - 22.2, - 22.4, - 22.6, - 22.7, - 22.8, - 22.9, - 23.1, - 23.2, - 23.2, - 23.4, - 23.5, - 23.7, - 24, - 24.4, - 24.8, - 25.3, - 26, - 26.5, - 26.8, - 27.1, - 27.4, - 27.4, - 27.3, - 27, - 26.4, - 25.3, - 25.6, - 25.2, - 24.9, - 25.7, - 26.2, - 27, - 27.9, - 29.1, - 29.9, - 30.7, - 31.7, - 32.3, - 32.7, - 32.8, - 32.4, - 31.8, - 30.8, - 30.7, - 31.1, - 31.6, - 31.7, - 31.5, - 31.6, - 32.1, - 32.6, - 33.7, - 34.8, - 35.4, - 35.8, - 37.7, - 42.4, - 43.7, - 44.4, - 45.1, - 45.8, - 46.5, - 47, - 47.3, - 47.3, - 47, - 47.8, - 48.3, - 49, - 49.7, - 50.1, - 50.1, - 51.1, - 52.1, - 53, - 53.6, - 54.3, - 54.8, - 55.6, - 56.8, - 58.8, - 64.5, - 71.3, - 74.6, - 76, - 76.9, - 78.1, - 79.3, - 80.5, - 82.2, - 84, - 85.2, - 85.3, - 86.6, - 88.5, - 88.6, - 85.3, - 83.2, - 80.7, - 79.4, - 78.4, - 77.7, - 77, - 76.4, - 75.2, - 71, - 69, - 67.8, - 66.9, - 65.9, - 65, - 63.6, - 61.7, - 59.7, - 57.8, - 56.7, - 55.2, - 53.9, - 52.6, - 51.6, - 51, - 50.4, - 49.8, - 49.3, - 48.8, - 48.4, - 47.8, - 47.5, - 47, - 46.3, - 45.6, - 45, - 44.6, - 44.5, - 44.6, - 44.4, - 44, - 43, - 40.3, - 38.8, - 37.7, - 36.8, - 35.4, - 34.1, - 32.3, - 31.1, - 31.2, - 30.6, - 29.5, - 28.8, - 28.3, - 27.6, - 27.5, - 27.4, - 27.3, - 27.3, - 26.8, - 26.5, - 25.6, - 24.9, - 24.6, - 24.3, - 24.5, - 24.6, - 24.8, - 24.6, - 24.4, - 24.4, - 24.4, - 24.5, - 24.6, - 24.6, - 24.6, - 24.6, - 24.4, - 24.2, - 23.7, - 23.3, - 23.2, - 23.2, - 23.3, - 23.5, - 23.6, - 23.6, - 23.6, - 23.6, - 23.6, - 23.6, - 23.5, - 23.5, - 23.6, - 23.5, - 23.4, - 23.3, - 23.2, - 23.2, - 23.1, - 23, - 23, - 22.9, - 22.9, - 23, - 23.1, - 23.2, - 23.6, - 24.2, - 24.5, - 24.6, - 24.7, - 24.9, - 25, - 25.2, - 25.3, - 25.5, - 15.9, - 15.9, - 16.4, - 17.2, - 17.9, - 18.9, - 19.4, - 19.5, - 19.5, - 19.6, - 19.6, - 19.8, - 19.8, - 20, - 20.1, - 20.1, - 20.1, - 20.1, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.5, - 20.5, - 20.7, - 20.8, - 21, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.2, - 22.3, - 22.4, - 22.6, - 22.7, - 22.8, - 22.9, - 23, - 23.1, - 23.1, - 23.3, - 23.4, - 23.7, - 24, - 24.4, - 24.8, - 25.3, - 25.9, - 26.2, - 26.5, - 26.8, - 26.9, - 26.8, - 26.5, - 26.2, - 25.5, - 24.6, - 25.1, - 24.8, - 24.6, - 25.2, - 25.7, - 26.6, - 28.2, - 29.3, - 30, - 30.6, - 31.1, - 31.5, - 31.9, - 32, - 31.4, - 30.7, - 30.4, - 30.7, - 31.2, - 31.5, - 31.1, - 30.8, - 30.9, - 31.5, - 32.6, - 33.5, - 34.4, - 35, - 36, - 38, - 42.8, - 43.7, - 44.3, - 45, - 45.7, - 46.3, - 46.7, - 46.8, - 46.4, - 46.7, - 47.5, - 48, - 48.5, - 49, - 49.2, - 49.6, - 50.7, - 51.7, - 52.3, - 52.6, - 53.1, - 53.8, - 55.1, - 56.4, - 57.7, - 61.1, - 67.9, - 73.1, - 75.3, - 76.5, - 77.6, - 78.8, - 80.2, - 81, - 81.6, - 82.8, - 83.3, - 84.3, - 84.5, - 85.7, - 84.9, - 81.9, - 80, - 78.7, - 77.7, - 76.8, - 76.1, - 75.2, - 73.8, - 69.6, - 68.6, - 67.1, - 66, - 65, - 64.5, - 63.5, - 62, - 59.9, - 58, - 56.8, - 55.5, - 54.1, - 53.3, - 52.5, - 51.7, - 51.1, - 50.5, - 50, - 49.5, - 48.9, - 48.4, - 48.1, - 47.7, - 47.3, - 46.1, - 45.3, - 44.5, - 44, - 43.8, - 43.9, - 43.6, - 42, - 39.7, - 38.4, - 37.6, - 36.8, - 35.6, - 34.1, - 32.2, - 30.7, - 30, - 29.5, - 28.9, - 28.4, - 27.7, - 27.5, - 27.4, - 27.3, - 27.3, - 27.1, - 26.7, - 26.3, - 25.5, - 24.7, - 24.3, - 24.1, - 24, - 24.1, - 24.3, - 24.3, - 24.2, - 24.2, - 24.4, - 24.5, - 24.6, - 24.7, - 24.7, - 24.6, - 24.6, - 24.4, - 24.2, - 23.8, - 23.2, - 23.1, - 23.1, - 23.3, - 23.4, - 23.4, - 23.4, - 23.4, - 23.4, - 23.4, - 23.3, - 23.3, - 23.4, - 23.3, - 23.3, - 23.2, - 23.2, - 23.1, - 23, - 22.9, - 22.9, - 22.9, - 22.9, - 22.9, - 23, - 23.1, - 23.4, - 24, - 24.4, - 24.5, - 24.7, - 24.8, - 24.9, - 25.1, - 25.2, - 25.3, - 15.8, - 16.2, - 16.8, - 17.6, - 18.6, - 19.3, - 19.5, - 19.6, - 19.7, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.2, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.5, - 20.5, - 20.6, - 20.7, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.3, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.8, - 21.8, - 22, - 22.1, - 22.2, - 22.3, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 22.9, - 23, - 23.1, - 23.2, - 23.4, - 23.7, - 24, - 24.3, - 24.7, - 25.2, - 25.5, - 25.7, - 25.9, - 26.2, - 26.1, - 25.7, - 25.1, - 25, - 24.7, - 24.3, - 24.8, - 24.4, - 24.6, - 25.7, - 26.5, - 27.3, - 28.7, - 29.4, - 29.8, - 30.1, - 30.2, - 30.3, - 30.6, - 30.6, - 30.1, - 30.1, - 30.6, - 31, - 31.2, - 31.3, - 30.8, - 30.5, - 30.6, - 31.6, - 32.4, - 33.1, - 33.9, - 34.8, - 36, - 37.8, - 41.5, - 43.2, - 44, - 44.7, - 45.3, - 45.8, - 45.9, - 45.7, - 45.8, - 46.6, - 47, - 47.4, - 47.9, - 48.2, - 48.6, - 49.4, - 50.2, - 50.9, - 51.3, - 51.6, - 52.3, - 53.2, - 54.7, - 55.6, - 56.5, - 58.4, - 64.8, - 72.6, - 75, - 76.4, - 77.3, - 78.1, - 79.2, - 79.7, - 80, - 80.3, - 81, - 81.3, - 81.6, - 83.9, - 83.1, - 80.3, - 79.3, - 78.1, - 77.1, - 76, - 74.1, - 72, - 70.1, - 68.2, - 67.6, - 66.5, - 65.4, - 64.3, - 63.3, - 62.9, - 61.6, - 60.3, - 58.6, - 57.2, - 56.1, - 55.1, - 54.3, - 53.6, - 52.7, - 51.8, - 51.1, - 50.5, - 49.9, - 49.3, - 48.8, - 48.6, - 48.3, - 47.7, - 46.5, - 45.5, - 44.6, - 43.8, - 43, - 43.2, - 42.7, - 40.3, - 38.9, - 37.7, - 36.9, - 36.4, - 35.3, - 34, - 32.4, - 30.8, - 29.4, - 29, - 28.5, - 27.9, - 27.5, - 27.4, - 27.4, - 27.3, - 27, - 26.7, - 26.5, - 26.3, - 25.6, - 24.9, - 24.5, - 24.2, - 23.9, - 23.9, - 23.9, - 24, - 24, - 24.2, - 24.3, - 24.5, - 24.6, - 24.7, - 24.7, - 24.7, - 24.6, - 24.5, - 24.3, - 23.8, - 23.3, - 23.1, - 22.9, - 23, - 23.2, - 23.2, - 23.1, - 23.2, - 23.2, - 23, - 22.9, - 23.1, - 23.2, - 23.2, - 23.2, - 23.1, - 23.1, - 23, - 22.9, - 22.9, - 22.8, - 22.8, - 22.8, - 22.8, - 22.9, - 23.1, - 23.2, - 23.7, - 24.3, - 24.5, - 24.6, - 24.7, - 24.8, - 24.9, - 25, - 25.1, - 16.1, - 16.6, - 17.3, - 18.3, - 19.1, - 19.4, - 19.6, - 19.7, - 19.8, - 19.8, - 19.9, - 20, - 20.1, - 20.1, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.4, - 20.5, - 20.6, - 20.6, - 20.7, - 20.8, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.4, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.5, - 22.6, - 22.6, - 22.8, - 22.8, - 22.9, - 23, - 23.2, - 23.4, - 23.7, - 23.9, - 24.2, - 24.6, - 24.9, - 25, - 25, - 25.3, - 25.4, - 25, - 24.3, - 23.8, - 23.9, - 23.9, - 24, - 24.4, - 24.1, - 24.8, - 25.9, - 26.8, - 27.8, - 28.8, - 29.2, - 29.3, - 29.3, - 29.2, - 29.2, - 29.4, - 29.3, - 28.9, - 29.9, - 30.9, - 31.3, - 31.2, - 30.9, - 30.3, - 30.3, - 30.6, - 31.1, - 32, - 33, - 33.9, - 34.9, - 36, - 37.3, - 39.1, - 42.9, - 43.8, - 44.3, - 44.8, - 45.1, - 44.9, - 44.8, - 45.4, - 45.9, - 45.9, - 46.5, - 47.2, - 47.6, - 48.3, - 49, - 49.6, - 50.1, - 50.5, - 50.8, - 51.4, - 52.5, - 53.7, - 54.7, - 55.7, - 57.2, - 63.3, - 72.8, - 74.8, - 76, - 76.7, - 77.4, - 78, - 78.4, - 78.8, - 79.2, - 79.8, - 80, - 80.3, - 80.8, - 80.2, - 79.1, - 78.2, - 77.6, - 76.8, - 75.7, - 72, - 69.1, - 67.9, - 67.1, - 66.5, - 65.8, - 64.8, - 63.5, - 62.2, - 61.3, - 60.5, - 60, - 58.4, - 57.2, - 56.4, - 55.5, - 54.5, - 53.6, - 52.7, - 51.9, - 51.2, - 50.6, - 50, - 49.4, - 49, - 48.9, - 48.4, - 47.7, - 46.8, - 45.6, - 44.5, - 43.4, - 42, - 41.4, - 40.5, - 38.7, - 38, - 36.9, - 36, - 35.3, - 34.5, - 33.4, - 32.2, - 30.9, - 29.7, - 28.8, - 28.1, - 27.6, - 27.5, - 27.4, - 27.3, - 27.1, - 26.6, - 26.4, - 26.3, - 25.9, - 25.3, - 24.7, - 24.5, - 24.3, - 24, - 23.8, - 23.7, - 23.7, - 23.9, - 24.2, - 24.4, - 24.5, - 24.7, - 24.7, - 24.7, - 24.7, - 24.6, - 24.5, - 24.2, - 23.7, - 23.4, - 23.2, - 22.9, - 22.8, - 22.9, - 22.9, - 22.8, - 22.8, - 22.8, - 22.7, - 22.9, - 23, - 23.1, - 23, - 23, - 23, - 23, - 22.9, - 22.9, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.9, - 23, - 23.1, - 23.6, - 24.3, - 24.4, - 24.5, - 24.6, - 24.7, - 24.8, - 24.9, - 24.9, - 16.4, - 17, - 17.9, - 18.9, - 19.4, - 19.5, - 19.7, - 19.8, - 19.9, - 19.9, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.5, - 20.6, - 20.6, - 20.7, - 20.8, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.4, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.4, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 23, - 23.2, - 23.4, - 23.6, - 23.8, - 24.1, - 24.3, - 24.4, - 24.4, - 24.2, - 24.4, - 24, - 23.5, - 22.9, - 22.9, - 23.1, - 23.3, - 23.6, - 23.9, - 24.5, - 25.5, - 26.2, - 27.1, - 28, - 28.6, - 28.7, - 28.6, - 28.3, - 28.2, - 28.2, - 28.4, - 28.1, - 28.2, - 29.5, - 30.6, - 31, - 30.9, - 30.5, - 29.9, - 30.3, - 30.7, - 31.3, - 32.2, - 33.1, - 33.9, - 34.7, - 35.7, - 36.7, - 38.3, - 42.3, - 43.4, - 43.9, - 44.2, - 44.4, - 44.2, - 44.1, - 44.5, - 44.7, - 45, - 45.5, - 46.4, - 47, - 47.8, - 48.3, - 48.9, - 49.3, - 49.8, - 50.3, - 51, - 52.1, - 52.8, - 53.9, - 55.1, - 56.1, - 62.3, - 71, - 74.1, - 74.9, - 75.9, - 76.4, - 77, - 77.4, - 77.4, - 78, - 78.5, - 78.8, - 79.2, - 79.4, - 79, - 78.2, - 77.5, - 76.8, - 76.1, - 74.9, - 71.4, - 68.3, - 67.2, - 66.3, - 65.4, - 64.7, - 63.7, - 62.7, - 61.4, - 60.4, - 59.3, - 58.7, - 57.5, - 56.9, - 56.2, - 55.1, - 54, - 53.2, - 52.5, - 51.7, - 50.9, - 50.2, - 49.6, - 49.1, - 48.7, - 48.5, - 48.1, - 47.5, - 46.9, - 45.8, - 44.8, - 43.6, - 42.2, - 39.7, - 38.1, - 37.4, - 37, - 35.9, - 34.9, - 34.1, - 33.3, - 32.3, - 31.5, - 30.6, - 29.6, - 28.7, - 27.8, - 27.5, - 27.4, - 27.3, - 27.3, - 26.8, - 26.5, - 26, - 25.3, - 25, - 24.8, - 24.5, - 24.4, - 24.3, - 24.1, - 23.7, - 23.6, - 23.6, - 23.9, - 24.2, - 24.4, - 24.6, - 24.7, - 24.8, - 24.8, - 24.8, - 24.6, - 24.5, - 24.2, - 23.8, - 23.4, - 23.2, - 23, - 22.8, - 22.7, - 22.8, - 22.7, - 22.6, - 22.6, - 22.6, - 22.8, - 22.9, - 22.9, - 22.9, - 22.9, - 22.9, - 22.9, - 22.9, - 22.8, - 22.7, - 22.7, - 22.7, - 22.7, - 22.8, - 22.9, - 22.9, - 23.1, - 23.5, - 24.1, - 24.4, - 24.4, - 24.4, - 24.4, - 24.4, - 24.5, - 24.5, - 16.7, - 17.4, - 18.4, - 19.2, - 19.5, - 19.6, - 19.8, - 19.8, - 19.9, - 20, - 20.1, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.5, - 20.5, - 20.6, - 20.7, - 20.8, - 20.8, - 20.9, - 20.9, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.4, - 21.4, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.4, - 22.3, - 22.4, - 22.6, - 22.5, - 22.6, - 22.8, - 23.1, - 23.2, - 23.4, - 23.5, - 23.7, - 23.8, - 23.9, - 23.9, - 23.7, - 23.5, - 23.4, - 22.7, - 22.5, - 22.7, - 23.2, - 23.4, - 23.6, - 24, - 24.8, - 25.5, - 26.2, - 26.7, - 27.3, - 27.9, - 28.2, - 28.3, - 28, - 27.6, - 27.5, - 27.6, - 27.7, - 27.8, - 28.3, - 29.2, - 30, - 30.4, - 30.2, - 29.9, - 29.8, - 30.4, - 31.1, - 32, - 32.6, - 33.3, - 33.8, - 34.4, - 35.1, - 35.9, - 36.9, - 40, - 42.8, - 43.3, - 43.5, - 43.7, - 43.2, - 43.4, - 43.9, - 44.4, - 44.8, - 45.2, - 45.9, - 46.4, - 47.1, - 47.7, - 48.1, - 48.5, - 49, - 49.7, - 50.5, - 51.3, - 52.2, - 53.3, - 54.3, - 55.1, - 58.6, - 65.4, - 69.9, - 72.2, - 73.9, - 75.3, - 76.1, - 76.5, - 76.5, - 77, - 77.3, - 77.6, - 78.2, - 78.4, - 78, - 77.5, - 77, - 76.2, - 74.9, - 72.1, - 69.7, - 67.7, - 66.5, - 65.3, - 64.3, - 63.3, - 62.5, - 61.6, - 60.7, - 59.7, - 58.3, - 57.2, - 56.7, - 56.4, - 55.5, - 54.4, - 53.5, - 52.9, - 52.3, - 51.4, - 50.3, - 49.5, - 49.1, - 48.6, - 48.1, - 47.8, - 47.5, - 47.1, - 46.7, - 46.2, - 45.4, - 43.9, - 42.6, - 39.8, - 37.8, - 36.8, - 36, - 35, - 33.8, - 32.9, - 31.7, - 30.8, - 30.3, - 29.8, - 29, - 28.2, - 27.6, - 27.4, - 27.3, - 27.3, - 27.1, - 26.6, - 26.2, - 25.4, - 24.5, - 24.3, - 24.3, - 24.2, - 24.3, - 24.1, - 23.8, - 23.5, - 23.4, - 23.6, - 24, - 24.3, - 24.5, - 24.6, - 24.7, - 24.8, - 24.8, - 24.7, - 24.6, - 24.4, - 24.2, - 23.8, - 23.4, - 23.3, - 23.2, - 23, - 22.7, - 22.7, - 22.7, - 22.6, - 22.5, - 22.6, - 22.7, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.7, - 22.7, - 22.7, - 22.7, - 22.7, - 22.7, - 22.8, - 22.9, - 23.1, - 23.4, - 23.9, - 24.1, - 23.9, - 23.9, - 23.8, - 23.8, - 23.9, - 23.9, - 16.9, - 17.8, - 18.8, - 19.4, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.5, - 20.5, - 20.6, - 20.6, - 20.7, - 20.8, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.4, - 21.4, - 21.5, - 21.6, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.4, - 22.4, - 22.5, - 22.6, - 22.9, - 23.1, - 23.1, - 23.2, - 23.3, - 23.3, - 23.4, - 23.4, - 23.3, - 23.2, - 23.1, - 22.6, - 22.2, - 22.6, - 23, - 23.6, - 24.3, - 24.4, - 25.1, - 25.9, - 26.3, - 26.6, - 27, - 27.4, - 27.7, - 27.8, - 27.8, - 27.3, - 27, - 27.1, - 27.3, - 27.7, - 28.4, - 28.8, - 29.3, - 29.7, - 29.8, - 29.2, - 29.2, - 29.9, - 30.6, - 31.6, - 32.6, - 33.1, - 33.4, - 33.8, - 34.2, - 34.7, - 35.3, - 36.1, - 37.3, - 39.6, - 40.9, - 42, - 42.4, - 42.5, - 43.4, - 44.1, - 44.5, - 44.7, - 45.1, - 45.4, - 45.7, - 46.4, - 46.8, - 47.3, - 48, - 48.8, - 49.5, - 50, - 50.5, - 51.7, - 52.5, - 53.3, - 54.3, - 55.5, - 62.4, - 65.8, - 67.9, - 69.7, - 72.9, - 74.4, - 75.3, - 75.7, - 76, - 76.4, - 76.9, - 77.3, - 77.4, - 77.3, - 77, - 76.6, - 75.3, - 72.8, - 68.9, - 68, - 67, - 65.8, - 64.7, - 63.5, - 62.2, - 61, - 60.2, - 59.9, - 59, - 57.6, - 56.7, - 55.8, - 55.1, - 53.8, - 53.4, - 52.7, - 52.2, - 52.3, - 51.3, - 50.2, - 49.1, - 48.4, - 48, - 47.5, - 47, - 46.9, - 46.7, - 46.5, - 46.3, - 45.7, - 44.3, - 42.9, - 40.9, - 38.6, - 37.4, - 36.3, - 35.1, - 33.3, - 31.5, - 30.5, - 29.8, - 29, - 28.6, - 28.2, - 27.8, - 27.5, - 27.3, - 27.3, - 27.1, - 26.7, - 26.4, - 25.7, - 24.8, - 24.2, - 24, - 24, - 24, - 24, - 23.8, - 23.5, - 23.4, - 23.3, - 23.6, - 24, - 24.3, - 24.5, - 24.6, - 24.7, - 24.8, - 24.8, - 24.7, - 24.6, - 24.4, - 24, - 23.7, - 23.4, - 23.3, - 23.2, - 22.9, - 22.6, - 22.6, - 22.6, - 22.5, - 22.4, - 22.5, - 22.6, - 22.7, - 22.7, - 22.7, - 22.7, - 22.7, - 22.7, - 22.7, - 22.6, - 22.6, - 22.6, - 22.6, - 22.6, - 22.7, - 22.8, - 22.9, - 23, - 23.2, - 23.5, - 23.5, - 23.4, - 23.4, - 23.4, - 23.5, - 23.5, - 23.6, - 17.1, - 18.1, - 19.1, - 19.5, - 19.7, - 19.8, - 19.9, - 19.9, - 19.9, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.5, - 20.5, - 20.5, - 20.6, - 20.6, - 20.7, - 20.8, - 20.9, - 20.9, - 21, - 21.1, - 21.1, - 21.2, - 21.2, - 21.3, - 21.4, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 21.9, - 21.9, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.3, - 22.5, - 22.7, - 22.8, - 22.9, - 22.9, - 22.9, - 22.9, - 23, - 23, - 22.8, - 22.7, - 22.8, - 22.6, - 22.1, - 22.3, - 22.7, - 23.1, - 23.8, - 24.6, - 25.2, - 25.8, - 26.2, - 26.5, - 26.8, - 27, - 27.3, - 27.5, - 27.3, - 27.3, - 26.7, - 26.8, - 27.1, - 27.6, - 28.5, - 29.2, - 29.4, - 29.6, - 29.4, - 28.9, - 28.2, - 29.4, - 30.3, - 31.1, - 32, - 32.8, - 33.1, - 33.4, - 33.7, - 33.9, - 34.3, - 34.9, - 35.5, - 36, - 36.2, - 36.5, - 38, - 41.2, - 42.6, - 43.4, - 43.9, - 44.1, - 44.1, - 44.4, - 44.5, - 45, - 45.7, - 46.3, - 47, - 47.7, - 48.6, - 49.1, - 49.4, - 50.1, - 51, - 51.7, - 52.6, - 53.5, - 53.9, - 57.5, - 62.6, - 65.1, - 66.4, - 68, - 70, - 73.2, - 74.4, - 75, - 75.6, - 76.2, - 76.4, - 76.6, - 76.6, - 76.5, - 76.2, - 74.3, - 69.7, - 67.9, - 67.1, - 66.1, - 65, - 64, - 62.4, - 60.9, - 59.7, - 57.9, - 57.9, - 57.6, - 57, - 56.2, - 55, - 53.6, - 52.6, - 52.2, - 51.5, - 51.1, - 50.9, - 50.4, - 49.7, - 49, - 48.2, - 47.5, - 46.9, - 46.4, - 46.2, - 46.2, - 46, - 45.9, - 45.4, - 44.1, - 43, - 41.7, - 39.3, - 38.1, - 36.8, - 35.3, - 33.7, - 31.8, - 30.8, - 30, - 28.9, - 28.4, - 28.2, - 27.6, - 27.4, - 27.3, - 27.2, - 26.9, - 26.4, - 26.1, - 25.2, - 24.5, - 24.1, - 23.9, - 23.7, - 23.8, - 23.7, - 23.5, - 23.3, - 23.2, - 23.3, - 23.7, - 24.1, - 24.3, - 24.5, - 24.6, - 24.7, - 24.7, - 24.7, - 24.6, - 24.5, - 24.3, - 23.9, - 23.5, - 23.3, - 23.2, - 23, - 22.6, - 22.5, - 22.5, - 22.6, - 22.5, - 22.4, - 22.4, - 22.5, - 22.6, - 22.7, - 22.7, - 22.7, - 22.6, - 22.7, - 22.6, - 22.6, - 22.6, - 22.6, - 22.6, - 22.6, - 22.7, - 22.8, - 22.9, - 22.9, - 23.1, - 23.2, - 23.3, - 23.3, - 23.2, - 23.2, - 23.3, - 23.3, - 23.4, - 17.1, - 18.3, - 19.2, - 19.5, - 19.7, - 19.7, - 19.8, - 19.8, - 19.9, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.5, - 20.5, - 20.6, - 20.6, - 20.7, - 20.7, - 20.8, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.3, - 21.4, - 21.4, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 21.8, - 21.9, - 21.9, - 22, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.6, - 22.6, - 22.6, - 22.5, - 22.5, - 22.6, - 22.7, - 22.6, - 22.3, - 22, - 22, - 22, - 22, - 22.6, - 23, - 23.4, - 24.1, - 25, - 25.6, - 26.1, - 26.4, - 26.6, - 26.7, - 26.9, - 27, - 27.2, - 27, - 26.8, - 26.4, - 26.7, - 27.3, - 28.3, - 29.1, - 29.5, - 29.5, - 29.1, - 28.3, - 27.6, - 27.8, - 29.7, - 30.7, - 31.6, - 32.3, - 32.6, - 32.7, - 32.9, - 33.3, - 33.7, - 33.9, - 34.3, - 34.8, - 35, - 35, - 35, - 35.8, - 39.8, - 42, - 42.6, - 43.1, - 43.1, - 43, - 43.4, - 44.1, - 45, - 45.8, - 46.6, - 47.3, - 47.7, - 48.3, - 48.7, - 48.9, - 49.6, - 50.2, - 51.1, - 51.8, - 52.5, - 52.8, - 53.5, - 56.6, - 59.6, - 61.6, - 64, - 66.3, - 68.4, - 70.2, - 73, - 74.6, - 75.1, - 75.5, - 75.9, - 76.1, - 76, - 75.6, - 73.5, - 67.6, - 67, - 66, - 64.7, - 63.8, - 62.6, - 60.7, - 59, - 57.9, - 56.3, - 56.2, - 56.2, - 56.1, - 55.9, - 54.7, - 53.6, - 52.5, - 51.6, - 51, - 50.6, - 50.1, - 49.5, - 49.1, - 48.8, - 48.3, - 47.6, - 46.7, - 46, - 45.5, - 45.4, - 45.3, - 45.2, - 44.3, - 43.4, - 42.7, - 41.6, - 40, - 38.6, - 37.1, - 35.7, - 34.3, - 33, - 31.8, - 30.6, - 29.5, - 28.7, - 28.2, - 27.5, - 27.3, - 27.2, - 27.2, - 26.6, - 25.7, - 25.2, - 24.5, - 24.2, - 23.9, - 23.7, - 23.6, - 23.6, - 23.7, - 23.4, - 23.1, - 23.1, - 23.4, - 23.9, - 24.1, - 24.3, - 24.5, - 24.6, - 24.7, - 24.7, - 24.7, - 24.6, - 24.4, - 24.1, - 23.7, - 23.4, - 23.2, - 23, - 22.6, - 22.4, - 22.4, - 22.5, - 22.5, - 22.5, - 22.4, - 22.3, - 22.4, - 22.5, - 22.6, - 22.6, - 22.6, - 22.6, - 22.6, - 22.6, - 22.5, - 22.5, - 22.5, - 22.5, - 22.6, - 22.7, - 22.8, - 22.8, - 22.9, - 23, - 23.1, - 23.2, - 23.1, - 23.1, - 23.1, - 23.1, - 23.2, - 23.2, - 17.2, - 18.4, - 19.2, - 19.5, - 19.6, - 19.7, - 19.7, - 19.7, - 19.8, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.3, - 20.4, - 20.5, - 20.5, - 20.6, - 20.6, - 20.6, - 20.7, - 20.7, - 20.8, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.1, - 21.2, - 21.3, - 21.3, - 21.4, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 21.8, - 21.8, - 21.9, - 21.9, - 22, - 22.1, - 22.3, - 22.4, - 22.4, - 22.4, - 22.1, - 21.8, - 21.8, - 21.8, - 22, - 21.8, - 21.6, - 21.6, - 21.6, - 21.7, - 22.2, - 22.9, - 23.4, - 23.8, - 24.4, - 25.1, - 25.7, - 26.2, - 26.3, - 26.3, - 26.4, - 26.5, - 26.8, - 27.2, - 27.1, - 26.4, - 26.2, - 26.8, - 27.4, - 28.4, - 29.1, - 29.4, - 29.1, - 27.8, - 27.1, - 27.4, - 28.8, - 30.1, - 30.9, - 31.5, - 31.9, - 31.8, - 31.8, - 32.1, - 33, - 33.4, - 33.5, - 33.8, - 34, - 34.2, - 34.2, - 34.6, - 35.8, - 37.3, - 39.3, - 39.5, - 41.1, - 41.5, - 42, - 43, - 44.1, - 44.9, - 45.9, - 46.6, - 47.1, - 47.6, - 48, - 48, - 48.3, - 48.8, - 49.5, - 50.3, - 50.7, - 51.4, - 51.9, - 52.7, - 53.7, - 55.1, - 56.5, - 58.1, - 61.3, - 62.8, - 65, - 68.1, - 71.6, - 71.1, - 72.2, - 74.1, - 75, - 75.3, - 73.4, - 71.4, - 65.9, - 65.1, - 64.3, - 63, - 61.2, - 60.3, - 58.6, - 57.2, - 56.3, - 55.1, - 54.7, - 54.8, - 54.9, - 54.8, - 54, - 53.4, - 52.7, - 52.1, - 51.5, - 50.9, - 50.1, - 49.4, - 48.8, - 48.1, - 47.7, - 47.1, - 46.5, - 45.9, - 45.2, - 44.5, - 44.4, - 44.2, - 43.4, - 42.3, - 41.3, - 40.9, - 39.9, - 38.7, - 37.4, - 36.3, - 35.2, - 33.9, - 32.6, - 31.1, - 30, - 29, - 28.1, - 27.4, - 27.2, - 27.1, - 27, - 26, - 25, - 24.6, - 24.1, - 24, - 23.8, - 23.6, - 23.5, - 23.4, - 23.6, - 23.4, - 23, - 23.1, - 23.6, - 24, - 24.2, - 24.4, - 24.5, - 24.5, - 24.6, - 24.6, - 24.6, - 24.5, - 24.3, - 23.9, - 23.5, - 23.2, - 23.1, - 22.6, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.4, - 22.3, - 22.2, - 22.3, - 22.5, - 22.5, - 22.5, - 22.5, - 22.5, - 22.5, - 22.5, - 22.5, - 22.5, - 22.4, - 22.5, - 22.6, - 22.7, - 22.8, - 22.8, - 22.9, - 22.9, - 23, - 23.1, - 23, - 22.9, - 22.9, - 23, - 23.1, - 23.2, - 17.6, - 18.4, - 19.1, - 19.3, - 19.4, - 19.5, - 19.6, - 19.7, - 19.8, - 20, - 20, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.5, - 20.5, - 20.6, - 20.6, - 20.7, - 20.7, - 20.8, - 20.8, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.1, - 21.2, - 21.3, - 21.3, - 21.3, - 21.4, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 21.7, - 21.8, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.3, - 22.3, - 21.9, - 21.4, - 21.2, - 21.1, - 21.2, - 21.3, - 21.3, - 21.4, - 21.5, - 21.8, - 22.2, - 22.8, - 23.2, - 23.6, - 24.2, - 24.8, - 25.3, - 25.8, - 26, - 26, - 26, - 25.9, - 26.1, - 26.6, - 27, - 27.2, - 26.5, - 26, - 26.6, - 27.4, - 28.4, - 28.9, - 29, - 28.5, - 26.8, - 26.8, - 27.8, - 28.9, - 30.2, - 30.7, - 30.8, - 30.8, - 30.8, - 31, - 31.4, - 32.4, - 32.8, - 33, - 33.3, - 33.6, - 33.8, - 34.1, - 34.7, - 35.5, - 36.1, - 36.8, - 36.8, - 37, - 39.3, - 41.9, - 43.1, - 44, - 44.9, - 45.8, - 46.4, - 46.9, - 47.2, - 47.2, - 47.1, - 47.6, - 48.2, - 48.8, - 49.2, - 49.8, - 50.5, - 51, - 52, - 53, - 54.2, - 55.3, - 56.1, - 57, - 58.1, - 60.4, - 62.2, - 65.4, - 66, - 66.7, - 68.7, - 70.7, - 71.1, - 67.8, - 65.8, - 62.9, - 62.3, - 61.7, - 60.6, - 59.6, - 58.5, - 57, - 56.1, - 55.1, - 53.9, - 53.3, - 53.5, - 53.6, - 53.5, - 53.2, - 53, - 52.7, - 52.5, - 51.9, - 51, - 50, - 49.5, - 49.1, - 48.3, - 47.3, - 46.6, - 46.2, - 45.5, - 44.6, - 43.7, - 43.3, - 43, - 41.9, - 40.2, - 39.5, - 39.2, - 38.7, - 38.2, - 37.4, - 36.7, - 35.7, - 34.3, - 33, - 31.1, - 29.9, - 28.8, - 28, - 27.2, - 27.1, - 27, - 26.9, - 25.5, - 24.4, - 24.4, - 24.1, - 23.8, - 23.6, - 23.5, - 23.3, - 23.2, - 23.5, - 23.3, - 22.9, - 23.2, - 23.7, - 24, - 24.3, - 24.3, - 24.4, - 24.4, - 24.5, - 24.4, - 24.3, - 24.2, - 23.9, - 23.5, - 23.2, - 23.1, - 22.7, - 22.4, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.4, - 22.3, - 22.2, - 22.3, - 22.4, - 22.4, - 22.4, - 22.4, - 22.4, - 22.5, - 22.4, - 22.4, - 22.4, - 22.4, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 22.9, - 22.9, - 22.9, - 22.8, - 22.8, - 22.9, - 23, - 23, - 23.1, - 18.7, - 18.9, - 19, - 19.1, - 19.3, - 19.5, - 19.6, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.4, - 20.4, - 20.5, - 20.5, - 20.6, - 20.6, - 20.7, - 20.7, - 20.8, - 20.8, - 20.8, - 20.9, - 21, - 21, - 21.1, - 21.1, - 21.2, - 21.2, - 21.3, - 21.3, - 21.4, - 21.4, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.6, - 21.7, - 21.7, - 21.9, - 22, - 22.2, - 22.2, - 22.1, - 21.7, - 21.3, - 21, - 20.9, - 20.9, - 21, - 21.1, - 21.3, - 21.7, - 22.1, - 22.6, - 23.2, - 23.5, - 23.7, - 24, - 24.5, - 25.1, - 25.4, - 25.7, - 25.8, - 25.6, - 25.5, - 25.5, - 26, - 26.5, - 26.6, - 26.7, - 26.3, - 25.8, - 26.6, - 27.4, - 28.1, - 28.4, - 28.4, - 27.7, - 26.3, - 26.6, - 27.5, - 28.5, - 29.8, - 30, - 30, - 30.4, - 30.7, - 30.9, - 31.2, - 32.1, - 32.6, - 33.1, - 33.5, - 33.8, - 33.9, - 34.2, - 34.6, - 35, - 35.3, - 35.7, - 35.7, - 36.1, - 38.3, - 42.3, - 43.5, - 44.3, - 45.1, - 45.7, - 46.1, - 46.3, - 46.5, - 46.4, - 46.2, - 47, - 47.6, - 48, - 48.5, - 49.2, - 49.7, - 50.2, - 51.2, - 52.3, - 53.5, - 54.5, - 54.9, - 55.6, - 56.1, - 56.4, - 57.2, - 59.2, - 60.4, - 60.8, - 62, - 63.9, - 63.6, - 62.5, - 61.6, - 60.1, - 60.1, - 59.4, - 58.5, - 57.8, - 56.8, - 56, - 55, - 53.9, - 53.1, - 52.3, - 52.3, - 52.4, - 52.6, - 52.4, - 52.3, - 52.2, - 52.1, - 51.5, - 50.6, - 49.8, - 49.3, - 49.2, - 48.7, - 48, - 47.1, - 46.4, - 45.5, - 44.3, - 43.4, - 42.6, - 40.9, - 39.6, - 38.8, - 38.1, - 37.7, - 37.5, - 37.3, - 37.1, - 36.7, - 35.9, - 34.5, - 33.3, - 31.6, - 29.9, - 28.6, - 27.9, - 27.2, - 27, - 26.9, - 26.8, - 25.3, - 24, - 24.1, - 24, - 23.8, - 23.5, - 23.4, - 23.2, - 23, - 23.3, - 23, - 22.8, - 23.1, - 23.6, - 24, - 24.2, - 24.3, - 24.3, - 24.3, - 24.2, - 24, - 23.9, - 23.6, - 23.4, - 23.2, - 23, - 22.8, - 22.3, - 22.1, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.2, - 22.1, - 22.2, - 22.3, - 22.3, - 22.4, - 22.4, - 22.4, - 22.4, - 22.4, - 22.4, - 22.4, - 22.4, - 22.4, - 22.5, - 22.6, - 22.7, - 22.7, - 22.7, - 22.7, - 22.7, - 22.7, - 22.7, - 22.8, - 22.9, - 22.9, - 23, - 23, - 19.3, - 19.3, - 19.3, - 19.3, - 19.4, - 19.5, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.4, - 20.5, - 20.5, - 20.5, - 20.6, - 20.6, - 20.7, - 20.7, - 20.8, - 20.8, - 20.8, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.3, - 21.3, - 21.3, - 21.4, - 21.4, - 21.5, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 22, - 22.1, - 22.2, - 22.2, - 21.8, - 21.2, - 20.9, - 20.8, - 20.7, - 20.8, - 21, - 21.3, - 21.7, - 22.3, - 22.8, - 23.2, - 23.6, - 23.8, - 24.2, - 24.7, - 25.2, - 25.4, - 25.4, - 25.5, - 25.4, - 25.4, - 25.2, - 25.3, - 26, - 26.4, - 26.1, - 25.6, - 25.8, - 26.2, - 26.8, - 27.4, - 27.8, - 28, - 27.8, - 27, - 25.9, - 26.6, - 27.5, - 28.1, - 28.9, - 29.2, - 30.1, - 31.1, - 31.5, - 31.8, - 32.2, - 32.7, - 33.1, - 33.6, - 33.9, - 33.9, - 34, - 34.1, - 34.2, - 34.4, - 34.7, - 34.9, - 35.2, - 36, - 40, - 43, - 43.8, - 44.4, - 44.9, - 45.3, - 45.4, - 45.4, - 45.5, - 45.5, - 45.5, - 46.3, - 46.8, - 47.6, - 48.2, - 48.7, - 49.1, - 49.7, - 50.8, - 51.6, - 52.6, - 53.3, - 53.7, - 54.4, - 54.8, - 55.1, - 55.5, - 55.9, - 56.6, - 57, - 57.3, - 58.6, - 58.7, - 58.3, - 58, - 57.7, - 57.5, - 57.2, - 57, - 56.5, - 55.8, - 54.8, - 53.7, - 52.9, - 52.2, - 51.5, - 51.4, - 51.4, - 51.5, - 51.5, - 51.4, - 51.4, - 51.5, - 50.9, - 50, - 49.3, - 48.9, - 48.6, - 48.4, - 47.9, - 47.3, - 46.6, - 45.7, - 44.7, - 43.9, - 42.7, - 39.6, - 38.2, - 37.7, - 37.1, - 36.6, - 36.5, - 36.6, - 36.5, - 36.3, - 35.8, - 34.8, - 33.8, - 32.8, - 30.6, - 28.6, - 27.8, - 27.1, - 27, - 26.9, - 26.7, - 25.3, - 24, - 23.8, - 23.7, - 23.7, - 23.5, - 23.3, - 23.1, - 22.9, - 22.9, - 22.7, - 22.7, - 22.9, - 23.4, - 23.9, - 24.1, - 24.1, - 24.1, - 24, - 23.8, - 23.6, - 23.3, - 23.2, - 23.1, - 23, - 22.8, - 22.4, - 22, - 22, - 22, - 22.1, - 22.2, - 22.2, - 22.2, - 22.2, - 22.1, - 22.1, - 22.1, - 22.3, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.4, - 22.5, - 22.6, - 22.6, - 22.6, - 22.6, - 22.6, - 22.6, - 22.7, - 22.8, - 22.8, - 22.8, - 22.9, - 23, - 19.5, - 19.5, - 19.5, - 19.4, - 19.5, - 19.6, - 19.8, - 19.9, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.3, - 20.4, - 20.5, - 20.5, - 20.6, - 20.6, - 20.6, - 20.7, - 20.7, - 20.7, - 20.8, - 20.8, - 20.9, - 20.9, - 20.9, - 21, - 21.1, - 21.1, - 21.2, - 21.2, - 21.2, - 21.3, - 21.3, - 21.3, - 21.4, - 21.4, - 21.4, - 21.5, - 21.5, - 21.6, - 21.8, - 22, - 22.1, - 22.2, - 22.1, - 21.4, - 20.8, - 20.7, - 20.6, - 20.7, - 21.1, - 21.5, - 22, - 22.5, - 22.8, - 23.1, - 23.3, - 23.6, - 23.8, - 24.3, - 25, - 25.4, - 25.5, - 25.2, - 25.3, - 25.1, - 24.9, - 24.9, - 25.3, - 25.8, - 26.2, - 25.7, - 25.3, - 26, - 26.5, - 26.7, - 27.2, - 27.7, - 27.8, - 27.3, - 26.3, - 25.8, - 26.5, - 27, - 27.6, - 28.6, - 29.5, - 30.7, - 32, - 32.2, - 32.5, - 32.9, - 33.1, - 33.4, - 33.7, - 33.8, - 33.9, - 33.9, - 33.9, - 33.9, - 34, - 34.1, - 34.4, - 35, - 36.2, - 39.5, - 42.8, - 43.6, - 44.1, - 44.4, - 44.7, - 44.6, - 44.6, - 44.6, - 44.8, - 45, - 45.6, - 46.6, - 47.5, - 47.9, - 48.2, - 48.6, - 49.4, - 50.1, - 50.8, - 51.5, - 52, - 52.6, - 53.2, - 53.4, - 53.8, - 54.4, - 54.9, - 55.6, - 55.9, - 55.9, - 56.1, - 56.2, - 56.3, - 56.2, - 56.1, - 56, - 55.7, - 55.7, - 55.3, - 54.4, - 53.7, - 52.8, - 52.1, - 51.4, - 50.9, - 50.7, - 50.7, - 50.5, - 50.5, - 50.6, - 50.7, - 50.8, - 50.4, - 49.6, - 48.8, - 48.1, - 47.7, - 47.5, - 47.2, - 46.9, - 46.4, - 45.8, - 45.1, - 44.3, - 43.1, - 40, - 38.1, - 37.3, - 36.6, - 35.9, - 35.5, - 35.6, - 35.6, - 35.5, - 35.2, - 34.6, - 33.9, - 33.1, - 30.7, - 28.7, - 27.8, - 27, - 26.9, - 26.9, - 26.5, - 25.2, - 24.3, - 23.9, - 23.6, - 23.5, - 23.3, - 23.3, - 23.1, - 22.9, - 22.7, - 22.6, - 22.6, - 22.8, - 23, - 23.5, - 23.8, - 23.9, - 23.7, - 23.5, - 23.3, - 23.1, - 22.9, - 22.8, - 22.7, - 22.6, - 22.3, - 22, - 21.9, - 22, - 22, - 22.1, - 22.1, - 22.1, - 22.1, - 22.1, - 22, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.2, - 22.3, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 22.9, - 22.9, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20, - 20.1, - 20.2, - 20.3, - 20.4, - 20.4, - 20.5, - 20.5, - 20.6, - 20.6, - 20.6, - 20.7, - 20.7, - 20.7, - 20.7, - 20.8, - 20.8, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.1, - 21.2, - 21.2, - 21.2, - 21.2, - 21.3, - 21.3, - 21.3, - 21.4, - 21.4, - 21.5, - 21.6, - 21.9, - 21.9, - 22, - 22.1, - 21.8, - 21, - 20.6, - 20.4, - 20.5, - 21, - 21.7, - 22.2, - 22.5, - 22.7, - 22.9, - 23.1, - 23.3, - 23.6, - 23.8, - 24.3, - 24.9, - 25.3, - 25.3, - 25, - 24.9, - 24.9, - 24.6, - 24.9, - 25.3, - 25.7, - 26.1, - 25.6, - 25.2, - 26.1, - 26.3, - 26.5, - 27.2, - 27.8, - 27.6, - 26.8, - 25.8, - 26, - 26.8, - 27.5, - 28.1, - 29.1, - 30.2, - 31.1, - 32.1, - 32.4, - 32.7, - 32.9, - 33, - 33.3, - 33.5, - 33.7, - 33.8, - 33.9, - 33.8, - 33.6, - 33.6, - 33.9, - 34.2, - 34.9, - 36.2, - 38.1, - 41, - 43, - 43.7, - 44, - 44, - 43.9, - 44, - 44.2, - 44.5, - 44.6, - 45.3, - 46.3, - 46.8, - 47.1, - 47.8, - 48.4, - 48.9, - 49.4, - 50, - 50.5, - 50.9, - 51.6, - 52, - 52.4, - 52.7, - 53.2, - 53.8, - 54.5, - 54.6, - 54.6, - 54.9, - 55.1, - 55.3, - 55.2, - 55, - 54.6, - 54.3, - 54.4, - 54, - 53.3, - 52.8, - 52, - 51.3, - 50.7, - 50.3, - 50.1, - 50, - 49.8, - 49.8, - 50, - 50.2, - 50.2, - 49.8, - 49.3, - 48.6, - 47.9, - 47.2, - 46.8, - 46.6, - 46.3, - 45.9, - 45.5, - 45, - 44.2, - 43, - 40.1, - 38.6, - 37.7, - 36.8, - 35.9, - 34.9, - 34.2, - 34.4, - 34.3, - 34.2, - 33.9, - 33.4, - 32.7, - 30.5, - 29.1, - 28.1, - 27.1, - 26.9, - 26.9, - 26.7, - 25.6, - 24.6, - 24.1, - 23.8, - 23.6, - 23.3, - 23.1, - 23, - 22.8, - 22.6, - 22.3, - 22.3, - 22.5, - 22.8, - 23, - 23.3, - 23.3, - 23.2, - 23, - 22.8, - 22.7, - 22.5, - 22.3, - 22.2, - 22.1, - 21.9, - 21.8, - 21.8, - 21.9, - 21.9, - 22, - 22, - 22, - 22, - 22, - 22, - 21.9, - 22.1, - 22.2, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.2, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 22.9, - 19.7, - 19.7, - 19.7, - 19.7, - 19.7, - 19.8, - 19.9, - 19.9, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.3, - 20.5, - 20.5, - 20.5, - 20.6, - 20.6, - 20.6, - 20.6, - 20.7, - 20.7, - 20.7, - 20.7, - 20.8, - 20.8, - 20.9, - 20.9, - 21, - 21.1, - 21.1, - 21.1, - 21.1, - 21.1, - 21.2, - 21.2, - 21.2, - 21.3, - 21.3, - 21.4, - 21.5, - 21.7, - 22, - 22, - 22, - 21.7, - 21.2, - 20.7, - 20.3, - 20.3, - 20.6, - 21.2, - 22.2, - 22.5, - 22.6, - 22.7, - 22.8, - 23, - 23.3, - 23.6, - 23.8, - 24.2, - 24.7, - 25.1, - 25.1, - 24.9, - 24.6, - 24.5, - 24.6, - 24.9, - 25.4, - 25.6, - 25.9, - 25.5, - 25, - 25.8, - 26.1, - 26.6, - 27.2, - 27.7, - 27.4, - 26.2, - 25.5, - 26.5, - 27.5, - 28.3, - 29.1, - 29.8, - 30.5, - 31, - 31.6, - 32, - 32.5, - 32.6, - 32.5, - 32.9, - 33.3, - 33.5, - 33.7, - 33.7, - 33.6, - 33.3, - 33.1, - 33.5, - 34, - 34.8, - 36.1, - 37.7, - 39.6, - 42.5, - 43.5, - 43.7, - 43.6, - 43.3, - 43.4, - 44.1, - 44.2, - 44.3, - 44.9, - 45.5, - 45.9, - 46.7, - 47.7, - 48.1, - 48.4, - 48.8, - 49.2, - 49.5, - 50.1, - 50.6, - 51, - 51.4, - 51.9, - 52.2, - 52.7, - 53.3, - 53.3, - 53.1, - 53.4, - 53.7, - 53.9, - 53.8, - 53.7, - 53.2, - 53, - 53.1, - 52.8, - 52.4, - 51.7, - 51, - 50.4, - 50, - 49.8, - 49.5, - 49.4, - 49.4, - 49.4, - 49.7, - 49.8, - 49.7, - 49.3, - 48.8, - 48.3, - 47.7, - 47.2, - 46.6, - 46.1, - 45.6, - 45.4, - 45.2, - 44.8, - 44.1, - 42.5, - 39.9, - 38.7, - 37.5, - 36.7, - 36, - 34.6, - 33, - 32.8, - 32.9, - 33.1, - 33.1, - 32.6, - 31.8, - 30.6, - 29.6, - 28.6, - 27.1, - 26.9, - 26.9, - 26.9, - 26.1, - 24.8, - 24.2, - 23.9, - 23.7, - 23.5, - 23.2, - 22.9, - 22.7, - 22.6, - 22.2, - 21.9, - 22.1, - 22.5, - 22.8, - 22.9, - 22.9, - 22.8, - 22.6, - 22.3, - 22.2, - 22, - 21.8, - 21.9, - 21.7, - 21.6, - 21.7, - 21.8, - 21.8, - 21.8, - 21.9, - 21.9, - 21.9, - 21.9, - 21.9, - 21.9, - 21.9, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.2, - 22.2, - 22.2, - 22.2, - 22.2, - 22.2, - 22.2, - 22.2, - 22.2, - 22.3, - 22.4, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.6, - 22.7, - 22.8, - 22.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.9, - 19.9, - 20, - 20.1, - 20.1, - 20.1, - 20.2, - 20.3, - 20.3, - 20.5, - 20.5, - 20.5, - 20.5, - 20.6, - 20.6, - 20.6, - 20.6, - 20.6, - 20.6, - 20.7, - 20.7, - 20.8, - 20.8, - 20.9, - 21, - 21, - 21, - 21, - 21, - 21.1, - 21.1, - 21.1, - 21.2, - 21.3, - 21.3, - 21.4, - 21.6, - 22, - 22.3, - 22.1, - 21.8, - 21.2, - 20.7, - 20.3, - 20.2, - 20.3, - 20.7, - 21.3, - 22.2, - 22.5, - 22.5, - 22.6, - 22.7, - 22.9, - 23.1, - 23.3, - 23.6, - 23.9, - 24.4, - 24.8, - 24.9, - 24.7, - 24.4, - 24.3, - 24.7, - 25, - 25.4, - 25.5, - 25.7, - 25.4, - 24.8, - 25.5, - 26.2, - 26.7, - 27.2, - 27.6, - 27, - 25.6, - 25.7, - 26.8, - 27.4, - 28, - 28.7, - 29.6, - 30.2, - 30.8, - 31.2, - 31.4, - 31.7, - 31.8, - 31.7, - 32.1, - 32.5, - 33, - 33.4, - 33.3, - 33.2, - 32.9, - 32.8, - 33.3, - 33.9, - 34.7, - 35.9, - 37.4, - 39.5, - 42.5, - 43.3, - 43.4, - 42.9, - 42.5, - 43.1, - 43.6, - 43.7, - 44.1, - 44.5, - 44.8, - 45.6, - 46.5, - 47, - 47.4, - 47.8, - 48.1, - 48.5, - 48.9, - 49.4, - 49.8, - 50.2, - 50.9, - 51.2, - 51.2, - 51.7, - 52.2, - 52.2, - 52, - 52.2, - 52.4, - 52.7, - 52.6, - 52.4, - 52.1, - 51.8, - 51.8, - 51.7, - 51.5, - 51, - 50.1, - 49.6, - 49.5, - 49.3, - 49, - 48.9, - 49, - 49.3, - 49.4, - 49.4, - 49.3, - 49, - 48.4, - 47.7, - 47.2, - 46.8, - 46.4, - 45.9, - 45.1, - 44.7, - 44.6, - 44.5, - 43.9, - 42.6, - 39.9, - 38.6, - 37.4, - 36.4, - 35.4, - 34.1, - 32.6, - 31.4, - 31.4, - 31.7, - 32, - 31.7, - 31.5, - 30.8, - 29.9, - 28.9, - 27.1, - 26.9, - 26.9, - 26.9, - 26, - 24.8, - 24.2, - 23.9, - 23.7, - 23.6, - 23.2, - 22.8, - 22.8, - 22.6, - 22.3, - 21.9, - 21.9, - 22.2, - 22.5, - 22.6, - 22.6, - 22.5, - 22.2, - 21.9, - 21.9, - 21.8, - 21.6, - 21.5, - 21.5, - 21.6, - 21.6, - 21.7, - 21.8, - 21.8, - 21.8, - 21.8, - 21.8, - 21.8, - 21.8, - 21.9, - 21.9, - 22, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.2, - 22.1, - 22.2, - 22.2, - 22.2, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.6, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.9, - 19.9, - 20, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.4, - 20.4, - 20.5, - 20.5, - 20.5, - 20.5, - 20.5, - 20.5, - 20.5, - 20.6, - 20.6, - 20.6, - 20.7, - 20.7, - 20.8, - 20.8, - 20.9, - 20.9, - 21, - 21, - 21, - 21, - 21, - 21.1, - 21.1, - 21.2, - 21.2, - 21.5, - 21.8, - 22.4, - 22.7, - 22.1, - 21.3, - 20.8, - 20.4, - 20.1, - 20.1, - 20.2, - 20.6, - 21.3, - 22.1, - 22.1, - 22, - 22.4, - 22.6, - 22.7, - 22.9, - 23.2, - 23.5, - 23.9, - 24.3, - 24.6, - 24.6, - 24.5, - 24.3, - 24.3, - 24.7, - 24.9, - 25.1, - 25.5, - 25.7, - 25.3, - 24.7, - 25.7, - 26.5, - 26.9, - 27.2, - 27.4, - 26.7, - 25.2, - 25.8, - 26.6, - 27, - 27.4, - 28.1, - 29, - 29.8, - 30.4, - 30.8, - 30.9, - 30.9, - 30.8, - 30.8, - 31.2, - 31.6, - 32.3, - 32.8, - 32.8, - 32.7, - 32.5, - 32.6, - 33.3, - 34, - 35, - 36, - 37.2, - 38.9, - 42, - 43, - 42.4, - 41.4, - 41.6, - 42.7, - 43.2, - 43.5, - 43.9, - 44, - 44.4, - 45.1, - 45.5, - 45.8, - 46.5, - 46.9, - 47.3, - 47.9, - 48.4, - 48.7, - 49.3, - 49.8, - 50.4, - 50.5, - 50.3, - 50.7, - 51.2, - 51.1, - 51.1, - 51.2, - 51.5, - 51.7, - 51.6, - 51.4, - 51.1, - 50.7, - 50.7, - 50.7, - 50.6, - 50.3, - 49.7, - 49.1, - 48.8, - 48.6, - 48.4, - 48.4, - 48.4, - 49, - 49, - 48.9, - 48.7, - 48.6, - 48.3, - 47.5, - 46.9, - 46.4, - 45.9, - 45.4, - 44.7, - 44.1, - 43.9, - 43.9, - 43.6, - 43, - 39.7, - 38.2, - 37.2, - 36.3, - 35.2, - 33.7, - 32.4, - 31.2, - 30.6, - 30.6, - 30.8, - 30.9, - 31, - 30.5, - 29.7, - 28.8, - 27.1, - 26.9, - 26.9, - 27, - 25.8, - 24.7, - 24.1, - 23.8, - 23.6, - 23.3, - 23, - 22.7, - 22.6, - 22.5, - 22.3, - 22, - 21.7, - 21.8, - 22, - 22.1, - 22, - 22, - 21.8, - 21.7, - 21.6, - 21.6, - 21.5, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.6, - 21.7, - 21.7, - 21.7, - 21.7, - 21.7, - 21.8, - 21.9, - 21.9, - 22, - 22, - 22, - 22.1, - 22.1, - 22.1, - 22.1, - 22.1, - 22.1, - 22.2, - 22.1, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.3, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 19.9, - 19.8, - 19.8, - 19.9, - 19.9, - 19.9, - 20, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.4, - 20.4, - 20.5, - 20.5, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.5, - 20.5, - 20.5, - 20.5, - 20.6, - 20.7, - 20.8, - 20.8, - 20.9, - 20.9, - 20.9, - 20.9, - 21, - 21, - 21, - 21, - 21.1, - 21.2, - 21.6, - 22, - 22.6, - 22.6, - 21.6, - 20.8, - 20.5, - 20.1, - 20, - 20.1, - 20.1, - 20.4, - 20.9, - 21.3, - 21.2, - 21.4, - 22.3, - 22.8, - 22.8, - 23, - 23.6, - 24, - 24.2, - 24.6, - 24.6, - 24.5, - 24.3, - 24.1, - 24.2, - 24.5, - 24.8, - 25.1, - 25.4, - 25.6, - 25.3, - 24.5, - 25.5, - 26.5, - 26.8, - 27.1, - 27.2, - 26.4, - 25.1, - 25.6, - 26.2, - 26.5, - 27, - 27.7, - 28.3, - 29.3, - 30, - 30.5, - 30.5, - 30.3, - 30, - 30, - 30.4, - 30.9, - 31.5, - 32.1, - 32.4, - 32.3, - 32.2, - 32.5, - 33.3, - 34.1, - 34.9, - 35.9, - 36.9, - 38.2, - 40.6, - 41.6, - 40.7, - 39.7, - 40.1, - 41.3, - 42.6, - 43.3, - 43.5, - 43.2, - 43.9, - 44.3, - 44.6, - 45, - 45.7, - 46.2, - 46.8, - 47.4, - 47.9, - 48.4, - 49, - 49.5, - 49.8, - 49.7, - 49.5, - 49.8, - 50.2, - 50.1, - 50.2, - 50.3, - 50.5, - 50.8, - 50.8, - 50.4, - 50.2, - 49.9, - 49.9, - 50, - 49.9, - 49.8, - 49.5, - 49.1, - 48.5, - 47.9, - 47.8, - 47.9, - 47.8, - 48.3, - 48.5, - 48.3, - 48.2, - 48.3, - 47.9, - 47.3, - 46.8, - 45.9, - 45.1, - 44.7, - 44.4, - 43.7, - 43.4, - 43.3, - 43.1, - 42.6, - 39.6, - 38, - 37.1, - 36.3, - 35.3, - 33.9, - 32.6, - 31.5, - 30.7, - 30, - 30, - 30, - 30.2, - 29.9, - 29.3, - 28.6, - 27.1, - 27, - 27, - 27, - 25.9, - 25, - 24.3, - 23.9, - 23.6, - 23.3, - 23, - 22.7, - 22.5, - 22.3, - 22.1, - 21.9, - 21.7, - 21.6, - 21.7, - 21.6, - 21.5, - 21.6, - 21.5, - 21.5, - 21.4, - 21.3, - 21.3, - 21.3, - 21.4, - 21.5, - 21.5, - 21.6, - 21.5, - 21.6, - 21.6, - 21.7, - 21.7, - 21.7, - 21.8, - 21.8, - 21.9, - 21.9, - 22, - 22, - 22, - 22, - 22.1, - 22.1, - 22.1, - 22.1, - 22.1, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.7, - 22.8, - 22.8, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 20, - 19.9, - 20, - 20, - 20, - 20.1, - 20.2, - 20.3, - 20.4, - 20.4, - 20.4, - 20.4, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.8, - 20.8, - 20.9, - 20.9, - 20.9, - 20.9, - 21, - 21, - 21, - 21.3, - 21.6, - 22.1, - 22.5, - 22.1, - 21, - 20.5, - 20.3, - 19.9, - 19.8, - 19.9, - 20, - 20.2, - 20.5, - 20.7, - 21, - 22, - 23.1, - 23.5, - 23.5, - 23.5, - 24.1, - 24.6, - 24.9, - 24.9, - 24.8, - 24.6, - 24.1, - 24.1, - 24.4, - 24.5, - 24.7, - 25.1, - 25.4, - 25.3, - 25.1, - 24.3, - 25.2, - 26.2, - 26.5, - 26.6, - 26.8, - 26, - 24.9, - 25.3, - 25.8, - 26.7, - 27.4, - 27.5, - 27.6, - 28.9, - 29.8, - 30, - 29.8, - 29.5, - 28.9, - 29.2, - 29.9, - 30.2, - 30.8, - 31.5, - 31.8, - 31.8, - 31.9, - 32.5, - 33.3, - 34, - 34.7, - 35.6, - 36.6, - 37.4, - 38.8, - 39.8, - 39.7, - 38.9, - 39, - 39.1, - 40.9, - 42.4, - 42.2, - 42.1, - 43.1, - 43.9, - 44.6, - 45.2, - 45.7, - 46.5, - 47, - 47.6, - 48, - 48.4, - 48.7, - 49, - 49.3, - 49.2, - 49, - 49.2, - 49.3, - 49.4, - 49.5, - 49.6, - 49.6, - 49.9, - 50, - 49.7, - 49.4, - 49.3, - 49.4, - 49.5, - 49.4, - 49.4, - 49.3, - 49, - 48.4, - 47.6, - 47.2, - 47.2, - 47.1, - 47.5, - 47.9, - 47.8, - 47.6, - 47.7, - 47.3, - 46.9, - 46.4, - 45.7, - 44.9, - 44.4, - 44.2, - 43.6, - 43.3, - 43, - 42.8, - 41.4, - 39, - 37.8, - 37, - 36.3, - 35.5, - 34.2, - 32.9, - 31.9, - 31, - 30, - 29.6, - 29.4, - 29.4, - 29.2, - 29, - 28, - 27, - 27, - 27, - 27, - 26.2, - 25.5, - 24.8, - 24.3, - 23.9, - 23.6, - 23.3, - 22.9, - 22.8, - 22.4, - 22.1, - 21.8, - 21.7, - 21.5, - 21.5, - 21.5, - 21.4, - 21.3, - 21.3, - 21.3, - 21.2, - 21.2, - 21.2, - 21.3, - 21.4, - 21.4, - 21.5, - 21.5, - 21.5, - 21.6, - 21.6, - 21.6, - 21.7, - 21.7, - 21.8, - 21.8, - 21.9, - 21.9, - 21.9, - 22, - 22, - 22, - 22, - 22, - 22.1, - 22.1, - 22, - 22, - 22.1, - 22.1, - 22.2, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 20, - 20, - 20.1, - 20.2, - 20.3, - 20.4, - 20.3, - 20.3, - 20.2, - 20.2, - 20.2, - 20.2, - 20.2, - 20.2, - 20.3, - 20.3, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.7, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.9, - 20.9, - 21, - 21.4, - 21.8, - 22.1, - 22.1, - 21.4, - 20.6, - 20.2, - 20, - 19.7, - 19.6, - 19.8, - 20, - 20.1, - 20.4, - 20.9, - 21.9, - 23, - 23.9, - 24.4, - 24.4, - 24.3, - 24.6, - 25, - 25.3, - 25.3, - 25, - 24.5, - 23.9, - 24.1, - 24.7, - 24.8, - 24.8, - 25.1, - 25.1, - 25, - 24.6, - 24.2, - 25, - 25.8, - 26, - 26.1, - 26.4, - 25.6, - 24.7, - 24.9, - 25.5, - 26.5, - 27.1, - 26.8, - 27.1, - 28.3, - 29.4, - 29.6, - 29, - 28.5, - 28.5, - 29.5, - 30.2, - 30.1, - 30.5, - 30.9, - 31.1, - 31.3, - 31.9, - 32.7, - 33.5, - 34, - 34.6, - 35.3, - 36.1, - 36.6, - 37.1, - 37.8, - 38.2, - 38, - 37.8, - 38.4, - 39.2, - 40, - 40.2, - 41.3, - 43.4, - 44.3, - 45, - 45.6, - 46.2, - 46.9, - 47.4, - 47.9, - 48.2, - 48.4, - 48.5, - 48.6, - 48.7, - 48.6, - 48.5, - 48.7, - 48.7, - 48.8, - 48.9, - 49, - 49.2, - 49.3, - 49.4, - 49.3, - 48.9, - 48.8, - 48.8, - 49, - 48.9, - 49, - 49.1, - 48.7, - 48.2, - 47.5, - 46.6, - 46.3, - 46.6, - 47.2, - 47.4, - 47.2, - 46.8, - 46.9, - 46.8, - 46.4, - 45.8, - 45.3, - 44.8, - 44.5, - 44.3, - 43.9, - 43.6, - 43, - 42.1, - 39.9, - 38.4, - 37.3, - 36.6, - 36, - 35.3, - 34.2, - 33, - 32, - 31.2, - 30.4, - 29.4, - 29.1, - 28.8, - 28.7, - 28.7, - 27.5, - 27, - 27, - 27.1, - 27.1, - 26.5, - 26.1, - 25.4, - 24.7, - 24.2, - 23.9, - 23.5, - 23.3, - 22.9, - 22.6, - 22.2, - 21.7, - 21.7, - 21.4, - 21.4, - 21.4, - 21.3, - 21.2, - 21.1, - 21.1, - 21.1, - 21.1, - 21.2, - 21.3, - 21.3, - 21.4, - 21.4, - 21.4, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 21.8, - 21.8, - 21.9, - 21.9, - 21.9, - 21.9, - 22, - 22, - 22, - 22, - 22, - 22, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.2, - 22.3, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.2, - 20.1, - 19.9, - 20, - 20.1, - 20.1, - 20.1, - 20.1, - 20.2, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.6, - 20.7, - 20.7, - 20.7, - 20.7, - 20.8, - 20.7, - 20.8, - 20.8, - 21, - 21.4, - 21.8, - 21.9, - 21.6, - 20.8, - 20.3, - 20, - 19.6, - 19.5, - 19.6, - 19.8, - 20, - 20.4, - 20.9, - 21.7, - 22.9, - 23.8, - 24.6, - 24.9, - 25, - 24.9, - 25.1, - 25.3, - 25.5, - 25.5, - 25, - 24.4, - 23.7, - 24.3, - 24.8, - 24.9, - 25, - 25, - 24.8, - 24.4, - 24.1, - 24, - 24.7, - 25.2, - 25.2, - 25.5, - 25.6, - 25, - 24.7, - 25.2, - 25.2, - 25.8, - 26.3, - 26.4, - 27.1, - 27.9, - 28.6, - 28.7, - 28.2, - 28, - 28.8, - 29.7, - 30.5, - 30.6, - 30.6, - 30.8, - 31.2, - 31.6, - 32.4, - 33.1, - 33.6, - 34, - 34.4, - 34.8, - 35.4, - 35.8, - 36.1, - 36.3, - 36.3, - 36.5, - 36.5, - 38.3, - 38.8, - 39.3, - 40.3, - 42.3, - 44, - 45, - 45.8, - 46.2, - 46.6, - 47.1, - 47.4, - 47.7, - 48, - 48.1, - 48.2, - 48.1, - 48.2, - 48.2, - 48, - 48.2, - 48.2, - 48.3, - 48.5, - 48.5, - 48.7, - 48.8, - 49, - 48.8, - 48.4, - 48.1, - 48.4, - 48.5, - 48.4, - 48.7, - 48.7, - 48.4, - 47.8, - 47.1, - 46.3, - 45.8, - 46.2, - 46.8, - 46.9, - 46.6, - 46.2, - 46.1, - 46.3, - 46.1, - 45.7, - 45.2, - 44.8, - 44.5, - 44.5, - 44, - 43.5, - 42.9, - 41.6, - 39.5, - 38, - 36.7, - 35.8, - 35.1, - 34.5, - 33.5, - 32.6, - 31.8, - 31.1, - 30.3, - 29.5, - 28.8, - 28.3, - 28.2, - 28.2, - 27.1, - 27, - 27.1, - 27.1, - 27.1, - 26.6, - 26.3, - 25.8, - 25.1, - 24.4, - 24.1, - 23.7, - 23.4, - 22.9, - 22.6, - 22.2, - 21.7, - 21.5, - 21.3, - 21.3, - 21.2, - 21.2, - 21.1, - 21, - 21, - 21.1, - 21, - 21, - 21.1, - 21.3, - 21.3, - 21.3, - 21.3, - 21.3, - 21.3, - 21.5, - 21.6, - 21.7, - 21.7, - 21.7, - 21.8, - 21.8, - 21.8, - 21.8, - 21.9, - 21.9, - 21.9, - 21.9, - 21.9, - 22, - 22, - 22, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 19.7, - 19.8, - 19.7, - 19.7, - 19.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.9, - 19.9, - 20, - 20, - 20, - 19.9, - 19.8, - 19.8, - 19.9, - 19.9, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.6, - 20.6, - 20.6, - 20.7, - 20.7, - 20.7, - 20.7, - 20.7, - 20.8, - 21.3, - 21.6, - 21.4, - 20.9, - 20.3, - 20, - 19.7, - 19.4, - 19.4, - 19.6, - 19.9, - 20.2, - 20.7, - 21.4, - 22.4, - 23.3, - 24.2, - 24.9, - 25.2, - 25.3, - 25.4, - 25.5, - 25.6, - 25.6, - 25.5, - 24.9, - 24.1, - 23.6, - 24.4, - 24.8, - 25, - 25.1, - 24.9, - 24.5, - 23.9, - 23.7, - 23.9, - 24.2, - 24.4, - 24.5, - 24.7, - 24.7, - 24.5, - 25, - 26, - 25.6, - 25.5, - 25.7, - 26.1, - 26.7, - 27.4, - 27.4, - 27.3, - 27.4, - 28.5, - 29.6, - 30.1, - 30.7, - 31.1, - 31.1, - 31.4, - 31.8, - 32.4, - 32.9, - 33.3, - 33.7, - 33.9, - 34.1, - 34.4, - 34.8, - 35, - 35.4, - 35.3, - 35.1, - 35.2, - 36.2, - 38.5, - 39.3, - 40.3, - 41.7, - 43.5, - 44.5, - 45.5, - 46.1, - 46.3, - 46.7, - 46.9, - 47.2, - 47.4, - 47.6, - 47.7, - 47.7, - 47.6, - 47.7, - 47.8, - 47.5, - 47.6, - 47.7, - 47.9, - 48.1, - 48.1, - 48.3, - 48.5, - 48.4, - 48.2, - 47.9, - 47.5, - 47.9, - 47.9, - 48, - 48.3, - 48.4, - 48.3, - 47.6, - 46.8, - 45.9, - 45.2, - 45.8, - 46.3, - 46.4, - 46, - 45.7, - 45.6, - 45.8, - 45.8, - 45.6, - 45.2, - 44.7, - 44.4, - 44.3, - 44, - 43.6, - 42.9, - 41.5, - 39.1, - 37.7, - 36.6, - 35.3, - 34.1, - 33.3, - 32.2, - 31.5, - 30.9, - 30.6, - 29.9, - 29.3, - 28.6, - 27.9, - 27.8, - 27.6, - 27, - 27, - 27.1, - 27.1, - 27.1, - 26.5, - 26.3, - 25.8, - 25.2, - 24.5, - 24.1, - 23.8, - 23.5, - 23, - 22.5, - 22.1, - 21.6, - 21.4, - 21.2, - 21.2, - 21.2, - 21, - 20.7, - 20.5, - 20.8, - 20.8, - 20.7, - 20.5, - 20.6, - 21, - 20.9, - 20.8, - 20.7, - 20.8, - 20.9, - 21.2, - 21.4, - 21.6, - 21.6, - 21.7, - 21.7, - 21.7, - 21.8, - 21.8, - 21.8, - 21.8, - 21.9, - 21.9, - 21.9, - 22, - 22, - 22, - 22.1, - 22.1, - 22.2, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 19.6, - 19.6, - 19.6, - 19.7, - 19.7, - 19.8, - 19.7, - 19.7, - 19.7, - 19.7, - 19.7, - 19.7, - 19.7, - 19.8, - 19.8, - 19.8, - 19.7, - 19.6, - 19.6, - 19.7, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.3, - 20.4, - 20.5, - 20.5, - 20.5, - 20.6, - 20.6, - 20.6, - 20.5, - 20.6, - 20.6, - 20.6, - 20.6, - 20.9, - 21, - 20.6, - 20.2, - 19.9, - 19.6, - 19.3, - 19.3, - 19.6, - 19.7, - 20, - 20.5, - 21.2, - 22, - 22.8, - 23.4, - 24.2, - 24.9, - 25.3, - 25.4, - 25.5, - 25.4, - 25.4, - 25.4, - 25.2, - 24.6, - 23.7, - 23.5, - 24.3, - 24.9, - 25.1, - 25.1, - 24.9, - 24.4, - 23.5, - 23.6, - 23.7, - 23.9, - 24.1, - 24.2, - 24.6, - 24.9, - 25.5, - 26.2, - 27, - 26.8, - 26.1, - 25.9, - 26, - 26.2, - 26.6, - 26.7, - 27, - 27.7, - 28.9, - 30, - 30.5, - 31, - 31.5, - 31.8, - 32.1, - 32.5, - 32.8, - 33.1, - 33.3, - 33.6, - 33.7, - 33.9, - 34.2, - 34.3, - 34.4, - 34.5, - 34.5, - 34.3, - 35, - 36.3, - 38.5, - 39.9, - 42, - 43.3, - 44.1, - 44.9, - 45.6, - 46, - 46.2, - 46.4, - 46.5, - 46.8, - 47.1, - 47.2, - 47.3, - 47.2, - 47.1, - 47.3, - 47.3, - 47.1, - 47.2, - 47.4, - 47.6, - 47.7, - 47.8, - 47.9, - 48, - 47.8, - 47.5, - 47.4, - 47, - 47.3, - 47.3, - 47.4, - 47.9, - 48.1, - 48, - 47.5, - 46.7, - 45.7, - 44.7, - 45.2, - 45.7, - 45.8, - 45.5, - 45, - 44.9, - 45.2, - 45.3, - 45.3, - 45, - 44.6, - 44.3, - 44, - 43.8, - 43.5, - 42.6, - 40.2, - 38.8, - 37.5, - 36.3, - 35, - 33.7, - 32.8, - 31.2, - 30.4, - 30, - 29.9, - 29.5, - 29, - 28.5, - 27.8, - 27.5, - 27.2, - 26.9, - 27, - 27.1, - 27.1, - 27, - 26.5, - 26.3, - 25.8, - 25.1, - 24.5, - 24.1, - 23.8, - 23.4, - 22.9, - 22.5, - 21.9, - 21.5, - 21.3, - 21.1, - 21.1, - 20.9, - 20.6, - 20.1, - 19.9, - 20.1, - 20.2, - 20.2, - 20.1, - 20.1, - 20.4, - 20.4, - 20.3, - 20.2, - 20.2, - 20.4, - 20.6, - 20.9, - 21.2, - 21.4, - 21.6, - 21.6, - 21.7, - 21.7, - 21.7, - 21.8, - 21.8, - 21.8, - 21.9, - 21.9, - 22, - 22, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 19.4, - 19.4, - 19.4, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.6, - 19.5, - 19.5, - 19.4, - 19.4, - 19.5, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.3, - 20.3, - 20.4, - 20.5, - 20.5, - 20.6, - 20.5, - 20.5, - 20.5, - 20.5, - 20.5, - 20.4, - 20.4, - 20.5, - 20.5, - 20.3, - 19.9, - 19.5, - 19.2, - 19.1, - 19.4, - 19.6, - 19.8, - 20.2, - 20.9, - 21.7, - 22.3, - 22.7, - 23.3, - 23.9, - 24.6, - 25.1, - 25.2, - 25.2, - 25.1, - 24.9, - 24.8, - 24.6, - 24.1, - 23, - 23.5, - 24.2, - 24.7, - 25.2, - 25.3, - 25.3, - 24.7, - 23.3, - 23.5, - 23.7, - 24.2, - 24.9, - 25.4, - 26, - 26.7, - 27.2, - 27.5, - 27.7, - 27.6, - 27.2, - 26.7, - 26.6, - 26.6, - 26.7, - 27, - 27.7, - 28.5, - 29.4, - 30.1, - 30.6, - 31.2, - 31.8, - 32.1, - 32.3, - 32.5, - 32.7, - 32.9, - 33.1, - 33.3, - 33.4, - 33.6, - 34, - 34, - 33.8, - 33.8, - 33.7, - 34.1, - 35.3, - 36.4, - 38.2, - 40.4, - 42.6, - 43.6, - 44.3, - 44.8, - 45.2, - 45.7, - 45.9, - 45.8, - 46, - 46.5, - 46.7, - 46.9, - 46.8, - 46.8, - 46.6, - 46.7, - 46.8, - 46.7, - 46.9, - 47.1, - 47.2, - 47.4, - 47.5, - 47.5, - 47.5, - 47.3, - 46.9, - 47, - 46.6, - 46.7, - 46.7, - 46.9, - 47.5, - 47.7, - 47.6, - 47.2, - 46.6, - 45.7, - 44.6, - 44.4, - 44.7, - 44.9, - 44.8, - 44.4, - 44.3, - 44.5, - 44.7, - 44.8, - 44.8, - 44.5, - 44.2, - 43.9, - 43.6, - 43.3, - 42.3, - 39.7, - 38.6, - 37.5, - 36.2, - 35.1, - 34.2, - 33.2, - 31.7, - 30.7, - 29.8, - 29.3, - 28.9, - 28.7, - 28.5, - 28, - 27.4, - 26.9, - 26.9, - 26.9, - 27, - 27, - 26.7, - 26.3, - 26.2, - 25.8, - 25.1, - 24.4, - 24, - 23.8, - 23.3, - 22.8, - 22.4, - 21.8, - 21.3, - 21.1, - 21, - 20.8, - 20.3, - 20.1, - 19.6, - 19.3, - 19.5, - 19.7, - 19.7, - 19.7, - 19.8, - 19.9, - 19.9, - 19.9, - 19.8, - 19.9, - 20, - 20.2, - 20.4, - 20.6, - 20.8, - 21.1, - 21.2, - 21.4, - 21.4, - 21.6, - 21.8, - 21.8, - 21.9, - 21.9, - 21.9, - 22, - 22, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.6, - 22.7, - 22.8, - 22.8, - 19, - 19.1, - 19.3, - 19.5, - 19.5, - 19.5, - 19.5, - 19.5, - 19.5, - 19.4, - 19.4, - 19.4, - 19.3, - 19.3, - 19.1, - 19.1, - 19.2, - 19.3, - 19.4, - 19.6, - 19.8, - 19.9, - 20, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.4, - 20.4, - 20.4, - 20.5, - 20.4, - 20.4, - 20.4, - 20.4, - 20.3, - 20.3, - 20.3, - 20.3, - 20.2, - 20, - 19.6, - 19.2, - 19, - 19.1, - 19.3, - 19.4, - 19.7, - 20.1, - 20.8, - 21.6, - 22.2, - 22.6, - 23.1, - 23.5, - 24, - 24.4, - 24.6, - 24.4, - 24.3, - 24.1, - 24, - 23.6, - 23.1, - 22.8, - 24.1, - 24.6, - 24.7, - 25, - 25.3, - 25.3, - 24.9, - 23.4, - 23.3, - 23.6, - 24.1, - 25.2, - 26.1, - 27, - 27.6, - 27.9, - 27.9, - 27.8, - 27.8, - 27.7, - 27.5, - 27.4, - 27.5, - 27.7, - 28.1, - 28.6, - 29.2, - 29.6, - 30, - 30.5, - 31.1, - 31.6, - 31.8, - 31.8, - 31.8, - 32.2, - 32.6, - 32.8, - 32.9, - 32.9, - 33.2, - 33.7, - 33.5, - 33.2, - 33, - 33.2, - 34.2, - 35.2, - 36.4, - 37.8, - 40.4, - 42.6, - 43.7, - 44.2, - 44.3, - 44.5, - 45.1, - 45.3, - 45.2, - 45.6, - 46.2, - 46.5, - 46.5, - 46.4, - 46.4, - 46.1, - 46.1, - 46.2, - 46.3, - 46.8, - 46.8, - 46.8, - 47.1, - 47.2, - 47.2, - 47.1, - 46.9, - 46.5, - 46.6, - 46.4, - 46.1, - 46.2, - 46.6, - 47.2, - 47.4, - 47.3, - 46.9, - 46.2, - 45.5, - 44.6, - 44, - 43.9, - 44.1, - 44.2, - 44, - 43.9, - 44, - 44, - 44.2, - 44.2, - 44, - 43.9, - 43.5, - 43.2, - 42.9, - 42.2, - 40, - 38.8, - 37.7, - 36.7, - 35.7, - 34.7, - 33.6, - 32.4, - 31.3, - 30.3, - 29.2, - 28.5, - 28.4, - 28.3, - 27.9, - 27.1, - 26.8, - 26.8, - 26.8, - 26.8, - 26.6, - 26.3, - 26, - 25.8, - 25.5, - 24.8, - 24.2, - 24, - 23.7, - 23.2, - 22.7, - 22.3, - 21.7, - 21.3, - 21, - 20.9, - 20.5, - 19.9, - 19.5, - 18.7, - 17.7, - 17.7, - 18.2, - 18.7, - 18.7, - 19, - 19, - 19.1, - 19.1, - 18.9, - 19.3, - 19.5, - 19.7, - 19.9, - 20.1, - 20.3, - 20.5, - 20.8, - 20.9, - 21.1, - 21.6, - 21.8, - 21.8, - 21.9, - 21.9, - 22, - 22, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.5, - 22.5, - 22.5, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 18.5, - 18.7, - 19.1, - 19.2, - 19.2, - 19.2, - 19.2, - 19.3, - 19.3, - 19.1, - 19, - 18.8, - 18.6, - 18.6, - 18.7, - 18.9, - 19.2, - 19.4, - 19.6, - 19.8, - 19.9, - 19.9, - 20, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.3, - 20.3, - 20.3, - 20.2, - 20, - 20, - 20.1, - 20.2, - 19.9, - 19.6, - 19.2, - 18.9, - 18.8, - 19.1, - 19.2, - 19.4, - 19.6, - 20, - 20.4, - 21, - 21.9, - 22.5, - 22.7, - 22.9, - 23.1, - 23.4, - 23.4, - 23.1, - 22.7, - 22.7, - 22.6, - 22.4, - 22.4, - 23.3, - 24.7, - 25.1, - 24.9, - 24.9, - 25.1, - 25.1, - 24.7, - 23.5, - 23.1, - 23.4, - 23.9, - 25, - 26.1, - 27, - 27.6, - 27.8, - 27.7, - 27.6, - 27.7, - 27.7, - 27.8, - 27.9, - 28, - 28.3, - 28.6, - 28.8, - 29, - 29.2, - 29.8, - 30.2, - 30.7, - 30.9, - 30.9, - 30.9, - 31.1, - 31.5, - 32.1, - 32.4, - 32.3, - 32.3, - 32.9, - 33.2, - 32.9, - 32.3, - 32.6, - 33.6, - 34.3, - 35, - 36, - 38.4, - 41.1, - 42.8, - 43.6, - 43.8, - 43.7, - 44, - 44.4, - 44.6, - 44.8, - 45.3, - 45.9, - 46.1, - 46, - 45.7, - 45.8, - 45.4, - 45.5, - 45.8, - 46.1, - 46.4, - 46.3, - 46.5, - 46.8, - 46.9, - 46.9, - 46.8, - 46.4, - 46.1, - 46.2, - 46.2, - 45.8, - 45.8, - 46.5, - 47.1, - 47.1, - 46.9, - 46.6, - 46, - 45.2, - 44.5, - 44, - 43.6, - 43.5, - 43.6, - 43.5, - 43.2, - 43.3, - 42.6, - 42.5, - 42.1, - 42, - 42, - 42, - 42.2, - 42.1, - 41.6, - 40.3, - 38.9, - 37.7, - 36.9, - 36.1, - 35, - 33.9, - 32.8, - 31.1, - 30.1, - 29.2, - 28.4, - 27.6, - 27.5, - 27.2, - 26.8, - 26.7, - 26.7, - 26.7, - 26.4, - 25.8, - 25.7, - 25.5, - 25.1, - 24.8, - 24.4, - 24.1, - 23.9, - 23.6, - 23.1, - 22.6, - 22.3, - 21.7, - 21.2, - 21, - 20.8, - 20.1, - 19.6, - 18.4, - 15.9, - 15, - 14.7, - 15.4, - 16.1, - 16.5, - 16.8, - 17, - 17, - 17.1, - 16.8, - 17.4, - 17.9, - 18.4, - 18.8, - 19.5, - 19.7, - 20.1, - 20.3, - 20.4, - 20.8, - 21.3, - 21.7, - 21.8, - 21.9, - 21.9, - 22, - 22, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 17.9, - 18, - 18.5, - 18.6, - 18.7, - 18.6, - 18.6, - 18.7, - 18.5, - 18.1, - 18, - 17.8, - 17.8, - 18.3, - 18.9, - 19.2, - 19.5, - 19.6, - 19.8, - 19.9, - 20, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.3, - 20.3, - 20.2, - 20.2, - 20, - 19.7, - 19.5, - 19.8, - 19.8, - 19.6, - 19.3, - 19, - 18.7, - 18.8, - 19, - 19.1, - 19.3, - 19.5, - 19.8, - 20.2, - 20.7, - 21.6, - 22, - 22, - 21.8, - 21.7, - 21.8, - 21.6, - 21.3, - 21.4, - 21.7, - 21.9, - 22.6, - 23.4, - 24.3, - 25.2, - 25.3, - 25, - 24.8, - 24.7, - 24.6, - 24.2, - 23.3, - 22.9, - 23.3, - 23.9, - 25, - 26.1, - 27, - 27.5, - 27.6, - 27.4, - 27.2, - 27.4, - 27.6, - 27.8, - 27.9, - 28.1, - 28.4, - 28.5, - 28.6, - 28.5, - 28.7, - 29.1, - 29.7, - 30, - 30.2, - 30.1, - 30.1, - 30.5, - 31, - 31.6, - 31.8, - 31.4, - 31.6, - 32.1, - 32.1, - 32.2, - 31.5, - 32.4, - 33.6, - 34.2, - 35, - 36.2, - 39.5, - 41.9, - 42.9, - 43.3, - 43.2, - 43, - 43.5, - 43.7, - 44, - 44.6, - 45.1, - 45.5, - 45.5, - 45.3, - 44.9, - 45.1, - 44.9, - 45.3, - 45.7, - 45.9, - 45.9, - 45.8, - 46.1, - 46.4, - 46.5, - 46.6, - 46.4, - 46, - 45.6, - 45.9, - 45.8, - 45.4, - 45.6, - 46.4, - 46.9, - 46.9, - 46.6, - 46.1, - 45.7, - 45.2, - 44.6, - 44.1, - 43.6, - 42.6, - 41.9, - 42.1, - 41.5, - 41.3, - 40.6, - 40.1, - 40, - 40, - 39.9, - 39.7, - 40.2, - 40.6, - 40.5, - 39.7, - 38.5, - 37.5, - 36.9, - 36, - 35, - 33.9, - 33, - 30.8, - 29.6, - 28.7, - 27.8, - 27.2, - 27, - 26.7, - 26.6, - 26.6, - 26.6, - 26.2, - 25.3, - 24.7, - 24.5, - 24.5, - 24.2, - 24.3, - 24.3, - 24, - 23.8, - 23.6, - 23.1, - 22.6, - 22.3, - 21.7, - 21.2, - 20.9, - 20.7, - 19.9, - 19.3, - 17.4, - 14.7, - 13.9, - 13.6, - 14.1, - 14.6, - 15, - 15.4, - 15.6, - 15.7, - 15.6, - 15.4, - 15.9, - 16.4, - 16.8, - 16.8, - 17.1, - 17.6, - 18.3, - 18.8, - 19.8, - 20.4, - 20.9, - 21.5, - 21.8, - 21.9, - 21.9, - 22, - 22, - 22.1, - 22.1, - 22.1, - 22.2, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.6, - 22.6, - 22.6, - 22.6, - 22.7, - 22.8, - 22.8, - 22.8, - 17.2, - 17.3, - 17.4, - 17.6, - 17.7, - 17.7, - 17.7, - 17.6, - 17.4, - 17.1, - 17.1, - 17.4, - 18.1, - 18.9, - 19.3, - 19.5, - 19.7, - 19.9, - 20, - 20, - 20, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.2, - 19.9, - 19.6, - 19.8, - 19.7, - 19, - 19.1, - 19.3, - 19.2, - 19, - 18.7, - 18.6, - 18.8, - 18.9, - 19, - 19.2, - 19.4, - 19.6, - 19.9, - 20.3, - 20.7, - 21, - 21, - 20.9, - 20.8, - 20.7, - 20.7, - 20.9, - 21.3, - 21.6, - 22.2, - 23.5, - 24.2, - 24.8, - 25.2, - 25.2, - 24.9, - 24.2, - 23.8, - 23.6, - 23.4, - 23.1, - 22.8, - 23.2, - 24.2, - 25.4, - 26.2, - 26.7, - 27.1, - 27.2, - 26.9, - 26.6, - 26.9, - 27.2, - 27.5, - 27.7, - 28, - 28.2, - 28.2, - 28.2, - 28.2, - 28.3, - 28.3, - 28.9, - 29.4, - 29.6, - 29.4, - 29.5, - 30.1, - 30.8, - 31.1, - 31, - 30.7, - 30.9, - 31.1, - 30.9, - 31.3, - 31.1, - 32.1, - 33.5, - 34.2, - 35, - 36.1, - 38.5, - 41.4, - 42.3, - 42.7, - 42.5, - 42.3, - 42.8, - 43.3, - 44.1, - 44.5, - 44.9, - 45.1, - 44.9, - 44.6, - 44.2, - 44.2, - 44.3, - 44.9, - 45.4, - 45.4, - 45.4, - 45.5, - 45.8, - 46.1, - 46.3, - 46.3, - 46.2, - 45.6, - 45.1, - 45.3, - 45.3, - 45, - 45.5, - 46.2, - 46.6, - 46.6, - 46.2, - 45.6, - 45.3, - 44.9, - 44.5, - 44.1, - 43.3, - 41.1, - 39, - 38.5, - 38.3, - 38.5, - 38.6, - 38.7, - 38.6, - 38.7, - 38.7, - 38.6, - 38.8, - 39, - 38.8, - 38.4, - 37.8, - 37.2, - 36.8, - 35.9, - 34.9, - 33.6, - 32.6, - 30.2, - 29.1, - 28.2, - 27.4, - 27.1, - 26.9, - 26.6, - 26.5, - 26.5, - 26.4, - 25.6, - 24.4, - 23.9, - 23.6, - 23.7, - 23.6, - 23.8, - 23.9, - 23.8, - 23.8, - 23.6, - 23, - 22.6, - 22.2, - 21.6, - 21.2, - 20.9, - 20.6, - 19.9, - 19.3, - 17, - 14.7, - 13.8, - 13.2, - 13, - 13.1, - 13.7, - 14.1, - 14.3, - 14.5, - 14.4, - 14.1, - 14.8, - 15.3, - 15.4, - 15.2, - 15.2, - 15.5, - 16.1, - 17, - 18.4, - 20.1, - 20.6, - 21.1, - 21.7, - 21.9, - 21.9, - 21.9, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 22.9, - 16.6, - 16.6, - 16.6, - 16.7, - 16.9, - 16.9, - 16.9, - 16.8, - 16.7, - 16.7, - 17.1, - 17.8, - 18.8, - 19.2, - 19.5, - 19.7, - 19.9, - 20.1, - 20.1, - 20.1, - 20.1, - 20, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.2, - 20.2, - 20.1, - 19.7, - 18.9, - 19.3, - 19.3, - 18.6, - 18.5, - 18.7, - 18.8, - 18.9, - 18.7, - 18.5, - 18.7, - 18.8, - 19, - 19.1, - 19.3, - 19.5, - 19.6, - 19.9, - 20.1, - 20.3, - 20.4, - 20.5, - 20.4, - 20.4, - 20.7, - 21.2, - 21.5, - 22, - 22.6, - 23.4, - 23.9, - 24.3, - 24.4, - 24.3, - 23.9, - 23.2, - 22.8, - 22.7, - 22.8, - 22.8, - 22.7, - 23.2, - 24.7, - 25.7, - 25.9, - 26.1, - 26.5, - 26.5, - 26.2, - 26.3, - 26.5, - 26.8, - 27.3, - 27.6, - 27.9, - 28, - 27.9, - 27.9, - 28.1, - 27.9, - 27.8, - 28.1, - 28.6, - 28.8, - 28.4, - 29, - 29.9, - 30.4, - 30.5, - 30.3, - 30, - 30.2, - 30.3, - 29.9, - 30, - 30.7, - 32.1, - 33.5, - 34.2, - 34.8, - 35.5, - 36.6, - 38.4, - 39.5, - 41.6, - 41.8, - 42.1, - 42.8, - 43.5, - 44.1, - 44.4, - 44.6, - 44.6, - 44.3, - 43.8, - 43.5, - 43.3, - 43.4, - 44.3, - 44.7, - 44.7, - 44.9, - 45.3, - 45.7, - 46, - 46.1, - 46.1, - 45.9, - 45.4, - 44.8, - 44.7, - 44.7, - 44.8, - 45.4, - 45.8, - 46.1, - 46.2, - 45.9, - 45.4, - 44.9, - 44.5, - 44.2, - 43.9, - 42.6, - 40.4, - 38.3, - 37, - 36.4, - 36.7, - 37.2, - 37.3, - 37.3, - 37.4, - 37.4, - 37.5, - 37.7, - 37.7, - 37.7, - 37.5, - 37.2, - 36.9, - 36.5, - 35.8, - 34.7, - 33.4, - 31.6, - 30.1, - 29.1, - 28.2, - 27.5, - 27, - 26.6, - 26.5, - 26.5, - 26.4, - 25.9, - 25.3, - 24.2, - 23.5, - 23.2, - 23.1, - 23.2, - 23.4, - 23.6, - 23.7, - 23.7, - 23.5, - 23.1, - 22.5, - 22.2, - 21.5, - 21, - 20.8, - 20.4, - 19.8, - 19.1, - 16.8, - 15.3, - 14.2, - 13.3, - 12.3, - 12, - 12.7, - 13.2, - 13.4, - 13.7, - 13.5, - 13.4, - 14, - 14.2, - 13.9, - 13.5, - 13.9, - 14.7, - 15.5, - 16.6, - 17.8, - 19.7, - 20.5, - 20.8, - 21.4, - 21.8, - 21.9, - 21.9, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 22.9, - 16.1, - 16.1, - 16.1, - 16.2, - 16.3, - 16.3, - 16.3, - 16.4, - 16.4, - 16.8, - 17.5, - 18.3, - 19, - 19.4, - 19.6, - 19.9, - 20, - 20.1, - 20.1, - 20.1, - 20.1, - 20, - 20, - 20, - 20.1, - 20.1, - 20.1, - 20.1, - 20.2, - 20.2, - 20.2, - 20.1, - 19.9, - 19.3, - 18.6, - 18.7, - 18.8, - 18.3, - 18.2, - 18.3, - 19.4, - 20.4, - 20.1, - 19.1, - 18.7, - 18.8, - 19, - 19.1, - 19.3, - 19.4, - 19.5, - 19.7, - 19.8, - 20, - 20.1, - 20.2, - 20.2, - 20.5, - 20.9, - 21.2, - 21.8, - 22.3, - 22.5, - 22.8, - 23, - 23.2, - 23, - 22.8, - 22.5, - 22.4, - 22.3, - 22.4, - 22.5, - 22.6, - 22.6, - 23.3, - 24.7, - 25.3, - 25.1, - 25.2, - 25.6, - 25.8, - 26, - 26.6, - 26.9, - 27, - 27.2, - 27.5, - 27.8, - 27.9, - 27.9, - 27.7, - 27.8, - 27.7, - 27.4, - 27.7, - 28, - 27.9, - 28, - 28.8, - 29.7, - 29.9, - 29.8, - 29.6, - 29.4, - 29.5, - 29.6, - 29.5, - 30, - 30.9, - 32.3, - 33.6, - 34.1, - 34.4, - 34.9, - 35.5, - 36.5, - 37.4, - 40, - 41.2, - 42.1, - 43, - 43.7, - 44.1, - 44.4, - 44.3, - 44, - 43.5, - 43, - 42.7, - 42.5, - 42.8, - 43.6, - 43.6, - 44.1, - 44.8, - 45.4, - 45.7, - 45.8, - 45.7, - 45.7, - 45.5, - 45.2, - 44.7, - 44.2, - 44.2, - 44.8, - 45.3, - 45.5, - 45.7, - 45.7, - 45.5, - 45, - 44.6, - 44, - 43.7, - 43.4, - 42.1, - 40.2, - 38.5, - 37.1, - 35.9, - 36, - 36.2, - 36.3, - 36.4, - 36.5, - 36.5, - 36.6, - 36.9, - 36.9, - 36.8, - 36.8, - 36.5, - 36.3, - 36, - 35.6, - 34.7, - 33.3, - 31.4, - 29.9, - 29, - 28.2, - 27.4, - 26.8, - 26.6, - 26.5, - 26.4, - 26, - 25.3, - 24.7, - 24, - 23.5, - 23.2, - 22.9, - 22.8, - 23, - 23.2, - 23.4, - 23.5, - 23.5, - 23.2, - 22.7, - 23.6, - 22.7, - 21, - 20.7, - 20.2, - 19.6, - 18.9, - 17.1, - 15.6, - 14.2, - 13, - 12.1, - 11.5, - 11.8, - 12.4, - 12.7, - 12.8, - 12.7, - 12.9, - 13.1, - 12.9, - 12.6, - 12.7, - 13.5, - 14.3, - 15.3, - 16.3, - 17.3, - 19.2, - 20.3, - 20.7, - 21.1, - 21.6, - 21.9, - 21.9, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 22.9, - 15.6, - 15.7, - 15.7, - 15.8, - 15.9, - 15.9, - 16, - 16.1, - 16.4, - 17.1, - 17.9, - 18.7, - 19.2, - 19.5, - 19.7, - 19.9, - 20.1, - 20.1, - 20.1, - 20.1, - 20, - 20, - 20, - 19.9, - 20, - 20, - 20, - 20.1, - 20.2, - 20.2, - 20.1, - 20, - 19.7, - 19.2, - 18.5, - 18.2, - 18.2, - 18, - 18, - 18.6, - 20.8, - 21.2, - 21.2, - 20.6, - 18.7, - 18.9, - 19, - 19.1, - 19.2, - 19.3, - 19.4, - 19.6, - 19.7, - 19.8, - 20, - 20.1, - 20.3, - 20.6, - 21, - 21.3, - 21.5, - 21.8, - 21.9, - 21.9, - 21.9, - 21.8, - 21.8, - 21.8, - 21.8, - 21.9, - 22, - 22.2, - 22.3, - 22.4, - 22.6, - 23.6, - 24.5, - 24.8, - 25.1, - 25.6, - 25.7, - 26, - 26.5, - 27.2, - 27.5, - 27.5, - 27.5, - 27.7, - 27.9, - 28.1, - 27.9, - 27.5, - 27.4, - 27.5, - 27.2, - 27.6, - 27.5, - 27.8, - 28.1, - 28.6, - 29.3, - 29.3, - 29.1, - 29, - 29.1, - 29.1, - 29.2, - 29.3, - 30, - 30.7, - 31.8, - 33.2, - 33.7, - 33.8, - 34.1, - 35, - 36, - 36.6, - 38.1, - 41.1, - 42.3, - 43.1, - 43.7, - 44, - 44.3, - 44, - 43.4, - 42.7, - 42.2, - 42, - 41.7, - 42.4, - 42.6, - 43, - 44.1, - 44.7, - 45.1, - 45.4, - 45.5, - 45.3, - 45.2, - 45.2, - 44.8, - 44.2, - 43.8, - 44, - 44.7, - 45.1, - 45.3, - 45.3, - 45.3, - 45.1, - 44.6, - 44.3, - 43.8, - 43.3, - 42.7, - 41.7, - 40.1, - 38.7, - 37.2, - 35.9, - 35.2, - 35.2, - 35.4, - 35.7, - 35.6, - 35.6, - 35.9, - 36.3, - 36.4, - 36, - 36, - 35.7, - 35.6, - 35.4, - 35, - 34.3, - 33.3, - 31.6, - 29.5, - 28.6, - 27.7, - 26.9, - 26.6, - 26.5, - 26.4, - 26.2, - 25.5, - 24.4, - 23.7, - 23.7, - 23.5, - 23.3, - 22.9, - 22.6, - 22.6, - 22.8, - 22.9, - 23.1, - 23.3, - 23.3, - 24.4, - 26.6, - 26, - 21.7, - 20.6, - 20.1, - 19.5, - 18.9, - 18.1, - 15.9, - 14.2, - 13.1, - 12, - 11.3, - 11, - 11.6, - 11.9, - 12.1, - 12.1, - 12.3, - 12.1, - 11.6, - 12, - 12.5, - 13.2, - 13.9, - 14.8, - 15.8, - 16.9, - 18.5, - 20, - 20.4, - 20.8, - 21.3, - 21.8, - 22, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.4, - 22.5, - 22.5, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 22.9, - 15.2, - 15.3, - 15.3, - 15.4, - 15.6, - 15.7, - 15.9, - 16.1, - 16.7, - 17.5, - 18.5, - 19, - 19.3, - 19.6, - 19.7, - 19.9, - 20.1, - 20.1, - 20.1, - 20, - 19.9, - 19.9, - 19.9, - 19.8, - 19.8, - 19.8, - 19.9, - 20, - 20, - 20.1, - 20, - 19.8, - 19.5, - 19, - 18.4, - 17.9, - 17.8, - 17.8, - 18, - 19.3, - 21.2, - 21.6, - 21.6, - 20.6, - 18.9, - 18.9, - 19, - 19.1, - 19.2, - 19.3, - 19.4, - 19.6, - 19.8, - 19.9, - 20, - 20.1, - 20.3, - 20.7, - 21.1, - 21.1, - 21.2, - 21.2, - 21.2, - 21.2, - 21.3, - 21.4, - 21.5, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.3, - 22.6, - 23, - 23.8, - 24.6, - 25.3, - 25.9, - 26.4, - 26.8, - 27, - 27.3, - 27.7, - 27.9, - 27.9, - 27.9, - 27.9, - 28, - 28.2, - 28, - 27.4, - 27, - 27.1, - 26.9, - 27.1, - 27.3, - 27.8, - 28.2, - 28.6, - 28.7, - 28.6, - 28.6, - 28.7, - 28.8, - 28.9, - 29.3, - 29.7, - 29.8, - 30.3, - 30.8, - 31.8, - 32.7, - 33.2, - 33.8, - 34.9, - 35.7, - 36.6, - 38.1, - 41.3, - 42.3, - 43.1, - 43.6, - 43.9, - 44, - 43.7, - 43, - 42.1, - 41.2, - 41.1, - 41.1, - 41.7, - 42, - 43, - 43.7, - 44.1, - 44.6, - 45, - 45.1, - 44.9, - 44.6, - 44.7, - 44.4, - 43.7, - 43.6, - 43.8, - 44.4, - 44.9, - 45.1, - 45, - 45, - 44.8, - 44.5, - 44.1, - 43.8, - 43.2, - 42.7, - 41.8, - 40.3, - 38.8, - 37.4, - 36.1, - 35.1, - 34.4, - 34.2, - 34.3, - 34.4, - 34.3, - 34.7, - 35.2, - 35.4, - 35.1, - 35, - 34.8, - 34.7, - 34.4, - 33.9, - 33.5, - 32.7, - 30.9, - 29.1, - 28, - 27.1, - 26.7, - 26.6, - 26.5, - 26.4, - 26, - 25, - 23.8, - 23.3, - 23.4, - 23.4, - 23.1, - 22.7, - 22.4, - 22.4, - 22.4, - 22.4, - 22.6, - 22.8, - 23, - 26.4, - 27.2, - 26.5, - 22.7, - 20.5, - 20, - 19.6, - 19.1, - 18.8, - 16.7, - 14.6, - 13.3, - 12.2, - 11.3, - 10.7, - 10.9, - 11.2, - 11.4, - 11.4, - 11.5, - 11.1, - 11.1, - 11.6, - 11.9, - 12.5, - 13.3, - 14.2, - 15.1, - 16.3, - 17.5, - 19.7, - 20.2, - 20.6, - 21, - 21.6, - 21.9, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 22.8, - 15, - 15.2, - 15.3, - 15.5, - 15.7, - 15.9, - 16.2, - 16.8, - 17.5, - 18.3, - 18.9, - 19.2, - 19.4, - 19.6, - 19.8, - 19.9, - 20.1, - 20, - 19.9, - 19.9, - 19.8, - 19.8, - 19.7, - 19.6, - 19.3, - 19.4, - 19.7, - 19.8, - 19.9, - 19.9, - 19.8, - 19.6, - 19.3, - 18.6, - 18, - 17.7, - 17.7, - 17.8, - 18, - 18.4, - 19.9, - 20.9, - 20.3, - 19.4, - 19, - 19.1, - 19.1, - 19.2, - 19.4, - 19.5, - 19.5, - 19.6, - 19.8, - 20, - 20.2, - 20.1, - 20.3, - 20.6, - 20.8, - 20.9, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.6, - 21.8, - 22, - 22.1, - 22.2, - 22.4, - 22.6, - 23, - 23.5, - 24, - 24.5, - 25.3, - 26.3, - 26.9, - 27.2, - 27.5, - 27.8, - 28, - 28.1, - 28.1, - 28.1, - 28, - 27.8, - 27.7, - 27.6, - 26.9, - 26.6, - 26.8, - 27.2, - 27.5, - 27.8, - 28, - 28.3, - 28.4, - 28.3, - 28.4, - 28.5, - 28.6, - 28.6, - 29, - 29.9, - 30.4, - 30.3, - 30.5, - 30.8, - 31.4, - 32.4, - 33.1, - 33.7, - 34.5, - 35.4, - 36.6, - 38.4, - 41.4, - 42.2, - 43, - 43.5, - 43.7, - 43.7, - 43.4, - 42.6, - 41.6, - 40.3, - 40, - 40.6, - 40.8, - 41.5, - 42.4, - 42.9, - 43.4, - 44.2, - 44.7, - 44.7, - 44.3, - 43.7, - 43.9, - 43.7, - 43.3, - 43.3, - 43.7, - 44.3, - 44.8, - 44.9, - 44.8, - 44.5, - 44.5, - 44.3, - 44.2, - 43.8, - 43.3, - 42.7, - 41.7, - 40.4, - 38.7, - 37.4, - 36.3, - 35.3, - 34.3, - 33.3, - 32.8, - 33, - 32.9, - 33.3, - 33.7, - 34.1, - 34, - 34, - 33.7, - 33.6, - 33.3, - 32.5, - 31.7, - 30.4, - 29, - 28.1, - 27, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 25.7, - 24.8, - 23.8, - 23.3, - 23.1, - 23, - 22.7, - 22.4, - 22.2, - 22.2, - 22.1, - 22.1, - 22.3, - 22.4, - 23.5, - 26.6, - 27.1, - 25.9, - 22.2, - 20.3, - 19.9, - 19.5, - 19.2, - 18.7, - 16.3, - 14.7, - 13.3, - 12.2, - 11.4, - 10.7, - 10.4, - 10.7, - 10.7, - 10.6, - 10.6, - 10.4, - 10.7, - 11, - 11.2, - 12, - 12.7, - 13.6, - 14.5, - 15.5, - 17.1, - 18.9, - 20, - 20.5, - 20.9, - 21.3, - 21.7, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.4, - 22.4, - 22.4, - 22.4, - 22.4, - 22.5, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 15.1, - 15.3, - 15.5, - 15.7, - 16.1, - 16.6, - 17.1, - 17.9, - 18.4, - 18.9, - 19.2, - 19.3, - 19.5, - 19.6, - 19.7, - 19.8, - 19.9, - 19.9, - 19.8, - 19.8, - 19.6, - 19.5, - 19.2, - 19.1, - 18.6, - 18.8, - 19.1, - 19.4, - 19.7, - 19.7, - 19.6, - 19.3, - 18.8, - 18, - 17.6, - 17.6, - 17.7, - 17.9, - 18, - 18.2, - 18.6, - 19.5, - 19, - 18.8, - 18.9, - 19.1, - 19.2, - 19.4, - 19.4, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20.2, - 20.2, - 20.2, - 20.4, - 20.6, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.4, - 21.6, - 21.8, - 21.9, - 22.1, - 22.4, - 22.5, - 22.6, - 22.9, - 23.3, - 24.1, - 24.7, - 24.8, - 24.9, - 25.9, - 26.7, - 27, - 27.5, - 27.9, - 28.1, - 28.1, - 28.1, - 28, - 27.7, - 26.8, - 26.6, - 26.7, - 26.4, - 26.8, - 27.4, - 27.8, - 28.1, - 28.2, - 28.3, - 28.3, - 28.1, - 28.1, - 28.2, - 28.3, - 28.6, - 29, - 29.9, - 30.7, - 31, - 31.2, - 31.3, - 31.5, - 32.2, - 32.9, - 33.3, - 33.8, - 34.5, - 35.3, - 36.3, - 38.4, - 41.5, - 42.3, - 43, - 43.4, - 43.5, - 43.4, - 43, - 42.1, - 41, - 39.6, - 39.1, - 39.2, - 39.4, - 40.2, - 41.2, - 42, - 42.9, - 43.8, - 44.3, - 44.2, - 43.7, - 42.9, - 43, - 42.9, - 43, - 43.4, - 43.8, - 44.3, - 44.7, - 44.8, - 44.5, - 44.1, - 44.1, - 44.1, - 44.1, - 43.7, - 43.2, - 42.4, - 41.3, - 40.1, - 38.5, - 37.2, - 36.1, - 35.2, - 34, - 32.8, - 31.5, - 31.6, - 31.1, - 31.8, - 32.4, - 32.9, - 33.1, - 33.1, - 32.9, - 32.5, - 31.8, - 30.3, - 29.2, - 28.6, - 27.9, - 27, - 26.6, - 26.6, - 26.5, - 26.4, - 26.4, - 26.1, - 25.7, - 24.8, - 23.9, - 23.4, - 23, - 22.9, - 22.8, - 22.4, - 22.2, - 21.9, - 21.8, - 21.7, - 21.8, - 21.8, - 22.7, - 25.7, - 25.4, - 23.6, - 20.8, - 20.2, - 19.7, - 19.4, - 19, - 17.9, - 15.9, - 14.5, - 13.3, - 12.2, - 11.3, - 10.6, - 10.1, - 10.2, - 10.2, - 10.1, - 10.1, - 10, - 10.3, - 10.5, - 10.8, - 11.5, - 12.2, - 13, - 13.9, - 14.9, - 16.3, - 17.6, - 19.5, - 20.3, - 20.7, - 21.1, - 21.4, - 21.7, - 21.9, - 22.1, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.4, - 22.5, - 22.5, - 22.6, - 22.7, - 22.7, - 22.7, - 22.8, - 15.2, - 15.4, - 15.7, - 16.2, - 16.8, - 17.5, - 18.1, - 18.5, - 18.9, - 19.1, - 19.3, - 19.4, - 19.5, - 19.5, - 19.6, - 19.7, - 19.7, - 19.6, - 19.6, - 19.6, - 19.3, - 19, - 18.7, - 18.2, - 17.9, - 18.3, - 18.6, - 19, - 19.3, - 19.4, - 19.3, - 18.7, - 18.1, - 17.5, - 17.5, - 17.6, - 18, - 18.2, - 18.3, - 18.3, - 18.4, - 18.6, - 18.6, - 18.7, - 18.9, - 19, - 19.2, - 19.4, - 19.4, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.4, - 20.6, - 20.7, - 20.9, - 20.9, - 21, - 21.1, - 21.3, - 21.6, - 21.8, - 21.9, - 22.1, - 22.4, - 22.9, - 23.3, - 23.3, - 23.6, - 24, - 24.7, - 25.3, - 25.5, - 26.3, - 26.8, - 28.1, - 27.7, - 27.2, - 27.8, - 28, - 27.9, - 27.8, - 27.7, - 27, - 26, - 26.1, - 26.2, - 26.4, - 27.1, - 27.8, - 28.1, - 28.2, - 28.3, - 28.1, - 27.8, - 27.9, - 28, - 28.2, - 28.6, - 29.1, - 29.9, - 30.6, - 31.2, - 31.6, - 31.9, - 32, - 32.3, - 32.7, - 33.1, - 33.5, - 34, - 34.8, - 35.5, - 36.3, - 37.5, - 40.9, - 42.3, - 42.9, - 43.1, - 43.2, - 43, - 42.6, - 41.6, - 40.1, - 38.4, - 37.7, - 37.5, - 37.8, - 38.4, - 39.7, - 41.5, - 42.5, - 43.2, - 43.7, - 43.6, - 43.2, - 42.4, - 42.1, - 42.2, - 42.9, - 43.6, - 44, - 44.5, - 44.6, - 44.5, - 44.1, - 43.7, - 43.8, - 43.9, - 43.8, - 43.4, - 42.9, - 42, - 41.1, - 40.2, - 38.6, - 37.2, - 36.1, - 35.3, - 34, - 32.6, - 31.1, - 30.1, - 29.3, - 29.9, - 31.2, - 32.1, - 32, - 32.2, - 31.6, - 30.6, - 29.5, - 28.5, - 28, - 27.5, - 26.8, - 26.5, - 26.5, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 25.8, - 24.9, - 24.1, - 23.5, - 23.3, - 23.1, - 22.8, - 22.4, - 22.2, - 22, - 21.6, - 21.5, - 21.3, - 21.2, - 21.2, - 21.8, - 21.6, - 20.9, - 20.4, - 19.8, - 19.4, - 19.1, - 18.1, - 16.6, - 15.1, - 14, - 13, - 11.9, - 11.2, - 10.5, - 10, - 9.7, - 9.7, - 9.6, - 9.6, - 9.7, - 9.6, - 10, - 10.5, - 11.2, - 11.8, - 12.5, - 13.3, - 14.3, - 15.3, - 16.8, - 18.8, - 20, - 20.4, - 20.8, - 21.1, - 21.3, - 21.6, - 21.7, - 21.9, - 22, - 22, - 22, - 22, - 22, - 22, - 22, - 22.1, - 22.2, - 22.4, - 22.4, - 22.5, - 22.4, - 22.4, - 22.5, - 15.3, - 15.6, - 16, - 16.6, - 17.3, - 17.9, - 18.2, - 18.4, - 18.8, - 19, - 19.2, - 19.3, - 19.4, - 19.4, - 19.4, - 19.4, - 19.4, - 19.4, - 19.4, - 19.1, - 18.6, - 18.3, - 18, - 17.5, - 17.5, - 17.9, - 18.2, - 18.5, - 18.9, - 19.1, - 18.8, - 18.1, - 17.5, - 17.3, - 17.5, - 17.8, - 18.1, - 18.2, - 18.4, - 18.4, - 18.5, - 18.6, - 18.7, - 18.8, - 18.9, - 19.1, - 19.3, - 19.3, - 19.4, - 19.5, - 19.6, - 19.7, - 19.9, - 20, - 20.4, - 20.7, - 20.8, - 21, - 21.3, - 21.4, - 21.3, - 21.3, - 21.3, - 21.5, - 21.8, - 22.1, - 22.3, - 22.5, - 22.9, - 23.8, - 24.3, - 24.4, - 24.5, - 24.7, - 25.3, - 25.9, - 26.1, - 27.6, - 28.4, - 29.1, - 29, - 26.9, - 27.7, - 28, - 27.8, - 27.4, - 27, - 26.5, - 25.8, - 26.3, - 26.6, - 26.8, - 27.4, - 27.9, - 28.1, - 28.1, - 28, - 27.6, - 27.6, - 27.9, - 28.2, - 28.4, - 28.8, - 29.4, - 30.1, - 30.8, - 31.4, - 32, - 32.2, - 32.3, - 32.6, - 32.8, - 33.1, - 33.7, - 34.2, - 34.9, - 35.7, - 36.6, - 37.7, - 40.6, - 42.1, - 42.6, - 42.6, - 42.6, - 42.5, - 42, - 41.2, - 38.9, - 37, - 36.5, - 36.1, - 36.1, - 36.8, - 37.9, - 40.7, - 41.8, - 42.5, - 43, - 43, - 42.6, - 41.9, - 41.7, - 42.2, - 42.8, - 43.8, - 44.2, - 44.4, - 44.4, - 44, - 43.7, - 43.5, - 43.6, - 43.6, - 43.6, - 43.2, - 42.8, - 41.8, - 41.1, - 40.2, - 38.7, - 37.3, - 36.2, - 35.4, - 34.2, - 32.9, - 31.4, - 30, - 28.9, - 28.6, - 29.4, - 29.9, - 29.9, - 30.2, - 29.6, - 28.5, - 27.6, - 27.2, - 26.9, - 26.6, - 26.4, - 26.4, - 26.4, - 26.4, - 26.3, - 26.1, - 26, - 25.8, - 25.7, - 24.8, - 24.1, - 23.6, - 23.5, - 23.3, - 22.9, - 22.5, - 22.2, - 22, - 21.6, - 21.4, - 21.3, - 21.2, - 21.1, - 21, - 20.8, - 20.5, - 20, - 19.5, - 19.1, - 18, - 16.6, - 15.1, - 14, - 13.2, - 12.4, - 11.5, - 10.9, - 10.3, - 9.9, - 9.5, - 9.4, - 9.2, - 9.1, - 9.3, - 9.2, - 9.5, - 10.3, - 10.8, - 11.4, - 12.1, - 12.9, - 13.7, - 14.7, - 15.8, - 17.4, - 19, - 20.1, - 20.4, - 20.8, - 21, - 21.2, - 21.4, - 21.5, - 21.5, - 21.6, - 21.5, - 21.6, - 21.6, - 21.6, - 21.6, - 21.6, - 21.8, - 21.8, - 21.9, - 22, - 22, - 22, - 22.1, - 15.5, - 15.7, - 16, - 16.6, - 17.2, - 17.5, - 17.6, - 17.8, - 18.2, - 18.6, - 18.9, - 19.1, - 19.2, - 19.2, - 19.2, - 19.1, - 19, - 19, - 18.8, - 18.4, - 17.9, - 17.5, - 17.3, - 17.1, - 17.3, - 17.5, - 17.8, - 18.1, - 18.4, - 18.5, - 18.1, - 17.4, - 17.2, - 17.4, - 17.7, - 17.9, - 18.1, - 18.1, - 18.2, - 18.3, - 18.5, - 18.6, - 18.7, - 18.8, - 18.9, - 19.1, - 19.2, - 19.3, - 19.4, - 19.6, - 19.7, - 19.8, - 20, - 20.4, - 20.8, - 21.4, - 21.6, - 21.9, - 22.3, - 22.2, - 22.1, - 22.1, - 22, - 22, - 22.2, - 23, - 23.4, - 23.4, - 23.6, - 24.5, - 25, - 25.2, - 25.3, - 25.5, - 25.9, - 26.3, - 26.4, - 27, - 28.1, - 28.8, - 29.3, - 26.8, - 27.6, - 27.9, - 27.7, - 27.2, - 26.4, - 25.9, - 25.6, - 26.4, - 27.3, - 27.6, - 27.8, - 28, - 28, - 27.9, - 27.5, - 27.3, - 27.5, - 27.9, - 28.4, - 28.9, - 29.2, - 29.6, - 30, - 30.6, - 31.2, - 31.6, - 31.6, - 31.9, - 32.3, - 32.5, - 33, - 33.7, - 34.3, - 35.2, - 36.1, - 37, - 38.4, - 40.9, - 41.8, - 41.7, - 41.7, - 41.7, - 41.6, - 40.8, - 39.9, - 37.8, - 36.4, - 35.6, - 35.1, - 35, - 35.7, - 36.7, - 38.4, - 39.7, - 41.6, - 42.1, - 42.1, - 41.9, - 41.3, - 41.5, - 42.1, - 42.7, - 43.7, - 44.1, - 44.1, - 43.9, - 43.6, - 43.3, - 43.1, - 43.3, - 43.4, - 43.2, - 42.9, - 42.3, - 41.5, - 40.9, - 40, - 38.6, - 37.2, - 36.2, - 35.4, - 34.2, - 33, - 31.5, - 30.2, - 29, - 27.8, - 28.1, - 28.4, - 27.9, - 27.7, - 27.5, - 27, - 26.7, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.2, - 26, - 25.6, - 25.5, - 25.4, - 25, - 24.5, - 24, - 23.7, - 23.5, - 23.3, - 22.9, - 22.6, - 22.3, - 22.1, - 21.8, - 21.5, - 21.2, - 21.2, - 21.1, - 20.9, - 20.8, - 20.3, - 19.7, - 19.2, - 18.3, - 16.7, - 15.5, - 14.1, - 13.1, - 12.3, - 11.9, - 11.3, - 10.8, - 10.3, - 10, - 9.5, - 9.1, - 8.9, - 8.8, - 9, - 9, - 9.4, - 10.1, - 10.6, - 11.1, - 11.7, - 12.3, - 13.1, - 14.1, - 15, - 16.1, - 17.4, - 19.3, - 20.2, - 20.5, - 20.7, - 20.9, - 21.1, - 21.1, - 21.2, - 21.2, - 21.2, - 21.2, - 21.2, - 21.2, - 21.2, - 21.2, - 21.3, - 21.4, - 21.4, - 21.6, - 21.6, - 21.6, - 21.7, - 15.5, - 15.7, - 15.9, - 16.2, - 16.6, - 16.8, - 17, - 17.2, - 17.5, - 18, - 18.5, - 18.9, - 19, - 19, - 18.9, - 18.5, - 18.3, - 18.1, - 17.9, - 17.6, - 17.2, - 16.9, - 16.9, - 16.9, - 17, - 17.2, - 17.3, - 17.6, - 17.8, - 17.8, - 17.3, - 17.1, - 17.3, - 17.5, - 17.7, - 17.8, - 17.9, - 18, - 18.2, - 18.3, - 18.4, - 18.6, - 18.7, - 18.8, - 19, - 19.2, - 19.3, - 19.5, - 19.6, - 19.8, - 20, - 20.1, - 20.4, - 20.9, - 21.7, - 22.7, - 23, - 23.3, - 23.4, - 23.4, - 23.3, - 23.4, - 23.2, - 22.9, - 23.1, - 23.8, - 24.3, - 24.3, - 24.4, - 24.9, - 25.3, - 25.5, - 25.7, - 25.9, - 26.1, - 26.3, - 26.5, - 26.4, - 27.6, - 28.6, - 28.5, - 26.8, - 27.4, - 27.7, - 27.5, - 26.7, - 25.8, - 25.5, - 25.9, - 26.5, - 27.4, - 27.8, - 27.9, - 27.9, - 27.8, - 27.5, - 27.2, - 27.2, - 27.7, - 28.2, - 28.5, - 29.1, - 29.5, - 29.6, - 29.8, - 30.2, - 30.4, - 30.9, - 30.8, - 31.1, - 31.6, - 32.2, - 32.9, - 33.6, - 34.3, - 35.2, - 36.3, - 37.2, - 39.3, - 40.9, - 40.7, - 39.3, - 39, - 39.3, - 39.4, - 38, - 37.4, - 36.6, - 35.6, - 34.6, - 34, - 34.1, - 34.8, - 35.6, - 36.4, - 37.5, - 39, - 40.2, - 40.7, - 40.8, - 40.2, - 41.1, - 42, - 42.6, - 43.4, - 43.8, - 43.8, - 43.6, - 43.2, - 42.9, - 42.7, - 42.9, - 42.9, - 42.7, - 42.1, - 41.5, - 40.9, - 40.3, - 39.2, - 38.1, - 37, - 36.1, - 35.3, - 34.1, - 32.9, - 31.1, - 30.1, - 29, - 27.7, - 27.3, - 27.4, - 27.1, - 26.7, - 26.4, - 26.2, - 26.1, - 26.1, - 26.1, - 26.1, - 26.2, - 26.2, - 26.2, - 25.7, - 25.3, - 25, - 24.8, - 24.6, - 24.4, - 24.1, - 23.9, - 23.7, - 23.6, - 23.3, - 22.9, - 22.6, - 22.4, - 22.2, - 21.9, - 21.4, - 21.2, - 21.1, - 21, - 20.9, - 20.7, - 20.1, - 19.6, - 19.1, - 17.5, - 16.3, - 14.8, - 13.9, - 13, - 12.5, - 11.9, - 11.5, - 11.1, - 10.6, - 10.2, - 9.8, - 9.3, - 8.8, - 8.5, - 8.7, - 8.8, - 9.3, - 9.8, - 10.3, - 10.7, - 11.2, - 11.8, - 12.5, - 13.4, - 14.2, - 15.1, - 16.2, - 17.8, - 19.6, - 20.2, - 20.4, - 20.6, - 20.7, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 20.9, - 20.9, - 21, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 15.4, - 15.5, - 15.6, - 15.8, - 16, - 16.2, - 16.4, - 16.7, - 17, - 17.4, - 18, - 18.5, - 18.6, - 18.5, - 18.2, - 17.8, - 17.6, - 17.3, - 17.2, - 16.9, - 16.7, - 16.6, - 16.6, - 16.7, - 16.8, - 16.9, - 17, - 17.2, - 17.3, - 17.1, - 17, - 17.1, - 17.3, - 17.5, - 17.6, - 17.7, - 17.8, - 18.1, - 18.2, - 18.2, - 18.3, - 18.6, - 18.9, - 19, - 19.2, - 19.5, - 19.8, - 19.9, - 20, - 20.3, - 20.4, - 20.7, - 21.1, - 21.8, - 22.8, - 23.5, - 23.8, - 24, - 24.1, - 24.1, - 24.1, - 24.1, - 24, - 23.9, - 23.9, - 24.2, - 24.6, - 24.8, - 24.9, - 25.1, - 25.4, - 25.6, - 25.8, - 25.9, - 26.1, - 26.3, - 26.5, - 26.5, - 26.7, - 27.6, - 26.9, - 27.1, - 27.4, - 27.5, - 27.1, - 26.1, - 25.3, - 26, - 26.8, - 27.1, - 27.4, - 27.7, - 27.9, - 27.7, - 27.3, - 27, - 26.9, - 27.4, - 28, - 28.5, - 28.8, - 29.2, - 29.3, - 29.1, - 29.1, - 29.5, - 29.3, - 29.9, - 30.2, - 30.5, - 31.2, - 32.2, - 32.8, - 33.5, - 34.1, - 35.1, - 36.1, - 36.9, - 38.4, - 39.4, - 38.1, - 37, - 36.8, - 36.7, - 36.5, - 36, - 35.9, - 35.3, - 34.6, - 33.8, - 33.2, - 33.5, - 34.1, - 34.8, - 35.6, - 36.4, - 37.1, - 37.8, - 37.4, - 37.7, - 38, - 39.3, - 41.4, - 42.2, - 43.1, - 43.5, - 43.4, - 43.2, - 42.8, - 42.3, - 42, - 42.4, - 42.3, - 41.8, - 41.1, - 40.7, - 40, - 39, - 38, - 37.4, - 36.6, - 36, - 35.1, - 34, - 32.9, - 30.9, - 29.9, - 28.8, - 27.6, - 26.8, - 26.8, - 26.6, - 26.3, - 26.1, - 25.9, - 25.9, - 25.9, - 25.9, - 26, - 26.1, - 26.1, - 25.9, - 25.2, - 24.6, - 24.3, - 24.1, - 24.1, - 24, - 23.9, - 23.8, - 23.7, - 23.6, - 23.3, - 23.1, - 22.8, - 22.4, - 22.3, - 22.1, - 21.6, - 21.2, - 21, - 20.9, - 20.8, - 20.5, - 19.9, - 19.3, - 18.6, - 17.3, - 16.2, - 15, - 14.2, - 13.4, - 13, - 12.4, - 11.9, - 11.5, - 11.1, - 10.5, - 10, - 9.6, - 9, - 8.5, - 8.4, - 8.6, - 9, - 9.4, - 9.8, - 10.3, - 10.8, - 11.4, - 12, - 12.7, - 13.5, - 14.3, - 15.3, - 16.3, - 17.9, - 19.4, - 20.2, - 20.3, - 20.4, - 20.4, - 20.5, - 20.5, - 20.4, - 20.4, - 20.3, - 20.3, - 20.4, - 20.5, - 20.5, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 15.2, - 15.3, - 15.4, - 15.5, - 15.7, - 15.8, - 16.1, - 16.3, - 16.5, - 16.8, - 17.2, - 17.8, - 17.9, - 17.6, - 17.3, - 17.1, - 17, - 16.7, - 16.6, - 16.5, - 16.4, - 16.4, - 16.5, - 16.6, - 16.7, - 16.8, - 16.9, - 17.1, - 17.1, - 16.9, - 17, - 17.2, - 17.3, - 17.4, - 17.6, - 17.7, - 17.9, - 18.1, - 18.1, - 18.1, - 18.5, - 18.7, - 18.9, - 19.2, - 19.5, - 20, - 20.5, - 20.6, - 20.8, - 21.2, - 21.5, - 22, - 22.5, - 22.9, - 23.4, - 23.6, - 23.9, - 24.2, - 24.3, - 24.4, - 24.3, - 24.3, - 24.3, - 24.3, - 24.3, - 24.5, - 24.7, - 24.9, - 25, - 25.2, - 25.4, - 25.6, - 25.7, - 25.8, - 25.9, - 26.1, - 26.4, - 26.7, - 26.8, - 26.9, - 27.2, - 27.4, - 27.5, - 27.4, - 26.8, - 25.9, - 25, - 25.8, - 26.9, - 27.5, - 27.6, - 27.7, - 27.5, - 27, - 26.7, - 26.7, - 27, - 27.7, - 28.1, - 28.5, - 28.9, - 29.1, - 28.9, - 28.4, - 28.1, - 28.2, - 28.3, - 29.3, - 30, - 30.5, - 31.4, - 32.1, - 32.6, - 33.2, - 33.9, - 34.7, - 35.5, - 36.1, - 36.9, - 36.7, - 36.1, - 35.6, - 35.3, - 35.1, - 35, - 34.7, - 34.7, - 34.2, - 33.7, - 33.2, - 32.6, - 33, - 33.5, - 34.4, - 35, - 35.6, - 36.1, - 36.1, - 35.5, - 36.3, - 37.3, - 37.9, - 40.2, - 41.6, - 42.6, - 43.1, - 42.9, - 42.6, - 42.3, - 41.5, - 41.1, - 41.3, - 41.1, - 40.7, - 40.4, - 39.7, - 38.6, - 37.8, - 37, - 36.5, - 36.1, - 35.7, - 34.8, - 33.7, - 32.7, - 31.1, - 30.2, - 29.2, - 27.9, - 26.9, - 26.5, - 26.4, - 26, - 25.9, - 25.8, - 25.8, - 25.7, - 25.6, - 25.7, - 25.8, - 25.8, - 25.6, - 25, - 24.3, - 23.9, - 23.7, - 23.7, - 23.7, - 23.8, - 23.7, - 23.7, - 23.6, - 23.4, - 23.2, - 22.8, - 22.5, - 22.3, - 22.2, - 21.6, - 21.2, - 21, - 20.8, - 20.7, - 20.4, - 19.8, - 19.1, - 18.2, - 17.2, - 16.2, - 15.2, - 14.4, - 13.6, - 13.1, - 12.6, - 12.1, - 11.8, - 11.3, - 10.8, - 10.2, - 9.7, - 9.2, - 8.7, - 8.3, - 8.3, - 8.6, - 8.9, - 9.3, - 9.9, - 10.5, - 11.1, - 11.6, - 12.2, - 12.9, - 13.7, - 14.5, - 15.3, - 16.6, - 18.3, - 19.8, - 20.1, - 20.1, - 20.1, - 20.1, - 20, - 19.8, - 19.6, - 19.5, - 19.5, - 19.6, - 19.8, - 19.9, - 20, - 20, - 20.1, - 20.3, - 20.5, - 20.6, - 15, - 15.1, - 15.2, - 15.3, - 15.4, - 15.6, - 15.8, - 16, - 16.1, - 16.3, - 16.5, - 16.8, - 16.8, - 16.8, - 16.7, - 16.5, - 16.5, - 16.3, - 16.2, - 16.2, - 16.4, - 16.6, - 16.7, - 16.7, - 16.8, - 16.9, - 17, - 17, - 16.8, - 16.8, - 17.1, - 17.2, - 17.3, - 17.5, - 17.6, - 17.8, - 17.9, - 17.9, - 18, - 18.4, - 18.6, - 18.7, - 18.9, - 19.2, - 19.7, - 20.4, - 21.2, - 21.4, - 21.7, - 22.3, - 22.7, - 23.1, - 23.3, - 23.4, - 23.5, - 23.7, - 24, - 24.2, - 24.3, - 24.3, - 24.3, - 24.3, - 24.3, - 24.4, - 24.5, - 24.7, - 24.8, - 24.9, - 24.9, - 25, - 25.2, - 25.3, - 25.4, - 25.5, - 25.7, - 26, - 26.3, - 26.6, - 26.9, - 27, - 27.3, - 27.4, - 27.4, - 27.3, - 26.7, - 25.9, - 24.9, - 25.2, - 26.4, - 27.4, - 27.6, - 27.3, - 26.6, - 26.5, - 26.6, - 26.9, - 27.4, - 27.8, - 28.2, - 28.4, - 28.7, - 28.7, - 28.3, - 27.6, - 27.5, - 27.7, - 28.2, - 29.7, - 30, - 30.4, - 31, - 31.4, - 32.2, - 32.9, - 33.5, - 34.2, - 34.9, - 35.5, - 36, - 35.6, - 34.9, - 34.3, - 34, - 33.8, - 33.8, - 33.6, - 33.7, - 33.4, - 33, - 32.3, - 31.7, - 32.3, - 33.2, - 34, - 34.7, - 35, - 35.1, - 34.9, - 34.7, - 35.3, - 36.1, - 37.1, - 39.3, - 41.2, - 42.1, - 42.6, - 42.3, - 41.9, - 41.5, - 41, - 40.7, - 40.6, - 40.4, - 40.2, - 39.8, - 38.7, - 37.5, - 36.6, - 36.1, - 35.8, - 35.5, - 35, - 34.3, - 33.4, - 32.6, - 31.2, - 30.2, - 29.2, - 28.1, - 27, - 26.5, - 26.1, - 25.9, - 25.8, - 25.7, - 25.5, - 25.1, - 24.7, - 24.9, - 25.3, - 25.2, - 25.1, - 24.8, - 24.2, - 23.7, - 23.5, - 23.5, - 23.6, - 23.6, - 23.7, - 23.6, - 23.6, - 23.5, - 23.3, - 22.9, - 22.5, - 22.3, - 22.2, - 21.6, - 21.2, - 21, - 20.9, - 20.8, - 20.5, - 19.8, - 19.3, - 18.5, - 17.3, - 16.5, - 15.5, - 14.4, - 13.6, - 13.1, - 12.7, - 12.6, - 12.2, - 11.6, - 11.1, - 10.4, - 9.9, - 9.4, - 8.8, - 8.3, - 8, - 8.2, - 8.6, - 9, - 9.5, - 10.1, - 10.6, - 11.2, - 11.7, - 12.4, - 13, - 13.7, - 14.5, - 15.5, - 17, - 18.5, - 19.5, - 19.7, - 19.5, - 19.3, - 19.1, - 18.9, - 18.8, - 18.7, - 18.8, - 18.9, - 19, - 19.1, - 19.2, - 19.4, - 19.6, - 19.7, - 20, - 20.1, - 14.8, - 14.9, - 15, - 15.1, - 15.2, - 15.3, - 15.5, - 15.7, - 15.8, - 15.9, - 15.9, - 16.1, - 16.1, - 16.1, - 16.2, - 16.1, - 16.1, - 16, - 16.2, - 16.6, - 17, - 17.3, - 17.3, - 17.3, - 17, - 16.9, - 16.9, - 16.8, - 16.7, - 16.9, - 17.1, - 17.3, - 17.4, - 17.5, - 17.6, - 17.7, - 17.7, - 17.7, - 18, - 18.3, - 18.5, - 18.7, - 18.9, - 19.3, - 19.6, - 20.2, - 20.9, - 21.4, - 22, - 22.7, - 23.2, - 23.4, - 23.4, - 23.5, - 23.6, - 23.7, - 23.9, - 24.1, - 24.2, - 24.2, - 24.2, - 24.2, - 24.3, - 24.4, - 24.5, - 24.7, - 24.7, - 24.7, - 24.7, - 24.6, - 24.7, - 24.9, - 25, - 25.2, - 25.5, - 25.9, - 26.3, - 26.6, - 26.9, - 27.1, - 27.2, - 27.3, - 27.4, - 27.3, - 26.8, - 26, - 25, - 24.8, - 26.2, - 27.2, - 27.2, - 26.4, - 26.3, - 26.7, - 27.2, - 27.4, - 27.8, - 27.9, - 28.1, - 28.3, - 28.5, - 28.3, - 27.4, - 27, - 27.6, - 28.2, - 28.7, - 29.9, - 30, - 30.2, - 30.5, - 30.9, - 31.7, - 32.5, - 33, - 33.6, - 34.4, - 35, - 35.3, - 34.6, - 33.9, - 33.3, - 33, - 33, - 33, - 32.8, - 32.9, - 32.6, - 32, - 31.4, - 31.1, - 31.9, - 32.9, - 33.7, - 34.4, - 34.5, - 34.5, - 34, - 34.1, - 34.7, - 35.6, - 36.9, - 39.1, - 40.6, - 41.6, - 42.1, - 41.9, - 41.2, - 40.8, - 40.6, - 40.6, - 40.5, - 40.4, - 40.2, - 39.3, - 37.9, - 36.6, - 35.8, - 35.3, - 34.8, - 34.4, - 33.9, - 33.5, - 32.9, - 32, - 30.7, - 29.9, - 29.1, - 28.2, - 27.1, - 26.4, - 26.1, - 25.9, - 25.8, - 25.7, - 25.3, - 24.5, - 24.1, - 24.1, - 24.4, - 24.5, - 24.5, - 24.3, - 23.9, - 23.7, - 23.5, - 23.3, - 23.3, - 23.5, - 23.6, - 23.6, - 23.6, - 23.5, - 23.2, - 22.8, - 22.5, - 22.3, - 22, - 21.6, - 21.2, - 21.1, - 20.9, - 20.9, - 20.6, - 19.9, - 19.4, - 18.8, - 17.7, - 16.7, - 15.9, - 14.8, - 14, - 13.4, - 13.2, - 13, - 12.6, - 12.2, - 11.5, - 10.9, - 10.2, - 9.5, - 8.8, - 8.4, - 7.9, - 7.9, - 8.3, - 8.7, - 9.1, - 9.6, - 10.3, - 10.8, - 11.3, - 11.8, - 12.4, - 13, - 13.9, - 14.7, - 15.5, - 16.9, - 17.9, - 18.7, - 18.6, - 18.4, - 18.2, - 18.1, - 17.8, - 17.8, - 17.8, - 17.9, - 18.1, - 18.1, - 18.2, - 18.5, - 18.7, - 18.8, - 19, - 19.3, - 14.5, - 14.7, - 14.9, - 14.9, - 14.9, - 15, - 15.2, - 15.4, - 15.4, - 15.5, - 15.5, - 15.6, - 15.7, - 15.8, - 15.8, - 15.8, - 15.8, - 16.1, - 16.6, - 17.4, - 17.9, - 18.1, - 18, - 17.7, - 17.1, - 16.8, - 16.7, - 16.6, - 16.7, - 16.9, - 17.1, - 17.2, - 17.3, - 17.4, - 17.5, - 17.5, - 17.5, - 17.6, - 17.8, - 18, - 18.3, - 18.5, - 18.9, - 19.4, - 19.6, - 19.9, - 20.4, - 20.9, - 21.6, - 22.5, - 23.1, - 23.4, - 23.4, - 23.4, - 23.5, - 23.6, - 23.7, - 23.9, - 23.9, - 24, - 24, - 24, - 24.2, - 24.3, - 24.5, - 24.5, - 24.4, - 24.3, - 24.1, - 24, - 24.1, - 24.4, - 24.7, - 25.2, - 25.7, - 26.1, - 26.4, - 26.7, - 26.9, - 27.1, - 27.2, - 27.3, - 27.3, - 27.2, - 26.8, - 26, - 25, - 24.7, - 26.2, - 27.2, - 26.7, - 25.8, - 26.1, - 26.7, - 27.4, - 27.7, - 27.9, - 28, - 28.2, - 28.3, - 28.3, - 27.9, - 26.8, - 27.5, - 28.2, - 28.6, - 29.3, - 29.8, - 29.8, - 29.9, - 30.1, - 30.5, - 31.2, - 32.2, - 32.5, - 33.1, - 33.7, - 34.2, - 34.3, - 33.8, - 33.1, - 32.4, - 32, - 32.1, - 32.1, - 31.9, - 32, - 31.8, - 31.1, - 30.5, - 30.9, - 31.8, - 32.7, - 33.5, - 33.8, - 33.6, - 33.6, - 33.3, - 33.7, - 34.5, - 35.7, - 36.8, - 38.4, - 39.9, - 40.9, - 41.4, - 41.4, - 40.8, - 40.4, - 40.3, - 40.5, - 40.5, - 40.5, - 40.3, - 39.5, - 37.9, - 36.5, - 35.5, - 34.5, - 33.7, - 33.2, - 32.9, - 32.5, - 31.8, - 30.8, - 30, - 29.3, - 28.5, - 27.7, - 26.6, - 26.2, - 26, - 25.9, - 25.9, - 25.8, - 25.5, - 24.8, - 24.3, - 23.9, - 23.7, - 23.8, - 23.9, - 23.9, - 23.7, - 23.6, - 23.4, - 23.3, - 23.1, - 23.3, - 23.4, - 23.5, - 23.5, - 23.4, - 23.1, - 22.7, - 22.4, - 22.2, - 21.9, - 21.5, - 21.2, - 21.1, - 21, - 20.9, - 20.6, - 20, - 19.4, - 18.8, - 17.8, - 16.9, - 16.2, - 15.2, - 14.3, - 13.8, - 13.6, - 13.3, - 13, - 12.7, - 12.1, - 11.3, - 10.6, - 9.8, - 9.1, - 8.6, - 8, - 7.7, - 8, - 8.4, - 8.8, - 9.3, - 9.8, - 10.4, - 10.9, - 11.3, - 11.9, - 12.5, - 13.2, - 13.8, - 14.6, - 15.4, - 16, - 16.7, - 16.9, - 17, - 16.8, - 16.6, - 16, - 16, - 16.1, - 16.6, - 16.6, - 16.8, - 17.1, - 17.2, - 17.6, - 17.8, - 18, - 18.3, - 14.5, - 14.6, - 14.7, - 14.7, - 14.8, - 14.9, - 15, - 15.1, - 15.2, - 15.2, - 15.3, - 15.4, - 15.5, - 15.6, - 15.6, - 15.7, - 16, - 16.5, - 17.3, - 18.2, - 18.5, - 18.5, - 18.2, - 17.6, - 17, - 16.7, - 16.6, - 16.6, - 16.7, - 16.9, - 17, - 17.2, - 17.3, - 17.3, - 17.4, - 17.4, - 17.4, - 17.5, - 17.7, - 18, - 18.3, - 18.6, - 19, - 19.5, - 19.7, - 20, - 20.3, - 20.6, - 21.2, - 21.9, - 22.8, - 23.2, - 23.2, - 23.2, - 23.2, - 23.3, - 23.4, - 23.8, - 23.8, - 23.8, - 23.8, - 23.6, - 23.8, - 24, - 24.2, - 24.2, - 24, - 23.7, - 23.5, - 23.7, - 24.2, - 24.5, - 24.8, - 25.4, - 25.9, - 26.2, - 26.4, - 26.7, - 26.9, - 27.1, - 27.2, - 27.2, - 27.1, - 26.8, - 26.2, - 25.5, - 24.7, - 24.8, - 26.4, - 27.3, - 26.8, - 25.5, - 25.9, - 26.6, - 27.2, - 27.7, - 27.9, - 28, - 28.1, - 28.1, - 27.9, - 27.3, - 26.6, - 27.6, - 28.4, - 28.8, - 29.3, - 29.4, - 29.5, - 29.7, - 30, - 30.3, - 30.9, - 31.8, - 32.2, - 32.7, - 33.2, - 33.5, - 33.6, - 33.1, - 32.3, - 31.4, - 31, - 31.1, - 31.1, - 31.1, - 31.2, - 31, - 30.4, - 29.8, - 30.7, - 31.6, - 32.4, - 32.9, - 32.8, - 32.5, - 32.3, - 32.5, - 33.3, - 34.4, - 35.6, - 36.6, - 37.8, - 39.4, - 40.3, - 40.6, - 40.7, - 40.5, - 40.1, - 40.1, - 40.3, - 40.4, - 40.5, - 40.3, - 39.7, - 38.2, - 36.7, - 35.5, - 34.2, - 33.1, - 32.2, - 31.4, - 30.8, - 30.2, - 29.7, - 29.1, - 28.4, - 27.7, - 26.8, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 25.7, - 25.4, - 24.8, - 24.2, - 23.9, - 23.7, - 23.5, - 23.6, - 23.7, - 23.6, - 23.5, - 23.4, - 23.2, - 23, - 23, - 23.1, - 23.3, - 23.3, - 23.1, - 22.8, - 22.6, - 22.4, - 22.2, - 21.9, - 21.4, - 21.2, - 21.1, - 21, - 20.9, - 20.6, - 20, - 19.4, - 18.9, - 18, - 17, - 16.3, - 15.4, - 14.6, - 14.1, - 13.7, - 13.3, - 13.2, - 12.9, - 12.3, - 11.5, - 10.8, - 10.1, - 9.4, - 8.8, - 8.2, - 7.6, - 7.8, - 8.2, - 8.6, - 9, - 9.4, - 9.8, - 10.3, - 10.8, - 11.3, - 11.9, - 12.5, - 13.1, - 13.7, - 14.3, - 14.7, - 15, - 15.2, - 15.2, - 15.3, - 15, - 14.8, - 14.7, - 14.9, - 15.2, - 15.3, - 15.4, - 15.6, - 15.6, - 16.3, - 16.4, - 16.5, - 17, - 14.6, - 14.7, - 14.8, - 14.9, - 15, - 15.1, - 15.2, - 15.2, - 15.3, - 15.3, - 15.4, - 15.4, - 15.5, - 15.6, - 15.7, - 15.9, - 16.3, - 16.9, - 17.8, - 18.5, - 18.7, - 18.5, - 17.9, - 17.2, - 16.8, - 16.6, - 16.5, - 16.5, - 16.7, - 16.9, - 17.1, - 17.1, - 17.2, - 17.2, - 17.3, - 17.2, - 17.4, - 17.7, - 17.8, - 18.1, - 18.5, - 18.8, - 19.2, - 19.6, - 19.8, - 20.1, - 20.3, - 20.6, - 20.9, - 21.6, - 22.2, - 22.8, - 22.9, - 22.7, - 22.7, - 23, - 23.3, - 23.6, - 23.7, - 23.6, - 23.4, - 23, - 23, - 23.1, - 23.4, - 23.6, - 23.3, - 23, - 23.5, - 24.3, - 24.8, - 25, - 25.2, - 25.6, - 25.9, - 26.2, - 26.5, - 26.7, - 26.8, - 27, - 27, - 27, - 26.7, - 26, - 25.2, - 24.6, - 24.4, - 25.3, - 26.7, - 27.4, - 26.9, - 25.6, - 25.6, - 26.4, - 27, - 27.3, - 27.6, - 27.8, - 27.8, - 27.9, - 27.5, - 26.6, - 26.6, - 27.5, - 28.2, - 28.7, - 29, - 28.8, - 29.2, - 29.7, - 30, - 30.3, - 30.7, - 31.3, - 31.8, - 32.4, - 32.8, - 33, - 33.1, - 32.6, - 31.6, - 30.8, - 30.1, - 30.3, - 30.3, - 30.3, - 30.7, - 30.6, - 29.9, - 29.5, - 30.5, - 31.3, - 32, - 32, - 31.7, - 31.3, - 31.4, - 31.9, - 33, - 34.2, - 35.2, - 36.2, - 37.3, - 38.6, - 39.8, - 40, - 40.1, - 40, - 39.5, - 39.6, - 39.8, - 40.1, - 40.3, - 40.2, - 39.4, - 38, - 36.6, - 35.3, - 34, - 32.9, - 31.7, - 29.9, - 29.1, - 28.9, - 28.6, - 28.2, - 27.4, - 26.8, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.8, - 25.4, - 24.8, - 24.3, - 23.9, - 23.8, - 23.6, - 23.5, - 23.3, - 23.5, - 23.6, - 23.5, - 23.4, - 23.2, - 22.9, - 22.8, - 22.9, - 23, - 23, - 22.9, - 22.6, - 22.4, - 22.3, - 22.1, - 21.7, - 21.3, - 21.2, - 21, - 20.9, - 20.8, - 20.5, - 19.9, - 19.4, - 19, - 18.1, - 17.1, - 16.4, - 15.5, - 14.7, - 14.1, - 13.6, - 13.1, - 12.9, - 12.6, - 12.1, - 11.5, - 11, - 10.4, - 9.8, - 9.1, - 8.5, - 7.8, - 7.5, - 7.5, - 8.2, - 8.8, - 9, - 9.4, - 9.8, - 10.3, - 10.8, - 11.3, - 11.8, - 12.4, - 12.9, - 13.3, - 13.6, - 13.9, - 14, - 14, - 14.2, - 14, - 13.8, - 13.8, - 13.9, - 14.1, - 14.3, - 14.3, - 14.4, - 14.7, - 15.2, - 15.4, - 15.3, - 15.5, - 14.7, - 14.8, - 14.9, - 15, - 15.1, - 15.3, - 15.4, - 15.4, - 15.5, - 15.5, - 15.6, - 15.7, - 15.8, - 15.9, - 16, - 16.2, - 16.6, - 17.2, - 17.9, - 18.4, - 18.5, - 18, - 17.4, - 16.9, - 16.6, - 16.5, - 16.4, - 16.6, - 16.7, - 16.9, - 16.9, - 17, - 17, - 17.1, - 17.1, - 17.3, - 17.9, - 18.2, - 17.9, - 18.2, - 18.6, - 19, - 19.4, - 19.8, - 20, - 20.2, - 20.4, - 20.6, - 20.8, - 21.3, - 21.6, - 22, - 22.3, - 22.1, - 22.1, - 22.7, - 23.2, - 23.5, - 23.7, - 23.6, - 23.2, - 22.6, - 22.3, - 22.3, - 22.5, - 22.7, - 22.6, - 22.9, - 23.9, - 24.7, - 25.1, - 25.3, - 25.5, - 25.7, - 26, - 26.2, - 26.4, - 26.6, - 26.7, - 26.8, - 26.9, - 26.7, - 26.4, - 25.5, - 24.5, - 24.3, - 25.1, - 26, - 27, - 27.5, - 27, - 25.7, - 25.2, - 25.8, - 26.6, - 26.7, - 26.9, - 27.3, - 27.3, - 27.3, - 27, - 26.2, - 26.4, - 27.2, - 28, - 28.5, - 28.7, - 28.1, - 28.8, - 29.6, - 30, - 30.3, - 30.6, - 31.1, - 31.6, - 32.1, - 32.5, - 32.7, - 32.6, - 32.3, - 31.4, - 30.5, - 29.6, - 29.4, - 29.4, - 29.6, - 30.3, - 30.1, - 29.4, - 29.2, - 30.5, - 31, - 31.4, - 31.2, - 30.6, - 30.5, - 31.2, - 32.1, - 33, - 34.1, - 35, - 35.8, - 36.8, - 37.8, - 39.2, - 39.7, - 39.7, - 39.4, - 38.8, - 38.9, - 39.4, - 39.8, - 40, - 39.9, - 39, - 37.5, - 36.2, - 34.9, - 33.5, - 32.6, - 31.4, - 29.7, - 28.4, - 27.8, - 27.6, - 27.2, - 26.5, - 26.1, - 26, - 25.9, - 25.8, - 25.7, - 25.3, - 25.4, - 25.1, - 24.3, - 23.7, - 23.6, - 23.6, - 23.5, - 23.3, - 23.2, - 23.3, - 23.4, - 23.5, - 23.3, - 23.1, - 22.8, - 22.6, - 22.6, - 22.8, - 22.7, - 22.5, - 22.4, - 22.3, - 22.3, - 21.9, - 21.4, - 21.2, - 21.1, - 21, - 20.9, - 20.8, - 20.5, - 19.9, - 19.5, - 19.1, - 18.3, - 17.3, - 16.5, - 15.7, - 14.5, - 13.9, - 13.3, - 12.8, - 12.5, - 12.2, - 11.8, - 11.3, - 10.8, - 10.4, - 10, - 9.3, - 8.7, - 8.1, - 7.5, - 7.2, - 7.3, - 8.2, - 8.7, - 9.1, - 9.4, - 9.8, - 10.4, - 10.8, - 11.3, - 11.8, - 12.2, - 12.4, - 12.7, - 12.9, - 12.9, - 13.1, - 13.2, - 13.1, - 12.9, - 13.1, - 13.2, - 13.3, - 13.4, - 13.4, - 13.5, - 13.9, - 14.3, - 14.4, - 14.3, - 14.3, - 14.8, - 14.9, - 14.9, - 15, - 15.2, - 15.3, - 15.4, - 15.5, - 15.6, - 15.7, - 15.8, - 15.9, - 16, - 16.2, - 16.3, - 16.6, - 17, - 17.5, - 18, - 18.2, - 17.9, - 17.4, - 16.9, - 16.6, - 16.4, - 16.3, - 16.3, - 16.6, - 16.7, - 16.7, - 16.7, - 16.8, - 16.8, - 16.9, - 17, - 17.6, - 18.2, - 18.4, - 18.4, - 18.3, - 18.5, - 19.1, - 19.7, - 19.9, - 20, - 20.2, - 20.4, - 20.5, - 20.7, - 21, - 21.2, - 21.4, - 21.6, - 21.6, - 21.6, - 22.2, - 22.8, - 23.3, - 23.6, - 23.5, - 23.1, - 22.5, - 22, - 21.9, - 22, - 22.1, - 22.2, - 22.9, - 24, - 24.9, - 25.2, - 25.4, - 25.6, - 25.8, - 26, - 26.2, - 26.4, - 26.5, - 26.7, - 26.7, - 26.7, - 26.5, - 26, - 25, - 24.1, - 24.5, - 25.7, - 26.7, - 27.3, - 27.5, - 27, - 25.7, - 25, - 25.3, - 25.9, - 26.2, - 26.3, - 26.6, - 26.7, - 26.6, - 26.5, - 26.2, - 26.3, - 27.4, - 28.1, - 28.3, - 28, - 27.4, - 28.2, - 29.5, - 29.8, - 30.2, - 30.5, - 30.8, - 31.2, - 31.7, - 32.2, - 32.4, - 32.3, - 32, - 31.1, - 30.3, - 29.2, - 28.8, - 28.7, - 28.9, - 29.6, - 29.5, - 28.9, - 28.9, - 30.2, - 30.7, - 30.8, - 30.4, - 29.6, - 30.3, - 31.4, - 32.1, - 33.1, - 34, - 34.9, - 35.5, - 36.3, - 37.2, - 38.2, - 39.2, - 39.1, - 38.7, - 38.1, - 38.2, - 38.8, - 39.4, - 39.6, - 39.4, - 38.3, - 37, - 35.9, - 34.7, - 33.2, - 32.3, - 30.9, - 29.4, - 28.3, - 27.4, - 27, - 26.6, - 26.2, - 25.9, - 25.8, - 25.8, - 25.7, - 25.3, - 24.5, - 24.6, - 24.4, - 23.8, - 23.4, - 23.4, - 23.4, - 23.4, - 23.3, - 23.1, - 23, - 23.2, - 23.4, - 23.2, - 23, - 22.7, - 22.5, - 22.5, - 22.5, - 22.5, - 22.3, - 22.3, - 22.2, - 22.1, - 21.7, - 21.2, - 21.1, - 21, - 20.9, - 20.8, - 20.7, - 20.5, - 19.9, - 19.5, - 19.1, - 18.4, - 17.5, - 16.6, - 15.6, - 14.4, - 13.7, - 13.1, - 12.6, - 12.1, - 11.8, - 11.5, - 11, - 10.5, - 10.1, - 9.7, - 9.2, - 8.8, - 8.3, - 7.8, - 7.3, - 6.9, - 7.2, - 8.1, - 8.8, - 9, - 9.3, - 9.8, - 10.4, - 10.8, - 11.2, - 11.4, - 11.6, - 11.8, - 12, - 12.1, - 12.3, - 12.4, - 12.3, - 12.3, - 12.5, - 12.6, - 12.7, - 12.8, - 12.7, - 12.7, - 13, - 13.3, - 13.3, - 13.3, - 13.5, - 15.1, - 15.1, - 15.1, - 15.2, - 15.3, - 15.4, - 15.5, - 15.6, - 15.7, - 15.8, - 16, - 16.1, - 16.3, - 16.5, - 16.8, - 17.2, - 17.5, - 17.8, - 18, - 17.8, - 17.2, - 16.8, - 16.5, - 16.3, - 16.3, - 16.3, - 16.3, - 16.6, - 16.6, - 16.5, - 16.7, - 16.8, - 16.9, - 17, - 17.1, - 17.6, - 18.1, - 18.4, - 18.7, - 18.8, - 19.1, - 19.4, - 19.7, - 19.9, - 20.1, - 20.2, - 20.3, - 20.5, - 20.7, - 20.8, - 20.9, - 21, - 21.2, - 21.3, - 21.3, - 21.7, - 22.3, - 22.9, - 23.3, - 23.3, - 22.8, - 22.1, - 21.8, - 21.7, - 21.9, - 22.2, - 22.6, - 23.1, - 24.1, - 24.9, - 25.2, - 25.4, - 25.6, - 25.7, - 25.9, - 26.1, - 26.3, - 26.4, - 26.5, - 26.6, - 26.4, - 26.1, - 25.5, - 24.6, - 23.9, - 24.7, - 25.8, - 26.8, - 27.3, - 27.4, - 27.2, - 26, - 24.9, - 25, - 25.4, - 26.1, - 26, - 26, - 26, - 25.8, - 25.9, - 26, - 26.6, - 27.5, - 28.1, - 27.9, - 27, - 26.9, - 28, - 29.2, - 29.4, - 29.9, - 30.2, - 30.3, - 30.5, - 31, - 31.6, - 31.8, - 31.8, - 31.6, - 31, - 30.1, - 29.1, - 28.4, - 28.2, - 28.7, - 29.2, - 29.1, - 28.6, - 28.6, - 29.9, - 30.5, - 30.2, - 29.2, - 28.8, - 30.4, - 31.5, - 32.3, - 33.2, - 34.1, - 34.8, - 35.2, - 35.8, - 36.5, - 37.2, - 37.9, - 38.1, - 37.7, - 37.2, - 37.4, - 38, - 38.6, - 38.7, - 38.6, - 37.7, - 36.6, - 35.6, - 34.4, - 33, - 32.1, - 30.5, - 29.3, - 28.2, - 27.2, - 26.8, - 26.5, - 26, - 25.8, - 25.7, - 25.7, - 25.6, - 25, - 23.8, - 23.6, - 23.6, - 23.3, - 23.2, - 23.1, - 23.2, - 23.3, - 23.2, - 23, - 22.9, - 23, - 23.2, - 23.1, - 22.8, - 22.6, - 22.4, - 22.4, - 22.3, - 22.2, - 22.2, - 22.1, - 22, - 21.7, - 21.3, - 21, - 21, - 20.9, - 20.8, - 20.8, - 20.7, - 20.4, - 19.9, - 19.5, - 19.1, - 18.5, - 17.6, - 16.6, - 15.4, - 14.2, - 13.4, - 12.8, - 12.4, - 12, - 11.6, - 11.1, - 10.6, - 10.2, - 9.8, - 9.4, - 9, - 8.7, - 8.2, - 7.8, - 7.4, - 7, - 6.9, - 7.2, - 8, - 8.5, - 9, - 9.4, - 9.8, - 10.2, - 10.7, - 10.8, - 10.9, - 11.2, - 11.3, - 11.4, - 11.6, - 11.8, - 11.8, - 11.9, - 12, - 12, - 12.1, - 12.2, - 12.1, - 12.1, - 12.4, - 12.5, - 12.5, - 12.5, - 12.8, - 15.6, - 15.3, - 15.3, - 15.4, - 15.4, - 15.5, - 15.6, - 15.7, - 15.9, - 16, - 16.2, - 16.3, - 16.6, - 17, - 17.4, - 17.8, - 18, - 18.1, - 17.8, - 17.2, - 16.6, - 16.4, - 16.2, - 16.2, - 16.2, - 16.2, - 16.4, - 16.5, - 16.4, - 16.5, - 16.7, - 16.8, - 16.9, - 17, - 17.1, - 17.5, - 17.9, - 18.4, - 18.9, - 19.3, - 19.6, - 19.8, - 19.8, - 20, - 20.1, - 20.1, - 20.3, - 20.5, - 20.7, - 20.8, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.4, - 21.9, - 22.3, - 22.7, - 22.8, - 22.3, - 21.7, - 21.6, - 21.8, - 22.3, - 22.8, - 23.4, - 23.8, - 24.3, - 24.8, - 25.1, - 25.3, - 25.5, - 25.7, - 25.8, - 26, - 26.2, - 26.3, - 26.3, - 26.3, - 26, - 25.4, - 24.8, - 24, - 24, - 25, - 26, - 26.7, - 27, - 27.1, - 27.1, - 26.4, - 25.3, - 24.7, - 24.8, - 25.4, - 25.8, - 25.6, - 25.3, - 25.2, - 25.5, - 26.2, - 27, - 27.6, - 27.9, - 27.4, - 26.4, - 26.9, - 27.4, - 28.3, - 28.8, - 29.4, - 29.6, - 29.5, - 29.7, - 30.3, - 30.8, - 31, - 31, - 31, - 30.7, - 29.8, - 29, - 28.3, - 28, - 28.5, - 28.9, - 28.7, - 28.4, - 28.5, - 29.8, - 30.2, - 29.4, - 28.4, - 28.7, - 30.8, - 31.5, - 32.3, - 33.3, - 33.9, - 34.7, - 35.1, - 35.5, - 35.8, - 36.3, - 36.7, - 36.9, - 36.6, - 36.3, - 36.7, - 37.1, - 37.5, - 37.7, - 37.5, - 36.8, - 36.1, - 35.3, - 34, - 32.7, - 31.6, - 30.2, - 29.2, - 28.1, - 27.1, - 26.5, - 26.1, - 25.8, - 25.7, - 25.7, - 25.7, - 25.6, - 25, - 23.8, - 23.2, - 23.1, - 23, - 22.9, - 22.9, - 23, - 23.1, - 23.1, - 22.9, - 22.8, - 22.7, - 22.9, - 22.8, - 22.6, - 22.4, - 22.3, - 22.2, - 22.2, - 22.1, - 22, - 21.8, - 21.5, - 21.2, - 21.1, - 20.9, - 20.8, - 20.8, - 20.7, - 20.7, - 20.6, - 20.3, - 19.8, - 19.4, - 19.1, - 18.6, - 17.5, - 16.4, - 15, - 14, - 13.1, - 12.5, - 12.1, - 11.8, - 11.4, - 10.9, - 10.4, - 10, - 9.5, - 9.1, - 8.8, - 8.5, - 8, - 7.6, - 7.4, - 7.1, - 6.7, - 6.8, - 7.1, - 7.8, - 8.6, - 9.1, - 9.4, - 9.7, - 10.1, - 10.3, - 10.4, - 10.7, - 10.8, - 10.9, - 11.1, - 11.3, - 11.4, - 11.4, - 11.5, - 11.5, - 11.6, - 11.6, - 11.6, - 11.7, - 11.9, - 11.9, - 11.9, - 12, - 12.2, - 16.1, - 15.7, - 15.6, - 15.6, - 15.7, - 15.8, - 15.9, - 16, - 16.1, - 16.2, - 16.4, - 16.7, - 17, - 17.6, - 17.9, - 18.2, - 18.1, - 17.9, - 17.3, - 16.6, - 16.2, - 16.1, - 16.1, - 16.2, - 16.2, - 16.1, - 16.4, - 16.4, - 16.3, - 16.5, - 16.7, - 16.7, - 16.8, - 17, - 17.1, - 17.3, - 17.8, - 18.5, - 19.3, - 19.5, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.5, - 20.6, - 20.7, - 20.8, - 20.8, - 20.9, - 21, - 21.1, - 21.3, - 21.6, - 21.9, - 22.1, - 22.2, - 21.9, - 21.5, - 21.6, - 21.9, - 22.6, - 23.5, - 23.9, - 24.2, - 24.5, - 24.7, - 25, - 25.2, - 25.4, - 25.6, - 25.8, - 25.9, - 26, - 26.1, - 26.1, - 26, - 25.4, - 24.7, - 24.1, - 23.7, - 24.3, - 25.2, - 25.9, - 26.3, - 26.5, - 26.5, - 26.6, - 26.4, - 25.7, - 24.8, - 24.5, - 25, - 25.3, - 25, - 25, - 25.3, - 26, - 26.8, - 27.5, - 27.6, - 27.5, - 27, - 26.2, - 26.5, - 26.6, - 26.9, - 27.7, - 28.6, - 28.5, - 28.3, - 28.6, - 29.5, - 30, - 30.1, - 30.2, - 30.4, - 30.3, - 29.7, - 29, - 28.4, - 27.8, - 28.2, - 28.4, - 28.2, - 28.1, - 28.7, - 29.8, - 29.6, - 28.7, - 27.9, - 28.9, - 30.8, - 31.4, - 32.1, - 33, - 33.7, - 34.4, - 34.9, - 35.2, - 35.4, - 35.7, - 35.9, - 36, - 35.7, - 35.7, - 36, - 36.3, - 36.5, - 36.7, - 36.6, - 36.2, - 35.7, - 34.9, - 33.8, - 32.5, - 31.3, - 30, - 29.3, - 28.4, - 27.2, - 26.2, - 25.8, - 25.7, - 25.6, - 25.6, - 25.7, - 25.4, - 24.7, - 23.7, - 23, - 22.9, - 22.8, - 22.7, - 22.7, - 22.9, - 22.9, - 22.9, - 22.9, - 22.8, - 22.6, - 22.5, - 22.5, - 22.4, - 22.3, - 22.2, - 22.1, - 22, - 22, - 21.8, - 21.4, - 21.1, - 21, - 20.8, - 20.7, - 20.7, - 20.7, - 20.6, - 20.5, - 20.3, - 20, - 19.6, - 19.3, - 19.1, - 18.4, - 17.3, - 15.9, - 14.5, - 13.6, - 12.7, - 12.1, - 11.8, - 11.5, - 11.2, - 10.7, - 10.2, - 9.9, - 9.3, - 8.9, - 8.6, - 8.2, - 7.7, - 7.3, - 7.1, - 7.1, - 6.7, - 6.7, - 6.8, - 7.3, - 7.9, - 8.6, - 9, - 9.3, - 9.6, - 9.7, - 9.8, - 10, - 10.2, - 10.4, - 10.7, - 10.9, - 11.1, - 11, - 11.1, - 11.1, - 11.1, - 11.1, - 11.1, - 11.2, - 11.3, - 11.4, - 11.5, - 11.5, - 11.7, - 16.5, - 16.3, - 16.1, - 16.1, - 16.2, - 16.3, - 16.3, - 16.3, - 16.4, - 16.5, - 16.8, - 17.1, - 17.4, - 17.8, - 18, - 18.1, - 17.9, - 17.4, - 16.8, - 16.2, - 16, - 16, - 16, - 16.1, - 16.1, - 16.1, - 16.2, - 16.2, - 16.3, - 16.5, - 16.6, - 16.7, - 17, - 17.3, - 17.3, - 17.4, - 17.7, - 18.4, - 19.3, - 19.6, - 19.8, - 19.9, - 20.1, - 20.1, - 20.3, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.4, - 21.6, - 21.7, - 21.7, - 21.6, - 21.4, - 21.5, - 21.9, - 22.8, - 23.7, - 23.9, - 24.1, - 24.4, - 24.7, - 24.9, - 25.2, - 25.3, - 25.5, - 25.7, - 25.8, - 25.9, - 25.9, - 25.8, - 25.6, - 24.8, - 24, - 23.6, - 23.5, - 24.2, - 24.7, - 25.2, - 25.6, - 25.8, - 25.7, - 25.9, - 25.9, - 25.6, - 25, - 24.3, - 24.5, - 24.7, - 24.8, - 25.4, - 25.8, - 26.3, - 27, - 27.5, - 27.4, - 27.1, - 26.6, - 26, - 26, - 26.5, - 26.9, - 27.4, - 27.8, - 27.9, - 27.4, - 27.8, - 28.5, - 29, - 29.2, - 29, - 29.7, - 29.6, - 29, - 28.6, - 28.2, - 27.6, - 27.8, - 27.8, - 27.8, - 28.1, - 28.9, - 29.3, - 29, - 28.2, - 27.7, - 28.5, - 30.2, - 31.2, - 31.8, - 32.5, - 33.2, - 34, - 34.5, - 34.9, - 35.1, - 35.3, - 35.4, - 35.4, - 35.2, - 35.2, - 35.5, - 35.7, - 35.9, - 36, - 36, - 35.8, - 35.4, - 34.6, - 33.5, - 32.4, - 31.1, - 29.5, - 28.7, - 28, - 26.9, - 26, - 25.6, - 25.6, - 25.5, - 25.6, - 25.6, - 25.3, - 24.3, - 23.4, - 22.9, - 22.8, - 22.8, - 22.6, - 22.6, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.4, - 22.2, - 22.3, - 22.2, - 22.1, - 22.1, - 21.9, - 21.7, - 21.4, - 21, - 20.9, - 20.8, - 20.7, - 20.6, - 20.6, - 20.6, - 20.4, - 20, - 19.7, - 19.6, - 19.3, - 19.1, - 18.9, - 18, - 16.8, - 15.2, - 14, - 13.2, - 12.4, - 11.9, - 11.5, - 11.2, - 10.9, - 10.4, - 10, - 9.6, - 9.2, - 8.8, - 8.4, - 7.8, - 7.4, - 7.1, - 7, - 6.9, - 6.7, - 6.5, - 6.6, - 6.9, - 7.3, - 7.8, - 8.5, - 9, - 9.2, - 9.3, - 9.4, - 9.4, - 9.5, - 9.9, - 10.1, - 10.3, - 10.4, - 10.4, - 10.5, - 10.5, - 10.5, - 10.4, - 10.4, - 10.7, - 10.9, - 11, - 11, - 11, - 11.2, - 17, - 16.8, - 16.7, - 16.8, - 17, - 17.1, - 17, - 17, - 17, - 17.1, - 17.2, - 17.4, - 17.6, - 17.8, - 17.8, - 17.6, - 17.3, - 16.8, - 16.3, - 15.9, - 15.9, - 16, - 16, - 16, - 16, - 16, - 16.1, - 16.2, - 16.3, - 16.5, - 16.6, - 17.1, - 17.4, - 17.8, - 17.9, - 18, - 17.8, - 18.2, - 19, - 19.4, - 19.7, - 19.8, - 20, - 20.1, - 20.2, - 20.3, - 20.5, - 20.5, - 20.6, - 20.7, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.5, - 21.5, - 21.4, - 21.3, - 21.5, - 21.9, - 22.8, - 23.5, - 23.6, - 24.1, - 24.6, - 24.9, - 25.1, - 25.2, - 25.4, - 25.5, - 25.6, - 25.7, - 25.7, - 25.7, - 25.6, - 25.2, - 24.4, - 23.6, - 23.3, - 23.3, - 23.6, - 23.9, - 24.2, - 24.6, - 24.9, - 24.9, - 24.9, - 25, - 25, - 24.6, - 24.2, - 24.4, - 24.9, - 25.4, - 25.9, - 26.6, - 26.8, - 27.1, - 27.4, - 27.3, - 26.9, - 26.3, - 25.7, - 26.2, - 26.9, - 27.2, - 27.4, - 27.5, - 27.4, - 26.9, - 27.2, - 27.8, - 28.4, - 28.5, - 28.1, - 28.2, - 28.3, - 28.1, - 27.9, - 27.6, - 27.3, - 27.4, - 27.5, - 27.7, - 28.4, - 28.9, - 29, - 28.6, - 27.8, - 27.4, - 28, - 29.7, - 31, - 31.5, - 32.2, - 32.8, - 33.6, - 34.1, - 34.5, - 34.8, - 35, - 35, - 34.9, - 34.6, - 34.6, - 34.9, - 35.3, - 35.5, - 35.6, - 35.7, - 35.5, - 35.1, - 34.3, - 33.1, - 32.1, - 30.9, - 29.5, - 28.1, - 27.1, - 26.4, - 25.8, - 25.5, - 25.5, - 25.4, - 25.5, - 25.2, - 24.6, - 23.8, - 23.1, - 22.8, - 22.7, - 22.6, - 22.5, - 22.5, - 22.6, - 22.7, - 22.7, - 22.7, - 22.8, - 22.7, - 22.4, - 22.2, - 22.1, - 22, - 22, - 21.9, - 21.6, - 21.2, - 20.9, - 20.8, - 20.8, - 20.7, - 20.6, - 20.5, - 20.5, - 20.2, - 19.8, - 19.4, - 19.3, - 19.2, - 19.1, - 18.9, - 18.6, - 17.2, - 15.9, - 14.5, - 13.6, - 12.8, - 12.2, - 11.6, - 11.3, - 11, - 10.6, - 10.2, - 9.9, - 9.4, - 9.1, - 8.7, - 8.2, - 7.4, - 7, - 6.9, - 6.9, - 6.8, - 6.6, - 6.4, - 6.4, - 6.7, - 6.9, - 7.3, - 7.8, - 8.4, - 8.8, - 8.9, - 9.1, - 9.1, - 9.2, - 9.5, - 9.6, - 9.6, - 9.8, - 9.9, - 9.9, - 9.8, - 9.9, - 9.8, - 9.9, - 10.1, - 10.2, - 10.3, - 10.4, - 10.5, - 10.7, - 17.3, - 17.3, - 17.4, - 17.5, - 17.7, - 17.8, - 17.9, - 17.8, - 17.8, - 17.8, - 17.7, - 17.7, - 17.6, - 17.5, - 17.2, - 17, - 16.6, - 16.3, - 15.9, - 15.8, - 15.9, - 15.9, - 15.9, - 15.9, - 15.9, - 16, - 16.1, - 16.2, - 16.5, - 16.8, - 17.1, - 17.6, - 18, - 18.3, - 18.6, - 18.9, - 18.6, - 18.3, - 18.8, - 19.3, - 19.6, - 19.8, - 20, - 20.2, - 20.3, - 20.4, - 20.5, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.4, - 21.4, - 21.4, - 21.3, - 21.3, - 21.6, - 21.9, - 22.6, - 23, - 23.6, - 24.3, - 24.7, - 25, - 25.2, - 25.3, - 25.4, - 25.5, - 25.5, - 25.5, - 25.5, - 25.5, - 25.3, - 24.8, - 24.1, - 23.3, - 23.1, - 23.1, - 23.2, - 23.4, - 23.6, - 23.7, - 24, - 24.1, - 24.1, - 24.2, - 24.3, - 24.2, - 24.2, - 24.7, - 25.8, - 26.4, - 26.5, - 27.1, - 27.4, - 27.5, - 27.5, - 27.3, - 26.9, - 26.2, - 25.5, - 26.4, - 27.1, - 27.1, - 27.2, - 27.3, - 27, - 26.6, - 26.9, - 27.6, - 28.1, - 28.1, - 27.6, - 27.3, - 27.5, - 27.4, - 27.2, - 27.1, - 27, - 27.1, - 27.4, - 28.1, - 28.6, - 28.8, - 28.8, - 28.2, - 27.5, - 27.2, - 28.1, - 29.4, - 30.7, - 31.3, - 31.9, - 32.5, - 33.2, - 33.7, - 34.1, - 34.3, - 34.5, - 34.5, - 34.3, - 34, - 34.1, - 34.4, - 34.8, - 35.2, - 35.3, - 35.4, - 35.3, - 34.9, - 34.1, - 32.9, - 31.7, - 30.1, - 29.4, - 28.1, - 26.8, - 26.1, - 25.6, - 25.4, - 25.3, - 25.3, - 25.1, - 24.5, - 23.7, - 23.1, - 22.8, - 22.7, - 22.6, - 22.4, - 22.4, - 22.3, - 22.4, - 22.5, - 22.5, - 22.5, - 22.7, - 22.5, - 22.3, - 22.2, - 22.1, - 21.9, - 21.8, - 21.6, - 21.2, - 20.9, - 20.7, - 20.7, - 20.6, - 20.5, - 20.5, - 20.4, - 20.2, - 19.8, - 19.3, - 18.9, - 18.8, - 18.8, - 18.8, - 18.6, - 17.7, - 16.1, - 14.8, - 13.9, - 13, - 12.3, - 11.8, - 11.4, - 11.1, - 10.8, - 10.4, - 10.1, - 9.8, - 9.3, - 9, - 8.6, - 7.9, - 7.1, - 6.8, - 6.8, - 6.7, - 6.6, - 6.4, - 6.3, - 6.3, - 6.4, - 6.6, - 7, - 7.4, - 7.9, - 8, - 8.2, - 8.3, - 8.4, - 8.6, - 8.9, - 9.2, - 9.2, - 9.3, - 9.4, - 9.5, - 9.5, - 9.5, - 9.4, - 9.4, - 9.6, - 9.7, - 9.8, - 9.9, - 10, - 10.1, - 17.6, - 17.6, - 17.8, - 17.9, - 18, - 18.1, - 18.1, - 18.1, - 18.1, - 18, - 17.9, - 17.6, - 17.2, - 17, - 16.7, - 16.4, - 16.2, - 15.9, - 15.7, - 15.8, - 15.8, - 15.8, - 15.8, - 15.8, - 15.9, - 16.1, - 16.2, - 16.3, - 16.7, - 17.4, - 17.8, - 18.1, - 18.6, - 19, - 19.2, - 19.4, - 19.4, - 19.1, - 18.9, - 19.4, - 19.6, - 19.8, - 20, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.3, - 21.3, - 21.3, - 21.2, - 21.2, - 21.3, - 21.5, - 21.7, - 22.3, - 22.7, - 23.7, - 24.5, - 24.8, - 25, - 25.2, - 25.3, - 25.3, - 25.3, - 25.3, - 25.3, - 25.3, - 25.2, - 25.1, - 24.6, - 23.7, - 23, - 22.9, - 23, - 23, - 23.2, - 23.3, - 23.4, - 23.5, - 23.6, - 23.7, - 23.9, - 24, - 24.3, - 24.9, - 25.6, - 26.6, - 27.3, - 27.3, - 27.4, - 27.6, - 27.6, - 27.5, - 27.2, - 26.6, - 25.9, - 25.4, - 26.3, - 26.9, - 27, - 27.1, - 27.2, - 26.7, - 26.3, - 26.9, - 27.6, - 27.9, - 27.7, - 27.2, - 26.9, - 26.9, - 26.9, - 26.8, - 26.8, - 26.9, - 27.3, - 27.9, - 28.5, - 28.6, - 28.6, - 28.4, - 27.9, - 27.3, - 27.1, - 27.8, - 28.7, - 29.9, - 31, - 31.7, - 32.2, - 32.8, - 33.4, - 33.6, - 33.8, - 33.9, - 33.9, - 33.4, - 33.2, - 33.4, - 34, - 34.4, - 34.8, - 35, - 35.2, - 35.2, - 34.8, - 33.9, - 32.9, - 31.6, - 29.6, - 28.6, - 27.7, - 26.7, - 26, - 25.5, - 25.4, - 25.2, - 24.9, - 24.2, - 23.5, - 23, - 22.7, - 22.6, - 22.5, - 22.4, - 22.2, - 22.2, - 22.1, - 22.3, - 22.3, - 22.3, - 22.4, - 22.4, - 22.3, - 22.2, - 22, - 22, - 21.9, - 21.7, - 21.2, - 21, - 20.8, - 20.6, - 20.6, - 20.5, - 20.4, - 20.3, - 20.2, - 19.8, - 19.4, - 19, - 18.4, - 18.1, - 18.1, - 17.9, - 17.3, - 16.2, - 14.9, - 13.9, - 13.1, - 12.5, - 11.9, - 11.4, - 11.1, - 10.8, - 10.6, - 10.2, - 10, - 9.7, - 9.3, - 8.9, - 8.6, - 7.9, - 7, - 6.6, - 6.7, - 6.6, - 6.5, - 6.3, - 6.1, - 6.1, - 6.1, - 6.4, - 6.7, - 7, - 7.3, - 7.4, - 7.5, - 7.5, - 7.7, - 7.9, - 8.1, - 8.3, - 8.4, - 8.4, - 8.8, - 8.9, - 9, - 8.8, - 8.7, - 8.8, - 9.2, - 9.3, - 9.4, - 9.6, - 9.7, - 9.9, - 17.7, - 17.8, - 18, - 18.1, - 18.1, - 18.1, - 18, - 18, - 17.9, - 17.7, - 17.4, - 17, - 16.7, - 16.5, - 16.2, - 16, - 15.8, - 15.6, - 15.7, - 15.7, - 15.7, - 15.7, - 15.7, - 15.8, - 15.9, - 16.1, - 16.3, - 16.5, - 17, - 17.7, - 18.2, - 18.7, - 19.2, - 19.4, - 19.5, - 19.6, - 19.6, - 19.6, - 19.3, - 19.4, - 19.6, - 19.8, - 20, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.3, - 21.3, - 21.2, - 21.1, - 21.2, - 21.3, - 21.4, - 21.6, - 21.9, - 22.6, - 23.7, - 24.5, - 24.9, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 24.9, - 24.7, - 24.5, - 23.9, - 23.2, - 22.7, - 22.8, - 23, - 23.2, - 23.4, - 23.7, - 23.7, - 23.8, - 23.9, - 24.1, - 24.3, - 24.7, - 25.5, - 26.2, - 26.7, - 27.3, - 27.6, - 27.6, - 27.7, - 27.7, - 27.7, - 27.5, - 26.9, - 26.2, - 25.4, - 25.4, - 26.3, - 26.7, - 26.7, - 26.9, - 27, - 26.5, - 26.1, - 26.8, - 27.3, - 27.6, - 27.3, - 26.9, - 26.5, - 26.5, - 26.5, - 26.7, - 27.1, - 27.5, - 27.7, - 28.2, - 28.5, - 28.6, - 28.4, - 28, - 27.5, - 26.9, - 27.1, - 27.9, - 28.6, - 29.1, - 30.2, - 31.3, - 31.8, - 32.4, - 32.8, - 33, - 33, - 33.1, - 32.9, - 32.4, - 32.3, - 32.8, - 33.5, - 34.1, - 34.5, - 34.7, - 34.9, - 34.9, - 34.7, - 33.9, - 32.9, - 31.6, - 30, - 29.1, - 28, - 26.9, - 26, - 25.5, - 25.4, - 25.2, - 24.9, - 23.8, - 23.2, - 23, - 22.8, - 22.6, - 22.5, - 22.4, - 22.2, - 22, - 21.9, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.1, - 22, - 21.9, - 21.9, - 21.8, - 21.5, - 21, - 20.8, - 20.8, - 20.6, - 20.5, - 20.4, - 20.4, - 20.1, - 19.7, - 19.3, - 19, - 18.6, - 18, - 17.4, - 17.1, - 16.8, - 16, - 14.9, - 14.1, - 13.2, - 12.4, - 12, - 11.5, - 11, - 10.8, - 10.5, - 10.3, - 10.1, - 9.9, - 9.5, - 9.2, - 8.8, - 8.5, - 7.7, - 6.7, - 6.5, - 6.5, - 6.5, - 6.3, - 6.2, - 6, - 5.8, - 5.8, - 6.1, - 6.5, - 6.7, - 6.9, - 7, - 7, - 7.2, - 7.3, - 7.4, - 7.5, - 7.6, - 7.6, - 7.6, - 7.8, - 7.8, - 8, - 7.9, - 7.9, - 8, - 8.3, - 8.5, - 8.8, - 9.2, - 9.4, - 9.6, - 17.8, - 17.9, - 18, - 18, - 17.9, - 17.7, - 17.5, - 17.4, - 17.2, - 17, - 16.7, - 16.4, - 16.2, - 16.1, - 15.8, - 15.7, - 15.6, - 15.6, - 15.6, - 15.6, - 15.7, - 15.6, - 15.7, - 15.7, - 16, - 16.2, - 16.5, - 16.8, - 17.2, - 17.7, - 18.3, - 19.1, - 19.4, - 19.5, - 19.6, - 19.7, - 19.7, - 19.7, - 19.6, - 19.6, - 19.6, - 19.7, - 20, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.2, - 21.2, - 21.3, - 21.3, - 21.3, - 21.2, - 21.1, - 21.1, - 21.2, - 21.3, - 21.5, - 21.6, - 22.1, - 23, - 24, - 24.6, - 24.8, - 25, - 25, - 25, - 24.9, - 24.9, - 24.9, - 24.6, - 24.3, - 23.9, - 23.5, - 23.1, - 22.6, - 22.7, - 22.9, - 23, - 23.4, - 24, - 24.7, - 24.8, - 24.7, - 24.8, - 25, - 25.4, - 25.8, - 26.5, - 26.9, - 27.3, - 27.5, - 27.7, - 27.7, - 27.8, - 27.8, - 27.7, - 27.2, - 26.6, - 26, - 25.1, - 25.6, - 26.2, - 26.4, - 26.4, - 26.5, - 26.5, - 26.1, - 25.9, - 26.6, - 26.9, - 26.9, - 26.8, - 26.4, - 26.2, - 26.3, - 26.6, - 27.3, - 27.9, - 28.2, - 28.2, - 28.2, - 28.4, - 28.5, - 28.3, - 27.8, - 27, - 26.7, - 27.4, - 28, - 28.5, - 29, - 29.6, - 30.9, - 31.4, - 31.8, - 32.2, - 32.3, - 32.4, - 32.4, - 32.2, - 31.6, - 31.7, - 32.2, - 32.9, - 33.7, - 34.2, - 34.5, - 34.7, - 34.7, - 34.5, - 34, - 32.9, - 31.8, - 30.6, - 29.8, - 28.7, - 27.3, - 26.1, - 25.6, - 25.4, - 25.3, - 25.1, - 24.4, - 23.9, - 23.6, - 23.2, - 22.9, - 22.7, - 22.5, - 22.3, - 22.1, - 21.8, - 21.9, - 22, - 22, - 22, - 22, - 21.9, - 21.9, - 21.8, - 21.8, - 21.7, - 21.2, - 20.8, - 20.6, - 20.7, - 20.6, - 20.4, - 20.3, - 20.1, - 19.8, - 19.3, - 19, - 18.6, - 18.2, - 17.4, - 16.7, - 16.2, - 15.5, - 14.5, - 13.9, - 13.2, - 12.5, - 12, - 11.4, - 11.1, - 10.8, - 10.6, - 10.4, - 10.2, - 10, - 9.7, - 9.2, - 8.9, - 8.6, - 8.3, - 7.3, - 6.4, - 6.3, - 6.3, - 6.3, - 6.2, - 6.1, - 5.7, - 5.1, - 5.1, - 5.7, - 6.2, - 6.4, - 6.6, - 6.7, - 6.9, - 7, - 7, - 7.1, - 7.1, - 7.1, - 7.1, - 7.1, - 7.1, - 7.1, - 7.2, - 7.3, - 7.3, - 7.4, - 7.5, - 7.7, - 8.1, - 8.5, - 8.7, - 9.2, - 17.9, - 18, - 18, - 17.9, - 17.6, - 17.2, - 16.8, - 16.7, - 16.5, - 16.4, - 16.1, - 16, - 15.8, - 15.8, - 15.6, - 15.5, - 15.5, - 15.5, - 15.5, - 15.6, - 15.6, - 15.5, - 15.6, - 15.8, - 16.1, - 16.4, - 17, - 17.5, - 17.7, - 18, - 18.5, - 19.1, - 19.5, - 19.7, - 19.8, - 19.9, - 19.9, - 19.9, - 19.8, - 19.8, - 19.8, - 19.8, - 20, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.3, - 21.2, - 21.1, - 21, - 21.1, - 21.2, - 21.3, - 21.5, - 22, - 22.8, - 23.7, - 24.4, - 24.7, - 24.8, - 24.9, - 24.8, - 24.8, - 24.7, - 24.7, - 24.5, - 24.1, - 23.5, - 23, - 22.8, - 22.6, - 22.5, - 22.7, - 22.7, - 22.9, - 23.4, - 24.3, - 25.2, - 25.6, - 25.7, - 25.7, - 25.9, - 26.2, - 26.5, - 26.8, - 27.1, - 27.2, - 27.4, - 27.6, - 27.7, - 27.7, - 27.7, - 27.5, - 27, - 26.4, - 25.8, - 24.9, - 25.3, - 25.9, - 26.1, - 25.8, - 25.6, - 25.5, - 25.4, - 25.6, - 26, - 26.1, - 26.1, - 26, - 25.9, - 26.1, - 26.8, - 27.5, - 28.2, - 28.7, - 28.8, - 28.7, - 28.5, - 28.5, - 28.5, - 28.2, - 27.5, - 26.6, - 26.9, - 28, - 28.7, - 28.9, - 28.7, - 28.7, - 29.7, - 30.6, - 31.2, - 31.6, - 31.8, - 31.8, - 31.7, - 31.5, - 30.8, - 31, - 31.8, - 32.5, - 33.2, - 33.9, - 34.2, - 34.4, - 34.5, - 34.4, - 34, - 32.9, - 31.8, - 30.5, - 29.6, - 28.5, - 27.3, - 26.1, - 25.6, - 25.5, - 25.4, - 25.3, - 25, - 24.9, - 24.5, - 23.9, - 23.2, - 22.8, - 22.6, - 22.5, - 22.3, - 22, - 21.7, - 21.8, - 21.9, - 21.9, - 21.8, - 21.8, - 21.8, - 21.7, - 21.6, - 21.4, - 20.9, - 20.7, - 20.5, - 20.6, - 20.5, - 20.3, - 20.1, - 19.7, - 19.4, - 19.1, - 18.8, - 18.4, - 17.7, - 16.8, - 15.8, - 14.8, - 14.2, - 13.5, - 13, - 12.5, - 12, - 11.6, - 11.1, - 10.8, - 10.6, - 10.4, - 10.2, - 10, - 9.9, - 9.5, - 9, - 8.7, - 8.5, - 7.8, - 6.9, - 6.2, - 6.1, - 6.1, - 6, - 6, - 6.1, - 5.7, - 4.9, - 4.8, - 4.9, - 5.4, - 5.7, - 6.2, - 6.5, - 6.7, - 6.7, - 6.8, - 6.8, - 6.8, - 6.8, - 6.6, - 6.5, - 6.4, - 6.5, - 6.7, - 6.8, - 6.9, - 7, - 7.1, - 7.2, - 7.4, - 7.7, - 8, - 8.7, - 17.9, - 18, - 17.9, - 17.7, - 17.1, - 16.6, - 16.3, - 16.2, - 16, - 15.9, - 15.8, - 15.7, - 15.6, - 15.5, - 15.4, - 15.4, - 15.5, - 15.5, - 15.5, - 15.5, - 15.5, - 15.6, - 15.8, - 16.1, - 16.3, - 16.8, - 17.6, - 18.3, - 18.6, - 18.7, - 18.9, - 19.3, - 19.6, - 19.9, - 20, - 20.1, - 20, - 20, - 20, - 20, - 19.9, - 20, - 20.1, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.2, - 21.1, - 21, - 20.9, - 21, - 21.1, - 21.2, - 21.4, - 21.7, - 22.5, - 23.5, - 24.2, - 24.5, - 24.7, - 24.7, - 24.7, - 24.6, - 24.5, - 24.4, - 24.2, - 24.1, - 23.6, - 22.9, - 22.5, - 22.4, - 22.4, - 22.5, - 22.6, - 22.6, - 22.8, - 23.3, - 24.1, - 25.1, - 25.7, - 25.8, - 25.9, - 26, - 26.3, - 26.5, - 26.8, - 27, - 27.1, - 27.3, - 27.4, - 27.5, - 27.5, - 27.5, - 27.1, - 26.7, - 25.9, - 25.1, - 24.8, - 25.6, - 26.1, - 26, - 25.1, - 24.9, - 25.1, - 25.3, - 25.4, - 25.4, - 25.5, - 25.6, - 25.9, - 26.3, - 26.9, - 27.7, - 28.4, - 28.9, - 29.2, - 29.2, - 29, - 28.8, - 28.7, - 28.5, - 27.9, - 27, - 26.4, - 27.4, - 28.4, - 28.8, - 28.8, - 28.2, - 27.9, - 28.4, - 29.1, - 29.8, - 31.1, - 31.3, - 31.2, - 31, - 30.4, - 29.5, - 29.7, - 31.3, - 32.1, - 32.9, - 33.6, - 34, - 34.2, - 34.3, - 34.3, - 33.9, - 32.9, - 31.9, - 30.4, - 29.4, - 28.4, - 27.3, - 26.1, - 25.7, - 25.6, - 25.5, - 25.5, - 25.3, - 25.2, - 25, - 24.2, - 23.4, - 23, - 22.7, - 22.6, - 22.5, - 22.3, - 21.7, - 21.7, - 21.7, - 21.7, - 21.7, - 21.7, - 21.7, - 21.6, - 21.2, - 21, - 20.8, - 20.6, - 20.4, - 20.4, - 20.3, - 20.2, - 19.9, - 19.4, - 19, - 18.7, - 18.5, - 18, - 16.5, - 15.5, - 14.4, - 13.7, - 13.4, - 12.9, - 12.3, - 11.9, - 11.6, - 11.3, - 10.9, - 10.6, - 10.3, - 10.1, - 9.9, - 9.7, - 9.5, - 9.2, - 8.8, - 8.6, - 8.4, - 7.4, - 6.5, - 6.1, - 5.9, - 5.8, - 5.6, - 5.6, - 5.7, - 5.5, - 4.6, - 4.2, - 4.2, - 4.4, - 4.7, - 5.2, - 6, - 6.3, - 6.3, - 6.2, - 6.2, - 6.1, - 6, - 5.6, - 5.4, - 5.3, - 5.5, - 5.6, - 5.8, - 5.8, - 5.9, - 6.3, - 6.6, - 7.1, - 7.4, - 7.6, - 8, - 18, - 18, - 17.9, - 17.5, - 16.7, - 16.2, - 15.9, - 15.8, - 15.7, - 15.6, - 15.6, - 15.5, - 15.4, - 15.3, - 15.3, - 15.4, - 15.4, - 15.4, - 15.4, - 15.4, - 15.5, - 15.8, - 16.1, - 16.4, - 16.7, - 17.4, - 18.1, - 18.8, - 19, - 19.1, - 19.3, - 19.5, - 19.8, - 20, - 20.1, - 20.2, - 20.2, - 20.2, - 20.1, - 20.1, - 20.1, - 20.1, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.5, - 20.6, - 20.7, - 20.9, - 21, - 21, - 21.1, - 21.1, - 21, - 20.9, - 20.9, - 21, - 21.1, - 21.2, - 21.5, - 21.9, - 23.1, - 24, - 24.4, - 24.5, - 24.6, - 24.6, - 24.5, - 24.4, - 24.2, - 23.9, - 23.5, - 23.1, - 22.8, - 22.4, - 22.2, - 22.2, - 22.3, - 22.5, - 22.5, - 22.7, - 22.8, - 23.1, - 23.8, - 24.6, - 25.1, - 25.3, - 25.5, - 25.7, - 26.1, - 26.4, - 26.7, - 26.9, - 27, - 27.1, - 27.2, - 27.2, - 27.2, - 27.1, - 26.8, - 26.2, - 25.3, - 24.5, - 25.3, - 26.2, - 26.3, - 25.5, - 24.7, - 25.3, - 26, - 26.4, - 26.4, - 26.3, - 26, - 26.3, - 26.9, - 27.4, - 28, - 28.5, - 29, - 29.2, - 29.3, - 29.3, - 29.1, - 29, - 28.8, - 28.2, - 27.4, - 26.4, - 26.6, - 27.7, - 28.4, - 28.6, - 28.4, - 27.7, - 27.2, - 27.9, - 28.2, - 28.7, - 29.7, - 30.3, - 30, - 29.4, - 28.8, - 28.2, - 28.7, - 30.8, - 32, - 32.7, - 33.3, - 33.8, - 34, - 34.1, - 34.2, - 33.9, - 33.1, - 32.2, - 31, - 29.8, - 28.8, - 27.7, - 26.1, - 25.7, - 25.6, - 25.6, - 25.6, - 25.4, - 25.2, - 25, - 24.2, - 23.4, - 22.9, - 22.7, - 22.6, - 22.5, - 22.2, - 21.7, - 21.6, - 21.6, - 21.6, - 21.6, - 21.6, - 21.5, - 21.4, - 20.9, - 20.8, - 20.7, - 20.6, - 20.4, - 20.2, - 20.2, - 19.9, - 19.5, - 19.1, - 18.6, - 18, - 17.1, - 16.4, - 15.3, - 14.2, - 13.4, - 12.8, - 12.6, - 12.2, - 11.8, - 11.5, - 11.1, - 10.9, - 10.6, - 10.4, - 10.2, - 9.9, - 9.7, - 9.4, - 9.1, - 8.9, - 8.7, - 8.5, - 8.2, - 7, - 6.3, - 6, - 5.8, - 5.7, - 5.3, - 4.9, - 4.9, - 4.6, - 3.9, - 3.8, - 3.9, - 4, - 3.9, - 4.3, - 4.9, - 5.3, - 5.4, - 5.3, - 5.2, - 5.1, - 5.1, - 4.8, - 4.5, - 4.4, - 4.6, - 4.7, - 4.7, - 4.8, - 4.8, - 5, - 5.6, - 6.3, - 7, - 7.4, - 7.6, - 17.8, - 18, - 17.9, - 17.4, - 16.6, - 16, - 15.7, - 15.6, - 15.5, - 15.5, - 15.4, - 15.3, - 15.2, - 15.2, - 15.3, - 15.3, - 15.3, - 15.3, - 15.3, - 15.5, - 15.8, - 16.1, - 16.4, - 16.8, - 17.3, - 18, - 18.6, - 19, - 19.1, - 19.3, - 19.4, - 19.6, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.2, - 20.2, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.8, - 20.9, - 20.9, - 20.9, - 20.9, - 20.7, - 20.9, - 21, - 21.1, - 21.3, - 21.6, - 22.3, - 23.4, - 24.1, - 24.3, - 24.4, - 24.5, - 24.4, - 24.3, - 24.1, - 23.8, - 23.3, - 22.7, - 22.3, - 22.2, - 22.1, - 22.1, - 22.2, - 22.4, - 22.4, - 22.6, - 22.8, - 22.8, - 22.9, - 23.4, - 24, - 24.3, - 24.5, - 24.9, - 25.5, - 26, - 26.3, - 26.6, - 26.7, - 26.9, - 26.9, - 26.9, - 26.9, - 26.8, - 26.7, - 26.4, - 25.9, - 24.7, - 24.6, - 25.7, - 26.3, - 26.2, - 25, - 24.7, - 25.7, - 26.7, - 27.3, - 27.4, - 27.2, - 26.9, - 27.1, - 27.7, - 28.2, - 28.6, - 28.9, - 29.1, - 29.2, - 29.2, - 29.2, - 29, - 28.9, - 28.5, - 27.7, - 26.7, - 26.2, - 27, - 27.8, - 28.1, - 28.2, - 28, - 27.2, - 26.9, - 27.5, - 27.5, - 28.3, - 28.7, - 29.4, - 29.2, - 28.2, - 27.5, - 27.3, - 28.3, - 30.4, - 31.8, - 32.4, - 32.8, - 33.3, - 33.7, - 34, - 34.1, - 34, - 33.2, - 32.4, - 31.4, - 29.8, - 28.6, - 27.5, - 26.1, - 25.6, - 25.6, - 25.5, - 25.5, - 25.3, - 25.2, - 24.9, - 24.3, - 23.4, - 22.9, - 22.6, - 22.5, - 22.2, - 21.9, - 21.6, - 21.5, - 21.5, - 21.5, - 21.5, - 21.5, - 21.4, - 21.1, - 20.7, - 20.6, - 20.5, - 20.5, - 20.4, - 20.1, - 20, - 19.5, - 19.1, - 18.7, - 17.9, - 16.6, - 15.9, - 15.2, - 14.2, - 13.3, - 12.5, - 11.9, - 11.8, - 11.6, - 11.3, - 11, - 10.7, - 10.5, - 10.2, - 10.1, - 10, - 9.8, - 9.4, - 9.1, - 8.9, - 8.7, - 8.5, - 8.3, - 7.5, - 6.6, - 6.2, - 6, - 5.9, - 5.9, - 5.7, - 5.4, - 5.3, - 4.5, - 3.8, - 3.6, - 3.6, - 3.7, - 3.6, - 3.5, - 3.8, - 4.2, - 4.4, - 4.4, - 4.4, - 4.4, - 4.3, - 4.2, - 3.8, - 3.7, - 3.6, - 3.8, - 3.8, - 3.7, - 3.7, - 4.1, - 4.6, - 5.1, - 5.8, - 6.9, - 7.4, - 17.6, - 18, - 17.9, - 17.5, - 16.7, - 15.9, - 15.5, - 15.4, - 15.4, - 15.3, - 15.2, - 15.1, - 15.1, - 15.2, - 15.2, - 15.2, - 15.2, - 15.2, - 15.4, - 15.8, - 16.3, - 16.6, - 17, - 17.4, - 17.9, - 18.6, - 19, - 19.1, - 19.2, - 19.3, - 19.5, - 19.7, - 19.8, - 20, - 20.1, - 20.2, - 20.2, - 20.2, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.5, - 20.6, - 20.7, - 20.7, - 20.8, - 20.7, - 20.7, - 20.7, - 20.7, - 20.9, - 21, - 21.2, - 21.3, - 21.6, - 22.4, - 23.4, - 24, - 24.2, - 24.3, - 24.3, - 24.2, - 24.1, - 23.8, - 23.4, - 22.7, - 22.2, - 22, - 21.9, - 22, - 22.2, - 22.3, - 22.3, - 22.3, - 22.4, - 22.5, - 22.5, - 22.6, - 22.8, - 23.2, - 23.4, - 24, - 24.6, - 25.3, - 25.9, - 26.2, - 26.5, - 26.6, - 26.7, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 25.9, - 25.2, - 24.3, - 24.6, - 25.8, - 26.4, - 26.1, - 24.9, - 24.5, - 25.8, - 27, - 27.7, - 27.7, - 27.6, - 27.6, - 27.8, - 28.1, - 28.5, - 28.7, - 28.9, - 29.1, - 29.1, - 29.1, - 29, - 28.6, - 28.2, - 27.6, - 26.9, - 26.2, - 26.3, - 27.2, - 27.7, - 27.7, - 27.6, - 27.4, - 26.7, - 26.5, - 26.7, - 27, - 27.8, - 28.5, - 29, - 29.1, - 28.1, - 26.9, - 27.1, - 28.3, - 29.8, - 31.5, - 31.9, - 32.2, - 32.7, - 33.2, - 33.7, - 34, - 34, - 33.4, - 32.5, - 31.5, - 29.6, - 28.4, - 27.3, - 26.1, - 25.6, - 25.5, - 25.4, - 25.3, - 25.1, - 25.1, - 25, - 24.5, - 23.6, - 23, - 22.6, - 22.4, - 22, - 21.7, - 21.5, - 21.4, - 21.4, - 21.4, - 21.4, - 21.4, - 21.3, - 20.9, - 20.5, - 20.4, - 20.4, - 20.3, - 20.2, - 20.1, - 19.8, - 19.1, - 18.9, - 18.5, - 16.9, - 15.8, - 15.1, - 14.4, - 13.6, - 12.6, - 11.8, - 11.2, - 11, - 10.9, - 10.7, - 10.5, - 10.3, - 10, - 9.9, - 9.8, - 9.7, - 9.4, - 9.2, - 8.9, - 8.7, - 8.5, - 8.3, - 7.9, - 6.9, - 6.4, - 6.2, - 6.2, - 6.2, - 6.2, - 6.2, - 6.1, - 5.9, - 5.3, - 4.4, - 4, - 3.6, - 3.4, - 3.3, - 3.2, - 3.2, - 3.5, - 3.7, - 3.6, - 3.7, - 3.7, - 3.7, - 3.6, - 3.4, - 3.2, - 3.1, - 3.1, - 3, - 2.9, - 3, - 3.2, - 3.7, - 4, - 4.8, - 5.8, - 6.6, - 17.4, - 17.9, - 17.9, - 17.7, - 16.9, - 16, - 15.5, - 15.2, - 15.1, - 15.1, - 15, - 15.1, - 15.1, - 15.1, - 15.1, - 15.1, - 15.1, - 15.3, - 15.8, - 16.3, - 16.9, - 17.4, - 17.9, - 18.1, - 18.5, - 18.9, - 19.1, - 19.2, - 19.3, - 19.3, - 19.5, - 19.6, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.3, - 20.5, - 20.6, - 20.6, - 20.6, - 20.8, - 20.9, - 21, - 21.2, - 21.3, - 21.6, - 22.3, - 23.2, - 23.8, - 24, - 24.1, - 24, - 23.9, - 23.8, - 23.4, - 22.8, - 22.1, - 21.8, - 21.8, - 21.9, - 22, - 22.2, - 22.2, - 22.2, - 22.2, - 22.2, - 22.3, - 22.3, - 22.4, - 22.5, - 22.8, - 23.4, - 24.5, - 25, - 25.4, - 25.8, - 26.2, - 26.4, - 26.5, - 26.5, - 26.4, - 26.3, - 26.1, - 26, - 25.8, - 25.4, - 24.4, - 24, - 24.9, - 25.9, - 26.3, - 25.9, - 24.8, - 24.4, - 25.8, - 27.1, - 27.8, - 27.9, - 27.9, - 28, - 28.1, - 28.4, - 28.6, - 28.7, - 28.9, - 29, - 29, - 28.9, - 28.6, - 28, - 27.2, - 26.6, - 26.1, - 25.9, - 26.5, - 27.1, - 27.4, - 27.4, - 27.2, - 26.8, - 26.2, - 26, - 26.2, - 26.8, - 27.6, - 28.1, - 28.4, - 28.4, - 27.9, - 26.6, - 27.2, - 28.2, - 29.6, - 31.1, - 31.3, - 31.4, - 32, - 32.6, - 33.2, - 33.7, - 34, - 33.5, - 32.7, - 31.4, - 29.9, - 28.7, - 27.5, - 26.2, - 25.6, - 25.5, - 25.4, - 25.1, - 24.9, - 25, - 24.9, - 24.4, - 23.5, - 23, - 22.6, - 22.4, - 22, - 21.6, - 21.4, - 21.3, - 21.3, - 21.3, - 21.3, - 21.3, - 21, - 20.6, - 20.4, - 20.2, - 20.2, - 20.2, - 20.1, - 19.9, - 19.5, - 19, - 18.7, - 18.4, - 16.5, - 15.2, - 14.1, - 13.4, - 12.7, - 12.2, - 11.4, - 10.8, - 10.4, - 10.3, - 10.2, - 9.9, - 9.7, - 9.5, - 9.3, - 9.2, - 9.1, - 9, - 8.9, - 8.8, - 8.6, - 8.3, - 8, - 7.2, - 6.6, - 6.4, - 6.3, - 6.4, - 6.4, - 6.5, - 6.5, - 6.4, - 6.1, - 5.8, - 5.1, - 4.7, - 4.5, - 4.4, - 3.9, - 3.5, - 3.2, - 3.1, - 3.1, - 3.2, - 3.2, - 3.2, - 3.2, - 3.1, - 3.1, - 3, - 2.8, - 2.7, - 2.6, - 2.5, - 2.5, - 2.6, - 2.9, - 3.2, - 3.9, - 4.8, - 5.5, - 17.4, - 17.9, - 17.9, - 17.7, - 17, - 16.1, - 15.5, - 15.2, - 14.9, - 14.9, - 15, - 15, - 15.1, - 15.1, - 15, - 15, - 15.1, - 15.6, - 16.3, - 17, - 17.8, - 18.4, - 18.6, - 18.7, - 18.9, - 19, - 19.1, - 19.1, - 19.2, - 19.3, - 19.4, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.4, - 20.4, - 20.4, - 20.5, - 20.5, - 20.5, - 20.4, - 20.3, - 20.2, - 20.1, - 20, - 20, - 20, - 20.2, - 20.6, - 20.7, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.5, - 22, - 22.8, - 23.4, - 23.7, - 23.8, - 23.7, - 23.6, - 23.3, - 22.7, - 22.1, - 21.7, - 21.6, - 21.8, - 21.9, - 22, - 22.1, - 22.1, - 22.1, - 22.1, - 22.1, - 22.2, - 22.2, - 22.4, - 22.6, - 22.8, - 23.6, - 24.7, - 25.4, - 25.7, - 25.9, - 26.2, - 26.3, - 26.4, - 26.3, - 26.2, - 25.9, - 25.6, - 25.3, - 25.1, - 24.6, - 23.9, - 24.3, - 25.1, - 25.9, - 26.1, - 25.7, - 24.7, - 24.4, - 25.7, - 26.9, - 27.6, - 27.9, - 28, - 28.1, - 28.3, - 28.4, - 28.5, - 28.6, - 28.8, - 28.8, - 28.8, - 28.5, - 28, - 27.3, - 26.4, - 25.8, - 25.7, - 26, - 26.5, - 27.2, - 27.4, - 27.4, - 27.4, - 27.1, - 26.4, - 25.7, - 26.1, - 26.7, - 27.2, - 27.2, - 27.3, - 27.5, - 27.4, - 26.4, - 26.9, - 28, - 29.1, - 30.1, - 30.4, - 30.4, - 31.5, - 32.2, - 32.9, - 33.3, - 33.8, - 33.5, - 32.7, - 31.6, - 30.2, - 29.2, - 28, - 26.4, - 25.5, - 25.4, - 25.4, - 25.1, - 24.7, - 24.4, - 24, - 23.6, - 23.1, - 22.7, - 22.6, - 22.4, - 22.1, - 21.6, - 21.3, - 21.2, - 21.2, - 21.1, - 21.2, - 21.2, - 20.8, - 20.5, - 20.3, - 20.1, - 20, - 20, - 19.9, - 19.6, - 19.2, - 18.9, - 18.5, - 18, - 16, - 14.4, - 13.4, - 12.7, - 11.8, - 11.5, - 11.1, - 10.6, - 10, - 9.8, - 9.6, - 9.4, - 9.1, - 8.8, - 8.6, - 8.4, - 8.5, - 8.6, - 8.7, - 8.6, - 8.4, - 8, - 7.4, - 6.7, - 6.5, - 6.4, - 6.4, - 6.5, - 6.6, - 6.7, - 6.7, - 6.6, - 6.4, - 6, - 5.6, - 5.2, - 5, - 4.9, - 4.4, - 3.9, - 3.6, - 3.3, - 3, - 2.9, - 2.9, - 2.9, - 3, - 2.9, - 2.9, - 2.8, - 2.7, - 2.6, - 2.5, - 2.4, - 2.2, - 2.2, - 2.3, - 2.6, - 3.2, - 3.9, - 4.6, - 17.5, - 17.8, - 17.8, - 17.6, - 17, - 16.2, - 15.6, - 15.2, - 14.9, - 14.8, - 14.9, - 14.9, - 15, - 14.9, - 14.9, - 15, - 15.3, - 15.9, - 16.9, - 17.7, - 18.3, - 18.7, - 18.8, - 18.8, - 18.9, - 19, - 19, - 19.1, - 19.2, - 19.3, - 19.4, - 19.5, - 19.6, - 19.7, - 19.8, - 19.9, - 19.9, - 20, - 20.2, - 20.3, - 20.4, - 20.4, - 20.4, - 20.5, - 20.5, - 20.5, - 20.3, - 20.2, - 20, - 19.9, - 19.8, - 19.9, - 19.9, - 20.2, - 20.6, - 20.8, - 21, - 21.1, - 21.1, - 21.1, - 21.2, - 21.4, - 21.6, - 22.1, - 22.6, - 23, - 23.2, - 23.2, - 23, - 22.5, - 22.1, - 21.6, - 21.5, - 21.7, - 21.8, - 21.9, - 21.9, - 22, - 22, - 22, - 22, - 22, - 22.1, - 22.2, - 22.5, - 22.8, - 23, - 23.6, - 24.5, - 25.4, - 25.8, - 26, - 26.1, - 26.2, - 26.3, - 26.2, - 26, - 25.6, - 24.9, - 24.5, - 24.3, - 23.9, - 23.7, - 24.3, - 25, - 25.5, - 25.7, - 25.3, - 24.4, - 24.2, - 25.4, - 26.5, - 27.4, - 27.9, - 28, - 28.2, - 28.3, - 28.3, - 28.4, - 28.5, - 28.6, - 28.7, - 28.6, - 28.2, - 27.2, - 26.4, - 25.7, - 25.6, - 26.2, - 26.6, - 26.5, - 27.1, - 27.4, - 27.5, - 27.6, - 27.4, - 26.5, - 25.6, - 25.8, - 26.2, - 26.4, - 26.4, - 26.5, - 26.7, - 26.6, - 26.1, - 26.9, - 28.1, - 29.3, - 29.7, - 29.6, - 29.2, - 31, - 31.9, - 32.5, - 33, - 33.4, - 33.3, - 32.6, - 31.9, - 30.5, - 29.4, - 28.3, - 26.7, - 25.5, - 25.4, - 25.4, - 25.3, - 25, - 24.2, - 23.3, - 22.8, - 22.6, - 22.5, - 22.5, - 22.4, - 22.1, - 21.5, - 21.2, - 21.1, - 21.1, - 20.7, - 21, - 20.9, - 20.5, - 20.3, - 20.2, - 20.1, - 19.9, - 19.9, - 19.8, - 19.4, - 19, - 18.7, - 18.3, - 17.4, - 15.4, - 13.6, - 13, - 12.3, - 11.5, - 10.8, - 10.6, - 10.2, - 9.8, - 9.5, - 9.4, - 9.2, - 8.8, - 8.6, - 8.4, - 8.3, - 8.2, - 8.3, - 8.5, - 8.5, - 8.3, - 7.8, - 7.1, - 6.6, - 6.5, - 6.5, - 6.6, - 6.8, - 6.9, - 6.9, - 6.9, - 6.8, - 6.7, - 6.4, - 6.1, - 5.7, - 5.3, - 5, - 4.7, - 4.3, - 4, - 3.7, - 3.4, - 3, - 2.8, - 2.7, - 2.7, - 2.9, - 2.9, - 2.8, - 2.7, - 2.6, - 2.5, - 2.4, - 2.2, - 2.1, - 2, - 2.1, - 2.6, - 3.2, - 3.9, - 17.6, - 17.8, - 17.7, - 17.6, - 17.1, - 16.3, - 15.6, - 15.1, - 14.8, - 14.8, - 14.9, - 14.9, - 14.8, - 14.8, - 14.9, - 15.1, - 15.5, - 16.1, - 17, - 17.7, - 18.2, - 18.6, - 18.7, - 18.8, - 18.9, - 18.9, - 19, - 19.1, - 19.2, - 19.3, - 19.4, - 19.5, - 19.5, - 19.6, - 19.7, - 19.8, - 19.8, - 19.9, - 20.1, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.4, - 20.3, - 20.1, - 19.8, - 19.8, - 19.8, - 20, - 20.2, - 20.4, - 20.7, - 20.9, - 21, - 21.1, - 21.1, - 21.1, - 21.1, - 21.2, - 21.3, - 21.5, - 21.8, - 22.2, - 22.3, - 22.4, - 22.2, - 21.9, - 21.6, - 21.4, - 21.6, - 21.7, - 21.7, - 21.8, - 21.8, - 21.9, - 21.9, - 21.9, - 21.9, - 22, - 22.2, - 22.4, - 23, - 23.5, - 23.7, - 24.1, - 24.8, - 25.5, - 25.8, - 26, - 26.1, - 26.1, - 26.1, - 26.1, - 25.9, - 25.5, - 24.8, - 24, - 23.8, - 23.6, - 23.7, - 24.3, - 24.7, - 24.9, - 25, - 24.8, - 24.2, - 24.1, - 25.4, - 26.5, - 27.4, - 27.8, - 28, - 28, - 28.2, - 28.2, - 28.3, - 28.4, - 28.5, - 28.5, - 28.3, - 27.6, - 26.5, - 25.5, - 25.3, - 25.9, - 26.8, - 27.2, - 27, - 27.2, - 27.4, - 27.6, - 27.5, - 27, - 26.2, - 25.5, - 25.4, - 25.6, - 25.6, - 25.8, - 25.7, - 25.8, - 25.9, - 26.3, - 27.2, - 28.2, - 29.1, - 29.1, - 28.5, - 28.3, - 30.3, - 31.7, - 32.3, - 32.9, - 33.2, - 33.1, - 32.6, - 31.9, - 30.2, - 29.2, - 28.1, - 26.6, - 25.5, - 25.4, - 25.4, - 25.4, - 25.2, - 24.6, - 23.5, - 22.7, - 22.3, - 22.2, - 22.1, - 22.2, - 21.8, - 21.3, - 21, - 21, - 20.8, - 20.5, - 20.6, - 20.5, - 20.3, - 20.2, - 20.1, - 20, - 19.9, - 19.8, - 19.4, - 19.1, - 18.8, - 18.4, - 18, - 16.7, - 14.8, - 13.4, - 12.5, - 12, - 11.4, - 10.7, - 10.5, - 10.2, - 10, - 9.8, - 9.5, - 9.2, - 8.9, - 8.6, - 8.4, - 8.2, - 8, - 8.2, - 8.3, - 8.3, - 8, - 7.3, - 6.8, - 6.7, - 6.7, - 6.8, - 7, - 7.2, - 7.3, - 7.2, - 7.1, - 7, - 6.9, - 6.8, - 6.5, - 6.2, - 5.8, - 5.4, - 5.1, - 4.7, - 4.4, - 4.1, - 3.8, - 3.4, - 3, - 2.7, - 2.6, - 2.6, - 2.7, - 2.7, - 2.7, - 2.6, - 2.6, - 2.4, - 2.3, - 2.3, - 2.2, - 1.9, - 1.9, - 2.4, - 3.1, - 17.6, - 17.7, - 17.6, - 17.5, - 17.2, - 16.5, - 15.7, - 15.1, - 14.7, - 14.8, - 14.8, - 14.7, - 14.8, - 14.8, - 14.9, - 15.2, - 15.6, - 16, - 16.7, - 17.5, - 18.2, - 18.5, - 18.6, - 18.7, - 18.8, - 18.9, - 19, - 19, - 19.1, - 19.2, - 19.3, - 19.4, - 19.4, - 19.5, - 19.6, - 19.6, - 19.7, - 19.8, - 20, - 20.1, - 20.2, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.2, - 20, - 19.7, - 19.7, - 19.9, - 20.2, - 20.4, - 20.5, - 20.7, - 21, - 21, - 21.1, - 21, - 20.9, - 21, - 21.1, - 21.1, - 21.2, - 21.4, - 21.5, - 21.6, - 21.7, - 21.6, - 21.4, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 21.8, - 21.8, - 21.9, - 21.8, - 22, - 22.4, - 23, - 23.9, - 24.6, - 24.7, - 25, - 25.3, - 25.6, - 25.8, - 25.9, - 26, - 26.1, - 26, - 25.9, - 25.7, - 25.4, - 24.7, - 23.9, - 23.5, - 23.4, - 23.7, - 24.3, - 24.3, - 24.2, - 24.3, - 24.2, - 23.9, - 24.2, - 25.6, - 26.8, - 27.5, - 27.7, - 27.9, - 27.9, - 28, - 28.1, - 28.1, - 28.2, - 28.3, - 28.3, - 27.8, - 26.8, - 25.6, - 25.1, - 25.4, - 26.1, - 27, - 27.4, - 27.4, - 27.4, - 27.5, - 27.7, - 27.5, - 27, - 26.2, - 25.5, - 25.2, - 25.2, - 25.2, - 25.3, - 25.4, - 25.7, - 26.1, - 26.7, - 27.5, - 28.1, - 28.6, - 28.4, - 27.6, - 27.9, - 30.2, - 31.6, - 32.2, - 32.8, - 33.1, - 33, - 32.4, - 31.5, - 29.5, - 28.4, - 27.3, - 26.3, - 25.5, - 25.4, - 25.3, - 25.4, - 25.2, - 24.7, - 23.7, - 22.8, - 22.3, - 22.1, - 21.9, - 21.6, - 21.4, - 21.1, - 20.9, - 20.7, - 20.5, - 20.4, - 20.3, - 20.2, - 20.1, - 20, - 20, - 19.9, - 19.8, - 19.6, - 19.2, - 18.8, - 18.6, - 18.1, - 17.6, - 16.4, - 14, - 13, - 12.4, - 11.7, - 11.1, - 10.9, - 10.7, - 10.6, - 10.4, - 10.2, - 9.6, - 9.2, - 8.8, - 8.6, - 8.4, - 8.1, - 7.8, - 8, - 8, - 7.8, - 7.3, - 6.9, - 6.8, - 6.9, - 7.1, - 7.3, - 7.5, - 7.6, - 7.6, - 7.5, - 7.4, - 7.3, - 7.1, - 7.1, - 6.8, - 6.5, - 6.2, - 5.8, - 5.4, - 5, - 4.7, - 4.5, - 4.2, - 3.9, - 3.4, - 3.1, - 2.8, - 2.5, - 2.4, - 2.4, - 2.4, - 2.4, - 2.4, - 2.3, - 2.2, - 2.1, - 1.9, - 1.8, - 1.6, - 1.7, - 2.3, - 17.6, - 17.6, - 17.5, - 17.4, - 17.2, - 16.5, - 15.7, - 14.9, - 14.6, - 14.7, - 14.6, - 14.7, - 14.7, - 14.8, - 15, - 15.3, - 15.6, - 16, - 16.7, - 17.6, - 18.2, - 18.3, - 18.4, - 18.6, - 18.8, - 18.8, - 18.9, - 19, - 19.1, - 19.2, - 19.3, - 19.3, - 19.3, - 19.4, - 19.5, - 19.5, - 19.6, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.2, - 20.2, - 20.1, - 20, - 19.8, - 19.6, - 19.7, - 19.8, - 20.1, - 20.4, - 20.5, - 20.7, - 20.8, - 21, - 21, - 20.9, - 20.7, - 20.8, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.3, - 21.3, - 21.2, - 21.3, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.7, - 21.7, - 21.8, - 21.8, - 21.8, - 22.1, - 22.8, - 23.8, - 24.7, - 25.2, - 25.3, - 25.4, - 25.5, - 25.7, - 25.8, - 25.8, - 25.9, - 25.9, - 25.9, - 25.8, - 25.5, - 25.1, - 24.4, - 23.6, - 23.3, - 23.2, - 23.7, - 24.1, - 23.9, - 23.7, - 23.7, - 23.7, - 23.7, - 24.6, - 26.1, - 27, - 27.4, - 27.6, - 27.7, - 27.7, - 27.7, - 27.8, - 27.8, - 27.9, - 28, - 27.8, - 27.3, - 26.1, - 25, - 25.2, - 25.7, - 26.3, - 27.2, - 27.6, - 27.7, - 27.8, - 27.8, - 27.7, - 27.4, - 26.8, - 25.9, - 25.4, - 25.1, - 25, - 25.1, - 25.3, - 25.6, - 25.9, - 26.3, - 26.7, - 27.1, - 27.4, - 27.7, - 27.4, - 27.1, - 27.9, - 29.6, - 31.5, - 32, - 32.7, - 33, - 33, - 32.4, - 31.3, - 29.5, - 28.1, - 26.9, - 26.1, - 25.5, - 25.4, - 25.3, - 25.2, - 24.8, - 24.1, - 23.3, - 22.7, - 22.3, - 22.1, - 21.9, - 21.4, - 21.2, - 21, - 20.9, - 20.6, - 20.4, - 20.3, - 20.3, - 20.2, - 20.1, - 20, - 19.8, - 19.8, - 19.7, - 19.4, - 18.9, - 18.6, - 18.3, - 17.7, - 17, - 15.8, - 13.7, - 12.7, - 12, - 11.4, - 11.3, - 11.4, - 11.3, - 11.1, - 10.9, - 10.4, - 9.8, - 9.4, - 8.8, - 8.5, - 8.3, - 8.1, - 7.7, - 7.5, - 7.4, - 7.2, - 7, - 6.9, - 7.1, - 7.4, - 7.6, - 7.7, - 7.8, - 7.9, - 7.9, - 7.7, - 7.6, - 7.4, - 7.3, - 7.2, - 7, - 6.8, - 6.5, - 6.1, - 5.8, - 5.3, - 5, - 4.9, - 4.6, - 4.3, - 3.9, - 3.5, - 3.3, - 3.2, - 2.9, - 2.5, - 2.3, - 2.2, - 2.1, - 2, - 1.9, - 1.8, - 1.5, - 1.5, - 1.4, - 1.3, - 1.5, - 17.5, - 17.5, - 17.4, - 17.3, - 17, - 16.3, - 15.5, - 14.8, - 14.5, - 14.5, - 14.6, - 14.6, - 14.7, - 14.8, - 15, - 15.3, - 15.6, - 16.1, - 16.7, - 17.5, - 17.9, - 17.8, - 18, - 18.5, - 18.6, - 18.8, - 18.9, - 19, - 19.1, - 19.2, - 19.2, - 19.2, - 19.2, - 19.3, - 19.3, - 19.4, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20, - 20, - 20, - 20, - 20, - 19.8, - 19.6, - 19.5, - 19.7, - 19.8, - 20, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.6, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.1, - 21.1, - 21.1, - 21.3, - 21.4, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.6, - 21.7, - 21.7, - 21.9, - 22.4, - 23.5, - 24.6, - 25.2, - 25.4, - 25.5, - 25.5, - 25.6, - 25.7, - 25.8, - 25.8, - 25.8, - 25.7, - 25.7, - 25.5, - 25.3, - 24.8, - 23.9, - 23.4, - 23.1, - 23.2, - 23.5, - 23.7, - 23.5, - 23.4, - 23.4, - 23.6, - 23.9, - 25.2, - 26.4, - 27.1, - 27.3, - 27.4, - 27.4, - 27.4, - 27.3, - 27.2, - 27.4, - 27.5, - 27.5, - 27.3, - 26.6, - 25.4, - 24.8, - 25.1, - 25.7, - 26.5, - 27.3, - 27.8, - 27.9, - 27.9, - 27.9, - 27.6, - 27, - 26.1, - 25.4, - 25.1, - 24.9, - 24.9, - 25.1, - 25.3, - 25.6, - 25.8, - 26.1, - 26.4, - 26.5, - 26.6, - 26.6, - 26.7, - 27.3, - 28, - 28.9, - 31.2, - 31.9, - 32.5, - 32.9, - 32.9, - 32.4, - 31.6, - 29.5, - 28.1, - 26.9, - 26.1, - 25.5, - 25.3, - 25.2, - 25, - 24.2, - 23.3, - 22.8, - 22.4, - 22.1, - 22, - 21.7, - 21.3, - 21.1, - 20.9, - 20.6, - 20.4, - 20.2, - 20.3, - 20.3, - 20.2, - 20.1, - 20, - 19.9, - 19.7, - 19.6, - 19, - 18.7, - 18.3, - 17.9, - 17.2, - 16.2, - 15, - 13.6, - 12.6, - 12, - 11.7, - 11.8, - 11.9, - 11.8, - 11.5, - 11.1, - 10.5, - 9.8, - 9.4, - 8.9, - 8.5, - 8.3, - 8.1, - 7.7, - 7.4, - 7.3, - 7.2, - 7.3, - 7.4, - 7.7, - 7.8, - 8, - 8.1, - 8.2, - 8.2, - 8.1, - 7.9, - 7.8, - 7.7, - 7.5, - 7.4, - 7.2, - 7, - 6.7, - 6.5, - 6.3, - 5.8, - 5.4, - 5.3, - 5, - 4.7, - 4.3, - 4, - 3.8, - 3.9, - 3.7, - 3.4, - 3, - 2.7, - 2.6, - 2.5, - 2.4, - 2.3, - 2.1, - 2, - 1.7, - 1.4, - 1.2, - 17.4, - 17.3, - 17.2, - 17, - 16.6, - 15.9, - 15.2, - 14.6, - 14.4, - 14.6, - 14.6, - 14.6, - 14.6, - 14.8, - 15.1, - 15.3, - 15.7, - 16.2, - 16.4, - 16.7, - 16.9, - 17, - 17.8, - 18.4, - 18.6, - 18.7, - 18.9, - 19, - 19.1, - 19.1, - 19.1, - 19.1, - 19.1, - 19.1, - 19.2, - 19.4, - 19.4, - 19.6, - 19.7, - 19.8, - 19.8, - 19.9, - 19.9, - 19.9, - 19.9, - 19.8, - 19.7, - 19.5, - 19.3, - 19.5, - 19.7, - 19.9, - 20, - 20.2, - 20.4, - 20.4, - 20.5, - 20.5, - 20.4, - 20.2, - 20.3, - 20.5, - 20.7, - 20.9, - 20.9, - 20.9, - 20.9, - 20.9, - 21.1, - 21.1, - 21.2, - 21.3, - 21.4, - 21.4, - 21.4, - 21.5, - 21.5, - 21.5, - 21.6, - 21.6, - 22, - 22.8, - 24.1, - 25, - 25.3, - 25.4, - 25.5, - 25.5, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.5, - 25.3, - 25, - 24.5, - 23.8, - 23.3, - 23, - 23.1, - 23.3, - 23.4, - 23.2, - 23.3, - 23.4, - 23.5, - 24, - 25.3, - 26.4, - 26.9, - 27.1, - 27.1, - 27.1, - 27, - 26.9, - 26.7, - 26.9, - 27, - 26.9, - 26.5, - 25.9, - 25, - 24.7, - 25, - 25.7, - 26.5, - 27.2, - 27.6, - 27.8, - 27.8, - 27.6, - 27.2, - 26.5, - 25.7, - 25.1, - 24.8, - 24.8, - 24.9, - 25.1, - 25.2, - 25.3, - 25.6, - 26, - 26.2, - 26.1, - 26, - 26.4, - 27.1, - 28, - 28.6, - 29.4, - 31.2, - 31.8, - 32.5, - 32.9, - 32.8, - 32.3, - 31.7, - 29.5, - 28, - 26.9, - 26.2, - 25.5, - 25.3, - 25.2, - 25.1, - 24.5, - 23.8, - 23, - 22.5, - 22.2, - 22, - 21.7, - 21.3, - 21.1, - 21, - 20.6, - 20.3, - 20.2, - 20.1, - 20.1, - 20.1, - 20.1, - 20, - 19.9, - 19.8, - 19.5, - 18.9, - 18.5, - 18.1, - 17.5, - 16.2, - 15.3, - 14.1, - 13.5, - 12.7, - 12.3, - 12.2, - 12.3, - 12.3, - 12.1, - 11.7, - 11.3, - 10.6, - 10.1, - 9.5, - 9, - 8.6, - 8.3, - 8.1, - 7.8, - 7.6, - 7.7, - 7.7, - 7.8, - 7.9, - 8, - 8.2, - 8.3, - 8.4, - 8.5, - 8.5, - 8.4, - 8.2, - 8, - 7.9, - 7.7, - 7.5, - 7.3, - 7.2, - 7, - 6.8, - 6.6, - 6.2, - 5.8, - 5.6, - 5.3, - 5.1, - 4.7, - 4.5, - 4.3, - 4.3, - 4.1, - 3.8, - 3.5, - 3.3, - 3.1, - 3, - 2.9, - 2.8, - 2.8, - 2.6, - 2.3, - 1.8, - 1.5, - 17.3, - 17.1, - 17, - 16.7, - 16.1, - 15.4, - 14.8, - 14.4, - 14.5, - 14.6, - 14.5, - 14.5, - 14.6, - 14.8, - 15.1, - 15.3, - 15.6, - 15.8, - 15.8, - 16, - 16.3, - 17, - 18.1, - 18.6, - 18.8, - 18.9, - 18.9, - 19, - 19, - 19, - 19, - 19, - 18.9, - 19, - 19.1, - 19.2, - 19.3, - 19.4, - 19.5, - 19.6, - 19.7, - 19.8, - 19.8, - 19.8, - 19.8, - 19.7, - 19.5, - 19.2, - 19.1, - 19.4, - 19.6, - 19.8, - 19.9, - 20, - 20.1, - 20.3, - 20.2, - 20.2, - 20.1, - 20.1, - 20.1, - 20.3, - 20.5, - 20.8, - 20.8, - 20.8, - 20.8, - 20.9, - 21.1, - 21.1, - 21.2, - 21.2, - 21.3, - 21.4, - 21.4, - 21.4, - 21.4, - 21.4, - 21.6, - 21.8, - 22.4, - 23.5, - 24.4, - 25.1, - 25.3, - 25.4, - 25.4, - 25.5, - 25.5, - 25.6, - 25.5, - 25.5, - 25.5, - 25.4, - 25.3, - 25.2, - 24.9, - 24.4, - 23.8, - 23.3, - 22.9, - 22.9, - 23.1, - 23.1, - 23.1, - 23.2, - 23.3, - 23.5, - 24.1, - 25.3, - 26.2, - 26.6, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.1, - 26.3, - 26.2, - 25.9, - 25.4, - 24.9, - 24.7, - 24.6, - 24.8, - 25.4, - 26.1, - 26.7, - 27.1, - 27.3, - 27.3, - 27, - 26.5, - 26, - 25.3, - 24.8, - 24.7, - 24.7, - 24.8, - 24.9, - 25, - 25.3, - 25.6, - 25.9, - 26.1, - 26, - 25.9, - 26.5, - 27.2, - 28, - 28.5, - 29.8, - 31.4, - 31.8, - 32.4, - 32.8, - 32.8, - 32.3, - 31.6, - 29.7, - 28.5, - 27.4, - 26.4, - 25.6, - 25.4, - 25.2, - 25.1, - 24.9, - 24.5, - 23.5, - 22.8, - 22.4, - 22.2, - 21.9, - 21.4, - 21.1, - 21, - 20.9, - 20.6, - 20.3, - 20.1, - 20, - 20, - 20, - 19.9, - 19.8, - 19.8, - 19.4, - 18.9, - 18.4, - 18, - 16.7, - 15.4, - 14.4, - 13.5, - 13.2, - 12.9, - 12.7, - 12.7, - 12.8, - 12.5, - 12.2, - 11.8, - 11.3, - 10.8, - 10.4, - 9.8, - 9.2, - 8.7, - 8.3, - 8.1, - 7.9, - 7.9, - 8.1, - 8.1, - 8.2, - 8.2, - 8.4, - 8.5, - 8.6, - 8.7, - 8.7, - 8.7, - 8.6, - 8.5, - 8.3, - 8.1, - 7.9, - 7.7, - 7.5, - 7.3, - 7.1, - 7, - 6.8, - 6.5, - 6.1, - 5.8, - 5.6, - 5.4, - 5.1, - 4.9, - 4.8, - 4.6, - 4.3, - 4.1, - 3.9, - 3.6, - 3.4, - 3.3, - 3.1, - 3, - 3, - 2.8, - 2.5, - 2.1, - 1.7, - 17.1, - 17, - 16.9, - 16.7, - 16.2, - 15.4, - 14.5, - 14.4, - 14.5, - 14.4, - 14.4, - 14.5, - 14.6, - 14.9, - 15.1, - 15.2, - 15.3, - 15.5, - 15.6, - 16, - 16.5, - 17.6, - 18.4, - 18.8, - 18.9, - 19, - 19.1, - 19.1, - 19, - 18.9, - 18.8, - 18.7, - 18.7, - 18.8, - 18.9, - 19, - 19.1, - 19.3, - 19.4, - 19.5, - 19.6, - 19.6, - 19.7, - 19.7, - 19.6, - 19.5, - 19, - 18.1, - 18.3, - 19.3, - 19.5, - 19.7, - 19.8, - 19.9, - 20, - 20, - 20, - 20, - 20, - 20, - 20.1, - 20.2, - 20.3, - 20.5, - 20.7, - 20.6, - 20.7, - 20.9, - 21.1, - 21.1, - 21.1, - 21.2, - 21.2, - 21.3, - 21.3, - 21.3, - 21.3, - 21.5, - 21.8, - 22.2, - 23.2, - 24.3, - 24.8, - 25, - 25.2, - 25.2, - 25.2, - 25.3, - 25.3, - 25.3, - 25.3, - 25.2, - 25, - 24.9, - 24.9, - 25, - 24.9, - 24.7, - 24.1, - 23.4, - 23, - 22.8, - 22.9, - 23, - 23.1, - 23.1, - 23.2, - 23.6, - 24.3, - 25.4, - 26.2, - 26.3, - 26.1, - 25.9, - 25.6, - 25.6, - 25.6, - 25.5, - 25.6, - 25.3, - 24.9, - 24.5, - 24.4, - 24.5, - 24.5, - 24.6, - 25, - 25.6, - 26.1, - 26.4, - 26.6, - 26.6, - 26.2, - 25.7, - 25.2, - 24.8, - 24.6, - 24.6, - 24.6, - 24.7, - 24.8, - 25, - 25.2, - 25.4, - 25.6, - 25.8, - 25.8, - 25.7, - 26.3, - 26.9, - 27.6, - 28.4, - 29.3, - 31.5, - 31.9, - 32.5, - 32.8, - 32.8, - 32.3, - 31.4, - 29.9, - 28.9, - 27.7, - 26.5, - 25.6, - 25.4, - 25.2, - 25.1, - 25, - 24.9, - 23.6, - 22.8, - 22.4, - 22.2, - 21.8, - 21.4, - 21.3, - 21.2, - 21, - 20.8, - 20.4, - 20.1, - 19.9, - 19.9, - 19.8, - 19.8, - 19.7, - 19.7, - 19.2, - 18.7, - 18.3, - 17.9, - 16.4, - 15.2, - 14.5, - 14, - 13.6, - 13.3, - 13.2, - 13.1, - 13, - 12.6, - 12.2, - 11.7, - 11.3, - 10.9, - 10.5, - 10, - 9.5, - 8.9, - 8.4, - 8.1, - 8.1, - 8.2, - 8.4, - 8.5, - 8.6, - 8.7, - 8.8, - 8.9, - 8.9, - 9, - 9.1, - 9, - 9, - 8.8, - 8.6, - 8.4, - 8.1, - 7.8, - 7.6, - 7.4, - 7.2, - 7, - 6.9, - 6.7, - 6.3, - 6, - 5.7, - 5.5, - 5.4, - 5.2, - 5.1, - 4.8, - 4.7, - 4.5, - 4.2, - 4, - 3.8, - 3.6, - 3.4, - 3.2, - 3.1, - 2.8, - 2.6, - 2.1, - 1.7, - 16.9, - 16.9, - 16.9, - 16.8, - 16.4, - 15.6, - 14.4, - 14.3, - 14.4, - 14.3, - 14.4, - 14.4, - 14.8, - 15, - 15, - 15.1, - 15.2, - 15.4, - 15.7, - 16.1, - 16.9, - 18, - 18.6, - 18.9, - 19.1, - 19.1, - 19.2, - 19.1, - 19, - 18.8, - 18.5, - 18, - 17.9, - 18.1, - 18.3, - 18.6, - 18.9, - 19, - 19.2, - 19.3, - 19.4, - 19.5, - 19.7, - 19.5, - 19.5, - 19.1, - 17.8, - 16.9, - 17.5, - 19, - 19.4, - 19.6, - 19.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.3, - 20.5, - 20.6, - 20.6, - 20.7, - 20.9, - 21, - 21.1, - 21.2, - 21.2, - 21.2, - 21.2, - 21.2, - 21.5, - 21.8, - 22.1, - 22.6, - 23.7, - 24.5, - 24.8, - 24.9, - 24.9, - 24.9, - 24.8, - 24.7, - 24.7, - 24.7, - 24.7, - 24.5, - 24.1, - 24, - 24.2, - 24.7, - 25, - 24.9, - 24.4, - 23.5, - 22.9, - 22.7, - 22.8, - 22.9, - 23, - 23, - 23.1, - 23.6, - 24.6, - 25.6, - 26, - 25.9, - 25.5, - 25, - 24.6, - 24.6, - 24.6, - 24.9, - 24.7, - 24.4, - 24.2, - 24.2, - 24.3, - 24.4, - 24.4, - 24.4, - 24.7, - 25.1, - 25.5, - 25.8, - 25.8, - 25.7, - 25.4, - 24.9, - 24.6, - 24.5, - 24.5, - 24.5, - 24.6, - 24.6, - 24.8, - 24.9, - 25.1, - 25.2, - 25.3, - 25.4, - 25.4, - 25.7, - 26.2, - 26.7, - 27.3, - 28, - 29.2, - 31.4, - 31.9, - 32.5, - 32.8, - 32.8, - 32.3, - 31.4, - 29.8, - 28.7, - 27.5, - 26.5, - 25.6, - 25.4, - 25.2, - 25.1, - 25, - 24.7, - 23.4, - 22.6, - 22.2, - 22, - 21.7, - 21.6, - 21.5, - 21.3, - 21.1, - 20.9, - 20.5, - 20.1, - 20, - 19.9, - 19.8, - 19.7, - 19.7, - 19.6, - 19.1, - 18.6, - 18.2, - 17.9, - 16.9, - 15.9, - 15.7, - 15.2, - 14.5, - 14, - 13.6, - 13.3, - 13, - 12.6, - 12.1, - 11.7, - 11.3, - 10.9, - 10.4, - 10, - 9.5, - 8.9, - 8.4, - 8.2, - 8.3, - 8.4, - 8.7, - 8.8, - 8.9, - 9.1, - 9.3, - 9.4, - 9.4, - 9.5, - 9.5, - 9.4, - 9.4, - 9.2, - 9, - 8.7, - 8.3, - 8, - 7.8, - 7.6, - 7.3, - 7.1, - 6.9, - 6.7, - 6.4, - 6.1, - 5.8, - 5.5, - 5.4, - 5.3, - 5.2, - 5, - 4.8, - 4.7, - 4.5, - 4.2, - 4.1, - 3.9, - 3.7, - 3.4, - 3.1, - 2.8, - 2.5, - 2.1, - 1.7, - 16.8, - 16.9, - 17, - 16.9, - 16.5, - 15.7, - 14.4, - 14.3, - 14.3, - 14.3, - 14.3, - 14.5, - 14.8, - 14.9, - 14.9, - 15, - 15.2, - 15.4, - 15.8, - 16.3, - 17.2, - 18.2, - 18.6, - 18.9, - 19, - 19.1, - 19.1, - 19.1, - 18.9, - 18.5, - 17.6, - 16.9, - 16.8, - 17.1, - 17.6, - 18.3, - 18.7, - 18.9, - 19.1, - 19.3, - 19.4, - 19.4, - 19.5, - 19.4, - 19.2, - 18.5, - 17.2, - 16.6, - 17.1, - 18.8, - 19.4, - 19.5, - 19.6, - 19.7, - 19.6, - 19.4, - 19.3, - 19, - 19.2, - 19.3, - 19, - 19.6, - 20.1, - 20.2, - 20.4, - 20.6, - 20.6, - 20.6, - 20.7, - 20.8, - 21, - 21.1, - 21.1, - 21.2, - 21.1, - 21.3, - 21.7, - 21.9, - 22.2, - 22.8, - 23.7, - 24.3, - 24.6, - 24.6, - 24.4, - 24.1, - 23.8, - 23.7, - 23.6, - 23.5, - 23.5, - 23.4, - 23.1, - 23.1, - 23.5, - 24.3, - 24.9, - 24.9, - 24.4, - 23.3, - 22.7, - 22.6, - 22.7, - 22.8, - 22.9, - 22.9, - 23.2, - 23.9, - 24.8, - 25.4, - 25.6, - 25.5, - 24.9, - 24.2, - 23.8, - 23.8, - 23.9, - 24.1, - 24.1, - 24, - 24, - 24.1, - 24.2, - 24.2, - 24.3, - 24.3, - 24.5, - 24.6, - 24.8, - 25, - 24.9, - 24.9, - 24.6, - 24.4, - 24.4, - 24.4, - 24.4, - 24.5, - 24.6, - 24.7, - 24.7, - 24.8, - 24.9, - 25, - 25.1, - 25.3, - 25.5, - 25.9, - 26.2, - 26.6, - 27.5, - 28.5, - 29.4, - 31.4, - 31.9, - 32.5, - 32.9, - 32.8, - 32.3, - 31.5, - 29.7, - 28.6, - 27.4, - 26.5, - 25.6, - 25.3, - 25.2, - 25.1, - 24.9, - 24.2, - 23.1, - 22.4, - 22.2, - 22, - 21.9, - 22, - 21.8, - 21.5, - 21.2, - 21, - 20.7, - 20.3, - 20.1, - 20, - 19.8, - 19.7, - 19.6, - 19.5, - 19.2, - 18.8, - 18.4, - 18, - 17.7, - 17.2, - 16.7, - 16, - 15.4, - 14.8, - 14, - 13.5, - 13, - 12.6, - 12.2, - 11.7, - 11.3, - 10.8, - 10.3, - 9.9, - 9.4, - 8.8, - 8.5, - 8.5, - 8.6, - 8.8, - 9, - 9.1, - 9.3, - 9.5, - 9.6, - 9.8, - 9.9, - 9.9, - 9.9, - 9.9, - 9.7, - 9.5, - 9.2, - 8.9, - 8.5, - 8.2, - 7.9, - 7.6, - 7.3, - 7.1, - 6.9, - 6.7, - 6.4, - 6.1, - 5.8, - 5.5, - 5.3, - 5.2, - 5.1, - 5, - 4.8, - 4.7, - 4.5, - 4.4, - 4.2, - 4, - 3.8, - 3.4, - 3.1, - 2.7, - 2.4, - 2, - 1.7, - 16.8, - 16.9, - 17, - 16.8, - 16.4, - 15.6, - 14.3, - 14.2, - 14.2, - 14.3, - 14.3, - 14.6, - 14.7, - 14.8, - 14.8, - 14.9, - 15.2, - 15.5, - 15.9, - 16.6, - 17.5, - 18.1, - 18.5, - 18.7, - 18.8, - 18.9, - 18.9, - 18.8, - 18.5, - 17.7, - 16.6, - 16.4, - 16.9, - 17.5, - 18.3, - 18.7, - 18.9, - 19, - 19.2, - 19.2, - 19.3, - 19.4, - 19.4, - 19.2, - 18.7, - 17.5, - 16.8, - 16.5, - 17.2, - 18.3, - 19, - 19.2, - 19.3, - 19.3, - 19, - 18.3, - 18, - 17.7, - 18, - 17.9, - 17.7, - 18.7, - 20, - 20.2, - 20.4, - 20.5, - 20.4, - 20.5, - 20.5, - 20.6, - 20.7, - 20.8, - 21, - 21, - 20.9, - 21.3, - 21.7, - 21.9, - 22.2, - 22.6, - 23.5, - 24, - 24.2, - 24, - 23.5, - 23, - 22.7, - 22.7, - 22.6, - 22.6, - 22.6, - 22.6, - 22.6, - 22.8, - 23.2, - 24.2, - 24.9, - 24.9, - 24.2, - 23.1, - 22.6, - 22.6, - 22.6, - 22.7, - 22.8, - 22.8, - 23.2, - 24, - 24.6, - 24.8, - 24.8, - 24.5, - 24.1, - 23.7, - 23.5, - 23.6, - 23.7, - 23.8, - 23.8, - 23.8, - 23.9, - 24, - 24.1, - 24.1, - 24.2, - 24.2, - 24.2, - 24.2, - 24.3, - 24.3, - 24.3, - 24.3, - 24.2, - 24.2, - 24.2, - 24.3, - 24.4, - 24.6, - 24.7, - 24.9, - 24.9, - 25, - 25.2, - 25.3, - 25.2, - 25.5, - 26.1, - 26.7, - 27.1, - 27.2, - 27.7, - 28.4, - 29.2, - 31.2, - 32.1, - 32.6, - 32.9, - 32.9, - 32.4, - 31.4, - 29.8, - 28.7, - 27.5, - 26.5, - 25.7, - 25.3, - 25.2, - 25.2, - 24.9, - 24.2, - 23.2, - 22.7, - 22.5, - 22.3, - 22.3, - 22.3, - 22.2, - 21.7, - 21.3, - 21.1, - 20.9, - 20.7, - 20.5, - 20.2, - 20, - 19.8, - 19.6, - 19.6, - 19.4, - 19.2, - 18.7, - 18.4, - 18.2, - 17.9, - 17.4, - 16.7, - 16, - 15.4, - 14.6, - 13.8, - 13.1, - 12.6, - 12.1, - 11.7, - 11.2, - 10.7, - 10.2, - 9.7, - 9.2, - 8.7, - 8.7, - 9, - 9.2, - 9.3, - 9.4, - 9.5, - 9.6, - 9.9, - 10, - 10.1, - 10.2, - 10.3, - 10.2, - 10.1, - 9.8, - 9.6, - 9.3, - 9, - 8.5, - 8.2, - 7.9, - 7.6, - 7.3, - 7.1, - 6.9, - 6.7, - 6.4, - 6.1, - 5.8, - 5.5, - 5.3, - 5.1, - 5, - 4.8, - 4.7, - 4.6, - 4.4, - 4.3, - 4.1, - 3.9, - 3.7, - 3.4, - 3, - 2.7, - 2.4, - 2, - 1.6, - 16.7, - 16.9, - 16.9, - 16.6, - 16.2, - 15.4, - 14.3, - 14.1, - 14.3, - 14.2, - 14.3, - 14.4, - 14.5, - 14.6, - 14.7, - 15, - 15.2, - 15.6, - 16.1, - 16.9, - 17.5, - 17.9, - 18.1, - 18.4, - 18.5, - 18.5, - 18.3, - 18, - 17.5, - 16.6, - 16.2, - 16.6, - 17.4, - 18.4, - 18.8, - 19, - 19.1, - 19.1, - 19.2, - 19.2, - 19.3, - 19.3, - 19.3, - 19.1, - 18.2, - 17.1, - 16.6, - 16.5, - 16.9, - 17.6, - 17.9, - 18, - 18, - 18, - 17.8, - 17.4, - 17.2, - 17.1, - 17.3, - 17.2, - 17.5, - 18.4, - 19.8, - 20.3, - 20.3, - 20.3, - 20.2, - 20.4, - 20.5, - 20.5, - 20.5, - 20.6, - 20.7, - 20.7, - 20.9, - 21.2, - 21.5, - 21.7, - 22, - 22.4, - 22.9, - 23.4, - 23.5, - 23.1, - 22.6, - 22.3, - 22.2, - 22.2, - 22.3, - 22.3, - 22.3, - 22.4, - 22.5, - 22.6, - 23.2, - 24.2, - 24.8, - 24.8, - 24, - 22.9, - 22.4, - 22.5, - 22.5, - 22.6, - 22.7, - 22.9, - 23.3, - 23.8, - 24.1, - 24.1, - 23.8, - 23.6, - 23.5, - 23.3, - 23.3, - 23.5, - 23.5, - 23.6, - 23.7, - 23.8, - 23.9, - 23.9, - 24, - 24, - 24, - 24.1, - 24.1, - 24, - 24, - 24, - 24, - 24.1, - 24.1, - 24.2, - 24.2, - 24.3, - 24.5, - 24.8, - 25.1, - 25.5, - 25.6, - 25.6, - 25.9, - 25.8, - 25.5, - 25.6, - 26.3, - 27, - 27.8, - 28.2, - 28.4, - 28.5, - 29, - 30.9, - 32.1, - 32.7, - 33.2, - 33.1, - 32.5, - 31.6, - 30.1, - 28.9, - 27.7, - 26.7, - 25.8, - 25.4, - 25.3, - 25.2, - 25.1, - 24.8, - 24.2, - 23.4, - 22.8, - 22.5, - 22.4, - 22.4, - 22.3, - 21.9, - 21.4, - 21.2, - 21.2, - 21.2, - 21.1, - 20.8, - 20.4, - 20.1, - 19.9, - 19.7, - 19.6, - 19.6, - 19.2, - 18.8, - 18.6, - 18.3, - 17.9, - 17.3, - 16.5, - 15.8, - 15.1, - 13.9, - 13.2, - 12.5, - 11.9, - 11.5, - 11, - 10.5, - 10.1, - 9.5, - 9.2, - 9, - 9.1, - 9.5, - 9.7, - 9.8, - 9.9, - 10, - 10, - 10.2, - 10.3, - 10.4, - 10.5, - 10.4, - 10.3, - 10.1, - 9.8, - 9.5, - 9.2, - 8.9, - 8.5, - 8.1, - 7.8, - 7.5, - 7.3, - 7, - 6.9, - 6.7, - 6.4, - 6.1, - 5.8, - 5.4, - 5.2, - 5, - 4.8, - 4.7, - 4.5, - 4.4, - 4.2, - 4.1, - 4, - 3.8, - 3.6, - 3.3, - 3, - 2.7, - 2.4, - 2, - 1.6, - 16.7, - 16.8, - 16.7, - 16.4, - 15.9, - 15.1, - 14.2, - 14, - 14.2, - 14.1, - 14.3, - 14.4, - 14.6, - 14.7, - 14.9, - 15.1, - 15.3, - 15.7, - 16.2, - 16.6, - 16.9, - 17.2, - 17.5, - 17.8, - 17.9, - 17.7, - 17.3, - 16.9, - 16.5, - 16, - 16.2, - 16.9, - 17.9, - 18.8, - 19, - 19, - 19.2, - 19.2, - 19.2, - 19, - 19.1, - 19.2, - 19.1, - 18.8, - 17.8, - 16.8, - 16.4, - 16.4, - 16.7, - 16.8, - 16.9, - 16.9, - 16.9, - 16.9, - 16.9, - 16.9, - 17, - 17, - 17.2, - 17.4, - 17.5, - 18.2, - 19.3, - 19.9, - 20, - 20, - 20, - 20.1, - 20.3, - 20.4, - 20.5, - 20.6, - 20.6, - 20.7, - 20.9, - 21.1, - 21.4, - 21.7, - 21.9, - 22.4, - 22.7, - 22.9, - 22.8, - 22.5, - 22.2, - 22, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.3, - 22.4, - 22.6, - 23.2, - 24.1, - 24.5, - 24.6, - 23.7, - 22.8, - 22.3, - 22.4, - 22.5, - 22.5, - 22.7, - 22.9, - 23.2, - 23.4, - 23.5, - 23.4, - 23.3, - 23.2, - 23.2, - 23.1, - 23.3, - 23.4, - 23.4, - 23.5, - 23.6, - 23.7, - 23.7, - 23.8, - 23.9, - 23.9, - 24, - 24, - 24, - 23.9, - 23.9, - 24, - 24, - 24, - 24.1, - 24.2, - 24.3, - 24.5, - 24.7, - 25.1, - 25.7, - 26.4, - 26.6, - 26.7, - 26.8, - 26.4, - 25.8, - 25.6, - 26.2, - 26.9, - 27.7, - 28.5, - 29.1, - 29.2, - 29.4, - 31.3, - 32.2, - 32.9, - 33.4, - 33.2, - 32.4, - 31.5, - 30.1, - 28.9, - 27.7, - 26.6, - 25.8, - 25.4, - 25.3, - 25.3, - 25.3, - 25.1, - 24.8, - 24.1, - 23.2, - 22.7, - 22.5, - 22.4, - 22.3, - 21.9, - 21.5, - 21.4, - 21.4, - 21.4, - 21.3, - 21.2, - 20.9, - 20.6, - 20.2, - 19.9, - 19.8, - 19.7, - 19.6, - 19.3, - 19, - 18.7, - 18.3, - 17.8, - 17.1, - 16.2, - 15.3, - 14.2, - 13.3, - 12.4, - 11.8, - 11.3, - 10.8, - 10.4, - 9.9, - 9.5, - 9.4, - 9.4, - 9.8, - 10.1, - 10.2, - 10.3, - 10.3, - 10.4, - 10.4, - 10.5, - 10.6, - 10.6, - 10.6, - 10.5, - 10.3, - 10.1, - 9.8, - 9.4, - 9.1, - 8.8, - 8.4, - 8.1, - 7.7, - 7.5, - 7.2, - 7, - 6.8, - 6.7, - 6.4, - 6.2, - 5.8, - 5.4, - 5.2, - 5, - 4.8, - 4.6, - 4.4, - 4.2, - 4.1, - 3.9, - 3.8, - 3.7, - 3.5, - 3.3, - 3, - 2.8, - 2.4, - 2, - 1.6, - 16.6, - 16.6, - 16.5, - 16.2, - 15.6, - 14.9, - 14.2, - 14, - 14.1, - 14.2, - 14.5, - 14.7, - 14.8, - 15, - 15.1, - 15.2, - 15.4, - 15.6, - 16, - 16.1, - 16.2, - 16.4, - 16.6, - 16.7, - 16.7, - 16.5, - 16.2, - 16, - 15.9, - 15.8, - 16.3, - 17.2, - 18.5, - 18.8, - 18.9, - 19, - 19.1, - 19.1, - 19, - 18.4, - 18.6, - 18.9, - 19, - 18, - 17, - 16.4, - 16.3, - 16.4, - 16.4, - 16.5, - 16.6, - 16.6, - 16.7, - 16.8, - 17, - 17.2, - 17.5, - 17.6, - 17.9, - 18.4, - 18.1, - 18.2, - 19.2, - 19.8, - 20, - 20.1, - 20.2, - 20.1, - 20.1, - 20.2, - 20.4, - 20.6, - 20.6, - 20.7, - 20.9, - 21.2, - 21.5, - 21.7, - 21.9, - 22.3, - 22.5, - 22.5, - 22.4, - 22.1, - 22, - 21.9, - 21.9, - 22, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.7, - 23.1, - 23.8, - 24.1, - 24.1, - 23.4, - 22.6, - 22.3, - 22.3, - 22.4, - 22.4, - 22.6, - 22.7, - 22.8, - 23, - 23, - 23, - 23, - 23, - 23, - 23.1, - 23.2, - 23.2, - 23.3, - 23.5, - 23.5, - 23.6, - 23.6, - 23.7, - 23.8, - 23.8, - 23.8, - 23.9, - 23.9, - 23.8, - 23.9, - 23.9, - 23.9, - 24.1, - 24.2, - 24.4, - 24.5, - 24.7, - 25, - 25.6, - 26.2, - 26.9, - 27.5, - 27.8, - 27.5, - 27, - 26.6, - 25.9, - 26, - 26.6, - 27.3, - 28, - 28.8, - 29.2, - 29.8, - 31.5, - 32.3, - 33, - 33.5, - 33.1, - 32.2, - 31.2, - 29.8, - 28.6, - 27.6, - 26.6, - 25.8, - 25.4, - 25.4, - 25.3, - 25.4, - 25.2, - 24.9, - 24.1, - 23.2, - 22.7, - 22.5, - 22.3, - 22.1, - 21.8, - 21.7, - 21.7, - 21.8, - 21.8, - 21.6, - 21.4, - 21.3, - 21, - 20.5, - 20.2, - 20, - 19.9, - 19.8, - 19.7, - 19.4, - 19, - 18.6, - 18.2, - 17.7, - 16.8, - 15.8, - 14.7, - 13.5, - 12.6, - 11.8, - 11.2, - 10.7, - 10.3, - 9.8, - 9.6, - 9.6, - 9.9, - 10.3, - 10.5, - 10.6, - 10.7, - 10.7, - 10.8, - 10.8, - 10.8, - 10.8, - 10.8, - 10.7, - 10.5, - 10.3, - 10.1, - 9.9, - 9.4, - 9, - 8.8, - 8.3, - 7.9, - 7.7, - 7.4, - 7.1, - 6.9, - 6.7, - 6.5, - 6.3, - 6.1, - 5.7, - 5.4, - 5.1, - 5, - 4.8, - 4.6, - 4.4, - 4.2, - 4, - 3.8, - 3.7, - 3.5, - 3.3, - 3.2, - 3, - 2.7, - 2.4, - 1.9, - 1.6, - 16.4, - 16.4, - 16.3, - 15.9, - 15.3, - 14.7, - 14.1, - 13.9, - 14, - 14.4, - 14.7, - 14.9, - 15, - 15.1, - 15.2, - 15.2, - 15.3, - 15.5, - 15.6, - 15.7, - 15.8, - 15.9, - 15.9, - 15.9, - 15.7, - 15.6, - 15.5, - 15.5, - 15.6, - 15.8, - 16.4, - 17.6, - 18.6, - 18.8, - 18.8, - 18.8, - 18.8, - 18.8, - 18.6, - 17.6, - 17.6, - 18, - 18.2, - 17.2, - 16.4, - 16.2, - 16.3, - 16.4, - 16.5, - 16.5, - 16.6, - 16.7, - 16.9, - 17.1, - 17.6, - 18.1, - 18.4, - 18.6, - 18.9, - 19.3, - 19.4, - 19.7, - 19.9, - 20.1, - 20.2, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.5, - 20.6, - 20.7, - 20.9, - 21.1, - 21.5, - 21.7, - 21.8, - 22, - 22.1, - 22.1, - 22, - 21.9, - 21.8, - 21.8, - 21.8, - 21.9, - 22, - 22, - 22.2, - 22.3, - 22.5, - 22.7, - 23, - 23.3, - 23.6, - 23.5, - 22.9, - 22.4, - 22.2, - 22.2, - 22.3, - 22.4, - 22.4, - 22.5, - 22.6, - 22.7, - 22.8, - 22.7, - 22.8, - 22.8, - 22.9, - 23, - 23.1, - 23.2, - 23.3, - 23.4, - 23.5, - 23.5, - 23.6, - 23.6, - 23.7, - 23.7, - 23.7, - 23.8, - 23.8, - 23.8, - 23.8, - 24, - 24.3, - 24.6, - 24.9, - 25.2, - 25.3, - 25.4, - 26, - 26.4, - 26.6, - 27.3, - 28, - 28.3, - 28.1, - 27.8, - 27.4, - 26.5, - 26, - 26.6, - 27.2, - 27.9, - 28.5, - 29.2, - 30, - 31.6, - 32.4, - 33, - 33.5, - 33.1, - 32.2, - 31.2, - 29.6, - 28.5, - 27.6, - 26.7, - 25.9, - 25.5, - 25.4, - 25.3, - 25.3, - 25, - 24.4, - 23.5, - 23, - 22.6, - 22.4, - 22.2, - 22.2, - 22, - 22.1, - 22.2, - 22.3, - 22.3, - 22, - 21.6, - 21.5, - 21.3, - 21, - 20.5, - 20.1, - 20, - 20, - 19.8, - 19.6, - 19.1, - 18.7, - 18.4, - 18.1, - 17.5, - 16.3, - 15.2, - 13.7, - 12.8, - 12, - 11.2, - 10.6, - 10.2, - 10, - 9.8, - 10.1, - 10.4, - 10.7, - 10.9, - 11, - 11.1, - 11.1, - 11.2, - 11.2, - 11.1, - 11, - 10.9, - 10.8, - 10.6, - 10.3, - 10.1, - 9.9, - 9.5, - 9, - 8.8, - 8.4, - 7.9, - 7.5, - 7.3, - 7.1, - 6.8, - 6.5, - 6.4, - 6.2, - 5.9, - 5.6, - 5.3, - 5.1, - 4.9, - 4.7, - 4.5, - 4.4, - 4.2, - 3.9, - 3.8, - 3.6, - 3.4, - 3.2, - 3, - 2.8, - 2.5, - 2.2, - 1.8, - 1.5, - 16.2, - 16.1, - 15.9, - 15.6, - 15, - 14.5, - 14, - 13.8, - 14.1, - 14.6, - 14.9, - 15, - 15.1, - 15.1, - 15.2, - 15.2, - 15.2, - 15.3, - 15.3, - 15.4, - 15.5, - 15.5, - 15.5, - 15.4, - 15.2, - 15.2, - 15.3, - 15.4, - 15.7, - 16.1, - 16.8, - 17.9, - 18.5, - 18.6, - 18.5, - 18, - 17.8, - 18, - 17.6, - 16.9, - 16.6, - 17, - 16.9, - 16.4, - 16.2, - 16.3, - 16.3, - 16.3, - 16.4, - 16.5, - 16.7, - 17.1, - 17.6, - 17.9, - 18.5, - 19.1, - 19.5, - 19.6, - 19.6, - 19.8, - 19.9, - 19.9, - 20, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.4, - 20.5, - 20.6, - 20.6, - 20.7, - 20.9, - 21.1, - 21.3, - 21.6, - 21.8, - 21.8, - 21.9, - 21.9, - 21.8, - 21.7, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 22, - 22.2, - 22.3, - 22.5, - 22.8, - 22.9, - 23.1, - 23.1, - 22.9, - 22.6, - 22.2, - 22.1, - 22.1, - 22.2, - 22.3, - 22.3, - 22.4, - 22.5, - 22.5, - 22.6, - 22.6, - 22.7, - 22.8, - 22.9, - 22.9, - 23, - 23.1, - 23.2, - 23.3, - 23.4, - 23.4, - 23.5, - 23.5, - 23.6, - 23.6, - 23.6, - 23.6, - 23.7, - 23.8, - 24.2, - 24.7, - 25.4, - 25.9, - 26.3, - 26.6, - 26.6, - 26.6, - 26.9, - 27.3, - 27.2, - 27.7, - 28.3, - 28.4, - 28.4, - 28.2, - 27.8, - 26.9, - 26.3, - 27, - 27.7, - 28.5, - 29.1, - 29.6, - 30.5, - 31.7, - 32.6, - 33.2, - 33.5, - 33, - 32.3, - 31.5, - 29.9, - 28.9, - 27.8, - 26.8, - 26, - 25.5, - 25.3, - 25.3, - 25.2, - 24.6, - 24, - 23.3, - 22.8, - 22.6, - 22.4, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.7, - 22.7, - 22.5, - 22, - 21.6, - 21.5, - 21.3, - 20.9, - 20.4, - 20.2, - 20.1, - 19.9, - 19.8, - 19.4, - 18.9, - 18.6, - 18.3, - 18, - 17.1, - 15.7, - 13.9, - 13, - 12.2, - 11.4, - 10.7, - 10.4, - 10.1, - 10.2, - 10.5, - 10.8, - 11, - 11.2, - 11.4, - 11.5, - 11.5, - 11.5, - 11.5, - 11.4, - 11.2, - 11.1, - 10.9, - 10.7, - 10.4, - 10.1, - 10, - 9.6, - 9.2, - 8.9, - 8.6, - 8.2, - 7.4, - 7.1, - 6.9, - 6.7, - 6.4, - 6.3, - 6.1, - 5.8, - 5.5, - 5.2, - 5.1, - 4.9, - 4.7, - 4.5, - 4.3, - 4.1, - 3.9, - 3.7, - 3.5, - 3.3, - 3.1, - 2.7, - 2.5, - 2.2, - 1.9, - 1.6, - 1.3, - 15.8, - 15.7, - 15.5, - 15.1, - 14.7, - 14.3, - 13.9, - 13.8, - 14.2, - 14.7, - 14.9, - 15, - 15.1, - 15.1, - 15.1, - 15.1, - 15.1, - 15.1, - 15.1, - 15.2, - 15.3, - 15.3, - 15.2, - 15.1, - 15.1, - 15.2, - 15.4, - 15.6, - 15.9, - 16.4, - 17.2, - 17.9, - 18.3, - 18.2, - 17.6, - 17, - 16.8, - 17, - 16.8, - 16.4, - 16.2, - 16.3, - 16.2, - 16, - 16.2, - 16.2, - 16.2, - 16.3, - 16.5, - 16.7, - 16.9, - 17.5, - 18.4, - 18.9, - 19.3, - 19.5, - 19.6, - 19.7, - 19.7, - 19.8, - 20, - 20, - 20, - 20.1, - 20.2, - 20.3, - 20.4, - 20.4, - 20.4, - 20.5, - 20.6, - 20.7, - 20.7, - 20.7, - 21, - 21.1, - 21.2, - 21.4, - 21.6, - 21.7, - 21.7, - 21.7, - 21.7, - 21.6, - 21.7, - 21.7, - 21.8, - 21.8, - 21.9, - 22, - 22.2, - 22.3, - 22.5, - 22.7, - 22.9, - 22.9, - 22.7, - 22.4, - 22.3, - 22, - 21.9, - 22, - 22.1, - 22.2, - 22.2, - 22.3, - 22.4, - 22.4, - 22.5, - 22.5, - 22.6, - 22.7, - 22.8, - 22.9, - 23, - 23.1, - 23.2, - 23.2, - 23.3, - 23.3, - 23.4, - 23.4, - 23.5, - 23.5, - 23.6, - 23.7, - 23.9, - 24.3, - 25, - 25.8, - 26.6, - 27.2, - 27.5, - 27.6, - 27.6, - 27.7, - 27.8, - 28.1, - 28.1, - 28.3, - 28.5, - 28.5, - 28.5, - 28.3, - 27.9, - 27.1, - 26.6, - 27, - 27.6, - 28.1, - 28.6, - 29.5, - 30.9, - 31.8, - 32.7, - 33.3, - 33.7, - 33.2, - 32.5, - 31.7, - 29.9, - 29, - 27.9, - 27, - 26.1, - 25.4, - 25.3, - 25.3, - 25.2, - 24.8, - 24.5, - 23.9, - 23.1, - 22.7, - 22.6, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 22.9, - 22.9, - 22.8, - 22.3, - 21.8, - 21.6, - 21.4, - 21.2, - 20.8, - 20.3, - 20.1, - 20, - 19.8, - 19.6, - 19.2, - 18.8, - 18.5, - 18.1, - 17.4, - 16.1, - 14.6, - 13.3, - 12.4, - 11.5, - 10.8, - 10.5, - 10.4, - 10.5, - 10.8, - 11, - 11.3, - 11.5, - 11.7, - 11.9, - 11.9, - 11.8, - 11.8, - 11.6, - 11.4, - 11.2, - 11, - 10.8, - 10.5, - 10.3, - 10, - 9.8, - 9.4, - 9.1, - 8.8, - 8.5, - 7.8, - 7.1, - 6.8, - 6.6, - 6.3, - 6.1, - 5.9, - 5.7, - 5.4, - 5.2, - 5, - 4.8, - 4.6, - 4.4, - 4.2, - 4, - 3.8, - 3.6, - 3.4, - 3.2, - 3, - 2.7, - 2.4, - 2, - 1.7, - 1.5, - 1.3, - 15.4, - 15.2, - 15, - 14.7, - 14.4, - 14.1, - 13.7, - 13.8, - 14.4, - 14.7, - 14.9, - 15, - 15, - 14.9, - 14.9, - 14.8, - 14.9, - 14.9, - 14.9, - 14.9, - 15, - 15.1, - 15, - 14.9, - 15.1, - 15.3, - 15.4, - 15.7, - 15.9, - 16.4, - 17, - 17.3, - 17.5, - 17.4, - 16.9, - 16.4, - 16.3, - 16.4, - 16.4, - 16.2, - 16, - 16, - 16, - 16, - 16.1, - 16.1, - 16.3, - 16.5, - 16.8, - 17.1, - 17.4, - 18, - 19, - 19.4, - 19.5, - 19.6, - 19.7, - 19.7, - 19.7, - 19.8, - 19.9, - 19.8, - 19.7, - 19.7, - 20, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.5, - 21.6, - 21.6, - 21.6, - 21.6, - 21.6, - 21.7, - 21.7, - 21.8, - 21.9, - 22, - 22.2, - 22.3, - 22.4, - 22.6, - 22.7, - 22.6, - 22.4, - 22.1, - 22, - 21.8, - 21.9, - 22, - 22.1, - 22.1, - 22.2, - 22.2, - 22.3, - 22.4, - 22.5, - 22.6, - 22.6, - 22.7, - 22.8, - 22.9, - 22.9, - 23, - 23.1, - 23.2, - 23.2, - 23.3, - 23.3, - 23.4, - 23.4, - 23.5, - 23.7, - 24, - 24.4, - 25.1, - 26, - 26.9, - 27.5, - 27.8, - 27.9, - 28, - 28.1, - 28.1, - 28.2, - 28.3, - 28.4, - 28.5, - 28.6, - 28.6, - 28.6, - 28.4, - 28.2, - 27.7, - 27, - 27.3, - 27.9, - 28.2, - 28.4, - 29.3, - 31, - 31.9, - 32.7, - 33.3, - 33.7, - 33.3, - 32.5, - 31.7, - 29.9, - 28.9, - 27.8, - 27, - 26, - 25.4, - 25.3, - 25.3, - 25.2, - 25, - 24.9, - 24.4, - 23.3, - 22.9, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.9, - 22.9, - 22.8, - 22.4, - 22, - 21.6, - 21.5, - 21.3, - 21, - 20.5, - 20.2, - 20.1, - 19.9, - 19.8, - 19.4, - 18.9, - 18.7, - 18.3, - 17.8, - 16.5, - 15, - 13.6, - 12.4, - 11.3, - 11, - 10.7, - 10.6, - 10.8, - 11.1, - 11.3, - 11.6, - 11.8, - 12, - 12.1, - 12.1, - 12.1, - 11.9, - 11.8, - 11.6, - 11.3, - 11.1, - 10.9, - 10.7, - 10.5, - 10.2, - 9.9, - 9.8, - 9.5, - 9, - 8.7, - 8.1, - 7.4, - 7, - 6.7, - 6.4, - 6.2, - 5.9, - 5.6, - 5.3, - 5.1, - 4.9, - 4.7, - 4.4, - 4.2, - 4, - 3.9, - 3.8, - 3.6, - 3.5, - 3.3, - 3.1, - 2.8, - 2.5, - 2.2, - 1.9, - 1.5, - 1.2, - 15, - 14.8, - 14.6, - 14.3, - 14.1, - 13.9, - 13.7, - 14, - 14.5, - 14.7, - 14.9, - 14.9, - 14.8, - 14.7, - 14.6, - 14.5, - 14.6, - 14.6, - 14.6, - 14.7, - 14.8, - 14.9, - 14.9, - 14.8, - 15.1, - 15.3, - 15.5, - 15.7, - 15.9, - 16.2, - 16.6, - 16.8, - 16.8, - 16.7, - 16.5, - 16.2, - 16.1, - 16.1, - 16, - 15.9, - 15.9, - 16, - 15.9, - 15.9, - 16.1, - 16.2, - 16.5, - 16.7, - 17.1, - 17.7, - 18.2, - 18.7, - 19.3, - 19.5, - 19.5, - 19.6, - 19.7, - 19.7, - 19.8, - 19.8, - 19.7, - 19.2, - 18.8, - 18.9, - 19.5, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.8, - 21, - 21.1, - 21.2, - 21.3, - 21.3, - 21.4, - 21.5, - 21.5, - 21.5, - 21.6, - 21.6, - 21.6, - 21.7, - 21.8, - 21.8, - 21.9, - 22.1, - 22.2, - 22.3, - 22.4, - 22.4, - 22.3, - 22.1, - 21.8, - 21.8, - 21.8, - 21.9, - 22.1, - 22.1, - 22.2, - 22.3, - 22.3, - 22.3, - 22.4, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 22.8, - 22.9, - 22.9, - 23, - 23.1, - 23.2, - 23.3, - 23.3, - 23.4, - 23.5, - 23.8, - 24.2, - 24.7, - 25.5, - 26.2, - 27, - 27.6, - 27.8, - 28, - 28.1, - 28.1, - 28.2, - 28.3, - 28.4, - 28.4, - 28.5, - 28.6, - 28.7, - 28.7, - 28.8, - 28.7, - 28.6, - 28.3, - 27.8, - 27.6, - 28.4, - 29, - 29.1, - 29.5, - 31.3, - 32.1, - 32.8, - 33.4, - 33.8, - 33.4, - 32.6, - 31.7, - 30, - 28.7, - 27.6, - 26.7, - 26, - 25.4, - 25.3, - 25.2, - 25.2, - 25, - 24.9, - 24.4, - 23.4, - 22.9, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.8, - 22.7, - 22.3, - 21.9, - 21.7, - 21.5, - 21.4, - 21.2, - 20.8, - 20.3, - 20.1, - 20, - 19.8, - 19.6, - 19.1, - 18.7, - 18.4, - 18, - 16.8, - 15.2, - 13.8, - 12.2, - 11.4, - 11.3, - 11.1, - 10.8, - 11.1, - 11.5, - 11.6, - 11.8, - 12, - 12.1, - 12.2, - 12.3, - 12.2, - 12.1, - 11.9, - 11.7, - 11.5, - 11.3, - 11.1, - 10.9, - 10.7, - 10.6, - 10.2, - 10, - 9.8, - 9.3, - 8.8, - 8.4, - 7.9, - 7.4, - 7, - 6.6, - 6.4, - 6.1, - 5.7, - 5.4, - 5.1, - 4.8, - 4.6, - 4.4, - 4.2, - 4, - 4, - 3.9, - 3.8, - 3.6, - 3.4, - 3.1, - 2.9, - 2.8, - 2.5, - 2.2, - 1.7, - 1.4, - 14.5, - 14.3, - 14.2, - 14, - 13.9, - 13.7, - 13.6, - 14.2, - 14.5, - 14.7, - 14.7, - 14.7, - 14.6, - 14.4, - 14.3, - 14.2, - 14.2, - 14.3, - 14.4, - 14.5, - 14.6, - 14.7, - 14.7, - 14.8, - 15.1, - 15.3, - 15.5, - 15.7, - 15.8, - 16, - 16.2, - 16.3, - 16.3, - 16.3, - 16.2, - 16.1, - 15.9, - 15.8, - 15.8, - 15.8, - 15.9, - 15.9, - 15.8, - 16, - 16.2, - 16.4, - 16.7, - 16.9, - 17.4, - 18.3, - 19, - 19.2, - 19.4, - 19.6, - 19.6, - 19.6, - 19.7, - 19.8, - 19.8, - 19.8, - 19.5, - 18.6, - 18.2, - 18.1, - 18.6, - 19.2, - 19.4, - 19.5, - 19.9, - 20.3, - 20.4, - 20.5, - 20.6, - 20.8, - 21, - 21.1, - 21.1, - 21.2, - 21.3, - 21.4, - 21.4, - 21.4, - 21.5, - 21.6, - 21.5, - 21.6, - 21.7, - 21.7, - 21.8, - 21.8, - 21.9, - 22, - 22.2, - 22.3, - 22.1, - 22, - 21.7, - 21.7, - 21.7, - 21.8, - 22, - 22.1, - 22.1, - 22.2, - 22.3, - 22.4, - 22.4, - 22.4, - 22.5, - 22.6, - 22.7, - 22.7, - 22.7, - 22.8, - 22.9, - 22.9, - 23, - 23.1, - 23.1, - 23.2, - 23.4, - 23.7, - 24.2, - 24.8, - 25.4, - 26.1, - 26.8, - 27.2, - 27.6, - 27.8, - 27.9, - 28, - 28.1, - 28.2, - 28.2, - 28.3, - 28.4, - 28.4, - 28.4, - 28.6, - 28.7, - 28.8, - 28.9, - 28.9, - 28.9, - 28.8, - 28.6, - 28.4, - 28.3, - 29, - 29.5, - 30.7, - 31.7, - 32.3, - 33, - 33.6, - 33.9, - 33.5, - 32.7, - 31.9, - 30.6, - 29.1, - 28, - 27, - 26.1, - 25.4, - 25.3, - 25.2, - 25.1, - 24.9, - 24.7, - 23.9, - 23.2, - 22.9, - 22.8, - 22.7, - 22.7, - 22.8, - 22.7, - 22.8, - 22.7, - 22.6, - 22.4, - 22.1, - 21.8, - 21.6, - 21.5, - 21.4, - 21.3, - 20.9, - 20.4, - 20.1, - 20, - 19.8, - 19.7, - 19.2, - 18.8, - 18.4, - 18, - 16.7, - 15, - 13.5, - 11.9, - 11.7, - 11.7, - 11.3, - 11.1, - 11.4, - 11.7, - 11.9, - 12.1, - 12.3, - 12.3, - 12.5, - 12.5, - 12.5, - 12.4, - 12.1, - 11.9, - 11.7, - 11.6, - 11.5, - 11.2, - 11, - 10.8, - 10.6, - 10.3, - 10, - 9.6, - 9.1, - 8.7, - 8.4, - 8, - 7.4, - 6.9, - 6.5, - 6.3, - 6.1, - 5.7, - 5.3, - 5, - 4.8, - 4.5, - 4.3, - 4.2, - 4.2, - 4.1, - 3.9, - 3.7, - 3.5, - 3.2, - 3, - 2.8, - 2.6, - 2.2, - 1.9, - 1.5, - 14.1, - 13.9, - 13.8, - 13.7, - 13.7, - 13.6, - 13.6, - 14.3, - 14.6, - 14.7, - 14.6, - 14.6, - 14.4, - 14.1, - 14, - 14.1, - 14.3, - 14.3, - 14.4, - 14.5, - 14.5, - 14.6, - 14.7, - 14.9, - 15.1, - 15.3, - 15.5, - 15.6, - 15.8, - 15.9, - 16, - 16, - 16.1, - 16.1, - 16, - 15.9, - 15.7, - 15.7, - 15.7, - 15.8, - 15.8, - 15.8, - 15.8, - 16.1, - 16.4, - 16.6, - 16.9, - 17.2, - 18, - 18.8, - 19.3, - 19.4, - 19.5, - 19.5, - 19.6, - 19.6, - 19.7, - 19.8, - 19.8, - 19.7, - 19.4, - 18.4, - 18, - 17.7, - 17.9, - 18.1, - 18.3, - 19, - 19.7, - 20.2, - 20.4, - 20.5, - 20.6, - 20.7, - 20.9, - 21, - 21, - 21.2, - 21.3, - 21.4, - 21.3, - 21.4, - 21.5, - 21.5, - 21.5, - 21.6, - 21.7, - 21.7, - 21.7, - 21.7, - 21.8, - 21.8, - 21.9, - 21.9, - 21.7, - 21.6, - 21.6, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.4, - 22.5, - 22.6, - 22.6, - 22.6, - 22.6, - 22.7, - 22.7, - 22.8, - 22.9, - 23, - 23.1, - 23.2, - 23.3, - 23.5, - 23.7, - 24.2, - 24.8, - 25.6, - 26.3, - 26.8, - 27.2, - 27.4, - 27.6, - 27.7, - 27.8, - 27.8, - 27.9, - 28, - 28, - 28.1, - 28.2, - 28.2, - 28.2, - 28.2, - 28.3, - 28.6, - 28.8, - 28.9, - 29, - 29, - 29, - 29, - 29.1, - 29, - 29.1, - 29.4, - 31.2, - 31.9, - 32.6, - 33.2, - 33.7, - 33.8, - 33.5, - 32.8, - 31.9, - 30.7, - 29.1, - 28.2, - 27.3, - 26.3, - 25.4, - 25.3, - 25.2, - 25.1, - 24.7, - 24.1, - 23.1, - 22.8, - 22.7, - 22.6, - 22.6, - 22.6, - 22.6, - 22.6, - 22.6, - 22.5, - 22.4, - 22.2, - 22, - 21.7, - 21.6, - 21.5, - 21.4, - 21.2, - 20.8, - 20.3, - 20.1, - 20, - 19.8, - 19.6, - 19.2, - 18.8, - 18.4, - 18.1, - 16.3, - 14.2, - 12.7, - 12, - 12.1, - 11.9, - 11.5, - 11.3, - 11.7, - 12, - 12.3, - 12.4, - 12.5, - 12.6, - 12.8, - 12.8, - 12.8, - 12.7, - 12.4, - 12.2, - 12.2, - 12, - 11.8, - 11.6, - 11.5, - 11.2, - 10.9, - 10.5, - 10.3, - 10, - 9.5, - 9, - 8.7, - 8.5, - 7.8, - 7.2, - 6.8, - 6.6, - 6.3, - 6.1, - 5.5, - 5.2, - 5, - 4.7, - 4.5, - 4.4, - 4.4, - 4.2, - 4, - 3.8, - 3.5, - 3.2, - 3, - 2.8, - 2.5, - 2.2, - 1.9, - 1.6, - 13.7, - 13.6, - 13.5, - 13.5, - 13.5, - 13.5, - 13.8, - 14.4, - 14.6, - 14.6, - 14.5, - 14.4, - 14.2, - 13.9, - 14.1, - 14.3, - 14.5, - 14.6, - 14.6, - 14.6, - 14.7, - 14.8, - 14.9, - 15.1, - 15.2, - 15.4, - 15.5, - 15.6, - 15.8, - 15.8, - 15.9, - 15.9, - 15.9, - 15.9, - 15.8, - 15.6, - 15.6, - 15.7, - 15.7, - 15.8, - 15.7, - 15.7, - 16, - 16.3, - 16.5, - 16.8, - 17.3, - 17.9, - 18.6, - 19.3, - 19.4, - 19.4, - 19.5, - 19.5, - 19.5, - 19.6, - 19.7, - 19.8, - 19.8, - 19.7, - 19.1, - 18.1, - 17.7, - 17.5, - 17.6, - 17.7, - 18.1, - 18.9, - 19.8, - 20.2, - 20.3, - 20.4, - 20.6, - 20.6, - 20.7, - 20.7, - 20.7, - 21.1, - 21.2, - 21.2, - 21.2, - 21.1, - 21.2, - 21.3, - 21.4, - 21.6, - 21.6, - 21.6, - 21.6, - 21.7, - 21.7, - 21.7, - 21.7, - 21.6, - 21.6, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22, - 22.1, - 22.4, - 22.5, - 22.7, - 22.8, - 23, - 23.2, - 23.3, - 23.3, - 23.5, - 23.6, - 23.9, - 24, - 24.1, - 24.2, - 24.4, - 24.8, - 25.5, - 26.2, - 26.8, - 27.1, - 27.3, - 27.4, - 27.5, - 27.6, - 27.6, - 27.6, - 27.7, - 27.8, - 27.8, - 27.8, - 27.9, - 27.9, - 28, - 27.9, - 27.9, - 28.1, - 28.5, - 28.7, - 28.8, - 28.8, - 28.7, - 28.8, - 29, - 29.4, - 29.6, - 29.6, - 29.9, - 31.4, - 32.1, - 32.8, - 33.4, - 33.7, - 33.8, - 33.6, - 32.8, - 31.8, - 30.3, - 28.9, - 28, - 27.2, - 26.3, - 25.4, - 25.2, - 25.2, - 25.1, - 24.6, - 23.6, - 22.9, - 22.5, - 22.5, - 22.4, - 22.4, - 22.4, - 22.4, - 22.4, - 22.3, - 22.2, - 22.1, - 21.9, - 21.8, - 21.6, - 21.5, - 21.4, - 21.3, - 21, - 20.6, - 20.3, - 20.1, - 20, - 19.8, - 19.7, - 19.2, - 18.8, - 18.5, - 18.1, - 16, - 14.4, - 13.1, - 12.7, - 12.5, - 12.2, - 11.7, - 11.6, - 11.9, - 12.3, - 12.6, - 12.8, - 12.9, - 13, - 13.2, - 13.3, - 13.2, - 13.1, - 13, - 12.9, - 12.8, - 12.7, - 12.4, - 12.3, - 12, - 11.7, - 11.3, - 10.9, - 10.7, - 10.3, - 9.9, - 9.4, - 8.9, - 8.7, - 8.2, - 7.6, - 7.1, - 6.8, - 6.5, - 6.3, - 5.9, - 5.5, - 5.1, - 4.9, - 4.8, - 4.7, - 4.5, - 4.4, - 4.2, - 3.9, - 3.6, - 3.3, - 3.1, - 2.8, - 2.5, - 2.2, - 1.8, - 1.5, - 13.4, - 13.3, - 13.4, - 13.4, - 13.5, - 14, - 14.2, - 14.4, - 14.5, - 14.5, - 14.4, - 14.3, - 14, - 13.9, - 14.2, - 14.5, - 14.7, - 14.8, - 14.9, - 14.9, - 15, - 15.1, - 15.2, - 15.3, - 15.4, - 15.5, - 15.6, - 15.7, - 15.7, - 15.7, - 15.7, - 15.7, - 15.8, - 15.7, - 15.6, - 15.5, - 15.6, - 15.6, - 15.7, - 15.6, - 15.6, - 15.9, - 16.1, - 16.4, - 16.7, - 17, - 17.9, - 18.7, - 19.3, - 19.4, - 19.4, - 19.4, - 19.5, - 19.5, - 19.5, - 19.6, - 19.6, - 19.7, - 19.7, - 19.6, - 18.8, - 17.9, - 17.5, - 17.4, - 17.5, - 17.8, - 18.4, - 19.4, - 20, - 20.2, - 20.2, - 20.3, - 20.4, - 20.5, - 20.6, - 20.6, - 20.6, - 20.9, - 21, - 21, - 20.8, - 20.7, - 20.7, - 20.9, - 21.4, - 21.7, - 21.6, - 21.5, - 21.5, - 21.5, - 21.6, - 21.7, - 21.6, - 21.5, - 21.3, - 21.1, - 21.3, - 21.5, - 21.6, - 21.8, - 21.9, - 21.9, - 22, - 22.1, - 22.2, - 22.4, - 22.6, - 22.8, - 23.2, - 23.7, - 24.4, - 24.7, - 24.9, - 25.1, - 25.3, - 25.4, - 25.5, - 25.6, - 25.8, - 26.1, - 26.5, - 26.8, - 27, - 27.1, - 27.2, - 27.3, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.4, - 27.4, - 27.5, - 27.5, - 27.6, - 27.6, - 27.6, - 27.5, - 27.7, - 28, - 28.2, - 28.2, - 28.1, - 28.1, - 28.2, - 28.7, - 29.3, - 29.8, - 30.1, - 30.7, - 31.7, - 32.3, - 33, - 33.6, - 33.8, - 33.8, - 33.6, - 32.9, - 31.7, - 30.1, - 28.8, - 27.9, - 27, - 26.3, - 25.4, - 25.2, - 25.2, - 25.1, - 24.7, - 23.7, - 22.8, - 22.5, - 22.3, - 22.2, - 22.2, - 22.2, - 22.1, - 22, - 22, - 21.9, - 21.8, - 21.7, - 21.6, - 21.5, - 21.4, - 21.3, - 21.2, - 20.8, - 20.4, - 20.2, - 20, - 19.8, - 19.8, - 19.7, - 19.2, - 18.8, - 18.4, - 17.9, - 15.8, - 15.1, - 13.9, - 13.6, - 13.2, - 12.5, - 12.2, - 12, - 12.2, - 12.8, - 13.2, - 13.3, - 13.5, - 13.6, - 13.8, - 14, - 13.9, - 13.8, - 13.8, - 13.8, - 13.7, - 13.4, - 13.3, - 13.2, - 12.8, - 12.4, - 11.9, - 11.5, - 11.1, - 10.6, - 10.2, - 9.8, - 9.2, - 8.9, - 8.6, - 8.1, - 7.5, - 7, - 6.7, - 6.4, - 6.2, - 5.9, - 5.6, - 5.2, - 5, - 4.8, - 4.7, - 4.5, - 4.4, - 4.1, - 3.7, - 3.4, - 3.1, - 2.9, - 2.6, - 2.2, - 1.8, - 1.4, - 13.2, - 13.2, - 13.3, - 13.4, - 13.7, - 14.1, - 14.2, - 14.3, - 14.3, - 14.3, - 14.3, - 14.1, - 13.7, - 13.9, - 14.3, - 14.6, - 14.8, - 15, - 15.2, - 15.3, - 15.4, - 15.5, - 15.6, - 15.6, - 15.6, - 15.7, - 15.8, - 15.8, - 15.7, - 15.7, - 15.5, - 15.5, - 15.5, - 15.5, - 15.4, - 15.5, - 15.5, - 15.6, - 15.6, - 15.6, - 15.8, - 16, - 16.2, - 16.5, - 16.7, - 17.1, - 18, - 18.9, - 19.2, - 19.2, - 19.2, - 19.3, - 19.4, - 19.4, - 19.4, - 19.5, - 19.5, - 19.6, - 19.7, - 19.5, - 18.5, - 17.8, - 17.4, - 17.3, - 17.5, - 17.8, - 18.6, - 19.5, - 19.8, - 20, - 20.1, - 20.2, - 20.4, - 20.4, - 20.5, - 20.3, - 20, - 20.2, - 20.4, - 20.5, - 19.9, - 19.6, - 20, - 20.6, - 21.2, - 21.5, - 21.4, - 21.3, - 21.4, - 21.4, - 21.5, - 21.5, - 21.5, - 21.1, - 20.2, - 20.1, - 21, - 21.4, - 21.7, - 21.7, - 21.8, - 21.9, - 22.1, - 22.1, - 22.2, - 22.3, - 22.5, - 22.8, - 23.4, - 24.2, - 25.1, - 25.6, - 25.9, - 26, - 26.2, - 26.2, - 26.3, - 26.4, - 26.5, - 26.7, - 26.8, - 26.9, - 27, - 27.1, - 27.1, - 27.2, - 27.2, - 27.1, - 26.9, - 26.6, - 26.4, - 26.6, - 26.7, - 26.9, - 26.9, - 27.1, - 27.3, - 27.2, - 27, - 27.2, - 27.4, - 27.5, - 27.5, - 27.4, - 27.5, - 28.2, - 29.1, - 29.6, - 29.9, - 30.6, - 31.4, - 32, - 32.6, - 33.2, - 33.8, - 33.9, - 33.9, - 33.5, - 32.7, - 31.6, - 30, - 28.8, - 28, - 27.2, - 26.3, - 25.4, - 25.2, - 25.1, - 25.1, - 24.7, - 23.6, - 22.9, - 22.5, - 22.3, - 22, - 21.9, - 21.8, - 21.8, - 21.8, - 21.7, - 21.7, - 21.6, - 21.6, - 21.5, - 21.4, - 21.2, - 21.1, - 20.9, - 20.5, - 20.2, - 20.1, - 20, - 19.8, - 19.7, - 19.6, - 19.1, - 18.6, - 18.2, - 17.7, - 16.2, - 15.7, - 14.9, - 14.3, - 13.7, - 13, - 12.7, - 12.8, - 12.9, - 13.6, - 13.9, - 14.1, - 14.3, - 14.5, - 14.7, - 14.8, - 14.8, - 14.7, - 14.9, - 15, - 14.9, - 14.7, - 14.3, - 14.2, - 13.7, - 13.3, - 12.7, - 12.1, - 11.6, - 11.1, - 10.6, - 10.1, - 9.7, - 9.2, - 8.8, - 8.5, - 7.9, - 7.3, - 6.9, - 6.7, - 6.4, - 6.2, - 5.9, - 5.5, - 5.2, - 5, - 4.8, - 4.6, - 4.4, - 4.2, - 3.9, - 3.5, - 3.2, - 2.9, - 2.6, - 2.3, - 1.9, - 1.5, - 13.1, - 13.2, - 13.3, - 13.4, - 13.6, - 13.8, - 13.9, - 13.9, - 14, - 14.1, - 13.9, - 13.8, - 13.8, - 14.1, - 14.4, - 14.7, - 15, - 15.2, - 15.4, - 15.7, - 16, - 16.2, - 16.3, - 16.3, - 16.2, - 16.1, - 16, - 15.9, - 15.7, - 15.6, - 15.5, - 15.4, - 15.4, - 15.4, - 15.4, - 15.4, - 15.5, - 15.5, - 15.5, - 15.7, - 15.9, - 16.1, - 16.3, - 16.5, - 16.7, - 17, - 17.7, - 18.4, - 18.6, - 18.4, - 18.4, - 18.7, - 19, - 19, - 19, - 19.3, - 19.4, - 19.5, - 19.6, - 19.4, - 18.3, - 17.6, - 17.3, - 17.2, - 17.4, - 17.7, - 18.2, - 18.8, - 19, - 19.2, - 19.5, - 20, - 20.3, - 20.3, - 20.2, - 19.5, - 18.9, - 19.1, - 19.4, - 19.4, - 18.7, - 18.6, - 19.1, - 20.3, - 20.7, - 21, - 21, - 20.9, - 21.2, - 21.4, - 21.4, - 21.5, - 21.3, - 19.9, - 19.1, - 19.1, - 20.5, - 21.5, - 21.7, - 21.7, - 21.8, - 21.9, - 22.1, - 22.2, - 22.4, - 22.5, - 22.7, - 23.1, - 23.7, - 24.5, - 25.3, - 25.8, - 26.1, - 26.2, - 26.4, - 26.5, - 26.5, - 26.6, - 26.7, - 26.7, - 26.7, - 26.7, - 26.7, - 26.7, - 26.8, - 26.8, - 26.7, - 26.4, - 26, - 25.5, - 25.3, - 25.6, - 25.8, - 26.1, - 26.4, - 26.7, - 26.9, - 26.9, - 26.6, - 26.8, - 26.8, - 26.8, - 26.8, - 26.9, - 27.3, - 28.1, - 29.1, - 29.7, - 30.2, - 31.2, - 31.8, - 32.4, - 32.9, - 33.5, - 33.9, - 34, - 33.9, - 33.5, - 32.5, - 31.4, - 29.6, - 28.5, - 27.7, - 27, - 26.2, - 25.4, - 25.2, - 25.1, - 25.1, - 24.6, - 23.7, - 22.9, - 22.6, - 22.3, - 22.2, - 22.1, - 21.9, - 21.7, - 21.6, - 21.5, - 21.5, - 21.5, - 21.4, - 21.3, - 21.2, - 21, - 20.7, - 20.4, - 20.2, - 20, - 19.9, - 19.8, - 19.7, - 19.5, - 19, - 18.4, - 18.1, - 17.9, - 17.5, - 17.3, - 16.6, - 16, - 15.2, - 14.5, - 13.7, - 13.3, - 13.6, - 13.7, - 14.6, - 14.9, - 15.1, - 15.3, - 15.4, - 15.6, - 15.6, - 15.7, - 15.8, - 16, - 16.1, - 16.2, - 16.1, - 15.8, - 15.4, - 14.9, - 14.3, - 13.5, - 12.8, - 12.1, - 11.5, - 11, - 10.5, - 10, - 9.5, - 9.1, - 8.7, - 8.3, - 7.7, - 7.3, - 6.9, - 6.7, - 6.4, - 6.1, - 5.8, - 5.3, - 5.1, - 4.9, - 4.7, - 4.5, - 4.3, - 4, - 3.6, - 3.3, - 2.9, - 2.7, - 2.3, - 1.9, - 1.5, - 13.1, - 13.2, - 13.3, - 13.3, - 13.4, - 13.5, - 13.5, - 13.5, - 13.6, - 13.7, - 13.6, - 13.7, - 14, - 14.3, - 14.5, - 14.8, - 15.1, - 15.4, - 15.7, - 16.2, - 16.7, - 17.1, - 17.2, - 17.2, - 17.1, - 16.9, - 16.6, - 16.2, - 15.9, - 15.7, - 15.5, - 15.3, - 15.3, - 15.3, - 15.3, - 15.4, - 15.4, - 15.4, - 15.5, - 15.8, - 16, - 16.2, - 16.3, - 16.5, - 16.6, - 16.9, - 17.2, - 17.6, - 17.7, - 17.5, - 17.4, - 17.7, - 18.1, - 18.3, - 18.5, - 19.2, - 19.4, - 19.5, - 19.5, - 19.3, - 18.2, - 17.5, - 17.2, - 17.2, - 17.3, - 17.5, - 17.8, - 18, - 18.1, - 18.4, - 18.8, - 19.4, - 19.8, - 19.8, - 19.5, - 18.7, - 18.1, - 18.3, - 18.6, - 18.6, - 18.3, - 18.1, - 18.5, - 19.1, - 20, - 20.2, - 20.4, - 20.7, - 21, - 21.3, - 21.3, - 21.4, - 20.8, - 19.4, - 18.9, - 19.2, - 20.5, - 21.5, - 21.7, - 21.7, - 21.9, - 22, - 22.3, - 22.5, - 22.6, - 22.7, - 22.9, - 23.3, - 23.7, - 24.5, - 25.2, - 25.6, - 26, - 26.2, - 26.4, - 26.5, - 26.6, - 26.6, - 26.6, - 26.6, - 26.4, - 26.1, - 25.9, - 25.8, - 25.9, - 25.8, - 25.6, - 25.3, - 24.7, - 24.3, - 24.3, - 24.9, - 25.5, - 26, - 26.3, - 26.5, - 26.7, - 26.7, - 26.3, - 26.3, - 26.2, - 26.3, - 26.5, - 26.8, - 27.1, - 27.7, - 28.5, - 29.2, - 29.8, - 31.2, - 32.1, - 32.6, - 33.1, - 33.7, - 33.9, - 34, - 33.9, - 33.3, - 32.3, - 31.1, - 29.3, - 28.2, - 27.4, - 26.6, - 26, - 25.3, - 25.2, - 25.1, - 25.1, - 24.7, - 23.9, - 23.1, - 22.7, - 22.5, - 22.4, - 22.3, - 22, - 21.7, - 21.5, - 21.4, - 21.4, - 21.3, - 21.3, - 21.1, - 21, - 20.7, - 20.3, - 20.1, - 20, - 19.9, - 19.8, - 19.8, - 19.7, - 19.5, - 18.7, - 18.1, - 18, - 18.2, - 18.2, - 18.3, - 18.1, - 17.3, - 16.3, - 15.6, - 14.7, - 14.4, - 14.8, - 15, - 15.6, - 15.8, - 16, - 16.3, - 16.3, - 16.4, - 16.5, - 16.5, - 16.7, - 17, - 17.2, - 17.4, - 17.2, - 17, - 16.9, - 16.4, - 15.6, - 14.4, - 13.5, - 12.6, - 11.9, - 11.4, - 10.9, - 10.4, - 9.9, - 9.4, - 8.9, - 8.6, - 8.1, - 7.6, - 7.2, - 6.8, - 6.5, - 6.3, - 6, - 5.4, - 5.2, - 5, - 4.8, - 4.6, - 4.4, - 4.1, - 3.7, - 3.4, - 3, - 2.7, - 2.4, - 1.9, - 1.5, - 13.1, - 13.2, - 13.2, - 13.3, - 13.3, - 13.3, - 13.4, - 13.5, - 13.6, - 13.6, - 13.8, - 14, - 14.3, - 14.5, - 14.7, - 14.9, - 15.2, - 15.7, - 16.3, - 17, - 17.4, - 17.7, - 17.8, - 17.8, - 17.8, - 17.6, - 17.3, - 16.7, - 16.1, - 15.8, - 15.5, - 15.3, - 15.2, - 15.3, - 15.3, - 15.3, - 15.3, - 15.4, - 15.6, - 15.9, - 16, - 16.2, - 16.3, - 16.5, - 16.6, - 16.7, - 16.8, - 16.9, - 17, - 16.9, - 16.9, - 17.1, - 17.4, - 17.9, - 18.7, - 19.3, - 19.4, - 19.4, - 19.5, - 18.9, - 18, - 17.5, - 17.3, - 17.1, - 17.2, - 17.4, - 17.5, - 17.6, - 17.7, - 17.9, - 18.2, - 18.5, - 18.6, - 18.7, - 18.4, - 18, - 17.8, - 18, - 18.1, - 18.2, - 18.1, - 18, - 18.2, - 18.6, - 19.2, - 19.5, - 20.3, - 20.8, - 21.1, - 21.2, - 21.3, - 21.3, - 20.3, - 19.1, - 18.8, - 19.4, - 20.7, - 21.4, - 21.7, - 21.8, - 21.9, - 22.1, - 22.4, - 22.6, - 22.7, - 22.9, - 23.1, - 23.4, - 23.5, - 24, - 24.7, - 25.1, - 25.5, - 25.8, - 26, - 26.2, - 26.3, - 26.4, - 26.3, - 26, - 25.6, - 25.1, - 24.8, - 24.7, - 24.6, - 24.6, - 24.4, - 24, - 23.7, - 23.9, - 24.3, - 24.9, - 25.6, - 26.2, - 26.5, - 26.6, - 26.6, - 26.5, - 26.2, - 25.9, - 25.8, - 26.2, - 26.5, - 26.6, - 26.7, - 27.1, - 27.6, - 28.3, - 29.5, - 31.3, - 32, - 32.5, - 33, - 33.7, - 33.9, - 34.1, - 33.9, - 33.2, - 32.2, - 31, - 29.4, - 28.4, - 27.5, - 26.7, - 25.9, - 25.3, - 25.2, - 25.1, - 25, - 24.8, - 24.4, - 23.5, - 22.9, - 22.6, - 22.4, - 22.3, - 22.1, - 21.8, - 21.5, - 21.4, - 21.3, - 21.2, - 21.2, - 21, - 20.7, - 20.3, - 20.1, - 20, - 20, - 20, - 19.9, - 19.8, - 19.8, - 19.6, - 19.2, - 18.7, - 18.8, - 19, - 18.9, - 18.7, - 18.5, - 18.1, - 17.4, - 16.8, - 15.9, - 16, - 16.2, - 16.2, - 16.5, - 16.7, - 17, - 17.2, - 17.1, - 17.2, - 17.5, - 17.7, - 17.8, - 17.9, - 18.1, - 18.4, - 18.3, - 18.1, - 18, - 17.6, - 16.9, - 15.6, - 14.2, - 13.3, - 12.5, - 11.8, - 11.3, - 10.7, - 10.2, - 9.7, - 9.1, - 8.7, - 8.4, - 7.8, - 7.3, - 6.9, - 6.6, - 6.4, - 6.1, - 5.5, - 5.2, - 5, - 4.8, - 4.6, - 4.4, - 4.1, - 3.8, - 3.4, - 3.1, - 2.7, - 2.4, - 2, - 1.5, - 13, - 13.1, - 13.1, - 13.2, - 13.2, - 13.2, - 13.5, - 13.8, - 14, - 14, - 14.1, - 14.3, - 14.4, - 14.6, - 14.8, - 15, - 15.5, - 16.2, - 16.9, - 17.4, - 17.7, - 17.8, - 17.9, - 17.9, - 17.9, - 17.7, - 17.5, - 17, - 16.3, - 15.8, - 15.5, - 15.2, - 15.2, - 15.3, - 15.3, - 15.3, - 15.3, - 15.5, - 15.7, - 15.9, - 16, - 16.2, - 16.3, - 16.4, - 16.5, - 16.6, - 16.6, - 16.6, - 16.7, - 16.7, - 16.8, - 17.1, - 17.6, - 18.4, - 19.1, - 19.4, - 19.3, - 19.1, - 19.2, - 18.7, - 17.9, - 17.5, - 17.3, - 17.1, - 17, - 17.2, - 17.4, - 17.5, - 17.6, - 17.8, - 17.8, - 17.9, - 17.8, - 17.8, - 17.7, - 17.6, - 17.7, - 17.8, - 17.9, - 17.9, - 17.9, - 18.1, - 18.4, - 18.8, - 19.3, - 19.7, - 20.5, - 21, - 21.2, - 21.2, - 21.3, - 20.9, - 19.8, - 18.9, - 18.7, - 19.5, - 20.9, - 21.6, - 21.7, - 21.8, - 21.9, - 22.2, - 22.5, - 22.7, - 22.7, - 22.9, - 23, - 23.1, - 23.2, - 23.5, - 23.9, - 24.2, - 24.5, - 24.8, - 25.1, - 25.4, - 25.5, - 25.6, - 25.4, - 24.9, - 24.2, - 23.7, - 23.6, - 23.6, - 23.6, - 23.7, - 23.6, - 23.4, - 23.5, - 23.9, - 24.4, - 25, - 25.6, - 26.2, - 26.6, - 26.6, - 26.4, - 26.2, - 26, - 25.6, - 25.6, - 26, - 26.2, - 26.1, - 26.1, - 26.5, - 27, - 27.8, - 29, - 30.9, - 31.6, - 32.3, - 32.9, - 33.7, - 33.9, - 34.1, - 33.9, - 33, - 32.2, - 31.2, - 29.5, - 28.5, - 27.6, - 26.8, - 26, - 25.2, - 25.1, - 25, - 24.9, - 24.8, - 24.5, - 23.7, - 23, - 22.6, - 22.5, - 22.3, - 22.1, - 21.8, - 21.5, - 21.3, - 21.2, - 21.1, - 21, - 20.7, - 20.3, - 20.2, - 20.1, - 20.1, - 20.1, - 20.1, - 20, - 19.9, - 19.9, - 19.8, - 19.7, - 19.5, - 19.6, - 19.5, - 19.2, - 18.9, - 18.7, - 18.6, - 18.3, - 18.1, - 17.3, - 17.4, - 17.3, - 17.2, - 17.4, - 17.8, - 18.1, - 18.1, - 18.1, - 18.2, - 18.3, - 18.5, - 18.6, - 18.6, - 18.7, - 18.8, - 18.9, - 18.8, - 18.7, - 18.4, - 17.8, - 16.8, - 15.1, - 14, - 13, - 12.3, - 11.6, - 11.1, - 10.5, - 9.9, - 9.4, - 8.9, - 8.5, - 8.1, - 7.5, - 7, - 6.7, - 6.4, - 6.2, - 5.7, - 5.3, - 5.1, - 4.9, - 4.7, - 4.5, - 4.2, - 3.9, - 3.5, - 3.2, - 2.8, - 2.4, - 2, - 1.6, - 12.9, - 12.9, - 13, - 13.1, - 13.1, - 13.2, - 13.6, - 14, - 14, - 14.1, - 14.2, - 14.3, - 14.5, - 14.6, - 14.8, - 15.2, - 15.8, - 16.5, - 17.1, - 17.5, - 17.5, - 17.7, - 17.8, - 17.8, - 17.7, - 17.6, - 17.3, - 16.8, - 16.3, - 15.8, - 15.5, - 15.1, - 15.2, - 15.2, - 15.2, - 15.2, - 15.3, - 15.6, - 15.8, - 15.9, - 16, - 16.1, - 16.2, - 16.3, - 16.4, - 16.4, - 16.4, - 16.5, - 16.5, - 16.6, - 16.8, - 17.2, - 17.8, - 18.8, - 19.2, - 19.1, - 18.6, - 18.3, - 18.5, - 18.2, - 17.7, - 17.4, - 17.3, - 17.1, - 16.9, - 17.1, - 17.3, - 17.4, - 17.6, - 17.7, - 17.7, - 17.5, - 17.4, - 17.4, - 17.5, - 17.5, - 17.6, - 17.7, - 17.7, - 17.8, - 18, - 18.3, - 18.9, - 19.5, - 20.1, - 20.4, - 20.6, - 20.9, - 21.1, - 21.2, - 21.1, - 20.3, - 19.2, - 18.6, - 18.8, - 19.8, - 21, - 21.6, - 21.8, - 21.9, - 22, - 22.2, - 22.5, - 22.6, - 22.7, - 22.7, - 22.8, - 22.8, - 22.9, - 23, - 23.2, - 23.5, - 23.5, - 23.6, - 23.9, - 24.3, - 24.4, - 24.4, - 24.2, - 23.7, - 23.2, - 22.9, - 22.9, - 23, - 23.1, - 23.2, - 23.2, - 23.2, - 23.5, - 23.8, - 24.3, - 24.9, - 25.5, - 26.1, - 26.6, - 26.7, - 26.4, - 26, - 25.7, - 25.2, - 25.3, - 25.6, - 25.8, - 26, - 26.2, - 26.4, - 26.7, - 27.1, - 27.9, - 29.4, - 31.3, - 32.2, - 32.8, - 33.6, - 33.9, - 34.1, - 33.8, - 33, - 32.1, - 31.2, - 29.6, - 28.6, - 27.7, - 26.9, - 25.9, - 25.2, - 25.1, - 25, - 24.7, - 24.5, - 24.1, - 23.4, - 22.8, - 22.5, - 22.4, - 22.3, - 22.1, - 21.8, - 21.5, - 21.3, - 21.2, - 21.1, - 20.9, - 20.5, - 20.3, - 20.2, - 20.2, - 20.2, - 20.2, - 20.1, - 20, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 19.9, - 19.8, - 19.4, - 19.2, - 19, - 18.8, - 18.7, - 18.5, - 18.2, - 18.2, - 18.1, - 18.2, - 18.3, - 18.5, - 18.6, - 18.6, - 18.7, - 18.7, - 18.8, - 18.9, - 19, - 19.1, - 19.2, - 19.2, - 19.2, - 19.2, - 19.1, - 18.8, - 18.4, - 17.6, - 16.2, - 14.7, - 13.6, - 12.7, - 12, - 11.3, - 10.7, - 10, - 9.5, - 8.9, - 8.6, - 8.3, - 7.7, - 7.2, - 6.8, - 6.5, - 6.2, - 5.9, - 5.5, - 5.2, - 5, - 4.8, - 4.5, - 4.3, - 3.9, - 3.6, - 3.2, - 2.8, - 2.4, - 1.9, - 1.5, - 12.8, - 12.8, - 12.9, - 13, - 13.2, - 13.2, - 13.4, - 13.7, - 13.9, - 14, - 14.1, - 14.2, - 14.4, - 14.6, - 14.8, - 15.3, - 15.9, - 16.6, - 17, - 17.2, - 17.3, - 17.5, - 17.7, - 17.7, - 17.6, - 17.3, - 16.9, - 16.4, - 16, - 15.7, - 15.4, - 15.1, - 15.1, - 15.1, - 15.1, - 15.2, - 15.4, - 15.6, - 15.8, - 15.9, - 16, - 16.1, - 16.1, - 16.2, - 16.3, - 16.3, - 16.3, - 16.3, - 16.4, - 16.6, - 16.8, - 17.2, - 17.8, - 18.6, - 18.9, - 18.5, - 17.8, - 17.5, - 17.7, - 17.6, - 17.4, - 17.3, - 17.2, - 17, - 16.9, - 17, - 17.2, - 17.4, - 17.5, - 17.5, - 17.4, - 17.3, - 17.3, - 17.4, - 17.5, - 17.7, - 17.7, - 17.8, - 17.9, - 18, - 18.3, - 18.6, - 19.4, - 20.2, - 20.5, - 20.7, - 20.7, - 20.8, - 21, - 21.2, - 20.9, - 19.8, - 18.8, - 18.5, - 18.9, - 20.1, - 21.1, - 21.6, - 21.8, - 21.9, - 22, - 22.1, - 22.4, - 22.5, - 22.5, - 22.5, - 22.5, - 22.5, - 22.5, - 22.5, - 22.7, - 22.9, - 22.7, - 22.9, - 23.1, - 23.4, - 23.4, - 23.4, - 23.4, - 23.1, - 22.6, - 22.4, - 22.5, - 22.7, - 22.9, - 23, - 23.2, - 23.3, - 23.5, - 23.8, - 24.3, - 25, - 25.6, - 26.2, - 26.7, - 26.8, - 26.6, - 26.2, - 25.4, - 24.8, - 25.2, - 25.7, - 26.2, - 26.7, - 27.2, - 27.3, - 27.2, - 27, - 27.6, - 29.4, - 31.2, - 32.3, - 32.8, - 33.5, - 33.9, - 34, - 33.8, - 32.9, - 32, - 30.9, - 29.4, - 28.5, - 27.5, - 26.7, - 25.8, - 25.2, - 25, - 24.9, - 24.3, - 23.6, - 23.3, - 22.8, - 22.5, - 22.3, - 22.2, - 22.1, - 22, - 21.7, - 21.5, - 21.3, - 21.2, - 21.1, - 20.8, - 20.5, - 20.4, - 20.3, - 20.3, - 20.3, - 20.2, - 20.1, - 20.1, - 20, - 20, - 19.9, - 19.9, - 20, - 20, - 20, - 19.8, - 19.5, - 19.3, - 19.3, - 19.1, - 18.8, - 18.5, - 18.5, - 18.5, - 18.6, - 18.7, - 18.9, - 19, - 19.1, - 19.2, - 19.2, - 19.2, - 19.4, - 19.5, - 19.5, - 19.6, - 19.6, - 19.6, - 19.6, - 19.4, - 19.1, - 18.9, - 18.4, - 17.1, - 15.7, - 14.1, - 13.2, - 12.3, - 11.5, - 10.9, - 10.2, - 9.7, - 9.2, - 8.7, - 8.4, - 7.9, - 7.3, - 6.9, - 6.5, - 6.3, - 6, - 5.6, - 5.3, - 5.1, - 4.8, - 4.6, - 4.3, - 4, - 3.6, - 3.2, - 2.9, - 2.5, - 2.1, - 1.8, - 12.6, - 12.7, - 12.9, - 13.1, - 13.3, - 13.4, - 13.6, - 13.8, - 14.1, - 14.2, - 14.3, - 14.4, - 14.5, - 14.5, - 14.7, - 15.2, - 15.8, - 16.4, - 16.8, - 17, - 17.2, - 17.5, - 17.7, - 17.7, - 17.6, - 17.1, - 16.4, - 16, - 15.7, - 15.6, - 15.2, - 15, - 15, - 15, - 15.1, - 15.3, - 15.5, - 15.6, - 15.7, - 15.8, - 15.9, - 15.9, - 16, - 16, - 16.1, - 16.1, - 16.2, - 16.3, - 16.5, - 16.6, - 16.8, - 17, - 17.4, - 17.9, - 18, - 17.6, - 17.3, - 17.2, - 17.2, - 17.3, - 17.3, - 17.2, - 17.1, - 16.9, - 16.8, - 17, - 17.1, - 17.2, - 17.3, - 17.2, - 17.2, - 17.3, - 17.4, - 17.6, - 17.7, - 17.8, - 18, - 18.1, - 18.2, - 18.3, - 18.8, - 19.3, - 19.9, - 20.4, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21, - 20.7, - 19.4, - 18.6, - 18.5, - 19.1, - 20.4, - 21.2, - 21.5, - 21.7, - 21.8, - 22, - 22.1, - 22.2, - 22.3, - 22.4, - 22.4, - 22.4, - 22.3, - 22.3, - 22.2, - 22.3, - 22.3, - 22.2, - 22.2, - 22.6, - 23, - 23.1, - 22.9, - 22.9, - 22.5, - 22.2, - 22.4, - 22.8, - 23.1, - 23.2, - 23.3, - 23.5, - 23.7, - 24, - 24.4, - 24.9, - 25.6, - 26.4, - 26.9, - 27.1, - 27.1, - 26.8, - 26.1, - 25, - 24.7, - 25.3, - 25.8, - 26.4, - 27.1, - 27.9, - 28.6, - 28.4, - 27.7, - 27.9, - 30.1, - 31.5, - 32.4, - 32.8, - 33.5, - 33.9, - 34, - 33.8, - 32.9, - 32, - 30.7, - 29.2, - 28.2, - 27.2, - 26.5, - 25.7, - 25.1, - 25, - 24.9, - 24.3, - 23.1, - 22.7, - 22.4, - 22.2, - 22.1, - 22, - 21.9, - 21.8, - 21.6, - 21.4, - 21.2, - 21.1, - 21, - 20.8, - 20.6, - 20.5, - 20.4, - 20.4, - 20.3, - 20.2, - 20.2, - 20.1, - 20.1, - 20, - 20, - 20, - 20, - 20, - 20, - 20, - 19.9, - 19.8, - 19.7, - 19.5, - 19.2, - 18.8, - 18.8, - 19, - 19, - 19.1, - 19.3, - 19.4, - 19.6, - 19.8, - 19.8, - 19.7, - 19.7, - 19.9, - 20, - 20, - 20, - 20, - 20, - 19.8, - 19.5, - 19.2, - 18.9, - 17.9, - 16.5, - 14.8, - 13.7, - 12.7, - 11.8, - 11.1, - 10.4, - 9.8, - 9.3, - 8.8, - 8.5, - 7.9, - 7.3, - 6.9, - 6.6, - 6.3, - 6.1, - 5.8, - 5.4, - 5.1, - 4.8, - 4.6, - 4.4, - 4, - 3.6, - 3.3, - 3, - 2.6, - 2.3, - 2, - 12.6, - 12.8, - 13, - 13.1, - 13.4, - 13.7, - 14.2, - 14.4, - 14.5, - 14.7, - 14.7, - 14.8, - 14.8, - 14.7, - 14.7, - 15.1, - 15.8, - 16.2, - 16.5, - 16.8, - 17.2, - 17.6, - 17.8, - 17.7, - 17.4, - 16.8, - 16, - 15.6, - 15.4, - 15.4, - 15.1, - 15, - 15, - 15, - 15.1, - 15.3, - 15.4, - 15.5, - 15.6, - 15.6, - 15.7, - 15.8, - 15.8, - 15.9, - 16, - 16.1, - 16.2, - 16.3, - 16.5, - 16.6, - 16.8, - 16.9, - 17.1, - 17.2, - 17.2, - 17.1, - 17, - 17, - 17.1, - 17.2, - 17.2, - 17.1, - 17, - 16.8, - 16.7, - 16.9, - 16.9, - 17, - 17, - 17.1, - 17.2, - 17.4, - 17.6, - 17.7, - 17.9, - 18, - 18.3, - 18.7, - 18.9, - 18.9, - 19.5, - 20, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 21, - 20.7, - 19.3, - 18.4, - 18.4, - 19.2, - 20.3, - 21.1, - 21.4, - 21.5, - 21.6, - 21.9, - 22, - 22, - 22.1, - 22.3, - 22.4, - 22.3, - 22.2, - 22, - 21.8, - 21.8, - 21.6, - 21.7, - 21.8, - 22, - 22.4, - 22.6, - 22.3, - 22.3, - 22.1, - 22.2, - 22.8, - 23.3, - 23.5, - 23.6, - 23.8, - 24, - 24.3, - 24.9, - 25.6, - 26.2, - 26.6, - 27.2, - 27.5, - 27.5, - 27.4, - 27, - 26, - 24.8, - 24.6, - 25.2, - 25.7, - 26.4, - 27.2, - 28.1, - 29.1, - 29.3, - 28.6, - 28.3, - 29.9, - 31.6, - 32.4, - 32.9, - 33.5, - 33.9, - 34.1, - 33.8, - 33, - 32, - 30.6, - 29.2, - 28.2, - 27.2, - 26.4, - 25.6, - 25.1, - 25, - 24.9, - 24.5, - 23.7, - 22.9, - 22.4, - 22.1, - 22, - 21.8, - 21.7, - 21.6, - 21.5, - 21.3, - 21.2, - 21.1, - 21.1, - 20.9, - 20.8, - 20.6, - 20.5, - 20.4, - 20.3, - 20.2, - 20.2, - 20.2, - 20.1, - 20.1, - 20.1, - 20.1, - 20, - 20.1, - 20.1, - 20.1, - 20.1, - 20.1, - 20, - 19.8, - 19.5, - 19.2, - 19.2, - 19.4, - 19.5, - 19.6, - 19.8, - 19.9, - 20.1, - 20.2, - 20.2, - 20.1, - 20.2, - 20.3, - 20.4, - 20.4, - 20.5, - 20.5, - 20.4, - 20.1, - 19.8, - 19.4, - 19.1, - 18.6, - 17, - 15.5, - 14.1, - 12.9, - 12, - 11.3, - 10.6, - 10, - 9.4, - 8.9, - 8.5, - 8, - 7.4, - 7, - 6.6, - 6.3, - 6.1, - 5.8, - 5.5, - 5.2, - 4.9, - 4.7, - 4.4, - 4, - 3.7, - 3.4, - 3.1, - 2.8, - 2.5, - 2.3, - 12.7, - 12.9, - 13, - 13.2, - 13.5, - 14.2, - 14.8, - 15, - 15.1, - 15.2, - 15.2, - 15.2, - 15.2, - 15, - 14.9, - 15.3, - 15.9, - 16.3, - 16.5, - 16.9, - 17.3, - 17.7, - 17.8, - 17.8, - 17.4, - 16.6, - 15.8, - 15.4, - 15.3, - 15.1, - 14.9, - 14.9, - 14.9, - 15, - 15.2, - 15.2, - 15.3, - 15.4, - 15.5, - 15.6, - 15.7, - 15.8, - 15.9, - 16, - 16.1, - 16.2, - 16.3, - 16.4, - 16.5, - 16.6, - 16.7, - 16.8, - 16.9, - 16.9, - 16.9, - 16.8, - 16.8, - 16.9, - 17, - 17, - 17, - 16.9, - 16.8, - 16.6, - 16.7, - 16.8, - 16.8, - 16.9, - 17, - 17.2, - 17.3, - 17.5, - 17.7, - 17.8, - 18, - 18.2, - 18.5, - 19.3, - 19.7, - 19.6, - 20, - 20.2, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21, - 20.9, - 20.7, - 19.4, - 18.4, - 18.4, - 18.9, - 19.6, - 20.3, - 20.8, - 20.9, - 20.9, - 21.3, - 21.6, - 21.9, - 22, - 22.1, - 22.1, - 22, - 21.6, - 21.3, - 20.9, - 21.1, - 21.2, - 21.4, - 21.4, - 21.6, - 21.7, - 21.8, - 21.8, - 21.9, - 22.1, - 22.5, - 23.2, - 23.5, - 23.6, - 23.9, - 24.2, - 24.5, - 25.2, - 25.9, - 26.6, - 27.1, - 27.3, - 27.5, - 27.6, - 27.6, - 27.4, - 26.9, - 25.9, - 24.9, - 24.5, - 25.1, - 25.8, - 26.5, - 27.4, - 28.4, - 29.4, - 29.8, - 29.6, - 29.3, - 30.5, - 31.8, - 32.5, - 33, - 33.7, - 34, - 34.1, - 33.8, - 33, - 32.1, - 30.8, - 29.5, - 28.7, - 27.8, - 26.8, - 25.7, - 25.1, - 25, - 24.9, - 24.7, - 24.3, - 23.4, - 22.6, - 22.2, - 22, - 21.9, - 21.7, - 21.6, - 21.5, - 21.3, - 21.2, - 21.2, - 21.1, - 21, - 20.8, - 20.6, - 20.4, - 20.4, - 20.4, - 20.3, - 20.2, - 20.2, - 20.1, - 20.1, - 20.2, - 20.1, - 20.1, - 20.1, - 20.1, - 20.1, - 20.1, - 20.1, - 20.1, - 20, - 19.7, - 19.5, - 19.7, - 19.7, - 19.8, - 20, - 20.2, - 20.3, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.5, - 20.5, - 20.5, - 20.6, - 20.5, - 20.1, - 19.7, - 19.4, - 19, - 17.6, - 15.8, - 14.2, - 13.1, - 12.1, - 11.4, - 10.7, - 10.1, - 9.5, - 8.9, - 8.6, - 8.2, - 7.6, - 7.1, - 6.7, - 6.4, - 6.2, - 5.9, - 5.6, - 5.2, - 5, - 4.7, - 4.4, - 4.1, - 3.8, - 3.5, - 3.2, - 3, - 2.8, - 2.6, - 12.7, - 12.9, - 13, - 13.2, - 13.7, - 14.4, - 15.1, - 15.5, - 15.6, - 15.7, - 15.7, - 15.7, - 15.6, - 15.5, - 15.4, - 15.6, - 16, - 16.3, - 16.7, - 17.1, - 17.5, - 17.7, - 17.9, - 17.9, - 17.5, - 16.6, - 15.8, - 15.5, - 15.3, - 15.1, - 14.8, - 14.8, - 14.9, - 15, - 15.1, - 15.1, - 15.2, - 15.4, - 15.5, - 15.7, - 15.8, - 15.9, - 16, - 16.1, - 16.2, - 16.3, - 16.4, - 16.5, - 16.5, - 16.6, - 16.7, - 16.7, - 16.7, - 16.7, - 16.7, - 16.7, - 16.6, - 16.7, - 16.8, - 16.9, - 16.8, - 16.7, - 16.6, - 16.6, - 16.7, - 16.7, - 16.9, - 17.1, - 17.2, - 17.3, - 17.5, - 17.7, - 17.8, - 17.9, - 18.1, - 18.4, - 18.8, - 19.4, - 20, - 20.2, - 20.3, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 20.9, - 21, - 20.8, - 19.6, - 18.4, - 18.3, - 18.6, - 18.8, - 19, - 19.1, - 19.1, - 19.3, - 19.8, - 20.4, - 21.1, - 21.5, - 21.6, - 21.4, - 20.9, - 20.5, - 20.5, - 20.8, - 21.3, - 21.7, - 21.8, - 21.5, - 21.4, - 21.5, - 21.7, - 21.8, - 21.8, - 22.2, - 23, - 23.4, - 23.5, - 23.7, - 24, - 24.3, - 24.8, - 25.6, - 26.2, - 26.9, - 27.3, - 27.4, - 27.4, - 27.3, - 27.1, - 26.7, - 26.1, - 25.4, - 24.6, - 24.5, - 25.3, - 25.9, - 26.7, - 27.6, - 28.8, - 29.9, - 30.6, - 30.8, - 30.7, - 31.3, - 32.1, - 32.6, - 33.2, - 33.8, - 34.1, - 34.1, - 33.7, - 33, - 32, - 30.6, - 29.5, - 28.6, - 27.6, - 26.6, - 25.5, - 25.1, - 25, - 24.9, - 24.7, - 24.4, - 23.4, - 22.7, - 22.3, - 22.1, - 21.9, - 21.7, - 21.6, - 21.4, - 21.3, - 21.3, - 21.2, - 21.2, - 21, - 20.8, - 20.6, - 20.6, - 20.5, - 20.5, - 20.3, - 20.3, - 20.2, - 20.2, - 20.2, - 20.3, - 20.2, - 20.2, - 20.2, - 20.2, - 20.2, - 20.1, - 20.1, - 20.1, - 20, - 19.9, - 19.8, - 20, - 20, - 20, - 20.1, - 20.3, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.5, - 20.5, - 20.6, - 20.6, - 20.2, - 19.8, - 19.5, - 19.1, - 18, - 16.1, - 14.1, - 13.1, - 12.2, - 11.4, - 10.8, - 10.1, - 9.6, - 9, - 8.6, - 8.3, - 7.7, - 7.2, - 6.8, - 6.5, - 6.2, - 6, - 5.6, - 5.3, - 5.1, - 4.8, - 4.5, - 4.2, - 3.8, - 3.6, - 3.3, - 3.1, - 3, - 2.8, - 12.7, - 12.9, - 13, - 13.2, - 13.8, - 14.6, - 15.3, - 15.7, - 15.9, - 16, - 16.1, - 16.2, - 16.1, - 16.1, - 16, - 16, - 16, - 16.3, - 16.8, - 17.3, - 17.6, - 17.9, - 18, - 18, - 17.7, - 16.8, - 15.9, - 15.4, - 15.2, - 15, - 14.8, - 14.8, - 14.9, - 15, - 15.2, - 15.3, - 15.4, - 15.5, - 15.6, - 15.7, - 15.9, - 16, - 16.1, - 16.2, - 16.3, - 16.3, - 16.4, - 16.5, - 16.5, - 16.6, - 16.6, - 16.6, - 16.6, - 16.5, - 16.5, - 16.5, - 16.5, - 16.6, - 16.7, - 16.7, - 16.7, - 16.5, - 16.5, - 16.6, - 16.7, - 16.8, - 16.9, - 17.1, - 17.3, - 17.4, - 17.6, - 17.8, - 17.9, - 18, - 18.2, - 18.5, - 19.1, - 19.7, - 20.4, - 20.4, - 20.3, - 20.3, - 20.5, - 20.6, - 20.7, - 20.7, - 20.7, - 20.8, - 20.9, - 21, - 20.8, - 19.7, - 18.6, - 18.2, - 18.3, - 18.4, - 18.4, - 18.5, - 18.6, - 18.7, - 18.9, - 19.3, - 19.8, - 20, - 20.1, - 19.8, - 19.9, - 20.3, - 21.1, - 21.6, - 22, - 22.3, - 22.2, - 22, - 21.7, - 21.6, - 21.8, - 21.9, - 21.9, - 22.1, - 22.9, - 23.3, - 23.4, - 23.6, - 23.9, - 24.2, - 24.7, - 25.4, - 25.9, - 26.5, - 26.8, - 26.9, - 26.7, - 26.2, - 25.8, - 25.5, - 25.1, - 24.6, - 24.3, - 24.6, - 25.3, - 26, - 26.8, - 27.8, - 28.7, - 30.1, - 31.2, - 31.6, - 31.7, - 32, - 32.5, - 32.9, - 33.6, - 34, - 34.2, - 34.2, - 33.8, - 32.9, - 31.9, - 30.6, - 29.4, - 28.3, - 27.2, - 26.2, - 25.4, - 25.1, - 24.9, - 24.8, - 24.4, - 23.8, - 23, - 22.5, - 22.2, - 22, - 21.9, - 21.7, - 21.6, - 21.5, - 21.4, - 21.3, - 21.3, - 21.2, - 21.1, - 21, - 20.8, - 20.6, - 20.5, - 20.4, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.3, - 20.2, - 20.2, - 20.2, - 20.2, - 20.2, - 20.1, - 20.1, - 20, - 20, - 19.9, - 20, - 20.1, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.4, - 20.5, - 20.5, - 20.5, - 20.6, - 20.3, - 19.8, - 19.5, - 19.1, - 17.8, - 16.1, - 14.2, - 13.3, - 12.3, - 11.5, - 10.7, - 10.1, - 9.6, - 9.1, - 8.6, - 8.3, - 7.8, - 7.3, - 6.8, - 6.5, - 6.3, - 6, - 5.7, - 5.4, - 5.1, - 4.8, - 4.5, - 4.2, - 3.9, - 3.7, - 3.4, - 3.3, - 3.2, - 3 - ] + "raw16": "heightmap.r16", + "min": 1.2, + "max": 6554.7 } } }, diff --git a/packages/terrain/starter/desert/world.ts b/packages/terrain/starter/desert/world.ts index a91c8990cf..d6b07351c6 100644 --- a/packages/terrain/starter/desert/world.ts +++ b/packages/terrain/starter/desert/world.ts @@ -1,30 +1,19 @@ -// The desert starter world, as the game that copied it draws and collides with it. -// -// `bake.mjs` wrote a world package into `world/`: heightmap, splat masks, placements and the CC0 boulder. -// The engine's `WorldCells` streams it. `loadTerrainSplat` textures its ground. -// Everything that decides how the world looks, or what stops the player, is in this folder for the game to edit. -import type { IComputeDriven, ICtx } from "@threenative/core"; +import type { ICtx } from "@threenative/core"; +import { type IWorldPackage, loadTerrainSplat } from "@threenative/core/world"; +import type { IPhysicsContext } from "@threenative/physics"; import { - Heightfield, - type IWorldCellsLoadOptions, - type IWorldPackage, - WorldCells, - heightSamplerFromHeightmap, - loadTerrainSplat, - loadWorldHeightmap, -} from "@threenative/core/world"; -import { CollisionShape3D, type IPhysicsContext, RigidBody3D } from "@threenative/physics"; + type IStarterWorld, + type IStarterWorldSpec, + PropColliders as StarterPropColliders, + addStarterWorld, +} from "@threenative/terrain/starter"; import { EquirectangularReflectionMapping, - type Material, type Mesh, type MeshStandardMaterial, - Object3D, + type Object3D, type Texture, - Vector3, } from "three"; -import { HDRLoader } from "three/addons/loaders/HDRLoader.js"; - /** Where the game's asset source holds the baked `world/` folder. */ export const DESERT_URL = "terrain/desert/world.json"; @@ -42,294 +31,47 @@ export const PROP_SKY_LIGHT = 0.5; /** Metres around `follow` within which props collide. Beyond that, props only draw. */ export const PROP_COLLIDER_REACH = 60; -export interface IDesertWorld { - readonly world: WorldCells; - /** One heightfield for the whole world, in place before `addDesert` resolves. */ - readonly ground: RigidBody3D; - /** The prop colliders near `follow`. They stream with it (see `PROP_COLLIDER_REACH`). */ - readonly colliders: PropColliders; - /** The heightfield the ground stands on: `heightAt(x, z)` reads the terrain under any point. */ - readonly field: Heightfield; - /** Every placed prop's record, eight floats each, as the bake wrote it. */ - readonly placements: Float32Array; - /** The world's extent in metres, as the bake wrote it. */ - readonly extent: IWorldPackage["extent"]; +export interface IDesertWorld extends IStarterWorld {} +const spec: IStarterWorldSpec<{ dispose(): void }> = { + name: "Desert", + url: DESERT_URL, + colliders: COLLIDERS, + colliderReach: PROP_COLLIDER_REACH, + loadSurface: (ctx, url) => loadTerrainSplat({ assets: ctx.assets, url }), + lighting: { sky: "sky.hdr", lods: false, dress: dressProp }, + streaming: { + ring: 5, + impostors: false, + gpuScene: false, + shadows: { cast: true, receive: true }, + budgets: { residentCells: 64, instances: 40_000, bytes: 64_000_000 }, + terrain: { streamRadius: 6 }, + }, +}; +export class PropColliders extends StarterPropColliders { + constructor( + ctx: ICtx, IPhysicsContext>, + follow: Object3D, + manifest: IWorldPackage, + records: Float32Array, + ) { + super(ctx, follow, manifest, records, COLLIDERS, PROP_COLLIDER_REACH, "Desert"); + } } - -/** - * Stream the desert world around `follow` (usually the player or the camera) and give it collision. - * `whileCurrent` stops a departed scene's async load from attaching anything. Its resources are - * released on cancellation, failure and when the game's entity registry ends this scene. - */ export async function addDesert( ctx: ICtx, IPhysicsContext>, follow: Object3D, url = DESERT_URL, whileCurrent: () => boolean = () => true, ): Promise { - const assertCurrent = () => { - if (whileCurrent()) return; - const error = new Error("Desert world: its scene is no longer current."); - error.name = "AbortError"; - throw error; - }; - let surface: Material | undefined; - let releaseLighting: (() => void) | undefined; - let world: WorldCells | undefined; - let ground: RigidBody3D | undefined; - let colliders: PropColliders | undefined; - let released = false; - const dispose = () => { - if (released) return; - released = true; - colliders?.detach(); - colliders?.removeFromParent(); - ground?.dispose(); - ground?.object?.removeFromParent(); - world?.dispose(); - surface?.dispose(); - releaseLighting?.(); - }; - try { - assertCurrent(); - const manifest = (await (await fetchAsset(ctx, url, assertCurrent)).json()) as IWorldPackage; - assertCurrent(); - const base = url.slice(0, url.lastIndexOf("/") + 1); - const records = await ( - await fetchAsset(ctx, base + manifest.placements, assertCurrent) - ).arrayBuffer(); - assertCurrent(); - const [heightmapUrl] = await ctx.assets.resolve(base + manifest.terrain.heightmap); - assertCurrent(); - if (heightmapUrl === undefined) throw new Error("Desert world: its heightmap is not served."); - const heightmap = await loadWorldHeightmap(heightmapUrl); - assertCurrent(); - surface = await loadTerrainSplat({ assets: ctx.assets, url }); - assertCurrent(); - releaseLighting = await lightProps(ctx, url, manifest, assertCurrent); - assertCurrent(); - const options: IWorldCellsLoadOptions = { - url, - assets: ctx.assets, - data: { manifest, placements: records, heightmap }, - surface, - follow, - ring: 5, - impostors: false, - gpuScene: false, - shadows: { cast: true, receive: true }, - budgets: { residentCells: 64, instances: 40_000, bytes: 64_000_000 }, - terrain: { streamRadius: 6 }, - }; - world = await WorldCells.load(options); - assertCurrent(); - const field = desertField(manifest, heightmap); - ground = groundCollider(ctx, manifest, field); - const props = new Float32Array(records); - colliders = new PropColliders(ctx, follow, manifest, props); - ctx.add(world); - if (ground.object !== undefined) ctx.add(ground.object); - ctx.add(colliders); - ctx.entities.add(`desert-world.${world.uuid}`, { dispose }); - return { world, ground, colliders, field, placements: props, extent: manifest.extent }; - } catch (error) { - dispose(); - throw error; - } + const result = await addStarterWorld(ctx, follow, spec, url, whileCurrent); + return result; } - -// Every prop gets the sky as its own envMap before WorldCells adopts the shared cached models. -// Without it, a boulder lit by the sun alone draws flat. -async function lightProps( - ctx: ICtx, IPhysicsContext>, - url: string, - manifest: IWorldPackage, - assertCurrent: () => void, -): Promise<() => void> { - const base = url.slice(0, url.lastIndexOf("/") + 1); - const [skyUrl] = await ctx.assets.resolve(`${base}sky.hdr`); - assertCurrent(); - if (skyUrl === undefined) throw new Error("Desert world: 'sky.hdr' is not served."); - const sky = await new HDRLoader().loadAsync(skyUrl); - const originals = new Map(); - const dispose = () => { - for (const [material, original] of originals) { - if (material.envMap !== sky) continue; - material.envMap = original.envMap; - material.envMapIntensity = original.intensity; - } - sky.dispose(); - }; - try { - assertCurrent(); - sky.mapping = EquirectangularReflectionMapping; - for (const asset of Object.values(manifest.assets)) { - const model = await ctx.assets.model<{ scene: Object3D }>(base + asset.glb); - assertCurrent(); - model.scene.traverse((object) => { - const mesh = object as Mesh; - for (const material of [mesh.material ?? []].flat() as MeshStandardMaterial[]) - if (!originals.has(material)) - originals.set(material, { - envMap: material.envMap, - intensity: material.envMapIntensity, - }); - dressProp(mesh, sky); - }); - } - return dispose; - } catch (error) { - dispose(); - throw error; - } -} - /** One prop mesh: the sky as its envMap. */ function dressProp(mesh: Mesh, sky: Texture): void { for (const material of [mesh.material ?? []].flat() as MeshStandardMaterial[]) { + sky.mapping = EquirectangularReflectionMapping; material.envMap = sky; material.envMapIntensity = PROP_SKY_LIGHT; } } - -// The streamed terrain tiles arrive over several frames. A player spawned before its tile would -// fall through. One heightfield for the whole package avoids that, at a few hundred kilobytes. -/** The kit's one heightfield: the collider stands on it. */ -function desertField(manifest: IWorldPackage, heightmap: Uint16Array): Heightfield { - const { extent, terrain } = manifest; - return Heightfield.fromSampler({ - columns: terrain.columns, - rows: terrain.rows, - width: extent.sizeX, - depth: extent.sizeZ, - origin: { x: extent.minX + extent.sizeX / 2, z: extent.minZ + extent.sizeZ / 2 }, - sampleHeight: heightSamplerFromHeightmap(terrain, extent, heightmap), - }); -} - -function groundCollider( - ctx: ICtx, IPhysicsContext>, - manifest: IWorldPackage, - field: Heightfield, -): RigidBody3D { - const { extent } = manifest; - const anchor = new Object3D(); - anchor.position.set(extent.minX + extent.sizeX / 2, 0, extent.minZ + extent.sizeZ / 2); - return new RigidBody3D({ - object: anchor, - physics: ctx.physics, - type: "fixed", - entity: "terrain", - shape: CollisionShape3D.heightfield(field.rows, field.columns, field.toColliderHeights(), { - x: extent.sizeX, - y: 1, - z: extent.sizeZ, - }), - }); -} - -/** - * Fixed bodies for the props within `PROP_COLLIDER_REACH` of `follow`, rebuilt when it has moved a few - * metres. Thousands of resident bodies cost Rapier about 0.5 s per 8 s (measured). A hundred do not. - */ -export class PropColliders extends Object3D implements IComputeDriven { - readonly warmupNodes: readonly unknown[] = []; - readonly processCadence = "render" as const; - readonly #live = new Map(); - readonly #runs: { asset: string; offset: number; count: number }[] = []; - readonly #at = { x: Number.NaN, z: Number.NaN }; - readonly #followPosition = new Vector3(); - #released = false; - - constructor( - private readonly ctx: ICtx, IPhysicsContext>, - private readonly follow: Object3D, - manifest: IWorldPackage, - private readonly records: Float32Array, - ) { - super(); - this.name = "desert-prop-colliders"; - for (const cell of manifest.cells) - for (const run of cell.runs) if (COLLIDERS[run.asset]) this.#runs.push(run); - try { - this.#rebuild(); - } catch (error) { - this.detach(); - throw error; - } - } - - /** How many prop bodies exist right now. */ - get active(): number { - return this.#live.size; - } - - get released(): boolean { - return this.#released; - } - - attachRenderer(): void {} - - process(): void { - if (this.#released) return; - const p = this.follow.getWorldPosition(this.#followPosition); - if (Math.hypot(p.x - this.#at.x, p.z - this.#at.z) > 4 || Number.isNaN(this.#at.x)) - this.#rebuild(); - } - - detach(): void { - for (const body of this.#live.values()) body.dispose(); - this.#live.clear(); - this.#released = true; - } - - #rebuild(): void { - const { x: fx, z: fz } = this.follow.getWorldPosition(this.#followPosition); - this.#at.x = fx; - this.#at.z = fz; - const keep = new Set(); - for (const run of this.#runs) { - const shape = COLLIDERS[run.asset]; - if (!shape) continue; - for (let i = run.offset; i < run.offset + run.count; i++) { - const x = this.records[i * 8] as number; - const z = this.records[i * 8 + 2] as number; - if (Math.hypot(x - fx, z - fz) > PROP_COLLIDER_REACH) continue; - keep.add(i); - if (this.#live.has(i)) continue; - const collider = CollisionShape3D.sphere( - shape.sphere * (this.records[i * 8 + 7] as number), - ); - this.#live.set( - i, - new RigidBody3D({ - physics: this.ctx.physics, - type: "fixed", - entity: `${run.asset}.${String(i)}`, - position: { x, y: this.records[i * 8 + 1] as number, z }, - shape: collider, - }), - ); - } - } - for (const [i, body] of this.#live) - if (!keep.has(i)) { - body.dispose(); - this.#live.delete(i); - } - } -} - -async function fetchAsset( - ctx: ICtx, IPhysicsContext>, - url: string, - assertCurrent: () => void, -): Promise { - const [resolved] = await ctx.assets.resolve(url); - assertCurrent(); - if (resolved === undefined) throw new Error(`Desert world: '${url}' is not served.`); - const response = await fetch(resolved); - assertCurrent(); - if (!response.ok) throw new Error(`Desert world: '${url}' answered ${String(response.status)}.`); - return response; -} diff --git a/packages/terrain/starter/forest/README.md b/packages/terrain/starter/forest/README.md index e1554511b7..e4ca882043 100644 --- a/packages/terrain/starter/forest/README.md +++ b/packages/terrain/starter/forest/README.md @@ -18,8 +18,11 @@ Art is CC0 (Poly Haven); provenance is in `@threenative/terrain/starter-assets/c ## Three steps 1. Copy `node_modules/@threenative/terrain/starter/forest` to `src/terrain/forest`. -2. Run `node src/terrain/forest/bake.mjs --out assets/terrain/forest` (needs `@threenative/terrain` - as a devDependency). The same recipe always writes the same bytes. +2. Run `node src/terrain/forest/bake.mjs --out assets/terrain/forest`. The bake imports both + `@threenative/terrain` and `@threenative/assets`, so install them as devDependencies + (`npm install --save-dev @threenative/terrain @threenative/assets`); `three` must be installed + too, because `@threenative/terrain` declares it as a peer dependency. The same recipe always + writes the same bytes. 3. Add it to a scene: ```ts diff --git a/packages/terrain/starter/forest/bake.mjs b/packages/terrain/starter/forest/bake.mjs index 789c2a862a..51a306f38b 100644 --- a/packages/terrain/starter/forest/bake.mjs +++ b/packages/terrain/starter/forest/bake.mjs @@ -1,118 +1,8 @@ -// Bakes this folder's recipe into a self-contained world package. Run it once from your game -// project, then serve the folder it writes: -// node src/terrain/forest/bake.mjs [--assets ] [--out ] -// The engine's `WorldCells` streams `world/world.json` and `loadTerrainSplat` textures its ground; -// the game never evaluates terrain at runtime. -import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { Terrain, applyPlacementOverrides, bakeWorldPackage } from "@threenative/terrain"; +// Run with [--assets ] [--out ]; defaults to installed assets and ./world. +import { cookTerrainWorld } from "@threenative/assets"; +import { bakeKit } from "@threenative/terrain/kit"; -const here = dirname(fileURLToPath(import.meta.url)); - -/** `--key value` after the script; `--assets` and `--out` default as noted below. */ -function flag(name, fallback) { - const at = process.argv.indexOf(`--${name}`); - return at === -1 ? fallback : resolve(process.argv[at + 1] ?? ""); -} - -// The installed kit ships its models and textures beside the package manifest, so a copied kit -// finds them without asking the game for a path. `--assets` overrides that for a local checkout. -const installed = dirname(fileURLToPath(import.meta.resolve("@threenative/terrain/package.json"))); -const assetsDir = flag("assets", join(installed, "starter-assets")); -const outDir = flag("out", join(here, "world")); - -const recipe = JSON.parse(await readFile(join(here, "recipe.json"), "utf8")); -const assetTable = JSON.parse(await readFile(join(here, "assets.json"), "utf8")); -const surface = JSON.parse(await readFile(join(here, "surface.json"), "utf8")); - -const state = applyPlacementOverrides( - Terrain.fromJSON(recipe.recipe).evaluate(), - recipe.placementOverrides ?? {}, -); - -const assets = Object.fromEntries( - Object.entries(assetTable).map(([id, spec]) => [ - id, - { - bounds: spec.bounds, - glb: `models/${spec.near.split("/").at(-1)}`, - ...(spec.mid - ? { lods: [{ distance: spec.midDistance, glb: `models/${spec.mid.split("/").at(-1)}` }] } - : {}), - maxDistance: spec.maxDistance, - }, - ]), -); - -const started = performance.now(); -const { manifest, splat, files } = bakeWorldPackage(state, { - assets, - layers: { table: "terrain-table.json" }, +await bakeKit(import.meta.url, { + sky: "hdri/kloofendal_48d_partly_cloudy_1k.hdr", + cookTerrainWorld, }); -const written = new Map(); - -async function write(relative, bytes) { - await mkdir(join(outDir, dirname(relative)), { recursive: true }); - await writeFile(join(outDir, relative), bytes); - written.set(relative, bytes.length ?? bytes.byteLength); -} - -async function copy(from, to) { - await mkdir(join(outDir, dirname(to)), { recursive: true }); - await copyFile(join(assetsDir, from), join(outDir, to)); - written.set(to, (await readFile(join(outDir, to))).byteLength); -} - -await write("world.json", `${JSON.stringify(manifest, null, 2)}\n`); -// The lake and river the generator evaluated, so the game draws the water the bake shaped. -const round = (value) => Math.round(value * 1_000) / 1_000; -await write( - "water.json", - `${JSON.stringify( - { - lakes: state.waters.map((lake) => ({ - id: lake.id, - at: lake.at, - radius: lake.radius, - level: lake.level, - })), - rivers: state.rivers.map((river) => ({ - id: river.id, - width: round(river.width), - points: river.points.map((point) => point.map(round)), - })), - }, - null, - 2, - )}\n`, -); -for (const [name, bytes] of Object.entries(files)) await write(name, bytes); -for (const spec of Object.values(assetTable)) { - await copy(spec.near, `models/${spec.near.split("/").at(-1)}`); - if (spec.mid) await copy(spec.mid, `models/${spec.mid.split("/").at(-1)}`); -} -// The sky the props are lit by (CC0 Poly Haven Kloofendal); world.ts gives it to their materials. -await copy("hdri/kloofendal_48d_partly_cloudy_1k.hdr", "sky.hdr"); -for (const layer of [surface.base, ...surface.layers]) { - const source = surface.sources[layer.id]; - await copy(source.diff, `${surface.textures}/${layer.id}_diff.jpg`); - await copy(source.nrm, `${surface.textures}/${layer.id}_nrm.jpg`); -} -// `sources` is the bake's own lookup: the runtime reads only base/layers/splat/textures. -await write( - "terrain-table.json", - `${JSON.stringify({ ...surface, splat, textures: "textures" }, null, 2)}\n`, -); - -const perAsset = {}; -for (const cell of manifest.cells) - for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; -const total = [...written.values()].reduce((sum, bytes) => sum + bytes, 0); -console.log( - `${Object.entries(perAsset) - .map(([asset, count]) => `${asset} ${String(count)}`) - .join(", ")} placements; ${[...written] - .map(([name, bytes]) => `${name} ${String(bytes)} B`) - .join(", ")}; ${String(total)} B total; ${Math.round(performance.now() - started)} ms`, -); diff --git a/packages/terrain/starter/forest/heightmap.r16 b/packages/terrain/starter/forest/heightmap.r16 new file mode 100644 index 0000000000..3605127c89 Binary files /dev/null and b/packages/terrain/starter/forest/heightmap.r16 differ diff --git a/packages/terrain/starter/forest/recipe.json b/packages/terrain/starter/forest/recipe.json index 77ff6febca..968ed7ae02 100644 --- a/packages/terrain/starter/forest/recipe.json +++ b/packages/terrain/starter/forest/recipe.json @@ -2,7 +2,11 @@ "version": 1, "recipe": { "version": 1, - "config": { "size": 512, "resolution": 257, "seed": 73 }, + "config": { + "size": 512, + "resolution": 257, + "seed": 73 + }, "layers": [ { "enabled": true, @@ -13,4425 +17,9 @@ "data": { "width": 257, "height": 257, - "values": [ - 10.2, 10.2, 10.7, 11.8, 13.1, 14.3, 15.4, 15.9, 15.8, 15.6, 15.3, 15.2, 15.1, 15.2, - 15.4, 15.8, 16.1, 15.9, 15.4, 15, 14.4, 14, 13.9, 13.9, 14, 14.3, 14.5, 14.6, 14.6, - 14.4, 14.3, 14.2, 14.1, 14.1, 14.2, 14.4, 14.8, 15.1, 15.4, 15.6, 15.8, 15.9, 15.9, - 15.9, 15.7, 15.5, 15.1, 14.8, 14.5, 14.3, 13.8, 13.4, 13.3, 13, 12.7, 12.5, 12.5, - 12.4, 12.4, 12.3, 12.2, 12.2, 12, 11.9, 11.8, 11.7, 11.6, 11.5, 11.3, 11.3, 11.2, 11, - 10.9, 10.8, 10.8, 10.8, 10.8, 10.7, 10.7, 10.6, 10.6, 10.6, 10.7, 10.7, 10.8, 10.8, - 10.6, 10.5, 10, 9.2, 8.8, 8.6, 8.4, 8.3, 8.1, 7.9, 7.8, 7.7, 7.6, 7.5, 7.5, 7.4, 7.4, - 7.3, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.3, 7.4, 7.3, 7.1, 6.9, 6.8, - 6.7, 6.7, 6.7, 6.6, 6.6, 6.4, 6.3, 6.3, 6.1, 6.1, 6.1, 6.1, 6, 6, 6, 6, 6, 6, 5.8, - 5.7, 5.6, 5.4, 5.3, 5.2, 5.2, 5.2, 5.1, 5, 4.9, 4.9, 4.9, 4.9, 4.9, 4.8, 4.8, 4.8, - 4.8, 4.8, 4.8, 4.8, 4.9, 5.1, 5.3, 5.7, 6.3, 7, 7.5, 7.7, 8.1, 8.1, 8.2, 8.3, 8.4, - 8.6, 8.8, 8.9, 9, 8.9, 8.6, 8.2, 8.1, 8, 8, 8.1, 8.2, 8.2, 8.1, 8.1, 8.1, 8.1, 7.9, - 7.8, 7.6, 7.3, 7.3, 7.2, 7.3, 7.2, 6.9, 6.3, 5.6, 5, 4.3, 3.7, 3.2, 2.8, 2.4, 2.2, - 2.1, 2.1, 1.9, 1.8, 1.7, 1.6, 1.5, 1.4, 1.5, 1.4, 1.5, 1.5, 1.6, 1.6, 1.6, 1.5, 1.5, - 1.4, 1.2, 1.1, 1.2, 1.1, 1.1, 1.2, 1.6, 1.9, 2.1, 2.5, 2.8, 3.1, 3.4, 3.6, 3.8, 3.9, - 3.9, 3.8, 3.7, 3.6, 3.4, 3.4, 3.2, 3, 2.8, 2.5, 2.3, 2, 1.7, 1.4, 0.9, 10.3, 10.4, - 11.2, 12.3, 13.6, 14.8, 15.6, 15.9, 15.8, 15.5, 15.2, 15, 15, 15, 15.1, 15.6, 16, 16, - 15.7, 15.4, 14.8, 14.3, 14.1, 14.1, 14.3, 14.5, 14.7, 14.8, 14.7, 14.5, 14.4, 14.4, - 14.3, 14.3, 14.5, 14.8, 15.2, 15.5, 15.8, 16, 16, 16.1, 16.2, 16.1, 15.9, 15.5, 15.1, - 14.8, 14.4, 14.2, 13.9, 13.5, 13.3, 13, 12.8, 12.7, 12.6, 12.5, 12.5, 12.4, 12.4, - 12.3, 12.1, 12, 11.9, 11.8, 11.7, 11.6, 11.4, 11.3, 11.2, 11.1, 11, 10.9, 10.8, 10.7, - 10.8, 10.8, 10.7, 10.6, 10.7, 10.7, 10.8, 10.8, 10.8, 10.9, 10.8, 10.6, 10.3, 9.6, - 8.9, 8.6, 8.4, 8.3, 8.1, 7.9, 7.8, 7.7, 7.6, 7.5, 7.4, 7.4, 7.4, 7.4, 7.4, 7.5, 7.6, - 7.6, 7.6, 7.6, 7.7, 7.7, 7.7, 7.7, 7.8, 7.9, 7.7, 7.6, 7.4, 7.1, 6.9, 6.9, 6.9, 7, - 6.9, 6.6, 6.5, 6.5, 6.4, 6.3, 6.3, 6.3, 6.2, 6.1, 6.2, 6.2, 6.1, 6.1, 5.9, 5.8, 5.6, - 5.5, 5.4, 5.3, 5.2, 5.2, 5.1, 5, 4.9, 4.9, 4.9, 5, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, - 4.8, 4.8, 5, 5.3, 5.7, 6.2, 6.7, 7.1, 7.4, 7.7, 7.7, 7.7, 7.7, 7.9, 8.3, 8.6, 8.7, - 8.7, 8.6, 8.2, 8, 7.8, 7.8, 7.8, 7.8, 7.9, 7.9, 7.9, 8, 8.1, 8, 7.9, 7.6, 7.4, 7.2, - 7.2, 7.2, 7.2, 7, 6.5, 5.8, 5.1, 4.5, 3.9, 3.3, 2.9, 2.5, 2.2, 2, 1.9, 1.9, 1.8, 1.8, - 1.7, 1.5, 1.4, 1.4, 1.4, 1.2, 1, 0.9, 0.9, 0.9, 0.8, 0.8, 0.7, 0.6, 0.4, 0.4, 0.3, - 0.2, 0.3, 0.4, 0.8, 1.2, 1.5, 1.8, 2.2, 2.5, 2.9, 3.2, 3.5, 3.7, 3.8, 3.8, 3.7, 3.6, - 3.4, 3.3, 3.1, 2.9, 2.6, 2.2, 1.8, 1.5, 1.2, 0.8, 0.5, 10.3, 10.6, 11.6, 12.8, 14, 15, - 15.6, 15.7, 15.7, 15.3, 14.9, 14.8, 14.8, 14.9, 15, 15.4, 15.9, 16.1, 16, 15.6, 15.2, - 14.7, 14.4, 14.3, 14.5, 14.7, 14.8, 14.9, 14.9, 14.7, 14.6, 14.5, 14.5, 14.6, 14.8, - 15.2, 15.6, 15.8, 16.2, 16.4, 16.4, 16.4, 16.5, 16.3, 16, 15.5, 15.1, 14.7, 14.4, - 14.2, 14, 13.6, 13.2, 13, 12.8, 12.8, 12.7, 12.6, 12.6, 12.5, 12.6, 12.5, 12.4, 12.1, - 12, 11.9, 11.8, 11.7, 11.6, 11.4, 11.3, 11.2, 11.1, 11, 10.9, 10.7, 10.7, 10.7, 10.7, - 10.6, 10.7, 10.8, 10.8, 10.9, 10.9, 10.9, 10.9, 10.6, 10.5, 10, 9.1, 8.6, 8.4, 8.3, - 8.1, 8, 7.9, 7.8, 7.7, 7.6, 7.5, 7.5, 7.4, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 7.9, 7.9, 8, - 8, 8.1, 8.1, 8.2, 8.1, 8, 7.9, 7.7, 7.4, 7.3, 7.2, 7.1, 7, 6.8, 6.7, 6.8, 6.7, 6.6, - 6.5, 6.5, 6.5, 6.4, 6.3, 6.3, 6.2, 6.1, 6, 5.8, 5.7, 5.5, 5.4, 5.4, 5.3, 5.2, 5.1, 5, - 5, 4.9, 4.9, 5, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.7, 4.8, 5, 5.2, 5.5, 5.9, 6.2, - 6.6, 7, 7.2, 7.2, 7.1, 7.3, 7.5, 7.9, 8.2, 8.4, 8.4, 8.2, 8, 7.8, 7.6, 7.6, 7.5, 7.5, - 7.6, 7.7, 7.8, 8, 8, 7.9, 7.7, 7.5, 7.3, 7.1, 7, 7, 6.7, 6.3, 5.7, 5.2, 4.7, 4.1, 3.5, - 3, 2.6, 2.3, 2.2, 1.9, 1.8, 1.9, 1.8, 1.6, 1.4, 1.4, 1.4, 1.4, 1.2, 1, 0.8, 0.5, 0.4, - 0.3, 0.3, 0.2, 0.1, 0, -0.2, -0.4, -0.5, -0.5, -0.4, -0.2, 0, 0.4, 0.8, 1.1, 1.6, 2, - 2.4, 2.8, 3.2, 3.5, 3.6, 3.7, 3.7, 3.5, 3.3, 3.2, 3, 2.7, 2.3, 1.9, 1.5, 1, 0.6, 0.3, - 0.1, 10.5, 11.1, 12.1, 13.3, 14.5, 15.3, 15.6, 15.5, 15.3, 15, 14.8, 14.7, 14.7, 14.7, - 14.9, 15.3, 15.8, 16.1, 16.2, 16, 15.6, 15.2, 14.7, 14.5, 14.6, 14.8, 14.9, 14.9, - 14.8, 14.7, 14.7, 14.6, 14.7, 14.9, 15.1, 15.5, 15.9, 16.1, 16.4, 16.6, 16.6, 16.7, - 16.6, 16.2, 15.9, 15.5, 15, 14.7, 14.4, 14.2, 14, 13.6, 13.3, 13, 12.9, 12.8, 12.8, - 12.7, 12.7, 12.7, 12.6, 12.6, 12.5, 12.4, 12.2, 12.1, 12, 11.8, 11.7, 11.5, 11.4, - 11.3, 11.2, 11.1, 10.9, 10.8, 10.6, 10.6, 10.7, 10.7, 10.7, 10.8, 10.9, 10.9, 10.9, - 10.9, 10.9, 10.8, 10.6, 10.3, 9.4, 8.7, 8.4, 8.3, 8.1, 8, 7.9, 7.8, 7.7, 7.6, 7.6, - 7.5, 7.5, 7.5, 7.6, 7.8, 7.9, 8, 8.1, 8.2, 8.2, 8.2, 8.3, 8.4, 8.5, 8.6, 8.5, 8.4, - 8.3, 8.2, 8, 7.8, 7.6, 7.4, 7.3, 7.2, 7, 6.9, 7, 6.9, 6.7, 6.7, 6.8, 6.6, 6.5, 6.5, - 6.3, 6.2, 6, 5.9, 5.7, 5.6, 5.5, 5.4, 5.3, 5.2, 5.2, 5, 4.9, 4.9, 4.9, 4.9, 4.9, 4.8, - 4.8, 4.8, 4.7, 4.8, 4.8, 4.7, 4.8, 4.9, 5.1, 5.3, 5.6, 5.9, 6.2, 6.5, 6.9, 6.9, 6.8, - 6.9, 7.1, 7.4, 7.8, 8, 8, 7.9, 7.8, 7.6, 7.4, 7.3, 7.3, 7.3, 7.4, 7.6, 7.7, 7.7, 7.7, - 7.6, 7.4, 7.3, 7.1, 6.9, 6.6, 6.3, 5.9, 5.5, 5.1, 4.6, 4.2, 3.6, 3.1, 2.7, 2.4, 2.2, - 2.1, 2, 1.9, 1.9, 1.8, 1.6, 1.4, 1.3, 1.4, 1.3, 1.2, 1, 0.8, 0.6, 0.3, 0.2, 0.1, 0, - -0.1, -0.3, -0.5, -0.8, -1, -1, -1, -0.8, -0.7, -0.3, 0.2, 0.5, 1, 1.4, 1.8, 2.2, 2.7, - 3.1, 3.3, 3.4, 3.4, 3.2, 3.1, 2.9, 2.7, 2.4, 2, 1.6, 1.1, 0.7, 0.3, 0, -0.1, 10.8, - 11.7, 12.7, 13.9, 15.1, 15.7, 15.7, 15.5, 15.3, 15, 14.7, 14.6, 14.6, 14.6, 14.6, 15, - 15.6, 16.1, 16.3, 16.2, 15.9, 15.5, 15, 14.8, 14.8, 14.9, 15, 15, 14.8, 14.7, 14.7, - 14.7, 14.8, 15, 15.4, 15.8, 16.1, 16.4, 16.6, 16.8, 16.8, 16.9, 16.8, 16.2, 15.7, - 15.4, 15, 14.7, 14.4, 14.1, 13.9, 13.5, 13.3, 13.1, 13, 12.9, 12.9, 12.8, 12.8, 12.8, - 12.8, 12.7, 12.6, 12.5, 12.4, 12.3, 12.1, 12, 11.9, 11.7, 11.5, 11.4, 11.3, 11.2, 11, - 10.9, 10.7, 10.6, 10.6, 10.7, 10.7, 10.8, 10.9, 10.9, 10.8, 10.9, 10.9, 10.9, 10.6, - 10.4, 9.7, 8.8, 8.5, 8.4, 8.2, 8, 7.9, 7.9, 7.8, 7.7, 7.6, 7.5, 7.5, 7.6, 7.8, 8, 8, - 8.2, 8.3, 8.4, 8.4, 8.4, 8.5, 8.7, 8.8, 8.9, 8.8, 8.8, 8.7, 8.6, 8.5, 8.3, 8.1, 7.9, - 7.7, 7.6, 7.4, 7.2, 7.2, 7, 6.8, 6.8, 6.9, 6.9, 6.8, 6.7, 6.6, 6.4, 6.2, 6, 5.8, 5.7, - 5.6, 5.5, 5.5, 5.4, 5.2, 5, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.7, 4.7, 4.8, 4.8, - 4.8, 4.8, 4.9, 5, 5.1, 5.4, 5.7, 5.9, 6.1, 6.3, 6.5, 6.6, 6.6, 6.7, 7, 7.3, 7.6, 7.6, - 7.5, 7.4, 7.3, 7.1, 7.2, 7.2, 7.1, 7.1, 7.2, 7.2, 7.3, 7.2, 7.2, 7.2, 7, 6.9, 6.6, - 6.2, 5.8, 5.4, 5, 4.5, 4, 3.6, 3.3, 2.9, 2.5, 2.2, 2, 2, 2, 1.9, 1.8, 1.6, 1.5, 1.4, - 1.3, 1.3, 1.3, 1.2, 1.1, 1, 0.7, 0.4, 0.2, 0.2, 0, -0.3, -0.4, -0.6, -1.1, -1.4, -1.5, - -1.4, -1.3, -1.2, -0.9, -0.5, -0.1, 0.4, 0.8, 1.2, 1.7, 2.1, 2.6, 2.9, 3.1, 3, 2.9, - 2.9, 2.7, 2.5, 2.1, 1.6, 1.1, 0.7, 0.4, 0.1, -0.2, -0.4, 11.2, 12.3, 13.4, 14.6, 15.7, - 16.1, 15.8, 15.5, 15.2, 14.8, 14.6, 14.5, 14.4, 14.4, 14.5, 14.8, 15.3, 15.9, 16.3, - 16.3, 16.1, 15.8, 15.4, 15.1, 15, 15, 15.1, 15, 14.8, 14.8, 14.8, 14.8, 14.9, 15.2, - 15.6, 16, 16.4, 16.6, 16.7, 16.9, 16.9, 17.1, 16.9, 16.3, 15.7, 15.2, 14.9, 14.6, - 14.3, 14.1, 13.8, 13.5, 13.3, 13.3, 13.2, 13.1, 13, 12.9, 12.9, 12.9, 12.9, 12.9, - 12.8, 12.7, 12.6, 12.5, 12.4, 12.2, 12, 11.8, 11.7, 11.6, 11.5, 11.4, 11.2, 11.1, - 10.9, 10.6, 10.5, 10.6, 10.7, 10.7, 10.8, 10.8, 10.8, 10.9, 10.9, 10.9, 10.8, 10.5, - 10, 9, 8.6, 8.4, 8.2, 8, 7.9, 7.9, 7.8, 7.7, 7.6, 7.6, 7.7, 7.8, 8, 8.1, 8.2, 8.3, - 8.5, 8.5, 8.5, 8.6, 8.7, 8.9, 9, 9.1, 9.1, 9, 8.9, 8.9, 8.7, 8.6, 8.5, 8.3, 8.1, 7.9, - 7.7, 7.5, 7.4, 7.2, 7.2, 7.1, 7.1, 7.1, 7, 6.9, 6.8, 6.6, 6.5, 6.2, 6, 5.9, 5.7, 5.6, - 5.6, 5.4, 5.2, 5, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.7, 4.7, 4.8, 4.8, 4.8, 4.8, - 4.9, 5, 5.2, 5.3, 5.5, 5.8, 5.9, 6, 6.2, 6.3, 6.4, 6.5, 6.7, 7, 7.3, 7.2, 7.1, 7, 7, - 6.8, 6.8, 6.8, 6.8, 6.8, 6.8, 6.8, 6.8, 6.8, 6.8, 6.8, 6.8, 6.6, 6.3, 5.8, 5.3, 4.9, - 4.4, 4, 3.6, 3.3, 3.1, 2.8, 2.6, 2.5, 2.2, 2.2, 2.1, 2, 1.7, 1.6, 1.5, 1.5, 1.4, 1.3, - 1.1, 1, 1, 0.9, 0.7, 0.4, 0.3, 0.2, -0.1, -0.3, -0.6, -0.8, -1.2, -1.5, -1.7, -1.8, - -1.8, -1.7, -1.4, -1.1, -0.7, -0.3, 0.2, 0.6, 1.1, 1.5, 2, 2.3, 2.5, 2.6, 2.6, 2.6, - 2.5, 2.2, 1.8, 1.3, 0.8, 0.5, 0.1, -0.2, -0.4, -0.6, 11.8, 12.9, 14, 15.1, 16, 16.1, - 15.8, 15.3, 14.9, 14.6, 14.4, 14.3, 14.2, 14.2, 14.3, 14.6, 15, 15.7, 16.2, 16.3, - 16.4, 16.2, 15.8, 15.4, 15.2, 15.1, 15.1, 15, 15, 14.9, 14.9, 14.9, 15.1, 15.4, 15.7, - 16.1, 16.4, 16.6, 16.8, 16.9, 16.9, 17.1, 16.9, 16.2, 15.6, 15.1, 14.8, 14.6, 14.3, - 14, 13.7, 13.5, 13.4, 13.4, 13.3, 13.2, 13.1, 13, 13, 13, 13, 13.1, 13, 12.8, 12.8, - 12.7, 12.6, 12.5, 12.3, 12, 11.9, 11.8, 11.7, 11.6, 11.5, 11.3, 11.1, 10.9, 10.5, - 10.5, 10.7, 10.7, 10.7, 10.8, 10.8, 10.9, 10.9, 10.9, 10.9, 10.6, 10.3, 9.4, 8.7, 8.4, - 8.2, 8, 8, 7.9, 7.7, 7.7, 7.7, 7.8, 7.8, 8, 8.1, 8.3, 8.4, 8.4, 8.6, 8.7, 8.7, 8.9, 9, - 9.1, 9.2, 9.2, 9.2, 9.2, 9.1, 9, 8.9, 8.8, 8.7, 8.5, 8.3, 8.1, 8, 7.8, 7.6, 7.5, 7.4, - 7.3, 7.3, 7.3, 7.2, 7.1, 7, 6.8, 6.7, 6.5, 6.2, 6, 5.8, 5.7, 5.6, 5.5, 5.3, 5.1, 4.9, - 4.8, 4.8, 4.8, 4.7, 4.7, 4.7, 4.7, 4.8, 4.8, 4.8, 4.8, 4.9, 4.9, 5, 5.2, 5.3, 5.4, - 5.7, 5.9, 6, 6.1, 6.2, 6.2, 6.3, 6.5, 6.7, 7, 7, 6.8, 6.8, 6.7, 6.5, 6.3, 6.3, 6.4, - 6.4, 6.5, 6.6, 6.6, 6.4, 6.5, 6.5, 6.4, 6, 5.6, 5.2, 4.8, 4.3, 3.9, 3.6, 3.3, 3.1, - 2.9, 2.6, 2.7, 2.6, 2.5, 2.4, 2.2, 2, 1.9, 1.7, 1.7, 1.7, 2, 1.9, 1.3, 1, 0.9, 0.9, - 0.7, 0.5, 0.4, 0.3, -0.1, -0.4, -0.7, -0.8, -1, -1.2, -1.4, -1.6, -1.8, -1.9, -1.8, - -1.5, -1.1, -0.8, -0.5, 0, 0.4, 0.9, 1.3, 1.7, 1.9, 2.1, 2.2, 2.2, 2.1, 1.8, 1.4, 1, - 0.6, 0.2, -0.1, -0.3, -0.6, -0.7, 12.3, 13.5, 14.6, 15.6, 16.3, 16.2, 15.9, 15.2, - 14.8, 14.5, 14.4, 14.1, 14, 14, 14, 14.3, 14.7, 15.4, 15.9, 16.3, 16.6, 16.6, 16.2, - 15.8, 15.6, 15.4, 15.3, 15.2, 15.1, 15, 15, 15, 15.2, 15.4, 15.8, 16.2, 16.5, 16.7, - 16.9, 16.9, 17, 17, 16.7, 16.1, 15.4, 15, 14.8, 14.5, 14.3, 13.9, 13.7, 13.6, 13.5, - 13.4, 13.3, 13.2, 13.2, 13.1, 13.1, 13.2, 13.2, 13.2, 13.1, 13.1, 13, 12.9, 12.8, - 12.7, 12.7, 12.3, 12.1, 12, 11.9, 11.9, 11.7, 11.5, 11.3, 11.1, 10.8, 10.5, 10.6, - 10.7, 10.7, 10.8, 10.8, 10.8, 10.9, 10.9, 10.9, 10.7, 10.4, 9.8, 8.9, 8.4, 8.2, 8.1, - 8, 7.9, 7.8, 7.7, 7.9, 8.1, 8.1, 8.1, 8.3, 8.4, 8.5, 8.6, 8.8, 8.9, 9, 9.2, 9.2, 9.3, - 9.3, 9.3, 9.3, 9.3, 9.3, 9.2, 9.1, 9, 8.9, 8.7, 8.5, 8.4, 8.2, 8.1, 7.9, 7.7, 7.6, - 7.5, 7.4, 7.5, 7.4, 7.4, 7.2, 7.1, 6.9, 6.7, 6.4, 6, 5.9, 5.8, 5.8, 5.6, 5.4, 5.2, - 5.1, 5, 4.9, 4.8, 4.8, 4.7, 4.7, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 4.9, 4.9, 5.2, 5.4, - 5.3, 5.5, 5.7, 6, 6.1, 6.1, 6.1, 6.1, 6.3, 6.5, 6.7, 6.7, 6.7, 6.6, 6.5, 6.2, 6, 6, 6, - 6.1, 6.3, 6.5, 6.5, 6.3, 6.1, 6, 5.7, 5.3, 5, 4.6, 4.2, 3.9, 3.6, 3.4, 3.2, 2.9, 2.7, - 2.6, 2.6, 2.6, 2.5, 2.5, 2.3, 2.1, 1.9, 1.8, 1.8, 1.9, 2.2, 2, 1.3, 1.1, 1.1, 1, 0.8, - 0.7, 0.5, 0.3, 0, -0.3, -0.3, -0.5, -0.7, -0.9, -1, -1.3, -1.5, -1.6, -1.8, -1.7, - -1.4, -1.2, -1, -0.6, -0.2, 0.2, 0.6, 1, 1.3, 1.4, 1.6, 1.7, 1.6, 1.4, 1.1, 0.7, 0.3, - 0, -0.3, -0.5, -0.7, -0.9, 12.9, 14, 15.1, 16.2, 16.6, 16.3, 15.9, 15.2, 14.7, 14.4, - 14.2, 13.9, 13.8, 13.8, 13.8, 14, 14.5, 15.2, 15.8, 16.3, 16.6, 16.7, 16.6, 16.3, - 16.1, 15.8, 15.6, 15.3, 15.2, 15.2, 15.1, 15.1, 15.2, 15.5, 15.8, 16.2, 16.6, 16.9, - 17, 17, 17.1, 17, 16.6, 16, 15.3, 14.9, 14.7, 14.5, 14.2, 14, 13.9, 13.7, 13.6, 13.5, - 13.4, 13.3, 13.3, 13.2, 13.2, 13.2, 13.3, 13.3, 13.3, 13.2, 13.1, 13.1, 13, 13, 12.8, - 12.6, 12.4, 12.3, 12.2, 12.1, 11.9, 11.7, 11.5, 11.3, 11, 10.7, 10.4, 10.5, 10.6, - 10.7, 10.8, 10.8, 10.9, 10.9, 10.8, 10.7, 10.5, 10.1, 9, 8.5, 8.4, 8.3, 8.2, 8, 7.9, - 7.8, 8, 8.2, 8.2, 8.3, 8.5, 8.6, 8.7, 8.8, 8.9, 9.1, 9.2, 9.3, 9.4, 9.4, 9.4, 9.3, - 9.3, 9.4, 9.4, 9.3, 9.2, 9.1, 9, 8.9, 8.8, 8.6, 8.4, 8.3, 8.1, 7.9, 7.8, 7.7, 7.7, - 7.7, 7.6, 7.6, 7.5, 7.3, 7.1, 7, 6.7, 6.3, 6.2, 6.1, 5.9, 5.8, 5.6, 5.4, 5.3, 5.2, - 5.1, 5, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 4.9, 4.9, 5.1, 5.2, 5.3, 5.4, - 5.6, 5.9, 6, 6, 6, 6, 6.1, 6.2, 6.3, 6.3, 6.3, 6.3, 6.2, 5.9, 5.7, 5.6, 5.5, 5.7, 6.1, - 6.4, 6.3, 6, 5.7, 5.4, 5.2, 4.9, 4.6, 4.2, 3.9, 3.6, 3.4, 3.3, 3, 2.8, 2.8, 2.7, 2.7, - 2.6, 2.5, 2.4, 2.3, 2.2, 2, 1.9, 1.8, 1.7, 1.7, 1.5, 1.3, 1.3, 1.2, 1, 0.9, 0.8, 0.5, - 0.3, 0.2, 0.1, 0, -0.3, -0.6, -0.8, -0.9, -1.1, -1.4, -1.5, -1.6, -1.9, -1.8, -1.5, - -1.3, -1.1, -0.8, -0.4, 0, 0.4, 0.7, 0.8, 0.9, 1, 1, 0.9, 0.7, 0.3, 0, -0.3, -0.5, - -0.7, -0.9, -1.1, 13.3, 14.4, 15.5, 16.4, 16.5, 16.3, 15.8, 15.2, 14.6, 14.2, 13.9, - 13.8, 13.7, 13.8, 13.7, 13.8, 14.3, 15, 15.6, 16.2, 16.6, 16.7, 16.7, 16.6, 16.5, - 16.3, 16, 15.6, 15.4, 15.4, 15.3, 15.3, 15.3, 15.6, 16, 16.4, 16.8, 17, 17, 17.1, - 17.1, 17, 16.6, 16, 15.2, 14.8, 14.7, 14.5, 14.3, 14.1, 14, 13.8, 13.7, 13.6, 13.5, - 13.4, 13.4, 13.3, 13.3, 13.3, 13.4, 13.4, 13.4, 13.3, 13.3, 13.3, 13.3, 13.1, 12.9, - 12.7, 12.6, 12.5, 12.4, 12.2, 12.1, 11.9, 11.6, 11.4, 11.2, 10.9, 10.5, 10.4, 10.6, - 10.7, 10.8, 10.9, 10.9, 10.9, 10.7, 10.6, 10.5, 10, 9, 8.6, 8.5, 8.4, 8.3, 8.2, 8, 8, - 8, 8.1, 8.3, 8.5, 8.6, 8.7, 8.8, 9, 9.1, 9.2, 9.4, 9.4, 9.5, 9.5, 9.4, 9.4, 9.4, 9.4, - 9.4, 9.4, 9.4, 9.3, 9.2, 9.1, 9, 8.9, 8.7, 8.6, 8.4, 8.2, 8.1, 8.1, 8, 7.9, 7.9, 7.9, - 7.7, 7.6, 7.4, 7.2, 6.9, 6.7, 6.6, 6.3, 6.1, 5.9, 5.7, 5.6, 5.4, 5.2, 5.2, 5.1, 5, - 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 4.9, 4.9, 5, 5.1, 5.2, 5.4, 5.5, 5.8, 5.9, 6, - 6, 6, 5.9, 5.9, 5.9, 5.9, 5.9, 5.9, 5.8, 5.6, 5.4, 5.4, 5.3, 5.5, 6, 6.3, 5.9, 5.4, 5, - 4.8, 4.6, 4.4, 4.1, 3.9, 3.7, 3.5, 3.4, 3.3, 3.2, 3.1, 3.1, 3, 2.8, 2.7, 2.5, 2.4, - 2.3, 2.2, 2, 1.9, 1.8, 1.6, 1.5, 1.4, 1.4, 1.3, 1.2, 1, 0.9, 0.8, 0.6, 0.5, 0.4, 0.3, - 0.1, -0.3, -0.6, -0.8, -1, -1.2, -1.4, -1.6, -1.8, -2, -1.9, -1.7, -1.6, -1.5, -1.3, - -1, -0.6, -0.3, 0, 0.2, 0.3, 0.5, 0.4, 0.4, 0.2, -0.1, -0.3, -0.5, -0.7, -0.9, -1.2, - -1.3, 13.7, 14.8, 15.9, 16.5, 16.5, 16.2, 15.8, 15.2, 14.5, 14.1, 13.8, 13.7, 13.6, - 13.6, 13.6, 13.6, 14, 14.7, 15.3, 16, 16.5, 16.7, 16.8, 16.8, 16.7, 16.6, 16.3, 15.9, - 15.6, 15.5, 15.4, 15.4, 15.4, 15.7, 16.1, 16.6, 16.8, 17, 17, 17, 17, 16.8, 16.3, - 15.8, 15.1, 15, 14.8, 14.6, 14.4, 14.2, 14, 13.9, 13.8, 13.7, 13.6, 13.5, 13.5, 13.4, - 13.4, 13.4, 13.5, 13.5, 13.4, 13.4, 13.4, 13.4, 13.3, 13.2, 13, 12.9, 12.8, 12.7, - 12.6, 12.4, 12.2, 12, 11.7, 11.4, 11.3, 11.1, 10.7, 10.4, 10.5, 10.7, 10.8, 10.8, - 10.8, 10.7, 10.6, 10.6, 10.2, 9.5, 8.9, 8.7, 8.6, 8.5, 8.4, 8.3, 8.2, 8.2, 8.1, 8.2, - 8.5, 8.6, 8.8, 8.9, 9.1, 9.3, 9.3, 9.4, 9.5, 9.6, 9.6, 9.6, 9.6, 9.6, 9.6, 9.6, 9.6, - 9.5, 9.5, 9.4, 9.4, 9.4, 9.3, 9.2, 9.2, 9, 8.8, 8.6, 8.5, 8.4, 8.2, 8.2, 8.1, 8.1, 8, - 7.9, 7.7, 7.6, 7.4, 7.2, 7, 6.7, 6.4, 6.1, 5.9, 5.6, 5.3, 5.2, 5.2, 5.2, 5.1, 4.9, - 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 4.9, 5, 5.1, 5.2, 5.3, 5.5, 5.7, 5.9, 6, 6, - 5.9, 5.7, 5.6, 5.5, 5.5, 5.5, 5.5, 5.4, 5.3, 5.2, 5.2, 5.2, 5.4, 5.8, 6.3, 5.8, 5, - 4.7, 4.5, 4.4, 4.1, 3.9, 3.8, 3.7, 3.5, 3.4, 3.5, 3.5, 3.3, 3.2, 3.1, 3, 2.8, 2.5, - 2.3, 2.1, 2, 1.9, 1.7, 1.7, 1.6, 1.5, 1.3, 1.3, 1.2, 1.1, 1, 1, 1, 0.9, 0.7, 0.5, 0.3, - 0, -0.3, -0.6, -0.7, -0.9, -1.1, -1.2, -1.4, -1.6, -1.9, -2, -1.8, -1.8, -1.8, -1.6, - -1.4, -1.2, -1, -0.7, -0.5, -0.4, -0.2, -0.2, -0.2, -0.2, -0.4, -0.6, -0.8, -1, -1.1, - -1.3, -1.4, 14.2, 15.2, 16.2, 16.6, 16.5, 16.2, 15.7, 15.1, 14.3, 13.9, 13.7, 13.6, - 13.5, 13.4, 13.4, 13.5, 13.8, 14.4, 15.1, 15.8, 16.4, 16.6, 16.8, 16.9, 16.8, 16.8, - 16.5, 16.2, 15.9, 15.7, 15.5, 15.6, 15.6, 15.8, 16.2, 16.6, 16.9, 16.9, 17, 16.9, - 16.8, 16.4, 16, 15.6, 15.3, 15.2, 14.9, 14.7, 14.6, 14.5, 14.1, 14, 13.9, 13.9, 13.7, - 13.7, 13.6, 13.6, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.4, 13.3, 13.1, - 13, 13, 12.9, 12.7, 12.5, 12.2, 11.9, 11.7, 11.5, 11.3, 11.2, 11, 10.6, 10.4, 10.6, - 10.7, 10.7, 10.7, 10.7, 10.6, 10.4, 9.8, 9.1, 8.9, 8.7, 8.6, 8.5, 8.4, 8.4, 8.3, 8.3, - 8.3, 8.4, 8.6, 8.8, 9, 9.2, 9.4, 9.5, 9.5, 9.5, 9.6, 9.7, 9.7, 9.7, 9.7, 9.7, 9.7, - 9.7, 9.7, 9.7, 9.7, 9.7, 9.7, 9.7, 9.7, 9.7, 9.6, 9.4, 9.2, 9, 8.9, 8.7, 8.6, 8.5, - 8.4, 8.3, 8.3, 8.2, 8.1, 7.9, 7.8, 7.6, 7.3, 7, 6.8, 6.5, 6.2, 5.8, 5.4, 5.3, 5.3, - 5.3, 5.2, 5, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 4.9, 4.9, 5, 5.1, 5.2, 5.4, 5.7, - 6, 6.1, 6, 5.8, 5.5, 5.3, 5.2, 5.2, 5.1, 5.1, 5, 5, 5, 5, 5, 5.3, 5.7, 6.3, 5.9, 4.9, - 4.4, 4.4, 4.3, 4.1, 3.9, 3.8, 3.7, 3.5, 3.5, 3.5, 3.3, 3.1, 2.9, 2.8, 2.7, 2.6, 2.5, - 2.3, 2.1, 1.9, 1.7, 1.5, 1.6, 1.7, 1.5, 1.3, 1.2, 1.3, 1.3, 1.2, 1.2, 1.2, 1, 0.8, - 0.6, 0.4, 0.3, 0, -0.3, -0.5, -0.6, -0.8, -1, -1.1, -1.4, -1.7, -2, -1.8, -1.8, -1.9, - -1.9, -1.7, -1.6, -1.4, -1.2, -1, -0.9, -0.8, -0.7, -0.6, -0.7, -0.9, -0.9, -1, -1.1, - -1.2, -1.4, -1.6, 14.6, 15.6, 16.4, 16.6, 16.4, 16.1, 15.6, 15, 14.1, 13.7, 13.5, - 13.4, 13.3, 13.3, 13.3, 13.4, 13.6, 14.3, 15, 15.7, 16.2, 16.5, 16.7, 16.9, 16.9, - 16.9, 16.7, 16.4, 16.1, 15.8, 15.7, 15.7, 15.8, 15.9, 16.2, 16.6, 16.8, 17, 16.9, - 16.7, 16.5, 16.1, 15.8, 15.6, 15.5, 15.3, 14.9, 14.8, 14.7, 14.6, 14.3, 14.2, 14.1, - 14.2, 13.9, 13.8, 13.7, 13.7, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.5, - 13.3, 13.2, 13.1, 13.1, 13, 12.8, 12.6, 12.3, 12, 11.8, 11.6, 11.4, 11.3, 11.1, 10.8, - 10.4, 10.3, 10.5, 10.6, 10.7, 10.6, 10.5, 10, 9.2, 9, 9, 8.9, 8.8, 8.7, 8.5, 8.4, 8.4, - 8.4, 8.4, 8.5, 8.7, 9, 9.2, 9.4, 9.5, 9.6, 9.6, 9.7, 9.7, 9.7, 9.7, 9.8, 9.8, 9.8, - 9.8, 9.8, 9.8, 9.9, 9.9, 9.9, 10.1, 10.2, 10.3, 10.1, 10, 9.9, 9.8, 9.6, 9.4, 9.2, 9, - 8.9, 8.8, 8.7, 8.6, 8.5, 8.5, 8.4, 8.3, 8, 7.7, 7.4, 7.1, 6.8, 6.4, 6.1, 5.6, 5.4, - 5.5, 5.4, 5.1, 5, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 5, 5.1, 5.2, 5.4, - 5.7, 6, 6.1, 6, 5.7, 5.4, 5.2, 5.1, 5, 5, 4.9, 4.8, 4.7, 4.8, 4.7, 4.8, 5.1, 5.6, 6.1, - 6.1, 5.2, 4.5, 4.3, 4.2, 4, 4, 3.9, 3.8, 3.6, 3.7, 3.5, 3.2, 3, 3, 2.9, 2.8, 2.7, 2.6, - 2.5, 2.4, 2.4, 2.1, 1.9, 1.8, 1.6, 1.4, 1.4, 1.5, 1.5, 1.5, 1.4, 1.3, 1.2, 1.1, 0.9, - 0.8, 0.7, 0.5, 0.3, 0.1, 0, -0.2, -0.4, -0.6, -0.8, -1.1, -1.5, -1.9, -2, -1.9, -1.9, - -1.9, -1.9, -1.8, -1.7, -1.5, -1.3, -1.3, -1.2, -1.2, -1.2, -1.3, -1.3, -1.2, -1.2, - -1.2, -1.3, -1.6, -1.6, 15.1, 16, 16.5, 16.5, 16.3, 15.9, 15.4, 14.7, 13.9, 13.5, - 13.3, 13.2, 13.1, 13.2, 13.2, 13.3, 13.5, 14.2, 15, 15.6, 16, 16.4, 16.6, 16.7, 16.8, - 16.9, 16.8, 16.6, 16.4, 16.1, 16, 16, 16, 16.1, 16.4, 16.6, 16.8, 16.9, 16.8, 16.6, - 16.2, 15.9, 15.8, 15.7, 15.6, 15.4, 15.1, 15, 14.9, 14.8, 14.5, 14.3, 14.2, 14.2, 14, - 13.9, 13.8, 13.8, 13.7, 13.7, 13.7, 13.7, 13.6, 13.6, 13.6, 13.6, 13.5, 13.5, 13.4, - 13.3, 13.2, 13.1, 12.9, 12.8, 12.5, 12.2, 12, 11.8, 11.6, 11.5, 11.3, 11.1, 10.7, - 10.3, 10.2, 10.4, 10.5, 10.5, 10.2, 9.7, 9.2, 9.2, 9.2, 9, 8.9, 8.8, 8.7, 8.6, 8.5, - 8.6, 8.7, 8.8, 9, 9.2, 9.5, 9.6, 9.7, 9.7, 9.8, 9.8, 9.8, 9.8, 9.8, 9.9, 9.9, 9.9, 10, - 10, 10.1, 10.2, 10.2, 10.4, 10.6, 10.7, 10.7, 10.5, 10.4, 10.4, 10.3, 10.1, 9.9, 9.7, - 9.5, 9.3, 9.1, 9, 9, 8.9, 8.9, 8.8, 8.6, 8.4, 8.1, 7.7, 7.4, 7.1, 6.6, 6, 5.7, 5.5, - 5.5, 5.4, 5.2, 5, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 4.9, 5.1, 5.2, - 5.4, 5.5, 5.8, 5.8, 5.7, 5.5, 5.2, 5.1, 4.9, 4.7, 4.7, 4.8, 4.6, 4.5, 4.6, 4.5, 4.6, - 4.9, 5.4, 6, 6.3, 5.7, 5.1, 4.5, 4.2, 4.1, 4.1, 4, 4, 3.8, 3.8, 3.5, 3.2, 3.2, 3.2, - 3.1, 3, 2.9, 3, 2.9, 2.9, 3.1, 2.9, 2.8, 2.5, 2.2, 2, 2, 1.9, 1.9, 1.7, 1.6, 1.5, 1.4, - 1.3, 1.2, 1.1, 0.9, 0.7, 0.6, 0.6, 0.4, 0.2, 0.1, -0.2, -0.5, -0.9, -1.2, -1.6, -1.9, - -2, -1.9, -1.9, -1.9, -1.9, -1.8, -1.8, -1.7, -1.6, -1.6, -1.6, -1.6, -1.5, -1.4, - -1.5, -1.5, -1.4, -1.5, -1.7, -1.8, 15.5, 16.3, 16.6, 16.5, 16.2, 15.7, 15, 14.4, - 13.8, 13.5, 13.2, 13, 13, 13, 13.1, 13.1, 13.4, 14.1, 14.8, 15.3, 15.7, 16.1, 16.3, - 16.5, 16.7, 16.7, 16.7, 16.7, 16.6, 16.4, 16.3, 16.3, 16.4, 16.5, 16.7, 16.9, 17, - 16.9, 16.8, 16.4, 16.1, 15.9, 15.8, 15.7, 15.7, 15.6, 15.4, 15.3, 15.1, 15, 14.7, - 14.5, 14.3, 14.2, 14.1, 14, 13.9, 13.9, 13.8, 13.8, 13.8, 13.7, 13.7, 13.7, 13.7, - 13.7, 13.6, 13.6, 13.5, 13.4, 13.4, 13.3, 13.1, 12.9, 12.7, 12.4, 12.2, 12.1, 11.9, - 11.7, 11.5, 11.3, 11.1, 10.7, 10.3, 10.1, 10.2, 10.1, 9.9, 9.5, 9.4, 9.4, 9.3, 9.1, 9, - 9, 8.9, 8.7, 8.7, 8.7, 8.9, 9, 9.2, 9.4, 9.6, 9.7, 9.8, 9.8, 9.9, 9.9, 10, 9.9, 10, - 10, 10, 10.1, 10.2, 10.2, 10.4, 10.6, 10.7, 10.8, 10.9, 11, 10.9, 10.9, 10.8, 10.7, - 10.6, 10.5, 10.3, 10.1, 9.9, 9.8, 9.6, 9.4, 9.4, 9.3, 9.2, 9.1, 8.9, 8.7, 8.4, 8, 7.6, - 7.2, 6.7, 6.1, 5.9, 5.7, 5.6, 5.4, 5.3, 5, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, - 4.8, 4.8, 4.9, 4.9, 5.1, 5.2, 5.4, 5.5, 5.5, 5.4, 5.3, 5, 4.9, 4.8, 4.6, 4.5, 4.5, - 4.4, 4.4, 4.5, 4.4, 4.5, 4.7, 5.2, 5.7, 6.1, 6.1, 5.7, 5, 4.4, 4.2, 4.2, 4.1, 3.9, - 3.7, 3.8, 3.7, 3.5, 3.5, 3.4, 3.4, 3.4, 3.5, 3.6, 3.5, 3.5, 3.5, 3.5, 3.2, 3, 2.8, - 2.6, 2.5, 2.3, 2.2, 2.1, 2, 1.8, 1.7, 1.6, 1.6, 1.5, 1.3, 1.1, 1, 1, 1, 0.8, 0.6, 0.3, - -0.1, -0.5, -1, -1.4, -1.8, -2, -2.1, -2, -1.9, -1.9, -1.9, -1.9, -1.9, -1.8, -1.7, - -1.7, -1.7, -1.7, -1.7, -1.8, -1.7, -1.7, -1.7, -1.8, -1.9, 16, 16.5, 16.5, 16.3, - 16.1, 15.5, 14.7, 14.1, 13.7, 13.4, 13.1, 12.9, 12.9, 12.9, 13, 13.1, 13.4, 13.9, - 14.5, 15, 15.4, 15.6, 15.9, 16.1, 16.3, 16.4, 16.5, 16.7, 16.7, 16.6, 16.7, 16.7, - 16.8, 16.9, 17.1, 17.3, 17.3, 17, 16.7, 16.3, 16.1, 16, 15.9, 15.8, 15.8, 15.8, 15.7, - 15.6, 15.5, 15.3, 15, 14.8, 14.7, 14.5, 14.3, 14.2, 14.1, 14, 14, 13.9, 13.9, 13.8, - 13.8, 13.8, 13.9, 14.1, 13.8, 13.6, 13.6, 13.5, 13.4, 13.3, 13.2, 13, 12.8, 12.6, - 12.4, 12.3, 12.1, 12, 11.7, 11.4, 11.2, 11, 10.7, 10.4, 10, 9.8, 9.6, 9.6, 9.6, 9.5, - 9.4, 9.3, 9.2, 9, 8.9, 8.8, 8.8, 8.9, 9, 9.1, 9.4, 9.5, 9.6, 9.8, 9.9, 10, 10, 10.1, - 10.1, 10.1, 10.2, 10.2, 10.3, 10.4, 10.5, 10.6, 10.8, 10.9, 11, 11.2, 11.2, 11.2, - 11.2, 11.1, 11.1, 10.9, 10.9, 10.7, 10.6, 10.4, 10.3, 10.1, 9.9, 9.7, 9.6, 9.5, 9.5, - 9.4, 9.2, 8.9, 8.6, 8.2, 7.8, 7.4, 7, 6.5, 6, 5.8, 5.7, 5.5, 5.3, 5, 4.9, 4.8, 4.8, - 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 5, 5.1, 5.2, 5.3, 5.3, 5.2, 5, 4.9, 4.9, - 4.8, 4.6, 4.5, 4.4, 4.4, 4.4, 4.4, 4.4, 4.5, 4.6, 4.9, 5.3, 5.7, 5.8, 5.7, 5.2, 4.6, - 4.4, 4.2, 3.9, 3.9, 4.1, 4.1, 4, 3.9, 3.9, 3.9, 3.9, 4, 4.1, 4.2, 4.1, 4, 3.9, 3.7, - 3.4, 3.3, 3.1, 2.9, 2.8, 2.7, 2.5, 2.4, 2.3, 2.2, 2.1, 2.1, 2.1, 2, 1.8, 1.6, 1.5, - 1.5, 1.5, 1.4, 1.3, 0.9, 0.4, -0.1, -0.7, -1.2, -1.7, -1.9, -2, -2, -2, -2, -2, -2, - -1.9, -1.9, -1.9, -1.8, -1.8, -1.9, -1.9, -2, -2, -1.9, -1.9, -2, -2, 16.4, 16.7, - 16.6, 16.3, 15.9, 15.2, 14.5, 13.9, 13.4, 13.2, 12.9, 12.8, 12.8, 12.8, 12.8, 13, - 13.3, 13.7, 14.4, 14.8, 15.1, 15.3, 15.6, 15.7, 15.9, 16.1, 16.3, 16.4, 16.5, 16.7, - 16.8, 16.9, 17, 17.1, 17.3, 17.4, 17.3, 17, 16.6, 16.3, 16.1, 16, 16, 16, 15.9, 15.9, - 15.9, 15.9, 15.7, 15.5, 15.3, 15.1, 15, 14.8, 14.7, 14.5, 14.4, 14.4, 14.2, 14.1, 14, - 13.9, 13.8, 13.9, 14.1, 14.1, 13.8, 13.7, 13.6, 13.6, 13.5, 13.4, 13.4, 13.2, 12.9, - 12.7, 12.5, 12.3, 12.2, 12.1, 11.8, 11.5, 11.3, 11.1, 10.9, 10.6, 10.3, 10, 9.8, 9.7, - 9.7, 9.6, 9.5, 9.4, 9.2, 9.1, 9, 9, 9, 9.1, 9.2, 9.4, 9.6, 9.8, 9.8, 10, 10.1, 10.1, - 10.2, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9, 11.1, 11.2, 11.3, 11.4, 11.5, - 11.5, 11.5, 11.4, 11.4, 11.3, 11.2, 11.1, 10.9, 10.7, 10.6, 10.5, 10.4, 10.2, 10, 9.9, - 9.8, 9.7, 9.5, 9.4, 9.1, 8.8, 8.6, 8.2, 7.7, 7.3, 6.6, 6.1, 5.8, 5.6, 5.5, 5.3, 5.1, - 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 5.1, 5, 5, 5.1, 5.1, 5.1, - 5, 4.9, 4.8, 4.8, 4.7, 4.6, 4.5, 4.4, 4.3, 4.3, 4.2, 4.4, 4.6, 4.7, 4.9, 5.2, 5.2, - 5.1, 4.8, 4.4, 4.1, 4.1, 4, 4.3, 4.6, 4.6, 4.5, 4.5, 4.7, 4.6, 4.5, 4.6, 4.7, 4.6, - 4.5, 4.3, 4.1, 4, 3.7, 3.6, 3.4, 3.2, 3.1, 3.1, 2.9, 2.8, 2.7, 2.6, 2.5, 2.5, 2.5, - 2.4, 2.2, 2, 1.9, 1.8, 1.8, 1.8, 1.7, 1.5, 1, 0.3, -0.3, -0.9, -1.3, -1.7, -1.8, -1.9, - -2, -2, -2, -2, -2, -2, -1.9, -2, -2, -2, -2, -2, -2.1, -2.1, -2.1, -2.1, -2.1, 16.8, - 16.8, 16.6, 16.1, 15.6, 15, 14.3, 13.7, 13.3, 13.1, 12.8, 12.7, 12.7, 12.7, 12.8, - 12.9, 13.2, 13.7, 14.3, 14.8, 15.1, 15.4, 15.6, 15.7, 15.8, 16, 16.1, 16.2, 16.4, - 16.6, 16.7, 16.9, 17.1, 17.1, 17.2, 17.3, 17.2, 16.9, 16.6, 16.4, 16.2, 16.1, 16.1, - 16.1, 16.1, 16.1, 16, 16, 15.9, 15.7, 15.6, 15.4, 15.3, 15.1, 15, 14.9, 14.8, 14.6, - 14.5, 14.3, 14.1, 14, 13.9, 13.9, 13.9, 13.9, 13.8, 13.8, 13.7, 13.7, 13.6, 13.5, - 13.4, 13.3, 13.1, 12.9, 12.7, 12.4, 12.2, 12.1, 11.8, 11.5, 11.3, 11.1, 10.9, 10.6, - 10.4, 10.1, 9.9, 9.9, 9.8, 9.7, 9.6, 9.5, 9.3, 9.2, 9.2, 9.2, 9.3, 9.4, 9.5, 9.7, 9.9, - 10.1, 10.1, 10.2, 10.3, 10.4, 10.5, 10.5, 10.6, 10.8, 10.9, 11, 11.1, 11.2, 11.2, - 11.4, 11.5, 11.5, 11.6, 11.6, 11.6, 11.6, 11.6, 11.5, 11.4, 11.3, 11.2, 11, 10.8, - 10.7, 10.6, 10.5, 10.4, 10.2, 10, 9.9, 9.8, 9.6, 9.5, 9.2, 8.9, 8.7, 8.3, 7.8, 7.4, - 6.8, 6.2, 5.9, 5.6, 5.4, 5.2, 5.1, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, - 4.8, 4.9, 5, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 4.8, 4.7, 4.7, 4.6, 4.6, 4.5, 4.4, 4.3, - 4.3, 4.3, 4.3, 4.4, 4.4, 4.5, 4.7, 4.6, 4.4, 4.3, 4.1, 4.2, 4.4, 4.2, 4.4, 4.8, 5, 5, - 5, 5.2, 5.3, 5.1, 5, 5, 4.9, 4.7, 4.5, 4.3, 4.2, 4.1, 3.9, 3.6, 3.5, 3.4, 3.3, 3.2, - 3.2, 3.1, 3, 2.9, 2.8, 2.7, 2.7, 2.6, 2.5, 2.3, 2.2, 2.1, 2.1, 2, 1.8, 1.4, 0.8, 0.1, - -0.5, -0.9, -1.4, -1.7, -1.8, -1.9, -1.9, -2, -2, -2, -2, -2, -2, -2.1, -2.1, -2.1, - -2.2, -2.2, -2.2, -2.2, -2.2, -2.2, 16.9, 16.8, 16.5, 15.9, 15.5, 14.9, 14.2, 13.6, - 13.2, 13, 12.8, 12.7, 12.6, 12.6, 12.7, 12.9, 13.2, 13.7, 14.4, 14.9, 15.4, 15.6, - 15.7, 15.8, 15.9, 16, 16.1, 16.2, 16.3, 16.4, 16.6, 16.7, 16.8, 16.8, 16.9, 17.1, - 17.1, 16.9, 16.7, 16.5, 16.3, 16.2, 16.2, 16.2, 16.2, 16.1, 16.1, 16.1, 16.1, 15.9, - 15.8, 15.7, 15.5, 15.4, 15.3, 15.1, 15.1, 14.8, 14.6, 14.4, 14.2, 14.1, 14, 13.9, - 13.9, 13.9, 13.8, 13.8, 13.8, 13.7, 13.7, 13.6, 13.5, 13.4, 13.2, 13, 12.8, 12.5, - 12.3, 12.1, 11.7, 11.5, 11.3, 11.1, 10.9, 10.7, 10.4, 10.1, 10, 10, 10, 9.8, 9.7, 9.6, - 9.5, 9.5, 9.5, 9.6, 9.8, 9.8, 9.9, 10.1, 10.2, 10.3, 10.4, 10.5, 10.7, 10.8, 10.9, 11, - 11, 11.1, 11.2, 11.3, 11.4, 11.6, 11.6, 11.6, 11.7, 11.7, 11.7, 11.8, 11.7, 11.8, - 11.6, 11.4, 11.3, 11.2, 11.1, 11, 10.9, 10.7, 10.6, 10.5, 10.4, 10.2, 10, 9.9, 9.8, - 9.8, 9.6, 9.3, 8.9, 8.6, 8.2, 7.8, 7.4, 6.9, 6.4, 5.9, 5.6, 5.4, 5.2, 5, 4.9, 4.8, - 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 4.8, 4.8, 4.7, - 4.6, 4.6, 4.6, 4.6, 4.5, 4.5, 4.5, 4.4, 4.4, 4.3, 4.3, 4.3, 4.4, 4.5, 4.4, 4.1, 4.1, - 4.3, 4.4, 4.8, 5, 4.7, 4.7, 4.9, 5.3, 5.4, 5.5, 5.5, 5.4, 5.3, 5.2, 5.1, 5, 4.8, 4.7, - 4.5, 4.5, 4.3, 4, 4, 3.9, 3.7, 3.6, 3.5, 3.5, 3.4, 3.3, 3.2, 3.1, 3.1, 2.9, 2.9, 2.8, - 2.6, 2.5, 2.5, 2.6, 2.3, 2.1, 1.8, 1.3, 0.7, 0, -0.7, -1.2, -1.6, -1.9, -1.9, -1.9, - -1.9, -2, -2, -2, -2.1, -2.1, -2.1, -2.2, -2.2, -2.2, -2.3, -2.3, -2.3, -2.3, -2.3, - 16.9, 16.7, 16.3, 15.8, 15.3, 14.6, 14, 13.5, 13.2, 12.9, 12.7, 12.6, 12.5, 12.6, - 12.7, 12.8, 13.2, 13.7, 14.3, 14.9, 15.4, 15.7, 15.8, 15.9, 16, 16.1, 16.2, 16.4, - 16.3, 16.3, 16.4, 16.5, 16.5, 16.5, 16.7, 16.9, 16.9, 16.8, 16.6, 16.5, 16.4, 16.4, - 16.4, 16.3, 16.3, 16.3, 16.2, 16.2, 16.2, 16.1, 15.9, 15.9, 15.8, 15.6, 15.5, 15.4, - 15.2, 15, 14.8, 14.6, 14.4, 14.2, 14.1, 14.1, 14.1, 14, 13.9, 13.8, 13.8, 13.8, 13.7, - 13.6, 13.5, 13.5, 13.3, 13.2, 12.9, 12.7, 12.3, 12, 11.7, 11.5, 11.3, 11.1, 10.9, - 10.7, 10.5, 10.2, 10.1, 10.2, 10.1, 10, 9.9, 9.8, 9.7, 9.7, 9.9, 10.1, 10.3, 10.4, - 10.4, 10.5, 10.5, 10.6, 10.8, 10.9, 11.1, 11.2, 11.3, 11.4, 11.5, 11.5, 11.5, 11.6, - 11.7, 11.8, 11.8, 11.8, 11.8, 11.9, 11.9, 11.9, 11.8, 11.8, 11.6, 11.4, 11.3, 11.3, - 11.1, 11.1, 11, 10.8, 10.7, 10.5, 10.3, 10.2, 10, 9.8, 9.7, 9.6, 9.5, 9.2, 8.9, 8.6, - 8.2, 7.8, 7.4, 6.9, 6.4, 5.8, 5.6, 5.4, 5.1, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, - 4.8, 4.8, 4.9, 4.9, 4.9, 4.9, 4.9, 5, 5, 4.9, 4.9, 4.7, 4.6, 4.5, 4.5, 4.5, 4.5, 4.6, - 4.5, 4.4, 4.4, 4.4, 4.3, 4.4, 4.5, 4.5, 4.3, 4.3, 4.4, 4.6, 4.9, 5.5, 5.8, 5.3, 5.1, - 5.2, 5.5, 5.7, 5.8, 5.8, 5.6, 5.4, 5.3, 5.2, 5, 5, 4.8, 4.8, 4.7, 4.5, 4.4, 4.4, 4.3, - 4, 3.8, 3.8, 3.7, 3.6, 3.5, 3.4, 3.4, 3.3, 3.1, 3, 3, 3, 2.9, 2.8, 2.8, 2.7, 2.6, 2.2, - 1.8, 1.3, 0.7, -0.1, -0.8, -1.3, -1.7, -1.9, -1.8, -1.9, -1.9, -2, -2.1, -2.1, -2.1, - -2.2, -2.2, -2.2, -2.3, -2.3, -2.3, -2.3, -2.4, -2.4, 16.9, 16.6, 16.2, 15.7, 15.1, - 14.4, 13.9, 13.5, 13.2, 12.9, 12.8, 12.7, 12.6, 12.6, 12.7, 12.8, 13.1, 13.6, 14.1, - 14.7, 15.2, 15.5, 15.7, 15.8, 16, 16.1, 16.2, 16.5, 16.4, 16.2, 16.2, 16.2, 16.3, - 16.4, 16.5, 16.7, 16.8, 16.7, 16.6, 16.5, 16.5, 16.5, 16.5, 16.4, 16.4, 16.4, 16.4, - 16.4, 16.3, 16.2, 16.1, 16.1, 15.9, 15.8, 15.7, 15.5, 15.4, 15.2, 15, 14.8, 14.5, - 14.4, 14.3, 14.2, 14.1, 14, 13.9, 13.9, 13.9, 13.9, 13.8, 13.7, 13.6, 13.6, 13.5, - 13.3, 13.1, 12.7, 12.3, 11.9, 11.6, 11.4, 11.3, 11.1, 10.9, 10.8, 10.6, 10.2, 10.2, - 10.3, 10.2, 10.2, 10.2, 10.1, 10, 10.1, 10.4, 10.5, 10.7, 10.8, 10.8, 10.9, 11, 11.1, - 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9, 11.8, 11.8, 11.8, 11.8, 11.9, 12, - 11.9, 11.9, 11.9, 11.9, 11.8, 11.7, 11.6, 11.5, 11.4, 11.3, 11.2, 11.1, 11, 11, 10.8, - 10.6, 10.3, 10, 9.7, 9.5, 9.4, 9.3, 9.2, 9.1, 8.8, 8.4, 8.1, 7.8, 7.4, 7, 6.5, 6, 5.7, - 5.4, 5.1, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, - 5, 5, 5, 5, 4.9, 4.7, 4.6, 4.5, 4.4, 4.4, 4.5, 4.5, 4.5, 4.5, 4.4, 4.4, 4.4, 4.4, 4.3, - 4.3, 4.4, 4.4, 4.8, 5.3, 5.9, 6.3, 6.1, 5.5, 5.4, 5.6, 5.8, 5.8, 5.8, 5.6, 5.5, 5.4, - 5.3, 5.1, 5, 5, 5, 5, 4.8, 4.9, 4.9, 4.7, 4.4, 4.2, 4, 4, 3.9, 3.8, 3.7, 3.6, 3.5, - 3.5, 3.4, 3.3, 3.3, 3.4, 3.2, 3.1, 2.9, 2.7, 2.5, 2.2, 1.8, 1.5, 0.7, -0.1, -0.8, - -1.4, -1.7, -1.8, -1.9, -2, -2, -2.1, -2.1, -2.2, -2.2, -2.2, -2.3, -2.3, -2.3, -2.3, - -2.3, -2.3, -2.4, 16.9, 16.5, 15.9, 15.4, 14.8, 14.3, 13.9, 13.5, 13.3, 13, 12.8, - 12.7, 12.7, 12.6, 12.6, 12.8, 13, 13.4, 13.9, 14.5, 15, 15.3, 15.4, 15.6, 15.7, 16, - 16.2, 16.3, 16.2, 16.1, 16.1, 16.2, 16.3, 16.4, 16.5, 16.7, 16.9, 16.9, 16.8, 16.7, - 16.7, 16.6, 16.5, 16.5, 16.5, 16.5, 16.5, 16.5, 16.4, 16.4, 16.3, 16.2, 16.1, 16, - 15.8, 15.6, 15.5, 15.3, 15.1, 14.9, 14.7, 14.6, 14.5, 14.3, 14.2, 14.1, 14, 13.9, - 13.9, 13.8, 13.8, 13.7, 13.6, 13.6, 13.5, 13.3, 13, 12.6, 12.2, 11.9, 11.6, 11.5, - 11.3, 11.2, 11, 10.8, 10.7, 10.2, 10.3, 10.4, 10.4, 10.5, 10.5, 10.6, 10.6, 10.6, - 10.8, 10.9, 10.9, 11, 11.1, 11.2, 11.3, 11.4, 11.4, 11.6, 11.7, 11.7, 11.8, 11.9, 12, - 12.1, 12.1, 12, 12, 12, 12, 12, 11.9, 11.9, 11.8, 11.8, 11.8, 11.8, 11.7, 11.6, 11.5, - 11.4, 11.2, 11.1, 11.1, 11, 10.8, 10.5, 10.2, 9.8, 9.5, 9.3, 9.1, 9, 8.9, 8.8, 8.6, - 8.3, 8, 7.7, 7.4, 7, 6.5, 6, 5.7, 5.4, 5.2, 5, 4.9, 4.9, 4.8, 4.9, 4.9, 4.9, 4.8, 4.8, - 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 5, 5, 4.9, 4.9, 4.9, 4.7, 4.6, 4.6, 4.5, 4.4, 4.4, 4.4, - 4.4, 4.5, 4.4, 4.4, 4.4, 4.3, 4.2, 4.3, 4.4, 4.4, 4.7, 5.3, 5.9, 6.3, 6.4, 6, 5.8, - 5.8, 5.9, 5.9, 5.8, 5.6, 5.5, 5.4, 5.3, 5.1, 5, 5.1, 5.1, 5.1, 5.1, 5.2, 5.1, 4.9, - 4.7, 4.4, 4.3, 4.2, 4.2, 4.1, 4, 4, 3.9, 3.9, 3.9, 3.8, 3.6, 3.6, 3.5, 3.4, 3.2, 2.9, - 2.7, 2.4, 2.1, 1.8, 1.2, 0.4, -0.4, -1.2, -1.6, -1.8, -1.9, -2, -2.1, -2.1, -2, -2.1, - -2.2, -2.3, -2.3, -2.3, -2.3, -2.3, -2.3, -2.4, -2.5, 16.8, 16.2, 15.8, 15.2, 14.6, - 14.3, 14, 13.7, 13.4, 13.2, 12.9, 12.8, 12.8, 12.7, 12.7, 12.8, 13.1, 13.4, 13.7, - 14.2, 14.6, 14.9, 15.1, 15.3, 15.5, 15.7, 16, 16, 16, 16.1, 16.1, 16.2, 16.4, 16.5, - 16.8, 17.1, 17.2, 17.1, 17, 16.9, 16.8, 16.8, 16.7, 16.6, 16.6, 16.6, 16.6, 16.5, - 16.5, 16.5, 16.4, 16.3, 16.2, 16.1, 15.9, 15.8, 15.6, 15.5, 15.2, 15, 14.8, 14.6, - 14.5, 14.4, 14.3, 14.2, 14.1, 14, 13.9, 13.8, 13.8, 13.8, 13.6, 13.5, 13.3, 13.2, - 12.9, 12.5, 12.1, 11.9, 11.7, 11.5, 11.4, 11.2, 11.1, 10.9, 10.8, 10.4, 10.5, 10.6, - 10.7, 10.8, 10.9, 11, 11, 11.1, 11.1, 11, 11, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, - 11.7, 11.8, 11.8, 11.9, 12, 12, 12.1, 12.2, 12.2, 12.1, 12, 12.1, 12.1, 12.1, 12, 12, - 12, 11.9, 11.8, 11.8, 11.7, 11.6, 11.5, 11.3, 11.2, 11.1, 10.9, 10.7, 10.5, 10.2, 9.7, - 9.4, 9.1, 9, 8.8, 8.7, 8.7, 8.4, 8.2, 7.9, 7.6, 7.2, 6.9, 6.4, 6, 5.7, 5.5, 5.3, 5.2, - 5, 5, 4.9, 5.1, 5.1, 5.1, 5, 4.9, 4.9, 4.9, 5, 5, 5, 4.9, 4.8, 5, 5, 4.8, 4.8, 4.7, - 4.6, 4.5, 4.5, 4.5, 4.5, 4.4, 4.4, 4.4, 4.4, 4.4, 4.3, 4.2, 4.2, 4.4, 4.4, 4.4, 4.6, - 5, 5.5, 6.2, 6.4, 6.2, 6, 6, 6, 5.9, 5.7, 5.5, 5.4, 5.3, 5.3, 5.1, 5.1, 5.2, 5.3, 5.3, - 5.4, 5.6, 5.6, 5.4, 5.1, 4.7, 4.5, 4.4, 4.4, 4.4, 4.3, 4.3, 4.3, 4.3, 4.3, 4.1, 4, - 3.9, 3.7, 3.6, 3.4, 3.2, 3, 2.7, 2.5, 2, 1.4, 0.7, -0.1, -0.9, -1.6, -1.8, -1.8, -1.9, - -1.9, -1.9, -1.9, -2, -2.1, -2.1, -2.3, -2.4, -2.3, -2.3, -2.3, -2.3, -2.4, 16.7, 16, - 15.6, 15.1, 14.6, 14.3, 14, 13.7, 13.5, 13.2, 13, 12.9, 12.8, 12.8, 12.7, 12.8, 13.1, - 13.3, 13.6, 13.9, 14.3, 14.6, 14.8, 14.9, 15.2, 15.4, 15.5, 15.8, 16.1, 16.3, 16.4, - 16.6, 16.8, 17, 17.2, 17.4, 17.4, 17.4, 17.2, 17.1, 17, 16.9, 16.8, 16.7, 16.7, 16.7, - 16.7, 16.6, 16.6, 16.5, 16.4, 16.4, 16.3, 16.1, 15.9, 15.8, 15.6, 15.5, 15.2, 15, - 14.8, 14.7, 14.6, 14.5, 14.4, 14.2, 14.2, 14.1, 14, 13.9, 13.8, 13.8, 13.6, 13.4, - 13.2, 13, 12.7, 12.4, 12.2, 12.1, 11.8, 11.6, 11.4, 11.2, 11.1, 11, 10.9, 10.5, 10.6, - 10.8, 11, 11.1, 11.3, 11.3, 11.3, 11.3, 11.3, 11.2, 11.2, 11.3, 11.3, 11.4, 11.5, - 11.5, 11.6, 11.7, 11.8, 11.9, 12, 12, 12, 12.1, 12.2, 12.2, 12.1, 12.1, 12.1, 12.1, - 12.2, 12.1, 12.1, 12.2, 12.1, 12.1, 11.9, 11.8, 11.6, 11.5, 11.3, 11.1, 11, 10.8, - 10.7, 10.5, 10.1, 9.7, 9.4, 9, 8.8, 8.7, 8.5, 8.4, 8.2, 8.1, 7.8, 7.5, 7, 6.7, 6.2, 6, - 5.8, 5.6, 5.4, 5.4, 5.2, 5.1, 5.1, 5.1, 5.2, 5.3, 5.2, 5.2, 5.1, 5, 5, 5, 5, 4.9, 4.8, - 4.9, 5, 4.9, 4.8, 4.7, 4.6, 4.5, 4.5, 4.5, 4.4, 4.5, 4.5, 4.4, 4.4, 4.3, 4.3, 4.2, - 4.2, 4.3, 4.4, 4.5, 4.6, 4.8, 5.4, 6.1, 6.5, 6.4, 6.2, 6, 5.9, 5.8, 5.6, 5.4, 5.3, - 5.3, 5.3, 5.2, 5.2, 5.2, 5.4, 5.5, 5.7, 5.9, 6, 6, 5.8, 5, 4.7, 4.6, 4.6, 4.5, 4.5, - 4.6, 4.6, 4.7, 4.6, 4.4, 4.3, 4.1, 3.9, 3.7, 3.5, 3.3, 3, 2.8, 2.6, 2.1, 1.5, 0.9, - 0.2, -0.6, -1.5, -1.8, -1.9, -2, -1.9, -1.9, -1.9, -1.9, -2, -2.1, -2.2, -2.3, -2.3, - -2.1, -2.2, -2.3, -2.4, 16.4, 15.8, 15.4, 15, 14.6, 14.3, 14, 13.7, 13.5, 13.2, 13.1, - 13, 12.9, 12.9, 12.9, 13, 13.1, 13.2, 13.4, 13.7, 14, 14.3, 14.5, 14.7, 14.9, 15.1, - 15.4, 16.1, 16.5, 16.7, 16.9, 17.1, 17.3, 17.4, 17.5, 17.6, 17.5, 17.5, 17.4, 17.3, - 17.1, 16.9, 16.9, 16.9, 16.9, 16.8, 16.7, 16.7, 16.7, 16.6, 16.6, 16.5, 16.3, 16.1, - 15.9, 15.8, 15.6, 15.5, 15.2, 15.1, 15, 14.8, 14.7, 14.6, 14.5, 14.4, 14.3, 14.2, 14, - 13.9, 13.9, 13.8, 13.6, 13.5, 13.2, 12.9, 12.6, 12.5, 12.3, 12.2, 11.9, 11.6, 11.4, - 11.2, 11.1, 11, 10.9, 10.7, 10.7, 11, 11.3, 11.4, 11.5, 11.4, 11.4, 11.5, 11.4, 11.3, - 11.3, 11.3, 11.4, 11.4, 11.5, 11.5, 11.7, 11.8, 11.9, 12, 12, 12.1, 12.1, 12.2, 12.2, - 12.2, 12.2, 12.2, 12.2, 12.2, 12.2, 12.2, 12.2, 12.2, 12.3, 12.4, 12.1, 11.9, 11.7, - 11.5, 11.3, 11.1, 10.9, 10.8, 10.6, 10.4, 10.1, 9.7, 9.4, 9, 8.7, 8.5, 8.3, 8.2, 8, - 7.8, 7.6, 7.3, 6.9, 6.5, 6.1, 5.9, 5.8, 5.6, 5.4, 5.3, 5.2, 5.1, 5.1, 5.1, 5.2, 5.2, - 5.2, 5.2, 5.2, 5.2, 5.2, 5.1, 5, 5, 4.9, 4.9, 5, 4.9, 4.7, 4.6, 4.6, 4.5, 4.6, 4.5, - 4.4, 4.5, 4.5, 4.5, 4.5, 4.4, 4.3, 4.3, 4.3, 4.3, 4.4, 4.4, 4.6, 4.9, 5.5, 6.1, 6.5, - 6.5, 6.2, 6, 5.7, 5.6, 5.6, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.7, 5.8, 6, 6.1, 6.2, - 6.4, 6.3, 5.6, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.9, 4.8, 4.7, 4.7, 4.6, 4.4, 4.1, 3.9, - 3.7, 3.4, 3.1, 2.8, 2.5, 2, 1.5, 0.9, 0.1, -0.7, -1.4, -1.7, -1.9, -2, -2.1, -2, -2.1, - -2, -2.1, -2.1, -2.2, -2.2, -2.2, -2.1, -2.2, -2.3, -2.3, 16, 15.8, 15.5, 15.1, 14.9, - 14.6, 14.3, 13.9, 13.6, 13.3, 13.2, 13.1, 13, 13, 13, 13, 13.1, 13.2, 13.4, 13.6, - 13.8, 14.1, 14.3, 14.5, 14.8, 15.1, 15.6, 16.3, 16.8, 17.1, 17.3, 17.6, 17.7, 17.7, - 17.8, 17.8, 17.7, 17.4, 17.4, 17.3, 17.2, 17.1, 17, 16.9, 16.9, 16.9, 16.9, 16.8, - 16.7, 16.6, 16.6, 16.5, 16.3, 16.1, 16, 15.8, 15.7, 15.6, 15.3, 15.2, 15, 14.9, 14.8, - 14.7, 14.6, 14.5, 14.4, 14.3, 14.1, 13.9, 13.8, 13.7, 13.6, 13.4, 13.2, 13, 12.8, - 12.6, 12.3, 12.1, 11.9, 11.7, 11.5, 11.3, 11.2, 11.1, 11.1, 10.9, 11, 11.3, 11.6, - 11.7, 11.7, 11.6, 11.6, 11.6, 11.5, 11.4, 11.3, 11.3, 11.3, 11.4, 11.5, 11.6, 11.6, - 11.8, 11.9, 12, 12.2, 12.2, 12.2, 12.3, 12.2, 12.3, 12.3, 12.3, 12.3, 12.3, 12.2, - 12.3, 12.3, 12.2, 12.3, 12.2, 12, 11.9, 11.7, 11.5, 11.3, 11.1, 10.9, 10.8, 10.6, - 10.4, 10.1, 9.7, 9.3, 8.9, 8.6, 8.4, 8.2, 8, 7.8, 7.6, 7.4, 7.1, 6.8, 6.5, 6.1, 5.9, - 5.7, 5.5, 5.3, 5.2, 5.2, 5.2, 5.1, 5.1, 5.1, 5.2, 5.3, 5.3, 5.2, 5.2, 5.3, 5.1, 5, 5, - 5.1, 5.1, 5, 4.9, 4.7, 4.6, 4.6, 4.5, 4.6, 4.5, 4.3, 4.5, 4.5, 4.4, 4.4, 4.4, 4.4, - 4.3, 4.4, 4.4, 4.4, 4.4, 4.6, 4.9, 5.4, 5.9, 6.4, 6.5, 6.2, 5.8, 5.6, 5.5, 5.5, 5.4, - 5.4, 5.4, 5.5, 5.5, 5.6, 5.8, 5.9, 6, 6.2, 6.2, 6.2, 6.4, 6.4, 5.9, 5.2, 5.1, 5, 5.1, - 5.1, 5, 5, 5, 4.9, 4.9, 4.9, 4.6, 4.3, 4, 3.7, 3.4, 3.1, 2.8, 2.3, 1.8, 1.3, 0.7, 0, - -0.8, -1.2, -1.6, -1.8, -2, -2.2, -2.1, -2.1, -2, -2.1, -2.1, -2.2, -2.2, -2.2, -2.3, - -2.3, -2.4, -2.4, 15.8, 15.8, 15.7, 15.5, 15.2, 14.9, 14.6, 14.2, 13.9, 13.6, 13.4, - 13.3, 13.1, 13.1, 13, 13, 13.1, 13.3, 13.4, 13.6, 13.8, 14.1, 14.3, 14.5, 14.9, 15.5, - 16.1, 16.7, 17.1, 17.5, 17.8, 17.9, 17.9, 18, 17.9, 17.9, 17.7, 17.5, 17.5, 17.5, - 17.4, 17.1, 17.1, 17, 16.9, 16.9, 16.9, 16.8, 16.7, 16.6, 16.5, 16.3, 16.2, 16, 15.9, - 15.8, 15.7, 15.5, 15.3, 15.1, 15, 14.9, 14.8, 14.7, 14.6, 14.5, 14.4, 14.3, 14.1, - 13.9, 13.8, 13.7, 13.6, 13.3, 13.2, 13, 12.9, 12.7, 12.3, 12.1, 11.9, 11.7, 11.6, - 11.4, 11.3, 11.2, 11.2, 11, 11.1, 11.5, 11.7, 11.8, 11.8, 11.7, 11.7, 11.6, 11.5, - 11.4, 11.3, 11.3, 11.3, 11.3, 11.4, 11.6, 11.7, 11.8, 12, 12.1, 12.2, 12.2, 12.2, - 12.3, 12.3, 12.3, 12.3, 12.3, 12.5, 12.4, 12.3, 12.3, 12.3, 12.2, 12.1, 12.1, 12, - 11.9, 11.7, 11.5, 11.3, 11.1, 10.9, 10.8, 10.7, 10.4, 10.1, 9.7, 9.3, 8.9, 8.5, 8.3, - 8.1, 7.9, 7.7, 7.5, 7.3, 7, 6.8, 6.4, 6.1, 5.9, 5.7, 5.5, 5.4, 5.2, 5.1, 5.2, 5.2, - 5.2, 5.2, 5.2, 5.3, 5.3, 5.2, 5.3, 5.3, 5.2, 5.1, 5.1, 5.2, 5.1, 5, 4.9, 4.7, 4.7, - 4.7, 4.6, 4.6, 4.5, 4.4, 4.5, 4.5, 4.5, 4.4, 4.4, 4.3, 4.2, 4.3, 4.4, 4.5, 4.6, 4.6, - 5, 5.5, 5.9, 6.3, 6.5, 6.2, 5.8, 5.6, 5.5, 5.6, 5.5, 5.5, 5.6, 5.7, 5.7, 5.9, 6, 6.1, - 6.2, 6.5, 6.5, 6.4, 6.4, 6.3, 5.9, 5.5, 5.3, 5.2, 5.3, 5.3, 5.2, 5.2, 5.2, 5.1, 5, - 4.9, 4.7, 4.4, 4.1, 3.8, 3.4, 3, 2.6, 2.2, 1.6, 1, 0.4, -0.2, -0.7, -1.2, -1.6, -1.9, - -2, -2.2, -2, -1.9, -1.9, -2.1, -2.1, -2.3, -2.4, -2.4, -2.3, -2.2, -2.3, -2.5, 15.7, - 15.8, 15.8, 15.8, 15.7, 15.3, 15, 14.6, 14.2, 13.8, 13.5, 13.4, 13.2, 13.1, 13.1, - 13.1, 13.2, 13.3, 13.4, 13.6, 13.9, 14.2, 14.3, 14.7, 15.3, 15.9, 16.4, 16.9, 17.4, - 17.7, 17.9, 18, 18, 18.1, 18, 17.9, 17.7, 17.7, 17.6, 17.6, 17.5, 17.2, 17.1, 16.9, - 16.8, 16.8, 16.8, 16.7, 16.6, 16.5, 16.3, 16.3, 16, 15.9, 15.7, 15.7, 15.6, 15.4, - 15.2, 15, 15, 15, 14.9, 14.7, 14.6, 14.5, 14.4, 14.2, 14, 13.9, 13.7, 13.5, 13.4, - 13.2, 13, 12.8, 12.8, 12.6, 12.3, 12.1, 11.9, 11.8, 11.7, 11.6, 11.5, 11.3, 11.2, - 11.1, 11.3, 11.5, 11.7, 11.8, 11.7, 11.7, 11.6, 11.5, 11.4, 11.3, 11.3, 11.3, 11.2, - 11.2, 11.3, 11.5, 11.7, 11.9, 12, 12, 12.1, 12.1, 12.2, 12.2, 12.3, 12.3, 12.3, 12.3, - 12.4, 12.3, 12.3, 12.3, 12.2, 12.2, 12.1, 12.1, 12, 11.9, 11.7, 11.5, 11.4, 11.2, - 10.9, 10.9, 10.7, 10.5, 10.2, 9.7, 9.3, 8.9, 8.5, 8.2, 8, 7.8, 7.6, 7.4, 7.2, 7, 6.7, - 6.4, 6.2, 5.9, 5.8, 5.6, 5.5, 5.3, 5.2, 5.3, 5.3, 5.4, 5.4, 5.3, 5.3, 5.3, 5.3, 5.3, - 5.3, 5.2, 5.1, 5, 5.1, 5.1, 5, 4.9, 4.8, 4.7, 4.7, 4.7, 4.7, 4.6, 4.5, 4.5, 4.5, 4.5, - 4.4, 4.3, 4.2, 4, 4.1, 4.4, 4.5, 4.6, 4.7, 5, 5.5, 5.8, 6.3, 6.6, 6.3, 5.9, 5.7, 5.6, - 5.7, 5.7, 5.6, 5.6, 5.8, 5.9, 6.1, 6.2, 6.3, 6.4, 6.6, 6.6, 6.6, 6.4, 6.2, 5.9, 5.7, - 5.6, 5.5, 5.5, 5.4, 5.4, 5.4, 5.4, 5.2, 5.1, 4.9, 4.7, 4.4, 4.1, 3.8, 3.4, 3, 2.5, 2, - 1.4, 0.8, 0.2, -0.3, -0.8, -1.3, -1.7, -2, -2, -2, -2, -2.1, -2.1, -2.3, -2.4, -2.4, - -2.3, -2.4, -2.3, -2.3, -2.3, -2.6, 15.8, 15.9, 16, 16.1, 16, 15.6, 15.2, 14.8, 14.4, - 13.9, 13.7, 13.5, 13.3, 13.3, 13.2, 13.2, 13.3, 13.4, 13.5, 13.7, 14, 14.1, 14.4, 15, - 15.6, 16.2, 16.7, 17.1, 17.6, 17.8, 18, 18, 18.1, 18.2, 18, 17.9, 17.8, 17.7, 17.6, - 17.5, 17.4, 17.2, 17, 16.9, 16.8, 16.8, 16.7, 16.6, 16.6, 16.5, 16.3, 16.1, 15.9, - 15.8, 15.7, 15.6, 15.4, 15.2, 15.1, 15, 15, 14.9, 14.8, 14.7, 14.6, 14.5, 14.4, 14.1, - 13.9, 13.8, 13.6, 13.3, 13.2, 13, 12.7, 12.6, 12.5, 12.3, 12.2, 12.1, 12, 11.9, 11.9, - 11.8, 11.6, 11.5, 11.4, 11.3, 11.5, 11.6, 11.6, 11.7, 11.7, 11.6, 11.5, 11.4, 11.3, - 11.2, 11.2, 11.2, 11, 11.1, 11.2, 11.4, 11.6, 11.8, 11.9, 12, 12.1, 12.1, 12.2, 12.2, - 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, 12.2, 12.2, 12.1, 12.1, 12, 11.9, - 11.7, 11.6, 11.4, 11.2, 10.9, 10.8, 10.7, 10.4, 10.1, 9.7, 9.2, 8.9, 8.5, 8.2, 8, 7.7, - 7.5, 7.3, 7.1, 6.9, 6.7, 6.5, 6.2, 6.1, 5.9, 5.7, 5.5, 5.5, 5.4, 5.5, 5.5, 5.5, 5.5, - 5.4, 5.4, 5.3, 5.3, 5.3, 5.3, 5.3, 5.2, 5, 5, 5, 4.9, 4.8, 4.8, 4.7, 4.7, 4.7, 4.7, - 4.6, 4.5, 4.4, 4.5, 4.5, 4.5, 4.3, 4.1, 3.8, 4, 4.4, 4.5, 4.6, 4.7, 5.1, 5.5, 5.9, - 6.3, 6.6, 6.5, 6.1, 5.8, 5.7, 5.7, 5.8, 5.7, 5.8, 5.8, 5.9, 6.1, 6.2, 6.3, 6.5, 6.6, - 6.6, 6.6, 6.5, 6.4, 6.1, 5.9, 5.8, 5.7, 5.6, 5.6, 5.5, 5.5, 5.4, 5.3, 5.2, 5, 4.7, - 4.4, 4.1, 3.7, 3.3, 2.9, 2.4, 1.9, 1.4, 0.7, 0.1, -0.5, -1, -1.4, -1.8, -2, -2, -1.9, - -2, -2.2, -2.2, -2.2, -2.3, -2.3, -2.3, -2.3, -2.4, -2.4, -2.4, -2.4, 16, 16.3, 16.3, - 16.4, 16.2, 15.8, 15.4, 15, 14.7, 14.2, 13.8, 13.6, 13.4, 13.4, 13.3, 13.3, 13.3, - 13.5, 13.6, 13.9, 14, 14.2, 14.7, 15.3, 15.9, 16.5, 17, 17.4, 17.8, 18, 18, 18, 18.1, - 18.1, 18, 17.9, 17.7, 17.7, 17.6, 17.5, 17.2, 17, 16.9, 16.8, 16.7, 16.6, 16.5, 16.4, - 16.4, 16.4, 16.3, 16, 15.7, 15.5, 15.4, 15.3, 15.2, 15.1, 15, 15, 15, 14.9, 14.8, - 14.7, 14.5, 14.3, 14.1, 13.9, 13.7, 13.6, 13.4, 13.1, 13.1, 13, 12.4, 12.3, 12.2, - 12.2, 12.2, 12, 12, 12, 12, 11.9, 11.7, 11.6, 11.4, 11.4, 11.5, 11.5, 11.7, 11.7, - 11.6, 11.6, 11.5, 11.3, 11.1, 11.1, 11.1, 11, 10.8, 10.9, 11.1, 11.3, 11.5, 11.7, - 11.8, 12, 12.1, 12.1, 12.3, 12.2, 12.2, 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, - 12.2, 12.2, 12.2, 12.1, 12, 11.9, 11.7, 11.5, 11.3, 11.1, 10.9, 10.8, 10.6, 10.3, - 10.1, 9.7, 9.3, 8.9, 8.6, 8.2, 7.9, 7.6, 7.4, 7.3, 7, 6.8, 6.6, 6.5, 6.3, 6, 5.8, 5.7, - 5.7, 5.7, 5.6, 5.7, 5.6, 5.6, 5.6, 5.4, 5.3, 5.2, 5.3, 5.3, 5.3, 5.4, 5.2, 5.1, 5, 5, - 4.9, 4.8, 4.8, 4.7, 4.7, 4.7, 4.7, 4.6, 4.6, 4.5, 4.4, 4.4, 4.5, 4.4, 4.1, 3.8, 3.9, - 4.3, 4.5, 4.6, 4.8, 5.1, 5.5, 5.9, 6.3, 6.6, 6.6, 6.2, 5.9, 5.8, 5.8, 5.9, 5.9, 5.9, - 6, 6.1, 6.2, 6.3, 6.5, 6.7, 6.8, 6.7, 6.6, 6.5, 6.4, 6.2, 6, 5.9, 5.8, 5.7, 5.6, 5.6, - 5.5, 5.4, 5.3, 5.2, 5, 4.7, 4.3, 4, 3.6, 3.3, 2.9, 2.4, 1.9, 1.3, 0.6, 0, -0.6, -1.2, - -1.6, -1.9, -2.1, -2.1, -2, -1.9, -2.1, -2.1, -2.2, -2.2, -2.2, -2.2, -2.3, -2.3, - -2.4, -2.5, -2.3, 16.3, 16.5, 16.5, 16.5, 16.3, 15.9, 15.4, 15, 14.6, 14.3, 13.9, - 13.8, 13.6, 13.5, 13.5, 13.4, 13.4, 13.6, 13.8, 14, 14.1, 14.4, 15.1, 15.7, 16.4, - 16.9, 17.3, 17.6, 17.9, 18, 18, 18, 18, 18, 18, 17.8, 17.6, 17.5, 17.4, 17.2, 16.9, - 16.8, 16.8, 16.6, 16.5, 16.4, 16.2, 16.2, 16.2, 16.2, 16.1, 15.7, 15.4, 15.3, 15.1, - 15, 15, 15, 14.9, 14.9, 14.9, 14.8, 14.7, 14.5, 14.3, 14.1, 13.9, 13.7, 13.6, 13.4, - 13.1, 12.8, 12.9, 12.7, 12, 11.9, 11.9, 12, 12, 11.9, 11.9, 11.8, 11.9, 11.9, 11.7, - 11.6, 11.5, 11.4, 11.5, 11.6, 11.6, 11.6, 11.6, 11.5, 11.4, 11.1, 10.9, 10.8, 10.8, - 10.8, 10.7, 10.8, 11, 11.2, 11.4, 11.6, 11.9, 12, 12, 12.2, 12.3, 12.3, 12.2, 12.3, - 12.5, 12.4, 12.4, 12.3, 12.3, 12.2, 12.2, 12.2, 12.2, 12.1, 12.1, 12, 11.8, 11.6, - 11.4, 11.2, 11, 10.8, 10.6, 10.4, 10.2, 9.8, 9.4, 9, 8.6, 8.2, 7.9, 7.6, 7.4, 7.2, - 6.9, 6.8, 6.6, 6.4, 6.2, 6, 5.8, 5.7, 5.7, 5.7, 5.7, 5.6, 5.6, 5.6, 5.4, 5.3, 5.1, - 5.1, 5.2, 5.2, 5.3, 5.3, 5.2, 5.1, 5, 5, 5, 4.8, 4.7, 4.7, 4.7, 4.7, 4.8, 4.7, 4.6, - 4.5, 4.3, 4.4, 4.5, 4.4, 4.2, 3.9, 3.8, 4.1, 4.5, 4.6, 4.9, 5.1, 5.5, 5.9, 6.3, 6.5, - 6.6, 6.4, 6, 6, 6.1, 6.1, 5.9, 6, 6.2, 6.3, 6.5, 6.6, 6.6, 6.8, 6.8, 6.7, 6.6, 6.5, - 6.4, 6.3, 6.1, 6, 5.9, 5.8, 5.7, 5.7, 5.6, 5.5, 5.4, 5.3, 5, 4.6, 4.3, 3.9, 3.6, 3.2, - 2.9, 2.4, 1.9, 1.2, 0.5, 0, -0.7, -1.3, -1.8, -2, -2.1, -2.1, -2.1, -2, -2, -2, -2.1, - -2.2, -2.2, -2.2, -2.2, -2.2, -2.2, -2.2, -2.4, 16.5, 16.6, 16.6, 16.5, 16.3, 15.9, - 15.4, 15, 14.6, 14.3, 14, 13.9, 13.7, 13.6, 13.6, 13.6, 13.6, 13.7, 13.9, 14, 14.2, - 14.7, 15.4, 16.1, 16.7, 17.2, 17.6, 17.7, 17.9, 18, 18, 18, 18, 17.9, 17.9, 17.7, - 17.3, 17.2, 17, 16.7, 16.5, 16.4, 16.5, 16.4, 16.2, 16.1, 16, 15.9, 16, 15.8, 15.6, - 15.3, 15.1, 15, 14.9, 14.8, 14.8, 14.8, 14.8, 14.9, 14.8, 14.7, 14.6, 14.5, 14.2, 14, - 13.7, 13.5, 13.3, 13.1, 12.8, 12.4, 12.1, 11.8, 11.6, 11.6, 11.6, 11.7, 11.6, 11.6, - 11.7, 11.7, 11.7, 11.7, 11.6, 11.5, 11.5, 11.5, 11.5, 11.6, 11.6, 11.5, 11.4, 11.4, - 11.2, 11, 10.8, 10.6, 10.6, 10.5, 10.6, 10.6, 10.9, 11.1, 11.3, 11.6, 11.8, 11.9, 12, - 12.1, 12.2, 12.2, 12.3, 12.4, 12.4, 12.4, 12.3, 12.3, 12.3, 12.2, 12.2, 12.2, 12.2, - 12.2, 12.1, 12, 11.9, 11.6, 11.5, 11.3, 11, 10.7, 10.6, 10.4, 10.2, 9.8, 9.5, 9, 8.6, - 8.2, 7.8, 7.6, 7.3, 7.1, 6.9, 6.7, 6.6, 6.3, 6.1, 6, 5.9, 5.8, 5.8, 5.7, 5.7, 5.7, - 5.7, 5.5, 5.2, 5.1, 5, 5, 5.2, 5.3, 5.3, 5.2, 5.2, 5.1, 4.9, 5, 5, 4.9, 4.7, 4.6, 4.7, - 4.8, 4.7, 4.6, 4.5, 4.4, 4.3, 4.5, 4.5, 4.4, 4.2, 4, 3.8, 4.1, 4.4, 4.6, 4.9, 5.2, - 5.5, 5.9, 6.2, 6.4, 6.6, 6.5, 6, 6.1, 6.2, 6.2, 6, 6.1, 6.4, 6.6, 6.7, 6.8, 6.9, 6.9, - 6.9, 6.8, 6.7, 6.5, 6.5, 6.4, 6.3, 6.2, 6.1, 5.9, 5.9, 5.8, 5.7, 5.6, 5.4, 5.4, 5, - 4.5, 4.2, 3.9, 3.5, 3.2, 2.9, 2.4, 1.8, 1.2, 0.5, -0.1, -0.7, -1.3, -1.8, -2.1, -2.1, - -2.1, -2.1, -2.1, -2.1, -2.1, -2.1, -2.2, -2.2, -2, -2.1, -2.1, -2, -2.2, -2.3, 16.5, - 16.6, 16.6, 16.5, 16.3, 15.8, 15.4, 15, 14.7, 14.4, 14.1, 13.9, 13.8, 13.7, 13.7, - 13.7, 13.7, 13.9, 13.9, 14, 14.3, 15, 15.7, 16.4, 16.9, 17.5, 17.7, 17.8, 18, 18, 18, - 17.9, 17.8, 17.8, 17.7, 17.5, 17.1, 16.7, 16.4, 16.1, 16, 16.1, 16.1, 16, 15.9, 15.9, - 15.7, 15.6, 15.4, 15.2, 15.2, 15, 14.8, 14.8, 14.7, 14.7, 14.6, 14.6, 14.7, 14.7, - 14.6, 14.5, 14.3, 14.2, 14, 13.7, 13.5, 13.3, 12.9, 12.6, 12.1, 11.8, 11.6, 11.5, - 11.5, 11.5, 11.4, 11.4, 11.4, 11.4, 11.5, 11.6, 11.6, 11.7, 11.6, 11.5, 11.5, 11.6, - 11.6, 11.6, 11.5, 11.4, 11.3, 11.2, 11, 10.8, 10.6, 10.5, 10.4, 10.4, 10.5, 10.5, - 10.7, 11, 11.2, 11.5, 11.7, 11.8, 11.9, 12, 12.1, 12.1, 12.2, 12.3, 12.2, 12.2, 12.3, - 12.4, 12.3, 12.2, 12.2, 12.1, 12.1, 12.1, 12, 11.9, 11.8, 11.7, 11.4, 11.2, 11, 10.7, - 10.6, 10.4, 10.2, 9.9, 9.5, 9.1, 8.7, 8.2, 7.9, 7.6, 7.3, 7.1, 6.9, 6.7, 6.5, 6.4, - 6.2, 6.1, 5.9, 5.9, 5.9, 5.8, 5.8, 5.7, 5.4, 5.1, 5.2, 5.3, 5.2, 5, 5, 5.2, 5.2, 5.1, - 5.1, 5, 4.9, 4.9, 4.9, 4.8, 4.7, 4.7, 4.7, 4.8, 4.7, 4.6, 4.5, 4.4, 4.4, 4.5, 4.5, - 4.4, 4.2, 3.9, 3.9, 4.2, 4.4, 4.6, 4.9, 5.1, 5.4, 5.7, 6, 6.2, 6.5, 6.5, 6.1, 6, 6.1, - 6.1, 6.1, 6.4, 6.6, 6.8, 6.9, 7, 7, 7, 7, 6.9, 6.8, 6.7, 6.6, 6.6, 6.6, 6.3, 6.2, 6, - 6, 5.9, 5.8, 5.7, 5.5, 5.3, 4.9, 4.5, 4.2, 3.9, 3.6, 3.2, 2.8, 2.4, 1.8, 1.2, 0.5, - -0.1, -0.7, -1.3, -1.8, -2.1, -2.1, -2, -2.1, -2.1, -2.2, -2.1, -2.2, -2.2, -2.1, - -2.2, -2.3, -2.3, -2.2, -2.3, -2.3, 16.4, 16.5, 16.5, 16.4, 16.1, 15.8, 15.4, 15.1, - 14.8, 14.6, 14.3, 14.1, 14, 13.9, 13.8, 13.8, 13.8, 13.9, 13.9, 14, 14.5, 15.2, 15.9, - 16.5, 17, 17.5, 17.7, 17.8, 17.9, 18, 17.9, 17.8, 17.6, 17.4, 17.3, 17, 16.7, 16.3, - 16, 15.8, 15.8, 15.7, 15.6, 15.6, 15.6, 15.5, 15.4, 15.2, 15, 15, 14.9, 14.7, 14.6, - 14.6, 14.5, 14.5, 14.5, 14.5, 14.5, 14.5, 14.4, 14.2, 14.1, 13.9, 13.9, 13.6, 13.3, - 13, 12.6, 12.1, 11.8, 11.6, 11.4, 11.4, 11.4, 11.4, 11.4, 11.4, 11.4, 11.4, 11.5, - 11.6, 11.7, 11.7, 11.6, 11.5, 11.5, 11.6, 11.7, 11.6, 11.5, 11.3, 11.2, 11, 10.9, - 10.6, 10.4, 10.4, 10.3, 10.2, 10.3, 10.4, 10.6, 10.8, 11.1, 11.5, 11.6, 11.7, 11.9, - 12, 12.1, 12.2, 12.2, 12.2, 12.2, 12.2, 12.3, 12.4, 12.3, 12.3, 12.2, 12.1, 12.1, 12, - 11.9, 11.8, 11.7, 11.7, 11.4, 11.2, 11, 10.8, 10.6, 10.4, 10.2, 9.9, 9.5, 9.1, 8.7, - 8.3, 7.9, 7.6, 7.3, 7.1, 6.9, 6.7, 6.5, 6.4, 6.2, 6.1, 6, 6, 5.9, 5.9, 5.9, 5.6, 5.2, - 5.3, 5.5, 5.6, 5.4, 5.1, 4.9, 5.1, 5.1, 5, 5, 5, 4.9, 5, 5, 4.8, 4.7, 4.7, 4.7, 4.7, - 4.7, 4.6, 4.4, 4.5, 4.5, 4.5, 4.5, 4.4, 4.1, 3.8, 3.8, 4.1, 4.4, 4.7, 4.9, 5.1, 5.3, - 5.6, 5.9, 6.1, 6.4, 6.6, 6.3, 6.2, 6.2, 6.2, 6.3, 6.6, 6.8, 7, 7.2, 7.1, 7.1, 7.1, - 7.1, 7, 6.9, 6.8, 6.7, 6.6, 6.5, 6.4, 6.2, 6.1, 6.1, 6, 5.9, 5.8, 5.6, 5.2, 4.8, 4.5, - 4.2, 3.9, 3.6, 3.2, 2.8, 2.3, 1.7, 1.2, 0.5, -0.1, -0.8, -1.3, -1.7, -2, -2.1, -2.1, - -2.1, -2.2, -2.2, -2.1, -2.2, -2.2, -2.3, -2.3, -2.4, -2.4, -2.4, -2.3, -2.4, 16.2, - 16.3, 16.3, 16.2, 16.1, 15.8, 15.4, 15.1, 14.9, 14.6, 14.5, 14.4, 14.2, 14.1, 14, 14, - 14, 13.9, 14, 14.3, 14.7, 15.3, 16, 16.7, 17.1, 17.5, 17.7, 17.8, 17.9, 18, 17.9, - 17.6, 17.4, 17.2, 17, 16.7, 16.2, 15.9, 15.6, 15.6, 15.6, 15.4, 15.3, 15.2, 15.2, - 15.1, 15, 14.9, 14.8, 14.8, 14.7, 14.5, 14.4, 14.4, 14.4, 14.4, 14.5, 14.4, 14.3, - 14.2, 14.1, 14, 13.9, 13.8, 13.7, 13.3, 13, 12.7, 12.3, 11.8, 11.6, 11.5, 11.4, 11.3, - 11.3, 11.3, 11.3, 11.3, 11.4, 11.4, 11.5, 11.6, 11.7, 11.7, 11.7, 11.6, 11.6, 11.7, - 11.8, 11.6, 11.4, 11.3, 11.1, 10.9, 10.7, 10.5, 10.3, 10.2, 10.1, 10.1, 10.2, 10.3, - 10.5, 10.7, 11, 11.4, 11.5, 11.7, 11.9, 12, 12.1, 12.2, 12.2, 12.2, 12.2, 12.2, 12.3, - 12.4, 12.3, 12.2, 12.2, 12.1, 12.1, 12, 11.9, 11.8, 11.7, 11.5, 11.3, 11.2, 10.9, - 10.7, 10.6, 10.4, 10.2, 9.8, 9.5, 9.1, 8.7, 8.3, 7.9, 7.5, 7.3, 7.1, 6.9, 6.7, 6.6, - 6.4, 6.2, 6.1, 6.1, 6, 6, 6, 5.9, 5.7, 5.3, 5.4, 5.6, 5.7, 5.5, 5.3, 5.1, 5, 5, 5, 5, - 5, 5, 5, 4.9, 4.9, 4.8, 4.7, 4.7, 4.7, 4.7, 4.5, 4.4, 4.5, 4.6, 4.5, 4.4, 4.4, 4.1, - 3.9, 3.9, 4.1, 4.4, 4.7, 4.8, 5, 5.2, 5.4, 5.6, 5.9, 6.2, 6.5, 6.5, 6.3, 6.3, 6.3, - 6.5, 6.8, 7, 7.1, 7.2, 7.2, 7.2, 7.1, 7.1, 7, 7, 6.8, 6.7, 6.6, 6.5, 6.4, 6.2, 6.1, - 6.1, 6.1, 6, 5.8, 5.6, 5.3, 4.9, 4.4, 4.2, 4, 3.6, 3.2, 2.8, 2.3, 1.7, 1.1, 0.5, -0.1, - -0.8, -1.2, -1.6, -2, -2.1, -2.2, -2.1, -2.1, -2.1, -2, -2.1, -2.2, -2.2, -2.3, -2.3, - -2.3, -2.3, -2.3, -2.4, 16, 16.1, 16.1, 16.1, 15.8, 15.6, 15.4, 15.1, 14.9, 14.7, - 14.7, 14.7, 14.5, 14.4, 14.3, 14.1, 14.1, 14, 14.1, 14.4, 14.8, 15.3, 15.9, 16.5, - 17.2, 17.5, 17.7, 17.8, 17.8, 17.9, 17.7, 17.4, 17.1, 17.1, 17.1, 16.5, 15.9, 15.5, - 15.3, 15.4, 15.3, 15, 15, 14.9, 14.8, 14.8, 14.7, 14.6, 14.5, 14.5, 14.3, 14.3, 14.3, - 14.3, 14.3, 14.3, 14.2, 14.2, 14.2, 14.1, 13.9, 13.7, 13.6, 13.4, 13.2, 12.9, 12.6, - 12.3, 12, 11.6, 11.5, 11.5, 11.4, 11.3, 11.3, 11.3, 11.3, 11.3, 11.4, 11.5, 11.5, - 11.6, 11.7, 11.7, 11.7, 11.7, 11.7, 11.8, 11.9, 11.7, 11.6, 11.3, 11.1, 10.9, 10.6, - 10.4, 10.2, 10.1, 10, 10, 10, 10.2, 10.5, 10.7, 10.9, 11.1, 11.4, 11.6, 11.8, 11.9, - 12, 12.1, 12.1, 12.2, 12.1, 12.2, 12.2, 12.3, 12.2, 12.1, 12.1, 12.1, 12.1, 12, 11.9, - 11.8, 11.7, 11.5, 11.3, 11.2, 11, 10.8, 10.7, 10.4, 10.2, 9.8, 9.5, 9.1, 8.8, 8.3, - 7.9, 7.6, 7.3, 7.1, 6.9, 6.8, 6.6, 6.4, 6.2, 6.1, 6, 6.1, 6, 6, 5.9, 5.6, 5.3, 5.3, - 5.6, 5.6, 5.5, 5.4, 5.2, 5, 5, 5, 5.1, 5.1, 5, 5, 5, 5, 4.9, 4.7, 4.7, 4.7, 4.6, 4.5, - 4.6, 4.6, 4.5, 4.5, 4.4, 4.4, 4.3, 4.1, 4, 4, 4.3, 4.6, 4.8, 4.9, 5.1, 5.2, 5.4, 5.6, - 5.9, 6.4, 6.5, 6.4, 6.5, 6.6, 6.8, 7, 7.2, 7.2, 7.2, 7.2, 7.2, 7.2, 7.1, 7.1, 7, 6.9, - 6.8, 6.7, 6.6, 6.4, 6.2, 6.2, 6.2, 6.1, 6, 5.8, 5.6, 5.3, 4.9, 4.4, 4.2, 4, 3.6, 3.2, - 2.7, 2.2, 1.7, 1.1, 0.4, -0.1, -0.7, -1.1, -1.5, -1.9, -2.1, -2.2, -2.1, -2.1, -2, - -2.1, -2, -2.1, -2.2, -2.3, -2.3, -2.3, -2.3, -2.4, -2.4, 15.7, 15.7, 15.7, 15.7, - 15.5, 15.3, 15.2, 15, 14.8, 14.7, 14.7, 14.8, 14.8, 14.7, 14.5, 14.3, 14.2, 14.2, - 14.3, 14.5, 14.7, 15.1, 15.7, 16.3, 16.9, 17.3, 17.5, 17.6, 17.6, 17.5, 17.4, 17.1, - 16.8, 16.8, 16.7, 16.1, 15.4, 15.1, 15, 15, 14.8, 14.7, 14.7, 14.5, 14.4, 14.4, 14.4, - 14.3, 14.2, 14.1, 14.1, 14.1, 14.1, 14.2, 14.2, 14.1, 14.1, 14.1, 14.1, 13.9, 13.7, - 13.6, 13.4, 13.1, 12.8, 12.6, 12.2, 12, 11.7, 11.6, 11.5, 11.4, 11.3, 11.2, 11.2, - 11.2, 11.3, 11.4, 11.5, 11.6, 11.5, 11.6, 11.7, 11.8, 11.8, 11.8, 11.8, 11.8, 11.9, - 11.7, 11.6, 11.4, 11.2, 10.9, 10.7, 10.4, 10.2, 10, 10, 9.8, 9.8, 10, 10.3, 10.5, - 10.8, 11, 11.2, 11.5, 11.7, 11.8, 11.9, 12, 12.1, 12.2, 12.2, 12.2, 12.2, 12.2, 12.1, - 12.1, 12.1, 12, 12.1, 12.1, 12, 11.8, 11.8, 11.6, 11.4, 11.2, 11.1, 10.9, 10.8, 10.5, - 10.2, 9.8, 9.6, 9.2, 8.8, 8.3, 8, 7.7, 7.5, 7.2, 7, 6.8, 6.6, 6.6, 6.3, 6.2, 6.2, 6.2, - 6.1, 6.1, 5.9, 5.5, 5.3, 5.5, 5.7, 5.6, 5.5, 5.5, 5.3, 5.2, 5, 5.1, 5.1, 5.1, 5, 4.9, - 5, 5, 4.9, 4.9, 4.8, 4.8, 4.7, 4.6, 4.6, 4.5, 4.5, 4.4, 4.4, 4.4, 4.4, 4.3, 4.1, 4.1, - 4.3, 4.6, 4.7, 4.8, 4.9, 5.1, 5.3, 5.5, 5.8, 6.2, 6.5, 6.6, 6.7, 6.9, 7.1, 7.3, 7.3, - 7.3, 7.3, 7.3, 7.2, 7.2, 7.1, 7.1, 7, 6.9, 6.8, 6.7, 6.6, 6.4, 6.2, 6.2, 6.1, 6.1, 6, - 5.9, 5.6, 5.3, 4.9, 4.6, 4.3, 4, 3.7, 3.2, 2.7, 2.2, 1.6, 1, 0.5, -0.1, -0.5, -1, - -1.4, -1.9, -2.1, -2.2, -2.2, -2.3, -2.1, -2, -2.1, -2.2, -2.4, -2.4, -2.3, -2.3, - -2.3, -2.3, -2.4, 15.6, 15.4, 15.4, 15.3, 15.2, 15.1, 15, 14.8, 14.7, 14.6, 14.7, - 14.9, 14.9, 14.8, 14.6, 14.4, 14.3, 14.3, 14.5, 14.6, 14.8, 15.1, 15.6, 16.1, 16.6, - 16.9, 17.1, 17.2, 17.2, 17.2, 17.1, 16.7, 16.4, 16.2, 15.9, 15.4, 14.9, 14.7, 14.6, - 14.6, 14.4, 14.3, 14.3, 14.2, 14.1, 14.1, 14.1, 14.1, 14, 13.9, 13.9, 13.9, 14, 14, - 14, 14, 14, 14, 13.9, 13.7, 13.5, 13.4, 13.3, 13, 12.5, 12.3, 12.1, 11.9, 11.7, 11.5, - 11.4, 11.4, 11.3, 11.2, 11.2, 11.2, 11.3, 11.4, 11.6, 11.6, 11.7, 11.8, 11.7, 11.7, - 11.8, 11.8, 11.8, 11.8, 11.8, 11.7, 11.5, 11.3, 11.1, 10.8, 10.5, 10.3, 10.2, 10, 9.9, - 9.7, 9.7, 9.8, 10, 10.3, 10.6, 10.9, 11.2, 11.4, 11.5, 11.6, 11.8, 11.9, 12, 12, 12.1, - 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12, 12.1, 12, 11.9, 11.8, 11.6, 11.4, 11.2, - 11.1, 11, 10.9, 10.6, 10.2, 9.9, 9.6, 9.2, 8.9, 8.5, 8.2, 7.9, 7.6, 7.4, 7.1, 6.8, - 6.7, 6.5, 6.3, 6.2, 6.2, 6.2, 6.1, 6, 5.8, 5.4, 5.3, 5.6, 5.7, 5.6, 5.5, 5.5, 5.3, - 5.1, 5, 5.1, 5.1, 5.1, 5, 5, 5, 5, 5, 4.9, 4.9, 4.8, 4.7, 4.6, 4.6, 4.5, 4.5, 4.5, - 4.4, 4.4, 4.4, 4.4, 4.2, 4.2, 4.4, 4.6, 4.6, 4.7, 4.8, 5, 5.2, 5.4, 5.6, 6, 6.4, 6.6, - 6.8, 7, 7.2, 7.4, 7.4, 7.4, 7.4, 7.3, 7.2, 7.2, 7.1, 7, 7, 7, 6.8, 6.8, 6.6, 6.4, 6.3, - 6.2, 6.2, 6.1, 6.1, 6, 5.7, 5.4, 5, 4.7, 4.4, 4.1, 3.8, 3.3, 2.8, 2.3, 1.7, 1.1, 0.6, - 0, -0.5, -1, -1.5, -1.8, -2, -2.2, -2.2, -2.2, -2.1, -2, -2.1, -2.2, -2.3, -2.3, -2.2, - -2.2, -2.3, -2.3, -2.4, 15.6, 15.4, 15.2, 15.1, 15, 15, 14.8, 14.7, 14.6, 14.6, 14.6, - 14.7, 14.8, 14.7, 14.7, 14.5, 14.4, 14.5, 14.8, 14.8, 15, 15.2, 15.5, 15.9, 16.3, - 16.6, 16.7, 16.8, 16.8, 16.8, 16.6, 16.3, 16, 15.7, 15.2, 14.8, 14.4, 14.2, 14.3, - 14.3, 14.1, 14, 13.9, 13.9, 13.9, 13.9, 13.9, 13.9, 13.8, 13.7, 13.8, 13.8, 13.8, - 13.8, 13.8, 13.8, 13.8, 13.8, 13.6, 13.5, 13.3, 13.3, 13.1, 12.7, 12.4, 12.3, 12.1, - 11.8, 11.5, 11.4, 11.3, 11.3, 11.2, 11.2, 11.2, 11.3, 11.3, 11.4, 11.6, 11.7, 11.8, - 11.9, 11.8, 11.7, 11.8, 11.8, 11.7, 11.7, 11.7, 11.7, 11.5, 11.3, 11, 10.7, 10.5, - 10.2, 10, 9.9, 9.8, 9.7, 9.7, 9.7, 9.8, 10.1, 10.5, 10.7, 11, 11.2, 11.4, 11.5, 11.6, - 11.7, 11.8, 11.9, 12, 12, 12.1, 12.1, 12.1, 12, 12.1, 12, 12, 12, 12, 11.8, 11.7, - 11.5, 11.4, 11.2, 11.1, 11, 10.9, 10.7, 10.3, 9.9, 9.7, 9.3, 9, 8.7, 8.4, 8.1, 7.8, - 7.5, 7.1, 6.9, 6.7, 6.6, 6.5, 6.3, 6.2, 6.2, 6.1, 6, 5.6, 5.4, 5.6, 5.8, 5.8, 5.7, - 5.6, 5.4, 5.2, 5.1, 5, 5.1, 5.2, 5.1, 5.1, 5, 5, 4.9, 4.9, 4.8, 4.8, 4.7, 4.7, 4.7, - 4.6, 4.6, 4.6, 4.6, 4.5, 4.4, 4.4, 4.3, 4.2, 4.2, 4.3, 4.5, 4.6, 4.6, 4.7, 4.9, 5, - 5.2, 5.5, 5.9, 6.3, 6.7, 7, 7.2, 7.3, 7.4, 7.4, 7.4, 7.4, 7.4, 7.3, 7.2, 7.1, 7.1, - 7.1, 6.9, 6.8, 6.8, 6.7, 6.5, 6.4, 6.4, 6.3, 6.2, 6.1, 6, 5.8, 5.4, 5.1, 4.8, 4.5, - 4.2, 3.9, 3.4, 2.9, 2.3, 1.8, 1.2, 0.6, 0.1, -0.4, -0.9, -1.4, -1.7, -1.9, -2.2, -2.2, - -2.2, -2.1, -2.1, -2.1, -2.1, -2.2, -2.2, -2.2, -2.3, -2.4, -2.4, -2.4, 15.7, 15.4, - 15.2, 15.1, 15, 14.9, 14.8, 14.6, 14.5, 14.5, 14.5, 14.6, 14.7, 14.7, 14.6, 14.5, - 14.5, 14.8, 15.1, 15.2, 15.3, 15.4, 15.5, 15.7, 16, 16.3, 16.4, 16.4, 16.6, 16.7, - 16.2, 15.9, 15.5, 15.1, 14.7, 14.3, 14.1, 13.9, 13.9, 13.9, 13.9, 13.9, 13.8, 13.8, - 13.7, 13.7, 13.8, 13.7, 13.7, 13.7, 13.8, 13.8, 13.8, 13.8, 13.8, 13.8, 13.9, 13.9, - 13.8, 13.6, 13.5, 13.3, 13, 12.7, 12.5, 12.2, 12, 11.7, 11.5, 11.4, 11.3, 11.2, 11.1, - 11.2, 11.3, 11.4, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9, 11.8, 11.8, 11.9, 11.9, 11.7, - 11.7, 11.7, 11.7, 11.5, 11.2, 11, 10.8, 10.5, 10.3, 10, 9.8, 9.7, 9.6, 9.6, 9.6, 9.7, - 9.9, 10.3, 10.5, 10.8, 11.1, 11.2, 11.3, 11.5, 11.7, 11.7, 11.8, 11.8, 11.9, 12, 12, - 12, 12, 12, 12, 11.9, 11.9, 11.9, 11.7, 11.6, 11.5, 11.3, 11.2, 11.1, 11, 10.8, 10.6, - 10.3, 10, 9.8, 9.5, 9.1, 8.9, 8.6, 8.3, 8, 7.5, 7.2, 6.9, 6.7, 6.7, 6.6, 6.3, 6.2, 6, - 5.7, 5.6, 5.6, 5.8, 5.9, 5.9, 5.9, 5.9, 5.8, 5.6, 5.2, 5.1, 5, 5.1, 5.1, 5.1, 5, 5, 5, - 5, 5, 5, 4.8, 4.8, 4.8, 4.7, 4.7, 4.7, 4.6, 4.6, 4.6, 4.5, 4.4, 4.3, 4.3, 4.3, 4.3, - 4.4, 4.5, 4.6, 4.7, 4.9, 5.1, 5.1, 5.4, 5.8, 6.3, 6.8, 7.1, 7.4, 7.4, 7.4, 7.5, 7.6, - 7.5, 7.4, 7.4, 7.2, 7.1, 7.1, 7.1, 7, 6.8, 6.8, 6.7, 6.6, 6.6, 6.5, 6.4, 6.3, 6.2, 6, - 5.8, 5.5, 5.2, 4.9, 4.5, 4.2, 3.9, 3.5, 2.9, 2.3, 1.7, 1.2, 0.7, 0.2, -0.3, -0.8, - -1.4, -1.7, -1.9, -2.2, -2.2, -2.2, -2, -2, -2, -2, -2.1, -2.1, -2.3, -2.4, -2.4, - -2.4, -2.4, 15.9, 15.5, 15.3, 15.1, 15, 14.8, 14.7, 14.6, 14.5, 14.5, 14.5, 14.5, - 14.6, 14.6, 14.6, 14.6, 14.8, 15.1, 15.3, 15.5, 15.6, 15.8, 15.8, 15.8, 15.9, 15.9, - 15.9, 15.9, 16.2, 16.2, 15.7, 15.4, 15, 14.6, 14.2, 14, 13.8, 13.7, 13.7, 13.7, 13.7, - 13.7, 13.7, 13.6, 13.6, 13.6, 13.7, 13.6, 13.6, 13.7, 13.8, 13.8, 13.8, 13.9, 13.9, - 13.9, 14.1, 14.2, 14.1, 13.9, 13.7, 13.3, 13.1, 12.8, 12.4, 12.1, 11.8, 11.7, 11.6, - 11.4, 11.3, 11.3, 11.2, 11.2, 11.3, 11.4, 11.5, 11.6, 11.6, 11.8, 11.9, 11.9, 11.8, - 11.8, 11.9, 11.9, 11.7, 11.7, 11.7, 11.7, 11.5, 11.3, 11.1, 10.9, 10.6, 10.3, 10, 9.7, - 9.5, 9.5, 9.5, 9.5, 9.5, 9.7, 9.9, 10.2, 10.6, 10.9, 11, 11.2, 11.4, 11.5, 11.6, 11.7, - 11.8, 11.9, 11.9, 11.9, 11.9, 11.9, 11.9, 11.9, 11.9, 11.8, 11.8, 11.6, 11.5, 11.3, - 11.2, 11.1, 11, 10.8, 10.6, 10.5, 10.3, 10, 9.9, 9.6, 9.3, 9, 8.7, 8.5, 8.1, 7.6, 7.3, - 7, 6.8, 6.8, 6.7, 6.3, 5.9, 5.7, 5.6, 5.7, 6, 6, 6.1, 6.1, 6, 6, 5.9, 5.7, 5.4, 5.1, - 5, 5, 5, 5, 5.1, 5.1, 5.1, 5.1, 5, 5, 4.9, 4.9, 4.8, 4.8, 4.8, 4.8, 4.7, 4.7, 4.7, - 4.6, 4.5, 4.3, 4.2, 4.3, 4.3, 4.3, 4.4, 4.5, 4.7, 4.8, 5, 5.1, 5.3, 5.7, 6.2, 6.7, - 7.1, 7.4, 7.5, 7.6, 7.6, 7.6, 7.5, 7.5, 7.4, 7.2, 7.1, 7.1, 7, 7, 6.9, 6.8, 6.7, 6.6, - 6.6, 6.5, 6.4, 6.3, 6.2, 6.1, 5.9, 5.6, 5.3, 5, 4.6, 4.3, 3.9, 3.4, 2.9, 2.3, 1.7, - 1.3, 0.8, 0.3, -0.2, -0.7, -1.3, -1.6, -1.8, -2.1, -2.2, -2.3, -2.1, -2, -2.1, -2, - -2.1, -2.2, -2.3, -2.3, -2.3, -2.4, -2.3, 16, 15.6, 15.4, 15.2, 15, 14.8, 14.7, 14.7, - 14.6, 14.6, 14.6, 14.5, 14.5, 14.5, 14.5, 14.6, 15, 15.2, 15.4, 15.7, 16, 16.2, 16.2, - 16.2, 16.1, 15.8, 15.5, 15.4, 15.4, 15.2, 15, 14.8, 14.5, 14.1, 13.9, 13.7, 13.6, - 13.6, 13.5, 13.5, 13.6, 13.5, 13.5, 13.5, 13.6, 13.7, 13.6, 13.6, 13.6, 13.7, 13.8, - 13.8, 13.9, 14, 14, 14.1, 14.3, 14.4, 14.4, 14.2, 13.8, 13.5, 13.2, 12.7, 12.2, 12, - 11.8, 11.7, 11.6, 11.5, 11.4, 11.3, 11.2, 11.2, 11.3, 11.5, 11.6, 11.6, 11.6, 11.8, - 11.9, 11.9, 11.8, 11.8, 11.9, 11.9, 11.8, 11.7, 11.7, 11.7, 11.6, 11.4, 11.2, 11, - 10.7, 10.3, 10, 9.7, 9.5, 9.5, 9.4, 9.4, 9.5, 9.5, 9.7, 10, 10.3, 10.5, 10.8, 11, - 11.2, 11.4, 11.4, 11.6, 11.8, 11.9, 11.8, 11.8, 11.8, 11.8, 11.8, 11.8, 11.8, 11.7, - 11.6, 11.6, 11.5, 11.3, 11.1, 11, 10.9, 10.7, 10.6, 10.4, 10.2, 10.1, 9.9, 9.7, 9.4, - 9.1, 8.8, 8.5, 8.1, 7.8, 7.4, 7.2, 6.9, 6.8, 6.5, 6, 5.8, 5.9, 6, 6.1, 6.2, 6.1, 6.1, - 6.1, 6.2, 6.2, 6.1, 5.9, 5.5, 5.2, 5.1, 5, 4.9, 5, 5, 4.9, 5, 5, 5, 4.9, 4.8, 4.8, - 4.8, 4.8, 4.8, 4.8, 4.7, 4.7, 4.7, 4.6, 4.6, 4.4, 4.2, 4.2, 4.4, 4.4, 4.4, 4.4, 4.6, - 4.8, 4.9, 5.1, 5.2, 5.6, 6.1, 6.6, 7, 7.3, 7.5, 7.7, 7.7, 7.6, 7.5, 7.4, 7.3, 7.2, - 7.1, 7, 7, 6.9, 6.9, 6.8, 6.8, 6.7, 6.6, 6.6, 6.5, 6.4, 6.2, 6.1, 5.9, 5.7, 5.4, 5.1, - 4.7, 4.4, 4, 3.5, 2.9, 2.4, 1.9, 1.4, 0.9, 0.5, 0, -0.4, -0.9, -1.4, -1.8, -2, -2.2, - -2.2, -2.2, -2.2, -2.1, -2, -2.2, -2.3, -2.3, -2.3, -2.4, -2.4, -2.3, 16.2, 15.8, - 15.6, 15.3, 15, 14.9, 14.8, 14.7, 14.7, 14.7, 14.6, 14.5, 14.5, 14.5, 14.5, 14.7, 15, - 15.3, 15.6, 16.1, 16.4, 16.6, 16.7, 16.7, 16.6, 16.2, 15.6, 15.2, 14.9, 14.6, 14.4, - 14.3, 14.1, 13.8, 13.7, 13.6, 13.5, 13.5, 13.4, 13.4, 13.5, 13.5, 13.6, 13.6, 13.7, - 13.7, 13.6, 13.6, 13.7, 13.8, 13.9, 14, 14.1, 14.2, 14.2, 14.3, 14.4, 14.5, 14.4, - 14.2, 14, 13.7, 13.3, 12.6, 12.2, 12, 11.8, 11.7, 11.6, 11.5, 11.4, 11.2, 11.2, 11.3, - 11.3, 11.5, 11.5, 11.6, 11.7, 11.9, 11.9, 11.9, 11.9, 11.9, 11.9, 11.8, 11.7, 11.7, - 11.7, 11.7, 11.6, 11.4, 11.3, 11, 10.7, 10.4, 9.9, 9.7, 9.6, 9.4, 9.4, 9.4, 9.4, 9.4, - 9.6, 9.8, 10.1, 10.3, 10.6, 10.9, 11, 11.2, 11.4, 11.5, 11.7, 11.8, 11.7, 11.6, 11.7, - 11.7, 11.7, 11.7, 11.6, 11.5, 11.5, 11.5, 11.5, 11.3, 11.1, 11, 10.8, 10.7, 10.5, - 10.3, 10.2, 10, 9.9, 9.7, 9.5, 9.3, 9, 8.7, 8.3, 8, 7.6, 7.3, 6.9, 6.7, 6.2, 5.8, 6, - 6.2, 6.1, 6.2, 6.3, 6.2, 6.2, 6.3, 6.4, 6.4, 6.4, 6.2, 5.7, 5.3, 5.1, 5, 5, 5, 5, 4.9, - 4.9, 5, 4.9, 4.9, 4.9, 4.9, 4.8, 4.8, 4.8, 4.8, 4.8, 4.7, 4.7, 4.7, 4.6, 4.5, 4.4, - 4.3, 4.4, 4.4, 4.4, 4.4, 4.6, 4.7, 4.9, 5, 5.1, 5.4, 5.9, 6.4, 6.9, 7.3, 7.5, 7.6, - 7.7, 7.7, 7.5, 7.4, 7.4, 7.3, 7.2, 7.1, 7, 7, 7, 6.9, 6.8, 6.7, 6.6, 6.6, 6.5, 6.4, - 6.3, 6.1, 5.9, 5.7, 5.5, 5.2, 4.9, 4.6, 4.1, 3.6, 3.1, 2.6, 2, 1.5, 1.1, 0.7, 0.3, - -0.1, -0.6, -1.1, -1.6, -2, -2.2, -2.2, -2.2, -2.2, -2.1, -2.1, -2.2, -2.3, -2.2, - -2.2, -2.3, -2.2, -2.2, 16.4, 16.2, 15.9, 15.5, 15.2, 15.1, 14.8, 14.7, 14.6, 14.6, - 14.6, 14.6, 14.6, 14.6, 14.6, 14.7, 15.1, 15.5, 15.8, 16.3, 16.6, 16.9, 17.1, 17, - 16.9, 16.4, 15.8, 15.2, 14.7, 14.4, 14.2, 14.1, 14, 13.7, 13.6, 13.5, 13.4, 13.4, - 13.4, 13.4, 13.5, 13.5, 13.7, 13.8, 13.7, 13.7, 13.6, 13.7, 13.8, 13.9, 14, 14.1, - 14.2, 14.4, 14.4, 14.5, 14.6, 14.6, 14.4, 14.3, 14, 13.8, 13.2, 12.6, 12.3, 12, 11.7, - 11.7, 11.6, 11.5, 11.4, 11.3, 11.2, 11.3, 11.4, 11.4, 11.5, 11.6, 11.8, 11.9, 12, - 11.9, 11.8, 11.8, 11.9, 11.8, 11.8, 11.7, 11.7, 11.6, 11.6, 11.5, 11.3, 11.1, 10.8, - 10.5, 10, 9.6, 9.5, 9.4, 9.3, 9.2, 9.3, 9.4, 9.5, 9.7, 9.9, 10.2, 10.5, 10.7, 10.9, - 11.1, 11.3, 11.3, 11.4, 11.5, 11.5, 11.5, 11.6, 11.5, 11.5, 11.5, 11.4, 11.3, 11.4, - 11.4, 11.3, 11.2, 11, 11, 10.8, 10.6, 10.5, 10.3, 10.2, 10.1, 9.9, 9.8, 9.6, 9.4, 9.2, - 8.9, 8.5, 8.1, 7.7, 7.3, 7, 6.7, 6.1, 5.9, 6.2, 6.3, 6.2, 6.3, 6.4, 6.5, 6.4, 6.4, - 6.5, 6.7, 6.7, 6.4, 6, 5.5, 5.2, 5.1, 5.1, 5, 4.9, 4.9, 5, 5, 5, 4.9, 4.9, 4.9, 4.9, - 4.9, 4.8, 4.9, 4.9, 4.8, 4.7, 4.7, 4.6, 4.5, 4.5, 4.4, 4.3, 4.4, 4.4, 4.4, 4.5, 4.6, - 4.8, 4.9, 5, 5.2, 5.6, 6.2, 6.8, 7.2, 7.4, 7.6, 7.6, 7.7, 7.7, 7.5, 7.4, 7.3, 7.2, - 7.2, 7.1, 7, 7, 6.9, 6.9, 6.8, 6.7, 6.7, 6.6, 6.5, 6.4, 6.2, 6, 5.8, 5.6, 5.3, 5, 4.7, - 4.3, 3.8, 3.3, 2.8, 2.3, 1.7, 1.3, 0.9, 0.5, 0.1, -0.3, -0.7, -1.2, -1.8, -2.2, -2.2, - -2.1, -2.2, -2.2, -2.1, -2.2, -2.2, -2.2, -2.1, -2.2, -2, -1.9, 16.5, 16.4, 16.2, - 15.8, 15.5, 15.3, 15, 14.7, 14.6, 14.6, 14.6, 14.6, 14.6, 14.6, 14.6, 14.8, 15.2, - 15.6, 16, 16.5, 16.9, 17.2, 17.4, 17.4, 17.2, 16.7, 16, 15.4, 14.8, 14.4, 14.2, 14, - 13.9, 13.7, 13.5, 13.4, 13.3, 13.3, 13.4, 13.5, 13.5, 13.6, 14, 14, 13.9, 13.8, 13.8, - 13.8, 13.9, 14.1, 14, 14.1, 14.4, 14.6, 14.7, 14.6, 14.6, 14.6, 14.5, 14.4, 14.2, - 13.9, 13.3, 12.6, 12.2, 12, 11.8, 11.7, 11.6, 11.5, 11.4, 11.3, 11.3, 11.5, 11.5, - 11.5, 11.5, 11.6, 11.8, 11.9, 12, 12, 11.9, 11.8, 11.9, 11.9, 11.8, 11.7, 11.7, 11.6, - 11.6, 11.6, 11.4, 11.2, 10.9, 10.5, 10.2, 9.7, 9.5, 9.3, 9.2, 9.2, 9.2, 9.3, 9.5, 9.6, - 9.7, 10.1, 10.4, 10.6, 10.7, 10.9, 11.1, 11.2, 11.3, 11.3, 11.3, 11.3, 11.4, 11.3, - 11.3, 11.3, 11.3, 11.2, 11.2, 11.1, 11, 10.9, 10.9, 10.8, 10.7, 10.5, 10.3, 10.2, - 10.1, 10, 9.9, 9.8, 9.8, 9.5, 9.3, 9.1, 8.7, 8.2, 7.7, 7.3, 7, 6.5, 6.1, 6.3, 6.5, - 6.3, 6.4, 6.6, 6.7, 6.7, 6.6, 6.5, 6.6, 6.7, 6.7, 6.5, 6, 5.5, 5.3, 5.1, 5, 5, 4.9, - 4.8, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 4.8, 4.8, 4.8, 4.7, 4.6, 4.6, - 4.6, 4.5, 4.4, 4.4, 4.4, 4.4, 4.3, 4.5, 4.7, 4.8, 4.9, 5.1, 5.5, 6, 6.5, 7, 7.3, 7.5, - 7.6, 7.7, 7.7, 7.5, 7.4, 7.3, 7.3, 7.1, 7.1, 7.1, 7.1, 7, 6.9, 6.9, 6.7, 6.7, 6.7, - 6.6, 6.4, 6.3, 6.1, 5.9, 5.7, 5.4, 5.1, 4.8, 4.5, 4.1, 3.6, 3.2, 2.6, 2.1, 1.6, 1.2, - 0.8, 0.4, 0, -0.4, -0.8, -1.3, -1.7, -1.9, -2.1, -2.2, -2.2, -2.1, -2.1, -2.2, -2.1, - -2.1, -2.1, -2, -1.8, 16.7, 16.5, 16.4, 16.3, 16.1, 15.6, 15.3, 15, 14.8, 14.7, 14.7, - 14.7, 14.7, 14.7, 14.7, 15, 15.3, 15.8, 16.3, 16.8, 17.2, 17.5, 17.8, 17.8, 17.3, - 16.8, 16.2, 15.6, 15, 14.5, 14.1, 14, 13.8, 13.7, 13.5, 13.4, 13.3, 13.3, 13.5, 13.6, - 13.6, 13.8, 14.2, 14.2, 14.1, 14, 14, 14.1, 14.1, 14.1, 14.1, 14.2, 14.6, 14.9, 14.9, - 14.7, 14.6, 14.7, 14.7, 14.5, 14.4, 14, 13.4, 12.8, 12.4, 12.2, 11.9, 11.7, 11.6, - 11.5, 11.5, 11.4, 11.4, 11.5, 11.5, 11.5, 11.5, 11.6, 11.8, 12, 12.1, 12.1, 12, 12, - 12, 12.1, 12, 11.8, 11.7, 11.7, 11.7, 11.7, 11.6, 11.4, 11, 10.6, 10.2, 9.8, 9.6, 9.4, - 9.3, 9.1, 9.1, 9.3, 9.4, 9.5, 9.7, 10, 10.3, 10.5, 10.6, 10.8, 11, 11.1, 11.2, 11.2, - 11.2, 11.2, 11.2, 11.1, 11.1, 11.2, 11.1, 11, 11, 11, 10.8, 10.7, 10.7, 10.7, 10.5, - 10.3, 10.1, 10.1, 10, 9.8, 9.8, 9.8, 9.8, 9.6, 9.4, 9.1, 8.7, 8.2, 7.8, 7.1, 6.6, 6.3, - 6.5, 6.8, 6.8, 6.6, 6.7, 6.8, 6.8, 6.8, 6.7, 6.6, 6.6, 6.6, 6.6, 6.5, 6, 5.6, 5.4, - 5.1, 5, 4.9, 4.9, 4.9, 5, 4.9, 5, 4.9, 5, 5, 5, 4.9, 4.9, 4.9, 4.9, 4.8, 4.8, 4.8, - 4.7, 4.7, 4.7, 4.6, 4.6, 4.4, 4.4, 4.5, 4.3, 4.5, 4.7, 4.8, 4.9, 5.1, 5.4, 5.9, 6.3, - 6.8, 7.2, 7.4, 7.6, 7.6, 7.6, 7.5, 7.4, 7.3, 7.3, 7.2, 7.1, 7.1, 7.1, 7, 6.9, 6.8, - 6.8, 6.8, 6.7, 6.6, 6.5, 6.3, 6.2, 6, 5.8, 5.6, 5.4, 5.1, 4.7, 4.3, 4, 3.5, 3, 2.4, - 1.9, 1.5, 1.2, 0.8, 0.5, 0.2, -0.2, -0.6, -1.1, -1.5, -1.9, -2.1, -2.2, -2.1, -2.2, - -2.1, -1.9, -2, -2, -1.9, -1.8, 16.9, 16.7, 16.5, 16.5, 16.4, 16.1, 15.7, 15.3, 15, - 14.9, 14.8, 14.8, 14.8, 14.7, 14.9, 15.2, 15.6, 16.2, 16.6, 17, 17.5, 17.7, 18, 17.9, - 17.5, 17, 16.5, 15.8, 15.2, 14.7, 14.3, 14, 13.8, 13.7, 13.5, 13.4, 13.3, 13.5, 13.6, - 13.7, 13.7, 13.9, 14.2, 14.4, 14.4, 14.4, 14.3, 14.2, 14.1, 14.2, 14.2, 14.4, 14.8, - 15, 15, 14.8, 14.7, 14.7, 14.7, 14.6, 14.5, 14, 13.4, 12.9, 12.6, 12.3, 12.1, 11.8, - 11.7, 11.6, 11.5, 11.5, 11.4, 11.4, 11.4, 11.5, 11.6, 11.6, 11.8, 12, 12.1, 12.3, - 12.2, 12.1, 12.2, 12.2, 12.1, 11.9, 11.8, 11.7, 11.7, 11.6, 11.5, 11.4, 11, 10.6, - 10.3, 10, 9.7, 9.5, 9.5, 9.3, 9.1, 9.2, 9.3, 9.4, 9.7, 9.9, 10.2, 10.4, 10.5, 10.7, - 10.7, 10.8, 11, 11.1, 11.1, 11.2, 11.2, 11, 10.9, 10.9, 10.9, 10.9, 10.9, 10.8, 10.6, - 10.5, 10.5, 10.4, 10.3, 10, 10, 9.9, 9.8, 9.7, 9.7, 9.7, 9.7, 9.6, 9.4, 9, 8.6, 8.1, - 7.5, 6.6, 6.4, 6.6, 6.9, 7.1, 7.2, 7.2, 7.1, 7.1, 7, 6.9, 6.9, 6.8, 6.7, 6.6, 6.6, - 6.4, 6.1, 5.8, 5.5, 5.2, 5, 5, 5, 5, 5, 4.9, 5, 5, 5, 5, 5, 5, 4.9, 4.9, 4.9, 4.8, - 4.8, 4.8, 4.7, 4.8, 4.7, 4.7, 4.6, 4.5, 4.4, 4.6, 4.5, 4.5, 4.7, 4.8, 4.8, 5, 5.3, - 5.8, 6.2, 6.6, 7.1, 7.4, 7.6, 7.6, 7.6, 7.5, 7.4, 7.3, 7.3, 7.2, 7.2, 7.1, 7.1, 7, 7, - 6.9, 6.8, 6.9, 6.8, 6.6, 6.5, 6.4, 6.3, 6.2, 6, 5.7, 5.5, 5.3, 4.9, 4.6, 4.2, 3.8, - 3.3, 2.7, 2.3, 1.9, 1.6, 1.3, 1, 0.8, 0.5, 0, -0.6, -1, -1.5, -2, -2.1, -2.1, -2, -2, - -2, -2, -1.8, -1.8, -1.8, 17, 16.9, 16.7, 16.5, 16.5, 16.4, 16, 15.7, 15.4, 15.2, 15, - 14.9, 14.9, 14.8, 15.1, 15.4, 15.9, 16.5, 16.9, 17.3, 17.8, 18, 18.1, 18, 17.9, 17.6, - 16.8, 16, 15.4, 14.9, 14.4, 14, 13.8, 13.7, 13.5, 13.5, 13.5, 13.7, 13.7, 13.7, 13.7, - 14, 14.2, 14.6, 14.8, 14.8, 14.7, 14.4, 14.2, 14.3, 14.3, 14.6, 14.9, 15.1, 15.1, 15, - 14.9, 14.9, 14.8, 14.5, 14.2, 13.8, 13.3, 13, 12.7, 12.5, 12.3, 12.1, 11.8, 11.7, - 11.6, 11.5, 11.5, 11.5, 11.5, 11.6, 11.6, 11.6, 11.7, 11.9, 12.2, 12.4, 12.4, 12.5, - 12.6, 12.5, 12.3, 12.1, 11.9, 11.8, 11.6, 11.5, 11.4, 11.3, 11, 10.7, 10.5, 10.1, 9.8, - 9.6, 9.6, 9.4, 9.3, 9.3, 9.3, 9.4, 9.6, 9.8, 10, 10.2, 10.3, 10.4, 10.5, 10.5, 10.7, - 10.8, 10.9, 11.1, 11, 10.7, 10.6, 10.5, 10.5, 10.6, 10.6, 10.5, 10.4, 10.3, 10.2, 10, - 9.9, 9.8, 9.8, 9.7, 9.6, 9.5, 9.4, 9.5, 9.5, 9.4, 9.1, 8.8, 8.4, 7.9, 7.3, 6.5, 6.6, - 6.9, 7.2, 7.5, 7.6, 7.6, 7.5, 7.4, 7.3, 7.1, 7, 7, 6.8, 6.7, 6.5, 6.3, 6.1, 5.8, 5.5, - 5.3, 5.1, 5.1, 5, 5, 4.9, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4.9, 4.9, 4.8, 4.8, 4.8, 4.8, - 4.8, 4.7, 4.6, 4.5, 4.5, 4.6, 4.6, 4.6, 4.7, 4.8, 4.8, 5, 5.2, 5.6, 6.1, 6.6, 7, 7.3, - 7.5, 7.6, 7.6, 7.5, 7.4, 7.3, 7.3, 7.2, 7.2, 7.1, 7.1, 7.1, 7.1, 7, 6.9, 6.9, 6.8, - 6.7, 6.6, 6.5, 6.5, 6.3, 6.1, 5.9, 5.7, 5.4, 5.2, 4.9, 4.5, 4.1, 3.6, 3.1, 2.6, 2.3, - 2, 1.7, 1.5, 1.4, 1.2, 0.8, 0.1, -0.4, -1.1, -1.6, -2, -2.1, -2, -2, -1.9, -1.9, -1.7, - -1.7, -1.8, 17.1, 17, 16.8, 16.6, 16.6, 16.6, 16.2, 16, 15.7, 15.4, 15.2, 15.1, 14.9, - 14.9, 15.2, 15.7, 16.1, 16.8, 17.1, 17.5, 18.1, 18.4, 18.3, 18.2, 18.2, 17.9, 16.9, - 16.1, 15.4, 14.9, 14.5, 14.2, 13.9, 13.7, 13.6, 13.6, 13.6, 13.7, 13.7, 13.7, 13.8, - 13.9, 14.3, 14.8, 15.1, 15.2, 15.1, 14.7, 14.4, 14.4, 14.4, 14.6, 15, 15.1, 15.2, - 15.3, 15.2, 14.9, 14.5, 14.2, 13.9, 13.5, 13.2, 13, 12.8, 12.6, 12.4, 12.1, 12, 11.8, - 11.7, 11.6, 11.6, 11.5, 11.5, 11.6, 11.7, 11.7, 11.8, 11.9, 12.1, 12.4, 12.6, 12.9, - 13, 12.8, 12.4, 12.1, 11.9, 11.7, 11.6, 11.5, 11.4, 11.2, 10.9, 10.7, 10.5, 10.2, 9.9, - 9.7, 9.6, 9.5, 9.4, 9.3, 9.3, 9.4, 9.5, 9.7, 9.9, 9.9, 10, 10.1, 10.1, 10.1, 10.2, - 10.4, 10.7, 10.7, 10.6, 10.2, 10.1, 10, 10, 10, 10, 10, 9.9, 9.8, 9.7, 9.6, 9.5, 9.5, - 9.5, 9.4, 9.3, 9.2, 9.2, 9.1, 9.1, 9, 8.9, 8.6, 8.2, 7.8, 7.2, 6.7, 6.9, 7.3, 7.6, - 7.9, 8, 8, 7.8, 7.7, 7.7, 7.6, 7.3, 7.1, 7, 6.8, 6.6, 6.4, 6.1, 5.8, 5.5, 5.4, 5.2, 5, - 5, 4.9, 4.9, 5, 5, 5, 5, 5, 5, 5.1, 5.1, 5, 5, 4.9, 4.9, 4.8, 4.7, 4.7, 4.7, 4.7, 4.6, - 4.5, 4.5, 4.7, 4.6, 4.5, 4.6, 4.7, 4.8, 4.9, 5.1, 5.4, 5.9, 6.4, 6.9, 7.2, 7.4, 7.6, - 7.5, 7.4, 7.4, 7.3, 7.3, 7.2, 7.2, 7.2, 7.2, 7.2, 7.1, 7.1, 7.1, 7, 6.9, 6.8, 6.7, - 6.7, 6.6, 6.4, 6.2, 6, 5.9, 5.7, 5.4, 5.2, 4.8, 4.4, 4, 3.6, 3.1, 2.7, 2.5, 2.2, 2, - 1.8, 1.6, 1.4, 0.9, 0.3, -0.6, -1.2, -1.6, -1.8, -1.8, -1.8, -1.8, -1.7, -1.6, -1.6, - -1.7, 17.2, 17, 16.9, 16.7, 16.7, 16.7, 16.4, 16.2, 15.9, 15.7, 15.3, 15.1, 15, 15.1, - 15.4, 16, 16.5, 17, 17.4, 17.7, 18.2, 18.4, 18.3, 18.3, 18.2, 17.9, 17, 16.3, 15.5, - 15, 14.6, 14.3, 14.1, 13.8, 13.6, 13.6, 13.6, 13.7, 13.7, 13.7, 13.8, 13.9, 14.3, - 14.9, 15.3, 15.5, 15.4, 15.1, 14.8, 14.6, 14.5, 14.7, 14.9, 15.1, 15.2, 15.2, 15.2, - 14.7, 14.3, 14, 13.7, 13.3, 13.1, 13, 12.9, 12.6, 12.4, 12.3, 12.2, 12, 11.8, 11.8, - 11.7, 11.7, 11.6, 11.7, 11.8, 11.7, 11.8, 11.9, 12, 12.2, 12.7, 13.1, 13.2, 12.9, - 12.4, 12.1, 11.9, 11.8, 11.6, 11.5, 11.4, 11.2, 11, 10.8, 10.6, 10.4, 10.1, 9.8, 9.6, - 9.7, 9.5, 9.4, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.8, 9.8, 9.8, 9.9, 10, 10.1, 10, - 9.8, 9.7, 9.6, 9.5, 9.5, 9.5, 9.5, 9.5, 9.4, 9.5, 9.4, 9.2, 9.2, 9.1, 9.1, 9.1, 9.1, - 9, 8.9, 8.8, 8.8, 8.8, 8.7, 8.5, 8, 7.5, 6.9, 6.9, 7.4, 7.7, 8, 8.4, 8.4, 8.3, 8.2, - 8.2, 8.1, 7.9, 7.6, 7.3, 7.1, 6.9, 6.7, 6.4, 6.1, 5.8, 5.6, 5.4, 5.2, 5.1, 5, 5, 4.9, - 4.9, 5, 5, 5.1, 5.1, 5.1, 5.1, 5.1, 5, 5, 4.9, 4.9, 4.9, 4.8, 4.8, 4.7, 4.6, 4.5, 4.5, - 4.7, 4.7, 4.6, 4.5, 4.5, 4.7, 4.8, 4.8, 5, 5.3, 5.7, 6.2, 6.7, 7.1, 7.3, 7.5, 7.5, - 7.4, 7.4, 7.3, 7.3, 7.2, 7.2, 7.3, 7.2, 7.2, 7.2, 7.2, 7.1, 7.1, 7, 6.9, 6.9, 6.8, - 6.6, 6.5, 6.3, 6.2, 6, 5.8, 5.6, 5.4, 5.1, 4.8, 4.4, 4, 3.6, 3.3, 3, 2.7, 2.5, 2.3, - 2.1, 1.7, 1.4, 0.8, 0.1, -0.5, -1.2, -1.4, -1.4, -1.5, -1.6, -1.5, -1.4, -1.5, -1.7, - 17.2, 16.9, 16.7, 16.6, 16.6, 16.6, 16.4, 16.2, 16, 15.8, 15.5, 15.2, 15.1, 15.2, - 15.7, 16.4, 16.9, 17.2, 17.5, 17.7, 18, 18.3, 18.4, 18.3, 18.1, 17.6, 17, 16.4, 15.5, - 15.1, 14.8, 14.5, 14.2, 13.9, 13.7, 13.6, 13.6, 13.6, 13.6, 13.7, 13.8, 14, 14.3, - 14.8, 15.3, 15.6, 15.7, 15.5, 15.3, 15, 14.7, 14.7, 14.8, 14.9, 15, 15, 14.9, 14.6, - 14.2, 13.8, 13.5, 13.3, 13.1, 13, 12.8, 12.6, 12.4, 12.3, 12.3, 12.1, 12, 12, 11.9, - 11.9, 11.7, 11.7, 11.8, 11.8, 11.8, 12, 12.1, 12.4, 12.8, 13.1, 13.2, 12.8, 12.4, - 12.1, 11.9, 11.8, 11.6, 11.5, 11.4, 11.3, 11.1, 11, 10.8, 10.6, 10.2, 9.9, 9.9, 9.8, - 9.7, 9.4, 9.3, 9.2, 9.3, 9.4, 9.5, 9.6, 9.6, 9.6, 9.6, 9.5, 9.5, 9.6, 9.6, 9.5, 9.4, - 9.3, 9.3, 9.2, 9.1, 9.1, 9.1, 9.1, 9, 9.1, 8.9, 8.9, 8.9, 8.8, 8.7, 8.7, 8.7, 8.7, - 8.6, 8.6, 8.6, 8.6, 8.5, 8.3, 7.9, 7.4, 6.9, 7.2, 7.8, 8.1, 8.4, 8.6, 8.7, 8.6, 8.6, - 8.5, 8.4, 8.1, 7.8, 7.6, 7.2, 7, 6.7, 6.4, 6.1, 5.8, 5.6, 5.4, 5.3, 5.2, 5.1, 5, 5, 5, - 5, 5, 5.1, 5.1, 5.1, 5.1, 5.1, 5.1, 5, 4.9, 4.9, 4.9, 4.8, 4.7, 4.6, 4.6, 4.5, 4.6, - 4.7, 4.6, 4.6, 4.5, 4.4, 4.5, 4.6, 4.7, 4.7, 5, 5.4, 6, 6.5, 6.9, 7.2, 7.4, 7.4, 7.5, - 7.4, 7.3, 7.3, 7.3, 7.3, 7.3, 7.3, 7.2, 7.2, 7.2, 7.2, 7.2, 7.1, 7.1, 7, 6.8, 6.7, - 6.6, 6.5, 6.4, 6.2, 6, 5.8, 5.6, 5.4, 5.1, 4.8, 4.5, 4.2, 3.9, 3.6, 3.3, 3.1, 2.9, - 2.5, 2.2, 1.8, 1.2, 0.7, 0.2, -0.6, -1, -1.1, -1.2, -1.3, -1.3, -1.4, -1.5, -1.6, 17, - 16.6, 16.4, 16.3, 16.3, 16.4, 16.3, 16.1, 16, 15.8, 15.5, 15.2, 15.1, 15.3, 15.8, - 16.5, 17.1, 17.3, 17.6, 17.8, 18.1, 18.3, 18.4, 18.4, 18.1, 17.7, 17.1, 16.4, 15.7, - 15.1, 14.9, 14.5, 14.2, 13.8, 13.7, 13.5, 13.6, 13.6, 13.6, 13.7, 13.8, 14, 14.3, - 14.7, 15.2, 15.6, 15.8, 15.9, 15.7, 15.6, 15.2, 15, 14.9, 14.8, 14.8, 14.7, 14.5, - 14.3, 14, 13.7, 13.5, 13.3, 13.2, 13, 12.8, 12.6, 12.5, 12.4, 12.3, 12.2, 12.1, 12, - 12, 11.9, 11.8, 11.8, 11.9, 11.9, 11.9, 12.1, 12.3, 12.6, 12.9, 13.2, 13.2, 12.7, - 12.4, 12.1, 12, 11.8, 11.6, 11.5, 11.4, 11.3, 11.2, 11.1, 10.9, 10.7, 10.4, 10.2, - 10.1, 9.9, 9.7, 9.5, 9.4, 9.3, 9.2, 9.2, 9.3, 9.4, 9.4, 9.4, 9.3, 9.2, 9.2, 9.2, 9.4, - 9.3, 9.2, 9.2, 9.1, 9, 8.8, 8.8, 8.8, 8.8, 8.8, 8.8, 8.7, 8.7, 8.7, 8.7, 8.5, 8.5, - 8.4, 8.4, 8.4, 8.4, 8.4, 8.3, 8.2, 8.1, 7.9, 7.5, 7.3, 7.6, 8, 8.2, 8.6, 8.8, 8.8, - 8.8, 8.8, 8.7, 8.6, 8.4, 8, 7.7, 7.4, 7.1, 6.7, 6.5, 6.1, 5.9, 5.6, 5.4, 5.3, 5.1, - 5.1, 5.1, 5, 5.1, 5.1, 5.1, 5.2, 5.2, 5.1, 5.1, 5.1, 5.1, 5, 4.8, 4.7, 4.7, 4.6, 4.6, - 4.6, 4.6, 4.6, 4.7, 4.7, 4.6, 4.6, 4.6, 4.4, 4.3, 4.5, 4.5, 4.5, 4.8, 5.1, 5.6, 6.1, - 6.7, 6.9, 7.1, 7.3, 7.4, 7.3, 7.3, 7.3, 7.3, 7.3, 7.3, 7.3, 7.2, 7.2, 7.2, 7.3, 7.3, - 7.1, 7.2, 7.2, 6.9, 6.8, 6.7, 6.6, 6.6, 6.3, 6.1, 5.9, 5.8, 5.5, 5.3, 5.1, 4.8, 4.5, - 4.3, 4.1, 3.8, 3.5, 3.3, 3, 2.7, 2.4, 1.6, 1.1, 0.5, -0.1, -0.6, -0.9, -1, -1.1, -1.2, - -1.3, -1.4, -1.6, 16.6, 16.3, 16, 16, 16, 16.1, 16.1, 15.9, 15.8, 15.6, 15.3, 15, 15, - 15.3, 15.8, 16.5, 17, 17.3, 17.6, 17.9, 18.2, 18.4, 18.4, 18.4, 18.1, 17.7, 17.1, - 16.5, 15.8, 15.1, 14.8, 14.5, 14.2, 13.8, 13.6, 13.5, 13.6, 13.6, 13.7, 13.7, 13.9, - 14, 14.2, 14.6, 15, 15.4, 15.7, 16, 16, 16, 15.7, 15.3, 15.1, 14.9, 14.7, 14.5, 14.4, - 14.2, 14, 13.8, 13.5, 13.3, 13.1, 13, 12.9, 12.8, 12.7, 12.6, 12.5, 12.3, 12.2, 12.1, - 12, 11.9, 11.7, 11.8, 11.9, 11.9, 11.9, 12.2, 12.5, 12.7, 13.2, 13.4, 13.2, 12.7, - 12.2, 12, 11.9, 11.7, 11.6, 11.5, 11.5, 11.4, 11.2, 11.1, 11, 10.8, 10.6, 10.5, 10.4, - 10.1, 9.9, 9.7, 9.5, 9.5, 9.4, 9.2, 9.3, 9.3, 9.2, 9.2, 9.2, 9.1, 9.1, 9.1, 9.1, 9.1, - 9, 9, 8.9, 8.8, 8.7, 8.6, 8.6, 8.6, 8.8, 8.8, 8.6, 8.5, 8.6, 8.7, 8.5, 8.4, 8.3, 8.2, - 8.2, 8.1, 8, 8, 8, 7.9, 7.5, 7.3, 7.4, 7.8, 8.2, 8.5, 8.8, 8.9, 9, 9, 9, 8.9, 8.8, - 8.5, 8.2, 7.9, 7.5, 7.2, 6.8, 6.6, 6.2, 5.9, 5.6, 5.6, 5.4, 5.3, 5.2, 5.1, 5.1, 5.1, - 5.1, 5.2, 5.2, 5.2, 5.2, 5.2, 5.1, 5, 4.9, 4.8, 4.8, 4.8, 4.7, 4.6, 4.7, 4.7, 4.7, - 4.7, 4.7, 4.7, 4.6, 4.7, 4.4, 4.2, 4.2, 4.2, 4.3, 4.6, 4.8, 5.2, 5.6, 6.1, 6.5, 6.9, - 7.1, 7.2, 7.3, 7.3, 7.4, 7.3, 7.3, 7.3, 7.2, 7.2, 7.2, 7.3, 7.4, 7.3, 7.2, 7.2, 7.2, - 7, 6.9, 6.8, 6.8, 6.7, 6.4, 6.3, 6.1, 6, 5.8, 5.6, 5.4, 5.1, 4.9, 4.7, 4.5, 4.1, 3.9, - 3.5, 3.2, 2.9, 2.5, 2, 1.6, 0.9, 0.4, -0.1, -0.5, -0.8, -0.9, -1, -1.2, -1.4, -1.5, - 16.2, 15.9, 15.8, 15.6, 15.5, 15.5, 15.4, 15.5, 15.5, 15.3, 15.1, 14.9, 15, 15.2, - 15.8, 16.4, 16.9, 17.3, 17.7, 18, 18.2, 18.4, 18.5, 18.4, 18.1, 17.6, 17.1, 16.4, - 15.7, 15.2, 14.7, 14.5, 14.2, 13.8, 13.7, 13.6, 13.7, 13.7, 13.8, 13.9, 14, 14.1, - 14.1, 14.3, 14.7, 15.2, 15.6, 16, 16.2, 16.2, 16, 15.7, 15.4, 15.1, 14.8, 14.6, 14.4, - 14.2, 14, 13.7, 13.4, 13.2, 13.1, 13, 12.9, 12.8, 12.7, 12.6, 12.5, 12.4, 12.3, 12.1, - 12, 11.8, 11.7, 11.9, 12, 11.9, 12, 12.3, 12.6, 12.9, 13.3, 13.5, 13.2, 12.6, 12.2, - 12, 11.8, 11.7, 11.6, 11.5, 11.5, 11.4, 11.2, 11.1, 11, 10.9, 10.8, 10.7, 10.5, 10.3, - 10.1, 9.8, 9.6, 9.6, 9.5, 9.4, 9.3, 9.2, 9.1, 9.1, 9.1, 9.1, 9.1, 9.1, 9.1, 9, 9.1, 9, - 8.8, 8.9, 8.9, 8.8, 8.6, 8.6, 8.7, 8.7, 8.6, 8.4, 8.5, 8.5, 8.5, 8.5, 8.4, 8.2, 8, - 7.8, 7.6, 7.6, 7.7, 7.6, 7.4, 7.5, 7.8, 8.1, 8.4, 8.8, 9, 9, 9.1, 9.1, 9.1, 9.1, 8.9, - 8.7, 8.3, 8, 7.7, 7.4, 7, 6.7, 6.3, 5.8, 5.7, 5.7, 5.6, 5.4, 5.3, 5.1, 5.1, 5.1, 5.2, - 5.2, 5.2, 5.2, 5.2, 5.1, 5.1, 5, 4.9, 4.8, 4.9, 4.9, 4.8, 4.7, 4.7, 4.8, 4.7, 4.7, - 4.7, 4.7, 4.7, 4.6, 4.4, 4.1, 4.1, 4.1, 4.2, 4.4, 4.6, 5, 5.3, 5.8, 6.2, 6.6, 6.9, - 7.1, 7.2, 7.3, 7.4, 7.3, 7.3, 7.3, 7.2, 7.2, 7.2, 7.2, 7.3, 7.2, 7.2, 7.2, 7.2, 7.1, - 6.9, 6.8, 6.8, 6.7, 6.6, 6.5, 6.4, 6.3, 6.1, 5.9, 5.7, 5.4, 5.1, 4.8, 4.6, 4.3, 4, - 3.7, 3.3, 2.9, 2.5, 2.1, 1.8, 1.3, 0.7, 0.2, -0.2, -0.5, -0.8, -0.9, -1.1, -1.3, -1.4, - 15.6, 15.4, 15.4, 15.2, 15, 14.8, 14.9, 15.1, 15.1, 15, 14.9, 14.8, 14.9, 15.3, 15.8, - 16.4, 16.9, 17.3, 17.7, 18, 18.3, 18.4, 18.5, 18.4, 18.2, 17.7, 17.1, 16.4, 15.8, - 15.1, 14.7, 14.4, 14.2, 13.8, 13.7, 13.8, 13.8, 13.8, 13.9, 14, 14, 14.1, 14.2, 14.3, - 14.5, 15, 15.5, 16, 16.2, 16.3, 16.3, 16, 15.8, 15.5, 15.3, 15, 14.7, 14.5, 14.2, - 13.8, 13.4, 13.2, 13.2, 13, 12.9, 12.7, 12.6, 12.5, 12.4, 12.3, 12.1, 12, 11.9, 11.9, - 11.8, 11.9, 12, 12, 12.1, 12.4, 12.7, 13.1, 13.4, 13.3, 13.1, 12.6, 12.2, 12, 11.8, - 11.7, 11.6, 11.5, 11.5, 11.4, 11.3, 11.2, 11.1, 11, 10.9, 10.8, 10.6, 10.4, 10.2, 9.9, - 9.7, 9.6, 9.4, 9.4, 9.4, 9.2, 9.2, 9.2, 9.3, 9.3, 9.2, 9.1, 8.9, 8.9, 8.9, 9, 8.9, - 9.1, 9.1, 8.8, 8.4, 8.6, 8.7, 8.5, 8.3, 8.3, 8.4, 8.4, 8.5, 8.6, 8.4, 8.2, 7.9, 7.8, - 7.8, 7.6, 7.5, 7.5, 7.6, 7.9, 8.2, 8.5, 8.8, 9, 9.1, 9.1, 9.1, 9.2, 9.2, 9.1, 9, 8.8, - 8.5, 8.2, 8, 7.6, 7.2, 6.9, 6.4, 5.9, 5.6, 5.6, 5.5, 5.4, 5.3, 5.2, 5.2, 5.2, 5.2, - 5.2, 5.2, 5.2, 5.1, 5, 5, 5, 5, 5, 5, 5, 4.8, 4.7, 4.8, 4.8, 4.8, 4.7, 4.7, 4.7, 4.7, - 4.7, 4.5, 4.2, 4.1, 4, 4.1, 4.2, 4.5, 4.9, 5.1, 5.4, 5.9, 6.3, 6.6, 6.9, 7, 7.2, 7.2, - 7.3, 7.3, 7.3, 7.2, 7.2, 7.2, 7.2, 7.2, 7.2, 7.2, 7.2, 7.1, 7.1, 7, 6.9, 6.8, 6.8, - 6.7, 6.7, 6.7, 6.5, 6.4, 6.1, 5.8, 5.5, 5.3, 5, 4.7, 4.4, 4.2, 3.8, 3.4, 2.9, 2.5, - 2.2, 1.9, 1.5, 0.9, 0.4, 0, -0.3, -0.6, -0.9, -1, -1.1, -1.2, 14.8, 14.8, 14.8, 14.7, - 14.5, 14.5, 14.6, 14.7, 14.7, 14.7, 14.7, 14.8, 14.9, 15.3, 15.9, 16.4, 16.9, 17.4, - 17.7, 18, 18.3, 18.4, 18.5, 18.4, 18.2, 17.7, 17.1, 16.3, 15.6, 15, 14.6, 14.3, 14.1, - 13.9, 13.8, 13.8, 13.8, 13.8, 13.8, 13.9, 13.9, 14.1, 14.2, 14.2, 14.4, 14.7, 15.3, - 15.8, 16.2, 16.3, 16.4, 16.2, 16, 15.8, 15.6, 15.2, 14.9, 14.8, 14.6, 13.9, 13.4, - 13.1, 13.1, 12.9, 12.8, 12.7, 12.6, 12.6, 12.4, 12.2, 12.1, 12.1, 12.1, 12, 11.9, - 11.9, 11.9, 12, 12.2, 12.4, 12.8, 13.3, 13.6, 13.3, 13.1, 12.7, 12.3, 12, 11.9, 11.7, - 11.6, 11.6, 11.6, 11.6, 11.4, 11.3, 11.2, 11.1, 11, 10.9, 10.7, 10.4, 10.3, 9.9, 9.7, - 9.6, 9.5, 9.4, 9.4, 9.3, 9.2, 9.2, 9.3, 9.2, 9, 9, 9.2, 9.2, 9.1, 9, 9, 9.1, 9.1, 8.9, - 8.4, 8.4, 8.4, 8.4, 8.5, 8.5, 8.4, 8.4, 8.4, 8.4, 8.3, 8.1, 7.8, 8, 7.9, 7.7, 7.8, 8, - 8.3, 8.6, 8.7, 8.9, 9.1, 9.2, 9.2, 9.1, 9.2, 9.3, 9.3, 9.2, 9, 8.8, 8.6, 8.3, 8.1, - 7.8, 7.4, 7, 6.5, 6, 5.6, 5.5, 5.5, 5.4, 5.3, 5.2, 5.3, 5.3, 5.3, 5.3, 5.3, 5.2, 5.1, - 5, 5.1, 5.1, 5.1, 5.1, 5.1, 5, 4.9, 4.8, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 4.9, 4.8, 4.7, - 4.4, 4.1, 4, 4, 4.1, 4.4, 4.7, 4.9, 5.2, 5.6, 6, 6.3, 6.7, 6.9, 7.1, 7.3, 7.3, 7.4, - 7.3, 7.3, 7.3, 7.2, 7.2, 7.2, 7.2, 7.2, 7.1, 7.1, 7.1, 7.1, 7, 6.9, 6.9, 6.8, 6.8, - 6.8, 6.6, 6.5, 6.3, 6.1, 5.8, 5.5, 5.2, 4.9, 4.6, 4.3, 3.9, 3.5, 3, 2.6, 2.2, 1.9, - 1.6, 1.1, 0.7, 0.2, -0.2, -0.5, -0.8, -0.9, -1, -1.3, 14.3, 14.3, 14.3, 14.3, 14.3, - 14.3, 14.4, 14.4, 14.5, 14.5, 14.6, 14.7, 14.8, 15.1, 15.8, 16.3, 16.9, 17.3, 17.7, - 17.9, 18.2, 18.4, 18.5, 18.4, 18.2, 17.8, 17.1, 16.3, 15.5, 14.9, 14.5, 14.2, 14.1, - 13.9, 13.8, 13.7, 13.7, 13.8, 13.8, 13.8, 13.9, 14, 14.2, 14.3, 14.3, 14.6, 15.2, - 15.7, 16, 16.2, 16.4, 16.3, 16.1, 16, 15.8, 15.4, 15.1, 15, 14.6, 14, 13.4, 13.1, 13, - 12.9, 12.8, 12.7, 12.6, 12.6, 12.5, 12.3, 12.2, 12.1, 12.1, 12, 12, 11.9, 12, 12.1, - 12.2, 12.4, 12.7, 13.4, 13.7, 13.4, 13.2, 12.8, 12.3, 12, 11.9, 11.8, 11.6, 11.6, - 11.7, 11.7, 11.5, 11.3, 11.2, 11.1, 11, 10.9, 10.6, 10.4, 10.2, 10, 9.8, 9.7, 9.6, - 9.4, 9.3, 9.3, 9.4, 9.4, 9.1, 9.1, 9.2, 9.3, 9.2, 9.2, 9.1, 9.1, 9.1, 9.1, 9.2, 9, - 8.7, 8.6, 8.7, 8.8, 8.8, 8.7, 8.7, 8.7, 8.5, 8.2, 8, 8, 8.1, 8.3, 8.3, 8.1, 8.2, 8.5, - 8.9, 9.3, 9.3, 9.4, 9.4, 9.4, 9.3, 9.3, 9.3, 9.4, 9.4, 9.3, 9.1, 8.8, 8.6, 8.4, 8.2, - 7.9, 7.6, 7.2, 6.7, 6.4, 5.9, 5.7, 5.6, 5.5, 5.4, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.2, - 5.1, 5.1, 5.2, 5.1, 5.2, 5.2, 5.1, 5, 5, 4.9, 4.9, 4.9, 5, 5.1, 5.2, 5.1, 5, 5, 4.9, - 4.6, 4.2, 4, 4, 4.1, 4.1, 4.5, 4.8, 5, 5.3, 5.7, 6.1, 6.5, 6.8, 7, 7.2, 7.3, 7.3, 7.3, - 7.3, 7.3, 7.3, 7.2, 7.2, 7.2, 7.2, 7.1, 7.2, 7.1, 7.1, 7, 6.9, 6.9, 6.9, 6.8, 6.7, - 6.7, 6.6, 6.5, 6.2, 5.9, 5.8, 5.5, 5.1, 4.8, 4.4, 4, 3.6, 3.2, 2.7, 2.3, 2, 1.7, 1.3, - 0.9, 0.4, -0.1, -0.4, -0.6, -0.8, -1.2, -1.3, 14.1, 14.2, 14.2, 14.2, 14.2, 14.3, - 14.3, 14.4, 14.4, 14.5, 14.5, 14.6, 14.7, 15, 15.6, 16.2, 16.7, 17.2, 17.6, 17.8, - 18.1, 18.4, 18.4, 18.4, 18.2, 17.7, 17.1, 16.3, 15.7, 14.9, 14.4, 14.2, 14, 13.9, - 13.8, 13.7, 13.7, 13.7, 13.8, 13.8, 13.9, 14, 14.1, 14.3, 14.3, 14.6, 15.1, 15.5, - 15.8, 16, 16.2, 16.2, 16.1, 16, 15.7, 15.3, 15.1, 14.9, 14.5, 13.9, 13.4, 13.1, 13, - 12.8, 12.8, 12.8, 12.7, 12.6, 12.5, 12.3, 12.2, 12.1, 12.1, 12, 12, 12, 12, 12.1, - 12.2, 12.3, 12.7, 13.3, 13.6, 13.4, 13.1, 12.7, 12.4, 12.1, 11.9, 11.8, 11.7, 11.6, - 11.6, 11.6, 11.5, 11.4, 11.3, 11.1, 11, 10.8, 10.7, 10.5, 10.3, 10.1, 9.9, 9.7, 9.6, - 9.4, 9.3, 9.3, 9.4, 9.3, 9, 9.3, 9.5, 9.3, 9.2, 9.2, 9.1, 9.1, 9.1, 9.1, 9.2, 9.1, 9, - 9, 9, 9.1, 9, 8.9, 9, 9.2, 9, 8.6, 8.4, 8.3, 8.3, 8.5, 8.8, 8.8, 8.8, 9.1, 9.4, 9.6, - 9.7, 9.7, 9.7, 9.7, 9.5, 9.5, 9.5, 9.4, 9.4, 9.3, 9.1, 8.9, 8.7, 8.5, 8.3, 8.1, 7.8, - 7.3, 6.9, 6.5, 6.1, 5.8, 5.7, 5.5, 5.4, 5.2, 5.3, 5.4, 5.4, 5.4, 5.3, 5.2, 5.2, 5.2, - 5.2, 5.2, 5.2, 5.2, 5.2, 5.1, 5, 4.9, 4.9, 5, 5.1, 5.3, 5.4, 5.3, 5.3, 5.2, 5, 4.8, - 4.3, 4, 4, 4, 4.1, 4.4, 4.7, 5, 5.2, 5.5, 5.9, 6.3, 6.6, 6.9, 7.1, 7.3, 7.3, 7.3, 7.3, - 7.3, 7.2, 7.2, 7.2, 7.2, 7.2, 7.2, 7.3, 7.2, 7.1, 7.1, 7, 7, 6.9, 6.8, 6.7, 6.8, 6.7, - 6.6, 6.4, 6.1, 5.9, 5.7, 5.2, 4.9, 4.5, 4.2, 3.8, 3.3, 2.9, 2.5, 2.2, 2, 1.6, 1, 0.5, - 0.1, -0.2, -0.5, -0.8, -1, -0.9, 14.2, 14.3, 14.3, 14.3, 14.2, 14.2, 14.2, 14.2, 14.3, - 14.3, 14.3, 14.5, 14.6, 14.9, 15.4, 15.9, 16.4, 17, 17.4, 17.7, 18, 18.3, 18.4, 18.4, - 18.2, 17.8, 17.3, 16.5, 15.8, 15, 14.4, 14.1, 14, 13.9, 13.8, 13.6, 13.6, 13.6, 13.7, - 13.7, 13.8, 13.9, 14, 14.2, 14.4, 14.5, 14.9, 15.3, 15.6, 15.9, 16, 16.1, 16.1, 15.8, - 15.4, 15, 14.9, 14.7, 14.4, 13.8, 13.4, 13.2, 13, 12.9, 12.8, 12.7, 12.7, 12.6, 12.4, - 12.3, 12.2, 12.2, 12.2, 12.2, 12.1, 12, 12, 12.1, 12.1, 12.3, 12.7, 13.2, 13.5, 13.5, - 13.1, 12.7, 12.4, 12.1, 11.9, 11.8, 11.7, 11.7, 11.6, 11.6, 11.6, 11.5, 11.4, 11.2, - 11, 10.9, 10.7, 10.5, 10.3, 10.1, 9.9, 9.8, 9.6, 9.4, 9.2, 9.2, 9.2, 9.2, 9.3, 9.5, - 9.6, 9.4, 9.3, 9.3, 9.2, 9.1, 9.1, 9.1, 9.3, 9.3, 9.3, 9.2, 9.2, 9.1, 9, 9.1, 9.3, - 9.4, 9, 8.9, 8.7, 8.6, 8.7, 8.9, 9.2, 9.3, 9.3, 9.5, 9.8, 9.9, 10, 10.1, 10.1, 10, - 9.8, 9.7, 9.6, 9.5, 9.4, 9.3, 9.2, 9, 8.9, 8.7, 8.5, 8.2, 7.8, 7.4, 7, 6.5, 6.1, 5.9, - 5.7, 5.5, 5.4, 5.3, 5.3, 5.4, 5.4, 5.4, 5.3, 5.2, 5.2, 5.3, 5.2, 5.2, 5.3, 5.2, 5.2, - 5.2, 5.1, 5, 5.1, 5.1, 5.3, 5.5, 5.5, 5.6, 5.5, 5.3, 5.2, 4.9, 4.4, 4.1, 4, 4, 4, 4.3, - 4.6, 4.9, 5.1, 5.3, 5.8, 6.1, 6.4, 6.8, 7, 7.2, 7.4, 7.3, 7.3, 7.2, 7.2, 7.2, 7.2, - 7.2, 7.2, 7.2, 7.3, 7.2, 7.2, 7.2, 7.1, 7.1, 7, 6.9, 6.8, 6.8, 6.7, 6.6, 6.5, 6.2, 6, - 5.7, 5.4, 5, 4.7, 4.4, 4.1, 3.6, 3.2, 2.8, 2.4, 2.1, 1.7, 1.1, 0.6, 0.2, -0.1, -0.4, - -0.7, -0.8, -0.7, 14.3, 14.3, 14.3, 14.2, 14.1, 14, 13.9, 13.9, 13.9, 13.9, 14, 14.3, - 14.5, 14.6, 15, 15.5, 16.1, 16.7, 17.1, 17.5, 17.8, 18.1, 18.3, 18.3, 18.2, 17.9, - 17.4, 16.6, 15.8, 15, 14.5, 14.2, 14, 13.9, 13.7, 13.6, 13.6, 13.6, 13.6, 13.6, 13.7, - 13.8, 13.9, 14.2, 14.4, 14.6, 14.8, 15.2, 15.5, 15.8, 16, 16, 15.9, 15.6, 15.2, 14.8, - 14.6, 14.3, 14, 13.6, 13.4, 13.3, 13, 12.8, 12.8, 12.7, 12.5, 12.5, 12.5, 12.4, 12.4, - 12.4, 12.3, 12.2, 12.1, 12, 12, 12, 12, 12.3, 12.6, 13.1, 13.4, 13.4, 13.1, 12.7, - 12.3, 12.1, 12, 11.8, 11.7, 11.7, 11.7, 11.5, 11.5, 11.5, 11.4, 11.3, 11.1, 10.9, - 10.7, 10.5, 10.4, 10.1, 9.9, 9.7, 9.6, 9.3, 9.3, 9.3, 9.3, 9.4, 9.4, 9.4, 9.4, 9.4, - 9.3, 9.3, 9.3, 9.2, 9.2, 9.2, 9.2, 9.4, 9.5, 9.3, 9.3, 9.3, 9.3, 9.5, 9.7, 9.6, 9.2, - 9, 9, 9, 9, 9.1, 9.4, 9.6, 9.8, 10.1, 10.2, 10.4, 10.5, 10.5, 10.5, 10.3, 10, 9.9, - 9.8, 9.7, 9.6, 9.5, 9.4, 9.3, 9.2, 9, 8.6, 8.2, 7.8, 7.4, 7.1, 6.7, 6.2, 5.9, 5.7, - 5.5, 5.4, 5.3, 5.4, 5.4, 5.4, 5.4, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.2, - 5.1, 5, 5.1, 5.2, 5.4, 5.5, 5.7, 5.8, 5.7, 5.5, 5.3, 5, 4.5, 4.1, 4, 4, 4, 4.2, 4.6, - 4.9, 5.1, 5.2, 5.6, 6, 6.3, 6.7, 7, 7.2, 7.3, 7.3, 7.3, 7.3, 7.3, 7.3, 7.3, 7.2, 7.3, - 7.2, 7.3, 7.4, 7.5, 7.3, 7.2, 7.1, 7.1, 7, 6.9, 6.8, 6.7, 6.6, 6.5, 6.2, 6.1, 5.9, - 5.6, 5.2, 4.9, 4.7, 4.3, 3.9, 3.6, 3.2, 2.7, 2.2, 1.7, 1.2, 0.8, 0.4, 0, -0.3, -0.5, - -0.6, -0.7, 14.3, 14.3, 14.2, 14.1, 13.9, 13.7, 13.6, 13.6, 13.6, 13.6, 13.9, 14.1, - 14.3, 14.5, 14.7, 15.2, 15.7, 16.3, 16.8, 17.2, 17.5, 17.8, 18, 18.1, 18, 17.7, 17.3, - 16.7, 15.9, 15.1, 14.6, 14.2, 14, 13.9, 13.7, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, - 13.7, 13.9, 14.1, 14.4, 14.6, 14.8, 15, 15.4, 15.7, 15.9, 15.9, 15.7, 15.4, 15, 14.7, - 14.4, 14, 13.8, 13.5, 13.3, 13, 12.8, 12.7, 12.7, 12.7, 12.6, 12.6, 12.5, 12.5, 12.5, - 12.4, 12.3, 12.2, 12, 11.9, 11.9, 11.9, 12, 12.1, 12.4, 12.8, 13.1, 13.2, 13.2, 12.7, - 12.4, 12.1, 12, 11.8, 11.7, 11.6, 11.6, 11.6, 11.5, 11.5, 11.4, 11.3, 11, 10.9, 10.8, - 10.7, 10.4, 10.2, 9.9, 9.7, 9.4, 9.2, 9.4, 9.4, 9.5, 9.5, 9.4, 9.5, 9.5, 9.4, 9.3, - 9.4, 9.5, 9.3, 9.4, 9.4, 9.4, 9.7, 9.7, 9.6, 9.6, 9.7, 9.7, 9.7, 9.8, 9.7, 9.5, 9.3, - 9.2, 9.2, 9.3, 9.4, 9.5, 9.9, 10.2, 10.4, 10.6, 10.9, 11, 11, 10.8, 10.6, 10.3, 10.2, - 10, 9.9, 9.8, 9.7, 9.7, 9.6, 9.4, 9.1, 8.7, 8.2, 7.8, 7.4, 7.1, 6.9, 6.4, 6, 5.8, 5.6, - 5.5, 5.4, 5.4, 5.4, 5.5, 5.4, 5.4, 5.4, 5.4, 5.3, 5.3, 5.3, 5.4, 5.3, 5.2, 5.2, 5.1, - 5.1, 5.2, 5.2, 5.4, 5.6, 5.8, 5.8, 5.8, 5.7, 5.5, 5.3, 4.7, 4.2, 4.1, 4, 3.9, 4.1, - 4.5, 4.8, 5, 5.1, 5.5, 5.9, 6.3, 6.7, 7, 7.2, 7.3, 7.3, 7.3, 7.4, 7.4, 7.3, 7.3, 7.3, - 7.3, 7.3, 7.4, 7.5, 7.5, 7.3, 7.2, 7.1, 7, 7, 6.9, 6.8, 6.7, 6.6, 6.5, 6.3, 6.1, 5.9, - 5.7, 5.4, 5.1, 4.8, 4.5, 4.2, 3.8, 3.4, 2.9, 2.4, 1.8, 1.3, 0.9, 0.5, 0.1, -0.2, -0.3, - -0.4, -0.5, 14.2, 14.1, 13.9, 13.7, 13.6, 13.6, 13.5, 13.5, 13.5, 13.6, 13.9, 14, - 14.1, 14.4, 14.6, 14.8, 15.3, 15.9, 16.4, 16.9, 17.2, 17.4, 17.6, 17.6, 17.6, 17.4, - 17.1, 16.8, 16.1, 15.3, 14.7, 14.3, 14, 13.8, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, - 13.5, 13.6, 13.8, 14.1, 14.4, 14.6, 14.7, 14.9, 15.3, 15.6, 15.7, 15.7, 15.5, 15.2, - 14.9, 14.5, 14.2, 13.9, 13.6, 13.5, 13.2, 13, 12.8, 12.8, 12.8, 12.7, 12.6, 12.6, - 12.6, 12.6, 12.4, 12.3, 12.3, 12.1, 12, 11.8, 11.8, 11.9, 11.9, 12.1, 12.3, 12.6, - 12.9, 13, 13, 12.7, 12.3, 12.1, 11.9, 11.7, 11.6, 11.5, 11.5, 11.5, 11.5, 11.5, 11.4, - 11.3, 11.1, 10.9, 10.8, 10.7, 10.5, 10.2, 9.9, 9.7, 9.4, 9.1, 9.3, 9.6, 9.7, 9.6, 9.5, - 9.6, 9.5, 9.4, 9.4, 9.4, 9.6, 9.6, 9.7, 9.7, 9.8, 9.9, 9.9, 10, 9.9, 9.9, 9.9, 9.8, - 9.9, 9.9, 9.9, 9.7, 9.6, 9.6, 9.6, 9.7, 9.9, 10.3, 10.7, 10.9, 11.1, 11.3, 11.3, 11.3, - 11.2, 11.3, 11.1, 10.7, 10.5, 10.3, 10.3, 10.2, 10.1, 10, 9.7, 9.2, 8.7, 8.1, 7.7, - 7.4, 7, 6.7, 6.5, 6.2, 5.9, 5.7, 5.5, 5.4, 5.4, 5.4, 5.4, 5.5, 5.4, 5.4, 5.4, 5.3, - 5.3, 5.3, 5.3, 5.3, 5.2, 5.3, 5.2, 5.1, 5.2, 5.3, 5.4, 5.6, 5.7, 5.8, 5.9, 5.8, 5.7, - 5.5, 4.8, 4.3, 4.1, 4, 3.9, 4, 4.4, 4.8, 5, 5.1, 5.4, 6, 6.3, 6.7, 7, 7.2, 7.3, 7.3, - 7.4, 7.4, 7.4, 7.4, 7.4, 7.3, 7.3, 7.4, 7.3, 7.3, 7.3, 7.2, 7.1, 7.1, 7, 7, 6.9, 6.8, - 6.8, 6.7, 6.6, 6.4, 6.2, 6, 5.8, 5.5, 5.2, 4.9, 4.6, 4.4, 4.1, 3.6, 3.1, 2.5, 1.9, - 1.3, 1, 0.6, 0.1, -0.1, 0, -0.2, -0.2, 14, 13.8, 13.7, 13.5, 13.5, 13.6, 13.5, 13.5, - 13.5, 13.6, 13.9, 13.9, 14, 14.2, 14.5, 14.7, 14.9, 15.4, 16, 16.4, 16.8, 17, 17.1, - 17.1, 17.1, 17, 16.8, 16.6, 16.2, 15.5, 14.8, 14.3, 14.1, 13.8, 13.6, 13.6, 13.6, - 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.9, 14.1, 14.4, 14.5, 14.8, 15.1, 15.4, 15.4, - 15.3, 15.2, 15, 14.7, 14.4, 14.2, 14, 13.7, 13.4, 13.3, 13, 12.9, 12.8, 12.8, 12.6, - 12.5, 12.4, 12.5, 12.6, 12.3, 12.3, 12.3, 12, 11.8, 11.8, 11.7, 11.8, 11.8, 12.1, - 12.3, 12.4, 12.7, 13.1, 13.1, 12.5, 12.1, 11.9, 11.7, 11.5, 11.4, 11.4, 11.4, 11.5, - 11.5, 11.5, 11.4, 11.3, 11.2, 11, 10.8, 10.6, 10.4, 10, 9.8, 9.7, 9.4, 9.2, 9.5, 9.7, - 9.7, 9.7, 9.6, 9.6, 9.5, 9.5, 9.6, 9.8, 9.9, 9.8, 9.9, 10, 10.1, 10.1, 10.1, 10.2, - 10.2, 10.1, 10.1, 10.1, 10.2, 10.2, 10.1, 10, 9.9, 9.8, 9.9, 10.1, 10.3, 10.6, 11, - 11.2, 11.4, 11.4, 11.4, 11.5, 11.6, 11.8, 11.7, 11.4, 11.1, 11, 10.9, 10.7, 10.7, - 10.5, 10, 9.4, 8.7, 8.1, 7.7, 7.4, 7.1, 6.8, 6.5, 6.2, 5.9, 5.7, 5.6, 5.5, 5.5, 5.5, - 5.5, 5.5, 5.5, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.2, 5.2, 5.2, 5.2, 5.1, 5.2, 5.3, - 5.5, 5.7, 5.8, 6, 5.9, 5.7, 5.4, 4.9, 4.4, 4.2, 4.1, 4, 4, 4.3, 4.8, 5, 5.1, 5.4, 6, - 6.4, 6.8, 7.1, 7.2, 7.3, 7.3, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.3, 7.3, 7.3, 7.2, 7.2, - 7.2, 7.2, 7.1, 7, 7, 6.9, 6.9, 6.8, 6.7, 6.6, 6.5, 6.3, 6.1, 5.9, 5.7, 5.4, 5.1, 4.8, - 4.6, 4.2, 3.8, 3.2, 2.6, 2, 1.4, 0.9, 0.5, 0.2, 0.2, 0.1, -0.1, 0, 13.7, 13.7, 13.7, - 13.6, 13.5, 13.5, 13.4, 13.5, 13.5, 13.5, 13.8, 13.8, 13.9, 14.1, 14.3, 14.6, 14.7, - 14.9, 15.4, 15.9, 16.3, 16.5, 16.7, 16.7, 16.7, 16.7, 16.5, 16.3, 16, 15.5, 14.8, - 14.3, 14.1, 13.8, 13.6, 13.5, 13.4, 13.4, 13.5, 13.6, 13.6, 13.5, 13.6, 13.7, 13.9, - 14.2, 14.4, 14.5, 14.7, 15, 15, 15, 14.8, 14.7, 14.5, 14.4, 14.2, 14, 13.8, 13.6, - 13.3, 13.1, 13, 12.9, 12.7, 12.5, 12.4, 12.4, 12.4, 12.3, 12.3, 12.3, 11.9, 11.6, - 11.6, 11.5, 11.4, 11.5, 11.7, 11.9, 12.1, 12.1, 12.4, 12.7, 12.6, 12.2, 11.8, 11.7, - 11.5, 11.4, 11.3, 11.3, 11.4, 11.5, 11.5, 11.5, 11.4, 11.3, 11.2, 11, 10.7, 10.6, - 10.5, 10.1, 9.9, 9.7, 9.4, 9.5, 9.7, 9.8, 9.9, 9.8, 9.7, 9.6, 9.5, 9.7, 10, 10.1, - 10.2, 10.2, 10.2, 10.2, 10.4, 10.4, 10.3, 10.5, 10.4, 10.2, 10.2, 10.3, 10.2, 10.1, - 10, 9.9, 9.9, 9.9, 10, 10.2, 10.6, 11, 11.2, 11.5, 11.6, 11.6, 11.8, 11.9, 12, 12.1, - 11.9, 11.8, 11.7, 11.7, 11.7, 11.5, 11.2, 10.8, 10.2, 9.6, 8.8, 8.2, 7.8, 7.5, 7.2, - 6.8, 6.5, 6.3, 6, 5.8, 5.6, 5.5, 5.5, 5.6, 5.6, 5.6, 5.6, 5.5, 5.3, 5.3, 5.3, 5.3, - 5.3, 5.3, 5.3, 5.3, 5.3, 5.2, 5.1, 5.1, 5.2, 5.3, 5.6, 5.8, 5.9, 5.8, 5.7, 5.4, 5, - 4.5, 4.3, 4.2, 4.1, 4, 4.3, 4.7, 4.9, 5.1, 5.5, 6, 6.4, 6.8, 7.1, 7.2, 7.3, 7.3, 7.4, - 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.3, 7.3, 7.3, 7.2, 7.1, 7.1, 7.1, 7.1, 7, 6.9, - 6.8, 6.7, 6.6, 6.6, 6.4, 6.3, 6.1, 5.8, 5.4, 5, 4.8, 4.6, 4.2, 3.7, 3.3, 2.7, 2.1, - 1.4, 0.9, 0.5, 0, 0, 0.1, 0.1, 0.2, 13.5, 13.4, 13.4, 13.4, 13.5, 13.5, 13.4, 13.5, - 13.5, 13.6, 13.7, 13.7, 13.8, 14, 14.1, 14.4, 14.6, 14.7, 15, 15.4, 15.7, 16, 16.3, - 16.4, 16.4, 16.4, 16.3, 16, 15.6, 15.3, 14.7, 14.3, 14.1, 13.7, 13.6, 13.4, 13.1, - 13.1, 13.2, 13.4, 13.6, 13.6, 13.5, 13.6, 13.7, 13.9, 14.1, 14.3, 14.4, 14.5, 14.5, - 14.5, 14.4, 14.4, 14.4, 14.4, 14.3, 14.1, 14, 13.7, 13.5, 13.3, 13.1, 12.8, 12.7, - 12.8, 12.8, 12.6, 12.4, 12.2, 12.2, 12.1, 11.9, 11.4, 11.2, 11.2, 11, 11.1, 11.5, - 11.7, 11.8, 11.9, 12, 12.3, 12.1, 11.9, 11.6, 11.5, 11.4, 11.3, 11.3, 11.2, 11.4, - 11.5, 11.5, 11.4, 11.4, 11.3, 11.2, 11, 10.7, 10.5, 10.3, 10, 10, 9.8, 9.5, 9.7, 9.9, - 9.9, 10, 9.9, 9.7, 9.6, 9.6, 9.9, 10.1, 10.3, 10.4, 10.5, 10.4, 10.5, 10.6, 10.6, - 10.6, 10.5, 10.4, 10.2, 10.2, 10.1, 10.1, 10, 9.9, 9.9, 9.8, 10, 10.2, 10.4, 10.8, - 11.1, 11.5, 11.8, 11.9, 11.9, 12.1, 12.2, 12.5, 12.6, 12.4, 12.3, 12.3, 12.4, 12.4, - 12.2, 11.7, 11.1, 10.3, 9.6, 8.8, 8.3, 7.9, 7.6, 7.2, 6.9, 6.6, 6.3, 6.1, 5.8, 5.6, - 5.6, 5.6, 5.6, 5.6, 5.6, 5.6, 5.5, 5.4, 5.3, 5.3, 5.3, 5.3, 5.3, 5.2, 5.3, 5.3, 5.3, - 5.2, 5.1, 5.1, 5.1, 5.4, 5.7, 5.8, 5.7, 5.6, 5.3, 5, 4.5, 4.3, 4.3, 4.3, 4.2, 4.4, - 4.7, 4.9, 5.1, 5.5, 6, 6.4, 6.7, 7.1, 7.2, 7.2, 7.3, 7.3, 7.4, 7.4, 7.4, 7.4, 7.4, - 7.4, 7.3, 7.4, 7.4, 7.3, 7.2, 7.1, 7.1, 7, 7, 7, 6.9, 6.9, 6.8, 6.7, 6.6, 6.6, 6.4, - 6.1, 5.7, 5.4, 5, 4.8, 4.5, 4.1, 3.7, 3.2, 2.7, 2.1, 1.5, 0.9, 0.3, 0, 0.1, 0.2, 0.3, - 0.5, 13.6, 13.3, 13.1, 13.2, 13.4, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, - 13.8, 13.9, 14.1, 14.2, 14.4, 14.6, 14.8, 15.2, 15.5, 15.8, 16, 16.2, 16.2, 15.9, - 15.7, 15.4, 14.9, 14.5, 14.3, 14, 13.7, 13.6, 13.2, 13.1, 13.1, 13.1, 13.1, 13.3, - 13.5, 13.6, 13.5, 13.5, 13.6, 13.8, 14.1, 14.2, 14.2, 14.2, 14.2, 14.1, 14.2, 14.3, - 14.4, 14.4, 14.3, 14.1, 13.8, 13.6, 13.5, 13.1, 12.9, 13, 13, 12.8, 12.4, 12.2, 12.2, - 12.1, 12, 11.9, 11.8, 11.6, 11.5, 11.2, 10.9, 11.1, 11.4, 11.6, 11.6, 11.6, 11.8, - 11.7, 11.5, 11.4, 11.4, 11.3, 11.2, 11.2, 11.2, 11.3, 11.3, 11.3, 11.3, 11.3, 11.2, - 11.1, 11, 10.7, 10.4, 10.2, 10, 9.7, 9.5, 9.6, 10, 10, 10, 10, 9.9, 9.8, 9.6, 9.8, - 10.2, 10.3, 10.5, 10.6, 10.8, 10.7, 10.7, 10.7, 10.7, 10.7, 10.4, 10.3, 10.3, 10.2, - 10, 10, 10, 10, 9.9, 10, 10.3, 10.4, 10.7, 11.2, 11.5, 11.7, 12, 12.1, 12.3, 12.4, - 12.6, 12.7, 12.8, 12.7, 12.7, 12.8, 12.9, 12.8, 12.5, 12.1, 11.4, 10.5, 9.6, 9, 8.4, - 8.1, 7.8, 7.1, 6.8, 6.6, 6.4, 6.1, 5.8, 5.7, 5.6, 5.7, 5.6, 5.6, 5.6, 5.5, 5.5, 5.4, - 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.3, 5.1, 5.1, 5.1, 5.3, 5.5, 5.6, 5.6, - 5.5, 5.3, 5, 4.6, 4.4, 4.4, 4.4, 4.3, 4.4, 4.7, 4.9, 5.1, 5.5, 5.9, 6.3, 6.8, 7, 7.2, - 7.3, 7.3, 7.3, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.4, 7.3, 7.2, 7.1, 7.1, 7, 7, - 7, 7, 6.9, 6.9, 6.8, 6.7, 6.6, 6.4, 6.1, 5.8, 5.4, 5.1, 4.8, 4.6, 4.2, 3.8, 3.2, 2.6, - 2.1, 1.4, 0.6, 0.2, 0.2, 0.2, 0.4, 0.5, 0.8, 13.8, 13.6, 13.4, 13.2, 13.1, 13.2, 13.4, - 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.7, 13.9, 14, 14.2, 14.3, 14.4, 14.6, - 14.9, 15.2, 15.5, 15.8, 15.8, 15.5, 15.3, 15, 14.6, 14.3, 14.2, 14, 13.8, 13.5, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.6, 13.5, 13.5, 13.6, 13.7, 13.9, 14, 14, - 14, 14, 14.1, 14.2, 14.4, 14.4, 14.4, 14.3, 14, 13.6, 13.3, 13, 13, 13, 12.8, 12.6, - 12.5, 12.5, 12.5, 12.3, 12.3, 12.2, 12, 11.8, 11.7, 11.5, 11.2, 11, 11.1, 11.2, 11.3, - 11.3, 11.4, 11.4, 11.3, 11.3, 11.2, 11.2, 11.2, 11.1, 11.1, 11.3, 11.3, 11.3, 11.3, - 11.2, 11, 11, 10.8, 10.6, 10.3, 10.2, 9.8, 9.3, 9.5, 9.9, 10.1, 10.1, 10.1, 10, 10, - 9.9, 9.7, 10, 10.3, 10.4, 10.6, 10.9, 11, 10.8, 10.7, 10.7, 10.7, 10.5, 10.3, 10.2, - 10.1, 10.1, 10.1, 10, 10, 10, 10, 10.2, 10.4, 10.6, 10.9, 11.2, 11.5, 11.8, 12.1, - 12.4, 12.8, 13, 13, 13, 13, 13.1, 13.1, 13.2, 13.2, 13, 12.7, 12.2, 11.5, 10.6, 9.8, - 9.2, 8.5, 8.1, 7.9, 7.4, 6.9, 6.6, 6.3, 6, 5.9, 5.7, 5.6, 5.7, 5.8, 5.7, 5.7, 5.6, - 5.4, 5.4, 5.3, 5.3, 5.3, 5.4, 5.3, 5.3, 5.3, 5.2, 5.2, 5.2, 5.1, 5.1, 5.2, 5.3, 5.4, - 5.4, 5.4, 5.4, 5.3, 5, 4.6, 4.4, 4.5, 4.5, 4.5, 4.5, 4.7, 4.9, 5.2, 5.5, 5.8, 6.2, - 6.7, 6.9, 7.1, 7.2, 7.3, 7.3, 7.3, 7.3, 7.3, 7.3, 7.4, 7.3, 7.3, 7.3, 7.3, 7.2, 7.2, - 7.1, 7.1, 7.1, 7.1, 7.1, 7, 6.9, 6.9, 6.8, 6.9, 6.7, 6.4, 6.2, 5.9, 5.6, 5.2, 4.9, - 4.5, 4.1, 3.6, 3, 2.4, 1.8, 1, 0.5, 0.5, 0.4, 0.4, 0.6, 0.8, 1, 13.6, 13.6, 13.6, - 13.5, 13.2, 13.1, 13.3, 13.4, 13.4, 13.4, 13.4, 13.5, 13.5, 13.5, 13.6, 13.8, 13.9, - 14, 14, 14.2, 14.3, 14.4, 14.6, 14.9, 15.1, 15.1, 15, 14.9, 14.6, 14.3, 14.2, 14, - 13.8, 13.6, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.5, - 13.5, 13.5, 13.6, 13.6, 13.6, 13.7, 13.9, 14.1, 14.1, 14.3, 14.4, 14.4, 14.3, 14, - 13.6, 13.3, 13.1, 12.9, 12.7, 12.6, 12.6, 12.7, 12.8, 12.7, 12.5, 12.4, 12.2, 12, - 11.7, 11.7, 11.7, 11.5, 11.2, 10.9, 11.1, 11.2, 11.2, 11.3, 11.2, 11.1, 11, 11, 11, - 11, 11, 11, 11.1, 11.1, 11.1, 11.2, 11.1, 11, 11, 10.7, 10.2, 9.9, 9.8, 9.5, 9.4, 9.7, - 10, 10.1, 10.1, 10.1, 10, 10, 9.9, 9.8, 10, 10.2, 10.4, 10.5, 10.8, 10.9, 10.8, 10.7, - 10.7, 10.5, 10.3, 10.2, 10.1, 10, 10, 10, 10.1, 10.1, 10.1, 10.2, 10.3, 10.4, 10.6, - 10.9, 11.3, 11.6, 12, 12.3, 12.7, 12.9, 13.1, 13.3, 13.3, 13.4, 13.4, 13.5, 13.5, - 13.4, 13.2, 12.8, 12.3, 11.5, 10.5, 9.8, 9.3, 8.6, 8.3, 8, 7.6, 7.1, 6.5, 6.2, 6, 5.9, - 5.8, 5.7, 5.8, 5.9, 5.8, 5.7, 5.6, 5.4, 5.4, 5.4, 5.4, 5.4, 5.4, 5.3, 5.3, 5.2, 5.2, - 5.2, 5.1, 5.1, 5.1, 5.1, 5.2, 5.2, 5.3, 5.3, 5.3, 5.2, 5, 4.7, 4.5, 4.5, 4.5, 4.5, - 4.5, 4.7, 5, 5.2, 5.4, 5.7, 6.1, 6.6, 6.9, 7.1, 7.1, 7.2, 7.2, 7.2, 7.3, 7.3, 7.3, - 7.2, 7.2, 7.2, 7.2, 7.2, 7.1, 7.1, 7.1, 7.1, 7.1, 7.1, 7.1, 7, 7, 6.9, 6.9, 6.9, 6.7, - 6.5, 6.2, 6, 5.6, 5.1, 4.8, 4.4, 4, 3.4, 2.9, 2.3, 1.6, 0.7, 0.3, 0.5, 0.5, 0.7, 1, - 1.1, 1.2, 13.3, 13.4, 13.5, 13.6, 13.3, 13.1, 13.2, 13.2, 13.1, 13.1, 13.1, 13.1, - 13.3, 13.5, 13.5, 13.6, 13.7, 13.7, 13.8, 13.9, 14, 14.1, 14.2, 14.4, 14.6, 14.5, - 14.4, 14.3, 14.2, 14.1, 13.9, 13.7, 13.6, 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.2, 13.4, 13.5, 13.5, 13.5, 13.6, 13.7, 13.9, - 14.1, 14.1, 14.2, 14.3, 14.3, 14, 13.5, 13.3, 13.1, 12.9, 12.8, 12.9, 12.8, 12.8, - 12.7, 12.7, 12.5, 12.3, 12.1, 11.9, 11.8, 11.7, 11.6, 11.5, 11.4, 11.1, 11, 11.1, - 11.1, 11.1, 11, 10.9, 10.8, 10.7, 10.7, 10.6, 10.8, 10.8, 10.7, 10.6, 10.6, 10.9, 11, - 11, 10.9, 10.5, 10, 9.8, 9.5, 9.4, 9.7, 9.9, 10, 10.1, 10.1, 10.1, 10.1, 10, 10, 10, - 10, 10.2, 10.4, 10.5, 10.6, 10.8, 10.7, 10.7, 10.6, 10.4, 10.2, 10.1, 10, 10, 10, 10, - 10, 10.1, 10.2, 10.3, 10.4, 10.5, 10.7, 11, 11.4, 11.8, 12.2, 12.4, 12.7, 12.9, 13.2, - 13.4, 13.5, 13.6, 13.7, 13.8, 13.8, 13.6, 13.3, 12.8, 12.2, 11.4, 10.6, 9.9, 9.3, 8.7, - 8.3, 8, 7.6, 7.1, 6.5, 6.2, 6, 6, 5.8, 5.7, 5.8, 5.9, 5.7, 5.6, 5.5, 5.5, 5.4, 5.5, - 5.4, 5.4, 5.4, 5.4, 5.3, 5.3, 5.2, 5.2, 5.1, 5.1, 5, 5.1, 5.1, 5.1, 5.2, 5.3, 5.2, - 5.1, 5, 4.8, 4.6, 4.6, 4.5, 4.5, 4.5, 4.8, 5.1, 5.3, 5.4, 5.7, 6.1, 6.5, 6.8, 7, 7.1, - 7.1, 7.2, 7.2, 7.3, 7.2, 7.2, 7.1, 7.1, 7.1, 7.1, 7.1, 7.1, 7.1, 7, 7.1, 7.1, 7.1, - 7.1, 7.1, 7, 7, 6.9, 6.8, 6.7, 6.5, 6.3, 6, 5.5, 5.1, 4.7, 4.3, 3.8, 3.3, 2.9, 2.3, - 1.4, 0.5, 0.4, 0.5, 0.6, 0.9, 1.1, 1.3, 1.4, 13.1, 13.1, 13.2, 13.3, 13.4, 13.2, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.6, 13.6, 13.6, 13.6, 13.6, 13.7, - 13.8, 13.9, 14, 14.1, 14.1, 14, 14, 13.9, 13.8, 13.6, 13.5, 13.5, 13.3, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.2, - 13.4, 13.5, 13.5, 13.6, 13.8, 14, 14.1, 14.2, 14.2, 14, 13.6, 13.3, 13.2, 12.9, 13.1, - 13.2, 13, 12.8, 12.7, 12.5, 12.3, 12.3, 12.2, 12.1, 12, 11.9, 11.7, 11.5, 11.4, 11.3, - 11.2, 10.9, 10.9, 10.9, 10.9, 10.8, 10.7, 10.6, 10.6, 10.6, 10.6, 10.6, 10.7, 10.7, - 10.5, 10.7, 10.8, 10.8, 10.5, 10.1, 9.9, 9.7, 9.5, 9.4, 9.8, 10.1, 10.2, 10.2, 10.2, - 10.2, 10.2, 10.2, 10.2, 10.2, 10.1, 10.2, 10.4, 10.4, 10.5, 10.7, 10.8, 10.7, 10.5, - 10.2, 10.1, 10, 10, 10, 10, 10, 10, 10.1, 10.3, 10.4, 10.5, 10.6, 10.8, 11.1, 11.5, - 11.9, 12.2, 12.5, 12.7, 13, 13.3, 13.5, 13.6, 13.8, 13.9, 13.9, 13.9, 13.7, 13.4, - 12.8, 12.2, 11.4, 10.7, 9.9, 9.2, 8.8, 8.4, 8, 7.5, 7, 6.7, 6.4, 6.2, 6, 5.9, 5.7, - 5.7, 5.7, 5.7, 5.6, 5.6, 5.6, 5.6, 5.6, 5.6, 5.6, 5.5, 5.5, 5.5, 5.5, 5.4, 5.3, 5.2, - 5.1, 5, 5, 5, 5.1, 5.2, 5.2, 5.1, 5.1, 4.9, 4.8, 4.7, 4.7, 4.7, 4.7, 4.7, 5, 5.2, 5.4, - 5.5, 5.8, 6.1, 6.4, 6.8, 6.9, 7, 7.1, 7.1, 7.1, 7.2, 7.1, 7.1, 7.1, 7, 7, 7, 7, 6.9, - 7, 7, 7.1, 7.1, 7.1, 7.1, 7.1, 7.1, 7, 7, 6.8, 6.7, 6.4, 6.1, 5.9, 5.5, 5, 4.6, 4.2, - 3.8, 3.2, 2.7, 2.1, 1.5, 0.6, 0.6, 0.6, 0.6, 0.9, 1.1, 1.4, 1.5, 13.1, 13.1, 13.1, - 13.1, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.6, - 13.6, 13.6, 13.6, 13.5, 13.5, 13.6, 13.8, 13.8, 13.9, 13.8, 13.7, 13.6, 13.6, 13.5, - 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.3, 13.4, 13.6, 13.6, 13.8, 14, 14, 14, 13.9, - 13.6, 13.2, 13, 13, 13.3, 13.4, 13.2, 12.9, 12.8, 12.6, 12.4, 12.3, 12.2, 12.1, 11.9, - 11.9, 11.8, 11.7, 11.6, 11.5, 11.5, 11.2, 10.9, 10.9, 10.9, 11, 11, 10.9, 10.9, 10.9, - 10.9, 10.8, 10.9, 10.9, 10.5, 10.5, 10.4, 10.3, 10.3, 10.2, 9.9, 9.7, 9.5, 9.5, 10, - 10.3, 10.4, 10.6, 10.5, 10.4, 10.5, 10.5, 10.4, 10.3, 10.2, 10.3, 10.4, 10.4, 10.4, - 10.5, 10.6, 10.5, 10.2, 10.1, 10.1, 10.1, 10, 10, 10, 10, 10.1, 10.2, 10.4, 10.5, - 10.6, 10.7, 11, 11.2, 11.6, 12, 12.3, 12.5, 12.7, 13, 13.3, 13.5, 13.7, 13.9, 14, 14, - 13.9, 13.7, 13.3, 12.7, 12.1, 11.4, 10.6, 9.9, 9.2, 8.8, 8.4, 8, 7.5, 7.1, 6.8, 6.5, - 6.2, 6, 5.9, 5.7, 5.7, 5.8, 5.7, 5.6, 5.7, 5.8, 5.8, 5.7, 5.7, 5.8, 5.9, 5.8, 5.7, - 5.6, 5.5, 5.5, 5.4, 5.2, 5.1, 5.1, 5.1, 5.2, 5.2, 5.3, 5.3, 5.2, 5.1, 4.9, 4.9, 4.9, - 5, 5, 5.1, 5.2, 5.4, 5.5, 5.7, 5.8, 6.1, 6.4, 6.7, 6.8, 7, 7, 7.1, 7.1, 7.2, 7.2, 7.1, - 7.1, 7, 6.9, 6.9, 6.9, 6.9, 6.9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6.8, 6.6, 6.3, 6, 5.7, - 5.3, 4.9, 4.5, 4.2, 3.6, 3, 2.4, 1.9, 1.5, 0.9, 0.8, 0.8, 0.8, 0.9, 1.1, 1.3, 1.5, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.2, 13.5, 13.6, 13.6, 13.6, 13.5, 13.4, 13.5, 13.6, 13.6, 13.6, 13.6, 13.6, - 13.5, 13.5, 13.6, 13.4, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.6, 13.7, - 13.7, 13.7, 13.8, 13.8, 13.6, 13.2, 13.1, 13.3, 13.5, 13.5, 13.4, 13.2, 13, 12.7, - 12.5, 12.3, 12.2, 12.1, 11.8, 12.1, 12.2, 12, 11.9, 11.7, 11.6, 11.5, 11.4, 11.3, - 11.2, 11.1, 11, 11.1, 11, 11.1, 11.1, 11, 11.1, 11, 10.7, 10.5, 10.3, 10.1, 10, 10.1, - 9.9, 9.6, 9.4, 9.8, 10.4, 10.5, 10.6, 10.8, 10.8, 10.8, 10.8, 10.7, 10.5, 10.5, 10.5, - 10.5, 10.4, 10.4, 10.4, 10.4, 10.5, 10.5, 10.3, 10.1, 10.1, 10.1, 10.2, 10.2, 10.2, - 10.2, 10.3, 10.5, 10.7, 10.9, 10.9, 10.9, 11.1, 11.3, 11.6, 12, 12.3, 12.5, 12.7, - 12.9, 13.1, 13.4, 13.8, 13.9, 13.9, 14, 13.9, 13.6, 13.1, 12.6, 12.1, 11.3, 10.5, 9.8, - 9.3, 8.8, 8.4, 8, 7.6, 7.2, 6.9, 6.6, 6.3, 6, 5.9, 5.8, 5.7, 5.9, 5.8, 5.7, 5.7, 5.9, - 6, 6, 6.1, 6.2, 6.3, 6.3, 6.1, 5.9, 5.8, 5.8, 5.7, 5.5, 5.4, 5.4, 5.5, 5.5, 5.6, 5.5, - 5.5, 5.5, 5.5, 5.3, 5.3, 5.2, 5.2, 5.3, 5.4, 5.4, 5.6, 5.7, 5.8, 6, 6.2, 6.5, 6.7, - 6.8, 6.9, 7, 7.1, 7.2, 7.2, 7.2, 7.1, 7, 6.9, 6.9, 6.8, 6.8, 6.8, 6.8, 6.8, 6.9, 6.9, - 6.9, 7, 7, 7, 7, 6.9, 6.7, 6.5, 6.2, 5.9, 5.5, 5.2, 4.8, 4.4, 3.9, 3.4, 2.8, 2.3, 1.8, - 1.4, 1.1, 0.8, 0.7, 0.8, 0.9, 1.1, 1.3, 1.7, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.4, 13.3, 13.2, - 13.2, 13.5, 13.6, 13.6, 13.6, 13.6, 13.6, 13.5, 13.4, 13.3, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.5, 13.5, 13.5, 13.5, 13.5, 13.7, 13.7, 13.6, - 13.6, 13.6, 13.6, 13.6, 13.4, 13.3, 13, 12.7, 12.6, 12.4, 12.3, 12.1, 12.1, 12, 11.8, - 11.7, 11.6, 11.5, 11.4, 11.3, 11.2, 11.2, 11.1, 11.1, 11.2, 11.1, 11.1, 11.1, 11.1, - 11.3, 11.3, 11.2, 11, 10.9, 10.4, 9.9, 9.8, 9.7, 9.7, 10, 10.4, 10.7, 10.7, 10.8, - 10.8, 10.9, 11, 10.8, 10.7, 10.7, 10.6, 10.6, 10.7, 10.6, 10.5, 10.5, 10.5, 10.4, - 10.4, 10.4, 10.3, 10.2, 10.3, 10.4, 10.4, 10.3, 10.4, 10.6, 10.8, 11, 11.2, 11.3, - 11.3, 11.3, 11.4, 11.6, 12, 12.3, 12.4, 12.6, 12.8, 13, 13.3, 13.6, 13.6, 13.7, 13.7, - 13.6, 13.3, 13, 12.5, 11.9, 11.2, 10.4, 9.8, 9.4, 9, 8.6, 8.3, 8, 7.5, 7, 6.7, 6.3, - 6.1, 5.9, 5.8, 5.8, 5.8, 5.9, 5.8, 5.9, 6.1, 6.2, 6.4, 6.6, 6.7, 6.8, 6.7, 6.6, 6.4, - 6.3, 6.2, 6.2, 6, 6, 6, 6, 5.9, 5.9, 5.8, 5.9, 6, 6.1, 6, 5.9, 5.8, 5.7, 5.7, 5.8, - 5.8, 5.8, 5.9, 6, 6.1, 6.3, 6.6, 6.7, 6.8, 6.9, 7, 7.1, 7.2, 7.2, 7.2, 7.1, 7.1, 7, - 6.8, 6.8, 6.8, 6.8, 6.7, 6.7, 6.7, 6.8, 6.8, 6.8, 6.8, 6.9, 6.8, 6.8, 6.6, 6.4, 6.1, - 5.8, 5.4, 5, 4.6, 4.1, 3.7, 3.2, 2.7, 2.2, 1.7, 1.3, 0.9, 0.8, 0.6, 0.6, 0.9, 1.1, - 1.4, 1.7, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.3, 13.4, 13.5, - 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.2, 13.4, 13.4, 13.4, 13.5, 13.5, 13.7, 13.8, 13.7, 13.7, 13.7, 13.7, 13.8, 13.7, - 13.5, 13.2, 13.1, 13, 12.8, 12.7, 12.4, 12.1, 11.9, 11.8, 11.8, 11.7, 11.5, 11.4, - 11.4, 11.3, 11.3, 11.2, 11.2, 11.3, 11.2, 11.2, 11.2, 11.3, 11.5, 11.6, 11.5, 11.3, - 11.2, 10.8, 10.3, 10.1, 10.1, 10.4, 10.6, 10.8, 10.9, 10.9, 10.9, 11, 11, 10.9, 10.9, - 10.9, 10.7, 10.6, 10.6, 10.7, 10.8, 10.7, 10.6, 10.5, 10.4, 10.3, 10.4, 10.4, 10.5, - 10.5, 10.6, 10.5, 10.6, 10.8, 10.9, 11.1, 11.4, 11.7, 11.6, 11.5, 11.6, 11.5, 11.7, - 12, 12.2, 12.4, 12.6, 12.8, 12.9, 13.3, 13.5, 13.4, 13.5, 13.5, 13.3, 13.1, 12.8, - 12.3, 11.7, 11, 10.5, 9.9, 9.5, 9.1, 8.9, 8.7, 8.4, 7.9, 7.3, 6.8, 6.4, 6.3, 6.1, 5.9, - 5.8, 5.9, 5.9, 5.9, 6, 6.3, 6.6, 6.8, 7, 7.1, 7, 7, 6.9, 6.9, 6.8, 6.7, 6.6, 6.4, 6.3, - 6.2, 5.9, 5.8, 5.9, 6, 6.2, 6.3, 6.4, 6.4, 6.4, 6.4, 6.1, 6, 6.1, 6.1, 6.1, 6.2, 6.2, - 6.3, 6.4, 6.6, 6.7, 6.8, 6.9, 7, 7.1, 7.2, 7.2, 7.1, 7.1, 7, 6.9, 6.8, 6.7, 6.8, 6.8, - 6.7, 6.6, 6.6, 6.6, 6.6, 6.6, 6.7, 6.6, 6.5, 6.4, 6.3, 6.1, 5.9, 5.6, 5.2, 4.8, 4.4, - 3.9, 3.4, 3, 2.5, 2.1, 1.5, 1.2, 0.9, 0.8, 0.6, 0.6, 1, 1.2, 1.5, 1.7, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.6, - 13.7, 13.8, 13.9, 13.9, 13.8, 13.8, 13.8, 13.8, 13.9, 13.8, 13.7, 13.5, 13.4, 13.3, - 13.2, 12.9, 12.6, 12.3, 12.1, 12, 11.9, 11.9, 11.7, 11.5, 11.5, 11.4, 11.4, 11.3, - 11.3, 11.3, 11.3, 11.4, 11.4, 11.6, 11.8, 11.7, 11.6, 11.5, 11.4, 11.3, 11.2, 11.2, - 11.1, 11, 11.1, 11.2, 11.2, 11.3, 11.2, 11.2, 11.1, 11, 10.9, 11, 10.8, 10.7, 10.7, - 10.9, 11, 10.9, 10.8, 10.5, 10.4, 10.4, 10.4, 10.5, 10.6, 10.6, 10.9, 11, 11, 11.1, - 11.3, 11.4, 11.5, 11.7, 11.8, 11.7, 11.7, 11.7, 11.7, 11.9, 12.1, 12.4, 12.6, 12.6, - 12.8, 13.1, 13.3, 13.2, 13.2, 13.2, 13, 12.8, 12.5, 12, 11.5, 11, 10.5, 10, 9.6, 9.3, - 9.1, 8.9, 8.7, 8.2, 7.7, 7, 6.6, 6.4, 6.2, 6.1, 6, 5.9, 5.9, 5.9, 6.1, 6.5, 6.7, 6.9, - 7.1, 7.2, 7.3, 7.2, 7, 7, 6.8, 6.6, 6.5, 6.2, 6, 5.8, 5.5, 5.5, 5.7, 5.8, 6, 6.2, 6.3, - 6.4, 6.5, 6.4, 6.1, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.4, 6.5, 6.6, 6.6, 6.8, 6.9, 7, 7, - 7, 7, 7, 6.9, 6.9, 6.8, 6.7, 6.7, 6.6, 6.6, 6.6, 6.6, 6.5, 6.4, 6.4, 6.4, 6.5, 6.3, - 6.2, 6.1, 6, 5.9, 5.6, 5.3, 5, 4.6, 4.2, 3.7, 3.2, 2.8, 2.3, 1.9, 1.5, 1.2, 0.9, 0.8, - 0.6, 0.7, 1.1, 1.4, 1.5, 1.8, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.4, 13.8, 14, 14.1, 14.1, 14.2, 14, 13.9, 13.9, 13.9, 13.9, - 13.9, 13.9, 13.9, 13.8, 13.7, 13.6, 13.5, 13.3, 12.9, 12.5, 12.3, 12.2, 12.1, 11.9, - 11.8, 11.7, 11.6, 11.5, 11.5, 11.4, 11.4, 11.5, 11.5, 11.6, 11.6, 11.7, 11.8, 11.8, - 11.8, 11.7, 11.7, 11.6, 11.6, 11.6, 11.5, 11.4, 11.5, 11.5, 11.5, 11.5, 11.5, 11.4, - 11.3, 11.2, 11.1, 11, 11.1, 11, 11, 11.3, 11.3, 11.2, 11, 10.8, 10.8, 10.7, 10.7, - 10.8, 10.9, 11.1, 11.2, 11.2, 11.1, 11.4, 11.5, 11.4, 11.4, 11.6, 11.9, 11.8, 11.7, - 11.7, 11.8, 11.9, 12, 12.3, 12.4, 12.5, 12.7, 12.9, 13, 12.8, 12.8, 12.7, 12.6, 12.5, - 12.2, 11.8, 11.3, 11, 10.5, 10.1, 9.8, 9.5, 9.4, 9.2, 8.9, 8.5, 7.9, 7.2, 6.8, 6.4, - 6.2, 6.1, 6, 5.9, 5.8, 5.8, 6.1, 6.4, 6.7, 6.9, 7.3, 7.4, 7.4, 7.3, 7, 6.7, 6.4, 6.1, - 5.9, 5.6, 5.4, 5.3, 5.2, 5.2, 5.2, 5.3, 5.4, 5.6, 5.9, 6.1, 6.1, 6.1, 6, 6.4, 6.4, - 6.3, 6.3, 6.3, 6.4, 6.4, 6.5, 6.6, 6.6, 6.6, 6.7, 6.8, 6.8, 6.8, 6.8, 6.8, 6.8, 6.7, - 6.6, 6.6, 6.6, 6.5, 6.5, 6.5, 6.4, 6.4, 6.2, 6.1, 6.2, 6.1, 6, 5.9, 5.8, 5.7, 5.6, - 5.4, 5.1, 4.7, 4.4, 3.9, 3.4, 2.9, 2.5, 2.1, 1.7, 1.4, 1.3, 1, 0.8, 0.6, 0.9, 1.3, - 1.4, 1.6, 1.9, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.2, 13.7, 14, 14.1, 14.3, 14.5, 14.5, 14.3, 14.2, 14.1, 14, 14, 14, 14.1, 14.2, - 14.1, 13.9, 13.9, 13.8, 13.5, 13.1, 12.7, 12.5, 12.4, 12.1, 11.9, 11.9, 11.9, 11.8, - 11.6, 11.6, 11.5, 11.6, 11.6, 11.6, 11.8, 11.8, 11.8, 11.9, 12, 12, 11.9, 11.9, 11.9, - 11.9, 11.8, 11.8, 11.7, 11.7, 11.7, 11.7, 11.7, 11.7, 11.6, 11.5, 11.5, 11.4, 11.3, - 11.4, 11.4, 11.3, 11.3, 11.4, 11.4, 11.3, 11.1, 11.2, 11.2, 11.1, 11.1, 11.3, 11.4, - 11.4, 11.4, 11.3, 11.7, 11.7, 11.5, 11.6, 11.6, 11.8, 11.8, 11.8, 11.7, 11.8, 11.9, - 12, 12.2, 12.3, 12.4, 12.5, 12.5, 12.5, 12.4, 12.4, 12.3, 12.3, 12.1, 11.9, 11.5, - 11.2, 11, 10.7, 10.4, 10.1, 9.9, 9.7, 9.5, 9.1, 8.7, 8.1, 7.5, 7, 6.5, 6.2, 6, 6, 5.9, - 5.9, 5.9, 6.2, 6.4, 6.8, 6.9, 7.1, 7.2, 7.2, 7.2, 6.9, 6.5, 6.1, 5.9, 5.7, 5.4, 5.2, - 5.1, 5.1, 5.1, 5.1, 5.1, 5.1, 5.4, 5.8, 6, 6, 5.9, 6, 6.3, 6.4, 6.2, 6.3, 6.3, 6.4, - 6.4, 6.5, 6.5, 6.5, 6.5, 6.5, 6.6, 6.6, 6.6, 6.6, 6.6, 6.6, 6.5, 6.5, 6.5, 6.4, 6.4, - 6.4, 6.3, 6.3, 6.2, 6.1, 6, 5.9, 5.9, 5.8, 5.8, 5.6, 5.4, 5.2, 5.1, 4.8, 4.5, 4, 3.6, - 3.1, 2.7, 2.2, 1.9, 1.6, 1.4, 1.2, 0.8, 0.7, 0.8, 1.2, 1.4, 1.5, 1.6, 1.9, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.5, 13.9, 14, - 14.3, 14.5, 14.7, 14.7, 14.6, 14.4, 14.3, 14.2, 14.1, 14.1, 14.2, 14.1, 14.1, 14, - 13.9, 13.7, 13.3, 13, 12.8, 12.6, 12.4, 12.2, 12.2, 12, 11.9, 11.8, 11.8, 11.7, 11.7, - 11.7, 11.7, 11.8, 11.9, 11.9, 12, 12.1, 12.1, 12, 12, 12.1, 12.1, 11.9, 11.9, 11.8, - 11.8, 11.9, 11.8, 11.7, 11.7, 11.9, 12, 12, 11.9, 11.7, 11.7, 11.8, 11.7, 11.7, 11.6, - 11.7, 11.6, 11.5, 11.6, 11.6, 11.5, 11.4, 11.5, 11.6, 11.6, 11.6, 11.6, 11.7, 11.7, - 11.7, 11.8, 11.8, 11.9, 12, 11.8, 11.7, 11.8, 11.8, 11.9, 12, 12.1, 12.2, 12.2, 12.2, - 12.2, 12.2, 12.2, 12.1, 12, 11.8, 11.7, 11.4, 11.3, 11.2, 10.9, 10.6, 10.3, 10.2, - 10.2, 9.9, 9.5, 8.9, 8.3, 7.7, 7.3, 6.8, 6.3, 6, 5.9, 6, 5.9, 6.1, 6.3, 6.4, 6.6, 6.7, - 6.7, 6.8, 6.9, 7, 6.7, 6.3, 6, 5.8, 5.7, 5.4, 5.2, 5.1, 5, 5, 5, 5, 5.1, 5.3, 5.5, - 5.7, 5.8, 5.8, 5.9, 6.2, 6.3, 6.1, 6.2, 6.2, 6.3, 6.4, 6.3, 6.3, 6.3, 6.3, 6.4, 6.4, - 6.4, 6.4, 6.5, 6.4, 6.4, 6.4, 6.4, 6.3, 6.3, 6.3, 6.2, 6.2, 6.1, 6, 6, 5.9, 5.8, 5.8, - 5.7, 5.6, 5.4, 5.2, 4.9, 4.8, 4.6, 4.3, 3.8, 3.4, 2.9, 2.5, 2.1, 1.7, 1.5, 1.3, 0.9, - 0.6, 0.8, 1, 1.3, 1.5, 1.6, 1.8, 2.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.7, 13.8, 14, 14.4, 14.7, 14.9, 14.9, 14.8, - 14.7, 14.4, 14.3, 14.2, 14.1, 14.1, 14.1, 14.1, 14, 13.9, 13.6, 13.2, 13, 12.9, 12.6, - 12.5, 12.4, 12.2, 12, 12, 11.9, 11.7, 11.7, 11.8, 11.8, 11.9, 12, 12.1, 12.2, 12.2, - 12.2, 12.3, 12.2, 12.2, 12.1, 11.9, 12, 11.9, 12, 12.1, 12, 12, 12, 12.1, 12.2, 12.3, - 12.2, 12.1, 12.2, 12.1, 12, 11.9, 11.6, 11.8, 11.8, 11.9, 11.9, 11.8, 11.9, 11.8, - 11.8, 11.8, 11.7, 11.8, 11.8, 11.8, 11.9, 11.9, 11.9, 11.9, 11.9, 12, 11.9, 11.8, - 11.8, 11.9, 12, 12, 12, 12, 12.1, 12.1, 12.1, 12.1, 12.1, 12, 11.9, 11.8, 11.6, 11.4, - 11.3, 11.2, 10.9, 10.6, 10.6, 10.6, 10.5, 10.3, 9.7, 8.9, 8.3, 7.7, 7.4, 6.9, 6.4, - 6.1, 6, 6, 6.1, 6.3, 6.4, 6.5, 6.6, 6.5, 6.5, 6.6, 6.6, 6.5, 6.3, 6.1, 5.9, 5.8, 5.7, - 5.4, 5.3, 5.1, 5, 5, 4.9, 4.9, 5, 5.2, 5.3, 5.4, 5.8, 5.9, 5.9, 6, 6.1, 6.1, 6.1, 6.1, - 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, 6.2, - 6.1, 6.1, 6, 6, 6, 5.9, 5.9, 5.8, 5.7, 5.5, 5.4, 5.3, 5, 4.8, 4.6, 4.4, 4, 3.6, 3.1, - 2.7, 2.3, 2, 1.7, 1.4, 1, 0.8, 0.7, 1, 1.2, 1.4, 1.6, 1.7, 1.9, 2.3, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.6, 13.9, - 14.2, 14.5, 14.7, 14.9, 15, 14.9, 14.8, 14.7, 14.5, 14.3, 14.2, 14.2, 14.1, 14, 13.8, - 13.7, 13.4, 13.1, 12.9, 12.8, 12.6, 12.4, 12.2, 12.1, 12.1, 12, 11.8, 11.9, 11.9, 12, - 12, 12, 12.1, 12.1, 12.2, 12.2, 12.3, 12.4, 12.4, 12.3, 12.2, 12.1, 12.1, 12.4, 12.2, - 12.2, 12.2, 12.2, 12.3, 12.4, 12.4, 12.4, 12.2, 12.2, 12.3, 12.2, 12.2, 12.2, 12.2, - 12.2, 12.2, 12.2, 12.1, 12, 12, 11.9, 11.8, 11.9, 11.9, 11.9, 12.1, 12.2, 12.1, 12, - 11.9, 11.9, 12, 11.9, 11.9, 11.9, 12, 12, 12.1, 12, 12, 12, 12, 12.1, 12.1, 12, 12, - 11.9, 11.8, 11.5, 11.4, 11.3, 11.1, 10.9, 10.8, 10.8, 10.8, 10.8, 10.5, 9.8, 9, 8.4, - 7.9, 7.4, 6.9, 6.6, 6.3, 6.1, 6.1, 6.2, 6.4, 6.7, 6.7, 6.7, 6.6, 6.5, 6.4, 6.4, 6.2, - 6.1, 6, 5.9, 5.8, 5.7, 5.5, 5.4, 5.1, 5, 4.9, 4.9, 4.8, 4.9, 5, 5.2, 5.4, 5.6, 5.7, - 5.7, 5.9, 6, 6, 6, 6, 6, 6.1, 6.1, 6.1, 6.1, 6, 6, 6, 5.8, 5.9, 6, 6.1, 6, 6, 6.1, - 6.1, 6.1, 6, 5.9, 5.9, 5.9, 5.9, 5.8, 5.8, 5.7, 5.7, 5.4, 5.2, 5.1, 4.9, 4.7, 4.5, - 4.2, 3.8, 3.4, 3, 2.6, 2.2, 1.9, 1.6, 1.3, 1, 0.8, 0.9, 1.1, 1.3, 1.5, 1.7, 1.9, 2.2, - 2.4, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.2, 13.5, 13.7, 14, 14.3, 14.5, 14.7, 14.9, 15, 15, 15, 14.9, 14.5, 14.3, - 14.1, 14, 13.8, 13.8, 13.8, 13.6, 13.2, 13, 12.9, 12.8, 12.6, 12.4, 12.2, 12.2, 12.1, - 12, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.2, 12.3, 12.3, 12.4, 12.4, 12.5, 12.4, - 12.4, 12.4, 12.4, 12.4, 12.3, 12.3, 12.3, 12.4, 12.4, 12.5, 12.5, 12.4, 12.3, 12.2, - 12.4, 12.4, 12.4, 12.4, 12.4, 12.5, 12.4, 12.5, 12.3, 12.2, 12.2, 12.1, 12, 12, 12, - 12.1, 12.3, 12.3, 12.3, 12.2, 12.1, 12, 12, 12, 12.1, 12.2, 12.1, 12, 11.9, 11.9, 12, - 11.9, 12, 12, 11.9, 11.9, 11.8, 11.7, 11.6, 11.5, 11.4, 11.3, 11.1, 11, 11, 11.1, - 11.1, 10.9, 10.5, 9.9, 9.2, 8.5, 8, 7.5, 7.1, 6.8, 6.5, 6.3, 6.2, 6.4, 6.7, 7, 6.8, - 6.8, 6.8, 6.6, 6.4, 6.3, 6.1, 6, 6, 5.9, 5.9, 5.8, 5.6, 5.5, 5.3, 5.1, 5, 4.9, 4.9, - 4.8, 4.9, 5.1, 5.3, 5.5, 5.6, 5.7, 5.8, 5.9, 6, 6, 6, 6, 6, 6.1, 6, 6, 6, 6, 5.9, 5.7, - 5.6, 5.9, 6, 5.9, 5.9, 6, 6, 5.9, 5.9, 5.8, 5.8, 5.8, 5.8, 5.7, 5.7, 5.7, 5.6, 5.4, - 5.1, 4.9, 4.8, 4.6, 4.4, 4.1, 3.7, 3.3, 2.8, 2.4, 2.1, 1.8, 1.5, 1.3, 0.9, 0.8, 0.9, - 1.1, 1.3, 1.5, 1.8, 2.1, 2.3, 2.6, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.8, 14, 14.2, 14.4, 14.6, - 14.8, 14.9, 15, 15.1, 14.7, 14.4, 14.1, 13.9, 13.8, 13.8, 13.7, 13.5, 13.3, 13.1, 13, - 12.9, 12.8, 12.6, 12.4, 12.3, 12.2, 12.1, 12.1, 12.2, 12.2, 12.2, 12.2, 12.2, 12.2, - 12.3, 12.3, 12.4, 12.4, 12.4, 12.5, 12.4, 12.5, 12.4, 12.3, 12.3, 12.3, 12.3, 12.3, - 12.3, 12.5, 12.6, 12.6, 12.4, 12.3, 12.4, 12.5, 12.5, 12.5, 12.6, 12.7, 12.6, 12.6, - 12.5, 12.4, 12.4, 12.3, 12.3, 12.3, 12.3, 12.4, 12.4, 12.4, 12.3, 12.2, 12.2, 12.1, - 12.1, 12.2, 12.3, 12.2, 12, 11.9, 11.9, 11.9, 11.9, 11.9, 11.9, 12, 11.9, 11.8, 11.7, - 11.7, 11.6, 11.5, 11.4, 11.3, 11.2, 11.2, 11.3, 11.4, 11.3, 11, 10.6, 10, 9.3, 8.8, - 8.2, 7.6, 7.2, 6.8, 6.6, 6.4, 6.4, 6.6, 6.9, 7, 6.9, 7, 6.9, 6.6, 6.4, 6.3, 6.2, 6.2, - 6.1, 6, 5.9, 5.9, 5.8, 5.6, 5.4, 5.2, 5, 4.9, 4.8, 4.7, 4.8, 5, 5.2, 5.3, 5.5, 5.6, - 5.7, 5.8, 5.9, 5.9, 5.9, 5.9, 6, 6.1, 6.1, 6, 6, 5.9, 5.9, 5.7, 5.7, 5.8, 5.9, 5.9, - 5.9, 5.9, 5.9, 5.8, 5.8, 5.8, 5.8, 5.8, 5.7, 5.6, 5.6, 5.5, 5.4, 5.2, 5, 4.8, 4.7, - 4.6, 4.3, 3.9, 3.5, 3.1, 2.7, 2.3, 2, 1.8, 1.5, 1.3, 0.9, 0.8, 1, 1.2, 1.4, 1.5, 1.9, - 2.2, 2.4, 2.8, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.7, 13.9, 14.1, 14.3, 14.5, 14.6, 14.8, 14.9, - 14.8, 14.5, 14.2, 13.9, 13.7, 13.6, 13.4, 13.3, 13.2, 13.2, 13, 12.9, 12.9, 12.8, - 12.6, 12.3, 12.2, 12.2, 12.3, 12.4, 12.3, 12.2, 12.2, 12.3, 12.3, 12.3, 12.4, 12.3, - 12.3, 12.4, 12.4, 12.4, 12.4, 12.4, 12.4, 12.3, 12.3, 12.3, 12.3, 12.4, 12.4, 12.5, - 12.6, 12.6, 12.6, 12.6, 12.6, 12.5, 12.6, 12.7, 12.7, 12.6, 12.5, 12.5, 12.6, 12.5, - 12.5, 12.5, 12.5, 12.4, 12.4, 12.4, 12.3, 12.3, 12.2, 12.3, 12.2, 12.2, 12.3, 12.2, - 12, 12, 12, 11.9, 11.9, 11.8, 11.8, 11.9, 11.9, 11.8, 11.8, 11.8, 11.7, 11.7, 11.6, - 11.4, 11.3, 11.3, 11.4, 11.5, 11.6, 11.4, 11.1, 10.7, 10.1, 9.4, 8.9, 8.2, 7.7, 7.4, - 7, 6.6, 6.5, 6.7, 6.9, 7, 7, 7, 7, 7, 6.7, 6.6, 6.5, 6.4, 6.3, 6.3, 6.1, 6, 6, 5.9, - 5.8, 5.7, 5.4, 5.1, 4.8, 4.5, 4.5, 4.6, 4.8, 5, 5.2, 5.3, 5.5, 5.6, 5.7, 5.7, 5.8, - 5.9, 5.9, 5.9, 6, 6, 6, 6, 5.9, 5.9, 5.8, 5.7, 5.8, 5.9, 5.9, 5.9, 5.9, 5.8, 5.8, 5.7, - 5.7, 5.7, 5.7, 5.6, 5.5, 5.5, 5.4, 5.2, 5, 4.9, 4.8, 4.5, 4.4, 4.2, 3.9, 3.4, 2.9, - 2.6, 2.2, 2, 1.7, 1.5, 1.2, 1, 1, 1.2, 1.4, 1.6, 1.7, 2, 2.4, 2.8, 3.2, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.3, 13.6, 13.8, 14, 14.2, 14.4, 14.6, 14.8, 14.8, 14.6, 14.3, 14, 13.6, - 13.4, 13.3, 13.3, 13.2, 13.1, 13.1, 12.9, 12.9, 12.8, 12.6, 12.4, 12.4, 12.4, 12.5, - 12.4, 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, 12.2, 12.3, 12.3, - 12.3, 12.4, 12.4, 12.3, 12.3, 12.4, 12.5, 12.5, 12.6, 12.6, 12.7, 12.8, 12.9, 12.9, - 12.7, 12.6, 12.6, 12.6, 12.5, 12.4, 12.5, 12.6, 12.6, 12.6, 12.5, 12.4, 12.3, 12.2, - 12.3, 12.2, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12, 11.9, 11.9, 11.9, 11.9, 11.9, - 11.9, 11.9, 11.9, 11.9, 11.9, 11.8, 11.8, 11.7, 11.7, 11.7, 11.5, 11.4, 11.4, 11.6, - 11.7, 11.8, 11.8, 11.3, 10.7, 10, 9.4, 8.8, 8.2, 7.8, 7.6, 7, 6.7, 6.9, 7.2, 7.1, 7.1, - 7.2, 7.2, 7.2, 7.2, 7.1, 6.9, 6.7, 6.6, 6.6, 6.5, 6.3, 6.2, 6.3, 6.2, 6.1, 6, 5.7, - 5.5, 5.1, 4.8, 4.6, 4.5, 4.5, 4.8, 5, 5.1, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 6, 5.9, 5.8, - 5.9, 5.9, 5.9, 5.9, 5.9, 5.8, 5.8, 5.8, 5.9, 5.8, 5.8, 5.8, 5.7, 5.8, 5.7, 5.7, 5.7, - 5.7, 5.6, 5.4, 5.4, 5.3, 5, 4.9, 4.8, 4.6, 4.4, 4.3, 4.1, 3.7, 3.3, 2.8, 2.4, 2, 1.8, - 1.6, 1.4, 1.2, 1, 1, 1.2, 1.5, 1.7, 1.9, 2.4, 3, 3.4, 3.8, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.3, 13.6, 13.7, 13.9, 14.1, 14.3, 14.6, 14.7, 14.6, 14.4, 14.1, 13.7, 13.4, - 13.3, 13.3, 13.2, 13.1, 13.1, 13, 12.9, 12.8, 12.7, 12.6, 12.5, 12.5, 12.4, 12.4, - 12.4, 12.5, 12.4, 12.4, 12.4, 12.4, 12.4, 12.4, 12.3, 12.3, 12.3, 12.3, 12.3, 12.3, - 12.4, 12.5, 12.5, 12.4, 12.4, 12.6, 12.6, 12.7, 12.6, 12.5, 12.6, 12.7, 12.8, 12.7, - 12.6, 12.5, 12.4, 12.4, 12.5, 12.5, 12.5, 12.4, 12.4, 12.4, 12.2, 12.2, 12, 12, 11.9, - 11.8, 11.8, 11.8, 11.8, 11.7, 11.8, 11.8, 11.7, 11.7, 11.7, 11.8, 11.9, 11.9, 11.9, - 11.8, 11.8, 11.8, 11.9, 11.8, 11.7, 11.7, 11.7, 11.5, 11.4, 11.6, 11.8, 11.8, 11.9, - 11.8, 11.3, 10.7, 10.1, 9.4, 8.8, 8.3, 7.9, 7.5, 7, 6.9, 7.3, 7.4, 7.3, 7.3, 7.3, 7.4, - 7.5, 7.4, 7.4, 7.2, 7.1, 7, 7, 6.9, 6.7, 6.6, 6.6, 6.5, 6.5, 6.5, 6.2, 5.9, 5.5, 5.2, - 4.9, 4.6, 4.5, 4.5, 4.7, 4.8, 5, 5.1, 5.2, 5.4, 5.5, 5.6, 5.8, 5.8, 5.7, 5.6, 5.7, - 5.7, 5.8, 5.8, 5.8, 5.8, 5.8, 5.8, 5.8, 5.7, 5.6, 5.6, 5.7, 5.7, 5.7, 5.7, 5.7, 5.6, - 5.4, 5.4, 5.2, 4.9, 4.8, 4.7, 4.5, 4.3, 4.1, 3.8, 3.5, 3.1, 2.6, 2.3, 2, 1.7, 1.5, - 1.4, 1.1, 1.1, 1.1, 1.4, 1.6, 1.7, 2.2, 2.8, 3.4, 4, 4.3, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.3, 13.5, 13.7, 13.8, 14, 14.2, 14.4, 14.4, 14.3, 14, 13.6, 13.4, 13.2, - 13.2, 13.2, 13.1, 13, 13, 12.9, 12.9, 12.8, 12.7, 12.6, 12.5, 12.5, 12.5, 12.6, 12.6, - 12.6, 12.4, 12.4, 12.4, 12.4, 12.4, 12.3, 12.3, 12.3, 12.3, 12.3, 12.4, 12.4, 12.5, - 12.7, 12.5, 12.4, 12.5, 12.6, 12.7, 12.9, 12.9, 12.8, 12.8, 12.7, 12.6, 12.5, 12.4, - 12.4, 12.5, 12.5, 12.4, 12.4, 12.3, 12.3, 12.1, 12, 11.9, 11.8, 11.7, 11.6, 11.6, - 11.6, 11.6, 11.6, 11.6, 11.6, 11.6, 11.5, 11.7, 11.7, 11.7, 11.8, 11.9, 11.9, 11.8, - 11.8, 11.8, 11.8, 11.8, 11.7, 11.6, 11.5, 11.5, 11.4, 11.6, 11.8, 11.8, 11.7, 11.6, - 11.2, 10.6, 10.1, 9.4, 8.8, 8.4, 8, 7.7, 7.4, 7.4, 7.6, 7.6, 7.6, 7.6, 7.6, 7.6, 7.7, - 7.7, 7.7, 7.6, 7.5, 7.5, 7.5, 7.5, 7.3, 7.1, 7, 7, 7.1, 7, 6.7, 6.3, 5.8, 5.5, 5.2, - 4.9, 4.7, 4.4, 4.3, 4.5, 4.7, 4.8, 4.9, 5.1, 5.2, 5.2, 5.4, 5.5, 5.4, 5.4, 5.5, 5.5, - 5.6, 5.6, 5.7, 5.7, 5.7, 5.8, 5.8, 5.6, 5.6, 5.6, 5.6, 5.6, 5.6, 5.6, 5.6, 5.5, 5.3, - 5.3, 5.1, 4.8, 4.7, 4.6, 4.3, 4, 3.8, 3.7, 3.4, 3, 2.5, 2.1, 1.9, 1.6, 1.4, 1.3, 1.1, - 1.2, 1.4, 1.6, 1.7, 2, 2.6, 3.3, 3.9, 4.5, 4.7, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.2, 13.5, 13.7, 13.9, 14.1, 14.3, 14.3, 14.2, 13.9, 13.6, 13.3, 13.2, 13.1, - 13.1, 13.1, 13, 13, 13, 12.9, 12.8, 12.8, 12.7, 12.6, 12.6, 12.5, 12.5, 12.5, 12.5, - 12.5, 12.4, 12.4, 12.5, 12.5, 12.4, 12.3, 12.3, 12.3, 12.3, 12.4, 12.4, 12.6, 12.6, - 12.5, 12.5, 12.6, 12.6, 12.7, 13, 13, 12.8, 12.8, 12.7, 12.5, 12.6, 12.5, 12.4, 12.7, - 12.5, 12.3, 12.2, 12.1, 12, 12, 11.8, 11.7, 11.6, 11.6, 11.6, 11.5, 11.4, 11.4, 11.4, - 11.4, 11.5, 11.5, 11.4, 11.5, 11.7, 11.8, 11.8, 11.9, 11.9, 11.9, 11.9, 11.8, 11.7, - 11.7, 11.6, 11.5, 11.4, 11.4, 11.4, 11.5, 11.7, 11.7, 11.6, 11.3, 10.9, 10.4, 10, 9.4, - 9, 8.5, 8.2, 8.1, 7.9, 7.7, 7.9, 7.8, 7.8, 7.8, 7.8, 7.8, 7.8, 7.9, 7.9, 7.9, 7.9, 8, - 8, 8, 7.8, 7.6, 7.4, 7.4, 7.4, 7.4, 7.1, 6.7, 6.1, 5.7, 5.5, 5.2, 4.9, 4.7, 4.3, 4.3, - 4.4, 4.6, 4.6, 4.7, 4.8, 4.9, 5, 5.1, 5.1, 5.2, 5.2, 5.3, 5.3, 5.4, 5.5, 5.5, 5.5, - 5.6, 5.6, 5.6, 5.6, 5.5, 5.5, 5.5, 5.4, 5.4, 5.5, 5.4, 5.2, 5.1, 4.9, 4.7, 4.5, 4.3, - 4, 3.8, 3.6, 3.5, 3.1, 2.7, 2.3, 2, 1.8, 1.7, 1.5, 1.4, 1.3, 1.6, 1.7, 1.7, 1.9, 2.3, - 3, 3.6, 4.3, 4.9, 5.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.6, - 13.8, 14.1, 14.3, 14.3, 14.1, 13.9, 13.6, 13.3, 13.2, 13.1, 13, 13, 13, 13, 13, 12.9, - 12.9, 12.8, 12.7, 12.7, 12.7, 12.6, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.6, 12.6, - 12.6, 12.4, 12.4, 12.4, 12.3, 12.4, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.6, 12.6, - 12.8, 12.8, 12.8, 12.9, 12.8, 12.7, 12.5, 12.5, 12.5, 12.5, 12.5, 12.3, 12.1, 11.9, - 11.7, 11.8, 11.7, 11.7, 11.7, 11.7, 11.7, 11.5, 11.3, 11.3, 11.3, 11.3, 11.3, 11.3, - 11.3, 11.4, 11.8, 12, 11.9, 12, 12, 11.9, 11.9, 11.9, 11.8, 11.6, 11.5, 11.5, 11.5, - 11.4, 11.3, 11.3, 11.5, 11.5, 11.3, 11, 10.6, 10.3, 9.8, 9.4, 9.1, 8.7, 8.5, 8.6, 8.4, - 8.1, 8, 8, 8, 8, 8, 8, 8, 8.1, 8.2, 8.3, 8.4, 8.5, 8.5, 8.4, 8.2, 8, 7.8, 7.8, 7.7, - 7.7, 7.5, 7.1, 6.5, 6, 5.6, 5.5, 5.3, 5.1, 4.8, 4.5, 4.4, 4.5, 4.3, 4.4, 4.5, 4.6, - 4.7, 4.8, 4.9, 5, 5, 5, 5.1, 5.1, 5.2, 5.2, 5.3, 5.4, 5.4, 5.4, 5.4, 5.4, 5.3, 5.3, - 5.3, 5.2, 5.3, 5.1, 5.1, 4.9, 4.8, 4.6, 4.4, 4.1, 3.9, 3.6, 3.4, 3.2, 2.9, 2.5, 2.2, - 2, 1.9, 1.7, 1.5, 1.3, 1.5, 1.9, 1.9, 1.9, 2.1, 2.6, 3.3, 4, 4.7, 5.1, 5.3, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.6, 13.9, 14.1, 14.2, 14, - 13.7, 13.5, 13.3, 13.1, 13, 13, 12.9, 12.9, 12.9, 12.9, 12.9, 12.9, 12.8, 12.8, 12.7, - 12.7, 12.7, 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.7, 12.5, 12.4, 12.4, - 12.4, 12.4, 12.5, 12.5, 12.5, 12.5, 12.5, 12.6, 12.7, 12.8, 12.8, 12.8, 12.9, 12.8, - 12.7, 12.6, 12.5, 12.4, 12.4, 12.5, 12.5, 12.3, 12.1, 12, 11.8, 11.7, 11.7, 11.6, - 11.7, 11.7, 11.5, 11.4, 11.3, 11.2, 11.3, 11.3, 11.2, 11.3, 11.4, 11.5, 11.9, 12.1, - 12, 12, 12, 12, 12, 11.9, 11.8, 11.7, 11.7, 11.8, 11.6, 11.4, 11.3, 11.2, 11.3, 11.2, - 11, 10.8, 10.5, 10.2, 9.7, 9.4, 9.1, 8.9, 8.8, 8.8, 8.6, 8.4, 8.3, 8.2, 8.2, 8.2, 8.2, - 8.2, 8.3, 8.4, 8.5, 8.7, 8.8, 9, 9.1, 8.9, 8.7, 8.4, 8.2, 8, 7.9, 7.9, 7.8, 7.4, 6.9, - 6.2, 5.8, 5.5, 5.3, 5.1, 5, 4.9, 4.8, 4.6, 4.4, 4.3, 4.3, 4.4, 4.5, 4.5, 4.6, 4.7, - 4.6, 4.6, 4.7, 4.8, 4.9, 5, 5.1, 5.1, 5.2, 5.3, 5.2, 5.1, 5, 5, 5, 5, 5, 4.9, 4.8, - 4.7, 4.6, 4.6, 4.5, 4.2, 4, 3.6, 3.3, 3.1, 2.8, 2.5, 2.2, 2, 2, 1.8, 1.6, 1.4, 1.6, 2, - 1.9, 2, 2.4, 3, 3.6, 4.3, 4.9, 5.4, 5.6, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.3, 13.6, 13.7, 14.1, 14.3, 14.1, 13.7, 13.4, 13.2, 13, 12.9, 12.9, - 12.8, 12.8, 12.8, 12.8, 13, 13, 13, 13, 12.9, 12.8, 12.8, 12.7, 12.6, 12.6, 12.6, - 12.7, 12.6, 12.6, 12.5, 12.6, 12.6, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, - 12.7, 12.7, 12.8, 12.7, 12.7, 12.7, 12.7, 12.6, 12.6, 12.5, 12.5, 12.4, 12.3, 12.2, - 12.2, 12.1, 11.9, 11.9, 11.8, 11.7, 11.6, 11.6, 11.6, 11.5, 11.4, 11.4, 11.3, 11.2, - 11.2, 11.2, 11.2, 11.3, 11.4, 11.5, 11.9, 12.2, 12.2, 12.1, 12.1, 12, 12, 11.9, 11.9, - 11.9, 11.9, 12, 11.8, 11.6, 11.5, 11.3, 11.1, 10.8, 10.7, 10.6, 10.3, 10, 9.7, 9.4, - 9.1, 9.1, 9.1, 9, 8.8, 8.7, 8.6, 8.6, 8.6, 8.5, 8.5, 8.6, 8.6, 8.7, 8.8, 8.9, 9.1, - 9.4, 9.5, 9.2, 8.9, 8.7, 8.5, 8.4, 8.2, 8, 7.8, 7.6, 7.1, 6.6, 6.1, 5.7, 5.5, 5.2, - 5.1, 5.1, 5, 4.7, 4.5, 4.3, 4.2, 4.4, 4.4, 4.4, 4.4, 4.4, 4.4, 4.5, 4.6, 4.6, 4.7, - 4.8, 4.8, 4.8, 4.9, 5, 4.9, 4.8, 4.7, 4.8, 4.8, 4.8, 4.8, 4.8, 4.8, 4.7, 4.6, 4.5, - 4.4, 4.2, 3.9, 3.5, 3.2, 3, 2.8, 2.5, 2.2, 2.1, 2, 1.9, 1.7, 1.7, 1.8, 1.9, 2, 2.2, - 2.7, 3.3, 4, 4.6, 5.1, 5.5, 5.7, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.2, 13.5, 13.6, 13.9, 14.2, 14.1, 13.6, 13.3, 13.1, 12.9, 12.8, 12.8, 12.8, 12.8, - 12.8, 12.9, 13, 13, 13, 13, 12.9, 12.8, 12.7, 12.6, 12.7, 12.7, 12.7, 12.7, 12.7, - 12.6, 12.5, 12.5, 12.6, 12.5, 12.5, 12.5, 12.5, 12.6, 12.7, 12.6, 12.5, 12.6, 12.7, - 12.7, 12.6, 12.6, 12.7, 12.6, 12.6, 12.5, 12.5, 12.5, 12.4, 12.3, 12.1, 11.9, 11.8, - 11.7, 11.7, 11.8, 11.7, 11.7, 11.7, 11.6, 11.6, 11.5, 11.5, 11.4, 11.4, 11.3, 11.3, - 11.3, 11.4, 11.5, 11.6, 11.8, 12.1, 12.3, 12.2, 12.1, 12.1, 12, 12, 12.1, 12.1, 12.2, - 12.2, 12.1, 11.9, 11.8, 11.5, 11, 10.7, 10.5, 10.3, 10.1, 10, 9.8, 9.5, 9.5, 9.3, 9.3, - 9.2, 9.1, 9, 8.9, 8.9, 8.9, 8.9, 8.9, 8.9, 9, 9.3, 9.3, 9.2, 9.4, 9.6, 9.5, 9.3, 9.1, - 8.9, 8.7, 8.6, 8.4, 8.2, 7.9, 7.7, 7.3, 6.8, 6.3, 5.9, 5.7, 5.5, 5.2, 5.1, 5, 4.8, - 4.6, 4.5, 4.2, 4.2, 4.2, 4.3, 4.3, 4.4, 4.4, 4.5, 4.5, 4.4, 4.5, 4.5, 4.5, 4.6, 4.6, - 4.6, 4.6, 4.6, 4.5, 4.5, 4.6, 4.5, 4.6, 4.7, 4.8, 4.7, 4.5, 4.3, 4.1, 3.9, 3.6, 3.3, - 3.1, 2.9, 2.7, 2.4, 2.1, 2, 1.9, 1.8, 1.8, 1.9, 1.9, 2.1, 2.3, 2.5, 3, 3.7, 4.3, 4.9, - 5.3, 5.6, 5.8, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.6, - 13.7, 14.1, 14.2, 13.7, 13.3, 13, 12.9, 12.8, 12.8, 12.8, 12.8, 12.8, 12.8, 12.8, - 12.8, 12.8, 12.8, 12.8, 12.8, 12.7, 12.7, 12.9, 12.9, 12.8, 12.8, 12.9, 12.8, 12.7, - 12.7, 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.7, 12.7, 12.6, 12.6, 12.6, 12.7, 12.6, - 12.6, 12.6, 12.5, 12.5, 12.5, 12.5, 12.5, 12.4, 12.3, 12.2, 12, 12, 12, 12, 12.1, - 12.1, 12.1, 12.1, 12.1, 12.2, 12.1, 12.2, 12.1, 12, 11.9, 11.8, 11.8, 11.8, 11.8, - 11.8, 11.8, 11.9, 12.2, 12.3, 12.1, 12, 12, 12, 12.2, 12.4, 12.4, 12.4, 12.4, 12.2, - 12, 11.6, 11.1, 10.8, 10.6, 10.3, 10.1, 10, 9.9, 9.8, 9.7, 9.5, 9.5, 9.4, 9.3, 9.2, - 9.2, 9.3, 9.3, 9.3, 9.3, 9.4, 9.5, 9.6, 9.6, 9.5, 9.6, 9.6, 9.5, 9.4, 9.2, 9.1, 8.9, - 8.8, 8.5, 8.2, 8, 7.7, 7.4, 6.9, 6.5, 6.2, 5.9, 5.6, 5.4, 5.3, 5.2, 4.9, 4.7, 4.5, - 4.2, 4, 4, 4.1, 4.2, 4.3, 4.4, 4.4, 4.3, 4.3, 4.4, 4.3, 4.3, 4.3, 4.4, 4.4, 4.4, 4.4, - 4.4, 4.4, 4.4, 4.3, 4.3, 4.5, 4.5, 4.4, 4.2, 4, 3.7, 3.5, 3.3, 3.1, 2.9, 2.7, 2.5, - 2.3, 2.1, 1.9, 1.8, 1.9, 2, 2.1, 2.1, 2.3, 2.5, 2.8, 3.4, 4.1, 4.7, 5.2, 5.6, 5.8, - 5.9, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.6, 13.7, - 14, 14.2, 13.9, 13.3, 13, 12.8, 12.8, 12.8, 12.8, 12.8, 12.8, 12.9, 12.9, 12.8, 12.8, - 12.8, 12.8, 12.9, 12.9, 12.9, 12.9, 13, 12.9, 13, 13, 13, 12.9, 12.8, 12.6, 12.7, - 12.6, 12.6, 12.6, 12.6, 12.7, 12.7, 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.5, 12.5, - 12.5, 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.7, 12.8, 12.8, 12.9, 13, 13, 13, - 13, 13, 12.9, 13, 12.9, 12.9, 12.8, 12.7, 12.6, 12.5, 12.3, 12.2, 12.1, 12, 12.1, - 12.2, 12.2, 12.1, 12, 12.1, 12.3, 12.5, 12.7, 12.7, 12.7, 12.5, 12.1, 11.9, 11.5, - 11.1, 10.9, 10.5, 10.3, 10.1, 10, 10.1, 9.9, 9.7, 9.7, 9.6, 9.5, 9.5, 9.6, 9.7, 9.6, - 9.7, 9.8, 9.8, 9.9, 10, 10, 9.9, 9.9, 9.9, 9.7, 9.6, 9.4, 9.3, 9.1, 8.9, 8.6, 8.4, - 8.1, 7.8, 7.4, 7, 6.6, 6.3, 6.2, 5.8, 5.6, 5.4, 5.2, 5.1, 4.9, 4.6, 4.4, 4.3, 4.2, - 4.1, 4.1, 4.1, 4.1, 4, 4.2, 4.3, 4.3, 4.2, 4.2, 4.2, 4.2, 4.3, 4.2, 4.2, 4.3, 4.2, - 4.1, 4, 3.9, 4, 4.1, 4, 3.8, 3.7, 3.5, 3.2, 3.1, 2.9, 2.7, 2.5, 2.4, 2.2, 2, 1.9, 1.9, - 2, 2.2, 2.2, 2.4, 2.6, 2.8, 3.1, 3.7, 4.4, 5, 5.5, 5.8, 6, 6.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.6, 13.6, 13.8, 14.1, 14, 13.4, - 12.9, 12.8, 12.7, 12.8, 12.8, 12.7, 12.7, 12.8, 12.8, 12.8, 12.8, 12.8, 13, 13.1, 13, - 13, 13, 13, 13, 13, 12.8, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, - 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.6, 12.8, 12.9, 13, 13.1, 13.1, - 13.2, 13.2, 13.3, 13.4, 13.4, 13.4, 13.4, 13.5, 13.5, 13.5, 13.5, 13.4, 13.5, 13.5, - 13.5, 13.5, 13.4, 13.4, 13.4, 13.3, 13.1, 12.9, 12.7, 12.4, 12.3, 12.2, 12.1, 12.1, - 12, 12.2, 12.4, 12.6, 12.8, 12.9, 12.8, 12.5, 12.2, 12, 11.7, 11.3, 11, 10.7, 10.5, - 10.3, 10.1, 10.1, 10, 10, 9.9, 9.8, 9.9, 10, 10.1, 10.1, 10, 10.2, 10.4, 10.4, 10.5, - 10.5, 10.4, 10.2, 10.1, 10.1, 9.9, 9.8, 9.6, 9.4, 9.2, 9, 8.7, 8.5, 8.3, 7.9, 7.6, - 7.2, 6.9, 6.4, 6.2, 6, 5.7, 5.4, 5.3, 5.2, 5.1, 5, 4.7, 4.6, 4.6, 4.5, 4.3, 4.3, 4.2, - 4, 4.1, 4.2, 4.2, 4.2, 4.1, 4, 3.9, 4, 3.9, 4, 3.9, 3.9, 4, 3.8, 3.6, 3.7, 3.7, 3.4, - 3.3, 3.2, 3.1, 3, 2.8, 2.7, 2.5, 2.4, 2.3, 2.2, 2.1, 2.1, 2.1, 2.2, 2.3, 2.4, 2.6, - 2.9, 3.2, 3.5, 4, 4.6, 5.2, 5.7, 6, 6.2, 6.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.6, 13.7, 14, 14.1, 13.5, 13, 12.8, 12.8, 12.7, - 12.8, 12.8, 12.8, 12.9, 12.9, 12.9, 12.9, 12.9, 13, 13.1, 13.1, 13.1, 13.1, 13, 12.9, - 12.9, 12.9, 12.8, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, - 12.7, 12.7, 12.7, 12.7, 12.8, 12.9, 13.1, 13.2, 13.4, 13.4, 13.4, 13.5, 13.5, 13.5, - 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.6, 13.6, 13.6, - 13.5, 13.5, 13.5, 13.5, 13.5, 13.4, 13.3, 13.1, 12.9, 12.6, 12.3, 12.2, 12.2, 12.3, - 12.5, 12.7, 12.8, 12.8, 12.6, 12.4, 12.1, 11.8, 11.6, 11.4, 11.1, 10.9, 10.7, 10.5, - 10.3, 10.2, 10.2, 10.2, 10.1, 10.3, 10.5, 10.6, 10.7, 10.7, 10.7, 10.8, 10.8, 10.9, - 11, 11, 10.8, 10.6, 10.4, 10.3, 10.1, 10, 9.8, 9.6, 9.3, 9.1, 8.9, 8.6, 8.4, 8.1, 7.8, - 7.4, 7, 6.6, 6.3, 6, 5.8, 5.5, 5.4, 5.3, 5.2, 5.1, 4.9, 4.8, 4.7, 4.7, 4.6, 4.5, 4.3, - 4.1, 4, 4, 4.1, 4.1, 4, 4, 4, 4, 4, 4.1, 4, 3.9, 4.1, 3.9, 3.6, 3.5, 3.4, 3.1, 2.8, - 2.8, 2.8, 2.8, 2.7, 2.6, 2.5, 2.4, 2.3, 2.2, 2.2, 2.3, 2.3, 2.3, 2.4, 2.7, 2.9, 3.2, - 3.5, 3.9, 4.4, 4.9, 5.3, 5.9, 6.3, 6.5, 6.6, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.6, 13.9, 14.2, 13.8, 13.2, 12.9, 12.9, 12.9, - 13, 12.9, 12.9, 12.9, 12.9, 12.9, 12.9, 13, 13.1, 13, 13, 13.1, 13, 13.1, 12.9, 12.9, - 12.9, 12.9, 12.8, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.7, 12.8, 12.8, - 12.9, 13, 13.1, 13.2, 13.4, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, - 13.5, 13.5, 13.5, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.5, - 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.4, 13.3, 13, 12.8, 12.6, 12.6, 12.6, - 12.8, 12.8, 12.8, 12.6, 12.3, 12, 11.6, 11.5, 11.4, 11.3, 11.1, 10.9, 10.8, 10.5, - 10.5, 10.4, 10.4, 10.6, 10.8, 11.2, 11.3, 11.5, 11.5, 11.6, 11.6, 11.5, 11.5, 11.4, - 11.4, 11.2, 11, 10.7, 10.6, 10.4, 10.2, 10, 9.9, 9.5, 9.2, 9, 8.8, 8.5, 8.2, 7.9, 7.6, - 7.2, 6.8, 6.4, 6.1, 5.9, 5.7, 5.5, 5.4, 5.4, 5.2, 5.1, 5, 4.9, 4.8, 4.7, 4.5, 4.3, - 4.2, 4.1, 4.1, 4.1, 4, 4, 4.2, 4.2, 4.2, 4.2, 4.2, 4, 3.8, 4, 3.7, 3.5, 3.4, 3.4, 3.2, - 2.9, 2.7, 2.7, 2.6, 2.6, 2.6, 2.5, 2.5, 2.4, 2.3, 2.4, 2.5, 2.4, 2.4, 2.6, 3, 3.3, - 3.7, 3.9, 4.2, 4.7, 5.2, 5.6, 6.1, 6.6, 6.8, 7, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.6, 14, 14.2, 14, 13.5, 13.2, 13.1, 13.1, - 13.2, 13.1, 13, 13, 13, 13, 13, 13.2, 13.2, 13.1, 13, 13, 13.1, 13.1, 12.9, 12.9, - 12.9, 12.9, 12.8, 12.8, 12.7, 12.7, 12.7, 12.8, 12.8, 12.8, 12.9, 13.1, 13.2, 13.2, - 13.3, 13.4, 13.4, 13.4, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.6, 13.6, 13.6, - 13.7, 13.7, 13.8, 13.8, 13.8, 13.8, 13.9, 13.9, 13.8, 13.8, 13.8, 13.7, 13.7, 13.6, - 13.6, 13.6, 13.6, 13.6, 13.5, 13.5, 13.5, 13.5, 13.5, 13.4, 13.3, 13.2, 13, 12.8, - 12.9, 12.9, 12.8, 12.7, 12.4, 11.9, 11.5, 11.4, 11.4, 11.3, 11.2, 11.1, 10.9, 10.7, - 10.7, 10.7, 10.8, 11.2, 11.4, 11.7, 12, 12.3, 12.3, 12.4, 12.4, 12.2, 11.9, 11.8, - 11.7, 11.6, 11.4, 11.1, 10.8, 10.6, 10.4, 10.2, 10, 9.8, 9.5, 9.2, 8.9, 8.5, 8.3, 8, - 7.8, 7.5, 7, 6.6, 6.2, 6, 5.8, 5.7, 5.6, 5.5, 5.3, 5.2, 5.2, 5.1, 4.9, 4.6, 4.5, 4.3, - 4.2, 4.2, 4.2, 4.2, 3.9, 4.1, 4.2, 4.2, 4.2, 4.2, 4.1, 3.8, 3.7, 3.7, 3.7, 3.8, 3.7, - 3.7, 3.5, 3.1, 2.9, 2.8, 2.6, 2.5, 2.5, 2.5, 2.5, 2.5, 2.6, 2.5, 2.5, 2.6, 2.7, 3, - 3.4, 3.8, 4.2, 4.5, 4.8, 5.2, 5.6, 6, 6.6, 7, 7.2, 7.2, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.6, 13.8, 14.2, 14.2, 14, 13.7, - 13.3, 13.2, 13.2, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.2, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 12.9, 12.9, 12.8, 12.8, 12.8, 12.8, 12.9, 13.1, 13.2, - 13.4, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.7, - 13.7, 13.8, 13.9, 13.9, 13.9, 14, 14, 14.1, 14.1, 14.1, 14.1, 14.1, 14.1, 14, 14, 14, - 13.9, 13.9, 13.9, 13.9, 13.9, 13.8, 13.7, 13.6, 13.6, 13.5, 13.5, 13.5, 13.5, 13.5, - 13.4, 13.3, 13.2, 13.1, 13, 12.8, 12.3, 11.9, 11.5, 11.5, 11.5, 11.4, 11.3, 11.3, - 11.1, 10.9, 11, 11.1, 11.4, 11.8, 12, 12.3, 12.5, 12.8, 12.9, 12.9, 12.9, 12.6, 12.3, - 12.2, 12.1, 12, 11.7, 11.4, 11.2, 10.9, 10.6, 10.4, 10.2, 10, 9.7, 9.4, 9.1, 8.6, 8.4, - 8.1, 7.9, 7.6, 7.3, 6.9, 6.4, 6.1, 5.9, 5.8, 5.7, 5.5, 5.4, 5.2, 5, 5, 4.8, 4.6, 4.5, - 4.3, 4.3, 4.3, 4.3, 4.2, 4, 4.2, 4.2, 4.2, 4.2, 4.2, 4.1, 3.9, 3.8, 3.7, 3.9, 4.1, - 3.8, 3.9, 3.6, 3.1, 2.9, 2.7, 2.6, 2.7, 2.7, 2.5, 2.5, 2.6, 2.7, 2.7, 2.7, 2.9, 3.1, - 3.5, 3.8, 4.3, 4.7, 5.1, 5.4, 5.8, 6.2, 6.6, 7, 7.4, 7.4, 7.4, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.7, 13.8, 14.1, 14.3, 14.3, - 14.1, 13.7, 13.4, 13.3, 13.3, 13.2, 13.2, 13.2, 13.2, 13.3, 13.3, 13.3, 13.3, 13.2, - 13.2, 13.2, 13.2, 13.1, 13.3, 13.2, 13, 12.9, 12.8, 12.8, 12.9, 13.1, 13.3, 13.4, - 13.5, 13.5, 13.6, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.6, 13.7, 13.8, 13.9, - 14, 14.1, 14.1, 14.1, 14, 14, 13.9, 13.9, 13.9, 14, 14, 14, 13.9, 13.9, 14, 14, 14, - 13.9, 14, 14, 14.1, 14.1, 14.1, 14, 13.9, 13.8, 13.7, 13.6, 13.6, 13.5, 13.5, 13.5, - 13.5, 13.5, 13.4, 13.3, 13, 12.6, 12.2, 11.8, 11.6, 11.5, 11.5, 11.4, 11.3, 11.2, - 11.1, 11.3, 11.5, 11.9, 12.3, 12.5, 12.8, 13.1, 13.1, 13.3, 13.3, 13.2, 13, 12.7, - 12.6, 12.4, 12.3, 12, 11.7, 11.5, 11.2, 10.8, 10.6, 10.4, 10.2, 10, 9.7, 9.5, 8.8, - 8.5, 8.3, 8, 7.8, 7.5, 7.1, 6.8, 6.4, 6.1, 5.9, 5.8, 5.6, 5.4, 5.2, 5, 4.9, 4.8, 4.7, - 4.5, 4.4, 4.5, 4.6, 4.4, 4.2, 4.1, 4.3, 4.3, 4.2, 4.1, 4.1, 4.1, 4.1, 4.1, 3.9, 4, - 3.9, 3.7, 3.6, 3.4, 3.1, 2.8, 2.6, 2.7, 2.8, 2.8, 2.7, 2.8, 2.9, 2.9, 3, 3.1, 3.3, - 3.6, 4, 4.4, 4.8, 5.2, 5.7, 6.1, 6.5, 6.8, 7.1, 7.4, 7.5, 7.6, 7.6, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.6, 13.7, 13.9, 14.3, - 14.4, 14.3, 14.1, 13.7, 13.5, 13.4, 13.3, 13.3, 13.4, 13.5, 13.5, 13.6, 13.5, 13.5, - 13.4, 13.4, 13.4, 13.3, 13.3, 13.2, 13.2, 13, 13, 13.1, 13.2, 13.4, 13.5, 13.5, 13.5, - 13.5, 13.5, 13.6, 13.5, 13.5, 13.5, 13.6, 13.7, 13.8, 13.9, 14, 14.1, 14.1, 14.1, - 14.1, 14.1, 14, 13.9, 13.7, 13.5, 13.4, 13.3, 13.3, 13.3, 13.3, 13.3, 13.3, 13.4, - 13.5, 13.5, 13.5, 13.5, 13.6, 13.8, 13.9, 14, 14.1, 14.1, 14.1, 14, 14, 13.8, 13.7, - 13.6, 13.6, 13.5, 13.5, 13.5, 13.5, 13.5, 13.4, 13.2, 12.8, 12.3, 11.9, 11.6, 11.5, - 11.4, 11.4, 11.4, 11.4, 11.6, 12, 12.3, 12.6, 12.8, 13.1, 13.2, 13.3, 13.4, 13.5, - 13.4, 13.3, 13.3, 12.9, 12.6, 12.4, 12.3, 12, 11.8, 11.5, 11.2, 10.9, 10.7, 10.5, - 10.3, 10.3, 10.1, 9.2, 8.7, 8.5, 8.2, 7.9, 7.7, 7.6, 7.3, 6.8, 6.3, 6, 5.9, 5.7, 5.5, - 5.2, 5.1, 5, 4.8, 4.6, 4.5, 4.5, 4.6, 4.6, 4.4, 4.1, 4.1, 4.4, 4.4, 4.2, 4.1, 4.1, - 4.1, 4.2, 4.1, 4, 4, 3.9, 3.6, 3.4, 3.2, 3, 2.8, 3, 3.1, 3, 3, 2.9, 3, 3.1, 3.2, 3.3, - 3.5, 3.8, 4.2, 4.6, 4.9, 5.3, 5.7, 6.3, 6.7, 6.9, 7.2, 7.4, 7.6, 7.7, 7.8, 7.8, 13.2, - 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.6, 13.7, - 13.9, 14.3, 14.5, 14.4, 14.2, 14, 13.8, 13.6, 13.5, 13.5, 13.5, 13.6, 13.7, 13.7, - 13.7, 13.6, 13.6, 13.6, 13.5, 13.4, 13.4, 13.3, 13.3, 13.3, 13.3, 13.4, 13.5, 13.5, - 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.6, 13.7, 13.8, 13.9, 14, 14.1, 14.1, 14.1, 14, - 13.9, 13.7, 13.6, 13.5, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.3, 13.4, 13.6, 13.8, 13.9, 14, - 14.1, 14.1, 14, 13.9, 13.8, 13.6, 13.6, 13.5, 13.5, 13.5, 13.5, 13.4, 13.3, 13, 12.6, - 12, 11.6, 11.5, 11.6, 11.7, 11.7, 12.1, 12.6, 12.7, 12.8, 13, 13.2, 13.2, 13.4, 13.4, - 13.5, 13.4, 13.5, 13.5, 13.1, 12.8, 12.7, 12.5, 12.3, 12.1, 11.9, 11.7, 11.4, 11.1, - 10.9, 10.7, 10.6, 10.2, 9.4, 8.9, 8.7, 8.4, 8.2, 7.9, 7.7, 7.5, 7, 6.5, 6.2, 5.9, 5.7, - 5.5, 5.2, 5.1, 4.9, 4.7, 4.5, 4.5, 4.5, 4.5, 4.3, 4.1, 4.1, 4.3, 4.5, 4.3, 4.2, 4.2, - 4.2, 4.2, 4.3, 4.2, 4.1, 4.1, 4, 3.7, 3.5, 3.3, 3.2, 3.2, 3.2, 3.2, 3.1, 3.1, 3.2, - 3.3, 3.4, 3.5, 3.6, 3.8, 4.2, 4.6, 5, 5.4, 5.8, 6.3, 6.8, 7, 7.3, 7.4, 7.6, 7.8, 8, - 8.2, 8.2, 13.4, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.2, 13.6, 13.7, 13.9, 14.3, 14.6, 14.6, 14.4, 14.2, 14.1, 13.9, 13.7, 13.6, 13.6, - 13.7, 13.8, 13.8, 13.8, 13.8, 13.8, 13.9, 13.7, 13.6, 13.5, 13.4, 13.4, 13.4, 13.5, - 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.7, 13.8, 14, 14.1, 14.1, 14.1, 14.1, 14, - 13.9, 13.6, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, - 13.2, 13.4, 13.6, 13.8, 14, 14.1, 14.1, 14.1, 13.9, 13.8, 13.7, 13.6, 13.5, 13.5, - 13.5, 13.4, 13.4, 13.2, 12.8, 12.2, 11.8, 11.8, 11.9, 12.1, 12.7, 13.1, 13.2, 13.3, - 13.4, 13.6, 13.6, 13.6, 13.6, 13.6, 13.5, 13.4, 13.3, 13.2, 13, 12.8, 12.7, 12.6, - 12.5, 12.3, 12.2, 11.9, 11.5, 11.2, 10.9, 10.6, 10.1, 9.6, 9.2, 8.9, 8.6, 8.3, 7.9, - 7.7, 7.5, 7.1, 6.7, 6.3, 6, 5.8, 5.5, 5.3, 5.1, 4.9, 4.7, 4.5, 4.5, 4.5, 4.3, 4.1, - 4.2, 4.4, 4.5, 4.5, 4.4, 4.4, 4.4, 4.3, 4.3, 4.4, 4.4, 4.3, 4.1, 4.1, 3.9, 3.6, 3.4, - 3.3, 3.3, 3.3, 3.3, 3.3, 3.3, 3.4, 3.6, 3.7, 3.8, 3.9, 4.2, 4.6, 5, 5.5, 5.9, 6.4, - 6.9, 7.3, 7.6, 7.7, 7.8, 8, 8.1, 8.3, 8.4, 8.6, 13.4, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.5, 13.7, 13.9, 14.3, 14.6, 14.7, 14.6, - 14.4, 14.3, 14.1, 13.9, 13.8, 13.7, 13.8, 13.8, 13.8, 13.9, 14, 14.1, 14.1, 14, 13.8, - 13.7, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.6, 13.6, 13.7, 13.8, 13.9, 14.1, 14.1, - 14.1, 14, 13.9, 13.8, 13.6, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.7, 13.9, 14, - 14.1, 14.1, 14, 13.8, 13.6, 13.5, 13.5, 13.5, 13.4, 13.4, 13.3, 13, 12.6, 12.3, 12.2, - 12.5, 13.1, 13.5, 13.7, 13.9, 14, 14.1, 14.1, 14.1, 14, 13.8, 13.7, 13.5, 13.3, 13.1, - 13, 12.9, 12.9, 12.9, 12.7, 12.5, 12.3, 12.1, 11.8, 11.5, 11.1, 10.8, 10.2, 9.7, 9.4, - 9.1, 8.8, 8.5, 8.1, 7.8, 7.5, 7.2, 6.8, 6.6, 6.2, 5.9, 5.6, 5.3, 5.1, 4.9, 4.7, 4.6, - 4.6, 4.4, 4.3, 4.3, 4.4, 4.5, 4.6, 4.6, 4.7, 4.7, 4.8, 4.7, 4.6, 4.7, 4.7, 4.7, 4.5, - 4.2, 4.1, 3.8, 3.6, 3.5, 3.4, 3.4, 3.4, 3.4, 3.5, 3.7, 3.8, 3.9, 4.1, 4.2, 4.6, 4.9, - 5.4, 5.9, 6.4, 6.9, 7.3, 7.7, 8, 8, 8.1, 8.2, 8.4, 8.5, 8.7, 8.9, 13.2, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.7, 13.9, 14.3, - 14.7, 14.8, 14.7, 14.5, 14.3, 14.1, 14, 14, 13.9, 13.9, 13.9, 13.9, 14, 14.1, 14.2, - 14.3, 14.1, 14, 13.8, 13.7, 13.6, 13.6, 13.6, 13.7, 13.8, 13.9, 14, 14.1, 14.1, 14.2, - 14.1, 13.9, 13.6, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, - 13.3, 13.6, 13.8, 14, 14.1, 14, 13.9, 13.8, 13.6, 13.5, 13.5, 13.4, 13.4, 13.4, 13.2, - 12.9, 12.6, 12.7, 13.3, 13.7, 14, 14.2, 14.4, 14.4, 14.4, 14.4, 14.3, 14.1, 13.9, - 13.7, 13.5, 13.1, 13, 13, 13, 12.9, 12.7, 12.6, 12.4, 12.1, 11.9, 11.8, 11.5, 11.1, - 10.5, 10, 9.6, 9.3, 8.9, 8.6, 8.3, 8, 7.7, 7.4, 7, 6.7, 6.3, 6, 5.7, 5.4, 5.2, 5, 4.8, - 4.7, 4.5, 4.4, 4.4, 4.5, 4.6, 4.7, 4.9, 5, 5.1, 5.2, 5.2, 5.3, 5.3, 5.2, 5.2, 5.1, - 4.9, 4.6, 4.4, 4.1, 3.8, 3.6, 3.6, 3.6, 3.5, 3.6, 3.8, 3.9, 4, 4.2, 4.3, 4.4, 4.8, - 5.2, 5.6, 6.4, 7, 7.4, 7.7, 8, 8.2, 8.3, 8.3, 8.4, 8.6, 8.7, 8.9, 9, 13.2, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.7, 13.9, 14.3, - 14.7, 14.8, 14.8, 14.6, 14.4, 14.1, 13.9, 13.9, 13.9, 14, 14, 14, 14.1, 14.2, 14.2, - 14.2, 14.2, 14.1, 14, 13.9, 13.8, 13.9, 13.9, 14, 14.1, 14.2, 14.2, 14.1, 14, 13.8, - 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.4, 13.7, 14, 14.1, 14.1, 14, 13.7, 13.5, 13.5, 13.5, 13.5, 13.4, - 13.4, 13.2, 13, 13.3, 13.6, 14, 14.3, 14.4, 14.5, 14.5, 14.4, 14.3, 14.2, 13.9, 13.7, - 13.5, 13.2, 13, 13, 12.9, 12.7, 12.5, 12.3, 12.2, 11.9, 11.9, 11.9, 11.6, 11.2, 10.9, - 10.5, 10, 9.5, 9.1, 8.8, 8.4, 8.1, 7.9, 7.6, 7.2, 6.8, 6.4, 6.1, 5.8, 5.6, 5.3, 5.1, - 4.8, 4.6, 4.3, 4.3, 4.5, 4.7, 4.8, 5.1, 5.3, 5.5, 5.7, 5.7, 5.7, 5.8, 5.8, 5.8, 5.7, - 5.6, 5.4, 5.1, 4.8, 4.5, 4.1, 3.9, 3.9, 3.8, 3.7, 3.9, 4, 4.2, 4.3, 4.4, 4.5, 4.6, 5, - 5.4, 5.9, 6.8, 7.3, 7.7, 8.1, 8.3, 8.5, 8.6, 8.6, 8.6, 8.6, 8.8, 9, 9, 13.5, 13.4, - 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.6, 13.9, - 14.3, 14.7, 14.9, 14.9, 14.7, 14.4, 14.1, 14, 13.9, 13.8, 13.9, 13.9, 13.9, 14, 14.1, - 14.2, 14.2, 14.2, 14.2, 14.2, 14.1, 14.1, 14.1, 14.1, 14.1, 14.1, 14, 13.9, 13.6, - 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.7, 13.9, 14, 13.9, 13.7, 13.6, - 13.5, 13.5, 13.5, 13.4, 13.4, 13.3, 13.2, 13.4, 13.7, 13.9, 14.1, 14.3, 14.3, 14.2, - 14.1, 14, 13.7, 13.4, 13.2, 13.1, 12.9, 12.9, 12.8, 12.6, 12.3, 12, 11.8, 11.6, 11.7, - 11.5, 11.3, 11.1, 11, 10.8, 10.4, 9.7, 9.4, 8.9, 8.6, 8.3, 7.9, 7.6, 7.2, 6.9, 6.5, - 6.1, 5.8, 5.6, 5.3, 5.1, 4.9, 4.6, 4.4, 4.4, 4.6, 4.9, 5.1, 5.5, 5.8, 6, 6.1, 6.2, - 6.2, 6.2, 6.2, 6.2, 6.1, 6, 5.7, 5.5, 5.1, 4.8, 4.4, 4.2, 4.1, 3.9, 4, 4.2, 4.3, 4.4, - 4.5, 4.6, 4.7, 4.8, 5.2, 5.6, 6.2, 7, 7.5, 8, 8.4, 8.5, 8.6, 8.7, 8.6, 8.6, 8.6, 8.8, - 9, 9.1, 13.6, 13.6, 13.6, 13.4, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.3, 13.6, 13.9, 14.3, 14.7, 14.9, 14.9, 14.7, 14.5, 14.2, 14, 13.9, 13.8, 13.8, - 13.7, 13.7, 13.8, 13.9, 14, 14.1, 14.2, 14.2, 14.2, 14.1, 14.1, 14, 13.9, 13.8, 13.7, - 13.7, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.3, 13.7, - 13.9, 13.9, 13.8, 13.7, 13.6, 13.5, 13.4, 13.4, 13.4, 13.3, 13.4, 13.6, 13.8, 14, - 14.1, 14.2, 14.1, 13.8, 13.6, 13.3, 13, 12.8, 12.8, 12.7, 12.5, 12.5, 12.3, 12, 11.7, - 11.5, 11.3, 11.2, 11.1, 11, 11, 10.9, 10.8, 10.5, 9.9, 9.5, 9.1, 8.7, 8.4, 8, 7.6, - 7.2, 6.9, 6.6, 6.2, 5.9, 5.6, 5.4, 5.2, 5, 4.8, 4.6, 4.5, 4.8, 5, 5.4, 5.8, 6.2, 6.3, - 6.4, 6.5, 6.6, 6.6, 6.6, 6.6, 6.5, 6.3, 6, 5.8, 5.4, 5, 4.7, 4.5, 4.2, 4.2, 4.4, 4.5, - 4.6, 4.7, 4.7, 4.8, 4.9, 5.1, 5.4, 5.9, 6.6, 7.2, 7.7, 8.1, 8.4, 8.5, 8.6, 8.5, 8.4, - 8.4, 8.6, 8.8, 9, 9.1, 13.8, 13.8, 13.7, 13.5, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.3, 13.6, 13.8, 14.2, 14.7, 14.8, 14.8, 14.7, 14.5, 14.2, 14.1, - 14, 13.9, 13.7, 13.6, 13.6, 13.7, 13.8, 13.9, 14, 14.1, 14.1, 14.1, 14, 14, 13.9, - 13.8, 13.7, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.4, 13.7, 13.9, 13.9, 13.8, 13.7, 13.5, 13.4, 13.4, 13.4, 13.4, - 13.7, 13.9, 14.1, 14.3, 14.2, 14, 13.7, 13.3, 13, 12.7, 12.5, 12.5, 12.4, 12.2, 12.1, - 11.9, 11.7, 11.5, 11.3, 11.2, 11, 10.9, 10.8, 10.8, 10.7, 10.6, 10.5, 10.1, 9.7, 9.2, - 8.8, 8.5, 8.1, 7.7, 7.4, 7, 6.7, 6.4, 6, 5.7, 5.5, 5.4, 5.2, 4.9, 4.6, 4.6, 5, 5.4, - 5.8, 6.2, 6.4, 6.6, 6.8, 6.9, 6.9, 6.9, 6.9, 6.8, 6.7, 6.5, 6.3, 6.1, 5.7, 5.3, 5, - 4.7, 4.4, 4.5, 4.7, 4.8, 4.9, 4.9, 5, 5.1, 5.2, 5.5, 5.7, 6.1, 6.9, 7.3, 7.8, 8.1, - 8.3, 8.4, 8.3, 8.2, 8.2, 8.3, 8.5, 8.8, 9, 9, 13.9, 13.8, 13.6, 13.5, 13.5, 13.2, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.6, 13.9, 14.3, 14.6, 14.7, 14.7, - 14.7, 14.6, 14.2, 14, 13.9, 13.9, 13.6, 13.5, 13.5, 13.6, 13.6, 13.7, 13.8, 13.9, 14, - 14, 13.9, 13.8, 13.8, 13.6, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.9, 14, 13.9, 13.8, - 13.6, 13.5, 13.5, 13.6, 13.8, 14.1, 14.4, 14.4, 14.4, 14.2, 13.7, 13.2, 13, 12.7, - 12.4, 12.3, 12.2, 12, 11.9, 11.7, 11.6, 11.4, 11.3, 11.1, 11, 10.8, 10.7, 10.6, 10.5, - 10.5, 10.4, 10.2, 9.8, 9.4, 9, 8.6, 8.3, 8, 7.6, 7.2, 6.8, 6.5, 6.1, 5.9, 5.6, 5.4, - 5.2, 5, 4.6, 4.5, 5.1, 5.7, 6.1, 6.5, 6.6, 6.8, 7, 7.1, 7.1, 7.2, 7.1, 7, 6.9, 6.7, - 6.5, 6.3, 5.9, 5.5, 5.2, 4.8, 4.7, 4.8, 4.9, 5, 5.1, 5.2, 5.2, 5.4, 5.5, 5.7, 5.9, - 6.3, 6.8, 7.4, 7.8, 8.1, 8.2, 8.2, 8.1, 8, 8, 8.1, 8.4, 8.7, 8.8, 8.8, 14, 13.9, 13.7, - 13.6, 13.4, 13.1, 13.2, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.6, 14.1, 14.5, - 14.6, 14.6, 14.6, 14.6, 14.4, 14.1, 13.9, 13.9, 13.8, 13.5, 13.3, 13.4, 13.5, 13.5, - 13.5, 13.6, 13.7, 13.8, 13.8, 13.7, 13.6, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, - 13.5, 13.8, 14, 14, 13.8, 13.7, 13.6, 13.7, 13.9, 14.3, 14.6, 14.7, 14.6, 14.4, 13.8, - 13.1, 12.9, 12.6, 12.3, 12.2, 12.1, 11.9, 11.7, 11.6, 11.5, 11.4, 11.2, 11.1, 10.9, - 10.9, 10.7, 10.5, 10.4, 10.4, 10.3, 10.2, 9.9, 9.6, 9.1, 8.7, 8.4, 8.1, 7.9, 7.5, 7.1, - 6.6, 6.3, 6, 5.7, 5.5, 5.2, 4.8, 4.6, 4.5, 5.3, 5.9, 6.2, 6.6, 6.7, 6.9, 7.1, 7.2, - 7.3, 7.3, 7.1, 7, 6.9, 6.7, 6.6, 6.4, 6.1, 5.7, 5.3, 5, 5, 5.1, 5.1, 5.2, 5.3, 5.4, - 5.5, 5.6, 5.8, 5.9, 6.2, 6.6, 7.1, 7.6, 8, 8.1, 8.1, 8, 7.9, 7.8, 7.9, 8, 8.3, 8.5, - 8.6, 8.6, 14.1, 13.9, 13.7, 13.6, 13.5, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.2, 13.5, 14, 14.3, 14.4, 14.5, 14.5, 14.4, 14.2, 14, 13.9, 13.8, 13.6, 13.4, - 13.2, 13.1, 13.2, 13.2, 13.2, 13.3, 13.4, 13.5, 13.5, 13.5, 13.4, 13.2, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.8, 14, 14, 13.9, 13.8, 13.9, 14.2, 14.5, 14.7, - 14.9, 14.8, 14.6, 14.1, 13.3, 12.8, 12.5, 12.4, 12.2, 11.9, 11.7, 11.5, 11.6, 11.5, - 11.3, 11.2, 11, 10.9, 10.8, 10.6, 10.4, 10.3, 10.2, 10.2, 10.2, 10, 9.7, 9.3, 8.9, - 8.6, 8.3, 8, 7.6, 7.2, 6.8, 6.5, 6.2, 5.8, 5.6, 5.2, 4.8, 4.7, 4.5, 5.4, 6, 6.3, 6.6, - 6.8, 7, 7.1, 7.2, 7.3, 7.3, 7.2, 7.1, 7, 6.9, 6.6, 6.4, 6.1, 5.8, 5.3, 5.2, 5.2, 5.3, - 5.4, 5.5, 5.6, 5.6, 5.7, 5.9, 6, 6.3, 6.6, 7, 7.5, 8, 8.1, 8.2, 8, 7.9, 7.8, 7.8, 7.8, - 7.9, 8.1, 8.3, 8.3, 8.2, 14.4, 14.1, 13.8, 13.6, 13.6, 13.6, 13.2, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.6, 14, 14.2, 14.3, 14.3, 14.2, 14, 13.9, 13.8, 13.7, - 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.7, 13.8, 14, 14.1, 14, 14.1, 14.3, - 14.6, 14.8, 14.9, 15, 14.9, 14.5, 13.6, 12.9, 12.6, 12.4, 12.2, 11.9, 11.7, 11.5, - 11.5, 11.4, 11.3, 11.2, 11.1, 10.8, 10.7, 10.5, 10.3, 10.3, 10.2, 10.2, 10.2, 10.1, - 9.8, 9.5, 9.1, 8.7, 8.4, 8.1, 7.7, 7.3, 7, 6.6, 6.4, 6, 5.7, 5.4, 5, 4.7, 4.6, 5.5, - 6.2, 6.5, 6.8, 7.1, 7.2, 7.2, 7.3, 7.3, 7.4, 7.3, 7.2, 7.1, 7, 6.7, 6.5, 6.3, 5.9, - 5.6, 5.5, 5.5, 5.5, 5.6, 5.7, 5.8, 5.9, 6, 6.2, 6.4, 6.6, 7, 7.5, 7.9, 8.2, 8.2, 8.1, - 7.9, 7.8, 7.7, 7.7, 7.7, 7.8, 7.9, 8, 8, 7.9, 14.6, 14.3, 14, 13.7, 13.6, 13.5, 13.2, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.9, 14, 13.9, 13.9, - 13.9, 13.8, 13.7, 13.5, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.5, 13.7, - 13.9, 14.2, 14.2, 14.3, 14.5, 14.7, 14.9, 15.1, 15.1, 15, 14.7, 14, 13.1, 12.7, 12.4, - 12.2, 11.9, 11.7, 11.5, 11.5, 11.4, 11.2, 11.2, 11.1, 10.8, 10.6, 10.4, 10.3, 10.2, - 10.2, 10.1, 10.1, 10, 9.9, 9.5, 9.2, 8.9, 8.6, 8.2, 7.8, 7.4, 7.1, 6.7, 6.4, 6.1, 5.8, - 5.4, 4.8, 4.6, 4.8, 5.8, 6.4, 6.7, 7.1, 7.3, 7.4, 7.4, 7.4, 7.5, 7.5, 7.5, 7.3, 7.2, - 7.1, 6.9, 6.6, 6.3, 6, 5.8, 5.7, 5.8, 5.8, 5.8, 5.9, 6, 6.2, 6.3, 6.6, 6.8, 7, 7.3, - 7.6, 7.9, 8.1, 8, 7.9, 7.8, 7.7, 7.6, 7.6, 7.6, 7.7, 7.8, 7.8, 7.8, 7.8, 14.8, 14.4, - 14.1, 13.8, 13.6, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.3, 13.5, 13.6, 13.7, 13.6, 13.7, 13.6, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.3, 13.3, 13.5, 13.9, 14.1, 14.3, 14.3, 14.5, 14.7, 15, 15.2, 15.2, - 15.1, 14.8, 14.2, 13.4, 12.8, 12.4, 12.2, 12, 11.7, 11.5, 11.4, 11.3, 11.2, 11.1, 11, - 10.8, 10.6, 10.4, 10.3, 10.2, 10.2, 10.1, 10, 9.9, 9.9, 9.5, 9.2, 8.9, 8.7, 8.4, 8, - 7.7, 7.3, 6.9, 6.5, 6.2, 5.7, 5, 4.6, 4.6, 5.2, 6.2, 6.6, 6.8, 7.2, 7.3, 7.4, 7.5, - 7.6, 7.7, 7.6, 7.6, 7.5, 7.4, 7.2, 7, 6.7, 6.4, 6.2, 6, 5.9, 5.9, 5.9, 6, 6.1, 6.3, - 6.5, 6.9, 7.1, 7.3, 7.4, 7.6, 7.7, 7.8, 7.9, 7.8, 7.7, 7.7, 7.6, 7.6, 7.6, 7.6, 7.6, - 7.7, 7.8, 7.8, 7.8, 14.9, 14.6, 14.2, 13.9, 13.7, 13.6, 13.4, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.3, 13.3, 13.3, 13.4, 13.4, 13.2, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.7, 14.1, 14.3, 14.4, - 14.5, 14.7, 15, 15.2, 15.4, 15.3, 14.9, 14.3, 13.6, 13, 12.5, 12.1, 11.9, 11.7, 11.6, - 11.4, 11.3, 11.2, 11.1, 11.1, 10.8, 10.6, 10.5, 10.4, 10.3, 10.1, 10, 9.9, 9.8, 9.7, - 9.3, 9.1, 9, 8.8, 8.5, 8.2, 7.8, 7.4, 7, 6.7, 6.4, 5.7, 4.9, 4.7, 4.9, 5.8, 6.5, 6.8, - 7, 7.2, 7.3, 7.5, 7.7, 7.8, 7.9, 7.9, 7.8, 7.6, 7.5, 7.4, 7.1, 6.7, 6.5, 6.4, 6.3, - 6.1, 6, 6.1, 6.2, 6.3, 6.6, 7.1, 7.5, 7.7, 7.8, 7.8, 7.8, 7.7, 7.7, 7.7, 7.7, 7.6, - 7.6, 7.5, 7.5, 7.6, 7.6, 7.6, 7.7, 7.8, 7.8, 7.8, 15.1, 14.7, 14.3, 14, 13.8, 13.6, - 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.5, 13.9, 14.2, 14.4, 14.5, 14.6, 14.9, 15.2, 15.3, 15.3, 15, 14.5, 13.8, - 13.2, 12.7, 12.1, 11.9, 11.6, 11.5, 11.4, 11.4, 11.3, 11.2, 11.2, 11, 10.8, 10.6, - 10.5, 10.3, 10.1, 10, 9.9, 9.8, 9.6, 9.3, 9.1, 9, 8.8, 8.6, 8.2, 7.8, 7.5, 7.2, 6.7, - 6.3, 5.7, 5.2, 5.1, 5.5, 6.2, 6.7, 6.9, 7.1, 7.4, 7.5, 7.7, 7.8, 7.9, 8.1, 8.1, 7.9, - 7.7, 7.6, 7.4, 7.1, 6.9, 6.7, 6.6, 6.4, 6.2, 6.2, 6.3, 6.4, 6.6, 7.1, 7.6, 7.9, 8, 8, - 7.9, 7.8, 7.8, 7.8, 7.7, 7.6, 7.6, 7.6, 7.6, 7.6, 7.6, 7.6, 7.7, 7.8, 7.9, 7.9, 7.8, - 15.3, 14.8, 14.4, 14.1, 13.8, 13.6, 13.5, 13.4, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.6, 13.9, 14.2, 14.4, 14.5, 14.8, - 15.1, 15.2, 15.1, 14.9, 14.5, 13.8, 13.2, 12.7, 12.2, 11.9, 11.6, 11.5, 11.4, 11.4, - 11.5, 11.4, 11.4, 11.3, 11.1, 10.8, 10.6, 10.4, 10.2, 10.1, 10, 9.8, 9.6, 9.4, 9.1, 9, - 8.8, 8.5, 8.2, 7.8, 7.4, 7.1, 6.7, 6.2, 5.7, 5.4, 5.6, 6, 6.4, 6.9, 7.1, 7.3, 7.5, - 7.7, 7.8, 7.9, 8, 8.1, 8.1, 7.9, 7.8, 7.7, 7.5, 7.3, 7.1, 6.9, 6.7, 6.5, 6.4, 6.4, - 6.5, 6.6, 7, 7.4, 7.8, 8, 8, 8, 7.9, 7.9, 7.9, 7.8, 7.8, 7.7, 7.6, 7.6, 7.7, 7.7, 7.7, - 7.7, 7.8, 7.9, 7.9, 7.8, 7.8, 15.4, 14.8, 14.4, 14, 13.7, 13.6, 13.5, 13.4, 13.2, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, - 13.6, 14, 14.3, 14.4, 14.6, 14.8, 14.9, 14.9, 14.8, 14.4, 13.7, 13.2, 12.6, 12.2, 12, - 11.8, 11.5, 11.5, 11.6, 11.7, 11.8, 11.7, 11.6, 11.5, 11.2, 10.8, 10.5, 10.3, 10.2, - 10.1, 9.8, 9.6, 9.4, 9.1, 8.8, 8.6, 8.3, 8, 7.7, 7.3, 7.1, 6.6, 6.3, 5.9, 6, 6.3, 6.5, - 6.8, 7.1, 7.4, 7.6, 7.7, 7.7, 7.9, 8, 8, 8, 8, 7.9, 7.8, 7.7, 7.6, 7.5, 7.2, 7, 6.8, - 6.6, 6.6, 6.6, 6.7, 7, 7.4, 7.7, 8, 8.1, 8, 8, 8, 8, 8, 7.9, 7.9, 7.8, 7.7, 7.7, 7.8, - 7.9, 7.9, 7.8, 7.8, 7.8, 7.8, 7.8, 7.7, 15.5, 14.9, 14.5, 14.1, 13.7, 13.6, 13.5, - 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.2, 13.5, 13.8, 14.3, 14.4, 14.4, 14.6, 14.6, 14.6, 14.5, 14.1, 13.6, 13.2, - 12.7, 12.3, 12.1, 11.9, 11.8, 11.8, 11.9, 12.1, 12.2, 12, 11.9, 11.7, 11.4, 11, 10.7, - 10.4, 10.2, 10, 9.8, 9.5, 9.2, 8.9, 8.6, 8.4, 8.1, 7.8, 7.5, 7.2, 6.9, 6.6, 6.4, 6.2, - 6.5, 6.7, 6.9, 7.1, 7.3, 7.5, 7.7, 7.8, 7.9, 7.9, 8, 8.1, 8.1, 8.1, 8, 7.9, 7.8, 7.7, - 7.6, 7.3, 7.1, 6.9, 6.8, 6.7, 6.7, 6.9, 7.2, 7.5, 7.8, 8, 8, 8.1, 8.2, 8.2, 8.2, 8.1, - 8, 7.9, 7.9, 7.8, 7.8, 7.9, 8, 7.9, 7.8, 7.8, 7.8, 7.7, 7.6, 7.5, 15.6, 15, 14.5, - 14.2, 13.8, 13.6, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.7, 14.2, 14.2, 14.2, 14.4, 14.4, 14.3, - 14.1, 13.8, 13.5, 13, 12.6, 12.3, 12.1, 12, 12, 12.1, 12.1, 12.2, 12.2, 12.1, 12, - 11.8, 11.5, 11.2, 10.8, 10.4, 10.1, 10, 9.8, 9.4, 9, 8.7, 8.4, 8.2, 7.9, 7.7, 7.4, - 7.2, 6.9, 6.6, 6.5, 6.5, 6.8, 7, 7.2, 7.4, 7.7, 7.8, 7.9, 7.9, 8, 8, 8.1, 8.1, 8.1, - 8.1, 8.1, 7.9, 7.9, 7.8, 7.6, 7.4, 7.2, 7, 6.9, 6.9, 7, 7.1, 7.3, 7.5, 7.8, 8, 8.1, - 8.2, 8.5, 8.5, 8.3, 8.2, 8.1, 8, 7.9, 7.9, 7.9, 8, 7.9, 7.8, 7.8, 7.7, 7.7, 7.6, 7.5, - 7.3, 15.6, 15, 14.6, 14.2, 13.8, 13.6, 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.5, 14.2, 14, 13.8, - 13.9, 14, 13.9, 13.7, 13.4, 13.1, 12.8, 12.5, 12.2, 12.2, 12.2, 12.1, 12.1, 12.2, - 12.3, 12.3, 12.2, 12, 11.7, 11.4, 11.1, 10.6, 10.2, 10, 9.8, 9.6, 9.2, 8.9, 8.6, 8.3, - 8, 7.8, 7.6, 7.4, 7.1, 6.9, 6.8, 6.8, 6.9, 7.1, 7.4, 7.5, 7.7, 7.8, 7.9, 7.9, 8, 8.1, - 8.1, 8.1, 8.2, 8.2, 8.2, 8.1, 8, 8, 7.9, 7.7, 7.5, 7.3, 7.2, 7.1, 7.1, 7.2, 7.3, 7.4, - 7.7, 7.9, 8.1, 8.2, 8.5, 8.7, 8.6, 8.4, 8.2, 8.2, 8.1, 8, 8, 7.9, 7.9, 7.9, 7.8, 7.8, - 7.7, 7.6, 7.4, 7.3, 7, 15.6, 15.1, 14.6, 14.3, 13.9, 13.6, 13.5, 13.3, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.4, 14.1, 13.6, 13.5, 13.5, 13.5, 13.4, 13.3, 13.1, 12.8, 12.5, 12.3, 12, 11.9, - 11.9, 11.9, 12.1, 12.2, 12.2, 12.1, 12, 11.7, 11.4, 11.1, 10.7, 10.4, 10, 9.8, 9.6, - 9.4, 9, 8.7, 8.5, 8.2, 7.9, 7.7, 7.5, 7.3, 7.1, 6.8, 6.9, 7, 7, 7.3, 7.6, 7.7, 7.8, - 7.9, 8, 8, 8.1, 8.2, 8.2, 8.2, 8.2, 8.3, 8.3, 8.2, 8.1, 8, 7.9, 7.8, 7.7, 7.5, 7.4, - 7.3, 7.3, 7.3, 7.5, 7.7, 8, 8.2, 8.3, 8.4, 8.6, 8.7, 8.5, 8.3, 8.3, 8.3, 8.3, 8.3, - 8.2, 8.1, 8, 7.9, 7.9, 7.9, 7.7, 7.5, 7.2, 7, 6.8, 15.7, 15.1, 14.6, 14.3, 14, 13.6, - 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.5, 14.1, 13.5, 13.3, 13.1, 13.2, 13.1, 12.9, 12.7, 12.5, - 12.3, 12, 11.7, 11.6, 11.5, 11.6, 11.9, 12, 12, 11.8, 11.6, 11.4, 11.1, 10.8, 10.5, - 10.2, 9.9, 9.6, 9.4, 9.2, 8.8, 8.6, 8.3, 8.1, 7.9, 7.7, 7.6, 7.3, 7.1, 6.9, 7, 7.2, - 7.2, 7.5, 7.8, 8, 8.1, 8.2, 8.1, 8.2, 8.2, 8.2, 8.3, 8.3, 8.3, 8.3, 8.3, 8.2, 8.2, - 8.1, 8, 7.9, 7.8, 7.7, 7.7, 7.6, 7.5, 7.6, 7.7, 7.9, 8.3, 8.4, 8.4, 8.6, 8.7, 8.6, - 8.4, 8.3, 8.3, 8.3, 8.4, 8.4, 8.3, 8.2, 8.1, 8.1, 8.1, 8, 7.8, 7.5, 7.1, 6.9, 6.7, - 15.7, 15, 14.5, 14.2, 13.8, 13.6, 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.9, 14.1, 13.7, 13.1, - 12.8, 12.8, 12.9, 12.6, 12.4, 12.3, 12.2, 11.7, 11.5, 11.4, 11.4, 11.4, 11.6, 11.7, - 11.6, 11.5, 11.2, 11, 10.7, 10.5, 10.3, 10.1, 9.8, 9.5, 9.3, 9, 8.7, 8.5, 8.3, 8.1, - 7.9, 7.7, 7.5, 7.2, 7.2, 7.2, 7.2, 7.4, 7.5, 7.7, 7.9, 8, 8.2, 8.3, 8.3, 8.4, 8.4, - 8.3, 8.3, 8.4, 8.5, 8.4, 8.3, 8.2, 8.2, 8.2, 8.1, 8, 8, 7.9, 7.9, 7.9, 7.9, 7.9, 8, - 8.3, 8.5, 8.6, 8.6, 8.7, 8.6, 8.4, 8.3, 8.3, 8.3, 8.4, 8.5, 8.5, 8.4, 8.3, 8.2, 8.2, - 8.2, 8.1, 7.8, 7.5, 7.1, 6.8, 6.6, 15.8, 15, 14.5, 14.1, 13.8, 13.6, 13.4, 13.2, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, - 13.6, 14, 14.2, 13.8, 13.4, 12.8, 12.5, 12.4, 12.3, 12.1, 12, 11.8, 11.4, 11.3, 11.2, - 11.2, 11.1, 11.2, 11.3, 11.2, 11, 10.8, 10.6, 10.3, 10.2, 10.2, 10, 9.7, 9.3, 9, 8.9, - 8.6, 8.5, 8.3, 8.1, 8, 7.7, 7.6, 7.5, 7.4, 7.4, 7.6, 7.7, 7.8, 7.9, 8, 8.1, 8.2, 8.3, - 8.4, 8.4, 8.4, 8.4, 8.3, 8.3, 8.4, 8.3, 8.2, 8.3, 8.3, 8.3, 8.2, 8.1, 8.1, 8.1, 8.1, - 8.1, 8.2, 8.2, 8.4, 8.5, 8.6, 8.7, 8.8, 8.8, 8.6, 8.4, 8.3, 8.2, 8.3, 8.5, 8.7, 8.6, - 8.4, 8.3, 8.3, 8.3, 8.3, 8.1, 7.9, 7.5, 7.1, 6.7, 6.6, 15.8, 15, 14.6, 14.2, 13.8, - 13.6, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.2, 13.6, 14.1, 14.2, 13.9, 13.8, 13.3, 12.6, 12.1, 11.9, 11.9, - 11.7, 11.5, 11.2, 11.1, 11, 10.9, 10.8, 10.8, 10.7, 10.7, 10.5, 10.4, 10.2, 10.1, 9.9, - 9.9, 9.7, 9.4, 9.1, 8.8, 8.8, 8.6, 8.4, 8.2, 8, 7.9, 7.8, 7.7, 7.7, 7.6, 7.6, 7.8, - 7.9, 7.9, 8, 8.1, 8.3, 8.3, 8.3, 8.4, 8.3, 8.4, 8.4, 8.4, 8.4, 8.3, 8.3, 8.3, 8.3, - 8.4, 8.3, 8.3, 8.2, 8.2, 8.2, 8.2, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.9, 9, 8.9, 8.6, - 8.4, 8.3, 8.3, 8.4, 8.5, 8.7, 8.6, 8.4, 8.3, 8.3, 8.3, 8.4, 8.2, 7.9, 7.5, 7, 6.7, - 6.6, 15.8, 15.1, 14.6, 14.3, 14, 13.7, 13.6, 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.6, 14.1, 14.2, 14, 13.9, - 13.6, 13, 12.3, 11.7, 11.5, 11.2, 11.1, 10.9, 10.6, 10.5, 10.5, 10.5, 10.4, 10.3, - 10.2, 10.1, 10.1, 9.9, 9.8, 9.7, 9.5, 9.3, 9, 8.8, 8.7, 8.6, 8.6, 8.3, 8.2, 8.1, 7.9, - 7.8, 7.8, 7.7, 7.7, 7.7, 7.9, 8, 8.1, 8.1, 8.2, 8.4, 8.5, 8.4, 8.4, 8.4, 8.4, 8.4, - 8.4, 8.4, 8.3, 8.4, 8.4, 8.4, 8.4, 8.4, 8.3, 8.3, 8.3, 8.3, 8.4, 8.3, 8.4, 8.4, 8.5, - 8.7, 8.9, 9.1, 9.1, 8.9, 8.7, 8.5, 8.3, 8.3, 8.4, 8.4, 8.5, 8.4, 8.4, 8.4, 8.3, 8.4, - 8.4, 8.2, 7.8, 7.5, 7, 6.7, 6.6, 15.8, 15.1, 14.6, 14.4, 14.2, 13.8, 13.6, 13.6, 13.4, - 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, - 13.8, 14.1, 14.2, 14, 13.9, 13.6, 13, 12.4, 11.9, 11.4, 10.8, 10.5, 10.3, 10, 9.9, 10, - 10.1, 10.1, 10, 10, 9.9, 9.8, 9.7, 9.6, 9.5, 9.2, 9, 8.9, 8.7, 8.7, 8.6, 8.6, 8.4, - 8.2, 8.2, 7.9, 7.8, 7.8, 7.8, 7.8, 7.8, 7.9, 8.2, 8.3, 8.3, 8.4, 8.5, 8.7, 8.6, 8.5, - 8.4, 8.4, 8.4, 8.4, 8.4, 8.4, 8.5, 8.5, 8.4, 8.4, 8.4, 8.4, 8.5, 8.5, 8.4, 8.4, 8.4, - 8.4, 8.5, 8.6, 8.9, 9.2, 9.4, 9.3, 9.1, 8.8, 8.5, 8.4, 8.4, 8.4, 8.4, 8.4, 8.4, 8.4, - 8.4, 8.4, 8.4, 8.4, 8.1, 7.8, 7.6, 7.2, 6.9, 6.8, 15.8, 15.2, 14.7, 14.3, 14.2, 13.9, - 13.7, 13.7, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.2, 13.6, 14.1, 14.2, 14.2, 14, 13.6, 13.3, 12.8, 12.3, 11.9, 11.6, 11.1, - 10.7, 10.2, 9.8, 9.5, 9.6, 9.8, 9.8, 9.7, 9.7, 9.7, 9.6, 9.5, 9.4, 9.2, 9.1, 9, 8.8, - 8.7, 8.7, 8.7, 8.6, 8.4, 8.2, 8.1, 8, 7.9, 8, 8, 8, 8, 8.2, 8.3, 8.4, 8.5, 8.6, 8.6, - 8.7, 8.6, 8.5, 8.5, 8.5, 8.5, 8.5, 8.6, 8.6, 8.6, 8.5, 8.5, 8.5, 8.5, 8.5, 8.5, 8.5, - 8.4, 8.4, 8.4, 8.5, 8.7, 8.9, 9.3, 9.7, 9.7, 9.6, 9.4, 9, 8.7, 8.5, 8.4, 8.4, 8.4, - 8.4, 8.5, 8.5, 8.4, 8.4, 8.4, 8.3, 8.1, 7.9, 7.8, 7.5, 7.2, 7.1, 15.9, 15.3, 14.7, - 14.4, 14.2, 14, 13.8, 13.7, 13.7, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.3, 14.1, 14.2, 14.2, 13.9, 13.4, 13.1, 12.8, 12.4, 12.1, - 11.8, 11.6, 11.4, 11.1, 10.5, 9.9, 9.5, 9.4, 9.4, 9.4, 9.4, 9.4, 9.4, 9.3, 9.3, 9.3, - 9.2, 9.1, 8.9, 8.7, 8.7, 8.7, 8.7, 8.6, 8.5, 8.3, 8.2, 8, 8.1, 8.2, 8.2, 8.2, 8.3, - 8.4, 8.4, 8.4, 8.5, 8.6, 8.6, 8.6, 8.6, 8.5, 8.5, 8.5, 8.5, 8.5, 8.6, 8.6, 8.5, 8.5, - 8.5, 8.5, 8.5, 8.5, 8.5, 8.6, 8.6, 8.5, 8.6, 8.8, 9.1, 9.4, 9.8, 10, 10, 9.9, 9.7, - 9.2, 8.8, 8.6, 8.6, 8.5, 8.5, 8.5, 8.5, 8.5, 8.4, 8.4, 8.3, 8.2, 8.1, 8, 8, 7.8, 7.5, - 7.3, 16, 15.4, 14.9, 14.5, 14.2, 14.1, 14, 13.8, 13.7, 13.4, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.6, 14.2, 14.1, 13.9, 13.2, 12.8, 12.8, - 12.8, 12.4, 12, 11.7, 11.5, 11.4, 11.1, 10.6, 10, 9.8, 9.7, 9.5, 9.3, 9.2, 9.3, 9.2, - 9.2, 9.3, 9.3, 9.3, 9.1, 8.9, 8.7, 8.6, 8.6, 8.6, 8.5, 8.5, 8.3, 8.1, 8.3, 8.5, 8.4, - 8.4, 8.4, 8.5, 8.7, 8.6, 8.5, 8.6, 8.7, 8.7, 8.7, 8.6, 8.6, 8.7, 8.6, 8.5, 8.5, 8.6, - 8.6, 8.5, 8.5, 8.5, 8.5, 8.6, 8.6, 8.6, 8.7, 8.7, 8.7, 8.9, 9.3, 9.8, 10.1, 10.4, - 10.5, 10.4, 10.3, 9.9, 9.4, 9, 8.8, 8.7, 8.7, 8.8, 8.7, 8.7, 8.6, 8.4, 8.4, 8.3, 8.2, - 8.1, 8.2, 8.2, 8.1, 7.8, 7.6, 16.2, 15.7, 15.3, 14.8, 14.4, 14.2, 14.1, 14, 13.8, - 13.5, 13.3, 13.2, 13.3, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.5, 13.9, 14.1, - 14, 13.6, 12.9, 12.5, 12.4, 12.4, 12.2, 11.8, 11.6, 11.4, 11.3, 11, 10.6, 10.3, 10.1, - 9.9, 9.6, 9.3, 9.3, 9.4, 9.3, 9.2, 9.2, 9.4, 9.3, 8.9, 8.7, 8.6, 8.5, 8.5, 8.5, 8.3, - 8.3, 8.2, 8.2, 8.5, 8.6, 8.5, 8.5, 8.5, 8.7, 8.8, 8.7, 8.6, 8.7, 8.8, 8.9, 8.7, 8.6, - 8.8, 8.9, 8.7, 8.5, 8.5, 8.5, 8.6, 8.6, 8.6, 8.6, 8.6, 8.7, 8.7, 8.8, 8.8, 8.9, 9.2, - 9.5, 10, 10.5, 10.8, 11, 11, 10.9, 10.7, 10.2, 9.6, 9.2, 9, 9, 9.1, 9.1, 9.2, 8.9, - 8.7, 8.5, 8.3, 8.2, 8.2, 8.3, 8.3, 8.3, 8.2, 8, 8, 16.5, 16.1, 15.7, 15.3, 14.9, 14.5, - 14.3, 14.1, 14, 13.7, 13.5, 13.5, 13.4, 13.4, 13.3, 13.2, 13.2, 13.2, 13.2, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, - 13.8, 14, 14, 13.8, 13.5, 12.9, 12.5, 12.3, 12.1, 12, 11.8, 11.5, 11.3, 11.1, 10.8, - 10.4, 10.2, 10, 9.8, 9.5, 9.6, 9.7, 9.7, 9.6, 9.5, 9.3, 9.2, 9.2, 9, 8.9, 8.9, 8.7, - 8.6, 8.6, 8.5, 8.4, 8.3, 8.4, 8.6, 8.6, 8.5, 8.6, 8.6, 8.7, 8.8, 8.8, 8.7, 8.7, 8.8, - 8.9, 8.7, 8.6, 8.7, 8.7, 8.6, 8.5, 8.5, 8.6, 8.6, 8.6, 8.7, 8.8, 8.9, 9, 9.1, 9.2, - 9.3, 9.4, 9.9, 10.4, 10.8, 11.3, 11.4, 11.4, 11.3, 11.1, 10.9, 10.4, 9.9, 9.5, 9.3, - 9.3, 9.3, 9.3, 9.2, 9, 8.7, 8.6, 8.4, 8.2, 8.2, 8.4, 8.4, 8.4, 8.3, 8.3, 8.5, 17, - 16.6, 16.2, 15.8, 15.4, 15.1, 14.8, 14.6, 14.4, 14.2, 14.1, 14, 13.7, 13.6, 13.5, - 13.4, 13.4, 13.4, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.3, 13.7, 14, 14, 13.9, 13.7, 13.4, 13, 12.7, 12.5, 12.3, 12.1, - 11.8, 11.3, 11.1, 11.1, 10.8, 10.3, 10.2, 10, 9.8, 9.6, 9.8, 10, 10, 10.1, 9.9, 9.8, - 9.6, 9.5, 9.4, 9.2, 9.1, 9.1, 8.9, 8.8, 8.8, 8.7, 8.5, 8.6, 8.7, 8.6, 8.7, 8.7, 8.7, - 8.7, 8.9, 9, 8.8, 8.7, 8.8, 8.7, 8.6, 8.7, 8.7, 8.7, 8.6, 8.6, 8.6, 8.6, 8.6, 8.7, - 8.9, 9.1, 9.5, 9.7, 9.9, 10, 10, 10.2, 10.6, 11.1, 11.6, 11.9, 11.9, 11.7, 11.5, 11.3, - 10.9, 10.5, 10.2, 9.8, 9.6, 9.5, 9.4, 9.3, 9.2, 8.9, 8.7, 8.6, 8.5, 8.4, 8.3, 8.4, - 8.6, 8.6, 8.6, 8.6, 8.8, 17.5, 17.1, 16.7, 16.3, 16, 15.8, 15.5, 15.2, 15, 14.7, 14.5, - 14.4, 14.3, 14.2, 14, 13.9, 13.8, 13.8, 13.8, 13.7, 13.6, 13.5, 13.4, 13.2, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.7, 14, 14, 14, 13.8, 13.6, 13.3, 12.9, - 12.7, 12.5, 12.4, 12.1, 11.7, 11.2, 10.8, 10.7, 10.4, 10.2, 10.2, 10.2, 10, 9.9, 9.9, - 10.2, 10.4, 10.4, 10.3, 10.1, 10, 9.8, 9.7, 9.5, 9.4, 9.3, 9.3, 9.2, 9.1, 8.9, 8.7, - 8.6, 8.7, 8.7, 8.8, 8.8, 8.8, 8.8, 9, 9.1, 8.9, 8.7, 8.8, 8.7, 8.7, 8.8, 8.8, 8.7, - 8.7, 8.7, 8.7, 8.7, 8.8, 9.1, 9.5, 9.9, 10.3, 10.4, 10.6, 10.7, 10.7, 10.8, 11.2, - 11.7, 12, 12.1, 12, 11.8, 11.5, 11.2, 10.9, 10.6, 10.4, 10.2, 9.9, 9.8, 9.6, 9.4, 9.1, - 8.9, 8.8, 8.7, 8.5, 8.5, 8.4, 8.5, 8.7, 8.7, 8.7, 8.8, 9.1, 17.9, 17.5, 17.2, 16.9, - 16.6, 16.3, 16.1, 15.8, 15.6, 15.3, 15.1, 15, 14.8, 14.6, 14.4, 14.4, 14.3, 14.3, - 14.2, 14.2, 14.1, 14, 13.9, 13.5, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.3, 13.3, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, - 13.9, 14, 14, 13.8, 13.5, 13.2, 13.1, 12.8, 12.6, 12.4, 12.1, 11.9, 11.4, 11, 10.6, - 10.4, 10.3, 10.2, 10.2, 10.1, 10.2, 10.1, 10.2, 10.4, 10.5, 10.6, 10.5, 10.3, 10.2, - 10.1, 10, 9.8, 9.7, 9.6, 9.6, 9.5, 9.5, 9.4, 9, 8.7, 8.7, 8.9, 8.8, 8.8, 8.8, 8.8, 9, - 9.1, 8.8, 8.7, 8.7, 8.7, 8.7, 8.8, 8.8, 8.8, 8.7, 8.8, 8.8, 8.8, 9.2, 9.8, 10.3, 10.6, - 10.8, 11, 11, 11.1, 11, 11.1, 11.5, 11.9, 12.1, 12, 12, 11.8, 11.5, 11.1, 10.7, 10.5, - 10.4, 10.3, 10.2, 10, 9.7, 9.4, 9.2, 9.1, 8.9, 8.8, 8.7, 8.7, 8.6, 8.6, 8.8, 8.9, 9, - 9.2, 9.3, 18.2, 17.9, 17.6, 17.3, 17.1, 16.8, 16.6, 16.3, 16, 15.9, 15.7, 15.6, 15.5, - 15.2, 15.1, 15, 15, 14.9, 14.9, 14.8, 14.6, 14.3, 14.1, 13.9, 13.6, 13.5, 13.3, 13.2, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.3, 13.4, 13.4, 13.4, 13.4, 13.3, 13.2, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.7, 13.9, 13.9, 13.8, 13.4, 13, 12.9, 12.9, 12.6, 12.3, 12.1, - 11.8, 11.4, 11, 10.8, 10.5, 10.4, 10.3, 10.3, 10.2, 10.2, 10.2, 10.2, 10.3, 10.4, - 10.5, 10.7, 10.7, 10.6, 10.5, 10.4, 10.3, 10.2, 10.1, 10, 9.9, 9.8, 9.8, 9.7, 9.4, - 8.9, 8.8, 8.8, 8.9, 9, 8.9, 8.9, 8.9, 8.9, 8.8, 8.7, 8.8, 8.8, 8.8, 8.8, 8.8, 8.8, - 8.8, 8.8, 8.9, 9.3, 10, 10.6, 11, 11.2, 11.2, 11.3, 11.3, 11.2, 11.2, 11.4, 11.7, - 11.9, 11.9, 11.9, 11.8, 11.6, 11.2, 10.9, 10.6, 10.4, 10.4, 10.4, 10.4, 10.2, 10, 9.8, - 9.7, 9.6, 9.4, 9.1, 9, 9, 9, 9.1, 9.3, 9.3, 9.5, 9.6, 9.8, 18.4, 18.2, 17.9, 17.7, - 17.5, 17.2, 17, 16.8, 16.6, 16.4, 16.3, 16.1, 15.9, 15.8, 15.7, 15.7, 15.6, 15.6, - 15.5, 15.4, 15.2, 14.8, 14.4, 14.2, 13.9, 13.7, 13.6, 13.4, 13.2, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, - 13.3, 13.4, 13.4, 13.4, 13.4, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.8, - 13.7, 13.6, 13.3, 12.9, 12.7, 12.6, 12.7, 12.5, 12.1, 11.8, 11.4, 11.1, 10.8, 10.6, - 10.5, 10.4, 10.3, 10.3, 10.2, 10.2, 10.3, 10.3, 10.4, 10.5, 10.7, 10.9, 11, 10.9, - 10.8, 10.7, 10.6, 10.6, 10.5, 10.3, 10.2, 10.1, 10, 9.8, 9.5, 9.2, 9, 8.8, 8.8, 8.9, - 8.9, 8.8, 8.7, 8.7, 8.8, 8.9, 8.8, 8.8, 8.8, 8.8, 8.8, 8.8, 8.9, 9, 9.3, 10, 10.8, - 11.3, 11.6, 11.7, 11.5, 11.4, 11.3, 11.2, 11.3, 11.5, 11.7, 11.7, 11.7, 11.7, 11.5, - 11.2, 10.9, 10.6, 10.5, 10.4, 10.4, 10.5, 10.5, 10.4, 10.3, 10.2, 10.2, 10, 9.7, 9.5, - 9.5, 9.6, 9.6, 9.7, 9.7, 9.7, 10, 10, 10.2, 18.7, 18.5, 18.3, 18.2, 18, 17.8, 17.5, - 17.2, 17.1, 16.9, 16.8, 16.6, 16.5, 16.3, 16.2, 16.2, 16.2, 16.2, 16.1, 16, 15.7, - 15.3, 14.9, 14.5, 14.2, 14.1, 13.9, 13.7, 13.5, 13.3, 13.2, 13.2, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.4, 13.4, - 13.4, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.6, 13.8, 13.8, 13.4, 13, 12.6, - 12.4, 12.3, 12.3, 12.5, 12.4, 11.9, 11.5, 11.2, 10.9, 10.7, 10.5, 10.5, 10.4, 10.3, - 10.3, 10.3, 10.3, 10.3, 10.4, 10.6, 10.9, 11.1, 11.3, 11.3, 11.3, 11.2, 11.1, 11.1, - 10.9, 10.8, 10.7, 10.6, 10.4, 10.2, 10, 9.7, 9.4, 9.2, 9, 8.8, 8.8, 8.8, 8.8, 8.7, - 8.7, 8.8, 8.9, 8.9, 8.9, 8.9, 8.9, 8.9, 8.9, 9.1, 9.4, 10, 10.7, 11.4, 11.8, 11.9, - 11.8, 11.5, 11.4, 11.4, 11.4, 11.5, 11.6, 11.6, 11.6, 11.5, 11.4, 11.2, 10.9, 10.6, - 10.5, 10.4, 10.5, 10.4, 10.6, 10.6, 10.5, 10.5, 10.4, 10.3, 10.1, 9.9, 9.7, 9.7, 9.8, - 10, 9.9, 9.9, 10.1, 10.2, 10.3, 10.6, 19, 18.9, 18.8, 18.6, 18.5, 18.3, 18, 17.7, - 17.5, 17.3, 17.3, 17.1, 16.9, 16.7, 16.7, 16.7, 16.7, 16.7, 16.6, 16.5, 16.2, 15.8, - 15.4, 15, 14.7, 14.4, 14.2, 14, 13.9, 13.7, 13.6, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.4, 13.4, 13.5, 13.5, 13.4, - 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.8, 13.9, 13.6, 13, 12.5, 12.3, 12.3, 12.1, 12, - 12.1, 12, 11.6, 11.2, 10.9, 10.8, 10.8, 10.5, 10.5, 10.4, 10.3, 10.3, 10.3, 10.3, - 10.4, 10.6, 11, 11.2, 11.5, 11.7, 11.7, 11.8, 11.8, 11.7, 11.4, 11.3, 11.2, 11.1, 11, - 10.8, 10.5, 10.2, 9.9, 9.6, 9.3, 9.1, 8.9, 8.9, 8.8, 8.9, 8.9, 8.9, 8.8, 8.8, 8.9, - 8.9, 8.9, 8.9, 9, 9.1, 9.4, 10, 10.6, 11.2, 11.8, 11.9, 11.9, 11.7, 11.5, 11.4, 11.5, - 11.6, 11.7, 11.8, 11.6, 11.5, 11.3, 11.2, 11, 10.8, 10.6, 10.5, 10.5, 10.5, 10.5, - 10.5, 10.6, 10.5, 10.5, 10.5, 10.3, 10.2, 10, 9.9, 9.9, 10, 10.1, 10.1, 10.2, 10.2, - 10.3, 10.4, 10.8, 19.3, 19.3, 19.4, 19.2, 19.1, 18.9, 18.5, 18.2, 18, 17.8, 17.7, - 17.5, 17.3, 17.2, 17.1, 17.2, 17.2, 17.2, 17.1, 16.9, 16.6, 16.2, 15.8, 15.4, 15.1, - 14.7, 14.5, 14.3, 14.1, 14, 13.9, 13.7, 13.6, 13.4, 13.3, 13.2, 13.2, 13.1, 13.1, - 13.2, 13.2, 13.2, 13.2, 13.2, 13.3, 13.4, 13.5, 13.6, 13.6, 13.6, 13.6, 13.4, 13.3, - 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.3, 13.6, 13.8, 13.9, 13.6, 13.2, 12.6, 12.1, 12, 12, 11.8, 11.7, 11.6, 11.4, - 11.2, 10.9, 10.8, 10.7, 10.7, 10.5, 10.4, 10.3, 10.2, 10.3, 10.3, 10.5, 10.7, 11, - 11.4, 11.7, 12, 12.2, 12.3, 12.3, 12.2, 12, 11.8, 11.6, 11.5, 11.4, 11.3, 11, 10.7, - 10.3, 10, 9.7, 9.4, 9.2, 8.9, 8.9, 9, 9, 9.1, 9, 8.9, 8.8, 8.8, 8.8, 8.9, 9, 9.1, 9.3, - 9.8, 10.3, 10.8, 11.3, 11.7, 11.8, 11.7, 11.5, 11.5, 11.6, 11.8, 11.9, 11.9, 11.8, - 11.5, 11.3, 11.1, 11, 10.9, 10.7, 10.6, 10.6, 10.7, 10.7, 10.6, 10.7, 10.7, 10.7, - 10.6, 10.4, 10.3, 10.2, 10.1, 10.1, 10.2, 10.3, 10.4, 10.4, 10.3, 10.3, 10.3, 10.4, - 10.8, 19.7, 19.8, 19.8, 19.7, 19.6, 19.4, 19, 18.7, 18.5, 18.3, 18.2, 17.9, 17.6, - 17.5, 17.5, 17.6, 17.6, 17.5, 17.4, 17.2, 16.9, 16.6, 16.2, 15.9, 15.5, 15.2, 14.9, - 14.7, 14.5, 14.4, 14.3, 14.1, 13.9, 13.8, 13.7, 13.6, 13.5, 13.5, 13.5, 13.5, 13.5, - 13.5, 13.5, 13.5, 13.5, 13.6, 13.6, 13.7, 13.7, 13.7, 13.6, 13.5, 13.5, 13.5, 13.4, - 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.7, - 13.9, 13.9, 13.7, 13.1, 12.7, 12.4, 12.1, 11.8, 11.8, 11.6, 11.4, 11.2, 11.1, 10.9, - 10.8, 10.6, 10.6, 10.6, 10.5, 10.4, 10.3, 10.3, 10.4, 10.6, 10.8, 11.2, 11.6, 12, - 12.3, 12.5, 12.7, 12.8, 12.8, 12.6, 12.3, 12.1, 11.9, 11.7, 11.6, 11.4, 11.2, 10.9, - 10.5, 10, 9.7, 9.4, 9.2, 9, 9.1, 9.2, 9.1, 9.1, 9.1, 9, 8.9, 8.9, 8.9, 9, 9.1, 9.2, - 9.6, 10.2, 10.7, 11.1, 11.4, 11.6, 11.6, 11.5, 11.5, 11.6, 12, 12.1, 12.1, 12, 11.7, - 11.4, 11.2, 11, 10.9, 10.8, 10.7, 10.6, 10.7, 10.9, 11, 11, 10.9, 10.8, 10.7, 10.5, - 10.3, 10.3, 10.2, 10.3, 10.6, 10.8, 10.9, 10.9, 10.7, 10.6, 10.4, 10.4, 10.5, 10.8, - 20.4, 20.4, 20.3, 20.1, 20, 19.8, 19.5, 19.2, 18.9, 18.7, 18.5, 18.2, 18, 17.9, 17.9, - 17.9, 17.8, 17.8, 17.6, 17.4, 17.2, 17, 16.7, 16.3, 16, 15.6, 15.3, 15.1, 14.9, 14.8, - 14.6, 14.5, 14.4, 14.2, 14, 13.8, 13.7, 13.7, 13.7, 13.7, 13.7, 13.6, 13.6, 13.6, - 13.6, 13.7, 13.9, 13.9, 13.9, 13.9, 13.8, 13.7, 13.6, 13.6, 13.6, 13.5, 13.4, 13.2, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.7, 13.9, 13.9, 13.7, 13.2, - 12.8, 12.5, 12.4, 12.2, 11.7, 11.6, 11.5, 11.2, 11.1, 10.9, 10.8, 10.7, 10.7, 10.7, - 10.7, 10.6, 10.6, 10.6, 10.7, 10.8, 11.1, 11.5, 11.9, 12.4, 12.7, 13, 13.2, 13.3, - 13.3, 13.2, 13, 12.6, 12.3, 12.1, 12, 11.7, 11.5, 11.2, 10.9, 10.5, 10, 9.7, 9.4, 9.2, - 9.2, 9.3, 9.2, 9.1, 9.1, 9.3, 9.3, 9.2, 9, 8.9, 9.1, 9.2, 9.4, 10.1, 10.7, 11.2, 11.7, - 11.8, 11.7, 11.6, 11.6, 11.6, 11.8, 12.2, 12.3, 12.1, 11.9, 11.6, 11.3, 11.1, 11, 11, - 10.9, 10.8, 10.8, 10.9, 11, 11.1, 11.1, 11.1, 10.9, 10.6, 10.4, 10.4, 10.4, 10.4, - 10.7, 11, 11.3, 11.3, 11.3, 11.1, 10.8, 10.6, 10.5, 10.6, 10.8, 21, 20.8, 20.7, 20.5, - 20.4, 20.2, 19.9, 19.6, 19.4, 19.1, 18.8, 18.6, 18.5, 18.3, 18.2, 18.2, 18.1, 18, - 17.9, 17.7, 17.6, 17.4, 17.1, 16.8, 16.4, 16.1, 15.8, 15.6, 15.5, 15.3, 15.1, 15, - 14.8, 14.6, 14.5, 14.2, 14, 14, 13.9, 13.9, 14, 14, 13.9, 13.8, 13.8, 13.9, 13.9, 14, - 14, 14, 13.8, 13.8, 13.8, 13.7, 13.6, 13.5, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.3, 13.7, 13.9, 13.9, 13.6, 13.3, 13, 12.8, 12.5, 12.4, 12.2, 11.8, - 11.6, 11.2, 11.1, 11, 11, 10.9, 10.8, 10.9, 10.9, 10.9, 10.8, 10.9, 11, 11.1, 11.4, - 11.8, 12.3, 12.7, 13.1, 13.4, 13.7, 13.8, 13.9, 13.8, 13.6, 13.3, 13, 12.6, 12.3, - 12.1, 11.9, 11.6, 11.2, 10.8, 10.4, 10.1, 9.7, 9.5, 9.4, 9.3, 9.4, 9.3, 9.2, 9.2, 9.4, - 9.4, 9.2, 9.1, 9, 9.1, 9.4, 9.8, 10.5, 11.2, 11.8, 12.1, 12.1, 12, 11.8, 11.6, 11.7, - 11.9, 12.1, 12.2, 12, 11.8, 11.5, 11.2, 11, 11, 11.1, 11.2, 11.2, 11.2, 11.3, 11.4, - 11.4, 11.3, 11.2, 11.1, 10.7, 10.5, 10.5, 10.5, 10.7, 11.1, 11.4, 11.6, 11.6, 11.5, - 11.3, 10.9, 10.7, 10.6, 10.7, 10.9, 21.2, 21.1, 21, 20.9, 20.7, 20.5, 20.2, 20, 19.8, - 19.5, 19.2, 18.9, 18.8, 18.7, 18.6, 18.5, 18.4, 18.3, 18.1, 18, 17.9, 17.7, 17.4, - 17.2, 16.9, 16.5, 16.3, 16.1, 16, 15.8, 15.7, 15.5, 15.3, 15.1, 14.9, 14.6, 14.4, - 14.2, 14.2, 14.1, 14.2, 14.2, 14, 14, 14, 14.1, 14.1, 14.1, 14.1, 14, 13.9, 13.9, - 13.9, 13.8, 13.6, 13.5, 13.5, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, - 13.8, 13.9, 13.7, 13.3, 13.1, 12.9, 12.7, 12.3, 12.3, 12.1, 11.8, 11.5, 11.2, 11.1, - 11.1, 11.1, 11, 11, 11.1, 11.1, 11.1, 11.1, 11.2, 11.4, 11.6, 12, 12.5, 13.1, 13.6, - 13.8, 14.1, 14.3, 14.4, 14.3, 14.2, 14, 13.7, 13.3, 13, 12.6, 12.3, 12.1, 11.7, 11.3, - 10.8, 10.4, 10, 9.7, 9.5, 9.5, 9.4, 9.4, 9.4, 9.3, 9.3, 9.3, 9.3, 9.2, 9.2, 9.1, 9.4, - 9.8, 10.4, 11.2, 11.8, 12.3, 12.6, 12.5, 12.3, 12, 11.8, 11.7, 11.9, 12, 12, 11.9, - 11.7, 11.5, 11.4, 11.3, 11.2, 11.3, 11.5, 11.6, 11.8, 11.8, 11.9, 11.8, 11.5, 11.3, - 11.1, 10.8, 10.7, 10.8, 10.9, 11.1, 11.4, 11.7, 11.9, 11.9, 11.7, 11.4, 11, 10.8, - 10.8, 10.8, 11.1, 21.5, 21.4, 21.3, 21.2, 21, 20.8, 20.5, 20.2, 20, 19.8, 19.5, 19.3, - 19.1, 18.9, 18.8, 18.7, 18.6, 18.5, 18.3, 18.2, 18.1, 17.9, 17.7, 17.5, 17.3, 17, - 16.8, 16.7, 16.6, 16.4, 16.3, 16.1, 15.9, 15.7, 15.4, 15.1, 14.8, 14.6, 14.4, 14.2, - 14.1, 14.2, 14.2, 14.1, 14.1, 14.2, 14.2, 14.2, 14.3, 14.2, 14, 14, 13.9, 13.7, 13.7, - 13.6, 13.5, 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.8, 13.8, 13.4, - 13.2, 13.1, 12.9, 12.6, 12.3, 12.1, 12.1, 11.8, 11.6, 11.4, 11.3, 11.2, 11.2, 11.2, - 11.2, 11.3, 11.3, 11.3, 11.5, 11.7, 11.9, 12.2, 12.6, 13.1, 13.5, 13.9, 14.3, 14.6, - 14.8, 14.8, 14.7, 14.5, 14.3, 14, 13.8, 13.4, 13, 12.7, 12.4, 12, 11.4, 10.8, 10.3, - 10, 9.7, 9.6, 9.5, 9.5, 9.6, 9.5, 9.4, 9.3, 9.3, 9.3, 9.2, 9.3, 9.4, 9.8, 10.3, 11, - 11.7, 12.4, 12.8, 12.9, 12.8, 12.5, 12.1, 11.9, 11.8, 11.8, 11.9, 11.9, 11.9, 11.9, - 11.8, 11.7, 11.7, 11.6, 11.7, 11.9, 12.1, 12.3, 12.3, 12.2, 12, 11.7, 11.4, 11.1, - 10.9, 11, 11.1, 11.2, 11.4, 11.7, 12, 12.2, 12.1, 11.9, 11.5, 11.2, 10.9, 10.9, 11.1, - 11.3, 21.7, 21.6, 21.5, 21.3, 21.2, 21, 20.8, 20.5, 20.3, 20.1, 19.9, 19.7, 19.5, - 19.2, 19.1, 18.9, 18.8, 18.7, 18.6, 18.5, 18.4, 18.2, 18.1, 17.9, 17.6, 17.4, 17.3, - 17.2, 17.1, 16.9, 16.8, 16.7, 16.5, 16.2, 15.9, 15.6, 15.2, 14.9, 14.7, 14.5, 14.3, - 14.4, 14.4, 14.3, 14.2, 14.3, 14.3, 14.3, 14.4, 14.3, 14.2, 14, 13.8, 13.8, 13.7, - 13.6, 13.5, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.4, 13.6, 13.9, 13.7, 13.2, - 13, 13, 12.8, 12.5, 12.3, 12.1, 12, 11.8, 11.7, 11.6, 11.5, 11.4, 11.4, 11.4, 11.4, - 11.5, 11.6, 11.7, 11.8, 12.2, 12.5, 12.7, 13.1, 13.5, 13.8, 14.2, 14.6, 14.9, 15, 15, - 14.9, 14.8, 14.6, 14.4, 14.1, 13.8, 13.4, 13.1, 12.6, 12.1, 11.5, 10.8, 10.2, 9.9, - 9.7, 9.6, 9.6, 9.6, 9.7, 9.6, 9.4, 9.4, 9.3, 9.3, 9.3, 9.4, 9.8, 10.3, 11, 11.7, 12.3, - 12.9, 13.2, 13.1, 12.9, 12.6, 12.2, 12, 11.9, 11.9, 11.9, 12.1, 12.2, 12.3, 12.2, - 12.1, 12.1, 12.1, 12.1, 12.2, 12.4, 12.6, 12.7, 12.4, 12.1, 11.7, 11.3, 11.1, 11.1, - 11.2, 11.4, 11.4, 11.6, 11.9, 12.1, 12.3, 12.3, 12, 11.6, 11.3, 11.2, 11.2, 11.4, - 11.6, 21.9, 21.8, 21.6, 21.5, 21.4, 21.2, 21, 20.7, 20.5, 20.4, 20.3, 20.1, 19.7, - 19.5, 19.3, 19.1, 19.1, 19.1, 19, 18.9, 18.7, 18.5, 18.4, 18.1, 17.8, 17.7, 17.6, - 17.5, 17.4, 17.3, 17.2, 17.1, 16.9, 16.7, 16.3, 15.9, 15.5, 15.2, 14.9, 14.7, 14.5, - 14.4, 14.4, 14.4, 14.3, 14.3, 14.3, 14.3, 14.3, 14.2, 14.1, 13.9, 13.8, 13.7, 13.7, - 13.6, 13.5, 13.5, 13.4, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.1, 13.1, 13.3, 13.4, 13.5, 13.7, 13.9, 13.7, 13.1, - 12.9, 12.8, 12.6, 12.5, 12.3, 12.2, 12.1, 12, 11.8, 11.7, 11.7, 11.7, 11.7, 11.8, - 11.8, 11.9, 12, 12, 12.2, 12.6, 12.9, 13.1, 13.4, 13.9, 14.2, 14.4, 14.8, 15, 15.2, - 15.2, 15.1, 15, 14.8, 14.7, 14.5, 14.2, 13.7, 13.2, 12.7, 12.1, 11.4, 10.6, 10.1, 9.9, - 9.7, 9.6, 9.6, 9.6, 9.6, 9.5, 9.5, 9.4, 9.3, 9.4, 9.4, 9.7, 10.2, 10.8, 11.5, 12.2, - 12.8, 13.3, 13.4, 13.3, 12.9, 12.6, 12.2, 12, 11.9, 12, 12.2, 12.5, 12.7, 12.8, 12.7, - 12.6, 12.5, 12.6, 12.6, 12.6, 12.6, 12.7, 12.6, 12.3, 11.9, 11.6, 11.4, 11.2, 11.2, - 11.4, 11.5, 11.5, 11.7, 12, 12.2, 12.4, 12.4, 12.2, 11.9, 11.6, 11.5, 11.6, 11.7, - 11.9, 22, 21.8, 21.7, 21.6, 21.6, 21.4, 21.2, 21, 20.7, 20.5, 20.5, 20.2, 19.9, 19.7, - 19.5, 19.4, 19.4, 19.4, 19.3, 19.2, 19.1, 18.9, 18.7, 18.4, 18.2, 18.1, 18, 17.9, - 17.7, 17.6, 17.6, 17.4, 17.3, 17, 16.6, 16.2, 15.8, 15.4, 15.1, 14.9, 14.7, 14.6, - 14.5, 14.4, 14.4, 14.4, 14.4, 14.3, 14.2, 14, 13.9, 13.8, 13.8, 13.8, 13.6, 13.5, - 13.5, 13.5, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.4, 13.3, 13.3, 13.4, 13.5, 13.6, 13.8, 13.9, 13.8, 13.3, 12.9, - 12.7, 12.6, 12.5, 12.4, 12.3, 12.2, 12.1, 12, 12.1, 12.1, 12.2, 12.3, 12.4, 12.3, - 12.3, 12.4, 12.5, 12.7, 12.9, 13.2, 13.4, 13.7, 14.1, 14.3, 14.5, 14.8, 15, 15.2, - 15.2, 15.2, 15.1, 15, 14.9, 14.7, 14.3, 13.8, 13.2, 12.5, 11.9, 11.2, 10.5, 10.1, 9.9, - 9.8, 9.7, 9.7, 9.6, 9.6, 9.6, 9.5, 9.4, 9.4, 9.6, 9.8, 10.1, 10.8, 11.5, 12.2, 12.7, - 13.3, 13.5, 13.5, 13.2, 12.9, 12.6, 12.3, 12, 12, 12.2, 12.6, 13, 13.2, 13.2, 13, - 12.9, 12.9, 12.9, 12.9, 12.8, 12.7, 12.5, 12.3, 12, 11.7, 11.6, 11.5, 11.6, 11.6, - 11.6, 11.6, 11.6, 11.7, 12, 12.2, 12.3, 12.4, 12.3, 12.1, 11.9, 12, 12.1, 12.1, 12.2, - 22.1, 21.9, 21.8, 21.8, 21.7, 21.6, 21.4, 21.2, 20.9, 20.8, 20.6, 20.3, 20.1, 20, - 19.8, 19.8, 19.9, 19.7, 19.6, 19.5, 19.4, 19.4, 19, 18.7, 18.5, 18.4, 18.3, 18.2, - 18.1, 18, 17.9, 17.7, 17.5, 17.2, 16.8, 16.4, 16, 15.6, 15.3, 15.1, 14.9, 14.7, 14.6, - 14.5, 14.5, 14.6, 14.4, 14.3, 14.2, 14, 13.9, 13.8, 13.8, 13.7, 13.5, 13.5, 13.5, - 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.2, 13.5, 13.6, 13.5, 13.5, 13.5, 13.6, 13.7, 13.9, 13.9, 13.8, 13.4, 13, 12.8, - 12.7, 12.6, 12.5, 12.4, 12.4, 12.4, 12.4, 12.5, 12.7, 12.9, 13, 13, 12.9, 12.8, 12.9, - 13, 13.1, 13.2, 13.4, 13.5, 13.8, 14.2, 14.3, 14.5, 14.7, 15, 15.1, 15.2, 15.2, 15.2, - 15.1, 14.9, 14.6, 14.3, 13.7, 13, 12.3, 11.7, 11, 10.5, 10.1, 9.9, 9.8, 9.8, 9.7, 9.7, - 9.7, 9.6, 9.5, 9.5, 9.6, 9.8, 10.2, 10.8, 11.4, 12.1, 12.7, 13.2, 13.5, 13.6, 13.4, - 13.1, 12.7, 12.5, 12.2, 12.1, 12.2, 12.6, 13, 13.3, 13.5, 13.5, 13.4, 13.2, 13.1, - 13.1, 13, 12.8, 12.6, 12.3, 12.1, 11.8, 11.7, 11.6, 11.8, 11.9, 11.8, 11.7, 11.5, - 11.5, 11.8, 12.1, 12.1, 12.2, 12.2, 12.2, 12.2, 12.2, 12.4, 12.4, 12.5, 12.6, 22.3, - 22.2, 22.1, 22, 21.8, 21.7, 21.5, 21.3, 21.1, 21, 20.8, 20.6, 20.3, 20.2, 20, 20, 20, - 19.9, 19.9, 19.9, 19.8, 19.7, 19.4, 19, 18.8, 18.7, 18.6, 18.6, 18.5, 18.3, 18.1, 18, - 17.8, 17.4, 16.9, 16.5, 16.1, 15.8, 15.4, 15.2, 15, 14.9, 14.7, 14.5, 14.4, 14.4, - 14.3, 14.1, 14.1, 14, 14, 13.9, 13.7, 13.6, 13.5, 13.5, 13.5, 13.3, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.8, 13.8, 13.7, - 13.6, 13.7, 13.9, 14, 14.1, 14.2, 13.9, 13.6, 13.3, 13, 12.9, 12.8, 12.7, 12.6, 12.6, - 12.6, 12.7, 12.9, 13.3, 13.5, 13.7, 13.6, 13.5, 13.4, 13.3, 13.3, 13.3, 13.4, 13.6, - 13.7, 13.8, 14.1, 14.3, 14.4, 14.6, 14.8, 14.9, 15, 15.1, 15.1, 15, 14.8, 14.5, 14.1, - 13.6, 12.8, 12.1, 11.4, 10.9, 10.4, 10.1, 9.9, 9.8, 9.8, 9.8, 9.8, 9.7, 9.6, 9.6, 9.7, - 9.9, 10.3, 10.9, 11.5, 12.1, 12.6, 13.1, 13.5, 13.6, 13.4, 13.2, 12.9, 12.6, 12.4, - 12.2, 12.2, 12.5, 13, 13.4, 13.6, 13.6, 13.6, 13.6, 13.4, 13.2, 13.1, 12.9, 12.7, - 12.5, 12.2, 12, 11.8, 11.6, 11.7, 11.9, 12, 11.9, 11.7, 11.5, 11.5, 11.8, 12, 12, - 12.1, 12.1, 12.2, 12.2, 12.4, 12.7, 12.8, 13, 13.2, 22.4, 22.3, 22.3, 22.2, 22, 21.8, - 21.7, 21.5, 21.4, 21.2, 21.1, 20.9, 20.6, 20.4, 20.2, 20.1, 20.1, 20, 20, 20, 19.9, - 19.7, 19.5, 19.3, 19.1, 19, 18.9, 18.9, 18.7, 18.5, 18.2, 18, 17.7, 17.3, 16.9, 16.4, - 16, 15.7, 15.4, 15.2, 15.2, 15.1, 14.8, 14.5, 14.4, 14.3, 14.3, 14.1, 14, 13.9, 13.9, - 13.8, 13.7, 13.6, 13.5, 13.5, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.8, 14.1, 14, 13.8, 13.7, 13.9, 14.2, 14.3, - 14.5, 14.4, 14.1, 13.8, 13.6, 13.2, 13.1, 13, 12.9, 12.8, 12.8, 12.9, 13.1, 13.5, - 13.9, 14, 14.1, 14.1, 14, 13.9, 13.8, 13.7, 13.7, 13.7, 13.8, 13.8, 13.9, 14, 14.2, - 14.3, 14.4, 14.5, 14.6, 14.8, 14.9, 14.9, 14.7, 14.5, 14.2, 13.8, 13.2, 12.6, 11.8, - 11.1, 10.6, 10.2, 10.1, 10, 9.9, 9.8, 9.8, 9.8, 9.7, 9.8, 9.8, 10.1, 10.5, 11.1, 11.8, - 12.3, 12.7, 13.1, 13.3, 13.5, 13.4, 13.1, 12.9, 12.6, 12.5, 12.3, 12.3, 12.3, 12.7, - 13.1, 13.5, 13.7, 13.7, 13.7, 13.7, 13.6, 13.3, 13, 12.8, 12.6, 12.4, 12.2, 12, 11.8, - 11.7, 11.7, 12, 12.1, 11.9, 11.7, 11.6, 11.5, 11.6, 11.8, 12, 12, 12.1, 12.1, 12.3, - 12.6, 12.9, 13.2, 13.5, 13.9, 22.4, 22.4, 22.4, 22.4, 22.2, 22, 21.9, 21.7, 21.6, - 21.4, 21.3, 21, 20.7, 20.5, 20.3, 20.2, 20.1, 20.1, 20.1, 20.1, 19.9, 19.8, 19.6, - 19.5, 19.4, 19.3, 19.1, 19, 18.8, 18.6, 18.3, 17.9, 17.7, 17.3, 16.8, 16.3, 15.9, - 15.6, 15.4, 15.2, 15.2, 15.1, 14.8, 14.5, 14.4, 14.3, 14.3, 14.2, 13.9, 13.8, 13.7, - 13.7, 13.7, 13.7, 13.5, 13.5, 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.7, 14.1, 14.2, 14.2, 14, 14, 14.2, 14.5, 14.8, 14.8, - 14.7, 14.5, 14.3, 13.9, 13.6, 13.3, 13.2, 13.1, 13.1, 13.1, 13.2, 13.5, 13.8, 14.1, - 14.3, 14.5, 14.5, 14.4, 14.3, 14.2, 14.2, 14.1, 14, 13.9, 14, 14, 14, 14.1, 14.2, - 14.2, 14.2, 14.3, 14.5, 14.6, 14.6, 14.4, 14.1, 13.7, 13.3, 12.8, 12.2, 11.5, 10.9, - 10.5, 10.2, 10.1, 10.1, 10, 9.9, 9.9, 9.8, 9.9, 10.1, 10.4, 10.9, 11.5, 11.9, 12.8, - 13.2, 13.3, 13.5, 13.4, 13.2, 13, 12.8, 12.6, 12.5, 12.4, 12.3, 12.3, 12.4, 12.8, - 13.3, 13.6, 13.6, 13.6, 13.6, 13.5, 13.5, 13.2, 12.9, 12.8, 12.6, 12.3, 12.2, 12.1, - 11.9, 11.8, 11.9, 12.2, 12.2, 11.9, 11.7, 11.6, 11.6, 11.7, 11.9, 12, 12.1, 12.1, - 12.3, 12.6, 13, 13.5, 13.8, 14.2, 14.6, 22.5, 22.6, 22.6, 22.5, 22.4, 22.2, 22.1, - 21.9, 21.8, 21.5, 21.3, 20.9, 20.6, 20.5, 20.3, 20.2, 20.1, 20.1, 20.1, 20, 19.9, - 19.9, 19.7, 19.6, 19.5, 19.3, 19.2, 19.1, 18.9, 18.6, 18.3, 17.9, 17.7, 17.2, 16.8, - 16.3, 15.9, 15.6, 15.3, 15.2, 15, 14.9, 14.7, 14.5, 14.4, 14.3, 14.3, 14.3, 14, 13.8, - 13.7, 13.8, 13.8, 13.7, 13.6, 13.5, 13.4, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 14, 14.3, 14.4, 14.2, 14.2, 14.3, 14.5, 15, 15.3, - 15.2, 15, 14.9, 14.7, 14.1, 13.8, 13.6, 13.5, 13.4, 13.3, 13.3, 13.5, 13.7, 14, 14.1, - 14.4, 14.7, 14.8, 14.7, 14.7, 14.6, 14.5, 14.4, 14.2, 14.1, 14, 14, 14, 14.1, 14, 14, - 14, 14, 14.1, 14.2, 14.1, 13.9, 13.6, 13.3, 13, 12.5, 11.9, 11.2, 10.7, 10.4, 10.2, - 10.1, 10.1, 10, 10, 10, 10, 10.1, 10.5, 11.1, 11.8, 12.3, 12.9, 13.4, 13.6, 13.7, - 13.6, 13.4, 13, 12.8, 12.6, 12.4, 12.4, 12.4, 12.4, 12.5, 12.5, 12.8, 13.3, 13.5, - 13.6, 13.6, 13.5, 13.5, 13.3, 13.1, 12.9, 12.9, 12.6, 12.3, 12.1, 12.1, 12, 12, 12.2, - 12.3, 12.2, 12, 11.9, 11.7, 11.7, 11.8, 11.9, 12, 12.2, 12.4, 12.7, 13, 13.4, 13.9, - 14.2, 14.5, 14.9, 22.6, 22.7, 22.7, 22.6, 22.5, 22.4, 22.2, 22.1, 21.8, 21.5, 21.2, - 20.8, 20.5, 20.3, 20.2, 20, 19.9, 19.8, 19.8, 19.8, 19.8, 19.8, 19.7, 19.6, 19.5, - 19.3, 19.2, 19, 18.8, 18.5, 18.2, 17.9, 17.6, 17.1, 16.7, 16.2, 15.8, 15.5, 15.2, - 15.1, 14.9, 14.7, 14.7, 14.6, 14.6, 14.5, 14.4, 14.3, 14.1, 13.9, 13.7, 13.7, 13.7, - 13.7, 13.6, 13.6, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.2, 13.5, 14, 14.4, 14.5, 14.2, 14.2, 14.4, 14.8, 15.3, 15.6, 15.6, 15.4, - 15.2, 14.8, 14.3, 14.1, 13.8, 13.7, 13.6, 13.5, 13.5, 13.6, 13.9, 14, 14.2, 14.5, - 14.8, 15, 15.1, 15.1, 15, 14.9, 14.7, 14.5, 14.3, 14.2, 14.1, 14, 14, 13.9, 13.9, - 13.8, 13.8, 13.8, 13.8, 13.7, 13.5, 13.1, 12.8, 12.4, 11.9, 11.4, 10.9, 10.5, 10.3, - 10.2, 10.1, 10.1, 10.1, 10, 10.1, 10.3, 10.7, 11.2, 11.8, 12.4, 12.9, 13.4, 13.7, - 13.8, 13.8, 13.5, 13.2, 12.9, 12.6, 12.5, 12.5, 12.4, 12.5, 12.6, 12.6, 12.7, 12.9, - 13.2, 13.4, 13.5, 13.5, 13.5, 13.5, 13.3, 13.1, 12.9, 12.8, 12.7, 12.4, 12.2, 12.2, - 12.1, 12.1, 12.2, 12.4, 12.4, 12.3, 12.2, 11.9, 11.8, 11.9, 12, 12.1, 12.3, 12.6, - 12.9, 13.2, 13.7, 14.2, 14.4, 14.8, 15.1, 22.6, 22.6, 22.6, 22.6, 22.5, 22.3, 22.2, - 22, 21.7, 21.3, 20.9, 20.5, 20.2, 20.1, 19.9, 19.8, 19.7, 19.6, 19.6, 19.6, 19.5, - 19.5, 19.5, 19.5, 19.4, 19.3, 19.2, 18.9, 18.6, 18.3, 18.2, 17.9, 17.5, 17, 16.6, - 16.2, 15.8, 15.4, 15.1, 14.9, 14.7, 14.7, 14.6, 14.6, 14.5, 14.5, 14.4, 14.3, 14.1, - 13.9, 13.8, 13.8, 13.8, 13.7, 13.7, 13.6, 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.6, 13.8, 14.1, 14.2, 14.3, 14.2, 14.3, 14.7, 15, - 15.4, 15.7, 15.9, 15.6, 15.3, 14.9, 14.6, 14.3, 14.1, 13.9, 13.8, 13.7, 13.7, 13.8, - 14, 14.1, 14.2, 14.5, 14.9, 15.2, 15.4, 15.4, 15.3, 15.3, 15.2, 14.9, 14.7, 14.4, - 14.2, 14, 13.9, 13.8, 13.7, 13.6, 13.5, 13.5, 13.4, 13.3, 13, 12.7, 12.3, 11.9, 11.5, - 11.1, 10.7, 10.5, 10.3, 10.3, 10.2, 10.2, 10.1, 10.1, 10.4, 10.8, 11.5, 12, 12.5, 13, - 13.4, 13.7, 13.9, 13.8, 13.6, 13.4, 13.1, 12.8, 12.6, 12.6, 12.6, 12.7, 12.8, 12.9, - 12.9, 12.9, 13, 13.2, 13.4, 13.5, 13.4, 13.5, 13.5, 13.3, 13, 12.9, 12.8, 12.7, 12.5, - 12.4, 12.5, 12.4, 12.2, 12.3, 12.5, 12.5, 12.4, 12.3, 12.1, 12, 12, 12.1, 12.2, 12.4, - 12.7, 13, 13.3, 13.8, 14.2, 14.5, 14.9, 15.2, 22.5, 22.5, 22.4, 22.3, 22.2, 22, 21.9, - 21.6, 21.3, 20.9, 20.5, 20.2, 20, 19.8, 19.7, 19.5, 19.4, 19.4, 19.3, 19.3, 19.2, - 19.1, 19.2, 19.2, 19.2, 19.2, 19.1, 18.8, 18.5, 18.2, 18, 17.7, 17.4, 17, 16.5, 16, - 15.7, 15.3, 15, 14.8, 14.6, 14.5, 14.4, 14.2, 14.2, 14.2, 14.2, 14.1, 14, 13.8, 13.8, - 13.8, 13.9, 13.7, 13.7, 13.6, 13.5, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.4, 14, 14.1, 14.2, 14.3, 14.3, 14.5, 14.7, 15.2, 15.5, 15.7, 16, 16, - 15.8, 15.6, 15.2, 14.8, 14.6, 14.4, 14.3, 14.1, 14, 13.9, 14, 14.1, 14.1, 14.3, 14.6, - 15, 15.3, 15.6, 15.7, 15.8, 15.8, 15.7, 15.4, 15.1, 14.8, 14.4, 14.1, 13.9, 13.7, - 13.5, 13.4, 13.3, 13.1, 13, 12.8, 12.5, 12.2, 11.9, 11.6, 11.2, 10.9, 10.7, 10.5, - 10.3, 10.3, 10.3, 10.2, 10.1, 10.4, 11.1, 11.6, 12.2, 12.7, 13, 13.5, 13.7, 13.8, - 13.9, 13.8, 13.6, 13.4, 13.2, 12.9, 12.7, 12.7, 12.8, 12.9, 13, 13.1, 13.1, 13.1, - 13.1, 13.4, 13.5, 13.5, 13.5, 13.5, 13.4, 13.3, 13.1, 13, 12.9, 12.9, 12.8, 12.7, - 12.7, 12.5, 12.3, 12.3, 12.4, 12.5, 12.5, 12.4, 12.2, 12.1, 12.1, 12.2, 12.4, 12.7, - 12.9, 13.1, 13.4, 13.8, 14.2, 14.6, 15, 15.3, 22.5, 22.4, 22.2, 22, 21.8, 21.6, 21.5, - 21.2, 20.8, 20.4, 20.1, 19.8, 19.7, 19.5, 19.4, 19.2, 19.1, 19, 18.9, 18.8, 18.8, - 18.8, 18.8, 18.9, 18.9, 18.9, 18.9, 18.6, 18.3, 18, 17.8, 17.6, 17.4, 16.9, 16.4, - 15.9, 15.5, 15.2, 14.9, 14.8, 14.5, 14.4, 14.4, 14.2, 14.1, 14, 13.9, 14, 14, 14, - 13.9, 13.8, 13.8, 13.7, 13.7, 13.6, 13.5, 13.4, 13.4, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.4, 13.8, 14.2, 14.3, 14.4, 14.5, 14.6, 14.8, 15.1, 15.5, 15.8, 15.9, - 16.1, 16.1, 16, 15.7, 15.4, 15.1, 14.9, 14.8, 14.7, 14.5, 14.4, 14.2, 14.2, 14.2, - 14.3, 14.5, 14.8, 15.2, 15.6, 15.9, 16.1, 16.1, 16.2, 16, 15.8, 15.5, 15.2, 14.8, - 14.3, 14, 13.7, 13.4, 13.3, 13.1, 12.9, 12.7, 12.5, 12.2, 11.9, 11.7, 11.5, 11.1, - 10.8, 10.6, 10.5, 10.4, 10.4, 10.4, 10.3, 10.4, 11, 11.7, 12.3, 12.7, 13, 13.3, 13.8, - 13.7, 13.7, 13.7, 13.7, 13.6, 13.6, 13.4, 13.2, 13, 13, 13, 13.1, 13.3, 13.3, 13.3, - 13.3, 13.4, 13.6, 13.6, 13.6, 13.6, 13.7, 13.6, 13.4, 13.3, 13.3, 13.3, 13.3, 13.2, - 13, 12.9, 12.7, 12.4, 12.2, 12.4, 12.7, 12.6, 12.6, 12.4, 12.3, 12.4, 12.6, 12.8, 13, - 13.2, 13.3, 13.6, 13.9, 14.3, 14.7, 15, 15.2, 22.4, 22.2, 21.8, 21.7, 21.5, 21.3, - 21.1, 20.8, 20.3, 20, 19.7, 19.5, 19.3, 19.1, 18.9, 18.7, 18.5, 18.4, 18.4, 18.4, - 18.4, 18.4, 18.3, 18.3, 18.4, 18.4, 18.4, 18.2, 18, 17.7, 17.6, 17.4, 17.1, 16.7, - 16.3, 15.8, 15.4, 15, 14.9, 14.7, 14.5, 14.4, 14.3, 14.2, 14.1, 14, 13.9, 14.1, 14.1, - 14.2, 14.1, 14, 14, 13.9, 13.8, 13.7, 13.5, 13.5, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.3, 13.7, 14.1, 14.3, 14.6, 14.8, 14.9, 14.9, 15, 15.3, 15.6, 15.9, 16, 16.1, - 16.1, 16.1, 15.9, 15.6, 15.4, 15.3, 15.1, 15, 14.8, 14.6, 14.5, 14.4, 14.4, 14.5, - 14.7, 15.1, 15.6, 16, 16.3, 16.4, 16.5, 16.5, 16.3, 16.1, 15.9, 15.5, 15.1, 14.6, - 14.1, 13.8, 13.4, 13.2, 13, 12.8, 12.5, 12.3, 12, 11.8, 11.5, 11.3, 11, 10.8, 10.6, - 10.5, 10.4, 10.4, 10.5, 10.5, 10.9, 11.6, 12.1, 12.6, 12.9, 13.1, 13.2, 13.5, 13.5, - 13.6, 13.6, 13.7, 13.8, 13.8, 13.6, 13.5, 13.4, 13.3, 13.2, 13.3, 13.4, 13.4, 13.4, - 13.5, 13.7, 13.9, 14, 14, 13.9, 13.9, 13.8, 13.7, 13.6, 13.6, 13.6, 13.6, 13.5, 13.4, - 13.4, 13.1, 12.5, 12.3, 13.1, 13.2, 12.7, 12.7, 12.7, 12.7, 12.9, 13.1, 13.3, 13.4, - 13.5, 13.6, 13.8, 14, 14.3, 14.6, 14.9, 15.1, 21.7, 21.6, 21.5, 21.3, 21.2, 20.9, - 20.7, 20.3, 19.8, 19.4, 19.2, 18.9, 18.6, 18.4, 18.2, 18, 17.8, 17.7, 17.6, 17.6, - 17.7, 17.7, 17.7, 17.7, 17.7, 17.7, 17.7, 17.6, 17.5, 17.3, 17.2, 17, 16.8, 16.5, - 16.1, 15.6, 15.2, 14.9, 14.7, 14.6, 14.5, 14.4, 14.3, 14.1, 13.9, 14, 14.1, 14.1, - 14.1, 14.1, 14.1, 14, 13.9, 13.9, 13.8, 13.7, 13.6, 13.6, 13.5, 13.5, 13.2, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.7, 14.1, 14.3, 14.6, 14.9, 15, 15.1, 15.2, 15.3, 15.5, 15.7, 16, - 16.2, 16.2, 16.3, 16.2, 16, 15.9, 15.8, 15.6, 15.4, 15.2, 15, 14.9, 14.7, 14.7, 14.7, - 14.8, 15.1, 15.6, 15.9, 16.4, 16.6, 16.7, 16.8, 16.8, 16.6, 16.4, 16.1, 15.7, 15.2, - 14.7, 14.2, 13.8, 13.5, 13.2, 12.9, 12.6, 12.4, 12.2, 11.9, 11.7, 11.4, 11.2, 10.9, - 10.7, 10.6, 10.5, 10.5, 10.5, 10.5, 10.8, 11.4, 12, 12.5, 12.9, 13, 13.1, 13.2, 13.3, - 13.4, 13.5, 13.6, 13.7, 13.9, 13.9, 13.8, 13.8, 13.7, 13.5, 13.4, 13.5, 13.5, 13.6, - 13.6, 13.9, 14.1, 14.3, 14.4, 14.4, 14.3, 14.2, 14.1, 13.9, 13.9, 13.9, 13.8, 13.8, - 13.9, 13.9, 13.8, 13.5, 12.8, 12.4, 13, 13, 12.8, 12.9, 13.1, 13.3, 13.4, 13.6, 13.6, - 13.7, 13.7, 13.8, 14, 14.1, 14.4, 14.7, 15, 15.1, 21.2, 21.1, 21, 20.8, 20.6, 20.4, - 20.1, 19.8, 19.3, 18.9, 18.5, 18.2, 17.9, 17.6, 17.4, 17.3, 17.2, 17, 17, 17, 17.1, - 17.1, 17.1, 17, 16.9, 17, 17.1, 17, 17, 16.9, 16.9, 16.8, 16.6, 16.2, 15.8, 15.4, - 15.1, 14.9, 14.7, 14.6, 14.5, 14.3, 14, 13.9, 13.9, 14, 14, 14.1, 14, 13.9, 14.1, 14, - 13.9, 13.9, 13.8, 13.7, 13.7, 13.6, 13.6, 13.5, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.9, - 14.2, 14.5, 14.9, 15.2, 15.5, 15.7, 15.8, 15.9, 15.9, 15.9, 16.1, 16.3, 16.4, 16.4, - 16.3, 16.2, 16.2, 16.1, 15.8, 15.5, 15.3, 15.2, 15.1, 15, 14.9, 15, 15.1, 15.4, 15.7, - 16.1, 16.5, 16.7, 16.9, 17, 17, 16.9, 16.6, 16.2, 15.8, 15.3, 14.8, 14.3, 13.9, 13.6, - 13.2, 12.9, 12.6, 12.4, 12.1, 11.9, 11.6, 11.3, 11.2, 10.9, 10.8, 10.7, 10.6, 10.6, - 10.6, 10.8, 11.3, 11.9, 12.4, 12.7, 12.9, 13, 13.1, 13.1, 13.2, 13.3, 13.5, 13.6, - 13.7, 13.9, 13.9, 13.9, 13.9, 13.8, 13.7, 13.6, 13.5, 13.5, 13.6, 13.8, 14.1, 14.3, - 14.5, 14.6, 14.6, 14.5, 14.4, 14.2, 14.1, 14.1, 14.1, 14.1, 14.2, 14.2, 14.3, 14.2, - 13.8, 13.3, 12.7, 12.5, 12.7, 12.9, 13.2, 13.5, 13.8, 13.9, 13.9, 13.9, 13.9, 14, 14, - 14.1, 14.4, 14.6, 14.8, 15.1, 15.1, 20.7, 20.6, 20.5, 20.3, 20.1, 19.8, 19.6, 19.2, - 18.8, 18.4, 18, 17.6, 17.2, 17, 16.9, 16.8, 16.8, 16.7, 16.6, 16.7, 16.9, 16.8, 16.5, - 16.6, 16.6, 16.5, 16.5, 16.5, 16.5, 16.5, 16.5, 16.5, 16.3, 15.9, 15.6, 15.2, 15.1, - 14.8, 14.7, 14.6, 14.5, 14.3, 14.1, 14.1, 13.9, 13.8, 13.9, 14, 13.9, 13.9, 14.1, - 14.1, 14, 13.9, 13.9, 13.9, 13.7, 13.7, 13.6, 13.5, 13.5, 13.4, 13.2, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.8, - 14.1, 14.3, 14.7, 15.2, 15.6, 16.2, 16.3, 16.4, 16.4, 16.3, 16.2, 16.3, 16.4, 16.4, - 16.4, 16.4, 16.3, 16.3, 16.1, 15.9, 15.7, 15.5, 15.4, 15.3, 15.2, 15.2, 15.3, 15.5, - 15.7, 16, 16.3, 16.7, 17, 17.1, 17.1, 17.2, 17.1, 16.7, 16.4, 15.9, 15.4, 14.9, 14.3, - 13.9, 13.6, 13.3, 12.9, 12.6, 12.4, 12.1, 11.8, 11.6, 11.3, 11.1, 10.9, 10.8, 10.7, - 10.7, 10.6, 10.7, 11, 11.7, 12.2, 12.6, 12.8, 13, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, - 13.5, 13.8, 14, 13.9, 13.9, 13.9, 13.9, 13.8, 13.6, 13.5, 13.5, 13.6, 13.9, 14.3, - 14.4, 14.6, 14.7, 14.7, 14.6, 14.5, 14.4, 14.3, 14.2, 14.2, 14.3, 14.5, 14.6, 14.6, - 14.4, 14, 13.6, 13.4, 13.1, 13.3, 13.5, 13.7, 14, 14.2, 14.2, 14.2, 14.1, 14.2, 14.2, - 14.2, 14.2, 14.4, 14.6, 14.9, 15, 14.9, 20.2, 20, 19.9, 19.7, 19.5, 19.2, 19, 18.7, - 18.3, 17.8, 17.5, 17.1, 16.9, 16.8, 16.8, 16.8, 16.8, 16.7, 16.5, 16.5, 16.6, 16.5, - 16.3, 16.2, 16.3, 16.1, 16.1, 16.1, 16.1, 16.2, 16.2, 16.1, 15.9, 15.6, 15.4, 15.2, - 15, 14.8, 14.7, 14.4, 14.3, 14.4, 14.3, 14.1, 13.9, 13.8, 13.9, 13.9, 13.9, 13.9, 14, - 14.1, 14, 14, 14, 14, 13.8, 13.7, 13.7, 13.6, 13.5, 13.6, 13.4, 13.2, 13.2, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.9, 14.1, 14.3, - 14.5, 14.9, 15.5, 16, 16.5, 16.7, 16.8, 16.7, 16.5, 16.4, 16.5, 16.6, 16.5, 16.4, - 16.4, 16.3, 16.3, 16.2, 16.1, 16, 15.8, 15.7, 15.6, 15.5, 15.6, 15.7, 15.9, 16.1, - 16.5, 16.8, 17.1, 17.2, 17.3, 17.4, 17.4, 17.2, 16.9, 16.4, 15.9, 15.3, 14.8, 14.3, - 13.9, 13.5, 13.4, 13, 12.7, 12.5, 12.2, 11.8, 11.6, 11.3, 11, 10.9, 10.8, 10.8, 10.8, - 10.7, 10.8, 11.3, 12, 12.4, 12.6, 12.8, 13, 13.1, 13.1, 13, 13.1, 13.2, 13.4, 13.6, - 13.8, 13.9, 13.9, 13.8, 13.8, 13.8, 13.7, 13.6, 13.4, 13.4, 13.6, 14, 14.3, 14.5, - 14.6, 14.7, 14.7, 14.6, 14.5, 14.5, 14.4, 14.3, 14.4, 14.7, 14.9, 15, 14.9, 14.6, - 14.2, 13.8, 13.6, 13.5, 13.6, 13.8, 14, 14.3, 14.4, 14.5, 14.5, 14.4, 14.3, 14.3, - 14.2, 14.3, 14.5, 14.7, 14.9, 14.9, 14.8, 19.6, 19.4, 19.2, 18.9, 18.6, 18.3, 18.1, - 17.8, 17.4, 17.1, 17, 16.9, 16.9, 16.8, 16.8, 16.9, 16.7, 16.6, 16.5, 16.4, 16.2, - 16.2, 16.1, 16, 16, 16, 15.9, 16, 16, 16, 16, 15.9, 15.6, 15.4, 15.3, 15.2, 15.1, - 14.9, 14.6, 14.4, 14.2, 14.3, 14.5, 14.3, 14, 13.9, 14, 14.1, 14, 13.9, 13.9, 14.1, - 14.1, 14, 14, 13.9, 13.8, 13.8, 13.7, 13.6, 13.6, 13.5, 13.4, 13.4, 13.3, 13.2, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.9, 14.1, 14.2, 14.4, 14.7, 14.9, - 15.3, 15.7, 16.2, 16.6, 16.9, 17, 16.8, 16.7, 16.7, 16.6, 16.6, 16.6, 16.5, 16.5, - 16.4, 16.5, 16.5, 16.6, 16.6, 16.4, 16.3, 16.2, 16.1, 16.1, 16.2, 16.4, 16.6, 17, - 17.3, 17.4, 17.5, 17.6, 17.6, 17.6, 17.3, 17, 16.5, 15.9, 15.3, 14.7, 14.3, 13.9, - 13.6, 13.4, 13.2, 12.8, 12.5, 12.3, 12, 11.7, 11.3, 11, 10.9, 10.8, 10.8, 10.8, 10.9, - 11, 11.6, 12.2, 12.6, 12.7, 12.8, 12.9, 12.9, 13, 13, 13.1, 13.3, 13.6, 13.7, 13.8, - 13.8, 13.8, 13.8, 13.7, 13.6, 13.5, 13.4, 13.3, 13.4, 13.6, 13.8, 14.1, 14.3, 14.5, - 14.6, 14.6, 14.6, 14.5, 14.4, 14.3, 14.4, 14.7, 15, 15.1, 15.1, 14.9, 14.7, 14.3, - 13.9, 13.5, 13.5, 13.7, 13.9, 14.2, 14.5, 14.6, 14.8, 14.8, 14.6, 14.5, 14.4, 14.3, - 14.3, 14.6, 14.8, 14.8, 14.8, 14.6, 18.9, 18.7, 18.5, 18.2, 17.9, 17.5, 17.3, 17.1, - 16.9, 16.7, 16.8, 16.8, 16.8, 16.7, 16.5, 16.5, 16.4, 16.2, 16.2, 16.2, 16.1, 16, 16, - 16, 16, 15.9, 15.8, 15.8, 16, 15.9, 15.7, 15.6, 15.4, 15.3, 15.2, 15.3, 15.2, 14.8, - 14.6, 14.5, 14.3, 14.1, 14.3, 14.5, 14.3, 14, 14.1, 14, 13.9, 13.9, 13.8, 14, 14.1, - 14, 14, 13.9, 13.8, 13.8, 13.6, 13.6, 13.6, 13.5, 13.4, 13.4, 13.3, 13.2, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.3, 13.7, 14.1, 14.4, 14.8, 15.1, 15.2, 15.3, 15.6, 15.8, - 16, 16.4, 16.7, 16.9, 16.9, 16.8, 16.8, 16.7, 16.7, 16.7, 16.7, 16.7, 16.7, 16.7, - 16.8, 17, 17.2, 17.2, 17.1, 17, 16.9, 16.8, 16.8, 16.8, 16.9, 17.1, 17.3, 17.6, 17.7, - 17.8, 17.9, 17.9, 17.7, 17.4, 17, 16.4, 15.8, 15.2, 14.7, 14.3, 14, 13.7, 13.4, 13.2, - 13, 12.6, 12.4, 12.2, 11.8, 11.4, 11, 10.9, 10.9, 10.9, 10.9, 11, 11.6, 12.2, 12.5, - 12.8, 12.9, 12.9, 12.9, 12.9, 12.9, 12.9, 13.1, 13.4, 13.7, 13.7, 13.7, 13.7, 13.7, - 13.7, 13.5, 13.3, 13.2, 13.2, 13.3, 13.4, 13.6, 13.8, 13.9, 14.1, 14.4, 14.5, 14.5, - 14.5, 14.4, 14.3, 14.3, 14.4, 14.6, 14.9, 14.9, 14.8, 14.8, 14.7, 14.4, 14, 13.6, - 13.6, 13.8, 14.1, 14.5, 14.8, 14.9, 14.9, 14.8, 14.7, 14.5, 14.4, 14.3, 14.3, 14.5, - 14.6, 14.6, 14.4, 14.1, 18.5, 18.3, 18, 17.6, 17.4, 17.1, 16.9, 16.9, 16.8, 16.8, - 16.7, 16.7, 16.5, 16.4, 16.4, 16.3, 16, 16, 16, 15.9, 15.9, 15.9, 15.8, 15.8, 15.7, - 15.6, 15.6, 15.7, 15.9, 16, 15.7, 15.5, 15.3, 15.2, 15, 14.9, 14.7, 14.5, 14.4, 14.5, - 14.4, 14.2, 14.2, 14.5, 14.5, 14.2, 14.1, 14.1, 13.9, 13.9, 13.7, 13.8, 14.1, 14.1, - 14, 13.8, 13.7, 13.7, 13.6, 13.6, 13.6, 13.5, 13.4, 13.4, 13.4, 13.3, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.4, 13.8, 14.1, 14.5, 15.2, 15.5, 15.8, 15.9, 16, 16.2, 16.3, - 16.4, 16.6, 16.9, 16.9, 16.9, 16.9, 16.9, 16.8, 16.8, 16.9, 16.9, 17, 17.1, 17.2, - 17.3, 17.5, 17.7, 17.7, 17.5, 17.4, 17.4, 17.3, 17.3, 17.2, 17.3, 17.4, 17.7, 17.9, - 18.1, 18.2, 18.2, 18.1, 17.8, 17.4, 16.8, 16.2, 15.6, 15.2, 14.8, 14.5, 14.1, 13.9, - 13.6, 13.3, 13.1, 12.7, 12.4, 12.3, 12, 11.5, 11.2, 11, 11, 11, 10.9, 11.2, 12, 12.6, - 12.8, 13, 13, 13, 12.9, 12.9, 12.9, 12.9, 13.1, 13.4, 13.6, 13.7, 13.6, 13.5, 13.5, - 13.3, 13.1, 13, 13, 13.1, 13.3, 13.5, 13.6, 13.8, 13.8, 13.9, 14.1, 14.2, 14.4, 14.4, - 14.3, 14.3, 14.3, 14.4, 14.6, 14.7, 14.7, 14.7, 14.6, 14.5, 14.3, 14, 13.8, 13.8, - 14.1, 14.4, 14.8, 15, 15.1, 15, 14.9, 14.8, 14.6, 14.3, 14.3, 14.3, 14.4, 14.4, 14.2, - 14, 13.8, 18, 17.8, 17.4, 17, 16.9, 16.8, 16.7, 16.8, 16.8, 16.8, 16.6, 16.4, 16.3, - 16.3, 16.2, 16.1, 15.9, 15.9, 15.8, 15.8, 15.8, 15.8, 15.7, 15.6, 15.5, 15.4, 15.5, - 15.6, 15.7, 15.8, 15.6, 15.4, 15.3, 15.2, 14.8, 14.6, 14.5, 14.3, 14.1, 14.4, 14.5, - 14.3, 14.2, 14.4, 14.5, 14.5, 14.3, 14.1, 14, 14, 13.9, 13.6, 13.8, 14.1, 13.9, 13.8, - 13.7, 13.7, 13.7, 13.7, 13.6, 13.5, 13.5, 13.5, 13.4, 13.4, 13.3, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, - 13.7, 14.1, 14.2, 14.7, 15.5, 16, 16.3, 16.9, 17, 16.9, 17, 17, 17, 17, 17, 16.9, 17, - 17.1, 17, 17, 17.1, 17.2, 17.3, 17.4, 17.6, 17.7, 17.9, 17.9, 18, 17.9, 17.7, 17.6, - 17.5, 17.5, 17.4, 17.4, 17.6, 17.8, 18, 18.2, 18.4, 18.5, 18.4, 18.1, 17.7, 17.1, - 16.5, 16, 15.6, 15.3, 15, 14.8, 14.4, 14.1, 13.7, 13.4, 13.1, 12.7, 12.4, 12.2, 11.9, - 11.5, 11.2, 11.1, 11, 11, 11.1, 11.7, 12.5, 12.9, 13.2, 13.3, 13.1, 13, 12.9, 12.9, - 12.9, 13, 13.2, 13.4, 13.5, 13.6, 13.5, 13.3, 13.1, 12.9, 12.7, 12.7, 12.8, 13.1, - 13.4, 13.5, 13.6, 13.6, 13.5, 13.7, 13.8, 13.9, 14, 14.1, 14.1, 14.2, 14.3, 14.4, - 14.5, 14.5, 14.4, 14.4, 14.3, 14.2, 14.1, 14, 14, 14.1, 14.4, 14.6, 14.8, 15, 15, - 14.9, 14.9, 14.9, 14.5, 14.3, 14.2, 14.2, 14.2, 14.1, 14, 13.8, 13.8, 17.5, 17.2, 17, - 16.8, 16.7, 16.8, 16.8, 16.7, 16.7, 16.7, 16.3, 16.1, 16.1, 16.1, 16, 16, 15.9, 15.8, - 15.8, 15.8, 15.7, 15.7, 15.6, 15.5, 15.4, 15.4, 15.4, 15.5, 15.6, 15.7, 15.7, 15.5, - 15.3, 15.3, 14.9, 14.7, 14.6, 14.5, 14.3, 14.4, 14.5, 14.4, 14.3, 14.2, 14.3, 14.5, - 14.5, 14.2, 14.1, 14, 13.9, 13.6, 13.4, 13.7, 13.9, 13.8, 13.7, 13.7, 13.7, 13.7, - 13.6, 13.5, 13.5, 13.6, 13.5, 13.4, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 14, 14.3, 14.5, 15.1, 16, 16.6, - 16.8, 17.3, 17.9, 17.8, 17.7, 17.7, 17.7, 17.4, 17.2, 17.1, 17, 17, 17.1, 17.1, 17.3, - 17.5, 17.7, 17.9, 18, 18.2, 18.3, 18.3, 18.3, 18.2, 18.1, 17.9, 17.8, 17.8, 17.7, - 17.6, 17.6, 17.8, 18, 18.3, 18.5, 18.6, 18.7, 18.5, 17.9, 17.5, 17, 16.4, 15.9, 15.6, - 15.4, 15.2, 14.9, 14.6, 14.2, 13.8, 13.4, 13, 12.8, 12.3, 11.9, 11.7, 11.4, 11.2, - 11.1, 11.1, 11.1, 11.5, 12.2, 12.9, 13.3, 13.5, 13.5, 13.2, 12.9, 12.8, 12.8, 12.9, - 13, 13.3, 13.3, 13.4, 13.4, 13.3, 13.1, 12.8, 12.6, 12.5, 12.6, 12.9, 13.2, 13.4, - 13.5, 13.4, 13.3, 13.3, 13.4, 13.5, 13.6, 13.7, 13.8, 14, 14.1, 14.3, 14.3, 14.3, - 14.2, 14.2, 14, 13.9, 14, 14, 13.9, 14.1, 14.3, 14.5, 14.6, 14.8, 14.9, 14.9, 14.9, - 14.8, 14.7, 14.5, 14.3, 14.2, 14.2, 14.1, 14, 13.9, 13.8, 13.8, 16.8, 16.7, 16.8, - 16.7, 16.6, 16.7, 16.7, 16.5, 16.4, 16.4, 16.2, 16, 16, 15.9, 15.9, 15.9, 15.9, 15.8, - 15.8, 15.8, 15.7, 15.6, 15.5, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.5, 15.7, 15.5, - 15.3, 15.1, 14.7, 14.6, 14.6, 14.5, 14.4, 14.4, 14.5, 14.4, 14.2, 14.2, 14.2, 14.3, - 14.6, 14.4, 14.1, 14.1, 13.9, 13.5, 13.4, 13.7, 14, 13.8, 13.7, 13.7, 13.7, 13.7, - 13.6, 13.6, 13.5, 13.6, 13.6, 13.4, 13.4, 13.4, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.8, 14.1, 14.4, 14.9, 15.6, 16.2, 16.9, - 17.3, 17.7, 18.1, 18.5, 18.6, 18.6, 18.4, 18.3, 17.9, 17.5, 17.3, 17.1, 17, 17.2, - 17.4, 17.7, 18.1, 18.3, 18.5, 18.6, 18.7, 18.8, 18.8, 18.7, 18.5, 18.3, 18.1, 17.9, - 17.9, 17.8, 17.7, 17.7, 17.9, 18.2, 18.5, 18.7, 18.8, 18.7, 18.3, 17.7, 17.3, 16.9, - 16.4, 16, 15.8, 15.6, 15.4, 15.1, 14.8, 14.3, 13.8, 13.4, 12.9, 12.7, 12.4, 11.8, - 11.5, 11.4, 11.3, 11.3, 11.3, 11.4, 11.9, 12.5, 13.1, 13.4, 13.5, 13.4, 13.1, 12.7, - 12.6, 12.6, 12.8, 13, 13.2, 13.2, 13.2, 13.2, 13.1, 12.8, 12.5, 12.3, 12.4, 12.7, - 12.9, 13.2, 13.3, 13.3, 13.1, 12.8, 13, 13.2, 13.3, 13.3, 13.4, 13.6, 13.9, 14.1, - 14.2, 14.2, 14.1, 14, 13.9, 13.7, 13.7, 13.7, 13.8, 13.9, 14.2, 14.5, 14.5, 14.6, - 14.7, 14.8, 15, 14.9, 14.8, 14.7, 14.5, 14.4, 14.3, 14.2, 14.1, 14.1, 14, 13.9, 13.9, - 16.6, 16.5, 16.6, 16.6, 16.4, 16.3, 16.3, 16.3, 16.2, 16.1, 16, 16, 16, 15.9, 15.8, - 15.8, 15.8, 15.8, 15.8, 15.8, 15.6, 15.5, 15.4, 15.4, 15.3, 15.4, 15.4, 15.3, 15.3, - 15.3, 15.6, 15.7, 15.2, 14.9, 14.4, 14.3, 14.6, 14.7, 14.5, 14.5, 14.4, 14.4, 14.3, - 14.2, 14.2, 14.2, 14.4, 14.6, 14.3, 14, 13.9, 13.5, 13.3, 13.7, 14, 13.8, 13.8, 13.7, - 13.7, 13.7, 13.7, 13.6, 13.5, 13.5, 13.5, 13.5, 13.4, 13.5, 13.5, 13.4, 13.4, 13.2, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.4, 13.8, 14.2, 14.6, 15.2, 16, 16.7, - 17.2, 17.9, 18.5, 18.9, 19.1, 19.3, 19.3, 19.2, 19, 18.8, 18.4, 17.9, 17.7, 17.5, - 17.3, 17.5, 17.8, 18.3, 18.7, 19, 19.2, 19.2, 19.2, 19.2, 19.1, 19, 18.8, 18.5, 18.1, - 17.9, 17.8, 17.7, 17.7, 17.8, 18, 18.4, 18.7, 18.9, 18.9, 18.7, 18.1, 17.6, 17.2, - 16.9, 16.5, 16.2, 16, 15.9, 15.6, 15.4, 15, 14.6, 14, 13.4, 13, 12.6, 12.4, 11.9, - 11.5, 11.4, 11.4, 11.5, 11.5, 11.7, 12.2, 12.8, 13.2, 13.4, 13.4, 13.3, 13, 12.7, - 12.5, 12.6, 12.7, 12.9, 13, 13, 13, 12.9, 12.8, 12.5, 12.2, 12.2, 12.3, 12.6, 12.8, - 13, 13, 12.8, 12.6, 12.5, 12.6, 12.8, 12.9, 13, 13.1, 13.4, 13.8, 13.9, 14, 13.9, - 13.9, 13.8, 13.7, 13.6, 13.7, 13.7, 13.8, 14.1, 14.4, 14.5, 14.6, 14.6, 14.7, 14.8, - 15, 14.9, 14.8, 14.7, 14.5, 14.4, 14.3, 14.2, 14.2, 14.2, 14.2, 14.2, 14.1, 16.4, - 16.4, 16.5, 16.4, 16.2, 16.1, 16.1, 16.1, 16.1, 16, 15.9, 15.9, 15.9, 15.8, 15.8, - 15.8, 15.8, 15.8, 15.8, 15.7, 15.5, 15.4, 15.4, 15.4, 15.3, 15.4, 15.3, 15.3, 15.3, - 15.3, 15.5, 15.7, 15.2, 14.9, 14.6, 14.3, 14.5, 14.7, 14.6, 14.4, 14.3, 14.4, 14.4, - 14.3, 14.4, 14.3, 14.2, 14.5, 14.4, 14.1, 13.9, 13.5, 13.3, 13.7, 14, 13.8, 13.8, - 13.7, 13.7, 13.7, 13.7, 13.6, 13.6, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.5, 13.5, - 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.2, 13.2, 13.2, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.3, 13.6, 14, 14.4, 14.8, 15.4, 16, 16.7, 17.4, - 18, 18.6, 19.1, 19.5, 19.7, 19.8, 19.9, 19.7, 19.5, 19.3, 18.9, 18.4, 18.1, 17.8, - 17.6, 17.9, 18.3, 18.8, 19.2, 19.5, 19.7, 19.8, 19.7, 19.6, 19.5, 19.2, 19, 18.6, - 18.1, 17.8, 17.8, 17.7, 17.7, 17.9, 18.1, 18.5, 18.8, 19.1, 19, 18.7, 18.1, 17.6, - 17.2, 16.9, 16.6, 16.4, 16.2, 16, 15.8, 15.6, 15.2, 14.8, 14.2, 13.7, 13.2, 12.8, - 12.5, 12.1, 11.5, 11.5, 11.5, 11.5, 11.7, 12.2, 12.7, 13.1, 13.3, 13.3, 13.2, 13.1, - 12.8, 12.6, 12.5, 12.5, 12.6, 12.7, 12.7, 12.7, 12.6, 12.6, 12.4, 12.2, 12, 12, 12.2, - 12.5, 12.7, 12.7, 12.6, 12.4, 12.3, 12.3, 12.4, 12.5, 12.6, 12.7, 12.9, 13.1, 13.4, - 13.6, 13.7, 13.7, 13.7, 13.7, 13.6, 13.6, 13.7, 13.7, 14, 14.2, 14.4, 14.4, 14.4, - 14.5, 14.6, 14.6, 14.7, 14.7, 14.6, 14.5, 14.4, 14.4, 14.4, 14.4, 14.3, 14.3, 14.4, - 14.4, 14.4, 16.4, 16.3, 16.3, 16.2, 16.1, 16.1, 16.1, 16.2, 16.1, 16, 15.8, 15.8, - 15.8, 15.9, 15.9, 15.9, 16, 15.9, 15.8, 15.6, 15.5, 15.4, 15.4, 15.4, 15.3, 15.4, - 15.3, 15.3, 15.3, 15.3, 15.4, 15.7, 15.5, 15.1, 14.9, 14.6, 14.5, 14.7, 14.7, 14.5, - 14.4, 14.5, 14.5, 14.4, 14.5, 14.3, 14.1, 14.5, 14.4, 14.2, 14, 13.7, 13.4, 13.8, 14, - 13.9, 13.8, 13.7, 13.7, 13.7, 13.7, 13.7, 13.6, 13.5, 13.6, 13.6, 13.6, 13.6, 13.6, - 13.6, 13.5, 13.4, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.2, 13.3, 13.4, 13.5, - 13.6, 13.5, 13.4, 13.3, 13.2, 13.2, 13.2, 13.6, 13.9, 14.2, 14.5, 15, 15.7, 16.4, - 16.9, 17.5, 18.1, 18.7, 19.2, 19.6, 19.9, 20, 20.2, 20.2, 20.1, 19.9, 19.7, 19.2, - 18.5, 18.2, 17.9, 18, 18.3, 18.8, 19.3, 19.6, 19.9, 20.1, 20.2, 20.2, 20, 19.7, 19.3, - 19, 18.5, 18.1, 17.9, 17.8, 17.8, 17.8, 17.9, 18.2, 18.6, 19, 19.2, 19, 18.7, 18.3, - 17.8, 17.4, 17.1, 16.9, 16.6, 16.4, 16.2, 16, 15.8, 15.4, 15, 14.5, 14, 13.6, 13.1, - 12.7, 12.2, 11.6, 11.5, 11.6, 11.7, 12.1, 12.8, 13.2, 13.5, 13.4, 13.3, 13.1, 12.9, - 12.7, 12.5, 12.5, 12.5, 12.4, 12.4, 12.5, 12.4, 12.3, 12.2, 12, 11.9, 11.9, 12, 12.3, - 12.5, 12.7, 12.6, 12.4, 12.3, 12.2, 12.3, 12.4, 12.4, 12.4, 12.6, 12.7, 12.9, 13.1, - 13.4, 13.5, 13.5, 13.6, 13.6, 13.6, 13.6, 13.7, 13.9, 14.2, 14.3, 14.4, 14.3, 14.2, - 14.3, 14.4, 14.4, 14.4, 14.4, 14.4, 14.4, 14.4, 14.5, 14.8, 14.7, 14.6, 14.6, 14.7, - 14.7, 14.8, 16.3, 16.3, 16.2, 16.2, 16.1, 16.1, 16.1, 16.1, 16, 15.9, 15.8, 15.8, - 15.8, 15.9, 15.9, 16, 15.9, 15.7, 15.6, 15.5, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, - 15.4, 15.3, 15.3, 15.3, 15.3, 15.5, 15.6, 15.4, 15.1, 14.8, 14.8, 14.8, 14.8, 14.7, - 14.6, 14.5, 14.5, 14.4, 14.3, 14.4, 14.2, 14.5, 14.5, 14.2, 14.1, 13.7, 13.5, 14, 14, - 14, 13.8, 13.8, 13.7, 13.7, 13.7, 13.7, 13.6, 13.6, 13.5, 13.5, 13.6, 13.6, 13.6, - 13.6, 13.5, 13.4, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.2, 13.2, 13.4, 13.5, 13.5, 13.5, 13.5, 13.6, - 13.7, 13.6, 13.5, 13.6, 13.5, 13.4, 13.4, 13.9, 14.2, 14.5, 15.1, 15.8, 16.5, 17.1, - 17.6, 18.3, 18.9, 19.4, 19.9, 20.2, 20.3, 20.4, 20.5, 20.5, 20.5, 20.2, 19.9, 19.4, - 18.7, 18.2, 18.1, 18.4, 18.8, 19.3, 19.7, 20, 20.3, 20.5, 20.5, 20.4, 20.2, 19.8, - 19.4, 19, 18.4, 18, 17.9, 17.8, 17.8, 17.9, 18.1, 18.4, 18.8, 19.1, 19.2, 19.1, 18.8, - 18.4, 18, 17.6, 17.4, 17.1, 16.9, 16.6, 16.3, 16.1, 15.8, 15.5, 15.2, 14.8, 14.4, 14, - 13.5, 12.8, 12.2, 11.7, 11.6, 11.7, 12, 12.6, 13.2, 13.5, 13.6, 13.5, 13.3, 13.1, - 12.8, 12.6, 12.5, 12.3, 12.2, 12.2, 12.2, 12.3, 12.2, 12.1, 11.9, 11.8, 11.9, 11.9, - 12.1, 12.5, 12.7, 12.7, 12.7, 12.5, 12.4, 12.3, 12.3, 12.4, 12.4, 12.5, 12.6, 12.8, - 13.1, 13.3, 13.4, 13.5, 13.5, 13.6, 13.6, 13.6, 13.7, 13.8, 14.1, 14.3, 14.4, 14.4, - 14.3, 14.2, 14.2, 14.1, 14.2, 14.2, 14.3, 14.3, 14.3, 14.5, 14.6, 14.9, 15, 15, 15.1, - 15.1, 15.2, 15.2, 16.2, 16.2, 16.2, 16.3, 16.2, 16.2, 16.1, 16, 15.9, 15.9, 15.8, - 15.8, 15.8, 15.9, 16, 15.9, 15.8, 15.6, 15.5, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, - 15.4, 15.4, 15.3, 15.3, 15.3, 15.3, 15.4, 15.6, 15.7, 15.5, 15.1, 15.1, 15.1, 15.1, - 14.9, 14.8, 14.6, 14.5, 14.5, 14.3, 14.2, 14.1, 14.6, 14.6, 14.2, 14, 13.6, 13.7, 14, - 13.9, 13.9, 13.8, 13.8, 13.7, 13.7, 13.7, 13.6, 13.6, 13.6, 13.5, 13.5, 13.5, 13.6, - 13.6, 13.5, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.3, 13.4, 13.5, 13.4, 13.6, 13.7, 13.8, 13.7, 13.7, - 13.6, 13.6, 13.6, 13.5, 13.6, 13.6, 13.7, 13.9, 14.2, 14.6, 15.2, 15.9, 16.5, 17.1, - 17.8, 18.5, 19.1, 19.6, 20.1, 20.5, 20.7, 20.7, 20.7, 20.7, 20.8, 20.8, 20.6, 20, - 19.5, 19, 18.7, 18.7, 19.1, 19.4, 19.8, 20.1, 20.4, 20.7, 20.8, 20.7, 20.6, 20.4, 20, - 19.4, 19, 18.3, 18, 17.9, 17.9, 18, 18, 18.2, 18.5, 19, 19.3, 19.3, 19.3, 19.1, 18.6, - 18.2, 18, 17.7, 17.3, 17.1, 16.7, 16.4, 16.1, 15.8, 15.7, 15.3, 15, 14.6, 14.1, 13.6, - 12.9, 12.2, 11.7, 11.6, 11.9, 12.5, 13.2, 13.4, 13.6, 13.7, 13.6, 13.4, 13.1, 12.8, - 12.6, 12.4, 12.2, 12, 12, 12.1, 12.1, 11.9, 11.8, 11.8, 11.8, 11.9, 12, 12.2, 12.7, - 13, 13.1, 13, 12.8, 12.6, 12.4, 12.3, 12.4, 12.5, 12.6, 12.9, 13, 13.3, 13.5, 13.5, - 13.6, 13.7, 13.7, 13.7, 13.8, 13.9, 14.1, 14.3, 14.4, 14.4, 14.3, 14.2, 14.1, 14, 14, - 14, 14, 14.2, 14.2, 14.3, 14.6, 14.9, 15.1, 15.3, 15.5, 15.7, 15.7, 15.8, 15.8, 16.3, - 16.3, 16.2, 16.1, 16.1, 16.2, 16, 16, 15.9, 15.9, 15.8, 15.8, 15.8, 15.9, 16, 15.9, - 15.8, 15.6, 15.5, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.3, 15.3, - 15.3, 15.3, 15.4, 15.7, 15.7, 15.5, 15.5, 15.5, 15.4, 15.2, 15.1, 15, 14.5, 14.3, - 14.3, 14.2, 14.1, 14.7, 14.8, 14.3, 13.8, 13.7, 13.8, 14, 13.9, 13.8, 13.8, 13.7, - 13.7, 13.7, 13.6, 13.6, 13.6, 13.6, 13.5, 13.5, 13.5, 13.4, 13.4, 13.4, 13.2, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, - 13.4, 13.6, 13.7, 13.7, 13.7, 13.9, 13.9, 13.9, 13.9, 13.7, 13.6, 13.6, 13.5, 13.6, - 13.7, 13.9, 14.2, 14.4, 14.7, 15.3, 16, 16.7, 17.4, 17.9, 18.5, 19.1, 19.6, 20, 20.5, - 20.8, 20.9, 21, 20.9, 20.9, 20.9, 20.9, 20.8, 20.3, 19.7, 19.3, 19.1, 19.3, 19.7, 20, - 20.2, 20.4, 20.6, 20.8, 20.9, 20.8, 20.6, 20.3, 19.9, 19.3, 18.8, 18.3, 18.1, 18, - 18.1, 18.1, 18.3, 18.5, 18.8, 19.2, 19.5, 19.5, 19.5, 19.3, 18.8, 18.5, 18.3, 18, - 17.7, 17.3, 16.9, 16.5, 16.1, 15.9, 15.7, 15.4, 15, 14.5, 14.1, 13.5, 12.7, 12.1, - 11.7, 11.8, 12.2, 12.8, 13.4, 13.5, 13.6, 13.6, 13.6, 13.4, 13.2, 12.9, 12.6, 12.2, - 12, 11.9, 11.9, 11.9, 11.9, 11.8, 11.7, 11.7, 11.8, 11.9, 12.1, 12.6, 13, 13.4, 13.7, - 13.5, 13.2, 13, 12.7, 12.5, 12.5, 12.6, 12.9, 13.1, 13.3, 13.5, 13.6, 13.8, 13.8, - 13.9, 13.9, 14, 14.1, 14.3, 14.4, 14.6, 14.6, 14.5, 14.4, 14.2, 14.1, 13.9, 13.9, 14, - 14, 14.1, 14.2, 14.4, 14.8, 15.1, 15.4, 15.6, 15.8, 16, 16.1, 16.2, 16.2, 16.2, 16.2, - 16.2, 16.2, 16.2, 16.1, 16, 15.9, 15.9, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.9, - 15.9, 15.8, 15.6, 15.5, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.3, - 15.4, 15.4, 15.4, 15.4, 15.6, 15.7, 15.8, 15.7, 15.7, 15.5, 15.3, 15.2, 14.8, 14.3, - 14.2, 14.1, 14.2, 14.6, 14.7, 14.1, 13.9, 13.9, 13.9, 13.9, 13.8, 13.7, 13.7, 13.7, - 13.7, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.5, 13.5, 13.4, 13.4, 13.3, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, - 13.6, 13.8, 13.8, 13.9, 13.9, 14, 13.9, 13.9, 13.9, 13.8, 13.7, 13.7, 13.7, 13.9, - 14.1, 14.3, 14.5, 14.9, 15.5, 16.2, 16.8, 17.5, 18.2, 18.7, 19.1, 19.6, 20, 20.4, - 20.8, 21.1, 21.3, 21.3, 21.2, 21.1, 21, 21, 20.8, 20.5, 20.1, 19.6, 19.6, 19.9, 20.2, - 20.5, 20.6, 20.6, 20.7, 20.8, 20.9, 20.9, 20.6, 20.1, 19.6, 19.1, 18.7, 18.4, 18.2, - 18.2, 18.3, 18.4, 18.5, 18.8, 19.1, 19.4, 19.7, 19.8, 19.8, 19.4, 19.1, 18.8, 18.7, - 18.4, 17.9, 17.5, 17, 16.6, 16.2, 15.9, 15.8, 15.6, 15, 14.4, 13.9, 13.3, 12.6, 12.1, - 11.8, 12, 12.5, 13, 13.4, 13.5, 13.6, 13.6, 13.5, 13.4, 13.1, 12.9, 12.6, 12.1, 11.9, - 11.8, 11.8, 11.8, 11.8, 11.7, 11.8, 11.8, 11.8, 12, 12.4, 13, 13.5, 13.8, 14.1, 14, - 13.7, 13.4, 13.2, 12.9, 12.7, 12.8, 13, 13.4, 13.6, 13.8, 13.9, 14, 14.1, 14.1, 14.2, - 14.3, 14.5, 14.7, 14.8, 14.8, 14.8, 14.6, 14.4, 14.2, 14.1, 14, 14, 14, 14, 14.2, - 14.4, 14.7, 15, 15.2, 15.5, 15.8, 16, 16.2, 16.4, 16.5, 16.5, 16.2, 16.2, 16.2, 16.1, - 16, 16, 16, 15.9, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.7, - 15.5, 15.5, 15.5, 15.5, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.4, 15.3, 15.3, 15.4, - 15.3, 15.3, 15.5, 15.6, 15.7, 15.7, 15.7, 15.6, 15.5, 15.1, 14.6, 14.1, 14.1, 14.2, - 14.3, 14.2, 13.8, 13.9, 14, 14, 13.9, 13.8, 13.7, 13.7, 13.7, 13.7, 13.6, 13.6, 13.6, - 13.6, 13.6, 13.5, 13.5, 13.4, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.6, 13.9, 14, 14, 14, 14.1, - 14.1, 14.1, 14.1, 14, 14, 14, 14.1, 14.1, 14.2, 14.5, 14.9, 15.1, 15.7, 16.4, 17.1, - 17.8, 18.4, 18.9, 19.4, 19.8, 20.2, 20.5, 20.8, 21.2, 21.4, 21.5, 21.5, 21.5, 21.2, - 21.1, 21, 20.8, 20.6, 20.4, 20.2, 20.3, 20.4, 20.6, 20.9, 21.1, 20.9, 20.7, 20.8, - 20.8, 20.7, 20.3, 19.8, 19.4, 18.9, 18.6, 18.5, 18.4, 18.4, 18.5, 18.6, 18.8, 19.1, - 19.3, 19.7, 19.9, 20, 20, 19.6, 19.3, 19.1, 18.9, 18.6, 18.1, 17.7, 17.1, 16.6, 16.2, - 15.8, 15.7, 15.4, 14.9, 14.4, 13.6, 13, 12.4, 12, 11.9, 12.2, 12.9, 13.4, 13.6, 13.6, - 13.6, 13.5, 13.4, 13.2, 13, 12.8, 12.4, 12, 11.9, 11.8, 11.7, 11.7, 11.8, 11.8, 11.8, - 11.8, 12, 12.4, 13, 13.5, 14, 14.3, 14.4, 14.4, 14.2, 14, 13.7, 13.4, 13.1, 13.2, - 13.4, 13.7, 13.9, 14.1, 14.2, 14.2, 14.3, 14.4, 14.6, 14.8, 15, 15, 15, 14.9, 14.8, - 14.6, 14.5, 14.3, 14.1, 14.1, 14, 14.1, 14.3, 14.5, 14.7, 14.8, 15, 15.3, 15.5, 15.7, - 16, 16.2, 16.4, 16.6, 16.7, 16.2, 16.1, 16.1, 16.1, 16, 15.9, 15.8, 15.8, 15.8, 15.9, - 15.9, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.7, 15.6, 15.5, 15.5, 15.5, 15.5, - 15.5, 15.4, 15.4, 15.4, 15.4, 15.4, 15.3, 15.3, 15.3, 15.3, 15.3, 15.3, 15.3, 15.4, - 15.5, 15.6, 15.8, 15.8, 15.4, 15, 14.5, 14.3, 14.4, 14.2, 14, 13.9, 14, 14.1, 14, - 13.8, 13.8, 13.7, 13.7, 13.7, 13.6, 13.6, 13.6, 13.6, 13.6, 13.6, 13.5, 13.5, 13.4, - 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.5, 13.9, 14.1, 14.2, 14.2, 14.3, 14.4, 14.4, 14.4, 14.4, 14.3, - 14.3, 14.3, 14.4, 14.4, 14.6, 15.2, 15.8, 16.1, 16.7, 17.4, 18.1, 18.6, 19.2, 19.7, - 20.2, 20.5, 20.8, 21, 21.3, 21.6, 21.7, 21.7, 21.7, 21.7, 21.3, 21.1, 21, 20.9, 20.8, - 20.7, 20.8, 20.9, 20.9, 21, 21.2, 21.1, 20.9, 20.6, 20.6, 20.6, 20.3, 19.8, 19.4, - 19.1, 18.8, 18.7, 18.6, 18.6, 18.7, 18.8, 18.9, 19.1, 19.4, 19.6, 19.9, 20.1, 20.2, - 20.2, 20, 19.6, 19.3, 18.9, 18.5, 18.1, 17.7, 17.2, 16.6, 16.1, 15.8, 15.7, 15.5, - 14.9, 14.1, 13.4, 12.7, 12.3, 12, 12, 12.4, 13.2, 13.6, 13.6, 13.6, 13.5, 13.4, 13.2, - 13.1, 12.8, 12.5, 12.1, 11.9, 11.8, 11.7, 11.7, 11.8, 11.8, 11.8, 11.9, 12.1, 12.5, - 13.1, 13.6, 14.1, 14.5, 14.7, 14.8, 14.8, 14.7, 14.4, 14.2, 13.9, 13.7, 13.8, 13.9, - 14.1, 14.2, 14.4, 14.5, 14.6, 14.8, 14.9, 15.2, 15.3, 15.3, 15.3, 15.2, 15, 14.8, - 14.6, 14.4, 14.2, 14.1, 14.2, 14.3, 14.6, 14.8, 15, 15, 15, 15, 15.1, 15.3, 15.5, - 15.8, 16.1, 16.4, 16.7, 16.8, 16.2, 16.1, 16.1, 16, 15.9, 15.9, 15.9, 15.9, 15.9, - 15.9, 15.9, 15.9, 15.9, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.7, 15.7, 15.7, 15.7, - 15.7, 15.6, 15.5, 15.5, 15.5, 15.5, 15.4, 15.4, 15.3, 15.3, 15.3, 15.3, 15.3, 15.3, - 15.3, 15.5, 15.5, 15.5, 15.8, 15.5, 15.1, 14.6, 14.4, 14.4, 14.3, 14.3, 14.2, 14.2, - 14.2, 14, 13.8, 13.7, 13.7, 13.8, 13.8, 13.7, 13.6, 13.6, 13.6, 13.6, 13.6, 13.5, - 13.5, 13.4, 13.3, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.3, 13.6, 14, 14.3, 14.7, 14.6, 14.6, 14.6, 14.5, 14.5, 14.5, - 14.5, 14.6, 14.6, 14.6, 14.8, 15, 15.4, 15.9, 16.5, 17, 17.8, 18.5, 19, 19.4, 20, - 20.4, 20.8, 21.2, 21.5, 21.7, 21.9, 22, 22.1, 22, 21.9, 21.7, 21.5, 21.3, 21.1, 20.9, - 20.9, 20.9, 21, 21.1, 21.1, 21.2, 21.1, 21, 20.7, 20.3, 20.2, 20.2, 20, 19.5, 19.1, - 18.9, 18.8, 18.7, 18.7, 18.9, 19.1, 19.2, 19.3, 19.5, 19.6, 19.8, 20.1, 20.3, 20.4, - 20.3, 20.1, 19.7, 19.4, 19.1, 18.6, 18.1, 17.6, 17.1, 16.6, 16.1, 15.8, 15.5, 15.2, - 14.7, 13.9, 13.2, 12.6, 12.3, 12.1, 12.1, 12.7, 13.4, 13.6, 13.6, 13.5, 13.4, 13.2, - 13, 12.8, 12.6, 12.3, 12, 11.8, 11.8, 11.7, 11.8, 11.8, 11.8, 11.8, 12.2, 12.6, 13.1, - 13.6, 14.1, 14.4, 14.7, 14.9, 15.1, 15.1, 15, 14.7, 14.5, 14.3, 14.1, 14.2, 14.4, - 14.6, 14.8, 15, 15.1, 15.3, 15.4, 15.4, 15.5, 15.5, 15.5, 15.5, 15.3, 15.1, 14.7, - 14.5, 14.3, 14.2, 14.2, 14.5, 14.8, 15.1, 15.3, 15.4, 15.3, 15.1, 15, 15, 15.1, 15.3, - 15.6, 16, 16.3, 16.6, 16.8, 16.3, 16.2, 16.1, 16.1, 16, 16, 16, 15.9, 15.9, 15.9, - 15.9, 15.9, 15.9, 15.9, 15.9, 15.9, 15.9, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, - 15.8, 15.6, 15.6, 15.5, 15.5, 15.5, 15.4, 15.4, 15.3, 15.3, 15.3, 15.3, 15.3, 15.5, - 15.8, 15.9, 15.5, 15.6, 15.7, 15.1, 14.7, 14.6, 14.4, 14.3, 14.2, 14.1, 14.2, 14.2, - 14, 14, 13.8, 13.8, 13.7, 13.7, 13.7, 13.7, 13.6, 13.6, 13.6, 13.6, 13.5, 13.4, 13.4, - 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, - 13.5, 13.6, 14, 14.5, 14.9, 15, 14.9, 14.8, 14.7, 14.7, 14.7, 14.7, 14.8, 15, 15.3, - 15.4, 15.5, 15.8, 16.3, 16.9, 17.5, 18, 18.6, 19.2, 19.6, 20.2, 20.8, 21.1, 21.5, - 21.7, 22, 22.3, 22.5, 22.6, 22.5, 22.3, 22.1, 21.9, 21.7, 21.4, 21.3, 21.1, 21, 21, - 21.1, 21.1, 21.1, 21.1, 20.9, 20.7, 20.4, 20.1, 20, 20, 19.8, 19.3, 19, 18.9, 18.9, - 18.9, 18.9, 19.1, 19.4, 19.5, 19.6, 19.7, 19.9, 20.2, 20.4, 20.5, 20.5, 20.4, 20.2, - 19.9, 19.6, 19.2, 18.7, 18.2, 17.5, 16.9, 16.4, 16, 15.6, 15.3, 14.8, 14.3, 13.6, 13, - 12.5, 12.3, 12.2, 12.4, 12.9, 13.4, 13.5, 13.5, 13.4, 13.2, 13, 12.8, 12.6, 12.3, - 12.1, 11.9, 11.9, 11.8, 11.8, 11.9, 11.8, 11.8, 12.1, 12.6, 13.2, 13.6, 14, 14.3, - 14.6, 14.8, 14.9, 15, 15.1, 15, 14.9, 14.8, 14.6, 14.6, 14.7, 15, 15.2, 15.4, 15.7, - 15.8, 15.9, 16, 16, 15.9, 15.9, 15.8, 15.7, 15.5, 15.1, 14.7, 14.4, 14.3, 14.2, 14.4, - 14.8, 15.3, 15.6, 15.8, 15.7, 15.5, 15.2, 15, 15, 15, 15.1, 15.3, 15.7, 16, 16.4, - 16.6, 16.3, 16.4, 16.2, 16.2, 16.1, 16.2, 16, 15.9, 15.9, 15.9, 15.9, 15.9, 16, 15.9, - 15.9, 15.9, 15.9, 15.9, 15.8, 15.8, 15.8, 15.8, 15.8, 15.9, 15.8, 15.7, 15.7, 15.6, - 15.5, 15.5, 15.4, 15.4, 15.4, 15.3, 15.3, 15.3, 15.4, 15.8, 16.2, 16.2, 15.6, 15.6, - 15.8, 15.4, 15, 14.7, 14.5, 14.3, 14.2, 14, 14.1, 14.2, 14.1, 14, 13.9, 13.8, 13.8, - 13.8, 13.7, 13.7, 13.6, 13.6, 13.6, 13.5, 13.5, 13.4, 13.3, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.5, 13.6, 13.9, 14.3, 14.8, - 15.2, 15.1, 15, 14.9, 14.9, 14.9, 14.9, 15, 15.1, 15.4, 15.8, 16.1, 16.4, 16.6, 17.1, - 17.8, 18.4, 18.9, 19.4, 19.8, 20.3, 20.9, 21.4, 21.7, 21.9, 22.2, 22.5, 22.8, 22.9, - 22.9, 22.8, 22.5, 22.3, 22.1, 21.8, 21.5, 21.5, 21.2, 21.1, 21.1, 21, 21, 20.9, 20.8, - 20.6, 20.4, 20.3, 20.2, 20, 19.8, 19.5, 19.3, 19.1, 19.1, 19.1, 19.1, 19.1, 19.3, - 19.5, 19.7, 19.8, 20, 20.1, 20.3, 20.5, 20.6, 20.6, 20.5, 20.4, 20, 19.6, 19.2, 18.7, - 18.1, 17.3, 16.6, 16.2, 15.8, 15.4, 15, 14.5, 14, 13.4, 12.8, 12.5, 12.4, 12.3, 12.5, - 12.9, 13.2, 13.2, 13.2, 13.1, 12.9, 12.7, 12.5, 12.3, 12.1, 12.1, 11.9, 11.9, 11.8, - 11.8, 11.9, 11.9, 12, 12.5, 13.1, 13.5, 13.8, 14.1, 14.3, 14.6, 14.7, 14.7, 14.9, 15, - 15, 14.9, 14.9, 14.9, 15, 15.2, 15.6, 15.8, 16, 16.2, 16.3, 16.4, 16.4, 16.4, 16.3, - 16.2, 16.1, 16, 15.6, 15.2, 14.7, 14.4, 14.3, 14.3, 14.6, 15.1, 15.6, 16, 16.1, 16, - 15.7, 15.4, 15.1, 15.1, 15.1, 15, 15, 15.4, 15.8, 16.1, 16.3, 16.2, 16.3, 16.2, 16.2, - 16.1, 16.1, 16, 16, 16, 16, 16, 16, 16, 16, 15.9, 15.9, 16, 15.9, 15.9, 15.8, 15.8, - 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.7, 15.6, 15.6, 15.6, 15.5, 15.5, 15.4, 15.3, - 15.3, 15.4, 15.7, 16.1, 15.9, 15.5, 15.4, 15.8, 15.5, 15.1, 14.8, 14.6, 14.4, 14.3, - 14, 14.2, 14.4, 14.1, 14, 13.9, 13.8, 13.8, 13.8, 13.8, 13.7, 13.7, 13.6, 13.5, 13.5, - 13.4, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.3, 13.6, 13.8, 14.2, 14.5, 15, 15.2, 15.3, 15.3, 15.1, 15.1, 15.1, 15.1, - 15.3, 15.5, 15.8, 16.1, 16.6, 16.8, 17.1, 17.6, 18.2, 18.7, 19.2, 19.8, 20.4, 20.8, - 21.3, 21.6, 21.9, 22.3, 22.7, 23, 23.1, 23.1, 23.1, 23, 22.7, 22.4, 22.1, 21.8, 21.6, - 21.5, 21.3, 21.1, 21, 21, 21, 21, 20.8, 20.7, 20.5, 20.3, 20.2, 20.1, 19.9, 19.7, - 19.6, 19.5, 19.6, 19.5, 19.4, 19.4, 19.5, 19.7, 19.8, 20, 20.1, 20.3, 20.5, 20.6, - 20.7, 20.7, 20.7, 20.4, 19.9, 19.5, 19, 18.5, 17.8, 17, 16.4, 16, 15.6, 15.2, 14.8, - 14.3, 13.7, 13.1, 12.7, 12.6, 12.5, 12.6, 12.6, 12.7, 12.8, 12.8, 12.8, 12.7, 12.6, - 12.4, 12.2, 12, 12, 12, 12, 11.9, 11.8, 11.8, 11.9, 12.1, 12.4, 12.8, 13.3, 13.6, - 13.8, 13.9, 14.1, 14.3, 14.3, 14.5, 14.6, 14.8, 14.9, 15, 15, 15.1, 15.3, 15.7, 16, - 16.3, 16.5, 16.6, 16.6, 16.6, 16.7, 16.7, 16.6, 16.5, 16.4, 16.1, 15.7, 15.1, 14.7, - 14.5, 14.4, 14.5, 14.7, 15.2, 15.8, 16.1, 16.2, 16.2, 15.9, 15.6, 15.4, 15.4, 15.3, - 15.1, 14.9, 15.1, 15.7, 16, 16.3, 16.3, 16.3, 16.2, 16.2, 16.1, 16.1, 16.1, 16.1, - 16.1, 16, 16, 16, 16.1, 16, 16, 16, 16, 16, 15.9, 15.9, 15.9, 15.9, 15.9, 15.9, 15.8, - 15.8, 15.8, 15.8, 15.8, 15.8, 15.7, 15.7, 15.7, 15.6, 15.4, 15.3, 15.3, 15.4, 15.6, - 15.6, 15.4, 15.4, 15.8, 15.5, 15.1, 15, 14.7, 14.4, 14.2, 14, 14.4, 14.5, 14.1, 14.1, - 13.9, 13.8, 13.8, 13.8, 13.8, 13.7, 13.7, 13.6, 13.6, 13.5, 13.3, 13.2, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.3, 13.6, 14.2, - 14.6, 14.9, 15.2, 15.5, 15.8, 15.7, 15.4, 15.3, 15.3, 15.4, 15.7, 16, 16.2, 16.6, - 16.9, 17.2, 17.5, 17.9, 18.4, 18.9, 19.5, 20.2, 20.7, 21.2, 21.7, 21.9, 22.3, 22.6, - 23, 23.4, 23.5, 23.4, 23.3, 23.2, 23, 22.6, 22.3, 22, 21.7, 21.6, 21.5, 21.6, 21.5, - 21.5, 21.5, 21.4, 21.4, 21.2, 20.9, 20.6, 20.5, 20.4, 20.2, 20.2, 20.2, 20.2, 20.2, - 20, 19.8, 19.7, 19.7, 19.8, 20, 20.1, 20.3, 20.4, 20.5, 20.7, 20.7, 20.7, 20.6, 20.1, - 19.8, 19.3, 18.7, 18.2, 17.5, 16.8, 16.3, 15.9, 15.4, 15, 14.4, 13.9, 13.4, 12.9, - 12.7, 12.6, 12.8, 13.1, 12.9, 12.6, 12.5, 12.5, 12.4, 12.3, 12.3, 12.1, 12, 12, 12, - 12, 12, 11.9, 11.9, 11.9, 12.1, 12.6, 12.9, 13.2, 13.5, 13.6, 13.6, 13.7, 13.8, 13.9, - 14, 14.2, 14.4, 14.6, 14.8, 15, 15.2, 15.4, 15.7, 16.1, 16.4, 16.7, 16.9, 17, 17, 17, - 17.1, 17, 16.9, 16.8, 16.6, 16.2, 15.7, 15.1, 14.7, 14.5, 14.6, 14.6, 14.9, 15.3, - 15.8, 16.1, 16.2, 16.2, 16, 15.8, 15.7, 15.8, 15.8, 15.6, 15, 15.2, 15.8, 16.3, 16.6, - 16.3, 16.3, 16.3, 16.3, 16.2, 16.2, 16.1, 16.2, 16.1, 16, 16.1, 16.1, 16.2, 16.1, - 16.1, 16, 16.1, 16.1, 16, 16, 15.9, 15.9, 15.9, 15.9, 15.9, 15.8, 15.8, 15.8, 15.8, - 15.8, 15.8, 15.8, 15.8, 15.7, 15.6, 15.5, 15.4, 15.3, 15.3, 15.4, 15.4, 15.4, 15.7, - 15.5, 15.1, 14.9, 14.7, 14.5, 14.3, 14.1, 14.3, 14.5, 14.2, 14, 13.8, 13.9, 13.9, - 13.8, 13.7, 13.7, 13.7, 13.6, 13.6, 13.5, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.1, 13.2, 13.4, 13.8, 14.5, 14.9, 15.4, 15.7, - 15.9, 16.2, 16, 15.8, 15.6, 15.6, 15.8, 16.1, 16.4, 16.7, 17.1, 17.3, 17.6, 17.9, - 18.2, 18.7, 19.2, 19.9, 20.5, 21, 21.6, 22.1, 22.4, 22.9, 23.3, 23.4, 23.6, 23.7, - 23.6, 23.6, 23.4, 23.2, 22.9, 22.6, 22.3, 22.1, 22.1, 22.2, 22.2, 22.1, 22.1, 22.1, - 22, 21.9, 21.7, 21.4, 21, 20.9, 20.8, 20.8, 20.8, 20.9, 20.9, 20.7, 20.4, 20.1, 20.1, - 20, 20.1, 20.1, 20.2, 20.3, 20.4, 20.5, 20.6, 20.5, 20.4, 20.1, 19.8, 19.4, 18.9, - 18.4, 17.9, 17.3, 16.7, 16.1, 15.7, 15.2, 14.7, 14.1, 13.5, 13.1, 12.9, 12.8, 12.8, - 13, 13.1, 12.9, 12.6, 12.4, 12.3, 12.2, 12.2, 12.1, 12.1, 12, 12, 12, 12, 12, 11.9, - 12, 12.2, 12.6, 13, 13.3, 13.4, 13.5, 13.5, 13.5, 13.6, 13.6, 13.7, 13.9, 14.1, 14.3, - 14.6, 14.8, 15, 15.3, 15.7, 16.1, 16.5, 16.8, 17.1, 17.3, 17.4, 17.4, 17.4, 17.4, - 17.3, 17.2, 17, 16.8, 16.4, 15.8, 15.3, 14.8, 14.7, 14.6, 14.7, 15.1, 15.4, 15.8, - 15.9, 16.1, 16.1, 16.1, 15.9, 16.1, 16.3, 16.4, 16.3, 15.8, 15.9, 16.3, 16.9, 17.3, - 16.4, 16.5, 16.3, 16.2, 16.2, 16.2, 16.1, 16.2, 16.1, 16.1, 16.1, 16.1, 16.2, 16.3, - 16.2, 16.1, 16.2, 16.2, 16.3, 16, 16, 16, 15.9, 15.9, 15.9, 15.8, 15.8, 15.8, 15.9, - 15.9, 15.9, 15.8, 15.8, 15.8, 15.8, 15.8, 15.6, 15.4, 15.3, 15.4, 15.4, 15.4, 15.7, - 15.5, 15.1, 14.9, 14.7, 14.6, 14.4, 14.2, 14.3, 14.5, 14.2, 13.9, 13.8, 13.8, 13.8, - 13.8, 13.7, 13.6, 13.7, 13.6, 13.5, 13.4, 13.3, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.3, 13.4, 13.5, 14.2, 15, 15.5, 15.8, 16.2, - 16.3, 16.3, 16.3, 16.1, 16, 16.1, 16.2, 16.4, 16.9, 17.2, 17.4, 17.7, 18.1, 18.4, - 18.6, 19, 19.5, 20.2, 20.7, 21.2, 21.8, 22.3, 22.9, 23.4, 23.6, 23.7, 23.8, 23.9, - 23.8, 23.7, 23.5, 23.1, 23, 22.8, 22.7, 22.7, 22.7, 22.7, 22.8, 22.8, 22.7, 22.7, - 22.6, 22.4, 22.2, 21.9, 21.6, 21.5, 21.5, 21.4, 21.6, 21.7, 21.5, 21.1, 20.8, 20.5, - 20.3, 20.4, 20.4, 20.3, 20.3, 20.4, 20.4, 20.4, 20.4, 20.3, 20.1, 19.8, 19.5, 19.1, - 18.6, 18.1, 17.8, 17.5, 16.9, 16.1, 15.5, 15, 14.4, 13.8, 13.3, 13.1, 12.9, 12.9, - 12.9, 13, 13, 12.8, 12.5, 12.3, 12.2, 12.2, 12.2, 12.1, 12.1, 12, 12, 12, 12, 12, 12, - 12.2, 12.7, 13.1, 13.4, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.7, 13.9, 14.1, - 14.3, 14.5, 14.8, 15.1, 15.7, 16.2, 16.5, 16.9, 17.3, 17.6, 17.7, 17.8, 17.8, 17.7, - 17.6, 17.5, 17.4, 17.3, 17, 16.6, 16.1, 15.5, 15, 14.8, 14.9, 15, 15.3, 15.5, 15.7, - 15.8, 15.9, 15.9, 15.9, 16, 16.3, 16.7, 16.9, 16.9, 16.7, 16.7, 16.9, 17.4, 17.8, - 16.4, 16.4, 16.3, 16.2, 16.2, 16.2, 16.2, 16.1, 16.1, 16.1, 16.1, 16.1, 16.2, 16.3, - 16.3, 16.2, 16.2, 16.2, 16.2, 16.1, 16.1, 16, 15.9, 15.9, 15.9, 15.8, 15.9, 15.9, - 15.9, 15.9, 15.8, 15.8, 15.8, 15.8, 15.8, 15.8, 15.6, 15.5, 15.5, 15.4, 15.3, 15.5, - 15.6, 15.2, 15.1, 15, 14.7, 14.4, 14.3, 14.2, 14.2, 14.3, 14.2, 14.1, 13.9, 13.8, - 13.8, 13.7, 13.7, 13.7, 13.6, 13.6, 13.5, 13.4, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.3, 13.2, 13.5, 13.5, 13.4, 13.5, 13.8, 14.8, 15.6, 15.8, 16.3, - 16.8, 16.6, 16.5, 16.6, 16.5, 16.4, 16.5, 16.5, 16.9, 17.3, 17.5, 17.8, 18.1, 18.5, - 18.8, 19, 19.2, 19.8, 20.6, 21, 21.5, 22.1, 22.7, 23.3, 23.6, 23.8, 23.9, 24, 24, - 23.8, 23.7, 23.5, 23.3, 23.1, 23, 23, 23, 23.1, 23.2, 23.4, 23.4, 23.2, 23.2, 23, - 22.9, 22.7, 22.6, 22.5, 22.3, 22.2, 22.1, 22.1, 22.2, 22.1, 21.6, 21.1, 20.8, 20.7, - 20.8, 20.8, 20.6, 20.5, 20.4, 20.4, 20.4, 20.3, 20.1, 19.8, 19.5, 19.2, 18.8, 18.3, - 17.9, 17.7, 17.5, 16.9, 16.1, 15.6, 15, 14.3, 13.7, 13.2, 13.1, 13, 13, 13, 12.9, - 12.8, 12.6, 12.4, 12.3, 12.2, 12.2, 12.2, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, - 12.3, 12.8, 13.2, 13.4, 13.6, 13.6, 13.6, 13.6, 13.6, 13.5, 13.5, 13.5, 13.7, 13.9, - 14.2, 14.3, 14.5, 14.9, 15.4, 16.1, 16.6, 17, 17.4, 17.7, 18, 18.1, 18.2, 18.2, 17.9, - 17.7, 17.6, 17.5, 17.4, 17.2, 16.8, 16.4, 15.9, 15.3, 15.2, 15.2, 15.3, 15.5, 15.7, - 15.8, 15.7, 15.7, 15.7, 15.8, 16, 16.3, 16.8, 17.1, 17.3, 17.4, 17.3, 17.4, 17.8, - 18.3, 16.5, 16.4, 16.2, 16.3, 16.3, 16.3, 16.3, 16.2, 16.2, 16.2, 16.1, 16.2, 16.2, - 16.4, 16.4, 16.3, 16.3, 16.3, 16.2, 16.1, 16.1, 16.1, 15.9, 15.9, 15.9, 15.8, 15.9, - 16, 16, 15.9, 15.9, 15.9, 15.9, 15.9, 15.9, 15.9, 15.8, 15.7, 15.6, 15.4, 15.4, 15.5, - 15.6, 15.3, 15.1, 15.1, 14.8, 14.4, 14.3, 14.3, 14.4, 14.2, 14.1, 14.1, 14, 13.8, - 13.9, 13.8, 13.7, 13.7, 13.7, 13.6, 13.6, 13.5, 13.5, 13.4, 13.3, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.3, 13.5, 13.6, 13.6, 13.6, 13.9, 14.2, 15.2, 15.8, 16.4, 17.1, - 17.3, 17, 16.9, 16.8, 16.8, 16.6, 16.6, 16.7, 17.3, 17.7, 18, 18.4, 18.7, 19, 19.1, - 19.3, 19.6, 20.1, 20.9, 21.4, 21.9, 22.6, 23.2, 23.5, 23.7, 23.9, 24, 24.1, 24, 23.9, - 23.7, 23.5, 23.5, 23.4, 23.4, 23.4, 23.5, 23.6, 23.7, 23.8, 23.8, 23.6, 23.5, 23.3, - 23.2, 23.2, 23.2, 23.1, 23.1, 23, 22.8, 22.7, 22.5, 22.4, 22, 21.3, 21, 20.9, 21, - 20.9, 20.8, 20.7, 20.6, 20.5, 20.4, 20.2, 19.8, 19.5, 19.1, 18.9, 18.4, 18.1, 17.9, - 17.6, 17.2, 16.6, 15.9, 15.5, 14.9, 14, 13.5, 13.2, 13.2, 13.1, 13, 13, 12.8, 12.6, - 12.5, 12.4, 12.3, 12.3, 12.3, 12.3, 12.2, 12.1, 12.1, 12.2, 12.2, 12.3, 12.5, 12.9, - 13.4, 13.6, 13.7, 13.7, 13.7, 13.6, 13.6, 13.6, 13.5, 13.5, 13.6, 13.7, 13.9, 14.1, - 14.3, 14.6, 15.1, 15.8, 16.5, 17, 17.4, 17.9, 18.2, 18.4, 18.5, 18.5, 18.4, 18.2, - 17.9, 17.8, 17.7, 17.6, 17.4, 17, 16.6, 16.2, 15.8, 15.7, 15.7, 15.8, 15.9, 16, 16, - 15.8, 15.7, 15.7, 15.8, 16, 16.4, 16.8, 17.1, 17.3, 17.5, 17.7, 17.8, 18.2, 18.7, - 16.5, 16.4, 16.3, 16.4, 16.4, 16.3, 16.3, 16.3, 16.2, 16.2, 16.3, 16.4, 16.3, 16.4, - 16.4, 16.4, 16.4, 16.3, 16.3, 16.2, 16.1, 16, 15.9, 16, 16, 15.9, 15.9, 16, 16, 16, - 15.9, 15.9, 15.9, 15.9, 15.9, 16, 15.9, 15.8, 15.7, 15.5, 15.4, 15.6, 15.5, 15.4, - 15.1, 15.1, 14.8, 14.5, 14.3, 14.3, 14.6, 14.5, 14.2, 13.9, 13.8, 13.8, 13.9, 13.7, - 13.7, 13.6, 13.7, 13.7, 13.6, 13.6, 13.5, 13.6, 13.5, 13.4, 13.2, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.3, 13.7, 13.7, 13.7, 14, 14.4, 14.9, 15.8, 16.1, 16.7, 17.4, 17.5, 17.3, - 17.3, 17.2, 17, 16.7, 16.8, 17, 17.4, 18, 18.4, 18.7, 19, 19.3, 19.5, 19.7, 20.1, - 20.5, 21.2, 21.9, 22.5, 23, 23.6, 23.8, 24, 24.2, 24.2, 24.1, 24.1, 24, 23.8, 23.7, - 23.8, 23.8, 23.8, 23.8, 24.1, 24.3, 24.3, 24.3, 24.4, 24.4, 24, 23.7, 23.6, 23.6, - 23.6, 23.6, 23.5, 23.5, 23.3, 23, 22.8, 22.5, 22.1, 21.6, 21.3, 21.1, 21, 21, 20.9, - 20.8, 20.6, 20.5, 20.4, 20.1, 19.7, 19.2, 18.8, 18.4, 18.1, 17.9, 17.8, 17.4, 16.8, - 16.2, 15.5, 14.9, 14.2, 13.6, 13.4, 13.3, 13.2, 13.2, 13.1, 13.1, 12.9, 12.8, 12.6, - 12.5, 12.4, 12.5, 12.6, 12.6, 12.7, 12.6, 12.4, 12.5, 12.5, 12.7, 13.1, 13.5, 13.9, - 13.9, 13.8, 13.8, 13.8, 13.7, 13.6, 13.6, 13.6, 13.6, 13.7, 13.8, 13.9, 14.1, 14.4, - 14.8, 15.5, 16.3, 16.9, 17.4, 17.8, 18.2, 18.4, 18.7, 18.8, 18.8, 18.6, 18.4, 18.1, - 17.9, 17.8, 17.7, 17.5, 17.3, 17, 16.6, 16.3, 16.3, 16.3, 16.5, 16.5, 16.3, 16.1, - 15.9, 15.8, 15.8, 15.9, 16.2, 16.5, 16.9, 17.1, 17.3, 17.4, 17.6, 18, 18.4, 19, 16.6, - 16.5, 16.5, 16.5, 16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 16.5, 16.5, 16.4, 16.3, - 16.3, 16.3, 16.2, 16.3, 16.2, 16.1, 16, 15.9, 16, 16.1, 16.1, 16.1, 16, 16.1, 16, 16, - 16, 16, 15.9, 16, 16, 16, 15.9, 15.8, 15.6, 15.6, 15.8, 15.6, 15.4, 15.2, 15.2, 15, - 14.7, 14.5, 14.4, 14.5, 14.5, 14.1, 13.9, 13.8, 13.7, 13.8, 13.7, 13.7, 13.6, 13.7, - 13.6, 13.7, 13.7, 13.6, 13.6, 13.6, 13.5, 13.4, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.2, 13.3, 13.2, 13.3, 13.6, 13.8, - 13.9, 13.9, 14.3, 14.8, 15.4, 15.9, 16.3, 16.8, 17.4, 17.6, 17.6, 17.7, 17.6, 17.4, - 17.1, 17.3, 17.5, 17.6, 18.5, 18.9, 18.7, 19, 19.5, 20, 20.3, 20.5, 20.9, 21.8, 22.3, - 22.8, 23.3, 23.7, 24, 24.2, 24.3, 24.3, 24.2, 24.3, 24.1, 23.9, 23.9, 24.1, 24.2, - 24.3, 24.4, 24.7, 24.9, 24.9, 24.9, 25, 25, 24.6, 24.2, 24.2, 24.1, 24, 23.9, 23.7, - 23.6, 23.5, 23.2, 23, 22.7, 22.2, 21.9, 21.6, 21.4, 21.2, 21.1, 20.9, 20.8, 20.6, - 20.4, 20.2, 20, 19.5, 18.9, 18.5, 18.1, 17.8, 17.7, 17.6, 17.2, 16.4, 15.7, 15, 14.3, - 13.8, 13.5, 13.5, 13.3, 13.3, 13.2, 13.2, 13.3, 13.3, 13.1, 12.8, 12.7, 12.7, 12.9, - 13.2, 13.2, 13.3, 13.4, 13.3, 13.3, 13.1, 13.3, 13.6, 13.8, 14, 14, 13.9, 13.9, 13.9, - 13.8, 13.8, 13.8, 13.8, 13.8, 13.8, 13.9, 14, 14.2, 14.6, 15.2, 15.9, 16.8, 17.4, - 17.8, 18.1, 18.5, 18.8, 19.1, 19.2, 19.1, 18.9, 18.7, 18.4, 18.2, 18, 17.8, 17.7, - 17.5, 17.2, 16.9, 16.8, 16.8, 16.8, 17, 17, 16.7, 16.4, 16.2, 16.1, 16.1, 16.1, 16.4, - 16.6, 16.9, 17.2, 17.4, 17.5, 17.7, 18.2, 18.7, 19.3, 16.7, 16.7, 16.7, 16.7, 16.6, - 16.5, 16.5, 16.5, 16.6, 16.4, 16.5, 16.5, 16.5, 16.4, 16.4, 16.4, 16.3, 16.3, 16.3, - 16.3, 16.1, 16.1, 16.1, 16, 16.1, 16.2, 16.2, 16.1, 16.1, 16, 16, 16, 16, 16, 16, - 15.9, 15.9, 15.9, 15.8, 15.7, 15.8, 15.9, 15.7, 15.6, 15.3, 15.2, 15, 14.7, 14.5, - 14.4, 14.5, 14.4, 14.1, 14, 13.9, 13.7, 13.8, 13.7, 13.6, 13.7, 13.7, 13.6, 13.7, - 13.7, 13.6, 13.7, 13.6, 13.5, 13.5, 13.5, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.1, 13.1, 13.2, 13.4, 13.5, 13.5, 13.6, 13.6, 13.9, 14.1, 14.3, 14.4, - 14.7, 15.2, 15.8, 16.3, 16.9, 17.3, 17.7, 17.9, 18, 18.1, 17.8, 17.7, 17.5, 17.8, - 17.9, 18, 18.4, 18.8, 18.9, 19.2, 19.8, 20.8, 20.9, 20.9, 21.4, 22.1, 22.6, 23.1, - 23.4, 23.7, 24, 24.3, 24.5, 24.4, 24.4, 24.3, 24.2, 24.2, 24.3, 24.6, 24.7, 24.8, - 24.9, 25.2, 25.4, 25.5, 25.7, 25.5, 25.3, 25.1, 24.8, 24.7, 24.4, 24.3, 24.1, 23.8, - 23.6, 23.5, 23.3, 23.1, 22.8, 22.5, 22.2, 22.1, 22, 21.7, 21.3, 21, 20.7, 20.5, 20.3, - 20.1, 19.8, 19.2, 18.6, 18.2, 17.9, 17.5, 17.2, 17.1, 16.8, 16, 15.3, 14.7, 14.1, - 13.6, 13.5, 13.5, 13.4, 13.4, 13.5, 13.5, 13.5, 13.4, 13.1, 12.9, 12.8, 12.9, 13.2, - 13.7, 13.8, 13.8, 13.9, 13.9, 13.8, 13.8, 13.9, 14, 14, 14.1, 14.1, 14, 14, 14.1, - 14.2, 14.2, 14.2, 14.2, 14.3, 14.2, 14.2, 14.3, 14.5, 15, 15.7, 16.4, 17.1, 17.7, - 18.1, 18.5, 18.9, 19.3, 19.5, 19.6, 19.5, 19.4, 19.1, 18.8, 18.6, 18.4, 18.2, 18.1, - 17.9, 17.6, 17.3, 17.2, 17.2, 17.3, 17.3, 17.2, 17, 16.9, 16.7, 16.6, 16.5, 16.4, - 16.6, 16.7, 16.9, 17.2, 17.5, 17.7, 18, 18.6, 19.1, 19.6, 16.7, 16.7, 16.7, 16.8, - 16.7, 16.7, 16.6, 16.6, 16.6, 16.5, 16.5, 16.5, 16.4, 16.4, 16.4, 16.4, 16.3, 16.3, - 16.3, 16.3, 16.2, 16.4, 16.3, 16.2, 16.2, 16.2, 16.2, 16.2, 16.1, 16, 16, 15.9, 16, - 16, 15.9, 15.9, 15.9, 16, 15.8, 15.8, 15.8, 16, 15.8, 15.7, 15.5, 15.3, 15, 14.8, - 14.6, 14.5, 14.6, 14.5, 14.4, 14.2, 13.9, 13.7, 13.7, 13.8, 13.7, 13.8, 13.8, 13.7, - 13.7, 13.7, 13.6, 13.6, 13.6, 13.6, 13.6, 13.5, 13.2, 13.1, 13.1, 13.1, 13.1, 13.1, - 13.1, 13.1, 13.2, 13.3, 13.4, 13.5, 13.5, 13.5, 13.7, 13.8, 13.9, 14.3, 14.8, 15.1, - 15.2, 15.4, 15.8, 16.3, 16.8, 17.4, 17.7, 17.9, 18.1, 18.4, 18.5, 18.1, 17.8, 17.8, - 18.1, 18.2, 18.4, 18.5, 18.8, 19.5, 19.9, 20.2, 21.3, 21.3, 21.3, 21.7, 22.4, 22.9, - 23.3, 23.7, 23.9, 24.2, 24.5, 24.6, 24.6, 24.6, 24.5, 24.5, 24.6, 24.8, 25, 25.1, - 25.3, 25.5, 25.6, 25.8, 26, 26.1, 26, 25.7, 25.6, 25.3, 25.1, 24.7, 24.4, 24.1, 23.8, - 23.7, 23.5, 23.4, 23.2, 22.9, 22.7, 22.5, 22.4, 22.3, 22, 21.5, 21.1, 20.7, 20.4, - 20.1, 19.8, 19.5, 19.1, 18.6, 18.1, 17.7, 17.3, 16.8, 16.5, 16.1, 15.4, 14.8, 14.4, - 13.9, 13.6, 13.6, 13.5, 13.4, 13.5, 13.7, 13.7, 13.6, 13.5, 13.1, 12.9, 12.9, 13.1, - 13.6, 14, 14.2, 14.2, 14.1, 14.1, 14.1, 14.2, 14.3, 14.3, 14.2, 14.2, 14.2, 14.1, - 14.2, 14.4, 14.5, 14.6, 14.7, 14.8, 14.9, 14.9, 14.8, 14.9, 15.1, 15.6, 16.2, 16.8, - 17.4, 18, 18.4, 18.8, 19.2, 19.6, 19.8, 20, 20, 19.9, 19.6, 19.4, 19.1, 18.8, 18.6, - 18.4, 18.2, 17.9, 17.7, 17.6, 17.6, 17.7, 17.6, 17.4, 17.2, 17.1, 17, 17, 16.9, 16.9, - 16.9, 16.9, 17, 17.3, 17.7, 18.1, 18.5, 19.1, 19.5, 19.9, 16.6, 16.6, 16.6, 16.7, - 16.7, 16.8, 16.7, 16.8, 16.8, 16.8, 16.7, 16.5, 16.5, 16.4, 16.3, 16.3, 16.3, 16.4, - 16.3, 16.3, 16.3, 16.4, 16.4, 16.4, 16.4, 16.3, 16.2, 16.1, 16.1, 16, 16, 16, 16.1, - 16, 15.9, 15.9, 16, 16.1, 15.9, 15.8, 15.8, 16, 15.9, 15.7, 15.5, 15.4, 15.1, 15, - 14.9, 14.7, 14.7, 14.7, 14.4, 14.3, 14, 13.8, 13.7, 13.8, 13.7, 13.8, 13.9, 13.8, - 13.7, 13.7, 13.7, 13.7, 13.6, 13.6, 13.6, 13.3, 13.5, 13.5, 13.3, 13.4, 13.4, 13.5, - 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.6, 13.8, 14.1, 14.3, 14.4, 14.9, 15.3, 15.7, - 15.8, 16, 16.4, 17, 17.4, 17.7, 17.9, 18.1, 18.4, 18.9, 18.8, 18.4, 18.1, 18.1, 18.3, - 18.6, 18.8, 18.9, 19.4, 20.2, 20.6, 20.7, 21.5, 21.7, 21.7, 22.2, 22.8, 23.2, 23.6, - 24, 24.3, 24.4, 24.7, 24.9, 25, 25, 24.9, 24.9, 24.9, 25.1, 25.4, 25.6, 25.7, 25.9, - 26, 26.2, 26.4, 26.4, 26.3, 26.1, 25.9, 25.6, 25.3, 25, 24.6, 24.2, 24, 24, 23.8, - 23.6, 23.5, 23.3, 23.1, 22.8, 22.7, 22.5, 22, 21.5, 21.1, 20.6, 20.3, 20, 19.6, 19.3, - 18.9, 18.5, 18.1, 17.8, 17.4, 16.7, 16, 15.5, 14.9, 14.4, 14.1, 13.8, 13.6, 13.6, - 13.6, 13.7, 13.9, 14, 14, 13.9, 13.6, 13.3, 13, 13.1, 13.4, 14, 14.3, 14.4, 14.4, - 14.3, 14.3, 14.2, 14.3, 14.3, 14.4, 14.4, 14.3, 14.2, 14.3, 14.5, 14.6, 14.8, 15, - 15.2, 15.4, 15.5, 15.5, 15.5, 15.6, 15.8, 16.3, 16.9, 17.4, 17.9, 18.4, 18.8, 19.1, - 19.5, 19.8, 20.1, 20.3, 20.4, 20.3, 20.1, 19.8, 19.6, 19.3, 19, 18.7, 18.5, 18.2, 18, - 17.9, 18, 18, 17.8, 17.6, 17.5, 17.5, 17.5, 17.5, 17.5, 17.5, 17.4, 17.3, 17.4, 17.7, - 18, 18.3, 18.8, 19.3, 19.8, 20.2, 16.6, 16.6, 16.6, 16.5, 16.3, 16.3, 16.2, 16.4, - 16.6, 16.7, 16.7, 16.6, 16.5, 16.4, 16.4, 16.3, 16.4, 16.4, 16.3, 16.2, 16.2, 16.3, - 16.4, 16.4, 16.5, 16.4, 16.2, 16.1, 16, 16, 16.1, 16.1, 16.2, 16, 15.9, 16, 16.1, - 16.2, 16.1, 15.9, 16, 16, 16, 15.8, 15.6, 15.4, 15.4, 15.3, 15.3, 15, 14.9, 14.8, - 14.5, 14.4, 14.1, 13.8, 13.6, 13.7, 13.7, 13.7, 13.8, 13.8, 13.8, 13.8, 13.8, 13.7, - 13.7, 13.7, 13.7, 13.6, 13.8, 13.8, 13.6, 13.5, 13.6, 13.7, 13.7, 13.6, 13.6, 13.6, - 13.6, 13.7, 13.9, 14.3, 14.8, 15, 15.3, 15.6, 15.9, 16.1, 16.2, 16.5, 16.9, 17.3, - 17.7, 18, 18.2, 18.4, 18.7, 19.1, 19, 18.8, 18.5, 18.3, 18.5, 19, 19.4, 19.7, 20, - 20.5, 20.9, 21.1, 21.4, 21.8, 22.1, 22.6, 23, 23.5, 23.9, 24.2, 24.6, 24.7, 25, 25.2, - 25.3, 25.4, 25.4, 25.4, 25.3, 25.4, 25.6, 25.8, 26, 26.2, 26.3, 26.5, 26.6, 26.5, - 26.4, 26.3, 26.1, 25.9, 25.7, 25.3, 24.9, 24.5, 24.4, 24.3, 24.1, 24, 23.8, 23.7, - 23.5, 23.2, 22.9, 22.6, 22.2, 21.6, 21.1, 20.7, 20.3, 19.8, 19.4, 19.1, 18.8, 18.4, - 18.1, 17.8, 17.2, 16.3, 15.6, 15.1, 14.6, 14.3, 14, 13.8, 13.7, 13.7, 14, 14.2, 14.4, - 14.4, 14.3, 14.1, 13.8, 13.6, 13.4, 13.5, 13.9, 14.4, 14.7, 14.8, 14.7, 14.6, 14.5, - 14.4, 14.3, 14.4, 14.3, 14.3, 14.3, 14.4, 14.5, 14.6, 14.8, 15, 15.2, 15.5, 15.7, - 15.9, 16, 16.1, 16.2, 16.5, 17, 17.5, 18.1, 18.6, 19, 19.3, 19.6, 19.9, 20.2, 20.4, - 20.6, 20.7, 20.7, 20.5, 20.3, 19.9, 19.5, 19.2, 18.9, 18.7, 18.4, 18.3, 18.2, 18.1, - 18.1, 18, 17.7, 17.8, 18, 18.3, 18.3, 18.2, 18.1, 17.9, 17.7, 17.8, 18.1, 18.5, 18.8, - 19.2, 19.7, 20.2, 20.5, 16.5, 16.6, 16.4, 16.2, 16.1, 16.1, 16, 16.1, 16.3, 16.6, - 16.8, 16.5, 16.5, 16.4, 16.4, 16.4, 16.4, 16.4, 16.4, 16.3, 16.3, 16.3, 16.4, 16.5, - 16.5, 16.5, 16.4, 16.2, 16.1, 16.1, 16.1, 16.1, 16.1, 16, 16.1, 16.1, 16, 16.1, 16.1, - 16.1, 16.1, 16, 15.9, 15.8, 15.7, 15.6, 15.6, 15.5, 15.4, 15.2, 15.1, 14.9, 14.7, - 14.6, 14.2, 13.9, 13.8, 13.8, 13.8, 13.9, 14, 13.9, 14, 14, 14.1, 13.9, 13.8, 13.7, - 13.7, 13.9, 13.8, 13.6, 13.6, 13.6, 13.7, 13.7, 13.7, 13.7, 13.8, 13.8, 13.8, 14, - 14.4, 14.8, 15.4, 15.7, 15.9, 16.2, 16.4, 16.6, 16.7, 16.8, 17.1, 17.7, 18.1, 18.2, - 18.5, 18.7, 19.1, 19.2, 19.3, 19.2, 18.8, 18.6, 18.9, 19.4, 19.8, 20.3, 20.4, 20.6, - 21, 21.3, 21.7, 22.1, 22.5, 22.9, 23.3, 23.7, 24, 24.4, 24.7, 24.9, 25.1, 25.3, 25.5, - 25.6, 25.7, 25.7, 25.7, 25.7, 25.9, 26.1, 26.4, 26.5, 26.6, 26.8, 26.8, 26.7, 26.5, - 26.4, 26.2, 26, 25.8, 25.5, 25.2, 25, 24.8, 24.6, 24.4, 24.3, 24.2, 24, 23.7, 23.4, - 23, 22.7, 22.3, 21.8, 21.3, 20.8, 20.4, 19.8, 19.3, 18.9, 18.6, 18.4, 18, 17.4, 16.6, - 16, 15.3, 14.9, 14.6, 14.3, 14, 13.9, 13.9, 13.9, 14.2, 14.5, 14.6, 14.5, 14.4, 14.2, - 14.1, 13.9, 13.8, 14, 14.3, 14.8, 15.1, 15.2, 15.2, 15.1, 15, 14.7, 14.5, 14.4, 14.4, - 14.4, 14.4, 14.5, 14.7, 14.9, 14.9, 15.1, 15.3, 15.6, 15.9, 16, 16.2, 16.5, 16.6, 17, - 17.6, 18.1, 18.7, 19.1, 19.5, 19.8, 20, 20.3, 20.5, 20.7, 20.8, 20.9, 20.9, 20.8, - 20.5, 20.1, 19.7, 19.3, 19.1, 18.8, 18.5, 18.4, 18.3, 18.2, 18.1, 18, 18, 18.2, 18.5, - 18.7, 18.9, 18.8, 18.6, 18.4, 18.3, 18.3, 18.5, 18.9, 19.3, 19.7, 20.2, 20.5, 20.8, - 16.5, 16.5, 16.3, 16.2, 16.3, 16.2, 16.1, 16.1, 16.2, 16.4, 16.7, 16.7, 16.6, 16.6, - 16.5, 16.4, 16.5, 16.5, 16.4, 16.3, 16.5, 16.5, 16.5, 16.6, 16.5, 16.5, 16.5, 16.4, - 16.3, 16.2, 16.1, 16.1, 16.1, 16.2, 16.2, 16.2, 16, 16, 16.1, 16.1, 16.1, 16, 16, 16, - 15.9, 15.8, 15.7, 15.5, 15.4, 15.3, 15.2, 15.1, 14.9, 14.7, 14.5, 14.2, 14, 14, 14.2, - 14.3, 14.2, 14.1, 14.2, 14.3, 14.1, 13.8, 13.7, 13.7, 13.8, 13.9, 13.8, 13.7, 13.7, - 13.8, 13.8, 13.8, 13.8, 14.1, 14.3, 14.3, 14.3, 14.6, 14.9, 15.5, 16, 16.3, 16.7, - 16.9, 16.9, 17, 17, 17.2, 17.5, 18.2, 18.5, 18.5, 18.8, 19, 19.3, 19.5, 19.6, 19.4, - 19.1, 19, 19.1, 19.5, 19.9, 20.4, 20.6, 20.8, 21.2, 21.6, 22, 22.4, 22.7, 23.1, 23.5, - 23.8, 24.1, 24.4, 24.7, 25, 25.1, 25.3, 25.6, 25.8, 25.8, 25.9, 25.9, 26, 26.2, 26.4, - 26.7, 26.9, 27, 27.1, 27.1, 27, 26.8, 26.5, 26.2, 25.9, 25.8, 25.6, 25.4, 25.3, 25.1, - 24.8, 24.6, 24.5, 24.3, 24, 23.8, 23.4, 23, 22.6, 22.2, 21.9, 21.4, 21, 20.5, 19.8, - 19.2, 18.7, 18.3, 18.1, 17.9, 17, 16.1, 15.7, 15, 14.7, 14.5, 14.3, 14.1, 14, 14, - 14.1, 14.2, 14.5, 14.6, 14.6, 14.4, 14.3, 14.3, 14.4, 14.4, 14.5, 14.8, 15.2, 15.5, - 15.6, 15.7, 15.7, 15.5, 15.1, 14.8, 14.6, 14.6, 14.6, 14.6, 14.7, 14.8, 14.9, 15, - 15.2, 15.4, 15.7, 15.9, 16.1, 16.3, 16.7, 17, 17.6, 18.2, 18.7, 19.2, 19.6, 20, 20.2, - 20.4, 20.5, 20.8, 21, 21.1, 21.2, 21.2, 21, 20.6, 20.3, 20, 19.5, 19.3, 19, 18.7, - 18.5, 18.4, 18.2, 18.2, 18.3, 18.5, 18.8, 19.1, 19.2, 19.2, 19.1, 19, 18.9, 18.9, - 18.8, 18.9, 19.2, 19.7, 20.2, 20.7, 20.9, 21.1, 16.5, 16.5, 16.4, 16.5, 16.5, 16.4, - 16.2, 16.2, 16.4, 16.3, 16.5, 16.7, 16.7, 16.6, 16.6, 16.6, 16.6, 16.6, 16.6, 16.6, - 16.7, 16.7, 16.6, 16.6, 16.4, 16.3, 16.4, 16.6, 16.5, 16.3, 16.2, 16.3, 16.2, 16.3, - 16.3, 16.1, 16.1, 16.2, 16.3, 16.3, 16.1, 16, 16, 16.1, 16.1, 16, 15.8, 15.6, 15.4, - 15.4, 15.3, 15.2, 15.1, 14.9, 14.8, 14.5, 14.1, 14.1, 14.1, 14.1, 14.1, 14, 14.2, - 14.2, 14, 13.8, 13.8, 13.8, 13.9, 14.1, 14, 13.9, 13.9, 13.9, 14.2, 14.3, 14.3, 14.4, - 14.7, 14.9, 15.1, 15.4, 15.7, 16.2, 16.8, 17.2, 17.4, 17.5, 17.4, 17.4, 17.5, 17.7, - 18, 18.5, 18.8, 18.9, 19, 19.3, 19.6, 19.9, 19.9, 19.7, 19.5, 19.3, 19.4, 19.6, 19.8, - 20.2, 20.6, 20.9, 21.3, 21.7, 22.1, 22.5, 22.8, 23.2, 23.5, 23.8, 24.1, 24.4, 24.6, - 24.8, 25, 25.3, 25.6, 25.8, 25.9, 25.9, 26, 26.3, 26.4, 26.6, 27, 27.3, 27.5, 27.5, - 27.4, 27.2, 27.1, 26.7, 26.3, 26, 25.8, 25.7, 25.6, 25.5, 25.3, 25, 24.7, 24.5, 24.3, - 24, 23.7, 23.3, 22.9, 22.5, 22.2, 21.9, 21.4, 20.9, 20.3, 19.6, 19.1, 18.4, 18, 17.6, - 17.2, 16.4, 15.7, 15.2, 14.9, 14.8, 14.5, 14.3, 14.2, 14.1, 14, 14.1, 14.3, 14.5, - 14.7, 14.6, 14.5, 14.6, 14.7, 14.9, 15.2, 15.2, 15.4, 15.7, 15.9, 16.1, 16.2, 16.1, - 15.9, 15.5, 15.1, 14.9, 14.7, 14.7, 14.8, 14.9, 14.9, 15, 15.1, 15.3, 15.6, 15.9, - 16.1, 16.3, 16.5, 16.9, 17.5, 18.1, 18.6, 19.1, 19.5, 19.9, 20.3, 20.5, 20.6, 20.8, - 21.1, 21.3, 21.4, 21.5, 21.5, 21.1, 20.7, 20.4, 20.1, 19.7, 19.4, 19.1, 18.9, 18.7, - 18.6, 18.5, 18.6, 18.8, 19.1, 19.3, 19.4, 19.5, 19.5, 19.4, 19.4, 19.3, 19.3, 19.3, - 19.5, 19.6, 20, 20.5, 20.8, 21.2, 21.2, 16.5, 16.5, 16.5, 16.6, 16.5, 16.5, 16.3, - 16.3, 16.5, 16.5, 16.4, 16.6, 16.7, 16.7, 16.7, 16.8, 16.7, 16.7, 16.7, 16.6, 16.7, - 16.7, 16.7, 16.6, 16.4, 16.3, 16.3, 16.5, 16.5, 16.3, 16.2, 16.2, 16.1, 16.2, 16.1, - 16.2, 16.3, 16.4, 16.5, 16.4, 16.3, 16.2, 16.1, 16.2, 16.3, 16.2, 15.9, 15.8, 15.7, - 15.6, 15.6, 15.5, 15.3, 15.1, 14.9, 14.6, 14.4, 14.2, 14, 14, 14, 14.2, 14.4, 14.2, - 14, 13.8, 13.9, 14, 14, 14.3, 14.4, 14.3, 14.3, 14.5, 14.8, 14.9, 14.9, 15, 15.2, - 15.5, 15.8, 16.1, 16.5, 17.1, 17.5, 17.8, 17.9, 17.8, 17.7, 17.8, 17.9, 18.3, 18.4, - 18.7, 19, 19.2, 19.3, 19.6, 19.9, 20.1, 20.1, 20, 19.9, 19.6, 19.6, 19.7, 19.9, 20.1, - 20.6, 21, 21.4, 21.7, 22.2, 22.6, 23, 23.4, 23.6, 23.9, 24.1, 24.3, 24.5, 24.7, 24.9, - 25.2, 25.5, 25.8, 25.8, 25.9, 26.2, 26.4, 26.6, 26.9, 27.3, 27.6, 27.7, 27.7, 27.6, - 27.3, 27, 26.7, 26.4, 26.1, 25.9, 25.9, 25.8, 25.7, 25.6, 25.3, 24.7, 24.4, 24.2, 24, - 23.6, 23.3, 22.9, 22.4, 22.1, 21.7, 21.2, 20.7, 20, 19.4, 18.8, 18.3, 18, 17.7, 17, - 16.3, 15.6, 15.1, 14.9, 14.8, 14.6, 14.5, 14.4, 14.2, 14.2, 14.3, 14.4, 14.7, 14.8, - 14.8, 14.9, 15, 15.3, 15.5, 15.6, 15.8, 16, 16.3, 16.5, 16.5, 16.6, 16.6, 16.3, 15.8, - 15.4, 15, 14.9, 14.9, 14.9, 15, 15.1, 15.2, 15.3, 15.5, 15.8, 16.1, 16.3, 16.5, 16.9, - 17.3, 17.8, 18.4, 18.9, 19.3, 19.7, 20.1, 20.5, 20.7, 20.8, 21.1, 21.4, 21.7, 21.8, - 21.8, 21.6, 21.3, 20.8, 20.4, 20, 19.8, 19.5, 19.3, 19.1, 19, 19, 19, 19.2, 19.3, - 19.5, 19.6, 19.6, 19.7, 19.7, 19.8, 19.8, 19.8, 19.7, 19.8, 19.9, 20, 20.3, 20.6, - 20.9, 21.2, 21.3, 16.5, 16.5, 16.5, 16.5, 16.5, 16.5, 16.3, 16.3, 16.5, 16.6, 16.6, - 16.7, 16.8, 16.8, 16.8, 16.8, 16.7, 16.6, 16.4, 16.5, 16.6, 16.7, 16.9, 16.7, 16.5, - 16.5, 16.4, 16.5, 16.5, 16.4, 16.3, 16.3, 16.2, 16.1, 16.2, 16.4, 16.5, 16.5, 16.6, - 16.7, 16.6, 16.5, 16.4, 16.3, 16.4, 16.3, 16.1, 16.1, 16, 16, 15.9, 15.7, 15.4, 15.2, - 15.1, 14.9, 14.6, 14.4, 14.3, 14.3, 14.5, 14.7, 14.5, 14.2, 14.1, 14.1, 14.1, 14.3, - 14.5, 14.8, 15, 15.1, 15.2, 15.3, 15.4, 15.4, 15.5, 15.6, 15.7, 16, 16.4, 16.7, 17.1, - 17.5, 17.9, 18.2, 18.2, 18.2, 18.1, 18.1, 18.4, 18.6, 18.7, 19, 19.2, 19.4, 19.6, - 19.8, 20.1, 20.3, 20.3, 20.2, 20, 19.9, 19.8, 19.8, 20, 20.2, 20.6, 21.1, 21.5, 21.9, - 22.3, 22.7, 23.2, 23.7, 24, 24.1, 24.2, 24.3, 24.5, 24.7, 24.9, 25.1, 25.4, 25.6, - 25.8, 25.9, 26.3, 26.6, 26.8, 27.1, 27.5, 27.7, 27.8, 27.7, 27.5, 27.2, 26.9, 26.6, - 26.4, 26.2, 26, 26, 26, 25.9, 25.6, 25.2, 24.6, 24.3, 24, 23.9, 23.6, 23.2, 22.8, - 22.4, 22, 21.5, 21, 20.6, 19.9, 19.2, 18.7, 18.3, 17.9, 17.6, 16.9, 16.2, 15.6, 15.1, - 15, 14.9, 14.7, 14.8, 14.8, 14.5, 14.5, 14.5, 14.5, 14.7, 14.8, 15.1, 15.2, 15.4, - 15.6, 15.8, 16.1, 16.3, 16.6, 16.8, 16.9, 16.9, 17, 16.9, 16.6, 16.2, 15.6, 15.2, - 15.1, 15.1, 15.1, 15.2, 15.3, 15.4, 15.5, 15.8, 16, 16.3, 16.6, 16.9, 17.2, 17.7, - 18.2, 18.7, 19.1, 19.4, 19.8, 20.2, 20.6, 20.8, 21.1, 21.3, 21.6, 21.8, 21.9, 21.8, - 21.5, 21.2, 20.7, 20.2, 19.9, 19.7, 19.6, 19.4, 19.3, 19.3, 19.4, 19.5, 19.6, 19.7, - 19.7, 19.9, 19.9, 19.9, 20, 20.1, 20.2, 20.2, 20.1, 20.1, 20.2, 20.3, 20.5, 20.7, - 20.9, 21.1, 21.2, 16.5, 16.5, 16.5, 16.5, 16.5, 16.5, 16.4, 16.4, 16.6, 16.6, 16.6, - 16.7, 16.8, 16.7, 16.7, 16.7, 16.5, 16.4, 16.6, 16.7, 16.8, 16.9, 16.9, 16.8, 16.7, - 16.8, 16.5, 16.5, 16.5, 16.5, 16.4, 16.3, 16.2, 16.2, 16.5, 16.6, 16.7, 16.8, 16.7, - 16.8, 16.6, 16.6, 16.6, 16.5, 16.4, 16.3, 16.3, 16.4, 16.3, 16.3, 16.2, 15.9, 15.7, - 15.4, 15.3, 15.1, 14.9, 14.7, 14.5, 14.5, 14.8, 14.7, 14.4, 14.2, 14.3, 14.4, 14.5, - 14.6, 15, 15.3, 15.4, 15.5, 15.6, 15.6, 15.7, 15.8, 15.9, 16.1, 16.3, 16.7, 17, 17.4, - 17.7, 18.1, 18.4, 18.5, 18.5, 18.6, 18.5, 18.6, 18.7, 18.9, 19.1, 19.3, 19.4, 19.5, - 19.7, 19.9, 20.2, 20.3, 20.3, 20.1, 20.1, 20.1, 20.1, 20, 20.1, 20.3, 20.6, 21.1, - 21.6, 22.1, 22.5, 23, 23.4, 24, 24.4, 24.6, 24.6, 24.7, 24.8, 24.9, 25, 25.2, 25.4, - 25.7, 25.9, 26, 26.3, 26.7, 26.9, 27.2, 27.5, 27.6, 27.6, 27.4, 27.3, 27.1, 26.8, - 26.6, 26.4, 26.2, 26.1, 26.1, 26.2, 26, 25.5, 24.9, 24.4, 24.1, 23.8, 23.7, 23.5, - 23.1, 22.7, 22.3, 21.9, 21.4, 20.9, 20.5, 19.8, 19.2, 18.6, 18.2, 17.7, 17.2, 16.7, - 16.1, 15.5, 15.2, 15.1, 15, 14.9, 15, 15, 14.7, 14.7, 14.7, 14.7, 14.9, 15, 15.2, - 15.5, 15.7, 16, 16.3, 16.6, 17, 17.2, 17.3, 17.3, 17.3, 17.3, 17.2, 16.9, 16.5, 16, - 15.6, 15.4, 15.5, 15.4, 15.5, 15.5, 15.5, 15.7, 15.9, 16.2, 16.5, 16.8, 17, 17.5, - 17.9, 18.3, 18.8, 19.2, 19.6, 20, 20.3, 20.7, 21, 21.2, 21.4, 21.7, 21.8, 21.8, 21.6, - 21.3, 20.9, 20.4, 20, 19.8, 19.7, 19.6, 19.5, 19.5, 19.6, 19.7, 19.8, 20, 20, 20, - 20.1, 20.2, 20.2, 20.3, 20.4, 20.4, 20.4, 20.4, 20.4, 20.5, 20.7, 20.8, 20.8, 20.9, - 21, 21, 16.6, 16.5, 16.5, 16.5, 16.5, 16.5, 16.5, 16.5, 16.6, 16.6, 16.7, 16.7, 16.8, - 16.8, 16.8, 16.7, 16.6, 16.5, 16.6, 16.8, 16.8, 16.8, 16.8, 16.8, 16.7, 16.6, 16.5, - 16.5, 16.5, 16.5, 16.5, 16.4, 16.5, 16.5, 16.7, 16.8, 16.9, 16.9, 16.7, 16.8, 16.8, - 16.7, 16.7, 16.5, 16.4, 16.5, 16.5, 16.5, 16.5, 16.6, 16.5, 16.3, 16.1, 16, 15.8, - 15.4, 15.1, 15, 14.8, 14.6, 14.7, 14.6, 14.5, 14.6, 14.6, 14.8, 14.9, 15.1, 15.4, - 15.7, 15.8, 15.9, 16, 16.1, 16.2, 16.3, 16.5, 16.6, 16.9, 17.2, 17.5, 17.8, 18, 18.4, - 18.6, 18.7, 18.7, 18.8, 18.8, 18.9, 19.1, 19.2, 19.4, 19.4, 19.4, 19.5, 19.6, 19.8, - 20, 20.1, 20.1, 20.1, 20.2, 20.3, 20.3, 20.3, 20.4, 20.6, 20.8, 21.3, 21.9, 22.6, - 22.9, 23.2, 23.9, 24.4, 24.8, 25, 25.1, 25.2, 25.2, 25.3, 25.5, 25.7, 25.9, 26.1, - 26.1, 26.3, 26.6, 26.9, 27.1, 27.3, 27.5, 27.5, 27.4, 27.2, 27.2, 27, 26.8, 26.7, - 26.6, 26.3, 26.3, 26.3, 26.3, 26, 25.5, 24.9, 24.3, 23.9, 23.6, 23.4, 23, 22.7, 22.4, - 21.8, 21.6, 21.3, 20.6, 20, 19.6, 19.1, 18.7, 18.1, 17.7, 17.2, 16.6, 16, 15.5, 15.2, - 15.1, 15.1, 15.1, 15.1, 15, 14.9, 15, 14.9, 15, 15.3, 15.4, 15.6, 15.8, 16.1, 16.5, - 16.9, 17.2, 17.5, 17.6, 17.7, 17.6, 17.5, 17.4, 17.4, 17.2, 16.8, 16.5, 16.2, 16, 16, - 16, 15.9, 15.8, 15.7, 15.8, 16.1, 16.4, 16.7, 16.9, 17.1, 17.5, 17.9, 18.5, 19, 19.4, - 19.8, 20.2, 20.5, 20.8, 21.1, 21.3, 21.5, 21.6, 21.6, 21.6, 21.4, 21, 20.5, 20, 19.8, - 19.8, 19.7, 19.7, 19.7, 19.7, 19.8, 19.9, 20.1, 20.3, 20.3, 20.4, 20.4, 20.4, 20.6, - 20.6, 20.7, 20.6, 20.6, 20.6, 20.7, 20.9, 21, 20.9, 20.9, 20.8, 20.9, 20.9, 16.7, - 16.7, 16.5, 16.6, 16.5, 16.5, 16.5, 16.4, 16.5, 16.5, 16.7, 16.7, 16.7, 16.9, 17, - 16.9, 16.8, 16.7, 16.5, 16.8, 16.8, 16.7, 16.8, 16.7, 16.7, 16.6, 16.6, 16.5, 16.5, - 16.6, 16.7, 16.9, 16.9, 16.6, 16.7, 16.8, 16.9, 16.8, 16.8, 16.8, 16.9, 16.7, 16.7, - 16.6, 16.6, 16.8, 16.8, 16.8, 16.8, 16.8, 16.8, 16.7, 16.5, 16.5, 16.3, 15.9, 15.6, - 15.3, 15, 14.9, 14.9, 14.9, 14.8, 15, 15.1, 15.2, 15.3, 15.6, 15.9, 16.2, 16.3, 16.4, - 16.4, 16.5, 16.7, 16.8, 16.9, 17.1, 17.4, 17.6, 17.8, 18, 18.2, 18.4, 18.7, 18.9, - 18.9, 19, 19.1, 19.2, 19.3, 19.4, 19.5, 19.5, 19.4, 19.4, 19.5, 19.6, 19.7, 19.9, - 20.1, 20.2, 20.2, 20.3, 20.4, 20.5, 20.6, 20.8, 21.1, 21.6, 22.3, 23.1, 23.5, 23.9, - 24.3, 24.8, 25.2, 25.5, 25.6, 25.7, 25.7, 25.7, 25.9, 26.3, 26.4, 26.6, 26.5, 26.6, - 26.9, 27.1, 27.2, 27.3, 27.3, 27.3, 27.1, 27, 27, 27, 26.9, 26.8, 26.8, 26.6, 26.6, - 26.6, 26.5, 26.1, 25.5, 24.8, 24.2, 23.8, 23.4, 23, 22.6, 22.2, 21.8, 21.1, 20.9, - 20.6, 20.1, 19.7, 19.4, 19, 18.6, 18.1, 17.6, 17.2, 16.9, 16.3, 15.7, 15.3, 15.2, - 15.2, 15.2, 15.2, 15.1, 15.1, 15, 15, 15.1, 15.4, 15.7, 15.9, 16.2, 16.6, 17.1, 17.5, - 17.8, 18, 18, 18, 17.9, 17.8, 17.6, 17.5, 17.4, 17.2, 16.9, 16.7, 16.7, 16.7, 16.6, - 16.3, 16.1, 16, 16.1, 16.2, 16.5, 16.8, 16.9, 17.1, 17.5, 18, 18.6, 19, 19.5, 19.9, - 20.3, 20.6, 20.9, 21.1, 21.3, 21.5, 21.6, 21.5, 21.3, 21, 20.6, 20.1, 19.8, 19.7, - 19.7, 19.7, 19.8, 19.8, 19.9, 20, 20.1, 20.3, 20.5, 20.5, 20.7, 20.8, 20.9, 21.1, 21, - 20.9, 20.7, 20.7, 20.8, 21, 21.1, 21.1, 21, 20.9, 20.8, 20.9, 21, 16.9, 16.9, 16.6, - 16.6, 16.6, 16.5, 16.5, 16.4, 16.5, 16.6, 16.7, 16.6, 16.6, 16.7, 16.9, 16.9, 16.8, - 16.7, 16.6, 16.5, 16.6, 16.6, 16.5, 16.6, 16.8, 16.9, 16.8, 16.7, 16.5, 16.6, 16.7, - 17, 17.1, 16.8, 16.7, 16.8, 16.9, 16.9, 17, 17, 16.9, 16.8, 16.7, 16.8, 16.9, 17.1, - 17.2, 17.1, 17.1, 17, 17.1, 17, 16.8, 16.8, 16.7, 16.5, 16.2, 15.8, 15.3, 15.2, 15.1, - 15.1, 15.1, 15.1, 15.3, 15.4, 15.8, 16.1, 16.4, 16.6, 16.8, 16.9, 16.9, 17, 17.1, - 17.2, 17.3, 17.4, 17.6, 17.8, 17.9, 18.1, 18.3, 18.6, 18.9, 19.3, 19.3, 19.4, 19.5, - 19.5, 19.6, 19.6, 19.5, 19.4, 19.4, 19.4, 19.5, 19.5, 19.7, 20, 20.3, 20.4, 20.4, - 20.5, 20.6, 20.6, 20.9, 21.3, 21.6, 22.2, 22.9, 23.6, 24.1, 24.5, 24.7, 25.1, 25.6, - 25.9, 26.1, 26.2, 26.3, 26.3, 26.4, 26.8, 26.9, 27.1, 27, 27, 27.1, 27.2, 27.2, 27.3, - 27.2, 27.1, 27, 27, 27, 27, 27, 26.9, 26.8, 26.8, 26.9, 26.9, 26.7, 26.3, 25.5, 24.8, - 24.1, 23.6, 23.2, 22.8, 22.4, 21.8, 21.2, 20.8, 20.4, 20.1, 19.7, 19.5, 19.2, 18.8, - 18.4, 17.9, 17.5, 17.2, 16.9, 16.7, 16.2, 15.4, 15.4, 15.4, 15.4, 15.3, 15.2, 15.1, - 15.1, 15.1, 15.2, 15.4, 15.8, 16.2, 16.7, 17.2, 17.7, 18.2, 18.4, 18.5, 18.4, 18.3, - 18.2, 18, 17.8, 17.6, 17.5, 17.4, 17.3, 17.3, 17.2, 17.2, 17, 16.7, 16.5, 16.4, 16.3, - 16.4, 16.6, 16.8, 17, 17.3, 17.7, 18.3, 18.8, 19.3, 19.8, 20.1, 20.4, 20.6, 20.8, - 21.1, 21.4, 21.5, 21.5, 21.3, 20.9, 20.6, 20.2, 19.8, 19.6, 19.6, 19.7, 19.8, 19.9, - 20, 20, 20.1, 20.3, 20.5, 20.7, 20.8, 21, 21.1, 21.2, 21.3, 21.2, 21, 20.8, 20.8, 21, - 21.2, 21.2, 21.1, 21, 20.9, 20.9, 21, 21.4, 16.9, 16.9, 16.7, 16.6, 16.6, 16.6, 16.6, - 16.6, 16.6, 16.7, 16.8, 16.7, 16.8, 16.8, 16.8, 16.8, 16.8, 16.9, 16.8, 16.8, 16.7, - 16.4, 16.3, 16.6, 16.9, 17.1, 17.1, 16.9, 16.6, 16.7, 16.7, 17, 17, 16.9, 16.8, 17, - 17.1, 17.1, 17.1, 17.1, 17, 16.9, 16.9, 17.1, 17.2, 17.2, 17.3, 17.3, 17.3, 17.3, - 17.3, 17.2, 17.1, 17.1, 17.1, 16.8, 16.5, 16.1, 15.7, 15.4, 15.4, 15.3, 15.4, 15.4, - 15.6, 15.8, 16.2, 16.5, 16.8, 16.9, 17.1, 17.2, 17.3, 17.4, 17.4, 17.5, 17.5, 17.6, - 17.8, 17.9, 18, 18.2, 18.4, 18.7, 19.1, 19.5, 19.7, 19.8, 19.9, 19.9, 19.8, 19.7, - 19.6, 19.5, 19.5, 19.5, 19.6, 19.7, 20.2, 20.5, 20.7, 20.8, 20.7, 20.8, 21, 21.1, - 21.4, 21.8, 22.2, 22.8, 23.6, 24.2, 24.7, 25, 25.2, 25.6, 26.1, 26.4, 26.6, 26.8, - 26.9, 26.9, 27, 27.2, 27.3, 27.3, 27.3, 27.3, 27.3, 27.3, 27.3, 27.3, 27.2, 27.1, 27, - 27, 27, 27, 27, 26.9, 27, 27, 27.2, 27.2, 27, 26.6, 25.7, 24.8, 24, 23.5, 23.1, 22.6, - 22, 21.4, 20.9, 20.6, 20.1, 19.8, 19.6, 19.5, 19.1, 18.7, 18.2, 17.7, 17.4, 17.1, - 16.8, 16.6, 16.1, 15.6, 15.6, 15.6, 15.5, 15.3, 15.2, 15.2, 15.3, 15.4, 15.6, 15.8, - 16.1, 16.6, 17.3, 17.8, 18.4, 18.8, 19, 18.9, 18.8, 18.6, 18.4, 18.2, 18, 17.8, 17.7, - 17.7, 17.7, 17.7, 17.7, 17.7, 17.5, 17.3, 16.9, 16.7, 16.6, 16.6, 16.8, 17, 17.3, - 17.6, 18, 18.6, 19.1, 19.6, 20, 20.4, 20.5, 20.6, 20.8, 21, 21.3, 21.4, 21.3, 21, - 20.7, 20.3, 19.8, 19.6, 19.5, 19.5, 19.7, 19.8, 20, 20.1, 20.2, 20.5, 20.7, 20.8, 21, - 21.1, 21.2, 21.3, 21.2, 21.2, 21.1, 21, 20.9, 20.9, 21.1, 21.2, 21.2, 21.2, 21, 20.9, - 21, 21.3, 21.8, 16.9, 17, 16.9, 16.7, 16.7, 16.8, 16.8, 16.8, 16.8, 16.8, 16.8, 16.9, - 17.1, 17, 16.9, 17, 17, 17, 17, 17.1, 17, 16.7, 16.4, 16.5, 17, 17.2, 17.2, 17, 16.8, - 16.8, 16.9, 17, 17, 17, 17, 17.2, 17.2, 17.2, 17.1, 17.2, 17.1, 17, 17.1, 17.1, 17.2, - 17.3, 17.3, 17.4, 17.5, 17.5, 17.6, 17.6, 17.4, 17.3, 17.3, 17.2, 16.8, 16.3, 15.9, - 15.7, 15.7, 15.7, 15.8, 15.9, 16.1, 16.5, 16.8, 16.8, 17, 17.2, 17.4, 17.5, 17.5, - 17.6, 17.7, 17.8, 17.8, 17.9, 18, 18.1, 18.3, 18.3, 18.5, 18.9, 19.4, 19.7, 20, 20.1, - 20.1, 20.1, 20, 20, 19.8, 19.7, 19.7, 19.7, 19.9, 20.2, 20.7, 21, 21.2, 21.2, 21.1, - 21.2, 21.4, 21.7, 22.1, 22.6, 23, 23.5, 24.1, 24.8, 25.1, 25.4, 25.7, 26, 26.4, 26.7, - 26.9, 27.1, 27.3, 27.4, 27.4, 27.6, 27.6, 27.6, 27.6, 27.7, 27.7, 27.6, 27.5, 27.4, - 27.2, 27.1, 27.1, 27.1, 27.1, 27, 27, 27, 27.1, 27.2, 27.3, 27.3, 27, 26.6, 25.8, - 24.8, 24.1, 23.4, 22.9, 22.3, 21.7, 21.2, 20.7, 20.3, 20, 19.7, 19.5, 19.5, 19, 18.5, - 18, 17.6, 17.3, 17.1, 16.8, 16.5, 16, 15.7, 15.6, 15.6, 15.5, 15.4, 15.4, 15.5, 15.6, - 15.8, 16.1, 16.4, 16.6, 17.2, 17.9, 18.6, 19, 19.3, 19.3, 19.2, 19.1, 18.9, 18.7, - 18.5, 18.2, 18, 18, 18, 18, 18.2, 18.2, 18.2, 18, 17.8, 17.4, 17.2, 17, 17, 17.1, - 17.5, 17.7, 18, 18.5, 19, 19.5, 20, 20.3, 20.5, 20.6, 20.6, 20.8, 21, 21.2, 21.2, - 21.1, 20.8, 20.4, 20, 19.6, 19.5, 19.4, 19.5, 19.6, 20, 20.2, 20.4, 20.7, 20.9, 21, - 21.1, 21.3, 21.5, 21.3, 21.2, 21.1, 21, 20.9, 20.9, 21, 21, 21.1, 21.1, 21.1, 21.1, - 20.9, 20.9, 21.1, 21.6, 22, 17, 17, 17, 17, 16.9, 16.9, 17, 17.1, 17, 17, 17, 17.1, - 17.2, 17.2, 17.1, 17.2, 17.2, 17.1, 17, 17.2, 17.1, 16.8, 16.5, 16.7, 17, 17.3, 17.1, - 17, 16.9, 16.9, 17.1, 17.1, 17, 17, 17.2, 17.3, 17.2, 17.2, 17.2, 17.3, 17.2, 17.1, - 17.2, 17.2, 17.4, 17.5, 17.5, 17.5, 17.6, 17.6, 17.8, 17.8, 17.7, 17.6, 17.5, 17.4, - 17.1, 16.6, 16.3, 16, 15.9, 16.1, 16.3, 16.4, 16.6, 17, 17.1, 17, 17.2, 17.3, 17.5, - 17.7, 17.8, 17.9, 18, 18.1, 18.1, 18.1, 18.2, 18.3, 18.4, 18.5, 18.7, 19.1, 19.7, - 20.1, 20.4, 20.4, 20.4, 20.3, 20.2, 20.1, 20, 19.9, 20, 20, 20.2, 20.6, 21, 21.2, - 21.4, 21.4, 21.4, 21.6, 21.9, 22.2, 22.7, 23.2, 23.7, 24.2, 24.7, 25.2, 25.6, 26.1, - 26.3, 26.5, 26.7, 26.9, 27.2, 27.5, 27.7, 27.9, 27.9, 28, 28, 27.9, 27.8, 27.9, 27.9, - 27.7, 27.6, 27.5, 27.3, 27.2, 27.1, 27.1, 27.1, 27.1, 27.1, 27.2, 27.3, 27.4, 27.4, - 27.3, 26.9, 26.4, 25.5, 24.6, 24, 23.4, 22.8, 22.2, 21.4, 20.9, 20.5, 20.2, 19.9, - 19.5, 19.4, 19.1, 18.7, 18.2, 17.7, 17.4, 17.1, 17, 16.7, 16.5, 16.2, 15.8, 15.7, - 15.6, 15.6, 15.6, 15.7, 15.9, 16.1, 16.4, 16.7, 17, 17.3, 18, 18.6, 19.3, 19.6, 19.7, - 19.6, 19.5, 19.3, 19.2, 19, 18.8, 18.4, 18.2, 18.1, 18.2, 18.3, 18.5, 18.7, 18.6, - 18.5, 18.2, 17.9, 17.6, 17.5, 17.5, 17.7, 18, 18.2, 18.5, 19, 19.5, 19.9, 20.3, 20.6, - 20.7, 20.7, 20.8, 20.8, 21, 21.2, 21.2, 21, 20.7, 20.2, 19.7, 19.5, 19.4, 19.4, 19.4, - 19.6, 20, 20.4, 20.6, 20.9, 21.1, 21.2, 21.4, 21.6, 21.5, 21.3, 21.1, 21, 20.9, 21, - 21.2, 21.2, 21.2, 21.1, 21.1, 21.1, 21, 21, 21, 21.4, 21.9, 22.2, 17.2, 17.2, 17.3, - 17.2, 17, 17.1, 17.3, 17.5, 17.4, 17.3, 17.3, 17.2, 17.3, 17.3, 17.3, 17.4, 17.4, - 17.3, 17.1, 17.1, 16.9, 16.5, 16.6, 16.8, 17.1, 17.3, 17.2, 17.2, 17.1, 17.1, 17.2, - 17.2, 17.2, 17.2, 17.2, 17.3, 17.3, 17.3, 17.3, 17.3, 17.2, 17.2, 17.2, 17.3, 17.5, - 17.7, 17.6, 17.6, 17.9, 17.9, 17.9, 17.9, 17.8, 17.8, 17.7, 17.5, 17.2, 16.7, 16.5, - 16.3, 16.2, 16.4, 16.6, 16.7, 16.8, 17.1, 17.2, 17.2, 17.4, 17.5, 17.7, 18, 18.1, - 18.2, 18.3, 18.3, 18.3, 18.3, 18.3, 18.4, 18.6, 18.8, 19, 19.4, 20, 20.5, 20.7, 20.7, - 20.6, 20.5, 20.4, 20.3, 20.3, 20.2, 20.3, 20.4, 20.7, 21, 21.2, 21.4, 21.6, 21.7, - 21.8, 22, 22.4, 22.7, 23.2, 23.8, 24.4, 24.9, 25.4, 25.9, 26.3, 26.6, 26.7, 26.8, - 26.9, 27.2, 27.4, 27.7, 27.9, 28.1, 28.2, 28.2, 28.2, 28.1, 28.1, 28.1, 27.9, 27.7, - 27.7, 27.6, 27.4, 27.2, 27.1, 27.1, 27.2, 27.1, 27.2, 27.4, 27.5, 27.5, 27.5, 27.1, - 26.7, 26.1, 25.3, 24.4, 23.8, 23.2, 22.6, 21.8, 21.2, 20.8, 20.4, 20.1, 19.8, 19.4, - 19.2, 18.9, 18.4, 17.9, 17.5, 17.2, 17, 16.9, 16.6, 16.3, 16.1, 15.9, 15.8, 15.8, - 15.9, 16, 16.2, 16.4, 16.8, 17.1, 17.4, 17.7, 18.1, 18.6, 19.2, 19.9, 20, 20, 19.9, - 19.7, 19.6, 19.4, 19.2, 19, 18.6, 18.3, 18.2, 18.3, 18.6, 18.8, 19, 19.1, 18.9, 18.6, - 18.3, 18, 18, 18.1, 18.2, 18.4, 18.7, 19, 19.5, 19.9, 20.3, 20.6, 20.8, 20.9, 20.9, - 21, 21, 21.1, 21.2, 21.2, 21, 20.6, 20, 19.6, 19.4, 19.4, 19.4, 19.4, 19.6, 20, 20.4, - 20.7, 21, 21.3, 21.6, 21.9, 21.7, 21.5, 21.2, 21, 21, 20.9, 21.1, 21.3, 21.3, 21.2, - 21.1, 21, 21, 21, 21, 21.2, 21.6, 21.9, 22.2, 17.6, 17.6, 17.6, 17.3, 17.2, 17.5, - 17.6, 17.6, 17.5, 17.5, 17.5, 17.4, 17.4, 17.4, 17.3, 17.4, 17.4, 17.4, 17.3, 17.2, - 16.9, 16.7, 16.9, 17, 17.2, 17.4, 17.4, 17.4, 17.2, 17.2, 17.2, 17.2, 17.3, 17.3, - 17.3, 17.3, 17.4, 17.4, 17.4, 17.4, 17.3, 17.3, 17.4, 17.4, 17.5, 17.7, 17.8, 18, - 18.3, 18.3, 18.1, 18, 18, 18, 17.9, 17.6, 17.3, 17, 16.7, 16.5, 16.4, 16.5, 16.7, - 16.9, 17.1, 17.3, 17.3, 17.4, 17.6, 17.8, 18, 18.3, 18.5, 18.6, 18.6, 18.5, 18.5, - 18.5, 18.5, 18.6, 18.8, 19, 19.4, 19.9, 20.4, 20.8, 21, 20.9, 20.7, 20.5, 20.5, 20.5, - 20.4, 20.5, 20.5, 20.6, 20.9, 21.2, 21.4, 21.6, 21.8, 22, 22.2, 22.8, 23.2, 23.5, - 23.9, 24.5, 24.9, 25.4, 26.1, 26.5, 26.8, 27, 27, 27.1, 27.2, 27.3, 27.5, 27.8, 28.1, - 28.2, 28.4, 28.3, 28.2, 28.2, 28.2, 28.2, 27.9, 27.7, 27.7, 27.6, 27.5, 27.4, 27.3, - 27.3, 27.3, 27.3, 27.4, 27.6, 27.6, 27.6, 27.4, 27, 26.5, 25.8, 25.1, 24.2, 23.5, - 22.9, 22.4, 21.7, 21.2, 20.7, 20.4, 20.1, 19.7, 19.3, 19.2, 18.9, 18.3, 17.9, 17.4, - 17.1, 16.9, 16.8, 16.5, 16.2, 16.1, 16.1, 16.2, 16.2, 16.5, 16.6, 16.9, 17.3, 17.6, - 17.9, 18.2, 18.4, 18.8, 19.3, 19.7, 20.1, 20.2, 20.2, 20.1, 19.9, 19.8, 19.6, 19.3, - 19, 18.7, 18.5, 18.4, 18.6, 18.8, 19.1, 19.3, 19.4, 19.2, 19, 18.7, 18.5, 18.5, 18.6, - 18.7, 19, 19.2, 19.5, 19.9, 20.3, 20.6, 20.9, 21, 21.1, 21.1, 21.1, 21.1, 21.2, 21.2, - 21.1, 20.9, 20.4, 19.9, 19.6, 19.5, 19.4, 19.4, 19.5, 19.7, 20.2, 20.6, 20.9, 21.2, - 21.5, 21.8, 22, 21.8, 21.4, 21.1, 21, 21, 21.1, 21.3, 21.4, 21.3, 21.1, 21, 21, 21, - 21, 21.1, 21.3, 21.6, 21.9, 22.2, 18.1, 18, 17.8, 17.6, 17.7, 17.8, 17.8, 17.7, 17.6, - 17.6, 17.6, 17.6, 17.4, 17.4, 17.4, 17.5, 17.5, 17.3, 17.3, 17.1, 16.8, 16.8, 16.8, - 16.9, 17.1, 17.5, 17.6, 17.5, 17.3, 17.3, 17.3, 17.3, 17.4, 17.4, 17.3, 17.3, 17.4, - 17.4, 17.4, 17.5, 17.5, 17.4, 17.6, 17.7, 17.8, 18, 18.1, 18.3, 18.5, 18.5, 18.5, - 18.3, 18.1, 18.1, 18, 17.8, 17.5, 17.3, 17, 16.7, 16.7, 16.8, 16.9, 17.2, 17.3, 17.5, - 17.6, 17.6, 17.8, 18, 18.3, 18.7, 18.8, 18.9, 18.8, 18.8, 18.7, 18.7, 18.8, 18.9, - 19.1, 19.5, 20, 20.4, 20.8, 21.1, 21.2, 21.2, 21, 20.7, 20.6, 20.5, 20.5, 20.6, 20.6, - 20.6, 20.8, 21.1, 21.5, 21.9, 22.2, 22.5, 22.9, 23.6, 24, 24.2, 24.6, 25.1, 25.5, - 25.9, 26.3, 26.8, 27, 27.2, 27.3, 27.3, 27.4, 27.5, 27.9, 28, 28.1, 28.3, 29.1, 29, - 28.4, 28.3, 28.2, 28.1, 27.9, 27.8, 27.7, 27.7, 27.5, 27.5, 27.5, 27.4, 27.4, 27.5, - 27.7, 27.8, 27.7, 27.7, 27.3, 26.8, 26.2, 25.5, 24.8, 24.1, 23.4, 22.7, 22.2, 21.7, - 21.2, 20.7, 20.4, 20, 19.5, 19.2, 19.1, 18.8, 18.2, 17.6, 17.3, 17.1, 16.9, 16.7, - 16.5, 16.3, 16.4, 16.5, 16.8, 16.9, 17.1, 17.4, 17.7, 18.1, 18.3, 18.6, 18.9, 19.1, - 19.5, 19.9, 20.1, 20.3, 20.3, 20.2, 20.1, 19.9, 19.8, 19.7, 19.5, 19.1, 18.9, 18.7, - 18.7, 18.8, 19, 19.4, 19.6, 19.6, 19.5, 19.3, 19, 18.9, 19, 19.1, 19.2, 19.5, 19.7, - 20, 20.3, 20.6, 20.8, 21, 21.2, 21.3, 21.3, 21.3, 21.4, 21.4, 21.4, 21.3, 20.8, 20.4, - 19.9, 19.7, 19.5, 19.5, 19.5, 19.5, 19.9, 20.4, 20.8, 21.1, 21.5, 21.7, 21.8, 21.8, - 21.6, 21.3, 21.1, 21.1, 21.2, 21.3, 21.4, 21.3, 21.2, 21, 21, 21, 21, 21.1, 21.3, - 21.5, 21.7, 22, 22.4, 18.5, 18.4, 18.2, 18.1, 18.1, 18, 18, 17.9, 17.8, 17.7, 17.8, - 17.7, 17.6, 17.5, 17.5, 17.6, 17.6, 17.4, 17.4, 17.4, 17.1, 17, 16.8, 16.8, 17.1, - 17.3, 17.4, 17.4, 17.4, 17.3, 17.3, 17.4, 17.4, 17.4, 17.4, 17.4, 17.4, 17.4, 17.4, - 17.6, 17.7, 17.7, 17.9, 18, 18.2, 18.3, 18.4, 18.6, 18.8, 18.8, 18.8, 18.5, 18.3, - 18.2, 18.1, 17.9, 17.6, 17.3, 17.1, 16.9, 17, 17.1, 17.2, 17.4, 17.6, 17.7, 17.8, - 17.9, 18, 18.3, 18.6, 18.8, 19, 19.1, 19.1, 19, 19, 19, 19.2, 19.6, 19.8, 20.1, 20.4, - 20.8, 21.2, 21.4, 21.4, 21.3, 21.1, 20.9, 20.8, 20.7, 20.6, 20.7, 20.7, 20.7, 20.9, - 21.2, 21.6, 22.2, 22.6, 22.9, 23.5, 24.1, 24.5, 24.8, 25.1, 25.5, 25.8, 26.1, 26.4, - 26.8, 27.1, 27.4, 27.6, 27.6, 27.6, 27.8, 28, 28.1, 28.2, 28.4, 29, 28.8, 28.3, 28.2, - 28, 27.9, 27.8, 27.7, 27.8, 27.7, 27.5, 27.6, 27.6, 27.5, 27.6, 27.8, 28.1, 28.1, - 27.9, 27.7, 27.4, 26.6, 25.9, 25.2, 24.6, 24, 23.4, 22.8, 22.3, 21.7, 21.2, 20.8, - 20.3, 19.8, 19.3, 19, 18.9, 18.8, 18.4, 17.7, 17.2, 17.1, 17, 16.9, 16.8, 16.8, 16.9, - 17.1, 17.4, 17.6, 17.8, 18, 18.5, 18.9, 19.1, 19.5, 19.7, 19.8, 20, 20.2, 20.3, 20.3, - 20.2, 20.1, 20, 19.9, 19.8, 19.7, 19.5, 19.3, 19.1, 19, 19, 19.1, 19.4, 19.6, 19.8, - 19.8, 19.7, 19.5, 19.2, 19.3, 19.4, 19.5, 19.7, 19.9, 20.2, 20.5, 20.7, 20.9, 21, - 21.2, 21.4, 21.5, 21.6, 21.7, 21.8, 21.7, 21.6, 21.4, 20.8, 20.3, 19.9, 19.7, 19.6, - 19.6, 19.6, 19.7, 20.2, 20.8, 21.1, 21.4, 21.7, 21.8, 21.8, 21.6, 21.4, 21.2, 21.1, - 21.2, 21.3, 21.4, 21.4, 21.2, 21.1, 21, 21, 21.1, 21.2, 21.3, 21.5, 21.7, 21.9, 22.2, - 22.7, 18.8, 18.6, 18.5, 18.4, 18.3, 18.2, 18.2, 18, 17.9, 17.9, 17.9, 17.8, 17.7, - 17.6, 17.6, 17.6, 17.6, 17.4, 17.5, 17.7, 17.5, 17.3, 17.1, 17.1, 17.2, 17.3, 17.4, - 17.4, 17.4, 17.3, 17.4, 17.4, 17.4, 17.4, 17.5, 17.6, 17.6, 17.6, 17.6, 17.7, 17.9, - 18.2, 18.4, 18.5, 18.6, 18.8, 18.9, 19, 19.2, 19.2, 18.8, 18.6, 18.6, 18.5, 18.3, - 18.1, 17.8, 17.5, 17.4, 17.3, 17.3, 17.5, 17.5, 17.7, 18, 18.1, 18.2, 18.2, 18.3, - 18.5, 18.7, 19, 19.2, 19.3, 19.3, 19.4, 19.5, 19.5, 19.7, 20, 20.3, 20.6, 21, 21.3, - 21.6, 21.6, 21.6, 21.3, 21.1, 21.1, 21, 20.9, 20.8, 20.7, 20.8, 20.9, 21.1, 21.3, - 21.8, 22.2, 22.9, 23.4, 24.1, 24.6, 25, 25.2, 25.5, 25.8, 26, 26.3, 26.5, 26.8, 27.2, - 27.6, 28, 28.1, 28, 28, 28.1, 28.2, 28.3, 28.4, 28.5, 28.2, 28.1, 28, 27.8, 27.7, - 27.7, 27.6, 27.5, 27.5, 27.4, 27.5, 27.6, 27.5, 27.6, 27.9, 28.2, 28.2, 28, 27.6, - 27.2, 26.5, 25.6, 24.9, 24.4, 23.9, 23.2, 22.7, 22.2, 21.6, 21.1, 20.6, 20.1, 19.6, - 19.1, 18.8, 18.5, 18.4, 18, 17.6, 17.3, 17.2, 17.1, 17.1, 17.2, 17.3, 17.6, 18, 18.2, - 18.4, 18.6, 18.9, 19.3, 19.7, 19.9, 20.1, 20.2, 20.3, 20.4, 20.4, 20.3, 20.2, 20.1, - 20, 19.9, 19.8, 19.8, 20, 19.7, 19.4, 19.3, 19.2, 19.3, 19.4, 19.8, 20, 20, 20, 19.9, - 19.6, 19.5, 19.6, 19.8, 19.9, 20.1, 20.4, 20.6, 20.8, 20.9, 21.1, 21.2, 21.4, 21.6, - 21.8, 21.9, 22, 22, 21.9, 21.7, 21.4, 21, 20.5, 20.1, 19.8, 19.7, 19.7, 19.7, 19.9, - 20.5, 21.1, 21.3, 21.6, 21.8, 21.9, 21.8, 21.6, 21.4, 21.2, 21.2, 21.4, 21.4, 21.4, - 21.3, 21.1, 21, 21, 21.2, 21.4, 21.5, 21.5, 21.7, 21.8, 22.1, 22.5, 23, 19.1, 18.8, - 18.6, 18.5, 18.4, 18.2, 18.2, 18.1, 18, 18.1, 18.1, 17.9, 17.9, 17.9, 17.8, 17.6, - 17.6, 17.5, 17.6, 17.7, 17.5, 17.3, 17.2, 17.2, 17.2, 17.4, 17.4, 17.4, 17.4, 17.4, - 17.5, 17.6, 17.6, 17.7, 17.8, 17.8, 17.7, 17.7, 17.9, 18.1, 18.3, 18.6, 18.8, 19, - 19.2, 19.4, 19.3, 19.3, 19.4, 19.3, 18.9, 18.8, 18.9, 18.8, 18.6, 18.3, 18.1, 17.7, - 17.6, 17.6, 17.7, 17.9, 17.9, 18.1, 18.3, 18.4, 18.6, 18.7, 18.9, 18.8, 19, 19.3, - 19.5, 19.6, 19.7, 19.9, 20.1, 20.2, 20.4, 20.7, 20.9, 21.2, 21.5, 21.7, 21.8, 21.7, - 21.6, 21.4, 21.3, 21.3, 21.3, 21.2, 21, 20.8, 20.9, 21.1, 21.3, 21.5, 22, 22.5, 23.2, - 24, 24.8, 25.3, 25.6, 25.7, 25.9, 26.2, 26.4, 26.6, 26.8, 27, 27.4, 27.8, 28.2, 28.3, - 28.3, 28.3, 28.3, 28.3, 28.4, 28.4, 28.3, 28.2, 27.9, 27.8, 27.7, 27.6, 27.5, 27.5, - 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.6, 27.8, 27.9, 28, 27.8, 27.4, 26.8, 26.1, - 25.2, 24.6, 24, 23.5, 22.8, 22.4, 21.9, 21.5, 21, 20.5, 19.9, 19.5, 19.1, 18.6, 18.2, - 18.1, 17.9, 17.6, 17.4, 17.3, 17.3, 17.4, 17.6, 17.9, 18.3, 18.7, 18.8, 19, 19.4, - 19.7, 20, 20.4, 20.5, 20.6, 20.6, 20.6, 20.5, 20.4, 20.3, 20.2, 20.1, 20, 19.9, 19.9, - 20, 20.1, 19.9, 19.6, 19.5, 19.7, 19.8, 19.9, 20.2, 20.3, 20.3, 20.3, 20.2, 19.9, - 19.8, 20, 20.1, 20.3, 20.6, 20.8, 21, 21.1, 21.2, 21.3, 21.5, 21.7, 21.8, 21.9, 22.1, - 22.2, 22.2, 22.1, 21.9, 21.6, 21.3, 20.9, 20.4, 20, 19.8, 19.8, 19.9, 20.3, 20.9, - 21.4, 21.6, 21.8, 22, 22.1, 21.9, 21.6, 21.4, 21.3, 21.4, 21.6, 21.5, 21.4, 21.2, 21, - 21, 21.2, 21.4, 21.6, 21.7, 21.7, 21.9, 22.1, 22.5, 23, 23.3, 19.3, 19, 18.8, 18.6, - 18.5, 18.3, 18.3, 18.3, 18.2, 18.2, 18.1, 18, 18, 18, 18, 17.8, 17.7, 17.7, 17.6, - 17.6, 17.4, 17.4, 17.2, 17, 17.2, 17.4, 17.5, 17.5, 17.5, 17.6, 17.7, 17.7, 17.8, - 17.8, 17.9, 17.9, 17.9, 17.9, 18.1, 18.4, 18.8, 19, 19.2, 19.5, 19.7, 19.7, 19.7, - 19.5, 19.3, 19.2, 19.2, 19.2, 19.2, 19.1, 18.9, 18.6, 18.2, 17.9, 17.8, 17.8, 18, - 18.2, 18.4, 18.4, 18.6, 18.8, 19, 19.3, 19.6, 19.5, 19.5, 19.8, 19.9, 20.1, 20.3, - 20.4, 20.7, 21, 21.1, 21.3, 21.5, 21.8, 22, 22, 21.9, 21.7, 21.5, 21.4, 21.4, 21.4, - 21.4, 21.3, 21.1, 21.1, 21.1, 21.3, 21.5, 21.8, 22.5, 23.1, 23.8, 24.5, 25.3, 25.7, - 26, 26.2, 26.4, 26.6, 26.7, 27, 27.1, 27.3, 27.6, 27.9, 28.3, 28.4, 28.5, 28.5, 28.4, - 28.4, 28.4, 28.4, 28.3, 28.3, 28.1, 27.8, 27.7, 27.7, 27.6, 27.6, 27.6, 27.6, 27.6, - 27.5, 27.5, 27.5, 27.4, 27.5, 27.6, 27.6, 27.4, 27.1, 26.4, 25.7, 25, 24.4, 23.9, - 23.3, 22.6, 22.1, 21.8, 21.4, 20.8, 20.3, 19.9, 19.7, 19.3, 18.7, 18.1, 17.9, 17.8, - 17.7, 17.5, 17.4, 17.6, 17.9, 18.2, 18.5, 18.9, 19.3, 19.4, 19.7, 20, 20.3, 20.6, - 20.9, 21, 20.8, 20.8, 20.7, 20.5, 20.4, 20.3, 20.2, 20.1, 20.1, 20.1, 20.1, 20.2, - 20.2, 20.1, 19.9, 19.8, 20, 20.2, 20.4, 20.6, 20.6, 20.6, 20.6, 20.3, 20.1, 20, 20.1, - 20.4, 20.7, 21, 21.2, 21.3, 21.4, 21.5, 21.6, 21.7, 21.9, 22.1, 22.1, 22.2, 22.3, - 22.2, 22.1, 22, 21.9, 21.6, 21.2, 20.8, 20.3, 20, 20, 20.1, 20.5, 21.2, 21.6, 21.8, - 21.9, 22, 22.1, 22, 21.7, 21.5, 21.4, 21.6, 21.7, 21.7, 21.5, 21.3, 21.2, 21.3, 21.5, - 21.8, 22, 22, 22, 22, 22.4, 23, 23.4, 23.7, 19.4, 19.1, 18.9, 18.7, 18.6, 18.4, 18.4, - 18.4, 18.4, 18.2, 18.1, 18.1, 18.1, 18.1, 18, 17.9, 17.8, 17.8, 17.8, 17.7, 17.6, - 17.4, 17.3, 17.2, 17.1, 17.3, 17.5, 17.5, 17.6, 17.7, 17.8, 17.8, 17.8, 17.8, 17.9, - 18, 18.1, 18.2, 18.5, 18.9, 19.2, 19.5, 19.8, 20, 20.1, 20.1, 20, 19.7, 19.4, 19.5, - 19.5, 19.6, 19.5, 19.3, 19.1, 18.7, 18.3, 18.1, 18, 18.1, 18.3, 18.5, 18.7, 18.7, - 18.9, 19.1, 19.4, 19.7, 19.9, 19.9, 20, 20.2, 20.4, 20.5, 20.8, 21, 21.2, 21.4, 21.6, - 21.8, 22, 22.1, 22.2, 22.1, 21.9, 21.7, 21.5, 21.5, 21.4, 21.3, 21.5, 21.3, 21.3, - 21.3, 21.3, 21.5, 21.8, 22.3, 23.2, 23.9, 24.7, 25.2, 25.6, 26, 26.4, 26.6, 26.7, - 26.9, 27, 27.2, 27.4, 27.6, 27.9, 28.1, 28.3, 28.5, 28.6, 28.6, 28.5, 28.4, 28.3, - 28.3, 28.2, 28.2, 28.1, 28, 27.8, 27.7, 27.7, 27.8, 27.9, 27.7, 27.6, 27.6, 27.5, - 27.4, 27.3, 27.4, 27.5, 27.4, 27.2, 26.7, 26, 25.4, 24.9, 24.4, 23.8, 23.2, 22.5, - 21.9, 21.8, 21.2, 20.7, 20.2, 19.8, 19.4, 19, 18.6, 18.1, 17.9, 17.8, 17.7, 17.6, - 17.7, 18.2, 18.5, 18.7, 19.1, 19.6, 19.8, 20, 20.3, 20.6, 20.8, 21, 21.1, 21.2, 21, - 20.8, 20.7, 20.5, 20.4, 20.3, 20.3, 20.2, 20.2, 20.3, 20.5, 20.5, 20.4, 20.4, 20.2, - 20.2, 20.4, 20.7, 20.9, 21, 20.9, 20.9, 20.8, 20.5, 20.3, 20.2, 20.3, 20.7, 21, 21.2, - 21.4, 21.5, 21.7, 21.8, 21.8, 21.9, 22.1, 22.3, 22.4, 22.4, 22.4, 22.3, 22.4, 22.3, - 22.1, 21.8, 21.5, 21.2, 20.8, 20.5, 20.5, 20.6, 20.9, 21.4, 21.8, 21.9, 22, 22, 22, - 21.9, 21.7, 21.7, 21.7, 21.8, 21.9, 21.9, 21.9, 21.9, 21.9, 21.9, 22.1, 22.3, 22.4, - 22.5, 22.4, 22.4, 22.9, 23.5, 23.9, 24.1, 19.5, 19.3, 19, 18.9, 18.7, 18.6, 18.6, - 18.5, 18.4, 18.3, 18.3, 18.2, 18.2, 18.1, 18.1, 17.9, 17.9, 17.9, 17.9, 17.9, 17.7, - 17.5, 17.4, 17.3, 17.2, 17.2, 17.5, 17.6, 17.6, 17.7, 17.9, 17.9, 18, 18, 18.1, 18.2, - 18.4, 18.6, 18.9, 19.3, 19.6, 20, 20.3, 20.4, 20.3, 20.2, 20, 19.8, 19.6, 19.8, 19.9, - 19.8, 19.7, 19.5, 19.2, 18.9, 18.5, 18.3, 18.3, 18.5, 18.6, 18.8, 19.1, 19.2, 19.3, - 19.7, 19.9, 20.1, 20.2, 20.3, 20.5, 20.7, 20.9, 21, 21.3, 21.5, 21.6, 21.8, 21.9, - 22.1, 22.2, 22.3, 22.3, 22.2, 22, 21.7, 21.4, 21.4, 21.3, 21.2, 21.3, 21.4, 21.4, - 21.5, 21.5, 21.7, 22.2, 22.8, 23.6, 24.3, 25, 25.7, 25.9, 26.3, 26.6, 26.8, 27, 27.3, - 27.4, 27.4, 27.5, 27.7, 28, 28.2, 28.4, 28.6, 28.7, 28.8, 28.6, 28.4, 28.3, 28.1, 28, - 28, 27.9, 28, 27.8, 27.8, 27.9, 28.1, 28.1, 27.9, 27.8, 27.7, 27.6, 27.4, 27.3, 27.3, - 27.3, 27.1, 26.9, 26.5, 25.7, 25.1, 24.6, 24.2, 23.7, 23.1, 22.4, 21.9, 21.7, 21.3, - 21, 20.4, 19.7, 19.1, 18.7, 18.4, 18.1, 17.9, 17.9, 17.9, 17.8, 18.1, 18.6, 18.8, - 19.2, 19.6, 20.1, 20.4, 20.5, 20.8, 21.1, 21.3, 21.4, 21.3, 21.3, 21.1, 20.9, 20.7, - 20.5, 20.4, 20.4, 20.4, 20.4, 20.4, 20.6, 20.7, 20.7, 20.7, 20.6, 20.6, 20.7, 20.9, - 21.2, 21.3, 21.3, 21.3, 21.3, 21.1, 20.8, 20.6, 20.5, 20.7, 21, 21.3, 21.5, 21.7, - 21.7, 21.8, 21.9, 22, 22.1, 22.3, 22.5, 22.6, 22.7, 22.6, 22.6, 22.7, 22.6, 22.4, 22, - 21.8, 21.6, 21.3, 21.2, 21.1, 21.1, 21.3, 21.6, 21.9, 22, 22, 22, 22, 21.9, 21.8, - 21.8, 22, 22.2, 22.3, 22.4, 22.5, 22.6, 22.7, 22.9, 23.1, 22.9, 22.8, 22.7, 22.7, 23, - 23.6, 24, 24.4, 24.5, 19.6, 19.5, 19.2, 19, 18.8, 18.7, 18.7, 18.6, 18.6, 18.4, 18.4, - 18.3, 18.2, 18.2, 18.1, 18, 18, 18, 17.9, 17.8, 17.7, 17.6, 17.4, 17.4, 17.3, 17.2, - 17.4, 17.6, 17.7, 17.9, 18.1, 18.2, 18.1, 18.4, 18.5, 18.4, 18.6, 19, 19.4, 19.7, 20, - 20.4, 20.7, 20.5, 20.4, 20.2, 20, 19.9, 19.8, 19.9, 20, 19.9, 19.7, 19.5, 19.2, 18.9, - 18.7, 18.5, 18.6, 18.7, 18.9, 19.1, 19.3, 19.5, 19.8, 20.1, 20.4, 20.5, 20.6, 20.8, - 21, 21.2, 21.4, 21.7, 21.9, 22.1, 22.2, 22.3, 22.4, 22.6, 22.6, 22.7, 22.5, 22.3, - 22.1, 21.8, 21.5, 21.3, 21.2, 21.2, 21.3, 21.4, 21.5, 21.6, 21.8, 22.1, 22.6, 23.4, - 24.2, 24.8, 25.5, 26, 26.2, 26.6, 26.9, 27.2, 27.4, 27.7, 27.7, 27.7, 27.8, 28, 28.2, - 28.4, 28.5, 28.6, 28.8, 28.8, 28.7, 28.4, 28.4, 28.1, 27.9, 27.9, 27.8, 27.9, 27.8, - 27.8, 28, 28.2, 28.2, 28, 27.9, 27.7, 27.6, 27.4, 27.2, 27.2, 27, 26.8, 26.6, 26.1, - 25.4, 25, 24.4, 24, 23.6, 23, 22.4, 22.3, 22, 21.5, 21, 20.4, 19.7, 18.9, 18.6, 18.4, - 18.2, 18.1, 18, 18, 18.1, 18.5, 18.9, 19.2, 19.5, 20, 20.5, 20.8, 21, 21.3, 21.5, - 21.7, 21.7, 21.5, 21.4, 21.3, 21.2, 21, 20.8, 20.6, 20.6, 20.6, 20.6, 20.7, 20.9, 21, - 21, 20.9, 20.9, 20.9, 21.1, 21.4, 21.5, 21.6, 21.7, 21.7, 21.7, 21.5, 21.2, 21, 20.9, - 21, 21.3, 21.5, 21.7, 21.8, 21.8, 21.9, 22, 22.1, 22.3, 22.4, 22.6, 22.8, 22.8, 22.9, - 23, 22.9, 22.7, 22.5, 22.3, 22.1, 21.9, 21.8, 21.7, 21.6, 21.6, 21.7, 21.8, 21.9, - 21.9, 21.9, 21.9, 21.9, 21.9, 22, 22.2, 22.4, 22.7, 22.9, 23, 23.1, 23.2, 23.4, 23.6, - 23.8, 23.4, 23.1, 23, 23.1, 23.7, 24.3, 24.6, 24.7, 24.7, 19.7, 19.6, 19.4, 19.2, - 18.9, 18.8, 18.8, 18.7, 18.6, 18.5, 18.5, 18.5, 18.4, 18.2, 18.1, 18, 18, 18, 18, - 17.9, 17.7, 17.6, 17.4, 17.4, 17.4, 17.3, 17.3, 17.5, 17.9, 18.1, 18.2, 18.2, 18.2, - 18.5, 18.8, 18.8, 19.2, 19.6, 20, 20.2, 20.3, 20.6, 20.8, 20.6, 20.4, 20.2, 20.1, 20, - 20, 20, 20, 19.9, 19.7, 19.5, 19.3, 19, 18.9, 18.8, 18.8, 18.9, 19, 19.3, 19.6, 19.8, - 20.3, 20.6, 20.8, 21, 21.2, 21.3, 21.6, 21.8, 22, 22.3, 22.5, 22.6, 22.8, 22.9, 23, - 23.1, 23.1, 23, 22.8, 22.5, 22.2, 22.2, 21.8, 21.4, 21.3, 21.3, 21.4, 21.5, 21.7, - 21.9, 22.3, 22.8, 23.3, 24.1, 24.9, 25.5, 26, 26.4, 26.6, 26.9, 27.2, 27.5, 27.8, - 27.9, 27.9, 27.9, 28.1, 28.3, 28.4, 28.6, 28.7, 28.7, 28.8, 28.8, 28.6, 28.4, 28.3, - 28.1, 27.8, 27.9, 27.9, 27.8, 27.7, 27.8, 28, 28.2, 28.3, 28.1, 27.8, 27.6, 27.5, - 27.4, 27.2, 27, 26.8, 26.7, 26.3, 25.8, 25.3, 24.9, 24.3, 23.8, 23.6, 23.4, 23, 22.7, - 22.3, 21.7, 21.1, 20.6, 19.9, 18.8, 18.5, 18.4, 18.3, 18.3, 18.3, 18.3, 18.6, 19, - 19.3, 19.6, 20, 20.6, 20.9, 21.2, 21.5, 21.7, 21.8, 22, 22, 21.8, 21.6, 21.6, 21.5, - 21.4, 21.3, 21.1, 20.9, 20.9, 21, 21.1, 21.2, 21.3, 21.2, 21.1, 21.1, 21.2, 21.4, - 21.7, 21.9, 22.1, 22.1, 22.2, 22.2, 22, 21.8, 21.5, 21.3, 21.4, 21.6, 21.8, 21.9, - 21.9, 21.9, 22, 22, 22.2, 22.4, 22.5, 22.7, 22.9, 23, 23.1, 23.1, 23, 22.7, 22.5, - 22.4, 22.4, 22.3, 22.3, 22.2, 22.1, 22.1, 22.1, 21.9, 21.9, 21.9, 21.9, 21.8, 21.9, - 22, 22.3, 22.7, 23, 23.3, 23.4, 23.6, 23.7, 23.8, 23.9, 24, 24, 23.8, 23.5, 23.4, - 23.7, 24.3, 24.9, 24.9, 24.8, 24.7, 19.7, 19.5, 19.4, 19.2, 18.9, 18.9, 18.9, 18.7, - 18.7, 18.7, 18.6, 18.6, 18.5, 18.4, 18.2, 18.1, 18.1, 18.1, 18.1, 17.8, 17.5, 17.5, - 17.5, 17.5, 17.6, 17.7, 17.5, 17.5, 17.9, 18.1, 18.2, 18.3, 18.4, 18.8, 19, 19.3, - 19.6, 20.1, 20.5, 20.6, 20.7, 20.9, 20.9, 20.7, 20.4, 20.3, 20.2, 20.2, 20.1, 20.1, - 20, 19.9, 19.7, 19.5, 19.4, 19.3, 19.2, 19.1, 19.1, 19.2, 19.3, 19.5, 19.9, 20.3, - 20.8, 21.2, 21.5, 21.7, 21.8, 22, 22.3, 22.5, 22.7, 22.9, 23.2, 23.4, 23.6, 23.6, - 23.7, 23.7, 23.6, 23.3, 22.9, 22.6, 22.3, 22.2, 21.7, 21.4, 21.3, 21.4, 21.5, 21.7, - 22.1, 22.6, 23.2, 23.7, 24.1, 24.8, 25.6, 26.2, 26.6, 26.8, 27, 27.3, 27.6, 27.7, - 27.9, 28, 27.9, 28, 28.3, 28.6, 28.8, 28.8, 28.9, 28.8, 28.8, 28.7, 28.5, 28.3, 28.1, - 27.8, 27.7, 27.8, 27.7, 27.6, 27.6, 27.8, 28, 28.3, 28.3, 28.1, 27.8, 27.6, 27.5, - 27.3, 27.1, 26.9, 26.7, 26.5, 26.2, 25.6, 25, 24.6, 24.2, 23.9, 23.7, 23.4, 23.2, - 22.7, 22.2, 21.7, 21.1, 20.5, 19.6, 18.8, 18.5, 18.5, 18.5, 18.6, 18.8, 19, 19.2, - 19.5, 19.7, 20.1, 20.6, 21.1, 21.4, 21.7, 21.9, 22.1, 22.2, 22.2, 22.3, 22.1, 21.9, - 21.9, 21.9, 21.9, 21.8, 21.5, 21.2, 21.2, 21.3, 21.4, 21.4, 21.4, 21.2, 21.1, 21.1, - 21.3, 21.7, 22, 22.3, 22.5, 22.6, 22.7, 22.7, 22.6, 22.3, 22, 21.7, 21.7, 21.9, 21.9, - 22, 22, 22, 22.1, 22.2, 22.3, 22.5, 22.7, 22.8, 23, 23.1, 23.2, 23.2, 23.1, 22.8, - 22.6, 22.6, 22.6, 22.6, 22.7, 22.7, 22.6, 22.5, 22.3, 22.1, 22, 21.9, 21.9, 21.9, 22, - 22.3, 22.6, 23.1, 23.5, 23.7, 23.8, 23.9, 24.1, 24.2, 24.3, 24.3, 24.3, 24.1, 23.9, - 23.9, 24.3, 24.8, 25, 25, 24.8, 24.5, 19.7, 19.5, 19.4, 19.2, 18.9, 18.9, 18.9, 18.8, - 18.7, 18.7, 18.6, 18.7, 18.6, 18.4, 18.2, 18.2, 18.1, 18.1, 18, 17.6, 17.5, 17.5, - 17.5, 17.4, 17.6, 17.8, 17.6, 17.7, 18.1, 18.2, 18.3, 18.5, 18.7, 19.1, 19.5, 19.8, - 20.1, 20.5, 20.9, 21, 21.1, 21.1, 21, 20.7, 20.5, 20.5, 20.4, 20.4, 20.3, 20.2, 20, - 19.8, 19.7, 19.6, 19.5, 19.6, 19.5, 19.5, 19.5, 19.6, 19.7, 20.1, 20.5, 21, 21.5, 22, - 22.2, 22.4, 22.6, 22.8, 23, 23.2, 23.4, 23.7, 23.9, 24.2, 24.3, 24.3, 24.3, 24.2, - 23.9, 23.6, 23.1, 22.6, 22.4, 22, 21.7, 21.4, 21.4, 21.5, 21.8, 22.2, 22.7, 23.4, 24, - 24.5, 24.9, 25.4, 26, 26.6, 27.1, 27.2, 27.5, 27.8, 27.9, 27.9, 28, 28, 28.1, 28.4, - 28.7, 28.9, 29, 29, 29, 28.9, 28.7, 28.5, 28.3, 28.1, 27.9, 27.7, 27.6, 27.5, 27.5, - 27.4, 27.5, 27.7, 28, 28.3, 28.3, 28.1, 27.8, 27.6, 27.4, 27.2, 27, 26.8, 26.5, 26.1, - 25.8, 25.2, 24.6, 24.4, 24.2, 24.1, 23.9, 23.5, 23.2, 22.6, 22.1, 21.6, 20.9, 20.4, - 19.4, 18.8, 18.7, 18.6, 18.6, 18.8, 19.3, 19.6, 19.7, 19.9, 20.1, 20.5, 21, 21.4, - 21.7, 22, 22.4, 22.6, 22.6, 22.6, 22.5, 22.5, 22.3, 22.3, 22.3, 22.3, 22.2, 21.8, - 21.5, 21.4, 21.6, 21.6, 21.5, 21.4, 21.2, 21.2, 21.2, 21.5, 22, 22.4, 22.7, 22.9, - 23.1, 23.1, 23.1, 22.9, 22.7, 22.5, 22.2, 22.1, 22, 22, 22.1, 22.1, 22.1, 22.2, 22.3, - 22.4, 22.6, 22.8, 23, 23.1, 23.3, 23.3, 23.3, 23.1, 22.9, 22.8, 22.8, 22.8, 22.9, 23, - 23, 22.9, 22.7, 22.5, 22.2, 22.1, 22, 22, 22.1, 22.3, 22.7, 23, 23.4, 23.8, 24.1, - 24.2, 24.2, 24.3, 24.4, 24.5, 24.5, 24.4, 24.3, 24.2, 24.4, 24.7, 25, 25, 24.9, 24.6, - 24.2, 19.7, 19.5, 19.5, 19.2, 19, 19, 19, 18.8, 18.7, 18.6, 18.7, 18.8, 18.6, 18.4, - 18.3, 18.3, 18.2, 18.1, 17.9, 17.6, 17.7, 17.7, 17.6, 17.5, 17.5, 17.7, 17.7, 17.9, - 18.3, 18.5, 18.6, 18.8, 19.1, 19.5, 19.9, 20.2, 20.5, 20.8, 21.1, 21.3, 21.4, 21.3, - 21, 20.8, 20.7, 20.7, 20.7, 20.7, 20.5, 20.3, 20.1, 20, 19.9, 19.8, 19.8, 19.8, 19.9, - 20, 20.1, 20.2, 20.5, 20.9, 21.4, 21.8, 22.2, 22.6, 22.9, 23.2, 23.4, 23.6, 23.8, - 23.9, 24.1, 24.4, 24.7, 25, 25.1, 25.1, 24.9, 24.6, 24.2, 23.8, 23.2, 22.7, 22.5, - 22.1, 21.8, 21.6, 21.6, 22.1, 22.6, 23.1, 23.7, 24.3, 24.9, 25.3, 25.7, 26.1, 26.5, - 27, 27.4, 27.7, 27.8, 28.1, 28.1, 28.1, 28.2, 28.2, 28.5, 28.8, 29.1, 29.3, 29.3, - 29.2, 29.1, 28.9, 28.7, 28.4, 28.1, 27.9, 27.8, 27.6, 27.5, 27.4, 27.4, 27.4, 27.4, - 27.7, 28, 28.2, 28.1, 27.9, 27.7, 27.5, 27.3, 27.2, 27, 26.5, 26.1, 25.6, 25.1, 24.7, - 24.3, 24.1, 24, 23.9, 23.8, 23.5, 23.1, 22.5, 21.9, 21.4, 20.8, 20.3, 19.5, 18.9, - 18.9, 18.9, 18.9, 19.3, 19.7, 20, 20.2, 20.3, 20.6, 21.1, 21.6, 21.9, 22.2, 22.5, - 22.8, 22.9, 22.9, 22.9, 22.8, 22.7, 22.6, 22.5, 22.6, 22.5, 22.4, 22.1, 21.8, 21.7, - 21.7, 21.7, 21.5, 21.4, 21.3, 21.3, 21.3, 21.7, 22.4, 22.8, 23, 23.2, 23.4, 23.4, - 23.4, 23.4, 23.1, 22.8, 22.5, 22.3, 22.1, 22.1, 22.1, 22.1, 22.1, 22.2, 22.4, 22.5, - 22.7, 22.9, 23.1, 23.3, 23.4, 23.4, 23.3, 23.1, 23.1, 23.1, 23.1, 23.1, 23.2, 23.3, - 23.3, 23.2, 22.9, 22.5, 22.3, 22.3, 22.3, 22.3, 22.5, 22.8, 23.2, 23.4, 23.7, 24.1, - 24.6, 24.5, 24.5, 24.5, 24.6, 24.7, 24.7, 24.6, 24.5, 24.4, 24.6, 24.8, 25, 24.9, - 24.7, 24.4, 23.9, 19.8, 19.6, 19.5, 19.3, 19.1, 19.1, 19, 18.8, 18.7, 18.7, 18.6, - 18.7, 18.6, 18.5, 18.5, 18.3, 18.2, 18.1, 17.9, 17.7, 17.8, 17.8, 17.9, 17.8, 17.8, - 17.9, 18.1, 18.2, 18.5, 18.7, 18.9, 19.1, 19.5, 19.8, 20.1, 20.6, 21, 21.2, 21.5, - 21.7, 21.8, 21.5, 21.2, 21, 20.9, 21.1, 21.1, 21, 20.7, 20.4, 20.2, 20.1, 20, 20, - 20.1, 20.2, 20.4, 20.7, 20.8, 21.1, 21.5, 21.9, 22.2, 22.6, 23.1, 23.4, 23.7, 23.9, - 24.1, 24.4, 24.7, 24.8, 25, 25.3, 25.5, 25.7, 25.8, 25.7, 25.4, 24.9, 24.4, 23.8, - 23.3, 22.9, 22.8, 22.5, 22.2, 22, 22.4, 23, 23.5, 24, 24.6, 25.2, 25.7, 26.2, 26.5, - 26.8, 27.2, 27.5, 27.9, 28.2, 28.4, 28.4, 28.2, 28.3, 28.4, 28.5, 28.7, 29, 29.3, - 29.6, 29.5, 29.4, 29.3, 29, 28.6, 28.4, 28.1, 27.8, 27.7, 27.5, 27.5, 27.4, 27.4, - 27.4, 27.5, 27.7, 28, 28, 27.8, 27.7, 27.4, 27.2, 27.1, 27, 26.7, 26.2, 25.7, 25.1, - 24.7, 24.3, 24, 23.8, 23.6, 23.6, 23.4, 23.3, 22.9, 22.3, 21.7, 21.2, 20.7, 20.3, - 19.5, 19.1, 19.2, 19.3, 19.6, 19.8, 20.1, 20.4, 20.6, 20.7, 21.1, 21.7, 22.1, 22.5, - 22.8, 23, 23.1, 23.1, 23.1, 23.1, 23.1, 23, 22.9, 22.9, 22.8, 22.7, 22.5, 22.2, 22, - 21.9, 21.9, 21.8, 21.6, 21.5, 21.4, 21.4, 21.6, 22.2, 22.8, 23.2, 23.4, 23.5, 23.6, - 23.7, 23.7, 23.6, 23.4, 23, 22.7, 22.4, 22.2, 22.2, 22.1, 22.2, 22.2, 22.3, 22.4, - 22.6, 22.8, 23, 23.2, 23.4, 23.5, 23.5, 23.4, 23.3, 23.4, 23.5, 23.5, 23.5, 23.6, - 23.6, 23.6, 23.4, 23, 22.7, 22.6, 22.6, 22.7, 23, 23.2, 23.4, 23.6, 23.8, 24, 24.3, - 24.6, 24.5, 24.6, 24.7, 24.7, 24.7, 24.8, 24.7, 24.6, 24.5, 24.6, 24.7, 24.8, 24.8, - 24.7, 24.3, 23.5, 20, 19.8, 19.5, 19.2, 19.2, 19.2, 19.1, 18.9, 18.8, 18.7, 18.6, - 18.6, 18.5, 18.5, 18.5, 18.4, 18.2, 18.1, 17.9, 17.9, 17.9, 17.9, 17.9, 18, 18, 18.1, - 18.4, 18.5, 18.6, 18.9, 19.1, 19.4, 19.7, 20.1, 20.6, 21.1, 21.6, 21.8, 22, 22, 21.9, - 21.6, 21.4, 21.2, 21.3, 21.4, 21.5, 21.4, 20.9, 20.5, 20.4, 20.3, 20.3, 20.4, 20.5, - 20.7, 21, 21.4, 21.6, 21.8, 22.2, 22.7, 23, 23.5, 23.8, 24.1, 24.4, 24.7, 25, 25.3, - 25.4, 25.6, 25.9, 26.2, 26.3, 26.4, 26.4, 26.2, 25.7, 25.1, 24.5, 23.8, 23.3, 23, 23, - 22.7, 22.5, 22.5, 23, 23.7, 24.2, 24.7, 25.3, 25.8, 26.3, 26.9, 27.1, 27.5, 27.9, - 28.2, 28.6, 28.7, 28.7, 28.7, 28.6, 28.7, 28.8, 29, 29.2, 29.5, 29.6, 29.8, 29.8, - 29.5, 29.4, 29.1, 28.7, 28.5, 28.2, 27.9, 27.7, 27.5, 27.5, 27.5, 27.5, 27.4, 27.6, - 27.8, 28, 28, 27.8, 27.6, 27.3, 27.1, 26.9, 26.7, 26.5, 26, 25.3, 24.8, 24.4, 24, - 23.9, 23.5, 23.3, 23.3, 23.1, 23, 22.6, 21.9, 21.4, 21, 20.5, 20.2, 19.7, 19.4, 19.5, - 19.7, 20, 20.2, 20.6, 20.9, 21, 21.2, 21.7, 22.3, 22.7, 23, 23.2, 23.3, 23.4, 23.4, - 23.3, 23.3, 23.3, 23.2, 23.2, 23.1, 22.9, 22.7, 22.5, 22.3, 22.2, 22.1, 22, 21.8, - 21.7, 21.5, 21.5, 21.7, 22.1, 22.7, 23.2, 23.6, 23.8, 23.8, 23.9, 23.9, 23.9, 23.7, - 23.5, 23.1, 22.8, 22.5, 22.3, 22.2, 22.2, 22.2, 22.2, 22.3, 22.5, 22.7, 22.9, 23.1, - 23.4, 23.5, 23.5, 23.5, 23.5, 23.6, 23.8, 23.9, 23.9, 24, 24, 24, 23.9, 23.6, 23.3, - 23, 23, 23.1, 23.3, 23.5, 23.8, 24, 24.1, 24.2, 24.2, 24.3, 24.4, 24.4, 24.7, 24.7, - 24.7, 24.6, 24.6, 24.6, 24.3, 24.2, 24.2, 24.5, 24.6, 24.4, 24.2, 23.8, 23, 20.1, - 19.8, 19.5, 19.2, 19.1, 19.2, 19.2, 19, 18.8, 18.7, 18.5, 18.5, 18.5, 18.5, 18.5, - 18.4, 18.2, 18, 17.9, 17.8, 18, 18, 18, 18.1, 18.2, 18.3, 18.5, 18.6, 18.8, 19, 19.3, - 19.7, 20.1, 20.5, 21.1, 21.7, 22, 22.2, 22.3, 22.2, 22, 21.8, 21.6, 21.5, 21.7, 21.7, - 21.7, 21.5, 21.1, 20.7, 20.6, 20.6, 20.6, 20.7, 20.9, 21.3, 21.7, 22, 22.3, 22.6, - 22.9, 23.4, 23.8, 24.2, 24.6, 24.9, 25.3, 25.5, 25.8, 26.1, 26.2, 26.4, 26.7, 26.9, - 26.9, 26.9, 26.7, 26.3, 25.7, 25, 24.4, 23.8, 23.3, 23, 22.9, 22.9, 22.9, 23.2, 23.6, - 24.2, 25, 25.6, 26, 26.4, 26.9, 27.4, 27.8, 28.2, 28.5, 28.7, 28.9, 28.9, 28.9, 29, - 29, 29.1, 29.1, 29.3, 29.5, 29.8, 29.9, 29.9, 29.9, 29.8, 29.6, 29.2, 28.9, 28.6, - 28.3, 28.1, 27.9, 27.8, 27.7, 27.5, 27.6, 27.6, 27.8, 28.1, 28.2, 28, 27.9, 27.7, - 27.5, 27.3, 26.9, 26.4, 26.2, 25.7, 25, 24.4, 24.1, 23.8, 23.5, 23.2, 23, 23.1, 23, - 22.7, 22.2, 21.6, 21.2, 20.8, 20.5, 20.3, 20.1, 19.8, 19.7, 19.9, 20.2, 20.6, 21, - 21.2, 21.2, 21.5, 22.2, 22.7, 23, 23.3, 23.5, 23.6, 23.7, 23.6, 23.5, 23.4, 23.4, - 23.5, 23.3, 23.2, 23, 22.7, 22.6, 22.6, 22.5, 22.5, 22.3, 22, 21.9, 21.9, 22, 22.3, - 22.7, 23.2, 23.6, 23.9, 24, 24.1, 24.2, 24.1, 24, 23.7, 23.6, 23.3, 22.9, 22.5, 22.4, - 22.3, 22.3, 22.3, 22.3, 22.4, 22.6, 22.8, 23, 23.2, 23.4, 23.6, 23.6, 23.6, 23.8, - 24.2, 24.3, 24.3, 24.3, 24.4, 24.4, 24.3, 24.1, 23.8, 23.5, 23.4, 23.5, 23.6, 23.9, - 24.1, 24.3, 24.5, 24.6, 24.5, 24.4, 24.5, 24.5, 24.5, 24.6, 24.7, 24.4, 24.2, 24.2, - 24.1, 23.8, 23.6, 23.7, 24, 24.2, 23.9, 23.4, 23, 22.4, 20.2, 19.8, 19.6, 19.3, 19.2, - 19.3, 19, 18.9, 18.8, 18.7, 18.5, 18.5, 18.5, 18.4, 18.4, 18.3, 18.1, 18, 17.8, 17.7, - 17.9, 18.1, 18.1, 18.4, 18.5, 18.6, 18.7, 18.8, 19, 19.3, 19.7, 20.1, 20.5, 20.9, - 21.7, 22.2, 22.4, 22.7, 22.6, 22.5, 22.2, 21.9, 21.9, 21.9, 21.9, 21.9, 21.8, 21.6, - 21.3, 21, 20.8, 20.9, 21, 21.3, 21.6, 22, 22.5, 22.8, 23, 23.4, 23.6, 24, 24.4, 24.9, - 25.3, 25.7, 26, 26.3, 26.6, 26.8, 26.9, 27.1, 27.3, 27.3, 27.2, 27.1, 26.7, 26.2, - 25.6, 24.9, 24.2, 23.8, 23.5, 23.3, 23.2, 23.2, 23.5, 23.9, 24.4, 25.1, 25.7, 26.3, - 26.7, 27.1, 27.5, 27.9, 28.3, 28.6, 28.9, 29, 29.2, 29.2, 29.3, 29.4, 29.5, 29.6, - 29.6, 29.7, 29.8, 30, 30.1, 30.1, 30.1, 30, 29.8, 29.4, 29.2, 28.9, 28.6, 28.4, 28.1, - 28, 27.9, 27.8, 27.9, 27.9, 28.1, 28.3, 28.3, 28.3, 28.2, 27.9, 27.7, 27.3, 26.8, - 26.3, 25.8, 25.2, 24.6, 24.1, 23.8, 23.6, 23.3, 23, 22.9, 22.9, 22.9, 22.4, 21.6, - 21.3, 21.1, 21, 20.7, 20.4, 20.2, 20, 20, 20.1, 20.4, 20.8, 21.1, 21.3, 21.5, 21.9, - 22.4, 22.9, 23.2, 23.4, 23.6, 23.6, 23.8, 23.7, 23.6, 23.5, 23.5, 23.5, 23.4, 23.3, - 23.1, 22.9, 22.8, 22.8, 22.8, 22.8, 22.7, 22.5, 22.4, 22.6, 22.8, 23.1, 23.4, 23.7, - 24, 24.2, 24.2, 24.3, 24.2, 24.1, 24, 23.8, 23.8, 23.5, 22.9, 22.6, 22.4, 22.4, 22.3, - 22.3, 22.4, 22.6, 22.8, 22.9, 23, 23.2, 23.5, 23.7, 23.8, 23.9, 24.3, 24.6, 24.7, - 24.7, 24.8, 24.8, 24.7, 24.5, 24.3, 24, 23.8, 23.8, 23.8, 24, 24.3, 24.6, 24.9, 25.2, - 25.1, 24.8, 24.6, 24.6, 24.5, 24.5, 24.3, 24.1, 24, 23.7, 23.6, 23.5, 23.3, 23.2, - 23.3, 23.6, 23.8, 23.6, 23, 22.5, 22.1, 20.2, 19.8, 19.7, 19.4, 19.2, 19.1, 19, 18.9, - 18.8, 18.7, 18.6, 18.5, 18.5, 18.4, 18.3, 18.2, 18.1, 18, 17.8, 17.7, 17.9, 18.2, - 18.3, 18.5, 18.7, 18.8, 19, 19.2, 19.4, 19.7, 20.1, 20.5, 21, 21.7, 22.3, 22.6, 22.8, - 23, 22.8, 22.6, 22.3, 22.2, 22.1, 22, 22, 21.9, 21.7, 21.5, 21.3, 21.1, 21.1, 21.3, - 21.5, 21.9, 22.3, 22.9, 23.3, 23.6, 23.8, 24, 24.3, 24.8, 25.2, 25.6, 26, 26.4, 26.8, - 27.1, 27.4, 27.4, 27.5, 27.6, 27.7, 27.7, 27.5, 27.1, 26.6, 26, 25.5, 24.9, 24.3, 24, - 23.9, 23.8, 23.8, 23.9, 24.1, 24.5, 25.1, 25.7, 26.3, 26.9, 27.3, 27.7, 28, 28.5, - 28.9, 29, 29.2, 29.3, 29.4, 29.6, 29.7, 29.9, 30, 30, 30.1, 30.2, 30.1, 30.3, 30.5, - 30.6, 30.5, 30.3, 30, 29.7, 29.4, 29.2, 29, 28.8, 28.5, 28.3, 28.3, 28.2, 28.1, 28.2, - 28.3, 28.4, 28.5, 28.4, 28.3, 28.1, 28, 27.4, 26.7, 26.1, 25.4, 24.8, 24.4, 24, 23.6, - 23.5, 23.2, 22.9, 22.8, 22.7, 22.8, 22.3, 21.5, 21.2, 21.1, 21, 20.8, 20.5, 20.3, - 20.1, 20.2, 20.4, 20.8, 21.2, 21.6, 21.7, 21.9, 22.2, 22.6, 23, 23.3, 23.5, 23.6, - 23.7, 23.8, 23.8, 23.6, 23.6, 23.6, 23.6, 23.5, 23.3, 23.2, 23.1, 23.2, 23.2, 23.2, - 23.1, 23, 23, 23.3, 23.6, 23.8, 23.9, 24.1, 24.2, 24.4, 24.5, 24.5, 24.4, 24.3, 24.2, - 24, 23.8, 23.6, 23.3, 22.9, 22.6, 22.5, 22.4, 22.4, 22.5, 22.7, 22.7, 22.8, 22.9, - 23.1, 23.3, 23.6, 23.8, 24, 24.4, 24.7, 24.9, 25, 25.2, 25.3, 25.2, 25, 24.7, 24.4, - 24.1, 23.9, 23.9, 24, 24.3, 24.7, 25.1, 25.6, 25.8, 25.6, 25.2, 24.8, 24.7, 24.5, - 24.2, 23.9, 23.6, 23.4, 23.2, 23.1, 23, 23, 23, 23.2, 23.4, 23.6, 23.5, 22.8, 22.3, - 22, 20.3, 19.9, 19.8, 19.6, 19.3, 19, 19, 18.8, 18.7, 18.6, 18.5, 18.5, 18.5, 18.4, - 18.3, 18.2, 18.1, 17.9, 17.7, 17.7, 18.1, 18.2, 18.4, 18.7, 18.9, 19.1, 19.3, 19.4, - 19.7, 20.1, 20.6, 21.1, 21.7, 22.3, 22.8, 23.1, 23.1, 23, 22.9, 22.6, 22.4, 22.3, - 22.3, 22.1, 21.9, 21.8, 21.6, 21.5, 21.4, 21.4, 21.5, 21.7, 22, 22.4, 22.9, 23.5, 24, - 24.3, 24.5, 24.8, 25.2, 25.7, 26.1, 26.4, 26.7, 27.1, 27.6, 27.8, 28, 28, 28, 28.1, - 28.1, 27.9, 27.6, 27.1, 26.5, 25.9, 25.3, 24.8, 24.4, 24.3, 24.3, 24.2, 24.3, 24.5, - 24.8, 25.2, 25.7, 26.3, 26.9, 27.5, 27.9, 28.3, 28.7, 29.1, 29.3, 29.4, 29.4, 29.5, - 29.7, 29.9, 30, 30.2, 30.4, 30.5, 30.5, 30.4, 30.4, 30.5, 30.6, 30.7, 30.6, 30.3, - 30.2, 30.1, 29.9, 29.7, 29.5, 29.3, 28.9, 28.7, 28.6, 28.4, 28.3, 28.5, 28.5, 28.5, - 28.7, 28.6, 28.3, 28.1, 27.9, 27.3, 26.6, 26, 25, 24.5, 24.2, 24, 23.7, 23.6, 23.1, - 22.8, 22.7, 22.6, 22.3, 22, 21.5, 21.3, 21.1, 21, 20.9, 20.6, 20.5, 20.4, 20.4, 20.8, - 21.2, 21.6, 22.1, 22.3, 22.5, 22.6, 22.9, 23.2, 23.5, 23.6, 23.6, 23.7, 23.8, 23.8, - 23.7, 23.6, 23.6, 23.7, 23.6, 23.5, 23.4, 23.4, 23.5, 23.7, 23.6, 23.4, 23.6, 23.7, - 24, 24.3, 24.5, 24.5, 24.6, 24.6, 24.7, 24.7, 24.8, 24.6, 24.5, 24.3, 24, 23.7, 23.4, - 23.1, 22.9, 22.8, 22.7, 22.6, 22.6, 22.8, 23, 22.9, 22.9, 23, 23.2, 23.5, 23.7, 24, - 24.4, 24.8, 25.1, 25.2, 25.4, 25.5, 25.6, 25.4, 25.1, 24.8, 24.4, 24.1, 24, 24, 24.2, - 24.7, 25.2, 25.7, 26.1, 26.2, 26, 25.6, 25.2, 24.9, 24.4, 24, 23.8, 23.5, 23.1, 23, - 22.9, 22.9, 22.9, 23.1, 23.3, 23.4, 23.4, 23.2, 22.5, 22.2, 21.9, 20.4, 20.1, 19.8, - 19.6, 19.4, 19.2, 19.1, 19, 18.6, 18.5, 18.5, 18.5, 18.5, 18.5, 18.3, 18.3, 18.1, - 17.8, 17.8, 18, 18.4, 18.5, 18.7, 19, 19.2, 19.4, 19.6, 19.8, 20.3, 20.7, 21.2, 21.7, - 22.2, 22.7, 23.1, 23.1, 23, 22.8, 22.7, 22.7, 22.4, 22.3, 22.2, 22, 21.9, 21.8, 21.7, - 21.6, 21.6, 21.7, 21.9, 22.2, 22.6, 23.2, 23.7, 24.2, 24.7, 25, 25.4, 25.7, 26, 26.5, - 27, 27.3, 27.6, 27.9, 28.2, 28.4, 28.6, 28.6, 28.5, 28.4, 28.2, 27.9, 27.4, 26.9, - 26.4, 25.8, 25.2, 24.8, 24.7, 24.6, 24.7, 24.7, 24.8, 25.2, 25.6, 25.9, 26.4, 27, - 27.7, 28.2, 28.5, 28.9, 29.2, 29.5, 29.7, 29.8, 29.8, 29.9, 30.1, 30.2, 30.5, 30.6, - 30.7, 30.8, 30.8, 30.7, 30.6, 30.5, 30.5, 30.6, 30.6, 30.5, 30.5, 30.4, 30.3, 30, - 29.8, 29.6, 29.3, 29, 28.6, 28.5, 28.4, 28.3, 28.4, 28.4, 28.6, 28.5, 28.3, 28, 27.5, - 27, 26.3, 25.6, 24.7, 24.2, 24.1, 24, 23.8, 23.5, 23, 22.8, 22.7, 22.5, 22, 21.8, - 21.6, 21.4, 21.2, 21, 20.9, 20.7, 20.6, 20.7, 20.8, 21, 21.4, 21.9, 22.4, 22.7, 22.9, - 23.1, 23.2, 23.5, 23.6, 23.8, 23.9, 23.8, 23.8, 23.7, 23.6, 23.6, 23.6, 23.7, 23.7, - 23.7, 23.7, 23.7, 23.8, 24, 23.9, 23.9, 24.3, 24.4, 24.7, 24.9, 24.9, 24.9, 25, 25, - 25, 25, 24.9, 24.7, 24.5, 24.2, 23.9, 23.5, 23.2, 23, 22.9, 22.9, 22.9, 22.9, 23, - 23.1, 23, 23, 23.1, 23.2, 23.4, 23.7, 24.1, 24.5, 24.9, 25.2, 25.4, 25.6, 25.7, 25.8, - 25.7, 25.4, 25.1, 24.8, 24.4, 24.2, 24.1, 24.2, 24.5, 25.1, 25.7, 26.1, 26.5, 26.6, - 26.3, 26, 25.5, 24.9, 24.4, 23.9, 23.7, 23.5, 23.1, 22.9, 22.9, 22.9, 22.9, 23.2, - 23.3, 23.3, 23.2, 23, 22.4, 22, 21.9, 20.4, 20.1, 19.8, 19.6, 19.3, 19.2, 19.2, 19, - 18.5, 18.5, 18.4, 18.4, 18.5, 18.3, 18.2, 18.2, 18, 17.9, 18.1, 18.4, 18.7, 18.9, - 19.2, 19.5, 19.7, 20, 20.1, 20.4, 20.8, 21.4, 21.8, 22.2, 22.8, 23.1, 23.1, 23, 22.9, - 22.7, 22.7, 22.6, 22.4, 22.3, 22.2, 22.1, 21.9, 21.9, 21.8, 21.8, 21.9, 22.1, 22.4, - 22.8, 23.4, 24, 24.6, 25, 25.3, 25.7, 26.1, 26.4, 26.8, 27.3, 27.8, 28.1, 28.3, 28.5, - 28.8, 29, 29.1, 29.1, 28.9, 28.7, 28.2, 27.7, 27.2, 26.8, 26.2, 25.7, 25.2, 24.9, - 24.9, 25.1, 25.3, 25.4, 25.6, 25.9, 26.3, 26.8, 27.2, 27.7, 28.3, 28.8, 29.1, 29.4, - 29.7, 29.8, 30, 30, 30.2, 30.3, 30.6, 30.8, 30.9, 30.9, 31, 31, 31, 30.8, 30.6, 30.6, - 30.6, 30.6, 30.7, 30.8, 30.8, 30.6, 30.4, 30.3, 30.1, 29.8, 29.5, 29.2, 28.7, 28.4, - 28.2, 28.1, 28.1, 28.2, 28.3, 28.2, 28.1, 27.6, 27.1, 26.5, 25.8, 25.2, 24.5, 24, - 23.7, 23.6, 23.6, 23.3, 23, 22.9, 22.6, 22.3, 22, 21.9, 21.8, 21.5, 21.2, 21.1, 21, - 20.9, 20.9, 21, 21.2, 21.5, 21.8, 22.3, 22.8, 23, 23.2, 23.4, 23.5, 23.7, 23.9, 24.1, - 24, 23.9, 23.8, 23.7, 23.6, 23.6, 23.7, 23.7, 23.8, 23.8, 23.8, 23.9, 24, 24.2, 24.2, - 24.3, 24.6, 24.8, 25, 25.1, 25.1, 25.2, 25.3, 25.3, 25.2, 25.1, 24.9, 24.7, 24.5, - 24.2, 23.8, 23.4, 23.1, 23, 23, 23.2, 23.3, 23.3, 23.4, 23.4, 23.3, 23.3, 23.3, 23.5, - 23.8, 24.1, 24.6, 25, 25.3, 25.5, 25.7, 25.9, 26, 25.9, 25.7, 25.4, 25.1, 24.8, 24.5, - 24.4, 24.5, 24.5, 24.9, 25.5, 26, 26.5, 26.7, 26.7, 26.4, 26.1, 25.7, 25.1, 24.4, - 23.9, 23.6, 23.3, 23.2, 23.2, 23.2, 23.1, 23.1, 23.2, 23.1, 22.9, 22.8, 22.5, 22.2, - 22.1, 22, 20.4, 20.2, 19.8, 19.5, 19.2, 19, 18.8, 18.6, 18.4, 18.4, 18.4, 18.3, 18.4, - 18.3, 18.2, 18.1, 18, 18.1, 18.4, 18.9, 19.3, 19.7, 19.9, 20.1, 20.3, 20.6, 20.9, - 21.2, 21.7, 22, 22.4, 22.8, 23.1, 23.1, 22.9, 22.8, 22.7, 22.7, 22.6, 22.5, 22.4, - 22.3, 22.2, 22.1, 22, 22, 22, 22.2, 22.3, 22.6, 23, 23.7, 24.3, 24.8, 25.3, 25.8, - 26.1, 26.4, 26.8, 27.1, 27.4, 27.9, 28.3, 28.6, 28.9, 29.2, 29.3, 29.4, 29.5, 29.4, - 29.2, 28.8, 28.1, 27.6, 27.1, 26.6, 26.1, 25.7, 25.3, 25, 25.1, 25.5, 25.7, 25.9, - 26.2, 26.6, 27.1, 27.7, 28, 28.5, 28.9, 29.3, 29.6, 30, 30.2, 30.4, 30.4, 30.4, 30.5, - 30.8, 31, 31.2, 31.3, 31.3, 31.2, 31, 31, 30.8, 30.7, 30.7, 30.7, 30.8, 30.9, 31, - 30.9, 30.8, 30.6, 30.5, 30.2, 29.9, 29.5, 29.2, 28.7, 28.4, 28.1, 28, 28, 28, 27.9, - 27.8, 27.7, 27.3, 26.9, 26.2, 25.5, 24.8, 24.3, 23.9, 23.7, 23.6, 23.4, 23.2, 23.1, - 22.9, 22.6, 22.3, 22.1, 22, 21.8, 21.5, 21.3, 21.1, 21, 21, 21.1, 21.4, 21.7, 22, - 22.3, 22.7, 23.2, 23.4, 23.6, 23.7, 23.8, 23.9, 24, 24, 24, 23.9, 23.8, 23.7, 23.6, - 23.7, 23.7, 23.8, 23.8, 23.8, 23.9, 24, 24.1, 24.2, 24.3, 24.4, 24.7, 25, 25.2, 25.3, - 25.4, 25.4, 25.5, 25.5, 25.3, 25.2, 24.9, 24.7, 24.6, 24.3, 23.9, 23.4, 23.1, 23, - 23.1, 23.4, 23.6, 23.6, 23.6, 23.6, 23.5, 23.5, 23.7, 23.9, 24.1, 24.5, 25, 25.4, - 25.6, 25.8, 25.9, 26.1, 26.1, 25.9, 25.7, 25.4, 25.1, 24.9, 24.8, 24.8, 25, 25, 25.2, - 25.8, 26.3, 26.6, 26.8, 26.8, 26.6, 26.3, 25.9, 25.2, 24.5, 23.9, 23.5, 23.3, 23.3, - 23.7, 24, 23.7, 23.4, 23.1, 22.9, 22.7, 22.4, 22.3, 22.2, 22.2, 22.1, 20.3, 19.9, - 19.5, 19.2, 18.9, 18.7, 18.6, 18.5, 18.4, 18.5, 18.5, 18.3, 18.2, 18.3, 18.3, 18.2, - 18.2, 18.5, 19, 19.6, 20.2, 20.4, 20.6, 20.8, 21, 21.4, 21.7, 22, 22.3, 22.6, 22.9, - 23.1, 23.1, 22.9, 22.7, 22.6, 22.6, 22.6, 22.6, 22.5, 22.5, 22.5, 22.4, 22.2, 22.2, - 22.2, 22.4, 22.7, 22.9, 23.2, 23.8, 24.5, 25, 25.5, 25.9, 26.5, 26.9, 27.2, 27.5, - 27.8, 28.2, 28.5, 28.8, 29.1, 29.5, 29.7, 29.8, 29.8, 29.8, 29.7, 29.4, 28.9, 28.1, - 27.6, 27.1, 26.6, 26.2, 25.8, 25.5, 25.4, 25.6, 25.9, 26.2, 26.6, 26.9, 27.4, 28.1, - 28.4, 28.8, 29.2, 29.6, 29.9, 30.2, 30.5, 30.8, 30.9, 31, 30.9, 30.9, 31.2, 31.6, - 31.8, 31.7, 31.7, 31.3, 31, 31, 31, 30.8, 30.8, 30.9, 31.1, 31.2, 31.2, 31.1, 31, - 30.8, 30.5, 30.2, 29.9, 29.6, 29.2, 28.9, 28.5, 28.3, 28.1, 28.2, 28.1, 27.9, 27.8, - 27.5, 27.1, 26.6, 25.9, 25.2, 24.6, 24.2, 24, 24, 24, 23.7, 23.5, 23.2, 23, 22.8, - 22.5, 22.3, 22.1, 21.9, 21.6, 21.4, 21.2, 21.3, 21.5, 21.6, 21.9, 22.3, 22.6, 22.9, - 23.2, 23.6, 23.9, 24, 24, 24.1, 24, 24, 23.9, 23.9, 23.8, 23.7, 23.6, 23.6, 23.7, - 23.7, 23.8, 23.8, 23.9, 24, 24, 24.2, 24.2, 24.3, 24.5, 24.9, 25.2, 25.4, 25.4, 25.5, - 25.5, 25.6, 25.6, 25.4, 25.2, 24.9, 24.7, 24.5, 24.3, 23.9, 23.3, 23.1, 23.1, 23.2, - 23.5, 23.7, 23.7, 23.7, 23.7, 23.7, 23.8, 23.9, 24.1, 24.3, 24.7, 25.2, 25.6, 25.8, - 25.9, 26.1, 26.1, 26.1, 26, 25.7, 25.4, 25.2, 25.1, 25, 25.1, 25.3, 25.3, 25.6, 26.1, - 26.5, 26.6, 26.7, 26.7, 26.6, 26.4, 26, 25.3, 24.5, 23.9, 23.6, 23.5, 23.6, 23.9, - 24.1, 24, 23.6, 23.1, 22.7, 22.5, 22.4, 22.4, 22.3, 22.3, 22.2, 20.3, 19.7, 19.3, - 19.2, 18.9, 18.7, 18.6, 18.7, 18.7, 18.7, 18.7, 18.6, 18.2, 18.1, 18.3, 18.6, 19, - 19.3, 19.9, 20.4, 20.8, 21.2, 21.5, 21.6, 21.9, 22.1, 22.4, 22.6, 22.8, 22.9, 23, - 22.9, 22.8, 22.6, 22.5, 22.6, 22.6, 22.7, 22.7, 22.7, 22.7, 22.8, 22.6, 22.5, 22.5, - 22.6, 22.9, 23.2, 23.5, 24, 24.5, 25.1, 25.8, 26.3, 26.7, 27.3, 27.7, 28, 28.3, 28.6, - 28.8, 29.1, 29.4, 29.6, 29.9, 30.1, 30.2, 30.2, 30.2, 29.9, 29.5, 28.9, 28, 27.5, - 27.1, 26.7, 26.4, 26.1, 26, 25.9, 26, 26.4, 26.7, 27.3, 27.7, 28.1, 28.7, 29, 29.4, - 29.8, 30, 30.4, 30.7, 31.1, 31.3, 31.3, 31.4, 31.4, 31.4, 31.7, 31.9, 32, 31.8, 31.6, - 31.4, 31.1, 31.1, 31, 31, 30.9, 31.1, 31.5, 31.7, 31.3, 31.2, 31.1, 31, 30.8, 30.4, - 30, 29.7, 29.4, 29.1, 28.7, 28.4, 28.3, 28.4, 28.2, 28, 28, 27.7, 27.1, 26.4, 25.8, - 25.2, 24.7, 24.4, 24.4, 24.5, 24.4, 24.3, 24.1, 23.7, 23.5, 23.2, 22.8, 22.6, 22.3, - 22, 21.8, 21.7, 21.6, 21.8, 22, 22.2, 22.5, 22.8, 23.1, 23.3, 23.7, 24, 24.2, 24.3, - 24.3, 24.2, 24.1, 24, 23.9, 23.8, 23.7, 23.6, 23.7, 23.7, 23.7, 23.7, 23.8, 23.8, - 23.9, 24, 24.1, 24.2, 24.2, 24.4, 24.6, 24.8, 25.2, 25.4, 25.5, 25.5, 25.5, 25.5, - 25.6, 25.5, 25.3, 24.9, 24.6, 24.4, 24.2, 23.8, 23.4, 23.2, 23.2, 23.3, 23.6, 23.7, - 23.8, 23.9, 23.8, 23.9, 24, 24.1, 24.3, 24.6, 25, 25.5, 25.8, 25.9, 26.1, 26.2, 26.2, - 26.1, 26, 25.8, 25.6, 25.4, 25.4, 25.4, 25.5, 25.7, 25.7, 25.8, 26.1, 26.3, 26.5, - 26.6, 26.6, 26.5, 26.3, 25.9, 25.3, 24.5, 24, 23.8, 23.9, 24.1, 24.3, 24.4, 24.2, - 23.7, 23, 22.7, 22.5, 22.5, 22.4, 22.4, 22.3, 22.3, 20, 19.4, 19.2, 19.1, 18.9, 18.8, - 19, 19.1, 19.1, 19, 19.1, 19.1, 18.8, 18.7, 19, 19.5, 20.1, 20.3, 20.8, 21.1, 21.4, - 21.7, 22, 22.4, 22.7, 22.8, 23, 23.1, 23.1, 22.9, 22.8, 22.6, 22.5, 22.4, 22.5, 22.8, - 22.9, 22.9, 22.9, 23, 23.1, 23, 22.9, 22.9, 22.9, 23.1, 23.4, 23.7, 24.2, 24.7, 25.3, - 26, 26.5, 27, 27.4, 28, 28.4, 28.7, 29.1, 29.3, 29.5, 29.7, 30, 30.1, 30.3, 30.4, - 30.5, 30.5, 30.3, 29.9, 29.4, 28.8, 28.1, 27.5, 27.2, 27, 26.8, 26.7, 26.6, 26.5, - 26.6, 26.9, 27.3, 27.9, 28.3, 28.9, 29.4, 29.5, 29.9, 30.2, 30.5, 30.8, 31.2, 31.5, - 31.7, 31.8, 31.9, 31.9, 31.9, 32, 32.1, 31.9, 31.7, 31.6, 31.5, 31.3, 31.3, 31.3, - 31.5, 31.6, 31.6, 31.8, 31.9, 31.7, 31.5, 31.3, 31.3, 31.2, 30.7, 30.2, 29.9, 29.5, - 29.2, 28.8, 28.6, 28.7, 28.7, 28.4, 28, 28, 27.8, 27.1, 26.3, 25.8, 25.3, 24.9, 24.8, - 24.9, 25, 24.9, 25, 24.9, 24.5, 24, 23.6, 23.2, 22.8, 22.5, 22.2, 22.1, 22.1, 22.1, - 22.4, 22.6, 22.8, 23.1, 23.4, 23.7, 23.8, 24.1, 24.4, 24.5, 24.5, 24.4, 24.3, 24.1, - 23.9, 23.8, 23.7, 23.7, 23.6, 23.8, 23.8, 23.7, 23.7, 23.8, 23.9, 24, 24, 24.1, 24.2, - 24.3, 24.4, 24.6, 24.8, 25.1, 25.2, 25.4, 25.5, 25.5, 25.5, 25.5, 25.5, 25.3, 24.9, - 24.5, 24.3, 24, 23.7, 23.5, 23.5, 23.5, 23.6, 23.7, 23.8, 24, 24.2, 24, 24.2, 24.3, - 24.4, 24.7, 25, 25.4, 25.7, 26, 26.1, 26.2, 26.3, 26.2, 26.2, 26.1, 26, 25.9, 25.7, - 25.7, 25.8, 25.9, 26, 26, 26.1, 26.1, 26.2, 26.3, 26.3, 26.3, 26.2, 26, 25.6, 25.1, - 24.6, 24.3, 24.2, 24.3, 24.5, 24.7, 24.6, 24.2, 23.5, 23, 22.7, 22.6, 22.5, 22.5, - 22.5, 22.5, 22.4, 19.6, 19.1, 18.9, 19, 19, 19.1, 19.3, 19.5, 19.5, 19.5, 19.5, 19.6, - 19.5, 19.6, 20, 20.3, 20.8, 21.2, 21.4, 21.8, 22.2, 22.3, 22.6, 22.8, 23.1, 23.2, - 23.2, 23.1, 22.8, 22.7, 22.6, 22.5, 22.5, 22.5, 22.8, 23.3, 23.4, 23.4, 23.4, 23.4, - 23.5, 23.5, 23.3, 23.3, 23.5, 23.8, 24.1, 24.5, 25.1, 25.7, 26.4, 26.8, 27.2, 27.7, - 28.2, 28.7, 29.1, 29.4, 29.8, 29.9, 30.1, 30.3, 30.6, 30.6, 30.6, 30.7, 30.7, 30.6, - 30.3, 29.9, 29.3, 28.7, 28.1, 27.7, 27.4, 27.4, 27.3, 27.3, 27.2, 27.1, 27.1, 27.4, - 27.7, 28.3, 28.7, 29.4, 29.7, 30.1, 30.4, 30.8, 31, 31.2, 31.4, 31.7, 32, 32.3, 32.3, - 32.4, 32.2, 32.1, 32, 31.8, 31.8, 31.8, 31.9, 31.8, 31.6, 31.6, 31.9, 32.2, 32.1, - 32.1, 32, 31.9, 31.7, 31.5, 31.3, 31.2, 30.8, 30.5, 30.2, 29.9, 29.6, 29.1, 28.9, - 29.1, 28.9, 28.6, 28.3, 27.9, 27.5, 26.7, 26, 25.5, 25.3, 25.1, 25.1, 25.3, 25.5, - 25.6, 25.5, 25.4, 25, 24.4, 23.9, 23.4, 23, 22.6, 22.4, 22.3, 22.4, 22.6, 22.7, 23, - 23.4, 23.8, 24.2, 24.3, 24.4, 24.6, 24.8, 24.7, 24.6, 24.4, 24.3, 24, 23.8, 23.7, - 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.8, 23.9, 23.9, 24, 24.2, 24.2, 24.4, - 24.5, 24.6, 24.8, 25.1, 25.3, 25.5, 25.6, 25.5, 25.4, 25.3, 25.3, 25.1, 24.7, 24.4, - 24.1, 24, 23.9, 23.8, 23.8, 23.8, 23.8, 23.8, 23.9, 24.2, 24.4, 24.3, 24.5, 24.7, - 24.9, 25.3, 25.5, 25.8, 26, 26.2, 26.2, 26.3, 26.4, 26.4, 26.3, 26.3, 26.2, 26.1, 26, - 26.1, 26.1, 26.2, 26.2, 26.3, 26.3, 26.1, 26.1, 26, 26, 26, 25.9, 25.8, 25.6, 25.2, - 24.9, 24.8, 24.9, 25, 25.1, 25.1, 24.7, 23.9, 23.2, 22.9, 22.8, 22.7, 22.6, 22.6, - 22.6, 22.6, 22.5, 19.2, 19, 19, 19.2, 19.4, 19.5, 19.7, 19.9, 20.1, 20.2, 20.2, 20.2, - 20.3, 20.4, 20.7, 21, 21.3, 21.7, 22, 22.3, 22.6, 22.8, 22.9, 23, 23.1, 23.2, 23, - 22.8, 22.6, 22.5, 22.5, 22.5, 22.6, 22.8, 23.3, 23.7, 23.8, 23.8, 23.9, 23.8, 23.8, - 23.8, 23.7, 24, 24.2, 24.5, 25, 25.5, 26, 26.5, 27.2, 27.6, 27.9, 28.3, 28.8, 29.2, - 29.7, 30, 30.3, 30.5, 30.6, 30.8, 31, 30.9, 30.9, 30.9, 30.8, 30.6, 30.2, 29.8, 29.3, - 28.7, 28.3, 27.9, 27.7, 27.7, 27.8, 27.8, 27.6, 27.4, 27.5, 27.8, 28.2, 28.7, 29.1, - 29.7, 30.2, 30.6, 30.9, 31.5, 31.5, 31.5, 31.7, 32.1, 32.4, 32.5, 32.6, 32.6, 32.5, - 32.3, 32.1, 32, 31.9, 32, 32.2, 32.1, 32, 32, 32.2, 32.4, 32.4, 32.5, 32.5, 32.3, 32, - 31.5, 31.2, 31, 30.7, 30.5, 30.4, 30.2, 30, 29.7, 29.3, 29.2, 29, 28.6, 28.2, 27.6, - 27, 26.4, 25.9, 25.6, 25.4, 25.4, 25.4, 25.6, 25.8, 25.9, 26, 25.6, 25.2, 24.7, 24.4, - 23.9, 23.4, 23, 22.7, 22.6, 22.7, 22.8, 22.9, 23.3, 23.9, 24.4, 24.7, 24.8, 25, 25, - 25, 24.8, 24.6, 24.4, 24.1, 23.9, 23.8, 23.7, 23.7, 23.7, 23.7, 23.6, 23.7, 23.7, - 23.7, 23.8, 23.9, 23.9, 24, 24.2, 24.3, 24.4, 24.5, 24.7, 24.9, 25.1, 25.4, 25.5, - 25.6, 25.5, 25.3, 25.2, 25.1, 24.8, 24.5, 24.2, 24.1, 24, 24.1, 24.1, 24.1, 24, 23.9, - 24, 24.2, 24.5, 24.6, 24.7, 24.8, 25.1, 25.4, 25.7, 25.9, 26, 26.1, 26.2, 26.3, 26.5, - 26.6, 26.5, 26.5, 26.5, 26.4, 26.4, 26.4, 26.3, 26.3, 26.2, 26.2, 26.1, 26.1, 26, - 25.9, 25.9, 25.8, 25.8, 25.8, 25.8, 25.7, 25.5, 25.4, 25.4, 25.5, 25.5, 25.5, 25.2, - 24.6, 23.7, 23.1, 22.9, 22.9, 22.8, 22.8, 22.7, 22.7, 22.6, 22.6, 19.1, 19.2, 19.2, - 19.4, 19.7, 20, 20.3, 20.5, 20.7, 20.8, 20.9, 20.9, 21, 21.2, 21.5, 21.7, 21.9, 22.1, - 22.4, 22.6, 22.8, 22.9, 23, 23, 23, 22.9, 22.8, 22.6, 22.6, 22.6, 22.7, 22.8, 22.9, - 23.2, 23.6, 24, 24.2, 24.2, 24.3, 24.2, 24.1, 24.1, 24.3, 24.6, 24.9, 25.4, 25.9, - 26.4, 27, 27.4, 28, 28.4, 28.6, 28.8, 29.2, 29.7, 30.2, 30.5, 30.7, 30.8, 30.9, 31.1, - 31.1, 31.1, 31.1, 31, 30.7, 30.4, 30, 29.6, 29.1, 28.6, 28.2, 28, 27.8, 27.9, 28, 28, - 27.9, 27.8, 28, 28.3, 28.7, 29.2, 29.7, 30.3, 30.7, 30.9, 31.2, 31.6, 31.6, 31.7, - 31.9, 32.3, 32.5, 32.6, 32.6, 32.7, 32.8, 32.6, 32.6, 32.3, 32, 32.1, 32.2, 32.3, - 32.3, 32.4, 32.6, 32.7, 32.8, 32.8, 32.9, 32.6, 32.3, 31.7, 31.3, 31, 30.8, 30.7, - 30.7, 30.6, 30.5, 30.1, 29.8, 29.4, 29, 28.4, 28, 27.4, 26.8, 26.3, 26, 25.8, 25.8, - 25.8, 25.9, 26, 26.2, 26.3, 26.2, 25.9, 25.4, 24.9, 24.6, 24.2, 23.9, 23.3, 22.9, - 22.9, 22.8, 23, 23.2, 23.8, 24.4, 24.7, 25, 25.1, 25.2, 25.2, 25.1, 24.8, 24.5, 24.2, - 24, 23.8, 23.8, 23.8, 23.8, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.8, 23.9, 24, 24.1, - 24.2, 24.4, 24.6, 24.7, 24.9, 25.1, 25.3, 25.6, 25.6, 25.6, 25.4, 25.3, 25.1, 24.8, - 24.4, 24.3, 24.1, 24.1, 24.2, 24.2, 24.2, 24.2, 24.2, 24.3, 24.5, 25.1, 25.3, 25.1, - 25, 25.3, 25.6, 25.9, 26.1, 26.2, 26.2, 26.2, 26.3, 26.5, 26.7, 26.8, 26.7, 26.6, - 26.6, 26.6, 26.6, 26.5, 26.4, 26.3, 26.2, 26.1, 25.9, 25.9, 25.8, 25.8, 25.8, 25.8, - 25.8, 25.8, 25.9, 25.9, 25.9, 25.9, 26, 25.9, 25.8, 25.4, 25, 24.2, 23.4, 23, 23, - 22.9, 22.9, 22.8, 22.8, 22.8, 22.7, 22.7, 19.1, 19.3, 19.5, 19.9, 20.2, 20.4, 20.8, - 21, 21.2, 21.3, 21.4, 21.6, 21.7, 21.9, 22.1, 22.3, 22.5, 22.6, 22.7, 22.8, 22.8, - 22.8, 22.8, 22.7, 22.6, 22.6, 22.6, 22.6, 22.7, 23, 23.2, 23.3, 23.6, 23.8, 24.1, - 24.5, 24.6, 24.8, 24.8, 24.7, 24.6, 24.6, 24.8, 25.1, 25.7, 26.3, 26.7, 27.3, 27.9, - 28.1, 28.5, 28.9, 29.1, 29.4, 29.8, 30.3, 30.6, 30.9, 31, 31.1, 31.1, 31.2, 31.2, - 31.2, 31.1, 30.9, 30.6, 30.3, 29.7, 29.3, 28.9, 28.4, 28.2, 28, 28, 28, 28.1, 28.2, - 28.2, 28.2, 28.4, 28.9, 29.4, 29.8, 30.4, 30.7, 31.1, 31.5, 31.7, 31.8, 31.9, 32.1, - 32.3, 32.5, 32.6, 32.6, 32.6, 32.7, 32.8, 32.8, 32.8, 32.5, 32.1, 32.2, 32.4, 32.6, - 32.7, 32.9, 33.1, 33.2, 33.2, 33.2, 33.1, 32.8, 32.5, 32, 31.6, 31.4, 31.3, 31.3, - 31.4, 31.3, 30.9, 30.3, 29.9, 29.5, 28.9, 28.2, 27.8, 27.3, 26.8, 26.6, 26.4, 26.2, - 26.3, 26.4, 26.4, 26.5, 26.7, 26.7, 26.5, 26.2, 25.6, 25.1, 24.7, 24.3, 24, 23.6, - 23.2, 23.1, 23, 23.1, 23.6, 24.3, 24.9, 25.1, 25.3, 25.4, 25.4, 25.3, 25.1, 24.7, - 24.4, 24.1, 24, 23.9, 23.8, 23.9, 23.8, 23.7, 23.7, 23.7, 23.7, 23.7, 23.8, 23.9, 24, - 24.1, 24.2, 24.4, 24.6, 24.8, 25, 25.2, 25.3, 25.5, 25.7, 25.7, 25.6, 25.3, 25.1, - 24.9, 24.6, 24.3, 24.2, 24.1, 24.1, 24.2, 24.3, 24.3, 24.4, 24.5, 24.5, 24.8, 25.3, - 25.4, 25.4, 25.5, 25.8, 26, 26.3, 26.4, 26.4, 26.5, 26.5, 26.5, 26.6, 26.8, 26.8, - 26.8, 26.8, 26.8, 26.7, 26.7, 26.5, 26.3, 26.1, 26, 25.8, 25.7, 25.7, 25.7, 25.7, - 25.8, 25.9, 25.9, 26, 26, 26.1, 26.2, 26.5, 26.5, 26.3, 26, 25.3, 24.6, 23.8, 23.2, - 23.1, 23.1, 23, 23, 22.9, 22.9, 22.8, 22.8, 22.8, 19.3, 19.7, 20, 20.3, 20.5, 20.9, - 21.2, 21.4, 21.5, 21.7, 21.9, 22.1, 22.4, 22.6, 22.7, 22.9, 23, 22.9, 22.8, 22.7, - 22.6, 22.5, 22.5, 22.5, 22.5, 22.6, 22.7, 22.9, 23.2, 23.5, 23.8, 24.1, 24.3, 24.5, - 24.8, 25, 25.1, 25.2, 25.2, 25.1, 24.9, 24.9, 25.2, 25.6, 26.4, 27.1, 27.5, 28, 28.4, - 28.7, 29, 29.3, 29.6, 30, 30.4, 30.9, 31.1, 31.2, 31.1, 31.2, 31.3, 31.4, 31.4, 31.2, - 31, 30.8, 30.5, 30, 29.4, 28.9, 28.6, 28.3, 28.1, 28.1, 28.1, 28.1, 28.2, 28.3, 28.4, - 28.7, 29.1, 29.7, 30, 30.3, 30.9, 31.3, 31.7, 32, 32.2, 32.2, 32.3, 32.4, 32.7, 32.8, - 32.9, 32.9, 32.8, 32.9, 32.9, 32.8, 32.8, 32.6, 32.5, 32.7, 32.8, 32.9, 33.1, 33.3, - 33.4, 33.6, 33.7, 33.7, 33.4, 33, 32.5, 32.1, 31.8, 31.6, 31.6, 31.7, 31.7, 31.4, 31, - 30.3, 29.7, 29.2, 28.6, 27.9, 27.7, 27.2, 26.9, 26.9, 26.9, 26.8, 26.9, 27, 27, 26.9, - 26.9, 26.9, 26.6, 26.3, 25.9, 25.4, 24.9, 24.4, 24.1, 23.8, 23.5, 23.3, 23.2, 23.4, - 23.9, 24.6, 25.2, 25.5, 25.6, 25.6, 25.3, 25.1, 24.8, 24.5, 24.2, 24.1, 24, 23.9, - 23.8, 23.9, 23.8, 23.7, 23.7, 23.8, 23.8, 23.9, 24.1, 24.3, 24.3, 24.3, 24.4, 24.6, - 24.8, 25, 25.3, 25.5, 25.6, 25.7, 25.7, 25.7, 25.6, 25.3, 25.1, 25, 24.6, 24.4, 24.3, - 24.2, 24.3, 24.3, 24.3, 24.4, 24.5, 24.6, 24.6, 24.8, 25.2, 25.5, 25.8, 26, 26.2, - 26.4, 26.6, 26.7, 26.8, 26.8, 26.8, 26.8, 26.8, 26.8, 26.9, 27, 26.9, 26.8, 26.7, - 26.6, 26.4, 26.1, 25.8, 25.6, 25.4, 25.4, 25.4, 25.6, 25.7, 25.9, 26, 26.1, 26.1, - 26.1, 26.2, 26.5, 26.8, 26.7, 26.5, 25.7, 24.7, 24.1, 23.5, 23.2, 23.2, 23.1, 23.1, - 23.1, 23, 23, 23, 22.9, 22.9, 19.7, 20, 20.4, 20.7, 21, 21.3, 21.4, 21.6, 21.8, 22, - 22.3, 22.6, 22.9, 23.1, 23.1, 23.1, 23.1, 22.9, 22.7, 22.4, 22.3, 22.3, 22.4, 22.5, - 22.6, 22.9, 23.2, 23.5, 23.8, 24.1, 24.4, 24.8, 25, 25.2, 25.4, 25.6, 25.7, 25.7, - 25.6, 25.4, 25.2, 25.3, 25.7, 26.3, 27.2, 27.9, 28.4, 28.8, 28.9, 29.1, 29.4, 29.7, - 30.1, 30.6, 31.2, 31.4, 31.3, 31.2, 31.2, 31.3, 31.3, 31.3, 31.3, 31.1, 30.8, 30.5, - 30.1, 29.5, 29, 28.6, 28.3, 28.1, 28.1, 28.1, 28.2, 28.2, 28.3, 28.4, 28.6, 29, 29.6, - 30, 30.4, 30.9, 31.4, 31.9, 32.2, 32.5, 32.7, 32.7, 32.7, 32.7, 32.9, 33, 33, 33, - 32.9, 32.8, 32.7, 32.7, 32.7, 32.6, 32.8, 33.1, 33.2, 33.3, 33.5, 33.8, 33.8, 33.9, - 34, 33.8, 33.4, 32.9, 32.5, 32.1, 31.7, 31.5, 31.7, 31.9, 31.8, 31.5, 31, 30.2, 29.5, - 28.9, 28.4, 27.9, 27.8, 27.7, 27.5, 27.4, 27.4, 27.4, 27.4, 27.5, 27.5, 27.4, 27.2, - 27, 26.7, 26.5, 26.2, 25.7, 25.2, 24.8, 24.5, 24.1, 23.7, 23.4, 23.4, 23.8, 24.4, - 24.9, 25.6, 25.7, 25.7, 25.5, 25.2, 24.9, 24.5, 24.2, 24.1, 24.1, 24, 23.9, 23.9, - 23.9, 23.8, 23.8, 23.8, 23.9, 24, 24.1, 24.3, 24.5, 24.6, 24.6, 24.6, 24.8, 25.1, - 25.2, 25.5, 25.7, 25.8, 25.8, 25.8, 25.7, 25.6, 25.3, 25.1, 24.9, 24.7, 24.6, 24.5, - 24.5, 24.5, 24.6, 24.6, 24.6, 24.6, 24.8, 25, 25.2, 25.5, 25.9, 26.2, 26.4, 26.5, - 26.6, 26.8, 27, 27.1, 27.2, 27.2, 27.2, 27, 26.9, 26.9, 27, 26.9, 26.8, 26.7, 26.5, - 26.2, 25.8, 25.5, 25.3, 25.2, 25.2, 25.3, 25.4, 25.7, 25.9, 26.1, 26.2, 26.1, 26.2, - 26.3, 26.6, 26.8, 26.6, 26.2, 25.2, 24.4, 23.8, 23.5, 23.3, 23.3, 23.2, 23.2, 23.1, - 23.1, 23.1, 23, 23, 23, 19.9, 20.2, 20.5, 21.1, 21.4, 21.5, 21.6, 21.8, 22, 22.2, - 22.5, 22.8, 23.2, 23.3, 23.2, 23, 22.8, 22.6, 22.3, 22.1, 22.2, 22.4, 22.6, 22.9, - 23.2, 23.5, 23.9, 24.2, 24.6, 24.8, 24.9, 25.3, 25.6, 25.8, 26, 26.1, 26.2, 26.2, 26, - 25.9, 25.8, 25.9, 26.3, 26.9, 27.8, 28.5, 29.1, 29.3, 29.3, 29.4, 29.7, 30, 30.5, - 30.9, 31.4, 31.4, 31.3, 31.2, 31.1, 31.1, 31.1, 31.1, 31, 30.8, 30.5, 30.1, 29.6, - 29.1, 28.6, 28.3, 28.1, 28, 28.1, 28.2, 28.2, 28.2, 28.3, 28.5, 28.8, 29.3, 29.8, - 30.3, 30.9, 31.4, 32, 32.3, 32.6, 33, 33.1, 33.1, 33, 33, 33.1, 33.1, 33, 32.9, 32.8, - 32.7, 32.7, 32.8, 32.8, 33, 33.2, 33.5, 33.6, 33.7, 34, 34.2, 34.3, 34.2, 34.1, 33.9, - 33.5, 32.9, 32.3, 31.8, 31.5, 31.6, 31.9, 31.9, 31.8, 31.4, 30.7, 30, 29.3, 28.7, - 28.3, 28, 27.9, 27.9, 28, 28, 27.9, 27.9, 27.9, 27.8, 27.7, 27.7, 27.4, 27.1, 26.8, - 26.6, 26.3, 25.7, 25.4, 25.1, 24.7, 24.2, 23.8, 23.6, 23.8, 24.2, 24.7, 25.3, 25.9, - 25.8, 25.6, 25.3, 25, 24.7, 24.4, 24.2, 24.1, 24.1, 24, 24, 24, 23.9, 23.9, 23.9, 24, - 24.1, 24.2, 24.3, 24.5, 24.7, 24.8, 24.8, 24.9, 25, 25.2, 25.4, 25.7, 25.9, 26, 26, - 25.9, 25.8, 25.7, 25.6, 25.4, 25.2, 25, 24.9, 25, 25.1, 25, 24.9, 24.8, 24.8, 24.8, - 24.9, 25.2, 25.6, 25.9, 26.1, 26.4, 26.5, 26.6, 26.7, 27, 27.2, 27.4, 27.6, 27.7, - 27.5, 27.3, 27.1, 27, 27, 26.9, 26.9, 26.7, 26.4, 26.1, 25.7, 25.4, 25.2, 25.1, 25, - 25.1, 25.2, 25.5, 25.7, 26, 26.1, 26.2, 26.4, 26.5, 26.6, 26.6, 26.3, 25.8, 25, 24.2, - 23.7, 23.5, 23.4, 23.3, 23.3, 23.3, 23.2, 23.2, 23.1, 23.1, 23.1, 23.1, 20.3, 20.4, - 20.6, 21.1, 21.6, 21.8, 21.8, 21.7, 21.9, 22.1, 22.4, 22.8, 23.2, 23.2, 22.9, 22.6, - 22.4, 22.2, 22.1, 22, 22.2, 22.6, 23.1, 23.5, 23.9, 24.3, 24.5, 24.8, 25.1, 25.3, - 25.5, 25.8, 26.1, 26.3, 26.5, 26.6, 26.7, 26.6, 26.5, 26.6, 26.6, 26.8, 27.1, 27.6, - 28.3, 28.9, 29.3, 29.5, 29.6, 29.8, 30, 30.5, 30.8, 31.1, 31.3, 31.4, 31.3, 31.1, 31, - 30.9, 30.9, 30.8, 30.7, 30.5, 30.2, 29.7, 29.2, 28.7, 28.3, 28.1, 27.9, 27.8, 27.9, - 28, 28.1, 28.2, 28.4, 28.7, 29.1, 29.7, 30.1, 30.9, 31.6, 32, 32.5, 32.9, 33.3, 33.5, - 33.5, 33.4, 33.3, 33.3, 33.2, 33.2, 33, 32.8, 32.7, 32.6, 32.8, 32.9, 33.1, 33.3, - 33.6, 33.8, 34, 34.2, 34.5, 34.7, 34.6, 34.5, 34.2, 34, 33.5, 32.9, 32.3, 31.8, 31.6, - 31.7, 31.9, 31.9, 31.8, 31.3, 30.4, 29.6, 29, 28.6, 28.3, 28.1, 28.1, 28.3, 28.6, - 28.6, 28.5, 28.4, 28.2, 28.1, 27.9, 27.8, 27.6, 27.3, 26.9, 26.5, 26.2, 25.8, 25.4, - 25.2, 24.8, 24.3, 23.9, 23.8, 24.1, 24.5, 25.1, 25.7, 26, 25.8, 25.6, 25.3, 25, 24.6, - 24.4, 24.3, 24.2, 24.2, 24.1, 24.2, 24.1, 24, 24, 24.1, 24.2, 24.3, 24.4, 24.6, 24.7, - 24.9, 25, 25, 25.1, 25.3, 25.4, 25.6, 25.9, 26.1, 26.2, 26.2, 26.2, 26.2, 26.1, 26.1, - 25.9, 25.6, 25.4, 25.3, 25.4, 25.4, 25.4, 25.2, 24.9, 24.9, 24.9, 25, 25.4, 25.8, - 26.1, 26.3, 26.4, 26.5, 26.6, 26.8, 27.2, 27.5, 27.6, 27.8, 27.8, 27.6, 27.4, 27.2, - 27.2, 27.2, 27, 26.8, 26.6, 26.4, 26.1, 25.5, 25.2, 25.1, 25, 24.9, 24.9, 25, 25.2, - 25.5, 25.8, 26.1, 26.3, 26.5, 26.5, 26.4, 26.2, 25.9, 25.3, 24.6, 24, 23.7, 23.6, - 23.5, 23.4, 23.4, 23.4, 23.3, 23.3, 23.3, 23.2, 23.2, 23.4, 20.5, 20.6, 20.8, 21.1, - 21.5, 21.7, 21.7, 21.6, 21.7, 22, 22.3, 23, 23.3, 22.8, 22.4, 22.2, 22, 22, 22, 22.1, - 22.6, 23.1, 23.5, 24.1, 24.5, 24.8, 25, 25.2, 25.5, 25.7, 26, 26.3, 26.6, 26.8, 27, - 27.1, 27.1, 27.1, 27, 27.2, 27.5, 27.6, 27.9, 28.2, 28.7, 29.3, 29.6, 29.8, 30, 30.2, - 30.4, 30.8, 31.2, 31.4, 31.4, 31.3, 31.1, 30.9, 30.7, 30.5, 30.5, 30.4, 30.2, 29.9, - 29.6, 29.2, 28.7, 28.4, 28.1, 27.9, 27.7, 27.8, 27.9, 28, 28.1, 28.3, 28.6, 29.1, - 29.6, 29.9, 30.4, 31.4, 32.2, 32.5, 33, 33.4, 33.6, 33.8, 33.7, 33.5, 33.4, 33.3, - 33.1, 33, 32.9, 32.8, 32.8, 32.7, 32.9, 33.1, 33.3, 33.6, 33.9, 34.2, 34.5, 34.8, - 34.9, 35, 34.7, 34.5, 34.3, 34, 33.6, 33.1, 32.6, 31.9, 31.7, 31.8, 32, 31.9, 31.6, - 31, 30, 29.5, 29, 28.8, 28.6, 28.4, 28.4, 28.7, 28.9, 28.8, 28.8, 28.7, 28.5, 28.3, - 28.1, 27.8, 27.6, 27.3, 26.9, 26.5, 26.1, 25.8, 25.5, 25.3, 24.8, 24.4, 24.1, 24, - 24.4, 24.8, 25.4, 25.9, 25.9, 25.8, 25.6, 25.3, 25, 24.6, 24.5, 24.4, 24.3, 24.2, - 24.2, 24.2, 24.2, 24.2, 24.2, 24.2, 24.4, 24.5, 24.7, 25, 25.2, 25.2, 25.3, 25.3, - 25.4, 25.5, 25.6, 25.9, 26.1, 26.4, 26.6, 26.8, 26.8, 26.7, 26.7, 26.6, 26.4, 26.1, - 25.8, 25.7, 25.6, 25.6, 25.5, 25.3, 25.1, 25, 25, 25.1, 25.5, 25.9, 26.2, 26.4, 26.4, - 26.5, 26.6, 26.9, 27.2, 27.5, 27.7, 27.8, 27.7, 27.5, 27.4, 27.3, 27.3, 27.2, 27.1, - 26.8, 26.6, 26.3, 25.9, 25.4, 25.1, 24.9, 24.8, 24.8, 24.8, 24.9, 25, 25.3, 25.7, - 26.1, 26.4, 26.5, 26.3, 26, 25.8, 25.4, 24.8, 24.3, 23.9, 23.7, 23.7, 23.6, 23.5, - 23.5, 23.5, 23.4, 23.5, 23.4, 23.4, 23.5, 23.8, 20.4, 20.5, 20.8, 21.1, 21.4, 21.5, - 21.5, 21.5, 21.6, 21.9, 22.2, 22.7, 22.9, 22.6, 22.2, 21.9, 21.9, 22, 22.2, 22.6, - 23.3, 23.8, 24.3, 24.7, 24.9, 25.2, 25.3, 25.6, 25.9, 26.2, 26.5, 26.8, 27.1, 27.2, - 27.4, 27.5, 27.6, 27.6, 27.6, 27.8, 28.1, 28.3, 28.6, 28.7, 29.1, 29.6, 29.9, 30.2, - 30.5, 30.7, 30.9, 31.1, 31.4, 31.5, 31.4, 31.2, 31, 30.7, 30.4, 30.1, 29.9, 29.8, - 29.6, 29.3, 29.1, 28.8, 28.4, 28.1, 27.8, 27.8, 27.8, 27.9, 27.9, 28.1, 28.2, 28.5, - 28.9, 29.5, 29.8, 30.3, 30.9, 32, 32.6, 33, 33.5, 33.7, 33.9, 33.9, 33.8, 33.7, 33.5, - 33.3, 33.1, 33, 33, 33, 33, 33, 33.2, 33.4, 33.5, 33.9, 34.3, 34.5, 34.8, 35.1, 35.1, - 35, 34.7, 34.5, 34.4, 34.1, 33.8, 33.6, 33.1, 32.5, 32.3, 32.1, 32.1, 32.1, 31.5, - 30.6, 30, 29.6, 29.3, 29.2, 29.1, 29, 28.9, 29.1, 29.1, 29, 29, 28.9, 28.7, 28.6, - 28.2, 28, 27.6, 27.2, 26.8, 26.4, 26, 25.7, 25.6, 25.3, 24.8, 24.5, 24.2, 24.3, 24.9, - 25.3, 25.7, 26, 25.9, 25.8, 25.5, 25.2, 24.9, 24.7, 24.6, 24.5, 24.4, 24.3, 24.2, - 24.3, 24.4, 24.5, 24.5, 24.5, 24.7, 24.8, 25, 25.3, 25.5, 25.6, 25.6, 25.6, 25.6, - 25.7, 25.9, 26.2, 26.5, 26.8, 27.1, 27.3, 27.3, 27.3, 27.1, 27, 26.8, 26.4, 26.1, 26, - 25.8, 25.6, 25.4, 25.3, 25, 25, 25, 25.2, 25.5, 25.9, 26.3, 26.4, 26.5, 26.6, 26.8, - 26.9, 27.1, 27.4, 27.6, 27.7, 27.7, 27.5, 27.4, 27.3, 27.2, 27.2, 27, 26.7, 26.5, - 26.2, 25.8, 25.3, 25, 24.9, 24.8, 24.7, 24.7, 24.8, 24.9, 25.3, 25.8, 26.2, 26.4, - 26.4, 26, 25.6, 25.4, 24.9, 24.4, 24, 23.8, 23.8, 23.8, 23.7, 23.6, 23.5, 23.5, 23.6, - 23.6, 23.6, 23.7, 24, 24.4, 20.5, 20.6, 20.8, 21, 21.3, 21.3, 21.3, 21.4, 21.6, 21.8, - 22.1, 22.4, 22.4, 22.2, 22, 21.8, 21.9, 22.3, 22.8, 23.2, 24, 24.6, 25, 25.2, 25.3, - 25.5, 25.7, 26, 26.3, 26.6, 26.9, 27.2, 27.4, 27.5, 27.7, 28, 28.1, 28.2, 28.4, 28.6, - 28.6, 28.8, 29.1, 29.1, 29.4, 29.9, 30.4, 30.7, 30.9, 31, 31.1, 31.4, 31.5, 31.4, - 31.3, 31.1, 30.8, 30.5, 30.1, 29.8, 29.5, 29.3, 29.1, 28.8, 28.6, 28.4, 28.1, 27.8, - 27.8, 27.8, 27.9, 27.9, 28, 28.2, 28.3, 28.7, 29.2, 29.7, 30.2, 30.9, 31.7, 32.7, - 32.9, 33.4, 33.8, 33.9, 34, 34, 33.8, 33.6, 33.5, 33.4, 33.2, 33.1, 33.1, 33.1, 33.1, - 33.2, 33.4, 33.7, 34, 34.4, 34.6, 34.8, 35.2, 35.4, 35.3, 35.1, 34.9, 34.8, 34.7, - 34.4, 34.2, 34.1, 33.6, 33.3, 32.9, 32.2, 31.9, 31.8, 31.4, 30.6, 30.2, 29.9, 29.8, - 29.7, 29.6, 29.5, 29.5, 29.5, 29.4, 29.3, 29.2, 29, 28.8, 28.7, 28.4, 28.1, 27.5, - 27.1, 26.7, 26.4, 26, 25.7, 25.5, 25.2, 24.8, 24.5, 24.4, 24.6, 25.1, 25.6, 26, 26.1, - 26, 25.8, 25.4, 25.2, 24.9, 24.7, 24.6, 24.5, 24.5, 24.5, 24.5, 24.6, 24.9, 25.1, 25, - 24.9, 25, 25.1, 25.3, 25.6, 25.8, 25.9, 25.9, 25.9, 25.9, 26, 26.3, 26.6, 26.9, 27.2, - 27.5, 27.7, 27.7, 27.6, 27.4, 27.2, 27, 26.6, 26.3, 26.1, 25.9, 25.6, 25.3, 25.1, - 24.9, 24.9, 25.1, 25.3, 25.7, 26, 26.4, 26.5, 26.6, 26.8, 26.9, 27, 27, 27.2, 27.4, - 27.5, 27.5, 27.4, 27.3, 27.3, 27.2, 27.1, 26.9, 26.6, 26.4, 26.1, 25.7, 25.3, 25, - 24.9, 24.7, 24.7, 24.7, 24.8, 25, 25.4, 25.9, 26.2, 26.4, 26.3, 25.7, 25.3, 24.9, - 24.5, 24.2, 24, 23.9, 23.9, 23.8, 23.7, 23.7, 23.6, 23.6, 23.7, 23.8, 23.9, 24.1, - 24.6, 25.1, 20.7, 20.7, 20.8, 21, 21.1, 21.2, 21.2, 21.4, 21.5, 21.9, 22.2, 22.1, 22, - 22, 21.9, 22, 22.4, 23, 23.5, 24.1, 24.8, 25.1, 25.4, 25.4, 25.6, 25.8, 26, 26.3, - 26.7, 27, 27.2, 27.4, 27.7, 27.8, 28, 28.4, 28.6, 28.8, 29, 29.1, 29.2, 29.2, 29.3, - 29.5, 29.8, 30.5, 30.9, 31.2, 31.4, 31.3, 31.3, 31.4, 31.4, 31.2, 31.1, 30.8, 30.5, - 30.2, 29.8, 29.4, 29.2, 29, 28.7, 28.4, 28.2, 28.1, 27.9, 27.8, 27.9, 27.9, 27.9, 28, - 28.1, 28.2, 28.5, 28.9, 29.4, 29.8, 30.5, 31.5, 32.5, 33.1, 33.3, 33.8, 34, 34.1, - 34.2, 34, 33.8, 33.6, 33.5, 33.4, 33.3, 33.2, 33.2, 33.2, 33.4, 33.7, 33.9, 34.3, - 34.6, 34.9, 35.1, 35.3, 35.6, 35.6, 35.4, 35.2, 35.2, 35.2, 35.1, 34.7, 34.5, 34.3, - 34, 33.5, 33, 32.3, 31.6, 31.2, 30.9, 30.6, 30.4, 30.3, 30.2, 30.1, 30.2, 30, 29.7, - 29.6, 29.6, 29.5, 29.3, 29.1, 28.9, 28.7, 28.5, 28.2, 27.5, 26.9, 26.6, 26.2, 25.9, - 25.7, 25.5, 25.2, 24.9, 24.6, 24.5, 24.8, 25.5, 25.9, 26.1, 26, 25.9, 25.6, 25.4, - 25.2, 25, 24.8, 24.7, 24.6, 24.6, 24.8, 24.9, 24.9, 25.2, 25.4, 25.5, 25.3, 25.4, - 25.4, 25.5, 25.7, 25.9, 26.1, 26.1, 26.1, 26.2, 26.5, 26.9, 27.1, 27.4, 27.6, 27.8, - 28, 27.8, 27.7, 27.5, 27.2, 26.9, 26.6, 26.3, 26, 25.7, 25.4, 25.2, 25, 24.9, 25, - 25.2, 25.5, 25.9, 26.2, 26.5, 26.6, 26.7, 26.9, 27.1, 27, 27, 27.1, 27.2, 27.3, 27.2, - 27.1, 27.1, 27.1, 27.1, 26.9, 26.7, 26.5, 26.2, 25.9, 25.5, 25.1, 24.9, 24.9, 24.9, - 24.9, 25, 25.3, 25.5, 25.8, 26.2, 26.4, 26.4, 26, 25.5, 25, 24.6, 24.3, 24.1, 24, - 23.9, 23.9, 23.9, 23.8, 23.8, 23.7, 23.7, 23.9, 24, 24.3, 24.6, 25.1, 25.6, 20.7, - 20.8, 20.9, 20.9, 21.1, 21.1, 21.2, 21.4, 21.6, 21.8, 22, 22, 22, 22.2, 22.2, 22.7, - 23.4, 23.9, 24.2, 24.8, 25.2, 25.4, 25.5, 25.6, 25.8, 26.1, 26.4, 26.7, 27, 27.3, - 27.5, 27.7, 27.9, 28.1, 28.3, 28.7, 29, 29.3, 29.6, 29.6, 29.6, 29.6, 29.6, 29.9, - 30.3, 30.8, 31.1, 31.3, 31.4, 31.3, 31.3, 31.3, 31.2, 30.9, 30.7, 30.3, 30, 29.8, - 29.5, 29.3, 29, 28.7, 28.4, 28.2, 28, 28, 27.9, 27.9, 27.8, 27.9, 27.9, 28, 28.1, - 28.3, 28.6, 29.1, 29.6, 30, 30.8, 31.9, 32.9, 33.4, 33.6, 34, 34.1, 34.2, 34.1, 34, - 33.8, 33.6, 33.4, 33.4, 33.3, 33.3, 33.5, 33.7, 33.9, 34.1, 34.4, 34.7, 34.9, 35.2, - 35.5, 35.7, 35.8, 35.7, 35.5, 35.4, 35.3, 35.2, 35.3, 35, 34.6, 34.3, 33.9, 33.3, - 32.6, 32, 31.5, 30.9, 30.7, 30.6, 30.6, 30.5, 30.6, 30.5, 30.5, 30.4, 29.9, 29.9, - 29.9, 29.6, 29.3, 29.2, 29, 28.7, 28.4, 28.1, 27.5, 26.7, 26.3, 26, 25.7, 25.6, 25.4, - 25.1, 24.8, 24.7, 24.7, 25.1, 25.8, 26.1, 26, 25.9, 25.7, 25.6, 25.5, 25.3, 25.2, - 25.1, 24.9, 24.8, 24.8, 25.1, 25.1, 25.2, 25.4, 25.5, 25.6, 25.6, 25.5, 25.6, 25.7, - 25.8, 25.9, 26.1, 26.3, 26.4, 26.5, 26.9, 27.3, 27.6, 27.7, 27.8, 27.9, 28, 27.9, - 27.7, 27.4, 27.1, 26.7, 26.4, 26.1, 25.7, 25.5, 25.2, 25, 25, 25, 25.1, 25.4, 25.7, - 26, 26.2, 26.5, 26.7, 26.8, 27, 27, 27, 27, 27, 27.1, 27.1, 27, 27, 26.9, 26.8, 26.8, - 26.7, 26.5, 26.3, 26, 25.7, 25.5, 25.2, 25, 25.1, 25.2, 25.3, 25.4, 25.7, 26.1, 26.4, - 26.6, 26.5, 26.3, 25.7, 25.2, 24.8, 24.5, 24.3, 24.2, 24.1, 24, 24, 23.9, 23.9, 23.9, - 23.9, 24, 24.2, 24.4, 24.8, 25.2, 25.6, 26, 20.9, 20.9, 21, 21.1, 21.1, 21.2, 21.3, - 21.6, 21.8, 21.8, 21.8, 21.9, 22, 22.4, 22.7, 23.2, 24, 24.6, 25, 25.5, 25.6, 25.7, - 25.8, 25.9, 26.1, 26.4, 26.7, 26.9, 27.2, 27.5, 27.7, 27.9, 28.2, 28.4, 28.8, 29.2, - 29.6, 30, 30.2, 30.1, 30, 30, 30.1, 30.4, 30.7, 31.1, 31.3, 31.3, 31.4, 31.4, 31.2, - 31.1, 30.8, 30.5, 30.1, 29.8, 29.5, 29.4, 29.2, 29.1, 28.9, 28.6, 28.3, 28.1, 28, 28, - 28, 28, 27.9, 27.9, 28.1, 28.2, 28.3, 28.6, 28.8, 29.2, 29.9, 30.4, 31.1, 32.3, 33.1, - 33.6, 33.8, 34.1, 34.2, 34.1, 34, 33.9, 33.8, 33.6, 33.5, 33.4, 33.4, 33.4, 33.8, - 34.2, 34.5, 34.8, 34.9, 34.8, 35.1, 35.3, 35.5, 35.8, 35.9, 35.7, 35.7, 35.6, 35.4, - 35.3, 35.3, 35.1, 34.6, 34.2, 33.6, 32.9, 32.3, 31.9, 31.4, 31, 30.7, 30.6, 30.8, - 30.7, 30.7, 30.6, 30.5, 30.4, 30.1, 30.2, 30.1, 29.7, 29.5, 29.2, 29, 28.7, 28.3, - 27.9, 27.4, 26.7, 26.2, 25.9, 25.7, 25.5, 25.4, 25.1, 24.9, 24.9, 25.2, 25.7, 26.1, - 26.3, 26.1, 25.9, 25.7, 25.7, 25.6, 25.5, 25.4, 25.3, 25.2, 25, 24.9, 25, 25.1, 25.3, - 25.6, 25.7, 25.8, 25.7, 25.6, 25.7, 25.8, 25.9, 26, 26.2, 26.5, 26.6, 26.9, 27.4, - 27.7, 27.8, 27.9, 27.8, 27.9, 27.9, 27.8, 27.5, 27.2, 26.8, 26.3, 25.9, 25.5, 25.2, - 25.1, 25, 25, 25, 25, 25.1, 25.4, 25.7, 26, 26.3, 26.5, 26.7, 26.8, 26.9, 26.9, 26.9, - 27, 27, 27, 27, 26.9, 26.9, 26.8, 26.8, 26.8, 26.7, 26.5, 26.2, 25.9, 25.7, 25.5, - 25.4, 25.4, 25.4, 25.6, 25.7, 25.8, 26, 26.4, 26.7, 26.8, 26.6, 26.3, 25.6, 25.1, - 24.7, 24.5, 24.4, 24.3, 24.2, 24.1, 24, 24, 24, 24.1, 24.2, 24.3, 24.6, 24.9, 25.4, - 25.8, 26.1, 26.4, 21.2, 21.2, 21.2, 21.3, 21.3, 21.4, 21.5, 21.7, 21.8, 21.7, 21.7, - 21.9, 22.1, 22.6, 23.1, 23.7, 24.4, 24.9, 25.5, 25.9, 26, 26, 26.1, 26.3, 26.6, 26.9, - 27, 27.1, 27.4, 27.7, 27.9, 28.2, 28.5, 28.9, 29.4, 29.8, 30.2, 30.5, 30.6, 30.5, - 30.5, 30.4, 30.5, 30.7, 31, 31.3, 31.4, 31.4, 31.4, 31.3, 31.1, 30.9, 30.5, 30.1, - 29.7, 29.4, 29.2, 29, 29.1, 29, 28.9, 28.6, 28.2, 28.1, 28, 28.1, 28.2, 28.2, 28.1, - 28.2, 28.5, 28.6, 28.7, 29.1, 29.2, 29.6, 30.2, 30.8, 31.6, 32.5, 33.3, 33.7, 33.9, - 34.2, 34.3, 34.2, 34.1, 33.9, 33.8, 33.6, 33.5, 33.5, 33.5, 33.7, 34.2, 35.2, 35.5, - 35.5, 35.4, 35.2, 35.2, 35.4, 35.5, 35.7, 35.8, 35.8, 35.8, 35.7, 35.5, 35.4, 35.2, - 34.9, 34.4, 34, 33.4, 32.8, 32.2, 31.8, 31.5, 31.1, 30.7, 30.7, 30.9, 30.9, 30.8, - 30.6, 30.6, 30.6, 30.4, 30.3, 30, 29.7, 29.5, 29.2, 28.9, 28.6, 28.1, 27.6, 27.1, - 26.5, 26, 25.8, 25.7, 25.4, 25.3, 25.2, 25.1, 25.3, 25.7, 26.3, 26.6, 26.5, 26.2, - 25.9, 25.8, 25.8, 25.7, 25.7, 25.6, 25.5, 25.4, 25.2, 25.1, 25.2, 25.2, 25.4, 25.7, - 25.9, 25.9, 25.8, 25.8, 25.9, 25.9, 26.1, 26.2, 26.4, 26.6, 26.8, 27.2, 27.6, 27.8, - 27.9, 27.9, 27.8, 27.8, 27.7, 27.6, 27.3, 26.9, 26.5, 26, 25.5, 25.1, 25, 24.9, 24.9, - 25, 25.1, 25.2, 25.2, 25.4, 25.7, 26.1, 26.4, 26.6, 26.8, 26.8, 26.8, 26.8, 26.9, - 26.9, 26.9, 26.9, 26.9, 27, 27, 27, 26.9, 26.9, 26.8, 26.6, 26.4, 26.1, 25.8, 25.7, - 25.7, 25.8, 26, 26.1, 26.3, 26.4, 26.5, 26.6, 26.8, 26.8, 26.6, 26.3, 25.4, 24.9, - 24.7, 24.5, 24.4, 24.4, 24.4, 24.3, 24.2, 24.2, 24.2, 24.3, 24.4, 24.6, 25, 25.4, - 25.8, 26.2, 26.5, 26.7, 21.3, 21.3, 21.4, 21.4, 21.5, 21.6, 21.7, 21.7, 21.8, 21.8, - 21.8, 22, 22.5, 23.2, 23.8, 24.2, 24.8, 25.3, 25.8, 26.1, 26.3, 26.5, 26.5, 26.6, - 26.9, 27, 27.2, 27.4, 27.7, 28, 28.3, 28.7, 29.1, 29.6, 30.1, 30.4, 30.7, 30.9, 31, - 31.1, 31, 30.9, 30.9, 31, 31.2, 31.4, 31.4, 31.3, 31.3, 31.2, 31, 30.5, 30.1, 29.7, - 29.5, 29.2, 28.9, 28.8, 28.9, 28.9, 28.8, 28.6, 28.3, 28.2, 28.2, 28.3, 28.4, 28.5, - 28.6, 28.8, 29, 29.1, 29.2, 29.4, 29.6, 30, 30.6, 31.3, 31.9, 32.7, 33.5, 33.7, 34, - 34.3, 34.4, 34.4, 34.1, 33.9, 33.8, 33.7, 33.7, 33.7, 33.7, 34.1, 35.1, 36.6, 36.6, - 36.3, 35.9, 35.5, 35.3, 35.3, 35.4, 35.6, 35.8, 35.9, 35.9, 35.8, 35.7, 35.5, 35.2, - 34.8, 34.3, 33.8, 33.1, 32.6, 32.1, 31.9, 31.6, 31.2, 30.9, 30.7, 30.8, 30.9, 30.8, - 30.7, 30.7, 30.6, 30.5, 30.3, 30, 29.7, 29.4, 29.1, 28.7, 28.3, 27.8, 27.2, 26.7, - 26.3, 26, 25.8, 25.8, 25.5, 25.4, 25.4, 25.4, 25.8, 26.4, 26.8, 26.9, 26.7, 26.3, - 26.1, 26, 25.9, 26, 25.9, 25.8, 25.6, 25.4, 25.3, 25.3, 25.3, 25.4, 25.5, 25.7, 25.9, - 25.9, 26, 26, 25.9, 26, 26.2, 26.5, 26.7, 26.9, 27.3, 27.5, 27.8, 27.9, 27.9, 27.9, - 27.8, 27.7, 27.5, 27.3, 27.1, 26.7, 26.2, 25.8, 25.4, 25.1, 25, 24.9, 24.9, 25.1, - 25.2, 25.3, 25.4, 25.5, 25.8, 26.2, 26.4, 26.7, 26.8, 26.7, 26.7, 26.8, 26.8, 26.8, - 26.9, 27, 27, 27.1, 27.2, 27.2, 27.1, 27.1, 27.1, 27.1, 26.9, 26.7, 26.3, 26.1, 26.2, - 26.5, 26.6, 26.7, 26.9, 26.9, 26.9, 26.9, 26.7, 26.6, 26.4, 26, 25.2, 24.8, 24.7, - 24.6, 24.5, 24.5, 24.4, 24.3, 24.3, 24.3, 24.4, 24.5, 24.7, 24.9, 25.4, 25.8, 26.2, - 26.6, 26.7, 26.8, 21.5, 21.7, 21.7, 21.6, 21.8, 21.9, 22, 21.9, 21.8, 21.9, 22.1, - 22.3, 23, 23.8, 24.3, 24.7, 25.3, 26.2, 26.3, 26.4, 26.7, 26.8, 26.8, 26.8, 27, 27.2, - 27.4, 27.7, 28.1, 28.6, 28.9, 29.3, 29.8, 30.3, 30.8, 31, 31.2, 31.4, 31.7, 31.7, - 31.6, 31.5, 31.3, 31.4, 31.4, 31.4, 31.3, 31.3, 31.3, 31.2, 30.8, 30.2, 29.8, 29.4, - 29.1, 28.8, 28.7, 28.7, 28.7, 28.8, 28.7, 28.5, 28.4, 28.3, 28.3, 28.6, 28.8, 28.9, - 29.2, 29.5, 29.6, 29.6, 29.7, 29.9, 30.1, 30.5, 31.1, 31.7, 32.2, 32.8, 33.4, 33.8, - 34.1, 34.4, 34.6, 34.4, 34.1, 34, 34, 34, 34, 34.1, 34.4, 34.9, 35.8, 37.1, 36.9, - 36.6, 36.2, 35.8, 35.6, 35.3, 35.3, 35.4, 35.8, 36, 36, 35.9, 35.7, 35.5, 35.1, 34.7, - 34.1, 33.3, 32.8, 32.4, 32.2, 32, 31.7, 31.4, 31, 30.8, 30.7, 30.8, 31, 31, 30.8, - 30.6, 30.4, 30.3, 29.9, 29.6, 29.2, 28.8, 28.5, 28, 27.4, 26.9, 26.5, 26.3, 26.2, - 25.9, 25.8, 25.7, 25.6, 25.7, 26, 26.4, 26.8, 27, 27, 26.7, 26.5, 26.3, 26.2, 26.2, - 26.2, 26.1, 26, 25.8, 25.6, 25.5, 25.4, 25.5, 25.5, 25.6, 25.7, 25.8, 26, 26, 26.1, - 26.1, 26.2, 26.4, 26.8, 27.1, 27.5, 27.7, 27.9, 28, 28, 28, 27.9, 27.7, 27.6, 27.4, - 27.1, 26.9, 26.6, 26.1, 25.7, 25.4, 25.1, 25, 25.1, 25.4, 25.6, 25.6, 25.7, 25.7, - 25.7, 25.9, 26.2, 26.4, 26.6, 26.6, 26.6, 26.7, 26.7, 26.7, 26.8, 27, 27.2, 27.3, - 27.4, 27.4, 27.4, 27.3, 27.4, 27.6, 27.7, 27.6, 27.3, 27, 26.8, 26.8, 26.9, 26.9, 27, - 27.2, 27.3, 27.2, 27, 26.8, 26.6, 26.1, 25.7, 25.2, 24.9, 24.8, 24.7, 24.6, 24.5, - 24.5, 24.4, 24.4, 24.5, 24.7, 24.8, 25, 25.3, 25.6, 26.1, 26.6, 26.8, 26.8, 26.8, - 21.9, 22, 22, 21.9, 22, 22.2, 22.2, 22.1, 22, 22.1, 22.3, 22.7, 23.5, 24.2, 24.6, - 25.2, 25.7, 26.2, 26.5, 26.7, 26.9, 27, 26.9, 27, 27.1, 27.4, 27.8, 28.2, 28.7, 29.2, - 29.7, 30.1, 30.6, 30.9, 31.4, 31.7, 31.8, 32, 32.2, 32.2, 32.1, 31.9, 31.7, 31.6, - 31.6, 31.4, 31.2, 31.1, 31.1, 30.9, 30.5, 29.7, 29.3, 29, 28.7, 28.6, 28.6, 28.5, - 28.5, 28.6, 28.5, 28.5, 28.5, 28.7, 28.8, 28.9, 29.2, 29.5, 29.8, 30.1, 30.3, 30.3, - 30.3, 30.4, 30.5, 31, 31.6, 32.2, 32.6, 33.1, 33.5, 33.9, 34.3, 34.6, 34.7, 34.6, - 34.4, 34.3, 34.3, 34.3, 34.3, 34.6, 35, 35.9, 36.5, 37.2, 37.2, 36.8, 36.4, 36.1, - 35.9, 35.6, 35.4, 35.6, 35.9, 36.2, 36.1, 35.9, 35.6, 35.4, 34.9, 34.4, 34, 33.3, - 32.7, 32.5, 32.3, 32, 31.7, 31.4, 31, 30.8, 30.8, 30.9, 31, 30.9, 30.5, 30.4, 30.3, - 30.1, 29.6, 29.2, 28.9, 28.4, 27.9, 27.4, 27.1, 26.8, 26.5, 26.2, 26.1, 26, 25.8, - 25.8, 26, 26.4, 26.7, 27, 27.2, 27.1, 26.9, 26.7, 26.5, 26.4, 26.4, 26.4, 26.4, 26.3, - 26.2, 26.1, 25.8, 25.6, 25.6, 25.6, 25.6, 25.6, 25.8, 26, 26.1, 26.2, 26.3, 26.5, - 26.6, 26.9, 27.2, 27.6, 28, 28.2, 28.2, 28.2, 28.2, 28, 27.8, 27.7, 27.5, 27.2, 26.9, - 26.6, 26.4, 25.9, 25.7, 25.4, 25.3, 25.4, 25.6, 25.9, 26, 26.1, 26.2, 26.2, 26, 26, - 26.2, 26.4, 26.5, 26.5, 26.6, 26.6, 26.6, 26.7, 26.8, 27.1, 27.4, 27.6, 27.6, 27.6, - 27.6, 27.6, 27.8, 28.1, 28.2, 28.2, 27.9, 27.6, 27.3, 27.2, 27.2, 27.2, 27.3, 27.3, - 27.2, 27.2, 27, 27, 27, 26.1, 25.6, 25.2, 25, 24.9, 24.8, 24.7, 24.7, 24.6, 24.6, - 24.7, 24.9, 25.2, 25.3, 25.4, 25.7, 26, 26.4, 26.7, 26.9, 27, 27.1, 21.9, 22.1, 22.1, - 22.1, 22.1, 22.4, 22.3, 22.2, 22.3, 22.4, 22.6, 23.1, 24, 24.6, 24.9, 25.3, 25.9, - 26.4, 26.7, 26.9, 27, 27.1, 27, 27.1, 27.3, 27.7, 28.4, 28.7, 29.3, 29.9, 30.3, 30.7, - 31.2, 31.4, 31.8, 32, 32.4, 32.6, 32.7, 32.6, 32.5, 32.3, 32.1, 31.8, 31.6, 31.3, - 31.1, 30.8, 30.7, 30.3, 29.8, 29.3, 28.9, 28.7, 28.6, 28.5, 28.5, 28.4, 28.4, 28.4, - 28.4, 28.6, 28.8, 29, 29.2, 29.2, 29.6, 30, 30.3, 30.5, 30.7, 30.8, 30.7, 30.8, 31, - 31.5, 32, 32.5, 33, 33.4, 33.8, 34.3, 34.7, 34.9, 34.9, 34.8, 34.7, 34.6, 34.7, 34.6, - 34.7, 35.1, 35.6, 36.3, 36.7, 37.2, 37.3, 37, 36.7, 36.4, 36.2, 35.9, 35.8, 36.1, - 36.2, 36.2, 36, 35.7, 35.5, 35, 34.6, 34.2, 33.8, 33.2, 32.7, 32.4, 32.2, 31.9, 31.6, - 31.3, 31, 30.8, 30.8, 30.8, 30.7, 30.4, 30.2, 30.2, 30.1, 29.8, 29.3, 28.9, 28.6, - 28.1, 27.6, 27.2, 26.9, 26.5, 26.3, 26.1, 26.1, 26, 26, 26.2, 26.5, 27, 27.3, 27.4, - 27.3, 27.1, 26.9, 26.7, 26.7, 26.6, 26.6, 26.7, 26.7, 26.7, 26.6, 26.4, 26, 25.8, - 25.8, 25.8, 25.9, 25.9, 26.1, 26.4, 26.6, 26.7, 27, 27.2, 27.4, 27.5, 27.8, 28.2, - 28.5, 28.6, 28.6, 28.5, 28.2, 28, 27.8, 27.6, 27.3, 27, 26.7, 26.4, 26, 25.8, 25.5, - 25.4, 25.4, 25.6, 26, 26.4, 26.6, 26.7, 26.7, 26.6, 26.3, 26.1, 26.2, 26.3, 26.4, - 26.5, 26.5, 26.6, 26.7, 26.8, 27, 27.3, 27.5, 27.6, 27.6, 27.6, 27.6, 27.8, 28.2, - 28.5, 28.6, 28.5, 28.4, 28.1, 27.8, 27.6, 27.6, 27.5, 27.4, 27.4, 27.2, 27.1, 26.8, - 26.9, 27, 26.1, 25.5, 25.2, 25.1, 24.9, 24.8, 24.8, 24.8, 24.7, 24.7, 24.9, 25.1, - 25.4, 25.6, 25.9, 26.1, 26.4, 26.7, 26.9, 27.1, 27.3, 27.3, 22, 22.1, 22.1, 22.1, - 22.1, 22.2, 22.2, 22.3, 22.5, 22.7, 23, 23.5, 24.1, 24.6, 25.2, 25.6, 26.2, 26.5, - 26.8, 26.9, 27.1, 27.1, 27.1, 27.2, 27.5, 28.1, 28.9, 29.3, 29.8, 30.3, 30.7, 31.1, - 31.4, 31.7, 32.1, 32.4, 32.8, 33.2, 33.2, 33, 32.7, 32.4, 32.1, 31.8, 31.5, 31.2, - 30.9, 30.6, 30.3, 29.9, 29.4, 28.9, 28.6, 28.5, 28.4, 28.4, 28.4, 28.4, 28.4, 28.4, - 28.5, 28.8, 29.1, 29.2, 29.4, 29.6, 30, 30.3, 30.6, 30.8, 31, 31.1, 31.2, 31.3, 31.6, - 32, 32.4, 32.9, 33.4, 34, 34.4, 34.7, 35, 35.2, 35.2, 35.1, 35, 35, 35, 35, 35.2, - 35.7, 36.1, 36.3, 36.8, 37.1, 37.2, 37, 36.8, 36.7, 36.5, 36.3, 36.2, 36.3, 36.2, - 36.1, 35.8, 35.5, 35.3, 34.7, 34.3, 34.1, 33.7, 33.1, 32.7, 32.5, 32.3, 31.8, 31.4, - 31.1, 30.8, 30.7, 30.6, 30.5, 30.3, 30.1, 30, 29.9, 29.7, 29.3, 28.8, 28.5, 28.2, - 27.6, 27.3, 27.1, 26.8, 26.4, 26.2, 26.3, 26.4, 26.3, 26.4, 26.8, 27.1, 27.4, 27.6, - 27.6, 27.4, 27.1, 26.9, 26.8, 26.8, 26.8, 26.9, 27, 27.1, 27.1, 27, 26.7, 26.4, 26.2, - 26.1, 26.1, 26.4, 26.6, 26.8, 27.1, 27.3, 27.6, 28, 28.3, 28.4, 28.4, 28.5, 28.8, - 28.9, 29, 28.9, 28.7, 28.5, 28.2, 27.9, 27.6, 27.1, 26.7, 26.3, 25.9, 25.8, 25.7, - 25.4, 25.4, 25.6, 25.9, 26.3, 26.8, 27.1, 27.1, 26.9, 26.7, 26.4, 26.3, 26.3, 26.3, - 26.4, 26.4, 26.5, 26.7, 26.9, 27, 27.2, 27.4, 27.5, 27.5, 27.5, 27.5, 27.6, 27.9, - 28.4, 28.7, 28.9, 28.9, 28.8, 28.4, 28.1, 27.9, 27.8, 27.7, 27.6, 27.4, 27.2, 27, - 26.7, 26.5, 26.3, 25.8, 25.4, 25.2, 25.1, 25, 25, 24.9, 24.9, 24.9, 25, 25.2, 25.4, - 25.7, 26.1, 26.4, 26.6, 26.8, 27, 27.2, 27.4, 27.5, 27.5, 22, 22, 22, 22, 22, 22.1, - 22.1, 22.3, 22.6, 22.9, 23.3, 23.8, 24.2, 24.6, 25.2, 25.8, 26.3, 26.6, 26.8, 26.9, - 27, 27.1, 27.2, 27.4, 27.8, 28.7, 29.4, 29.7, 30.2, 30.6, 30.9, 31.3, 31.7, 32, 32.5, - 32.9, 33.3, 33.5, 33.5, 33.2, 32.8, 32.4, 32.1, 31.8, 31.5, 31.2, 30.9, 30.5, 30.1, - 29.5, 29, 28.7, 28.5, 28.5, 28.4, 28.4, 28.4, 28.4, 28.5, 28.6, 28.7, 29, 29.3, 29.5, - 29.9, 30.1, 30.3, 30.6, 31, 31.2, 31.4, 31.4, 31.5, 31.6, 32.1, 32.5, 32.8, 33.3, - 33.9, 34.4, 34.8, 35.1, 35.4, 35.4, 35.4, 35.3, 35.3, 35.3, 35.4, 35.4, 35.7, 36.2, - 36.6, 36.8, 37, 37.2, 37.2, 37.1, 37.1, 37, 36.8, 36.5, 36.4, 36.4, 36.1, 35.9, 35.6, - 35.3, 35, 34.4, 34, 33.7, 33.4, 32.9, 32.6, 32.5, 32.2, 31.6, 31.1, 30.9, 30.6, 30.5, - 30.3, 30.1, 30.1, 29.8, 29.6, 29.6, 29.3, 28.9, 28.4, 28.1, 27.7, 27.4, 27.1, 26.9, - 26.8, 26.7, 26.6, 27.1, 27.4, 27.1, 27.3, 27.6, 27.7, 27.8, 27.9, 27.7, 27.4, 27.2, - 27, 27, 27, 27, 27.1, 27.3, 27.5, 27.5, 27.4, 27.2, 26.9, 26.7, 26.6, 26.5, 26.7, - 27.1, 27.5, 27.8, 28.2, 28.6, 29, 29.2, 29, 29.1, 29.2, 29.3, 29.3, 29.4, 29.2, 28.9, - 28.7, 28.4, 28.2, 27.7, 27, 26.4, 25.9, 25.7, 25.6, 25.6, 25.5, 25.6, 25.9, 26.3, - 26.7, 27.1, 27.2, 27.1, 26.9, 26.7, 26.5, 26.5, 26.4, 26.4, 26.4, 26.5, 26.7, 26.9, - 27.2, 27.4, 27.4, 27.5, 27.5, 27.4, 27.4, 27.5, 27.6, 28.1, 28.6, 28.9, 29.1, 29.2, - 29.1, 28.7, 28.4, 28.1, 27.8, 27.7, 27.6, 27.4, 27.1, 26.9, 26.6, 26.3, 26, 25.6, - 25.4, 25.2, 25.2, 25.2, 25.1, 25.1, 25.1, 25.1, 25.3, 25.6, 25.9, 26.2, 26.5, 26.8, - 27, 27.1, 27.3, 27.6, 27.7, 27.7, 27.7, 21.8, 21.8, 21.8, 22, 22.1, 22.1, 22.1, 22.3, - 22.7, 23.2, 23.6, 23.9, 24.3, 24.7, 25.3, 26, 26.4, 26.6, 26.8, 26.8, 26.9, 27.1, - 27.4, 27.7, 28.3, 29.1, 29.7, 30.1, 30.6, 31, 31.3, 31.6, 32, 32.5, 33, 33.4, 33.7, - 33.8, 33.6, 33.2, 32.8, 32.4, 32, 31.7, 31.4, 31.2, 30.8, 30.4, 29.9, 29.3, 28.8, - 28.6, 28.5, 28.5, 28.5, 28.5, 28.5, 28.5, 28.6, 28.8, 29, 29.3, 29.6, 29.9, 30.3, - 30.5, 30.7, 31.1, 31.5, 31.7, 31.7, 31.7, 32, 32.1, 32.5, 33, 33.3, 33.9, 34.5, 35, - 35.3, 35.5, 35.6, 35.7, 35.7, 35.7, 35.7, 35.8, 35.9, 35.9, 36.2, 36.5, 36.9, 37.1, - 37.3, 37.5, 37.5, 37.5, 37.4, 37.3, 37, 36.6, 36.3, 36.1, 36, 35.7, 35.1, 34.8, 34.4, - 34.1, 33.7, 33.4, 33.1, 32.7, 32.2, 32, 31.8, 31.2, 30.9, 30.5, 30.2, 30.1, 29.9, - 29.7, 29.7, 29.5, 29.3, 29.1, 28.9, 28.6, 28.3, 27.9, 27.5, 27.3, 27.2, 27, 26.9, - 26.9, 26.9, 27.2, 27.7, 28, 28, 28.1, 28.1, 28.1, 28.1, 27.9, 27.7, 27.5, 27.4, 27.2, - 27.1, 27.3, 27.4, 27.6, 27.8, 27.8, 27.8, 27.6, 27.4, 27.2, 27.1, 27.1, 27.3, 27.7, - 28.1, 28.5, 29, 29.4, 29.6, 29.6, 29.4, 29.5, 29.6, 29.6, 29.6, 29.7, 29.6, 29.2, 29, - 28.8, 28.4, 27.9, 27.1, 26.3, 25.9, 25.7, 25.7, 25.7, 25.7, 26, 26.4, 26.8, 27.1, - 27.3, 27.4, 27.2, 26.9, 26.8, 26.7, 26.7, 26.7, 26.7, 26.7, 26.7, 26.9, 27.1, 27.4, - 27.4, 27.4, 27.3, 27.4, 27.3, 27.4, 27.6, 27.8, 28.3, 28.8, 29.2, 29.3, 29.2, 29.1, - 28.8, 28.5, 28.2, 27.9, 27.6, 27.4, 27.2, 26.9, 26.8, 26.5, 26.2, 25.9, 25.7, 25.5, - 25.4, 25.3, 25.4, 25.3, 25.4, 25.4, 25.6, 25.8, 26.2, 26.5, 26.7, 26.9, 27.1, 27.2, - 27.4, 27.6, 27.8, 27.9, 27.9, 27.9, 21.4, 21.5, 21.6, 22, 22.2, 22.2, 22.3, 22.6, - 23.2, 23.6, 23.9, 24.1, 24.5, 25.1, 25.7, 26.2, 26.4, 26.6, 26.7, 26.9, 27.1, 27.2, - 27.6, 28.1, 28.9, 29.7, 30.2, 30.6, 31, 31.4, 31.6, 32, 32.5, 33, 33.5, 33.8, 34, - 33.9, 33.6, 33.2, 32.8, 32.3, 31.9, 31.5, 31.3, 31, 30.5, 30, 29.6, 29.2, 28.8, 28.6, - 28.6, 28.7, 28.5, 28.6, 28.7, 28.7, 28.9, 29.2, 29.5, 29.8, 30.1, 30.3, 30.6, 30.8, - 30.9, 31.3, 31.8, 32.1, 32.2, 32.5, 32.7, 32.9, 33.1, 33.4, 33.9, 34.5, 35.1, 35.5, - 35.7, 35.8, 35.9, 36, 36.1, 36.1, 36.3, 36.3, 36.4, 36.5, 36.6, 36.9, 37.1, 37.4, - 37.5, 37.7, 37.8, 37.8, 37.6, 37.4, 37, 36.5, 36.2, 36, 35.7, 35.1, 34.5, 34.2, 34.1, - 33.9, 33.5, 33.2, 32.8, 32.3, 31.9, 31.6, 31.3, 30.9, 30.6, 30.2, 29.9, 29.7, 29.5, - 29.4, 29.3, 29.2, 29, 28.8, 28.5, 28.3, 28.2, 27.8, 27.5, 27.4, 27.2, 27.1, 27.1, 27, - 27, 27.4, 27.9, 28.3, 28.5, 28.5, 28.4, 28.3, 28.3, 28.3, 28.1, 28, 27.8, 27.5, 27.3, - 27.5, 27.7, 27.9, 28.2, 28.2, 28.2, 28.1, 27.9, 27.7, 27.6, 27.8, 28, 28.4, 28.8, - 29.2, 29.6, 29.8, 29.9, 29.9, 29.8, 29.9, 30, 30, 30, 30, 29.8, 29.6, 29.4, 29.2, - 28.8, 28.1, 27.3, 26.5, 26.1, 26.1, 26.3, 26.2, 26.3, 26.8, 27.1, 27.2, 27.3, 27.4, - 27.5, 27.4, 27, 26.9, 26.8, 26.8, 26.9, 27, 27, 27, 27.1, 27.3, 27.5, 27.4, 27.3, - 27.3, 27.3, 27.3, 27.4, 27.7, 28, 28.5, 29, 29.3, 29.4, 29.3, 29.1, 28.8, 28.5, 28.2, - 27.9, 27.7, 27.3, 27, 26.8, 26.6, 26.4, 26.1, 25.9, 25.7, 25.6, 25.6, 25.6, 25.5, - 25.5, 25.6, 25.9, 26.2, 26.5, 26.8, 27.1, 27.2, 27.2, 27.5, 27.7, 28, 28, 28, 28, - 28.1, 28.1, 21.1, 21.4, 21.7, 21.9, 22.1, 22.2, 22.3, 22.7, 23.3, 23.9, 24.2, 24.4, - 24.9, 25.5, 26, 26.4, 26.6, 26.8, 27, 27.1, 27.4, 27.6, 28.1, 28.8, 29.5, 30.1, 30.5, - 30.9, 31.3, 31.7, 32.1, 32.5, 33, 33.4, 33.8, 34, 34.1, 33.9, 33.5, 33.2, 32.8, 32.3, - 31.7, 31.3, 31, 30.6, 30.1, 29.7, 29.3, 29, 28.7, 28.7, 28.7, 28.7, 28.7, 28.9, 29.1, - 29.3, 29.5, 29.8, 30, 30.2, 30.5, 30.6, 30.8, 31.1, 31.2, 31.5, 32, 32.4, 32.6, 33, - 33.3, 33.5, 33.6, 33.9, 34.4, 34.9, 35.4, 35.9, 36.2, 36.2, 36.2, 36.3, 36.4, 36.5, - 36.6, 36.7, 36.9, 37.2, 37.1, 37.3, 37.4, 37.6, 37.8, 37.9, 38, 38, 37.7, 37.3, 36.8, - 36.4, 36, 35.8, 35.3, 34.7, 34.4, 34.1, 33.9, 33.6, 33.2, 32.8, 32.4, 32.1, 31.8, - 31.4, 30.9, 30.6, 30.3, 29.9, 29.6, 29.4, 29.2, 29.1, 29, 29, 28.8, 28.6, 28.4, 28.1, - 28, 27.8, 27.6, 27.5, 27.4, 27.5, 27.4, 27.2, 27.4, 27.8, 28.3, 28.5, 28.8, 28.8, - 28.8, 28.7, 28.8, 28.7, 28.4, 28.2, 27.9, 27.7, 27.6, 27.8, 28.1, 28.3, 28.6, 28.6, - 28.6, 28.4, 28.2, 28.1, 28.2, 28.4, 28.8, 29.1, 29.4, 29.8, 30, 30.1, 30.2, 30.2, - 30.2, 30.2, 30.3, 30.3, 30.4, 30.3, 30.2, 30.1, 29.9, 29.5, 29, 28.4, 27.7, 27, 26.8, - 27, 27.1, 27, 27.1, 27.5, 27.7, 27.6, 27.5, 27.4, 27.3, 27.1, 27, 27, 26.9, 27, 27.2, - 27.2, 27.2, 27.3, 27.4, 27.6, 27.6, 27.4, 27.4, 27.3, 27.3, 27.4, 27.5, 27.8, 28.2, - 28.8, 29.2, 29.4, 29.5, 29.3, 29.1, 28.9, 28.5, 28.3, 28, 27.6, 27.2, 26.9, 26.7, - 26.5, 26.3, 26.1, 26, 25.8, 25.8, 25.8, 25.8, 25.7, 25.8, 26.1, 26.4, 26.8, 27.1, - 27.3, 27.5, 27.6, 27.8, 27.9, 28.1, 28.2, 28.2, 28.3, 28.2, 28.2, 28.3, 21, 21.3, - 21.6, 21.9, 22.1, 22.2, 22.4, 22.7, 23.4, 24, 24.3, 24.6, 25, 25.7, 26.3, 26.8, 27.3, - 27.4, 27.5, 27.8, 28.1, 28.5, 28.9, 29.5, 30.1, 30.5, 30.9, 31.3, 31.7, 32.1, 32.5, - 33.1, 33.5, 33.9, 34.2, 34.2, 34.1, 33.8, 33.4, 32.9, 32.6, 32.1, 31.5, 31, 30.6, - 30.2, 29.9, 29.5, 29, 28.7, 28.7, 28.6, 28.6, 28.6, 28.9, 29.2, 29.5, 29.8, 29.9, - 30.1, 30.2, 30.5, 30.7, 30.9, 31.2, 31.3, 31.6, 31.9, 32.5, 32.8, 32.9, 33.3, 33.6, - 33.8, 34.1, 34.4, 34.9, 35.3, 35.8, 36.3, 36.6, 36.7, 36.6, 36.5, 36.6, 36.7, 36.9, - 37.1, 37.3, 37.6, 37.5, 37.6, 37.7, 37.9, 38, 38.1, 38.2, 38.2, 37.8, 37.3, 36.8, - 36.5, 36, 35.6, 35.1, 34.6, 34.6, 34.1, 33.6, 33.4, 33, 32.6, 32.3, 32.1, 31.7, 31.2, - 30.7, 30.3, 30, 29.6, 29.3, 29.2, 29.2, 29.1, 28.9, 28.7, 28.6, 28.5, 28.4, 28.3, - 28.2, 27.8, 27.6, 27.6, 27.6, 27.7, 27.7, 27.6, 27.9, 28.4, 28.9, 29, 29.2, 29.3, - 29.4, 29.3, 29.2, 29, 28.7, 28.4, 28.1, 27.9, 28, 28.2, 28.5, 28.7, 28.8, 28.9, 28.8, - 28.7, 28.6, 28.6, 28.7, 29, 29.2, 29.5, 29.8, 30.1, 30.3, 30.5, 30.6, 30.6, 30.6, - 30.5, 30.5, 30.6, 30.7, 30.8, 30.6, 30.4, 30.1, 29.7, 29.2, 28.8, 28.3, 28, 28.1, - 28.1, 28, 27.9, 27.8, 27.8, 27.7, 27.7, 27.6, 27.4, 27.2, 27, 27, 27, 27, 27.3, 27.3, - 27.3, 27.4, 27.5, 27.6, 27.8, 27.7, 27.5, 27.4, 27.4, 27.4, 27.6, 27.8, 28.2, 28.7, - 29.1, 29.4, 29.6, 29.5, 29.4, 29.1, 28.8, 28.4, 28.1, 27.8, 27.4, 27, 26.7, 26.5, - 26.4, 26.3, 26.2, 26.1, 26.1, 26.1, 26.1, 26.1, 26.1, 26.3, 26.7, 27, 27.3, 27.5, - 27.8, 28, 28.1, 28.2, 28.3, 28.4, 28.4, 28.5, 28.6, 28.6, 28.6, 28.6, 20.9, 21.2, - 21.5, 21.9, 22.2, 22.5, 22.8, 23.3, 23.9, 24.3, 24.5, 24.9, 25.5, 26.3, 26.9, 27.3, - 27.8, 28.1, 28.3, 28.6, 28.9, 29.3, 29.7, 30.1, 30.6, 31, 31.3, 31.8, 32.1, 32.5, 33, - 33.6, 34, 34.2, 34.3, 34.3, 34, 33.6, 33.2, 32.8, 32.3, 31.8, 31.2, 30.7, 30.2, 29.8, - 29.5, 29.1, 28.7, 28.7, 28.8, 28.8, 28.8, 28.9, 29.2, 29.5, 29.8, 30, 30.2, 30.3, - 30.4, 30.6, 30.8, 31.1, 31.5, 31.7, 32.1, 32.5, 32.9, 33.2, 33.3, 33.7, 33.9, 34.2, - 34.6, 35, 35.3, 35.8, 36.3, 36.6, 36.8, 36.9, 36.9, 36.8, 36.9, 37, 37.2, 37.4, 37.5, - 37.7, 37.8, 37.8, 38, 38.1, 38.3, 38.4, 38.4, 38.4, 38, 37.5, 36.9, 36.5, 35.9, 35.4, - 34.9, 34.4, 34.2, 33.9, 33.4, 33.3, 33.1, 32.7, 32.3, 31.9, 31.4, 30.9, 30.4, 30.1, - 29.7, 29.4, 29.1, 29, 29, 29, 28.8, 28.6, 28.4, 28.4, 28.4, 28.3, 28.1, 27.8, 27.7, - 27.8, 27.8, 27.8, 27.9, 28.1, 28.4, 28.8, 29.2, 29.5, 29.6, 29.6, 29.8, 29.7, 29.5, - 29.2, 28.9, 28.6, 28.3, 28.2, 28.2, 28.3, 28.6, 28.9, 29, 29.1, 29.1, 29, 29, 29, - 29.2, 29.3, 29.4, 29.7, 30, 30.3, 30.6, 30.8, 31, 31, 31, 30.9, 30.9, 31, 31, 30.9, - 30.8, 30.6, 30.2, 29.8, 29.5, 29.4, 29.2, 29, 29, 29, 28.7, 28.5, 28.3, 28.1, 28, - 27.7, 27.6, 27.4, 27.3, 27.2, 27.1, 27.1, 27.2, 27.4, 27.4, 27.3, 27.5, 27.7, 27.8, - 27.7, 27.6, 27.6, 27.6, 27.6, 27.8, 28, 28.3, 28.7, 29.2, 29.4, 29.7, 29.8, 29.6, - 29.3, 28.9, 28.6, 28.2, 27.8, 27.5, 27.2, 26.9, 26.7, 26.6, 26.5, 26.4, 26.3, 26.4, - 26.3, 26.3, 26.3, 26.4, 26.5, 26.9, 27.2, 27.4, 27.8, 28.1, 28.4, 28.5, 28.5, 28.5, - 28.5, 28.6, 28.7, 28.8, 29, 29, 28.9, 29 - ] + "raw16": "heightmap.r16", + "min": -2.6, + "max": 6550.9 } } }, @@ -4456,7 +44,12 @@ "opacity": 1, "id": "loose-talus", "type": "erode", - "params": { "method": "thermal", "talus": 38, "iterations": 1, "rate": 0.004 } + "params": { + "method": "thermal", + "talus": 38, + "iterations": 1, + "rate": 0.004 + } }, { "enabled": true, @@ -4467,10 +60,24 @@ "rules": [ { "material": "dirt", - "mask": { "type": "noise", "scale": 78, "threshold": 0.52, "seed": 73, "fade": 0.32 }, + "mask": { + "type": "noise", + "scale": 78, + "threshold": 0.52, + "seed": 73, + "fade": 0.32 + }, "strength": 0.5 }, - { "material": "rock", "mask": { "type": "slope", "min": 34, "max": 90, "fade": 9 } } + { + "material": "rock", + "mask": { + "type": "slope", + "min": 34, + "max": 90, + "fade": 9 + } + } ] } }, @@ -4480,7 +87,10 @@ "id": "lake-bed", "type": "flatten", "params": { - "at": [-70, 25], + "at": [ + -70, + 25 + ], "radius": 105, "height": 11.4, "falloff": 0.1 @@ -4500,11 +110,31 @@ "params": { "followTerrain": true, "points": [ - [-120, null, -220], - [-110, null, -175], - [-120, null, -130], - [-100, null, -95], - [-70, null, -60] + [ + -120, + null, + -220 + ], + [ + -110, + null, + -175 + ], + [ + -120, + null, + -130 + ], + [ + -100, + null, + -95 + ], + [ + -70, + null, + -60 + ] ], "width": 7, "depth": 1.8, @@ -4519,7 +149,10 @@ "type": "water", "params": { "kind": "lake", - "at": [-70, 25], + "at": [ + -70, + 25 + ], "radius": 105, "level": 13.3 } @@ -4535,9 +168,18 @@ "minDistance": 3.8, "maxSlope": 32, "avoidWater": true, - "scale": [0.6, 1.45] + "scale": [ + 0.6, + 1.45 + ] }, - "mask": { "type": "noise", "scale": 140, "threshold": 0.42, "seed": 11, "fade": 0.22 } + "mask": { + "type": "noise", + "scale": 140, + "threshold": 0.42, + "seed": 11, + "fade": 0.22 + } }, { "id": "firs-c", @@ -4548,9 +190,18 @@ "minDistance": 4.2, "maxSlope": 32, "avoidWater": true, - "scale": [0.7, 1.3] + "scale": [ + 0.7, + 1.3 + ] }, - "mask": { "type": "noise", "scale": 110, "threshold": 0.46, "seed": 23, "fade": 0.25 } + "mask": { + "type": "noise", + "scale": 110, + "threshold": 0.46, + "seed": 23, + "fade": 0.25 + } }, { "id": "firs-young", @@ -4561,9 +212,18 @@ "minDistance": 3, "maxSlope": 32, "avoidWater": true, - "scale": [0.3, 0.62] + "scale": [ + 0.3, + 0.62 + ] }, - "mask": { "type": "noise", "scale": 90, "threshold": 0.5, "seed": 47, "fade": 0.3 } + "mask": { + "type": "noise", + "scale": 90, + "threshold": 0.5, + "seed": 47, + "fade": 0.3 + } }, { "enabled": true, @@ -4577,9 +237,18 @@ "minSlope": 4, "maxSlope": 60, "avoidWater": true, - "scale": [0.5, 2.2] + "scale": [ + 0.5, + 2.2 + ] }, - "mask": { "type": "noise", "scale": 60, "threshold": 0.62, "seed": 29, "fade": 0.12 } + "mask": { + "type": "noise", + "scale": 60, + "threshold": 0.62, + "seed": 29, + "fade": 0.12 + } } ] } diff --git a/packages/terrain/starter/forest/sky.ts b/packages/terrain/starter/forest/sky.ts index 36b6fa5439..21e6e57258 100644 --- a/packages/terrain/starter/forest/sky.ts +++ b/packages/terrain/starter/forest/sky.ts @@ -2,6 +2,8 @@ // Edit any number here to change the look; nothing in a package decides it. import { Daylight } from "@threenative/core"; import { ACESFilmicToneMapping, Color, type Object3D, Vector3 } from "three"; +import { color, mix, normalize, positionLocal, smoothstep, vec3 } from "three/tsl"; +import type { Node } from "three/webgpu"; import { SKY_REFLECTION_LAYER } from "./water.js"; export function forestDaylight(follow: Object3D): Daylight { @@ -21,6 +23,15 @@ export function forestDaylight(follow: Object3D): Daylight { // The box's corners must stay inside the camera's far plane (5000 m in the kit's example scene). skySize: 5000, }); + // Compress the bright lower dome without dimming the blue sky the lake reflects. + const elevation = normalize(positionLocal).y; + daylight.sky.material.colorNode = mix( + color(daylight.haze.color).mul(mix(2.4, 3.2, smoothstep(-0.6, 0, elevation))), + vec3(daylight.sky.material.colorNode as Node<"vec4">).mul( + mix(0.3, 1, smoothstep(-0.05, 0.45, elevation)), + ), + smoothstep(-0.08, 0.08, elevation), + ); // The lake mirrors the sky: its mirror draws only the layer the sky sits on (see water.ts). daylight.sky.layers.enable(SKY_REFLECTION_LAYER); return daylight; diff --git a/packages/terrain/starter/forest/water.ts b/packages/terrain/starter/forest/water.ts index 39e7eab871..d76e9c8f63 100644 --- a/packages/terrain/starter/forest/water.ts +++ b/packages/terrain/starter/forest/water.ts @@ -18,6 +18,7 @@ import { MeshStandardMaterial, } from "three"; import { + attribute, cameraPosition, clamp, dot, @@ -61,19 +62,17 @@ export const SKY_REFLECTION_LAYER = 3; /** The ribbon sits this far above its profile, clear of the carved bed. */ const RIVER_LIFT = 0.05; /** Metres of water over the bed at which the lake is fully opaque; thinner water fades into the shore. */ -const SHORE_DEPTH = 0.3; +const SHORE_DEPTH = 1.8; /** * The colour the mirrored sky takes at grazing angles, in linear light. The sky's horizon is near-neutral, - * so the tint carries the lake's blue; the mirror keeps the sky's gradient. + * so the tint carries the lake's teal; the mirror keeps the sky's gradient. */ -const SHEEN = new Color(0x8fa2b1); +const SHEEN = new Color(0x94b9bf); /** Deep water's own colour; the bed shows through where the lake is shallow. */ -const BODY = new Color(0x1c3d3f); +const BODY = new Color(0x285b59); const RIVER = new Color(0x2a4b4c); -/** The pale, silty band where the water thins over the bank; it reads as a wet shore, not a cut edge. */ -const SHORE = new Color(0xb8c2b2); -/** Water thinner than this many metres takes the SHORE colour, fading in as it thins. */ -const SHORE_BAND = 2; +/** Shallow water stays teal, gently darkening with the measured depth of the bed. */ +const SHALLOW = new Color(0x3c746e); /** The heightfield's sample grid in world metres: one node per column and row, the first at (x0, z0). */ function gridOf(field: Heightfield): { stepX: number; stepZ: number; x0: number; z0: number } { @@ -133,16 +132,16 @@ function lakeGeometry(field: Heightfield, lake: ILake, flooded: Uint8Array): Buf const { stepX, stepZ, x0, z0 } = gridOf(field); const positions: number[] = []; const indices: number[] = []; + const depths: number[] = []; const vertexOf = new Map(); const vertex = (node: number): number => { const known = vertexOf.get(node); if (known !== undefined) return known; const index = positions.length / 3; - positions.push( - x0 + (node % columns) * stepX, - lake.level, - z0 + Math.floor(node / columns) * stepZ, - ); + const x = x0 + (node % columns) * stepX; + const z = z0 + Math.floor(node / columns) * stepZ; + positions.push(x, lake.level, z); + depths.push(lake.level - field.heightAt(x, z)); vertexOf.set(node, index); return index; }; @@ -161,14 +160,15 @@ function lakeGeometry(field: Heightfield, lake: ILake, flooded: Uint8Array): Buf } const geometry = new BufferGeometry(); geometry.setAttribute("position", new BufferAttribute(new Float32Array(positions), 3)); + geometry.setAttribute("waterDepth", new BufferAttribute(new Float32Array(depths), 1)); geometry.setIndex(indices); return geometry; } /** * The lake's surface: the sky mirrored in it at grazing angles, the bed seen through it where it is - * shallow, and the body's own colour where it is deep. Schlick at water's 1.333 index, so the water at - * your feet is clear and the far bank is a mirror. Where the water is thinner than SHORE_DEPTH it fades + * shallow, and the body's own colour where it is deep. Capped Schlick at water's 1.333 index keeps + * the far bank's reflection subdued. Where the water is thinner than SHORE_DEPTH it fades * to nothing, because the ground has risen through the level there. */ function lakeMaterial(surface: WaterSurface3D): MeshBasicNodeMaterial { @@ -176,16 +176,26 @@ function lakeMaterial(surface: WaterSurface3D): MeshBasicNodeMaterial { const facing = clamp(dot(vec3(0, 1, 0), view), float(0), float(1)); const fresnel = float(0.02).add(pow(float(1).sub(facing), 5).mul(0.98)); const thickness = surface.thicknessAt(); - const body = vec3(BODY.r, BODY.g, BODY.b); + // Bed depth shades the shore consistently; screen depth still preserves the bed and its shadows. + const bedDepth = attribute<"float">("waterDepth", "float"); + const body = mix( + vec3(SHALLOW.r, SHALLOW.g, SHALLOW.b), + vec3(BODY.r, BODY.g, BODY.b), + smoothstep(float(0), float(3), bedDepth), + ); const submerged = mix(surface.refractionAt(), body, smoothstep(float(0.4), float(3), thickness)); const material = new MeshBasicNodeMaterial({ transparent: true, side: DoubleSide }); // The HDR mirror is compressed, then tinted: its horizon is neutral, the water should not be. const mirror = surface.reflectionAt(); const compressed = mirror.div(mirror.add(vec3(1, 1, 1))); - const open = mix(submerged, compressed.mul(vec3(SHEEN.r, SHEEN.g, SHEEN.b)), fresnel); - const edge = float(1).sub(smoothstep(float(0.05), float(SHORE_BAND), thickness)); - material.colorNode = mix(open, vec3(SHORE.r, SHORE.g, SHORE.b), edge.mul(0.16)); - material.opacityNode = smoothstep(float(0), float(SHORE_DEPTH), thickness); + // A small sky floor reveals the mirror; the cap keeps grazing water from becoming an ice sheet. + const reflection = clamp(float(0.16).add(fresnel.mul(0.3)), float(0), float(0.46)); + const open = mix(submerged, compressed.mul(vec3(SHEEN.r, SHEEN.g, SHEEN.b)), reflection); + // Cap linear radiance before the scene tone curve, including the glancing shoreline. + material.colorNode = clamp(open, vec3(0, 0, 0), vec3(0.22, 0.22, 0.22)); + material.opacityNode = smoothstep(float(0), float(SHORE_DEPTH), bedDepth).mul( + smoothstep(float(0), float(0.3), thickness), + ); return material; } diff --git a/packages/terrain/starter/forest/world.ts b/packages/terrain/starter/forest/world.ts index 01c1f9871a..d61a0f0bd5 100644 --- a/packages/terrain/starter/forest/world.ts +++ b/packages/terrain/starter/forest/world.ts @@ -1,33 +1,24 @@ -// The forest starter world, as the game that copied it draws and collides with it. -// -// `bake.mjs` wrote a world package (heightmap, splat masks, placements, CC0 models) into `world/`; -// the engine's `WorldCells` streams it, `loadTerrainSplat` textures its ground, and everything -// that decides how the world looks or what stops the player is in this folder for the game to edit. -import type { IComputeDriven, ICtx } from "@threenative/core"; +import type { ICtx } from "@threenative/core"; +import { type IWorldPackage, loadTerrainSplat } from "@threenative/core/world"; +import type { IPhysicsContext } from "@threenative/physics"; import { - Heightfield, - type IWorldPackage, - WorldCells, - heightSamplerFromHeightmap, - loadTerrainSplat, - loadWorldHeightmap, -} from "@threenative/core/world"; -import { CollisionShape3D, type IPhysicsContext, RigidBody3D } from "@threenative/physics"; + type IStarterWorld, + type IStarterWorldSpec, + PropColliders as StarterPropColliders, + addStarterWorld, +} from "@threenative/terrain/starter"; import { type Box3, type BufferGeometry, Color, EquirectangularReflectionMapping, - type Material, type Mesh, type MeshStandardMaterial, - Object3D, + type Object3D, type Texture, Vector3, } from "three"; -import { HDRLoader } from "three/addons/loaders/HDRLoader.js"; import { type IForestWater, type IForestWaterFile, addForestWater } from "./water.js"; - /** Where the game's asset source holds the baked `world/` folder. */ export const FOREST_URL = "terrain/forest/world.json"; @@ -51,175 +42,58 @@ export const PROP_SKY_LIGHT = 0.65; /** Multiplies every boulder's albedo: the CC0 scan photographed pink-orange, the forest's stone is cool grey. */ export const BOULDER_TINT = new Color(0.6, 0.74, 0.86); -export interface IForestWorld { - readonly world: WorldCells; - /** One heightfield for the whole world, in place before `addForest` resolves. */ - readonly ground: RigidBody3D; - /** The prop colliders near `follow`; they stream with it (see `PROP_COLLIDER_REACH`). */ - readonly colliders: PropColliders; - /** The lake and river the bake wrote, drawn beside the ground (see `water.ts`). */ - readonly water: IForestWater; -} - /** Metres around `follow` within which props collide; beyond it they only draw. */ export const PROP_COLLIDER_REACH = 60; -/** - * Stream the forest around `follow` (usually the player or the camera) and give it collision. - * `whileCurrent` prevents a departed scene's async load from attaching anything; its resources - * are released on cancellation, failure and when the game's entity registry ends this scene. - */ +export interface IForestWorld extends IStarterWorld { + readonly water: IForestWater; +} +const spec: IStarterWorldSpec = { + name: "Forest", + url: FOREST_URL, + colliders: COLLIDERS, + colliderReach: PROP_COLLIDER_REACH, + loadSurface: (ctx, url) => loadTerrainSplat({ assets: ctx.assets, url }), + lighting: { sky: "sky.hdr", lods: true, dress: dressProp }, + streaming: { + ring: 5, + impostors: true, + gpuScene: false, + shadows: { cast: true, receive: true }, + budgets: { residentCells: 64, instances: 40_000, bytes: 64_000_000 }, + terrain: { streamRadius: 6 }, + }, + water: (ctx, file, field) => addForestWater(ctx, file as IForestWaterFile, field), +}; +export class PropColliders extends StarterPropColliders { + constructor( + ctx: ICtx, IPhysicsContext>, + follow: Object3D, + manifest: IWorldPackage, + records: Float32Array, + ) { + super(ctx, follow, manifest, records, COLLIDERS, PROP_COLLIDER_REACH, "Forest"); + } +} export async function addForest( ctx: ICtx, IPhysicsContext>, follow: Object3D, url = FOREST_URL, whileCurrent: () => boolean = () => true, ): Promise { - const assertCurrent = () => { - if (whileCurrent()) return; - const error = new Error("Forest world: its scene is no longer current."); - error.name = "AbortError"; - throw error; - }; - let surface: Material | undefined; - let releaseLighting: (() => void) | undefined; - let world: WorldCells | undefined; - let ground: RigidBody3D | undefined; - let colliders: PropColliders | undefined; - let water: IForestWater | undefined; - let released = false; - const dispose = () => { - if (released) return; - released = true; - colliders?.detach(); - colliders?.removeFromParent(); - water?.dispose(); - ground?.dispose(); - ground?.object?.removeFromParent(); - world?.dispose(); - surface?.dispose(); - releaseLighting?.(); - }; - try { - assertCurrent(); - const manifest = (await (await fetchAsset(ctx, url, assertCurrent)).json()) as IWorldPackage; - assertCurrent(); - const base = url.slice(0, url.lastIndexOf("/") + 1); - const records = await ( - await fetchAsset(ctx, base + manifest.placements, assertCurrent) - ).arrayBuffer(); - assertCurrent(); - const [heightmapUrl] = await ctx.assets.resolve(base + manifest.terrain.heightmap); - assertCurrent(); - if (heightmapUrl === undefined) throw new Error("Forest world: its heightmap is not served."); - const heightmap = await loadWorldHeightmap(heightmapUrl); - assertCurrent(); - surface = await loadTerrainSplat({ assets: ctx.assets, url }); - assertCurrent(); - releaseLighting = await lightProps(ctx, url, manifest, assertCurrent); - assertCurrent(); - world = await WorldCells.load({ - url, - assets: ctx.assets, - data: { manifest, placements: records, heightmap }, - surface, - follow, - ring: 5, - // Far firs draw as engine-baked impostors; the authored model stays the shadow caster. - impostors: true, - // The GPU-culled path drops the near firs from the main pass while their shadows still draw - // (measured in a fresh game, 2026-10-05); the CPU path draws them. Engine finding, PRD-466. - gpuScene: false, - // Trees and rocks shade the ground and each other; impostors keep the authored model as the caster. - shadows: { cast: true, receive: true }, - budgets: { residentCells: 64, instances: 40_000, bytes: 64_000_000 }, - terrain: { streamRadius: 6 }, - }); - assertCurrent(); - const field = forestField(manifest, heightmap); - ground = groundCollider(ctx, manifest, field); - const waterFile = (await ( - await fetchAsset(ctx, `${base}water.json`, assertCurrent) - ).json()) as IForestWaterFile; - assertCurrent(); - const forestWater = addForestWater(ctx, waterFile, field); - water = forestWater; - colliders = new PropColliders(ctx, follow, manifest, new Float32Array(records)); - ctx.add(world); - if (ground.object !== undefined) ctx.add(ground.object); - ctx.add(colliders); - ctx.entities.add(`forest-world.${world.uuid}`, { dispose }); - return { world, ground, colliders, water: forestWater }; - } catch (error) { - dispose(); - throw error; - } + const result = await addStarterWorld(ctx, follow, spec, url, whileCurrent); + if (!result.extra) throw new Error("Forest world: missing water."); + return { ...result, water: result.extra }; } - -// Cutout foliage with no environment draws flat and dark (the engine says TN_UNLIT_FOLIAGE): give -// every prop the sky as its own envMap before WorldCells adopts the shared cached models. Foliage -// cards also get normals bent out from the crown's axis, so a crown shades as one soft volume -// instead of a stack of flat cards, and lose the glossy specular glTF exporters give them. -async function lightProps( - ctx: ICtx, IPhysicsContext>, - url: string, - manifest: IWorldPackage, - assertCurrent: () => void, -): Promise<() => void> { - const base = url.slice(0, url.lastIndexOf("/") + 1); - const [skyUrl] = await ctx.assets.resolve(`${base}sky.hdr`); - assertCurrent(); - if (skyUrl === undefined) throw new Error("Forest world: 'sky.hdr' is not served."); - const sky = await new HDRLoader().loadAsync(skyUrl); - const originals = new Map< - MeshStandardMaterial, - { color: Color; envMap: Texture | null; intensity: number } - >(); - const dispose = () => { - for (const [material, original] of originals) { - if (material.envMap !== sky) continue; - material.envMap = original.envMap; - material.envMapIntensity = original.intensity; - material.color.copy(original.color); - } - sky.dispose(); - }; - try { - assertCurrent(); - sky.mapping = EquirectangularReflectionMapping; - // Every LOD of an asset, not only its nearest: a far boulder left undressed shows its raw scan. - const models = Object.entries(manifest.assets).flatMap(([id, asset]) => - [asset.glb, ...(asset.lods ?? []).map((lod) => lod.glb)].map((glb) => ({ id, glb })), - ); - for (const { id, glb } of models) { - const model = await ctx.assets.model<{ scene: Object3D }>(base + glb); - assertCurrent(); - model.scene.traverse((object) => { - const mesh = object as Mesh; - for (const material of [mesh.material ?? []].flat() as MeshStandardMaterial[]) - if (!originals.has(material)) - originals.set(material, { - color: material.color.clone(), - envMap: material.envMap, - intensity: material.envMapIntensity, - }); - dressProp(mesh, sky, id.startsWith("boulder") ? BOULDER_TINT : undefined, originals); - }); - } - return dispose; - } catch (error) { - dispose(); - throw error; - } -} - /** One prop mesh: the sky as its envMap, and matte, crown-shaded foliage. */ function dressProp( mesh: Mesh, sky: Texture, - tint: Color | undefined, + id: string, originals: ReadonlyMap, ): void { + sky.mapping = EquirectangularReflectionMapping; + const tint = id.startsWith("boulder") ? BOULDER_TINT : undefined; const surfaces = [mesh.material ?? []].flat() as MeshStandardMaterial[]; const foliage = surfaces.some((material) => material.alphaTest > 0); for (const material of surfaces) { @@ -256,158 +130,3 @@ function bendCrownNormals(geometry: BufferGeometry, bend: number): void { } normal.needsUpdate = true; } - -// The streamed terrain tiles arrive over several frames; a player spawned before its tile would fall -// through. One heightfield for the whole package (a few hundred kilobytes of samples) avoids that. -/** The kit's one heightfield: the collider stands on it and the water's shore reads it. */ -function forestField(manifest: IWorldPackage, heightmap: Uint16Array): Heightfield { - const { extent, terrain } = manifest; - return Heightfield.fromSampler({ - columns: terrain.columns, - rows: terrain.rows, - width: extent.sizeX, - depth: extent.sizeZ, - origin: { x: extent.minX + extent.sizeX / 2, z: extent.minZ + extent.sizeZ / 2 }, - sampleHeight: heightSamplerFromHeightmap(terrain, extent, heightmap), - }); -} - -function groundCollider( - ctx: ICtx, IPhysicsContext>, - manifest: IWorldPackage, - field: Heightfield, -): RigidBody3D { - const { extent } = manifest; - const anchor = new Object3D(); - anchor.position.set(extent.minX + extent.sizeX / 2, 0, extent.minZ + extent.sizeZ / 2); - return new RigidBody3D({ - object: anchor, - physics: ctx.physics, - type: "fixed", - entity: "terrain", - shape: CollisionShape3D.heightfield(field.rows, field.columns, field.toColliderHeights(), { - x: extent.sizeX, - y: 1, - z: extent.sizeZ, - }), - }); -} - -/** - * Fixed bodies for the props within `PROP_COLLIDER_REACH` of `follow`, rebuilt when it has moved a - * few metres. Thousands of resident bodies cost Rapier ~0.5 s per 8 s (measured); a hundred do not. - */ -export class PropColliders extends Object3D implements IComputeDriven { - readonly warmupNodes: readonly unknown[] = []; - readonly processCadence = "render" as const; - readonly #live = new Map(); - readonly #runs: { asset: string; offset: number; count: number }[] = []; - readonly #at = { x: Number.NaN, z: Number.NaN }; - readonly #followPosition = new Vector3(); - #released = false; - - constructor( - private readonly ctx: ICtx, IPhysicsContext>, - private readonly follow: Object3D, - manifest: IWorldPackage, - private readonly records: Float32Array, - ) { - super(); - this.name = "forest-prop-colliders"; - for (const cell of manifest.cells) - for (const run of cell.runs) if (COLLIDERS[run.asset]) this.#runs.push(run); - try { - this.#rebuild(); - } catch (error) { - this.detach(); - throw error; - } - } - - /** How many prop bodies exist right now. */ - get active(): number { - return this.#live.size; - } - - get released(): boolean { - return this.#released; - } - - attachRenderer(): void {} - - process(): void { - if (this.#released) return; - const p = this.follow.getWorldPosition(this.#followPosition); - if (Math.hypot(p.x - this.#at.x, p.z - this.#at.z) > 4 || Number.isNaN(this.#at.x)) - this.#rebuild(); - } - - detach(): void { - for (const body of this.#live.values()) body.dispose(); - this.#live.clear(); - this.#released = true; - } - - #rebuild(): void { - const { x: fx, z: fz } = this.follow.getWorldPosition(this.#followPosition); - this.#at.x = fx; - this.#at.z = fz; - const keep = new Set(); - for (const run of this.#runs) { - const shape = COLLIDERS[run.asset]; - if (!shape) continue; - for (let i = run.offset; i < run.offset + run.count; i++) { - const x = this.records[i * 8] as number; - const z = this.records[i * 8 + 2] as number; - if (Math.hypot(x - fx, z - fz) > PROP_COLLIDER_REACH) continue; - keep.add(i); - if (this.#live.has(i)) continue; - const { lift, collider } = colliderAt(shape, this.records[i * 8 + 7] as number); - this.#live.set( - i, - new RigidBody3D({ - physics: this.ctx.physics, - type: "fixed", - entity: `${run.asset}.${String(i)}`, - position: { x, y: (this.records[i * 8 + 1] as number) + lift, z }, - shape: collider, - }), - ); - } - } - for (const [i, body] of this.#live) - if (!keep.has(i)) { - body.dispose(); - this.#live.delete(i); - } - } -} - -/** One table row at one placement's scale; a capsule stands on the origin, a sphere sits on it. */ -function colliderAt( - shape: NonNullable<(typeof COLLIDERS)[string]>, - scale: number, -): { lift: number; collider: CollisionShape3D } { - if ("sphere" in shape) - return { lift: 0, collider: CollisionShape3D.sphere(shape.sphere * scale) }; - const [halfHeight, radius] = shape.capsule; - return { - lift: (halfHeight + radius) * scale, - collider: CollisionShape3D.capsule(halfHeight * scale, radius * scale), - }; -} - -async function fetchAsset( - ctx: ICtx, IPhysicsContext>, - path: string, - assertCurrent: () => void, -): Promise { - const candidates = await ctx.assets.resolve(path); - assertCurrent(); - for (const candidate of candidates) { - const response = await fetch(candidate); - assertCurrent(); - if (response.ok) return response; - } - throw new Error(`Forest world: '${path}' is not served.`); -} diff --git a/packages/terrain/starter/tundra/bake.mjs b/packages/terrain/starter/tundra/bake.mjs index cfa66bf69a..70097ec850 100644 --- a/packages/terrain/starter/tundra/bake.mjs +++ b/packages/terrain/starter/tundra/bake.mjs @@ -1,118 +1,6 @@ -// Bakes this folder's recipe into a self-contained world package. Run it once from your game -// project, then serve the folder it writes: -// node src/terrain/tundra/bake.mjs [--assets ] [--out ] -// The engine's `WorldCells` streams `world/world.json` and `loadTerrainSplat` textures its ground; -// the game never evaluates terrain at runtime. -import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { Terrain, applyPlacementOverrides, bakeWorldPackage } from "@threenative/terrain"; +// Run with [--assets ] [--out ]; defaults to installed assets and ./world. +import { bakeKit } from "@threenative/terrain/kit"; -const here = dirname(fileURLToPath(import.meta.url)); - -/** `--key value` after the script; `--assets` and `--out` default as noted below. */ -function flag(name, fallback) { - const at = process.argv.indexOf(`--${name}`); - return at === -1 ? fallback : resolve(process.argv[at + 1] ?? ""); -} - -// The installed kit ships its models and textures beside the package manifest, so a copied kit -// finds them without asking the game for a path. `--assets` overrides that for a local checkout. -const installed = dirname(fileURLToPath(import.meta.resolve("@threenative/terrain/package.json"))); -const assetsDir = flag("assets", join(installed, "starter-assets")); -const outDir = flag("out", join(here, "world")); - -const recipe = JSON.parse(await readFile(join(here, "recipe.json"), "utf8")); -const assetTable = JSON.parse(await readFile(join(here, "assets.json"), "utf8")); -const surface = JSON.parse(await readFile(join(here, "surface.json"), "utf8")); - -const state = applyPlacementOverrides( - Terrain.fromJSON(recipe.recipe).evaluate(), - recipe.placementOverrides ?? {}, -); - -const assets = Object.fromEntries( - Object.entries(assetTable).map(([id, spec]) => [ - id, - { - bounds: spec.bounds, - glb: `models/${spec.near.split("/").at(-1)}`, - ...(spec.mid - ? { lods: [{ distance: spec.midDistance, glb: `models/${spec.mid.split("/").at(-1)}` }] } - : {}), - maxDistance: spec.maxDistance, - }, - ]), -); - -const started = performance.now(); -const { manifest, splat, files } = bakeWorldPackage(state, { - assets, - layers: { table: "terrain-table.json" }, +await bakeKit(import.meta.url, { + sky: "hdri/kloofendal_48d_partly_cloudy_1k.hdr", }); -const written = new Map(); - -async function write(relative, bytes) { - await mkdir(join(outDir, dirname(relative)), { recursive: true }); - await writeFile(join(outDir, relative), bytes); - written.set(relative, bytes.length ?? bytes.byteLength); -} - -async function copy(from, to) { - await mkdir(join(outDir, dirname(to)), { recursive: true }); - await copyFile(join(assetsDir, from), join(outDir, to)); - written.set(to, (await readFile(join(outDir, to))).byteLength); -} - -await write("world.json", `${JSON.stringify(manifest, null, 2)}\n`); -// The lake and river the generator evaluated, so the game draws the water the bake shaped. -const round = (value) => Math.round(value * 1_000) / 1_000; -await write( - "water.json", - `${JSON.stringify( - { - lakes: state.waters.map((lake) => ({ - id: lake.id, - at: lake.at, - radius: lake.radius, - level: lake.level, - })), - rivers: state.rivers.map((river) => ({ - id: river.id, - width: round(river.width), - points: river.points.map((point) => point.map(round)), - })), - }, - null, - 2, - )}\n`, -); -for (const [name, bytes] of Object.entries(files)) await write(name, bytes); -for (const spec of Object.values(assetTable)) { - await copy(spec.near, `models/${spec.near.split("/").at(-1)}`); - if (spec.mid) await copy(spec.mid, `models/${spec.mid.split("/").at(-1)}`); -} -// The sky the props are lit by (CC0 Poly Haven Kloofendal); world.ts gives it to their materials. -await copy("hdri/kloofendal_48d_partly_cloudy_1k.hdr", "sky.hdr"); -for (const layer of [surface.base, ...surface.layers]) { - const source = surface.sources[layer.id]; - await copy(source.diff, `${surface.textures}/${layer.id}_diff.jpg`); - await copy(source.nrm, `${surface.textures}/${layer.id}_nrm.jpg`); -} -// `sources` is the bake's own lookup: the runtime reads only base/layers/splat/textures. -await write( - "terrain-table.json", - `${JSON.stringify({ ...surface, splat, textures: "textures" }, null, 2)}\n`, -); - -const perAsset = {}; -for (const cell of manifest.cells) - for (const run of cell.runs) perAsset[run.asset] = (perAsset[run.asset] ?? 0) + run.count; -const total = [...written.values()].reduce((sum, bytes) => sum + bytes, 0); -console.log( - `${Object.entries(perAsset) - .map(([asset, count]) => `${asset} ${String(count)}`) - .join(", ")} placements; ${[...written] - .map(([name, bytes]) => `${name} ${String(bytes)} B`) - .join(", ")}; ${String(total)} B total; ${Math.round(performance.now() - started)} ms`, -); diff --git a/packages/terrain/starter/tundra/heightmap.r16 b/packages/terrain/starter/tundra/heightmap.r16 new file mode 100644 index 0000000000..0e3c93495f Binary files /dev/null and b/packages/terrain/starter/tundra/heightmap.r16 differ diff --git a/packages/terrain/starter/tundra/recipe.json b/packages/terrain/starter/tundra/recipe.json index 6cc371668b..d713f95519 100644 --- a/packages/terrain/starter/tundra/recipe.json +++ b/packages/terrain/starter/tundra/recipe.json @@ -17,66057 +17,9 @@ "data": { "width": 257, "height": 257, - "values": [ - 11.9, - 12.9, - 13.9, - 14.8, - 15.7, - 16.5, - 17.2, - 17.8, - 18.4, - 19, - 19.7, - 20.4, - 21, - 21.5, - 22.1, - 22.6, - 23.1, - 23.4, - 23.4, - 23.4, - 23.2, - 23, - 23.3, - 22.8, - 21.6, - 20.7, - 19.8, - 18.7, - 17.6, - 16.8, - 15.9, - 15.3, - 15.1, - 15.2, - 15.2, - 15.2, - 15.4, - 15.4, - 15.2, - 15, - 15, - 15.2, - 15.3, - 15.4, - 15.6, - 15.7, - 15.5, - 15.4, - 15.4, - 15.4, - 15, - 14.7, - 14.4, - 14.2, - 14.2, - 14.2, - 14.1, - 14, - 13.9, - 13.7, - 13.7, - 13.8, - 13.7, - 13.2, - 13, - 13, - 12.9, - 12.8, - 12.8, - 12.6, - 12.4, - 12.2, - 12.1, - 11.8, - 11.6, - 11.4, - 11.3, - 11.2, - 11.2, - 11.3, - 11.2, - 11.2, - 11.1, - 11, - 10.9, - 10.9, - 11, - 11.1, - 11.2, - 11.3, - 11.4, - 11.6, - 11.8, - 12, - 12.3, - 12.6, - 12.9, - 13.2, - 13.3, - 13.5, - 13.8, - 13.9, - 14.2, - 14.4, - 14.5, - 14.7, - 14.8, - 14.9, - 15.1, - 15.2, - 15.4, - 15.6, - 15.9, - 16.1, - 16.4, - 16.7, - 17, - 17.3, - 17.5, - 17.7, - 17.6, - 17.6, - 17.8, - 17.9, - 18.3, - 19, - 19.6, - 20.1, - 20.5, - 20.9, - 21.1, - 21.5, - 21.8, - 22, - 22.1, - 22.3, - 22.6, - 22.7, - 23, - 22.9, - 22.6, - 22.2, - 22.2, - 22.4, - 22.8, - 23.2, - 23.6, - 23.8, - 24, - 24.2, - 24.4, - 24.4, - 24.6, - 24.8, - 25.2, - 25.6, - 25.9, - 26.2, - 26.4, - 26.5, - 26.7, - 26.8, - 27.1, - 27.2, - 27.3, - 27.4, - 27.6, - 27.7, - 27.9, - 28.2, - 28.4, - 28.5, - 28.7, - 29, - 29.1, - 29.4, - 29.7, - 29.8, - 29.9, - 30.2, - 30.3, - 30.4, - 30.5, - 30.7, - 31.1, - 31.3, - 31.4, - 31.5, - 31.7, - 32, - 32.1, - 32.1, - 32.2, - 32.3, - 32.4, - 32.5, - 32.6, - 32.7, - 32.8, - 32.9, - 33, - 33.1, - 33.2, - 33.1, - 33.1, - 33.2, - 33.2, - 33.3, - 33.4, - 33.4, - 33.5, - 33.6, - 33.7, - 33.8, - 33.8, - 33.8, - 33.9, - 33.9, - 34.1, - 34.1, - 34.2, - 34.3, - 34.4, - 34.5, - 34.7, - 34.8, - 35.1, - 35.2, - 35.3, - 35.5, - 35.6, - 35.8, - 36, - 36.2, - 36.4, - 36.6, - 37, - 37.3, - 37.7, - 38.1, - 38.3, - 38.8, - 39.1, - 39.4, - 39.7, - 40.1, - 40.2, - 40.2, - 40.3, - 40.4, - 40.7, - 41, - 41, - 41.2, - 41.4, - 41.6, - 41.6, - 12.3, - 13.3, - 14.3, - 15.2, - 16.2, - 17, - 17.7, - 18.3, - 18.9, - 19.5, - 20.2, - 20.9, - 21.5, - 22, - 22.5, - 23, - 23.5, - 23.8, - 23.9, - 24, - 23.8, - 23.6, - 23.9, - 23.7, - 22.6, - 21.6, - 20.8, - 19.8, - 18.8, - 17.9, - 17, - 16.2, - 15.8, - 15.7, - 15.6, - 15.7, - 15.8, - 15.9, - 15.7, - 15.6, - 15.7, - 15.8, - 15.9, - 16, - 16, - 16, - 15.9, - 15.7, - 15.8, - 15.9, - 15.7, - 15.2, - 14.9, - 14.6, - 14.6, - 14.6, - 14.5, - 14.4, - 14.3, - 14.2, - 14.1, - 14.1, - 14.1, - 13.7, - 13.5, - 13.3, - 13.2, - 13, - 13.1, - 13, - 12.8, - 12.6, - 12.5, - 12.3, - 12.1, - 11.9, - 11.7, - 11.6, - 11.6, - 11.7, - 11.6, - 11.5, - 11.5, - 11.3, - 11.3, - 11.3, - 11.4, - 11.5, - 11.5, - 11.6, - 11.7, - 11.8, - 12.1, - 12.3, - 12.6, - 12.8, - 13.1, - 13.3, - 13.5, - 13.7, - 13.9, - 14.2, - 14.5, - 14.7, - 14.8, - 14.9, - 15, - 15.1, - 15.3, - 15.6, - 15.9, - 16.1, - 16.4, - 16.8, - 17, - 17.4, - 17.7, - 18, - 18.3, - 18.4, - 18.3, - 18.2, - 18.3, - 18.4, - 18.8, - 19.5, - 20.1, - 20.6, - 21, - 21.3, - 21.5, - 21.8, - 22.1, - 22.4, - 22.7, - 22.9, - 23.1, - 23.3, - 23.5, - 23.7, - 23.5, - 23, - 22.8, - 22.9, - 23.1, - 23.4, - 23.7, - 23.9, - 24.2, - 24.4, - 24.5, - 24.6, - 24.8, - 24.9, - 25.1, - 25.4, - 25.7, - 26, - 26.3, - 26.5, - 26.6, - 26.7, - 27, - 27.1, - 27.2, - 27.3, - 27.5, - 27.7, - 27.9, - 28.2, - 28.4, - 28.5, - 28.7, - 28.9, - 29.1, - 29.3, - 29.7, - 29.9, - 30, - 30.1, - 30.2, - 30.3, - 30.4, - 30.6, - 30.8, - 30.9, - 30.9, - 31.1, - 31.2, - 31.5, - 31.8, - 31.8, - 31.8, - 32, - 32.2, - 32.3, - 32.4, - 32.5, - 32.6, - 32.7, - 32.7, - 32.8, - 32.8, - 33, - 33, - 33.1, - 33.1, - 33.2, - 33.3, - 33.4, - 33.4, - 33.5, - 33.5, - 33.5, - 33.6, - 33.7, - 33.7, - 33.7, - 33.8, - 34, - 34, - 34.2, - 34.3, - 34.5, - 34.6, - 34.8, - 35, - 35.1, - 35.2, - 35.4, - 35.6, - 35.8, - 36, - 36.2, - 36.4, - 36.7, - 37.2, - 37.6, - 38, - 38.4, - 38.6, - 39.1, - 39.4, - 39.5, - 39.7, - 40, - 40.1, - 40.2, - 40.3, - 40.5, - 40.8, - 40.9, - 40.9, - 41, - 41.2, - 41.4, - 41.5, - 12.7, - 13.8, - 14.8, - 15.7, - 16.6, - 17.4, - 18.1, - 18.8, - 19.4, - 20, - 20.6, - 21.2, - 21.8, - 22.3, - 22.8, - 23.3, - 23.8, - 24.1, - 24.4, - 24.5, - 24.5, - 24.4, - 24.3, - 24.1, - 23.2, - 22.4, - 21.8, - 20.9, - 19.9, - 19, - 18, - 17.1, - 16.5, - 16.1, - 16.1, - 16.1, - 16.2, - 16.3, - 16.2, - 16.1, - 16.2, - 16.5, - 16.6, - 16.6, - 16.4, - 16.3, - 16.2, - 16.2, - 16.4, - 16.6, - 16.2, - 15.7, - 15.3, - 15, - 15, - 15, - 14.9, - 14.9, - 14.8, - 14.7, - 14.5, - 14.5, - 14.3, - 14.2, - 14, - 13.7, - 13.6, - 13.5, - 13.6, - 13.4, - 13.2, - 13, - 12.9, - 12.7, - 12.5, - 12.3, - 12.1, - 12.1, - 12, - 12, - 12, - 12, - 11.9, - 11.8, - 11.7, - 11.8, - 11.8, - 11.9, - 12, - 12, - 12, - 12.2, - 12.4, - 12.6, - 12.9, - 13.1, - 13.2, - 13.5, - 13.6, - 13.8, - 14.2, - 14.6, - 15, - 15.2, - 15.3, - 15.4, - 15.5, - 15.6, - 15.8, - 16.1, - 16.4, - 16.8, - 17.1, - 17.5, - 17.8, - 18.1, - 18.4, - 18.7, - 18.9, - 19.2, - 19.1, - 18.8, - 18.9, - 19, - 19.4, - 20.1, - 20.8, - 21.2, - 21.5, - 21.7, - 22, - 22.3, - 22.5, - 22.9, - 23.1, - 23.3, - 23.6, - 23.8, - 24, - 24.1, - 24.1, - 23.8, - 23.5, - 23.4, - 23.5, - 23.7, - 23.9, - 24, - 24.3, - 24.5, - 24.6, - 24.7, - 24.9, - 25, - 25.2, - 25.4, - 25.6, - 25.9, - 26.2, - 26.4, - 26.6, - 26.6, - 26.9, - 27, - 27.1, - 27.2, - 27.5, - 27.7, - 27.9, - 28.3, - 28.3, - 28.4, - 28.6, - 28.9, - 29.1, - 29.4, - 29.6, - 29.7, - 29.9, - 29.9, - 30, - 30.1, - 30.2, - 30.3, - 30.4, - 30.5, - 30.5, - 30.7, - 30.8, - 31, - 31.4, - 31.5, - 31.8, - 32, - 32.1, - 32.1, - 32.2, - 32.3, - 32.4, - 32.5, - 32.5, - 32.6, - 32.7, - 32.9, - 33, - 33, - 33.1, - 33.2, - 33.2, - 33.2, - 33.2, - 33.3, - 33.3, - 33.3, - 33.4, - 33.4, - 33.5, - 33.5, - 33.6, - 33.8, - 33.9, - 34, - 34.2, - 34.3, - 34.5, - 34.7, - 34.9, - 35, - 35.1, - 35.3, - 35.5, - 35.7, - 36, - 36.3, - 36.6, - 37, - 37.5, - 38, - 38.5, - 38.8, - 39, - 39.3, - 39.4, - 39.6, - 39.8, - 39.9, - 40, - 40.3, - 40.3, - 40.5, - 40.7, - 40.8, - 40.8, - 40.9, - 41, - 41.1, - 41.2, - 13.2, - 14.3, - 15.4, - 16.3, - 17.2, - 17.9, - 18.6, - 19.2, - 19.8, - 20.4, - 21, - 21.6, - 22.2, - 22.7, - 23.2, - 23.7, - 24.1, - 24.5, - 24.8, - 25.1, - 25.2, - 25.1, - 24.9, - 24.6, - 24, - 23.3, - 22.7, - 21.9, - 21, - 20, - 18.9, - 18, - 17.3, - 16.8, - 16.6, - 16.6, - 16.6, - 16.6, - 16.6, - 16.6, - 16.6, - 16.9, - 17.2, - 17.2, - 16.8, - 16.6, - 16.6, - 16.7, - 17, - 17.1, - 16.8, - 16.1, - 15.8, - 15.5, - 15.4, - 15.4, - 15.4, - 15.3, - 15.2, - 15.1, - 14.8, - 14.7, - 14.6, - 14.5, - 14.4, - 14.3, - 14.1, - 14.1, - 14.1, - 13.8, - 13.5, - 13.4, - 13.2, - 13, - 12.9, - 12.7, - 12.6, - 12.6, - 12.5, - 12.5, - 12.4, - 12.4, - 12.3, - 12.3, - 12.3, - 12.4, - 12.5, - 12.5, - 12.5, - 12.6, - 12.6, - 12.7, - 12.9, - 13, - 13.2, - 13.4, - 13.6, - 13.7, - 14, - 14.4, - 14.9, - 15.3, - 15.8, - 16.1, - 16.2, - 16.2, - 16.3, - 16.4, - 16.6, - 16.9, - 17.2, - 17.5, - 17.9, - 18.2, - 18.5, - 18.7, - 19, - 19.3, - 19.5, - 19.6, - 19.6, - 19.4, - 19.5, - 19.6, - 19.9, - 20.7, - 21.5, - 21.9, - 22.1, - 22.4, - 22.6, - 22.8, - 23.1, - 23.4, - 23.6, - 23.8, - 24.1, - 24.3, - 24.4, - 24.5, - 24.5, - 24.4, - 24.1, - 23.9, - 24, - 24.1, - 24.1, - 24.2, - 24.3, - 24.5, - 24.6, - 24.7, - 24.9, - 25, - 25.2, - 25.4, - 25.7, - 25.9, - 26.1, - 26.3, - 26.5, - 26.6, - 26.8, - 26.9, - 27, - 27.2, - 27.4, - 27.6, - 27.8, - 28.1, - 28.3, - 28.4, - 28.7, - 28.8, - 29.1, - 29.3, - 29.5, - 29.6, - 29.7, - 29.7, - 29.8, - 29.9, - 29.9, - 30, - 30.1, - 30.2, - 30.3, - 30.5, - 30.7, - 30.8, - 31.2, - 31.5, - 31.7, - 31.9, - 31.8, - 31.9, - 32, - 32.1, - 32.2, - 32.3, - 32.3, - 32.6, - 32.7, - 32.7, - 32.9, - 32.9, - 32.9, - 32.9, - 33, - 33, - 33, - 33, - 33.1, - 33.1, - 33.1, - 33.2, - 33.3, - 33.5, - 33.6, - 33.7, - 33.8, - 34, - 34.1, - 34.3, - 34.4, - 34.5, - 34.7, - 34.9, - 35, - 35.2, - 35.5, - 35.8, - 36.2, - 36.7, - 37, - 37.4, - 37.8, - 38.3, - 38.8, - 39, - 39.2, - 39.4, - 39.5, - 39.7, - 39.8, - 39.9, - 40, - 40.3, - 40.3, - 40.4, - 40.5, - 40.6, - 40.7, - 40.7, - 40.8, - 41, - 41.1, - 13.8, - 14.8, - 15.9, - 16.8, - 17.7, - 18.5, - 19.1, - 19.7, - 20.3, - 20.9, - 21.4, - 22, - 22.7, - 23.2, - 23.7, - 24.1, - 24.5, - 24.9, - 25.3, - 25.6, - 25.8, - 25.9, - 25.7, - 25.4, - 25.1, - 24.4, - 23.6, - 22.8, - 22, - 20.9, - 20, - 19.2, - 18.4, - 17.7, - 17.3, - 17.1, - 17.1, - 17.1, - 17.1, - 17.1, - 17.1, - 17.3, - 17.5, - 17.6, - 17.3, - 17, - 17, - 17, - 17.3, - 17.6, - 17.3, - 16.7, - 16.4, - 16.2, - 16, - 15.9, - 15.8, - 15.7, - 15.6, - 15.5, - 15.2, - 15, - 14.9, - 14.9, - 14.8, - 14.7, - 14.6, - 14.5, - 14.3, - 14.1, - 13.9, - 13.8, - 13.6, - 13.4, - 13.2, - 13.1, - 13, - 13, - 12.9, - 12.9, - 12.8, - 12.8, - 12.7, - 12.7, - 12.9, - 13, - 13, - 13.1, - 13.2, - 13.3, - 13.4, - 13.5, - 13.6, - 13.8, - 13.8, - 14.1, - 14.3, - 14.4, - 14.6, - 15, - 15.6, - 16, - 16.5, - 16.8, - 16.9, - 17.2, - 17.4, - 17.4, - 17.6, - 17.9, - 18.1, - 18.4, - 18.7, - 18.9, - 19.1, - 19.4, - 19.6, - 19.9, - 20.1, - 20.2, - 20.1, - 20, - 20, - 20.2, - 20.5, - 21.2, - 22, - 22.4, - 22.7, - 22.9, - 23.2, - 23.4, - 23.7, - 23.9, - 24, - 24.2, - 24.5, - 24.7, - 24.8, - 24.9, - 24.8, - 24.7, - 24.4, - 24.3, - 24.3, - 24.3, - 24.4, - 24.5, - 24.5, - 24.6, - 24.7, - 24.8, - 24.9, - 25.1, - 25.2, - 25.5, - 25.8, - 26, - 26.2, - 26.3, - 26.4, - 26.6, - 26.7, - 26.9, - 27, - 27.2, - 27.3, - 27.6, - 27.8, - 28, - 28.2, - 28.4, - 28.6, - 28.8, - 28.9, - 29.2, - 29.3, - 29.5, - 29.5, - 29.6, - 29.7, - 29.7, - 29.8, - 29.8, - 29.9, - 30.1, - 30.2, - 30.4, - 30.6, - 30.8, - 31.1, - 31.4, - 31.6, - 31.7, - 31.7, - 31.7, - 31.8, - 31.9, - 32, - 32.1, - 32.2, - 32.3, - 32.4, - 32.5, - 32.5, - 32.5, - 32.5, - 32.6, - 32.7, - 32.7, - 32.8, - 32.8, - 32.9, - 32.9, - 33, - 33.1, - 33.3, - 33.4, - 33.5, - 33.6, - 33.8, - 33.9, - 34, - 34.2, - 34.4, - 34.5, - 34.6, - 34.7, - 35, - 35.3, - 35.7, - 36.2, - 36.6, - 36.9, - 37.3, - 37.6, - 38.1, - 38.6, - 38.9, - 39.1, - 39.2, - 39.3, - 39.5, - 39.6, - 39.8, - 39.8, - 40, - 40.1, - 40.2, - 40.3, - 40.4, - 40.5, - 40.6, - 40.6, - 40.7, - 40.8, - 40.9, - 14.2, - 15.3, - 16.3, - 17.3, - 18.2, - 19, - 19.7, - 20.3, - 20.8, - 21.4, - 21.9, - 22.4, - 23.1, - 23.6, - 24.1, - 24.6, - 25, - 25.4, - 25.8, - 26.1, - 26.3, - 26.5, - 26.4, - 26.5, - 26.5, - 25.7, - 24.6, - 23.8, - 22.9, - 22, - 21.1, - 20.3, - 19.5, - 18.7, - 18.2, - 17.8, - 17.7, - 17.6, - 17.6, - 17.6, - 17.6, - 17.7, - 17.9, - 17.9, - 17.8, - 17.5, - 17.3, - 17.3, - 17.5, - 17.9, - 18, - 17.6, - 17.3, - 17.1, - 16.8, - 16.5, - 16.3, - 16.2, - 16, - 15.8, - 15.6, - 15.4, - 15.3, - 15.2, - 15.1, - 15, - 15, - 14.8, - 14.6, - 14.5, - 14.4, - 14.2, - 14, - 13.8, - 13.6, - 13.5, - 13.5, - 13.5, - 13.4, - 13.3, - 13.3, - 13.3, - 13.2, - 13.2, - 13.3, - 13.4, - 13.5, - 13.7, - 13.8, - 13.9, - 14.1, - 14.3, - 14.4, - 14.7, - 14.8, - 14.9, - 15.1, - 15.2, - 15.3, - 15.8, - 16.4, - 16.9, - 17.2, - 17.3, - 17.6, - 17.9, - 18.2, - 18.4, - 18.5, - 18.8, - 19, - 19.1, - 19.4, - 19.6, - 19.8, - 20, - 20.3, - 20.6, - 20.8, - 20.9, - 20.8, - 20.5, - 20.5, - 20.7, - 21, - 21.7, - 22.3, - 22.8, - 23.2, - 23.4, - 23.7, - 23.9, - 24.2, - 24.3, - 24.5, - 24.7, - 24.8, - 25, - 25.2, - 25.3, - 25.1, - 25, - 24.8, - 24.7, - 24.7, - 24.6, - 24.6, - 24.6, - 24.7, - 24.7, - 24.8, - 24.9, - 25, - 25.1, - 25.3, - 25.5, - 25.8, - 26, - 26.1, - 26.3, - 26.4, - 26.6, - 26.7, - 26.9, - 27, - 27.2, - 27.3, - 27.5, - 27.8, - 28, - 28.2, - 28.3, - 28.6, - 28.8, - 29, - 29.1, - 29.3, - 29.3, - 29.4, - 29.5, - 29.5, - 29.5, - 29.6, - 29.6, - 29.8, - 30, - 30.2, - 30.4, - 30.6, - 30.8, - 31, - 31.2, - 31.4, - 31.4, - 31.5, - 31.6, - 31.7, - 31.8, - 31.8, - 31.9, - 31.9, - 32, - 32.1, - 32.2, - 32.2, - 32.2, - 32.3, - 32.4, - 32.4, - 32.4, - 32.5, - 32.6, - 32.6, - 32.8, - 32.9, - 33, - 33.2, - 33.3, - 33.4, - 33.5, - 33.7, - 33.8, - 34, - 34.1, - 34.3, - 34.5, - 34.6, - 34.8, - 35.1, - 35.6, - 36.1, - 36.5, - 36.8, - 37.1, - 37.5, - 37.7, - 38.1, - 38.6, - 38.8, - 38.9, - 39.1, - 39.2, - 39.4, - 39.6, - 39.6, - 39.7, - 39.9, - 40, - 40.1, - 40.2, - 40.2, - 40.3, - 40.4, - 40.5, - 40.6, - 40.6, - 40.8, - 14.7, - 15.8, - 16.8, - 17.8, - 18.7, - 19.5, - 20.2, - 20.8, - 21.3, - 21.9, - 22.3, - 22.8, - 23.4, - 24, - 24.6, - 25.1, - 25.6, - 26, - 26.4, - 26.7, - 26.9, - 27.1, - 27.1, - 27.1, - 27.3, - 26.4, - 25.5, - 24.7, - 23.9, - 23.1, - 22.3, - 21.5, - 20.7, - 19.9, - 19.3, - 18.7, - 18.4, - 18.2, - 18.2, - 18.3, - 18.3, - 18.2, - 18.3, - 18.4, - 18.3, - 18.1, - 17.8, - 17.6, - 17.7, - 17.9, - 18.2, - 18.2, - 18.2, - 17.9, - 17.5, - 17.2, - 16.9, - 16.8, - 16.5, - 16.2, - 16, - 15.7, - 15.6, - 15.4, - 15.3, - 15.2, - 15.2, - 15.1, - 15.1, - 14.9, - 14.8, - 14.6, - 14.4, - 14.2, - 14.1, - 14, - 13.9, - 13.9, - 13.8, - 13.7, - 13.7, - 13.7, - 13.7, - 13.8, - 13.9, - 14.1, - 14.2, - 14.3, - 14.4, - 14.6, - 14.8, - 15, - 15.1, - 15.4, - 15.6, - 15.6, - 15.8, - 15.9, - 16.1, - 16.8, - 17.2, - 17.6, - 17.8, - 17.9, - 18.2, - 18.5, - 18.8, - 19, - 19.2, - 19.5, - 19.7, - 19.9, - 20.2, - 20.3, - 20.5, - 20.8, - 21, - 21.3, - 21.4, - 21.6, - 21.5, - 21.2, - 21.1, - 21.2, - 21.5, - 22.2, - 22.8, - 23.2, - 23.5, - 23.8, - 24.1, - 24.3, - 24.5, - 24.8, - 24.9, - 25.1, - 25.1, - 25.2, - 25.3, - 25.3, - 25.3, - 25.3, - 25.2, - 25.1, - 25.1, - 25.1, - 24.9, - 24.8, - 24.8, - 24.9, - 24.9, - 25, - 25.1, - 25.2, - 25.3, - 25.5, - 25.8, - 26, - 26.2, - 26.4, - 26.5, - 26.6, - 26.8, - 26.9, - 27.1, - 27.2, - 27.3, - 27.5, - 27.7, - 28, - 28.1, - 28.4, - 28.7, - 28.8, - 29, - 29.1, - 29.2, - 29.2, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.5, - 29.7, - 29.9, - 30.1, - 30.4, - 30.6, - 30.8, - 31, - 31.2, - 31.3, - 31.3, - 31.3, - 31.5, - 31.7, - 31.7, - 31.6, - 31.5, - 31.6, - 31.6, - 31.7, - 31.8, - 31.8, - 31.9, - 32, - 32.1, - 32.1, - 32.2, - 32.3, - 32.4, - 32.5, - 32.6, - 32.8, - 32.9, - 33.1, - 33.2, - 33.3, - 33.4, - 33.6, - 33.8, - 33.9, - 34.1, - 34.2, - 34.3, - 34.6, - 34.9, - 35.4, - 35.9, - 36.3, - 36.6, - 36.9, - 37.3, - 37.6, - 37.8, - 38, - 38.4, - 38.5, - 38.7, - 38.9, - 39, - 39.2, - 39.5, - 39.5, - 39.6, - 39.8, - 39.9, - 39.9, - 40, - 40.1, - 40.2, - 40.3, - 40.3, - 40.4, - 40.4, - 40.5, - 15.1, - 16.2, - 17.3, - 18.3, - 19.1, - 19.9, - 20.6, - 21.3, - 21.8, - 22.4, - 22.9, - 23.4, - 23.9, - 24.5, - 25, - 25.6, - 26, - 26.5, - 26.9, - 27.3, - 27.5, - 27.6, - 27.6, - 27.7, - 27.8, - 27.1, - 26.3, - 25.6, - 24.9, - 24.2, - 23.5, - 22.8, - 22, - 21.2, - 20.4, - 19.7, - 19.2, - 19, - 18.9, - 18.9, - 18.9, - 18.8, - 18.8, - 18.9, - 18.8, - 18.6, - 18.3, - 18, - 17.9, - 18, - 18.2, - 18.5, - 18.8, - 18.6, - 18.3, - 18.2, - 17.9, - 17.7, - 17.3, - 16.9, - 16.6, - 16.3, - 16.1, - 15.8, - 15.6, - 15.5, - 15.5, - 15.4, - 15.4, - 15.3, - 15.2, - 14.9, - 14.7, - 14.6, - 14.5, - 14.4, - 14.3, - 14.3, - 14.3, - 14.3, - 14.3, - 14.3, - 14.4, - 14.4, - 14.5, - 14.6, - 14.7, - 14.9, - 15.1, - 15.3, - 15.5, - 15.7, - 15.9, - 16.1, - 16.2, - 16.3, - 16.4, - 16.6, - 16.9, - 17.5, - 17.9, - 18.2, - 18.3, - 18.5, - 18.8, - 19.2, - 19.5, - 19.6, - 19.9, - 20.2, - 20.5, - 20.7, - 20.9, - 21.1, - 21.4, - 21.6, - 21.7, - 21.9, - 22.1, - 22.2, - 22.2, - 21.9, - 21.7, - 21.7, - 22, - 22.6, - 23.1, - 23.5, - 23.8, - 24.1, - 24.5, - 24.6, - 24.8, - 25, - 25.1, - 25.3, - 25.4, - 25.4, - 25.5, - 25.5, - 25.6, - 25.6, - 25.5, - 25.5, - 25.5, - 25.4, - 25.2, - 25.1, - 25, - 25, - 25, - 25.1, - 25.2, - 25.3, - 25.4, - 25.5, - 25.8, - 26, - 26.3, - 26.4, - 26.5, - 26.7, - 26.8, - 26.9, - 27.2, - 27.3, - 27.3, - 27.5, - 27.7, - 27.9, - 28.1, - 28.4, - 28.7, - 28.8, - 28.9, - 29, - 29.1, - 29.1, - 29.1, - 29.1, - 29.2, - 29.3, - 29.3, - 29.5, - 29.7, - 29.9, - 30.1, - 30.3, - 30.5, - 30.6, - 30.8, - 30.9, - 30.9, - 31, - 31, - 31.1, - 31.3, - 31.3, - 31.3, - 31.3, - 31.2, - 31.2, - 31.3, - 31.4, - 31.5, - 31.6, - 31.7, - 31.8, - 32, - 32.1, - 32.1, - 32.2, - 32.4, - 32.5, - 32.6, - 32.8, - 32.9, - 33.1, - 33.2, - 33.3, - 33.5, - 33.7, - 33.9, - 34.1, - 34.2, - 34.4, - 34.7, - 35, - 35.5, - 35.9, - 36.4, - 36.6, - 36.9, - 37.3, - 37.6, - 37.8, - 38, - 38.2, - 38.3, - 38.5, - 38.7, - 38.9, - 39, - 39.1, - 39.3, - 39.4, - 39.6, - 39.7, - 39.7, - 39.8, - 39.9, - 40.1, - 40.2, - 40.1, - 40.2, - 40.2, - 40.2, - 15.6, - 16.7, - 17.7, - 18.7, - 19.6, - 20.4, - 21.1, - 21.7, - 22.3, - 22.9, - 23.4, - 23.9, - 24.4, - 25, - 25.5, - 26, - 26.5, - 26.9, - 27.3, - 27.6, - 27.8, - 28, - 28.1, - 28.1, - 28.2, - 28.1, - 27.5, - 26.6, - 25.9, - 25.3, - 24.6, - 24, - 23.2, - 22.5, - 21.8, - 20.9, - 20.2, - 19.9, - 19.7, - 19.6, - 19.5, - 19.4, - 19.4, - 19.4, - 19.3, - 19.1, - 18.9, - 18.6, - 18.3, - 18.2, - 18.3, - 18.5, - 18.8, - 18.8, - 18.8, - 18.8, - 18.7, - 18.5, - 18.3, - 17.9, - 17.6, - 17.2, - 16.9, - 16.6, - 16.2, - 16, - 15.9, - 15.8, - 15.7, - 15.6, - 15.4, - 15.3, - 15.1, - 15, - 14.9, - 14.9, - 14.8, - 14.8, - 14.9, - 14.8, - 14.8, - 14.9, - 14.9, - 15, - 15.1, - 15.2, - 15.4, - 15.7, - 16, - 16.5, - 16.9, - 17, - 16.8, - 16.8, - 16.9, - 17.1, - 17.4, - 17.6, - 17.8, - 18.2, - 18.5, - 18.7, - 18.9, - 19.2, - 19.4, - 19.8, - 20, - 20.3, - 20.6, - 20.9, - 21.2, - 21.5, - 21.7, - 21.9, - 22.3, - 22.5, - 22.5, - 22.6, - 22.7, - 22.8, - 22.9, - 22.6, - 22.3, - 22.3, - 22.5, - 22.9, - 23.3, - 23.7, - 24.1, - 24.3, - 24.6, - 24.8, - 24.9, - 25.1, - 25.3, - 25.5, - 25.6, - 25.7, - 25.8, - 25.8, - 25.9, - 25.8, - 25.7, - 25.7, - 25.8, - 25.7, - 25.6, - 25.4, - 25.3, - 25.2, - 25.2, - 25.2, - 25.3, - 25.4, - 25.5, - 25.6, - 25.8, - 26, - 26.2, - 26.4, - 26.5, - 26.8, - 26.9, - 27, - 27.1, - 27.2, - 27.4, - 27.6, - 27.7, - 27.9, - 28.1, - 28.4, - 28.7, - 28.8, - 28.8, - 28.9, - 28.9, - 28.9, - 28.9, - 29, - 29.1, - 29.2, - 29.4, - 29.5, - 29.6, - 29.8, - 30, - 30.1, - 30.2, - 30.4, - 30.5, - 30.5, - 30.6, - 30.7, - 30.7, - 30.7, - 30.8, - 30.9, - 31, - 31.1, - 31.1, - 31.1, - 31.2, - 31.2, - 31.3, - 31.3, - 31.4, - 31.6, - 31.8, - 31.9, - 31.9, - 32, - 32.2, - 32.3, - 32.4, - 32.7, - 32.8, - 33, - 33.2, - 33.3, - 33.5, - 33.7, - 33.9, - 34, - 34.2, - 34.5, - 34.9, - 35.2, - 35.6, - 35.9, - 36.3, - 36.6, - 36.9, - 37.2, - 37.5, - 37.8, - 37.9, - 38, - 38.2, - 38.4, - 38.6, - 38.8, - 39, - 39, - 39.2, - 39.4, - 39.5, - 39.5, - 39.5, - 39.6, - 39.8, - 39.9, - 39.9, - 39.9, - 39.9, - 39.9, - 39.9, - 16.5, - 17.3, - 18.3, - 19.1, - 20, - 20.8, - 21.4, - 22, - 22.6, - 23.2, - 23.8, - 24.4, - 24.9, - 25.4, - 26, - 26.4, - 26.9, - 27.3, - 27.6, - 27.8, - 28.1, - 28.3, - 28.4, - 28.5, - 28.6, - 28.6, - 28.5, - 28, - 26.9, - 26.2, - 25.6, - 25.1, - 24.5, - 23.8, - 23.1, - 22.2, - 21.4, - 21, - 20.6, - 20.4, - 20.1, - 19.9, - 19.9, - 19.9, - 19.8, - 19.6, - 19.4, - 19.2, - 18.9, - 18.6, - 18.5, - 18.6, - 18.7, - 18.9, - 19, - 19.1, - 19.1, - 19.1, - 19.1, - 19, - 18.7, - 18.1, - 17.9, - 17.6, - 17.2, - 16.8, - 16.5, - 16.3, - 16.1, - 15.9, - 15.8, - 15.7, - 15.5, - 15.4, - 15.4, - 15.3, - 15.3, - 15.3, - 15.4, - 15.4, - 15.4, - 15.5, - 15.6, - 15.7, - 15.8, - 16, - 16.2, - 16.6, - 16.9, - 17.4, - 17.9, - 17.9, - 17.8, - 18, - 18, - 18.1, - 18.3, - 18.5, - 18.7, - 19, - 19.2, - 19.3, - 19.5, - 19.8, - 20.1, - 20.3, - 20.6, - 21, - 21.4, - 21.7, - 22, - 22.2, - 22.4, - 22.5, - 22.8, - 23.1, - 23.1, - 23.2, - 23.2, - 23.3, - 23.4, - 23.2, - 22.9, - 22.9, - 23, - 23.2, - 23.6, - 24, - 24.3, - 24.6, - 24.8, - 25, - 25.2, - 25.3, - 25.5, - 25.7, - 25.8, - 25.9, - 26, - 25.9, - 26, - 25.9, - 25.9, - 25.9, - 26, - 26, - 25.9, - 25.7, - 25.6, - 25.6, - 25.5, - 25.4, - 25.4, - 25.5, - 25.5, - 25.6, - 25.8, - 26.1, - 26.2, - 26.4, - 26.6, - 26.8, - 27, - 27, - 27.1, - 27.2, - 27.4, - 27.6, - 27.8, - 27.9, - 28.2, - 28.4, - 28.6, - 28.6, - 28.7, - 28.8, - 28.7, - 28.7, - 28.8, - 28.9, - 29, - 29.2, - 29.3, - 29.4, - 29.6, - 29.7, - 29.8, - 29.9, - 30.1, - 30.1, - 30.2, - 30.2, - 30.3, - 30.4, - 30.4, - 30.5, - 30.5, - 30.6, - 30.6, - 30.8, - 30.9, - 31, - 31, - 31, - 31.1, - 31.2, - 31.2, - 31.4, - 31.5, - 31.7, - 31.7, - 31.9, - 32, - 32.2, - 32.4, - 32.6, - 32.8, - 32.9, - 33, - 33.2, - 33.4, - 33.6, - 33.8, - 34.1, - 34.3, - 34.7, - 35.1, - 35.4, - 35.7, - 36, - 36.3, - 36.6, - 36.9, - 37.2, - 37.3, - 37.6, - 37.7, - 37.8, - 38, - 38.2, - 38.5, - 38.6, - 38.7, - 38.9, - 39, - 39.3, - 39.4, - 39.3, - 39.4, - 39.4, - 39.7, - 39.6, - 39.6, - 39.6, - 39.7, - 39.6, - 39.6, - 17.1, - 17.7, - 18.7, - 19.5, - 20.3, - 21.1, - 21.8, - 22.4, - 23, - 23.6, - 24.1, - 24.7, - 25.3, - 25.8, - 26.3, - 26.8, - 27.3, - 27.7, - 28, - 28.2, - 28.4, - 28.6, - 28.7, - 28.8, - 29.1, - 29, - 28.8, - 28.6, - 28.1, - 27.2, - 26.5, - 25.9, - 25.4, - 24.9, - 24.2, - 23.5, - 22.7, - 22.1, - 21.7, - 21.2, - 20.7, - 20.6, - 20.5, - 20.4, - 20.3, - 20.2, - 20, - 19.7, - 19.4, - 19.2, - 19, - 18.9, - 18.8, - 18.9, - 19, - 19.2, - 19.3, - 19.3, - 19.4, - 19.4, - 19.3, - 18.9, - 18.6, - 18.4, - 18.3, - 17.9, - 17.3, - 16.9, - 16.7, - 16.4, - 16.3, - 16.1, - 16, - 16, - 15.9, - 15.8, - 15.8, - 16, - 16.2, - 16.3, - 16.2, - 16.3, - 16.3, - 16.4, - 16.6, - 16.8, - 16.9, - 17.4, - 17.8, - 18.2, - 18.3, - 18.4, - 18.7, - 19, - 19, - 19, - 19.3, - 19.5, - 19.6, - 19.7, - 19.9, - 20, - 20.2, - 20.4, - 20.7, - 20.9, - 21.2, - 21.7, - 22, - 22.4, - 22.7, - 22.9, - 23, - 23.1, - 23.5, - 23.7, - 23.7, - 23.8, - 23.8, - 23.8, - 23.8, - 23.6, - 23.4, - 23.4, - 23.5, - 23.6, - 23.7, - 24.1, - 24.5, - 24.8, - 25.1, - 25.3, - 25.5, - 25.6, - 25.8, - 25.9, - 25.9, - 26, - 26, - 26.1, - 26, - 26, - 26, - 26, - 26.1, - 26.1, - 26, - 26, - 25.9, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.6, - 25.7, - 25.9, - 26.1, - 26.2, - 26.4, - 26.6, - 26.8, - 27, - 27, - 27.1, - 27.3, - 27.4, - 27.5, - 27.8, - 28, - 28.3, - 28.5, - 28.5, - 28.5, - 28.6, - 28.6, - 28.6, - 28.6, - 28.7, - 28.7, - 28.8, - 28.9, - 29.1, - 29.3, - 29.4, - 29.5, - 29.7, - 29.8, - 29.8, - 29.8, - 29.9, - 29.9, - 30, - 30.1, - 30.1, - 30.2, - 30.2, - 30.3, - 30.3, - 30.4, - 30.6, - 30.7, - 30.8, - 30.8, - 30.9, - 30.9, - 31.1, - 31.2, - 31.4, - 31.5, - 31.6, - 31.7, - 31.9, - 32, - 32.3, - 32.4, - 32.6, - 32.8, - 32.8, - 33, - 33.3, - 33.6, - 33.9, - 34.2, - 34.5, - 34.8, - 35.1, - 35.5, - 35.8, - 36.1, - 36.4, - 36.7, - 36.9, - 37.1, - 37.2, - 37.4, - 37.6, - 37.8, - 38, - 38.1, - 38.3, - 38.5, - 38.6, - 38.7, - 38.8, - 38.9, - 39.1, - 39.1, - 39.2, - 39.2, - 39.3, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.3, - 17.4, - 18.2, - 18.9, - 19.7, - 20.7, - 21.4, - 22.1, - 22.7, - 23.2, - 23.9, - 24.5, - 25.1, - 25.7, - 26.2, - 26.7, - 27.2, - 27.7, - 28, - 28.3, - 28.6, - 28.8, - 28.9, - 29, - 29.1, - 29.2, - 29.3, - 29.3, - 29.5, - 29.2, - 28.5, - 27.7, - 27, - 26.4, - 25.9, - 25.3, - 24.6, - 23.9, - 23.3, - 22.8, - 22.2, - 21.6, - 21.5, - 21.3, - 21.1, - 20.9, - 20.7, - 20.5, - 20.3, - 19.9, - 19.6, - 19.5, - 19.4, - 19.2, - 19.1, - 19, - 19.1, - 19.2, - 19.3, - 19.6, - 19.6, - 19.4, - 19.3, - 19.1, - 19.1, - 19.2, - 18.9, - 18.2, - 17.7, - 17.3, - 17, - 16.9, - 16.7, - 16.7, - 16.6, - 16.5, - 16.6, - 16.8, - 17, - 17.2, - 17.4, - 17.3, - 17.1, - 17, - 17.1, - 17.2, - 17.5, - 17.8, - 18.1, - 18.7, - 19.4, - 19.3, - 19.2, - 19.5, - 19.6, - 19.7, - 19.9, - 20.2, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.9, - 21.1, - 21.3, - 21.6, - 21.9, - 22.4, - 22.7, - 22.9, - 23.2, - 23.4, - 23.6, - 23.7, - 24, - 24.1, - 24.1, - 24.2, - 24.2, - 24.1, - 24.1, - 24, - 23.9, - 23.8, - 23.9, - 24, - 24, - 24.3, - 24.7, - 25, - 25.3, - 25.5, - 25.7, - 25.9, - 26, - 26, - 26, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26, - 26, - 26.1, - 26.1, - 26.2, - 26.1, - 26.1, - 26.1, - 26, - 25.9, - 25.7, - 25.6, - 25.7, - 25.7, - 25.9, - 26, - 26.2, - 26.4, - 26.6, - 27, - 27, - 27.1, - 27.2, - 27.3, - 27.4, - 27.6, - 27.8, - 28.2, - 28.4, - 28.4, - 28.4, - 28.4, - 28.4, - 28.4, - 28.4, - 28.4, - 28.5, - 28.5, - 28.6, - 28.8, - 29, - 29.2, - 29.3, - 29.4, - 29.5, - 29.6, - 29.5, - 29.5, - 29.6, - 29.6, - 29.8, - 29.8, - 29.8, - 29.8, - 29.9, - 30, - 30.1, - 30.2, - 30.3, - 30.4, - 30.6, - 30.6, - 30.7, - 30.8, - 30.9, - 31.1, - 31.3, - 31.4, - 31.5, - 31.6, - 31.8, - 31.9, - 32.1, - 32.3, - 32.4, - 32.6, - 32.7, - 33, - 33.4, - 33.9, - 34.2, - 34.4, - 34.7, - 34.9, - 35.2, - 35.5, - 35.8, - 36.1, - 36.4, - 36.6, - 36.8, - 37, - 37.2, - 37.3, - 37.5, - 37.7, - 37.9, - 38.1, - 38.2, - 38.3, - 38.5, - 38.6, - 38.6, - 38.7, - 38.8, - 38.8, - 38.9, - 39, - 39, - 38.9, - 39, - 39, - 39.1, - 39.1, - 39.1, - 17.5, - 18.6, - 19.1, - 19.9, - 20.8, - 21.6, - 22.3, - 22.9, - 23.5, - 24.1, - 24.7, - 25.4, - 26, - 26.5, - 27, - 27.5, - 28, - 28.3, - 28.6, - 28.9, - 29.1, - 29.2, - 29.3, - 29.4, - 29.5, - 29.6, - 29.7, - 30, - 29.9, - 29.5, - 28.7, - 28.1, - 27.5, - 26.9, - 26.3, - 25.7, - 25.1, - 24.4, - 23.8, - 23.3, - 22.8, - 22.5, - 22.2, - 21.9, - 21.6, - 21.3, - 21.1, - 20.8, - 20.5, - 20.1, - 19.9, - 19.7, - 19.6, - 19.5, - 19.3, - 19.2, - 19.2, - 19.3, - 19.5, - 19.6, - 19.6, - 19.5, - 19.5, - 19.7, - 20, - 19.6, - 19.2, - 18.7, - 18.2, - 17.8, - 17.6, - 17.5, - 17.4, - 17.3, - 17.3, - 17.6, - 17.7, - 17.9, - 18.1, - 18.1, - 18.2, - 18.1, - 17.9, - 17.8, - 18, - 18.3, - 18.6, - 18.9, - 19.5, - 20, - 20, - 20.1, - 20.3, - 20.4, - 20.5, - 20.8, - 21.1, - 21.1, - 21.2, - 21.5, - 21.6, - 21.6, - 21.6, - 21.7, - 22, - 22.4, - 22.6, - 22.9, - 23.2, - 23.4, - 23.7, - 23.9, - 24.1, - 24.2, - 24.3, - 24.3, - 24.3, - 24.4, - 24.5, - 24.6, - 24.5, - 24.4, - 24.2, - 24.2, - 24.3, - 24.3, - 24.5, - 24.6, - 24.8, - 25.2, - 25.4, - 25.5, - 25.8, - 26, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26, - 26.1, - 26.1, - 26.1, - 26.2, - 26.2, - 26.2, - 26.2, - 26.2, - 26.1, - 25.9, - 25.8, - 25.8, - 25.8, - 25.9, - 26, - 26.2, - 26.4, - 26.6, - 26.8, - 27, - 27.1, - 27.2, - 27.4, - 27.5, - 27.7, - 28, - 28.2, - 28.3, - 28.3, - 28.3, - 28.3, - 28.3, - 28.3, - 28.3, - 28.3, - 28.3, - 28.4, - 28.5, - 28.8, - 29, - 29.2, - 29.5, - 29.4, - 29.1, - 29.2, - 29.2, - 29.1, - 29.2, - 29.3, - 29.3, - 29.4, - 29.5, - 29.5, - 29.6, - 29.7, - 29.8, - 29.9, - 30.1, - 30.2, - 30.3, - 30.4, - 30.5, - 30.6, - 30.8, - 31, - 31.1, - 31.3, - 31.4, - 31.5, - 31.7, - 31.9, - 32, - 32.1, - 32.3, - 32.4, - 32.7, - 33.2, - 33.7, - 34, - 34.3, - 34.5, - 34.7, - 34.9, - 35.2, - 35.6, - 35.8, - 36.1, - 36.4, - 36.5, - 36.7, - 36.8, - 37.1, - 37.3, - 37.3, - 37.5, - 37.7, - 37.9, - 38.1, - 38.2, - 38.3, - 38.4, - 38.4, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.7, - 38.7, - 38.7, - 38.7, - 38.8, - 17.6, - 18.9, - 19.4, - 20, - 20.9, - 21.8, - 22.6, - 23.2, - 23.8, - 24.4, - 24.9, - 25.7, - 26.3, - 26.8, - 27.4, - 27.8, - 28.3, - 28.6, - 28.9, - 29.2, - 29.4, - 29.5, - 29.6, - 29.7, - 29.8, - 29.9, - 30, - 30.3, - 30.3, - 30, - 29.6, - 29.1, - 28.5, - 28, - 27.4, - 26.8, - 26.2, - 25.5, - 24.9, - 24.5, - 24, - 23.5, - 23.1, - 22.7, - 22.3, - 22, - 21.7, - 21.4, - 21, - 20.6, - 20.3, - 20, - 19.9, - 19.8, - 19.6, - 19.4, - 19.3, - 19.4, - 19.5, - 19.6, - 19.7, - 19.7, - 19.7, - 19.8, - 19.9, - 19.8, - 19.8, - 19.5, - 19.2, - 18.8, - 18.7, - 18.5, - 18.3, - 18.2, - 18.3, - 18.4, - 18.5, - 18.7, - 18.9, - 19.1, - 19.2, - 19, - 18.8, - 18.7, - 18.8, - 19.1, - 19.3, - 19.5, - 19.9, - 20.3, - 20.5, - 20.8, - 21.1, - 21.3, - 21.4, - 21.7, - 21.8, - 21.9, - 22, - 22.3, - 22.3, - 22.3, - 22.4, - 22.5, - 22.7, - 23, - 23.3, - 23.5, - 23.7, - 24, - 24.2, - 24.4, - 24.6, - 24.6, - 24.6, - 24.5, - 24.6, - 24.8, - 24.9, - 25, - 24.9, - 24.8, - 24.7, - 24.6, - 24.6, - 24.7, - 24.8, - 24.8, - 25, - 25.2, - 25.4, - 25.6, - 25.9, - 26, - 26, - 26.1, - 26.2, - 26.2, - 26.2, - 26.1, - 26.1, - 26.1, - 26, - 26, - 26.1, - 26.2, - 26.2, - 26.3, - 26.3, - 26.3, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 26, - 26.1, - 26.2, - 26.4, - 26.6, - 26.8, - 27, - 27.2, - 27.3, - 27.5, - 27.7, - 27.9, - 28.1, - 28.2, - 28.2, - 28.2, - 28.1, - 28.2, - 28.2, - 28.2, - 28.2, - 28.2, - 28.3, - 28.5, - 28.6, - 28.8, - 28.9, - 29, - 29.3, - 29.1, - 28.9, - 28.8, - 28.8, - 28.9, - 28.9, - 28.9, - 29, - 29.1, - 29.2, - 29.2, - 29.3, - 29.4, - 29.5, - 29.7, - 29.8, - 30, - 30.1, - 30.3, - 30.4, - 30.5, - 30.7, - 30.8, - 30.9, - 31.1, - 31.2, - 31.4, - 31.5, - 31.7, - 31.9, - 32, - 32.1, - 32.2, - 32.5, - 33.1, - 33.7, - 34, - 34.3, - 34.5, - 34.7, - 34.9, - 35.1, - 35.5, - 35.7, - 36, - 36.2, - 36.3, - 36.5, - 36.8, - 37, - 37.2, - 37.3, - 37.4, - 37.5, - 37.7, - 37.9, - 38.1, - 38.1, - 38.1, - 38.2, - 38.4, - 38.4, - 38.3, - 38.3, - 38.3, - 38.3, - 38.3, - 38.4, - 38.4, - 38.4, - 38.5, - 38.5, - 18, - 19.1, - 19.5, - 20.2, - 21.1, - 22, - 22.8, - 23.4, - 24.1, - 24.7, - 25.3, - 25.9, - 26.6, - 27.1, - 27.6, - 28.1, - 28.5, - 28.9, - 29.2, - 29.5, - 29.6, - 29.8, - 29.9, - 30, - 30.1, - 30.3, - 30.4, - 30.5, - 30.6, - 30.5, - 30.3, - 30, - 29.4, - 29, - 28.5, - 27.8, - 27.2, - 26.6, - 26, - 25.5, - 25, - 24.5, - 24, - 23.5, - 23.1, - 22.7, - 22.4, - 22, - 21.5, - 21.1, - 20.7, - 20.4, - 20.3, - 20.1, - 19.9, - 19.7, - 19.5, - 19.4, - 19.5, - 19.6, - 19.8, - 19.8, - 19.9, - 19.9, - 20, - 20.1, - 20.1, - 20, - 19.8, - 19.6, - 19.4, - 19.3, - 19.2, - 19.1, - 19.1, - 19.1, - 19.2, - 19.6, - 19.8, - 19.8, - 19.9, - 19.8, - 19.8, - 19.9, - 19.8, - 19.8, - 20, - 20.3, - 20.6, - 20.9, - 21.1, - 21.4, - 21.7, - 21.9, - 22.2, - 22.6, - 22.8, - 22.9, - 22.9, - 23.1, - 23.1, - 23, - 23.1, - 23.4, - 23.6, - 23.7, - 23.9, - 24.2, - 24.4, - 24.6, - 24.7, - 24.9, - 25, - 25.1, - 25.1, - 25.1, - 25.1, - 25.2, - 25.3, - 25.4, - 25.4, - 25.2, - 25.1, - 25, - 25, - 25, - 25, - 25.1, - 25.2, - 25.3, - 25.5, - 25.6, - 25.9, - 26, - 26.1, - 26.1, - 26.2, - 26.2, - 26.2, - 26.1, - 26.1, - 26, - 26, - 26, - 26.1, - 26.2, - 26.3, - 26.3, - 26.4, - 26.4, - 26.4, - 26.4, - 26.4, - 26.3, - 26.2, - 26.1, - 26.1, - 26.2, - 26.3, - 26.4, - 26.5, - 26.8, - 27, - 27.2, - 27.3, - 27.5, - 27.9, - 28.1, - 28.1, - 28.1, - 28.1, - 28, - 28, - 28.1, - 28.2, - 28.2, - 28.2, - 28.2, - 28.3, - 28.4, - 28.5, - 28.6, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.7, - 28.8, - 28.9, - 29, - 29.1, - 29.3, - 29.5, - 29.7, - 29.7, - 29.9, - 30.1, - 30.3, - 30.4, - 30.6, - 30.7, - 30.8, - 30.9, - 31.1, - 31.2, - 31.4, - 31.6, - 31.8, - 31.9, - 31.9, - 32.1, - 32.4, - 33, - 33.6, - 33.9, - 34.3, - 34.5, - 34.7, - 34.9, - 35, - 35.4, - 35.7, - 35.9, - 36, - 36.2, - 36.4, - 36.7, - 36.9, - 37, - 37.2, - 37.2, - 37.3, - 37.5, - 37.7, - 37.8, - 37.9, - 37.9, - 37.9, - 38, - 38, - 37.9, - 37.9, - 38, - 38, - 38, - 38, - 38.1, - 38.3, - 38.3, - 38.3, - 18.7, - 19.3, - 20, - 20.7, - 21.4, - 22.2, - 22.9, - 23.6, - 24.3, - 24.9, - 25.5, - 26.1, - 27, - 27.4, - 27.8, - 28.3, - 28.7, - 29.1, - 29.4, - 29.6, - 29.8, - 30, - 30.2, - 30.4, - 30.5, - 30.6, - 30.6, - 30.7, - 30.9, - 30.9, - 30.7, - 30.5, - 30.2, - 29.8, - 29.4, - 28.9, - 28.3, - 27.8, - 27.2, - 26.6, - 26, - 25.5, - 24.9, - 24.4, - 23.9, - 23.5, - 23.2, - 22.7, - 22.1, - 21.6, - 21.2, - 20.9, - 20.7, - 20.4, - 20.3, - 20, - 19.8, - 19.6, - 19.6, - 19.7, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.2, - 20.3, - 20.2, - 20.1, - 19.9, - 19.8, - 19.8, - 19.8, - 19.7, - 19.8, - 20.1, - 20.3, - 20.4, - 20.5, - 20.6, - 20.7, - 20.7, - 20.7, - 20.6, - 20.8, - 21, - 21.2, - 21.5, - 21.7, - 21.9, - 22.1, - 22.4, - 22.9, - 23.2, - 23.6, - 23.7, - 23.8, - 23.8, - 23.8, - 23.9, - 23.8, - 23.9, - 24.1, - 24.3, - 24.5, - 24.6, - 24.9, - 25.1, - 25.3, - 25.3, - 25.4, - 25.5, - 25.6, - 25.6, - 25.6, - 25.7, - 25.8, - 25.9, - 26, - 26.1, - 25.9, - 25.7, - 25.5, - 25.4, - 25.3, - 25.4, - 25.4, - 25.5, - 25.5, - 25.6, - 25.7, - 25.8, - 25.9, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26, - 25.9, - 26, - 26.1, - 26.2, - 26.3, - 26.4, - 26.4, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.4, - 26.3, - 26.3, - 26.4, - 26.5, - 26.5, - 26.7, - 26.9, - 27.2, - 27.5, - 27.7, - 28, - 28, - 28, - 27.9, - 27.8, - 27.8, - 27.8, - 27.9, - 28, - 28, - 28.1, - 28.2, - 28.2, - 28.3, - 28.3, - 28.4, - 28.4, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.4, - 28.4, - 28.4, - 28.4, - 28.5, - 28.5, - 28.6, - 28.7, - 28.8, - 28.9, - 29.1, - 29.2, - 29.3, - 29.5, - 29.7, - 29.8, - 30.1, - 30.3, - 30.5, - 30.5, - 30.6, - 30.7, - 30.9, - 31.1, - 31.3, - 31.5, - 31.7, - 31.8, - 31.9, - 32.1, - 32.4, - 32.9, - 33.4, - 33.8, - 34.1, - 34.4, - 34.6, - 34.8, - 34.9, - 35.2, - 35.5, - 35.7, - 35.8, - 36, - 36.2, - 36.5, - 36.6, - 36.8, - 37, - 37, - 37.1, - 37.3, - 37.4, - 37.5, - 37.6, - 37.6, - 37.5, - 37.5, - 37.6, - 37.6, - 37.6, - 37.6, - 37.6, - 37.7, - 37.8, - 37.9, - 38, - 38, - 38, - 18.8, - 19.7, - 20.6, - 21.1, - 21.7, - 22.4, - 23.1, - 23.8, - 24.4, - 25.1, - 25.7, - 26.4, - 27.2, - 27.5, - 27.9, - 28.4, - 28.9, - 29.3, - 29.6, - 29.8, - 30, - 30.2, - 30.4, - 30.6, - 30.7, - 30.8, - 30.9, - 31.2, - 31.4, - 31.4, - 31.2, - 31.1, - 30.9, - 30.5, - 30.1, - 29.8, - 29.4, - 28.9, - 28.5, - 28, - 27.2, - 26.5, - 25.9, - 25.4, - 24.8, - 24.3, - 23.9, - 23.4, - 22.8, - 22.2, - 21.7, - 21.4, - 21.1, - 20.8, - 20.6, - 20.4, - 20.2, - 20, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.3, - 20.4, - 20.4, - 20.5, - 20.5, - 20.5, - 20.4, - 20.4, - 20.4, - 20.4, - 20.5, - 20.5, - 20.7, - 20.8, - 20.9, - 21.1, - 21.3, - 21.6, - 21.8, - 21.8, - 21.7, - 21.6, - 21.8, - 21.9, - 22.1, - 22.3, - 22.6, - 22.8, - 23, - 23.2, - 23.6, - 24, - 24.4, - 24.6, - 24.6, - 24.6, - 24.7, - 24.7, - 24.7, - 24.8, - 24.8, - 25, - 25.1, - 25.2, - 25.5, - 25.7, - 25.8, - 25.8, - 25.9, - 26, - 26.1, - 26.1, - 26.1, - 26.2, - 26.3, - 26.4, - 26.6, - 26.6, - 26.5, - 26.3, - 26.1, - 26, - 25.9, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.9, - 26, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26, - 26, - 26, - 26.2, - 26.3, - 26.4, - 26.5, - 26.5, - 26.6, - 26.6, - 26.7, - 26.7, - 26.7, - 26.8, - 26.7, - 26.6, - 26.5, - 26.5, - 26.5, - 26.5, - 26.6, - 26.7, - 26.9, - 27.3, - 27.7, - 27.9, - 27.9, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.7, - 27.7, - 27.8, - 27.9, - 28, - 28, - 28, - 28.1, - 28.1, - 28.1, - 28.2, - 28.2, - 28.1, - 28, - 28.1, - 28.1, - 28.1, - 28.1, - 28.2, - 28.3, - 28.3, - 28.4, - 28.4, - 28.5, - 28.5, - 28.7, - 28.8, - 29, - 29.1, - 29.3, - 29.4, - 29.6, - 29.8, - 30.1, - 30.3, - 30.4, - 30.5, - 30.6, - 30.7, - 31, - 31.2, - 31.5, - 31.5, - 31.6, - 31.8, - 32.1, - 32.4, - 32.9, - 33.2, - 33.6, - 34, - 34.2, - 34.4, - 34.6, - 34.8, - 35.1, - 35.2, - 35.5, - 35.7, - 35.8, - 36.1, - 36.3, - 36.4, - 36.7, - 36.8, - 36.8, - 36.9, - 36.9, - 37, - 37.2, - 37.4, - 37.2, - 37.1, - 37.2, - 37.2, - 37.2, - 37.3, - 37.3, - 37.3, - 37.4, - 37.5, - 37.6, - 37.6, - 37.7, - 37.7, - 19.1, - 19.9, - 20.6, - 21.3, - 22, - 22.7, - 23.4, - 24, - 24.6, - 25.3, - 26, - 26.6, - 27.1, - 27.6, - 28.1, - 28.6, - 29, - 29.4, - 29.8, - 30.1, - 30.3, - 30.5, - 30.6, - 30.8, - 30.9, - 31, - 31.2, - 31.6, - 32, - 31.9, - 31.6, - 31.6, - 31.3, - 31.2, - 30.8, - 30.5, - 30.2, - 29.9, - 29.6, - 29.1, - 28.5, - 27.7, - 27.1, - 26.5, - 25.9, - 25.4, - 24.9, - 24.3, - 23.6, - 22.9, - 22.4, - 21.9, - 21.6, - 21.3, - 21, - 20.8, - 20.6, - 20.5, - 20.4, - 20.3, - 20.3, - 20.3, - 20.3, - 20.5, - 20.7, - 20.8, - 20.9, - 20.9, - 20.9, - 20.9, - 20.9, - 20.9, - 21, - 21.1, - 21.3, - 21.4, - 21.6, - 21.7, - 21.7, - 21.9, - 22.5, - 22.8, - 23, - 22.8, - 22.7, - 22.7, - 22.9, - 23, - 23.3, - 23.5, - 23.7, - 23.9, - 24, - 24.3, - 24.6, - 25, - 25.2, - 25.4, - 25.4, - 25.5, - 25.6, - 25.7, - 25.7, - 25.6, - 25.7, - 25.8, - 25.9, - 26.1, - 26.2, - 26.2, - 26.3, - 26.4, - 26.5, - 26.6, - 26.6, - 26.6, - 26.7, - 26.7, - 26.9, - 27, - 27, - 27, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26.2, - 26.2, - 26.1, - 26.1, - 26.1, - 26.1, - 26.1, - 26.2, - 26.3, - 26.4, - 26.5, - 26.6, - 26.6, - 26.7, - 26.7, - 26.7, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.7, - 26.6, - 26.6, - 26.6, - 26.6, - 26.7, - 26.8, - 27.1, - 27.6, - 27.8, - 27.9, - 27.9, - 27.8, - 27.6, - 27.5, - 27.5, - 27.5, - 27.6, - 27.6, - 27.7, - 27.8, - 27.9, - 27.8, - 27.8, - 27.9, - 27.9, - 27.9, - 27.9, - 27.9, - 27.8, - 27.7, - 27.8, - 27.8, - 27.8, - 27.8, - 27.9, - 28, - 28, - 28.1, - 28.1, - 28.2, - 28.3, - 28.4, - 28.6, - 28.8, - 29, - 29.1, - 29.2, - 29.4, - 29.7, - 30, - 30.1, - 30.3, - 30.4, - 30.5, - 30.6, - 30.8, - 30.9, - 31.1, - 31.3, - 31.5, - 31.9, - 32.1, - 32.5, - 32.9, - 33.3, - 33.5, - 33.9, - 34.2, - 34.3, - 34.5, - 34.7, - 34.9, - 35.2, - 35.4, - 35.6, - 35.7, - 36, - 36.2, - 36.2, - 36.4, - 36.5, - 36.5, - 36.6, - 36.7, - 36.8, - 36.8, - 36.9, - 36.8, - 36.8, - 36.8, - 36.9, - 36.9, - 36.9, - 37, - 37.1, - 37.1, - 37.2, - 37.2, - 37.3, - 37.4, - 37.5, - 19.2, - 20, - 20.8, - 21.8, - 22.6, - 23.1, - 23.7, - 24.4, - 25.3, - 26.2, - 26.5, - 26.8, - 27.3, - 27.9, - 28.3, - 28.7, - 29.2, - 29.6, - 30, - 30.4, - 30.6, - 30.8, - 30.9, - 31.1, - 31.3, - 31.4, - 31.5, - 31.6, - 31.9, - 32.1, - 32, - 31.9, - 31.8, - 31.6, - 31.4, - 31.1, - 30.9, - 30.7, - 30.4, - 30.1, - 29.6, - 29.1, - 28.7, - 28.1, - 27.3, - 26.5, - 25.9, - 25.2, - 24.5, - 23.7, - 23, - 22.5, - 22.1, - 21.7, - 21.4, - 21.2, - 21.1, - 21, - 20.9, - 20.8, - 20.7, - 20.6, - 20.7, - 20.9, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.3, - 21.4, - 21.5, - 21.7, - 21.8, - 22, - 22.2, - 22.4, - 22.6, - 22.7, - 22.8, - 23.2, - 23.5, - 23.9, - 24, - 23.7, - 23.7, - 23.9, - 24, - 24.2, - 24.4, - 24.5, - 24.7, - 24.9, - 25.1, - 25.3, - 25.6, - 25.8, - 26.1, - 26.2, - 26.3, - 26.4, - 26.5, - 26.5, - 26.5, - 26.6, - 26.6, - 26.6, - 26.6, - 26.7, - 26.7, - 26.8, - 26.9, - 26.9, - 27.1, - 27.2, - 27.2, - 27.1, - 27.1, - 27.2, - 27.3, - 27.3, - 27.3, - 27.4, - 27.3, - 27.1, - 26.9, - 26.7, - 26.6, - 26.5, - 26.5, - 26.5, - 26.5, - 26.4, - 26.4, - 26.4, - 26.4, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 26.7, - 26.8, - 26.8, - 26.9, - 26.9, - 26.9, - 26.9, - 27, - 26.9, - 26.9, - 26.9, - 26.8, - 26.7, - 26.7, - 26.7, - 26.8, - 27, - 27.4, - 27.7, - 27.8, - 27.8, - 27.7, - 27.6, - 27.4, - 27.3, - 27.3, - 27.4, - 27.4, - 27.5, - 27.7, - 27.8, - 27.6, - 27.6, - 27.6, - 27.6, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.5, - 27.6, - 27.5, - 27.5, - 27.5, - 27.6, - 27.7, - 27.7, - 27.8, - 27.9, - 28.1, - 28.2, - 28.4, - 28.6, - 28.8, - 29, - 29.1, - 29.3, - 29.6, - 29.8, - 30, - 30.2, - 30.3, - 30.4, - 30.5, - 30.6, - 30.7, - 30.9, - 31.2, - 31.4, - 31.8, - 32.1, - 32.5, - 32.8, - 33.2, - 33.4, - 33.7, - 34, - 34.2, - 34.4, - 34.6, - 34.8, - 35.2, - 35.4, - 35.5, - 35.5, - 35.8, - 35.9, - 36, - 36.1, - 36.2, - 36.3, - 36.4, - 36.4, - 36.5, - 36.5, - 36.4, - 36.4, - 36.5, - 36.6, - 36.6, - 36.5, - 36.6, - 36.6, - 36.7, - 36.7, - 36.8, - 36.9, - 37.1, - 37.2, - 37.3, - 19.7, - 20.3, - 21.2, - 22.3, - 23.1, - 23.3, - 24, - 25, - 26, - 26.6, - 26.7, - 27, - 27.4, - 28, - 28.4, - 28.9, - 29.3, - 29.8, - 30.3, - 30.6, - 30.9, - 31.1, - 31.3, - 31.5, - 31.7, - 31.8, - 31.9, - 31.9, - 32.2, - 32.3, - 32.3, - 32.3, - 32.3, - 32.1, - 31.9, - 31.7, - 31.6, - 31.3, - 31, - 30.8, - 30.4, - 30.1, - 29.7, - 29.2, - 28.5, - 27.7, - 27, - 26.3, - 25.4, - 24.5, - 23.8, - 23.2, - 22.7, - 22.3, - 21.9, - 21.6, - 21.4, - 21.3, - 21.3, - 21.1, - 21, - 20.9, - 21.1, - 21.3, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.8, - 22, - 22.2, - 22.5, - 22.6, - 22.7, - 22.9, - 23.1, - 23.5, - 23.9, - 24, - 23.9, - 24.3, - 25, - 25.1, - 24.8, - 24.7, - 24.9, - 25.1, - 25.3, - 25.3, - 25.4, - 25.5, - 25.6, - 25.8, - 26, - 26.3, - 26.5, - 26.7, - 27.1, - 27.2, - 27.2, - 27.3, - 27.3, - 27.4, - 27.4, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.3, - 27.4, - 27.6, - 27.8, - 27.8, - 27.8, - 27.6, - 27.6, - 27.6, - 27.7, - 27.7, - 27.7, - 27.7, - 27.5, - 27.3, - 27.1, - 27, - 26.9, - 26.9, - 26.8, - 26.8, - 26.8, - 26.8, - 26.7, - 26.7, - 26.6, - 26.6, - 26.5, - 26.5, - 26.5, - 26.6, - 26.7, - 26.8, - 26.8, - 26.9, - 26.9, - 26.9, - 27, - 27, - 27, - 27, - 27, - 27, - 27, - 27, - 26.9, - 26.9, - 26.9, - 26.9, - 27, - 27.4, - 27.6, - 27.7, - 27.7, - 27.6, - 27.4, - 27.3, - 27.2, - 27.2, - 27.3, - 27.3, - 27.3, - 27.4, - 27.6, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.3, - 27.4, - 27.4, - 27.5, - 27.6, - 27.7, - 27.9, - 28, - 28.2, - 28.4, - 28.6, - 28.8, - 28.9, - 29.1, - 29.4, - 29.7, - 29.9, - 30.2, - 30.2, - 30.3, - 30.4, - 30.5, - 30.6, - 30.7, - 31, - 31.4, - 31.8, - 32.2, - 32.6, - 32.8, - 32.9, - 33.2, - 33.4, - 33.7, - 33.9, - 34.2, - 34.5, - 34.6, - 35, - 35.3, - 35.4, - 35.4, - 35.6, - 35.7, - 35.7, - 35.8, - 35.9, - 36.2, - 36.2, - 36.2, - 36.1, - 36.1, - 36.1, - 36.2, - 36.2, - 36.4, - 36.4, - 36.3, - 36.3, - 36.3, - 36.4, - 36.5, - 36.6, - 36.7, - 36.8, - 36.9, - 37.1, - 20.2, - 20.8, - 21.5, - 22.3, - 23, - 23.5, - 24.2, - 25.1, - 25.8, - 26.2, - 26.7, - 27.2, - 27.8, - 28.1, - 28.6, - 29.1, - 29.6, - 30.1, - 30.5, - 30.9, - 31.1, - 31.3, - 31.6, - 31.9, - 32.3, - 32.3, - 32.3, - 32.4, - 32.7, - 32.6, - 32.7, - 32.7, - 32.7, - 32.6, - 32.4, - 32.2, - 32.1, - 31.9, - 31.7, - 31.4, - 31.1, - 30.8, - 30.4, - 30, - 29.5, - 28.9, - 28.3, - 27.6, - 26.5, - 25.5, - 24.6, - 23.9, - 23.4, - 22.9, - 22.5, - 22.1, - 21.8, - 21.7, - 21.6, - 21.5, - 21.3, - 21.3, - 21.6, - 21.8, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.4, - 22.6, - 22.9, - 23.2, - 23.3, - 23.4, - 23.6, - 23.9, - 24.2, - 24.8, - 25, - 24.8, - 25.1, - 25.6, - 25.7, - 25.6, - 25.8, - 26, - 26.2, - 26.3, - 26.3, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 26.9, - 27, - 27.3, - 27.9, - 28, - 28, - 28.1, - 28, - 28.1, - 28.1, - 27.9, - 27.8, - 27.7, - 27.7, - 27.7, - 27.7, - 27.8, - 27.9, - 28.1, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 28, - 27.9, - 28, - 27.9, - 27.7, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27.1, - 27.1, - 27, - 27, - 27, - 26.9, - 26.9, - 26.8, - 26.8, - 26.8, - 26.8, - 26.9, - 26.9, - 27.1, - 27, - 27.1, - 27.1, - 27.1, - 27, - 27, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27, - 27, - 27, - 27.1, - 27.4, - 27.6, - 27.7, - 27.6, - 27.5, - 27.2, - 27.1, - 27, - 27, - 27, - 27.1, - 27.2, - 27.2, - 27.2, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27.1, - 27, - 27, - 27, - 27, - 27.1, - 27, - 27, - 27, - 27.1, - 27.2, - 27.3, - 27.4, - 27.6, - 27.7, - 27.8, - 27.9, - 28.1, - 28.2, - 28.4, - 28.6, - 28.8, - 29, - 29.1, - 29.4, - 29.8, - 29.9, - 30.1, - 30.2, - 30.2, - 30.2, - 30.4, - 30.6, - 30.9, - 31.4, - 31.9, - 32.3, - 32.5, - 32.7, - 32.9, - 33.1, - 33.3, - 33.5, - 33.8, - 34.1, - 34.3, - 34.5, - 34.8, - 35.1, - 35.2, - 35.3, - 35.3, - 35.4, - 35.5, - 35.6, - 35.7, - 35.8, - 35.8, - 35.9, - 35.8, - 35.8, - 35.8, - 35.9, - 36, - 36.1, - 36.1, - 36.1, - 36.1, - 36.2, - 36.2, - 36.3, - 36.4, - 36.5, - 36.6, - 36.7, - 36.8, - 20.3, - 21.1, - 21.7, - 22.3, - 23.1, - 23.7, - 24.3, - 25.1, - 25.9, - 26.4, - 27, - 27.6, - 28.1, - 28.3, - 28.8, - 29.4, - 29.9, - 30.3, - 30.7, - 31.1, - 31.4, - 31.6, - 31.9, - 32.1, - 32.4, - 32.5, - 32.7, - 32.9, - 33.1, - 33.1, - 33.2, - 33.2, - 33.1, - 33, - 32.9, - 32.7, - 32.6, - 32.4, - 32.2, - 31.9, - 31.7, - 31.4, - 31.1, - 30.7, - 30.2, - 29.7, - 29.2, - 28.5, - 27.6, - 26.5, - 25.5, - 24.7, - 24.1, - 23.6, - 23.1, - 22.6, - 22.2, - 22.1, - 22, - 21.8, - 21.7, - 21.8, - 22, - 22.2, - 22.4, - 22.5, - 22.6, - 22.7, - 22.8, - 23, - 23.3, - 23.5, - 23.8, - 24, - 24.2, - 24.4, - 24.7, - 24.9, - 25.3, - 25.6, - 25.8, - 25.9, - 26.1, - 26.2, - 26.5, - 26.9, - 27.1, - 27.1, - 27.2, - 27.3, - 27.3, - 27.3, - 27.4, - 27.5, - 27.5, - 27.6, - 27.7, - 27.9, - 28.3, - 28.5, - 28.6, - 28.7, - 28.7, - 28.7, - 28.6, - 28.5, - 28.3, - 28.2, - 28.1, - 28.1, - 28.2, - 28.3, - 28.4, - 28.5, - 28.7, - 28.7, - 28.6, - 28.6, - 28.5, - 28.3, - 28.3, - 28.3, - 28.2, - 28.2, - 28.1, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 27.1, - 27, - 27, - 27, - 27.1, - 27.1, - 27.1, - 27.1, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.2, - 27.4, - 27.6, - 27.6, - 27.6, - 27.4, - 27.2, - 27, - 26.9, - 26.8, - 26.9, - 26.9, - 26.9, - 27, - 27, - 27.1, - 27.1, - 27.1, - 27.1, - 27, - 26.9, - 26.9, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.9, - 27.1, - 27.2, - 27.3, - 27.5, - 27.6, - 27.7, - 27.8, - 28, - 28.1, - 28.2, - 28.5, - 28.7, - 28.8, - 29, - 29.1, - 29.5, - 29.7, - 30, - 30.1, - 30.1, - 30.2, - 30.2, - 30.5, - 30.7, - 31.3, - 31.8, - 32.2, - 32.4, - 32.6, - 32.8, - 32.9, - 33.2, - 33.5, - 33.7, - 33.9, - 34.1, - 34.3, - 34.6, - 34.8, - 34.9, - 35.1, - 35.1, - 35.1, - 35.3, - 35.5, - 35.5, - 35.4, - 35.4, - 35.5, - 35.4, - 35.5, - 35.6, - 35.6, - 35.8, - 35.8, - 35.8, - 35.9, - 35.9, - 36, - 36, - 36, - 36.1, - 36.2, - 36.4, - 36.5, - 36.6, - 20.5, - 21.3, - 21.9, - 22.5, - 23.2, - 23.9, - 24.5, - 25.2, - 25.9, - 26.5, - 27, - 27.7, - 28.2, - 28.6, - 29.1, - 29.7, - 30.1, - 30.7, - 31, - 31.3, - 31.6, - 32, - 32.6, - 32.7, - 32.7, - 32.8, - 33, - 33.2, - 33.3, - 33.4, - 33.5, - 33.5, - 33.5, - 33.4, - 33.3, - 33.1, - 33.1, - 32.9, - 32.7, - 32.4, - 32.2, - 31.9, - 31.6, - 31.2, - 30.8, - 30.3, - 29.8, - 29.2, - 28.4, - 27.4, - 26.4, - 25.5, - 24.8, - 24.2, - 23.6, - 23.1, - 22.8, - 22.6, - 22.4, - 22.3, - 22.3, - 22.4, - 22.6, - 22.9, - 23, - 23.1, - 23.1, - 23.3, - 23.4, - 23.7, - 23.9, - 24.2, - 24.4, - 24.7, - 25, - 25.3, - 25.6, - 25.8, - 26, - 26.3, - 26.6, - 27.2, - 27.2, - 27.2, - 27.4, - 27.8, - 27.9, - 28, - 28, - 28.1, - 28.2, - 28.4, - 28.4, - 28.4, - 28.3, - 28.3, - 28.4, - 28.5, - 28.7, - 28.8, - 28.9, - 29, - 29.1, - 29.3, - 29.1, - 29, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 28.7, - 28.8, - 28.8, - 28.8, - 28.9, - 28.9, - 28.8, - 28.8, - 28.7, - 28.6, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 27.8, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.3, - 27.4, - 27.5, - 27.6, - 27.6, - 27.5, - 27.1, - 26.8, - 26.8, - 26.7, - 26.7, - 26.7, - 26.8, - 26.8, - 26.8, - 26.8, - 26.8, - 26.9, - 26.8, - 26.8, - 26.7, - 26.7, - 26.7, - 26.6, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.6, - 26.6, - 26.7, - 26.8, - 27, - 27.1, - 27.3, - 27.4, - 27.6, - 27.7, - 27.9, - 28, - 28.2, - 28.4, - 28.6, - 28.7, - 28.8, - 29.1, - 29.2, - 29.5, - 29.7, - 29.9, - 30, - 30, - 30.1, - 30.4, - 30.7, - 31.1, - 31.6, - 31.9, - 32.1, - 32.4, - 32.6, - 32.8, - 33.1, - 33.3, - 33.4, - 33.7, - 33.9, - 34, - 34.2, - 34.4, - 34.6, - 34.8, - 34.8, - 34.8, - 35.1, - 35.2, - 35.1, - 35.1, - 35.1, - 35.1, - 35.2, - 35.2, - 35.3, - 35.4, - 35.5, - 35.5, - 35.5, - 35.6, - 35.6, - 35.7, - 35.8, - 35.9, - 36, - 36.1, - 36.2, - 36.4, - 36.5, - 20.7, - 21.4, - 22, - 22.7, - 23.5, - 24.1, - 24.7, - 25.4, - 26, - 26.6, - 27.1, - 27.8, - 28.4, - 28.9, - 29.4, - 30, - 30.5, - 30.9, - 31.2, - 31.6, - 31.8, - 32.3, - 33, - 33.2, - 33, - 33, - 33.2, - 33.4, - 33.6, - 33.7, - 33.6, - 33.7, - 33.9, - 33.8, - 33.7, - 33.5, - 33.6, - 33.4, - 33.1, - 32.9, - 32.7, - 32.4, - 32.1, - 31.8, - 31.4, - 30.9, - 30.5, - 29.9, - 29.2, - 28.3, - 27.2, - 26.2, - 25.4, - 24.7, - 24.2, - 23.8, - 23.5, - 23.1, - 22.9, - 22.7, - 22.7, - 22.9, - 23.2, - 23.5, - 23.7, - 23.7, - 23.7, - 23.9, - 24.1, - 24.4, - 24.6, - 24.9, - 25.2, - 25.5, - 25.8, - 26.2, - 26.5, - 26.8, - 27, - 27.2, - 27.4, - 28.2, - 28.3, - 28.2, - 28.4, - 28.6, - 28.7, - 28.8, - 28.9, - 29, - 29.4, - 29.5, - 29.3, - 29.2, - 29.1, - 29, - 29.1, - 29.1, - 29.2, - 29.3, - 29.7, - 29.9, - 29.9, - 29.9, - 29.8, - 29.5, - 29.3, - 29.4, - 29.4, - 29.2, - 29.1, - 29.1, - 29.1, - 29.1, - 29.2, - 29.1, - 29.1, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.8, - 27.8, - 27.8, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.4, - 27.4, - 27.4, - 27.5, - 27.5, - 27.5, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.5, - 27.5, - 27.6, - 27.5, - 27.4, - 27.2, - 26.8, - 26.6, - 26.6, - 26.6, - 26.6, - 26.6, - 26.6, - 26.6, - 26.6, - 26.6, - 26.6, - 26.7, - 26.6, - 26.5, - 26.5, - 26.5, - 26.4, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.4, - 26.5, - 26.5, - 26.6, - 26.7, - 26.9, - 27, - 27.3, - 27.5, - 27.5, - 27.7, - 27.9, - 28.1, - 28.4, - 28.6, - 28.7, - 28.7, - 28.9, - 29, - 29.1, - 29.4, - 29.8, - 29.9, - 30, - 30.1, - 30.3, - 30.5, - 30.9, - 31.2, - 31.6, - 31.9, - 32.2, - 32.4, - 32.6, - 32.8, - 33, - 33.2, - 33.4, - 33.6, - 33.8, - 34, - 34.2, - 34.3, - 34.4, - 34.5, - 34.6, - 34.6, - 34.7, - 34.8, - 34.8, - 34.9, - 35, - 35, - 34.9, - 35, - 35, - 35.1, - 35.1, - 35.3, - 35.4, - 35.5, - 35.6, - 35.6, - 35.8, - 35.9, - 36, - 36.1, - 36.3, - 36.4, - 20.9, - 21.5, - 22.1, - 22.9, - 23.6, - 24.2, - 24.9, - 25.5, - 26.2, - 26.9, - 27.6, - 28.3, - 28.8, - 29.4, - 30, - 30.5, - 31, - 31.2, - 31.6, - 31.8, - 32.1, - 32.4, - 33.1, - 33.3, - 33.1, - 33.3, - 33.5, - 33.7, - 33.9, - 34, - 33.9, - 34, - 34.1, - 34, - 34, - 34, - 33.9, - 33.7, - 33.5, - 33.3, - 33.1, - 32.9, - 32.6, - 32.3, - 32, - 31.6, - 31.1, - 30.6, - 30, - 29.1, - 28.1, - 27, - 26.1, - 25.4, - 25, - 24.6, - 24.2, - 23.8, - 23.5, - 23.4, - 23.5, - 23.8, - 23.9, - 24.2, - 24.4, - 24.5, - 24.6, - 24.7, - 25, - 25.2, - 25.4, - 25.7, - 26, - 26.3, - 26.7, - 27, - 27.3, - 27.6, - 27.8, - 27.9, - 28.2, - 29, - 29, - 29.1, - 29.2, - 29.4, - 29.6, - 29.6, - 29.7, - 29.9, - 30.1, - 30.1, - 29.9, - 29.9, - 29.8, - 29.7, - 29.7, - 29.8, - 29.8, - 29.8, - 30.1, - 30.5, - 30.7, - 30.6, - 30.6, - 30.4, - 30, - 29.9, - 29.9, - 29.8, - 29.7, - 29.6, - 29.6, - 29.5, - 29.6, - 29.5, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28.1, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 27.7, - 27.7, - 27.6, - 27.6, - 27.6, - 27.6, - 27.6, - 27.7, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.4, - 27.4, - 27.5, - 27.5, - 27.6, - 27.6, - 27.5, - 27.3, - 27.1, - 26.9, - 26.7, - 26.6, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.6, - 26.5, - 26.4, - 26.4, - 26.2, - 26.2, - 26.1, - 26.1, - 26, - 26, - 26, - 26.1, - 26.1, - 26.1, - 26.2, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 26.9, - 27, - 27.3, - 27.4, - 27.5, - 27.7, - 28, - 28.3, - 28.5, - 28.6, - 28.7, - 28.9, - 28.9, - 28.9, - 29.2, - 29.6, - 29.9, - 29.9, - 30, - 30.2, - 30.4, - 30.7, - 31, - 31.3, - 31.7, - 32, - 32.2, - 32.5, - 32.6, - 32.8, - 33, - 33.2, - 33.4, - 33.5, - 33.7, - 33.9, - 34.1, - 34.1, - 34.3, - 34.4, - 34.4, - 34.5, - 34.5, - 34.5, - 34.6, - 34.7, - 34.8, - 34.7, - 34.7, - 34.7, - 34.8, - 35, - 35.2, - 35.3, - 35.4, - 35.5, - 35.5, - 35.5, - 35.6, - 35.8, - 35.9, - 36.1, - 36.2, - 21.1, - 21.8, - 22.4, - 23.1, - 23.7, - 24.5, - 25.3, - 26, - 26.6, - 27.4, - 28.1, - 28.7, - 29.2, - 29.9, - 30.4, - 30.9, - 31.3, - 31.5, - 31.9, - 32.1, - 32.4, - 33, - 33.5, - 33.6, - 33.5, - 33.6, - 33.8, - 33.9, - 34.2, - 34.3, - 34.3, - 34.2, - 34.3, - 34.3, - 34.4, - 34.4, - 34.2, - 34.1, - 33.8, - 33.6, - 33.5, - 33.2, - 33, - 32.7, - 32.5, - 32.1, - 31.7, - 31.1, - 30.5, - 29.8, - 28.9, - 27.8, - 26.9, - 26.2, - 25.7, - 25.3, - 24.9, - 24.5, - 24.2, - 24.2, - 24.4, - 24.6, - 24.8, - 24.9, - 25.1, - 25.2, - 25.3, - 25.4, - 25.6, - 25.9, - 26.2, - 26.4, - 26.7, - 27.1, - 27.4, - 27.8, - 28.1, - 28.4, - 28.5, - 28.8, - 29, - 29.5, - 29.6, - 29.7, - 29.8, - 30.1, - 30.3, - 30.3, - 30.4, - 30.4, - 30.5, - 30.6, - 30.5, - 30.6, - 30.4, - 30.3, - 30.3, - 30.3, - 30.3, - 30.4, - 30.3, - 30.5, - 30.8, - 31.1, - 31.6, - 31.2, - 30.6, - 30.5, - 30.5, - 30.6, - 30.5, - 30.2, - 30.1, - 30, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.4, - 29.4, - 29.3, - 29.1, - 29, - 28.9, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.2, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.8, - 27.8, - 27.7, - 27.7, - 27.7, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.5, - 27.6, - 27.6, - 27.6, - 27.5, - 27.3, - 27.1, - 26.8, - 26.7, - 26.6, - 26.5, - 26.5, - 26.4, - 26.4, - 26.4, - 26.4, - 26.4, - 26.3, - 26.4, - 26.4, - 26.4, - 26.2, - 26.2, - 26.1, - 26, - 26, - 26, - 26, - 25.9, - 25.9, - 25.9, - 25.9, - 26, - 26, - 26.1, - 26.1, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 26.9, - 27.1, - 27.2, - 27.4, - 27.6, - 27.9, - 28.2, - 28.4, - 28.5, - 28.7, - 28.9, - 28.9, - 28.8, - 29, - 29.5, - 29.9, - 29.9, - 29.9, - 30, - 30.2, - 30.5, - 30.9, - 31.1, - 31.4, - 31.6, - 31.9, - 32.2, - 32.4, - 32.6, - 32.8, - 33, - 33.2, - 33.4, - 33.5, - 33.6, - 33.8, - 33.9, - 34, - 34.1, - 34.2, - 34.2, - 34.3, - 34.4, - 34.4, - 34.5, - 34.5, - 34.6, - 34.7, - 34.7, - 34.7, - 34.9, - 35, - 35.1, - 35.2, - 35.2, - 35.3, - 35.3, - 35.4, - 35.6, - 35.7, - 35.8, - 36, - 21.4, - 22, - 22.6, - 23.3, - 24.1, - 25, - 25.8, - 26.4, - 27, - 27.7, - 28.3, - 28.9, - 29.5, - 30.1, - 30.8, - 31.2, - 31.5, - 31.9, - 32.2, - 32.4, - 32.7, - 33.3, - 33.8, - 34.1, - 33.9, - 34, - 34.1, - 34.3, - 34.5, - 34.5, - 34.7, - 34.6, - 34.6, - 34.6, - 34.6, - 34.5, - 34.4, - 34.3, - 34.1, - 33.9, - 33.8, - 33.6, - 33.3, - 33.1, - 32.9, - 32.6, - 32.2, - 31.7, - 31.1, - 30.4, - 29.5, - 28.5, - 27.6, - 27, - 26.5, - 26, - 25.7, - 25.4, - 25, - 24.9, - 25.1, - 25.3, - 25.4, - 25.5, - 25.7, - 25.9, - 26.1, - 26.2, - 26.4, - 26.6, - 27, - 27.2, - 27.5, - 27.9, - 28.2, - 28.6, - 28.9, - 29.1, - 29.6, - 30, - 29.9, - 30.1, - 30.2, - 30.3, - 30.5, - 30.8, - 31, - 31, - 31.2, - 31.2, - 31.4, - 31.3, - 31.1, - 31.1, - 30.9, - 30.8, - 30.8, - 30.8, - 30.8, - 30.9, - 30.9, - 31, - 31.1, - 31.2, - 31.6, - 31.5, - 31.1, - 31, - 31, - 31, - 30.9, - 30.8, - 30.7, - 30.5, - 30.4, - 30.4, - 30.3, - 30.2, - 30.1, - 29.9, - 29.8, - 29.6, - 29.5, - 29.4, - 29.3, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.8, - 27.8, - 27.8, - 27.7, - 27.6, - 27.6, - 27.5, - 27.6, - 27.6, - 27.7, - 27.7, - 27.6, - 27.4, - 27.1, - 26.9, - 26.6, - 26.5, - 26.5, - 26.4, - 26.4, - 26.4, - 26.4, - 26.3, - 26.4, - 26.3, - 26.2, - 26.2, - 26.2, - 26.1, - 26.1, - 26, - 26, - 25.9, - 25.9, - 25.9, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.9, - 25.9, - 26, - 26, - 26.1, - 26.2, - 26.4, - 26.5, - 26.6, - 26.8, - 26.9, - 27, - 27.2, - 27.4, - 27.6, - 27.9, - 28.1, - 28.4, - 28.6, - 28.8, - 28.9, - 28.8, - 28.9, - 29.1, - 29.3, - 29.5, - 29.6, - 29.8, - 30, - 30.3, - 30.7, - 30.9, - 31.2, - 31.4, - 31.7, - 32, - 32.2, - 32.5, - 32.7, - 32.8, - 33, - 33.2, - 33.3, - 33.4, - 33.5, - 33.6, - 33.7, - 33.8, - 33.8, - 34, - 34.1, - 34.2, - 34.2, - 34.2, - 34.3, - 34.5, - 34.6, - 34.7, - 34.7, - 34.7, - 34.7, - 34.8, - 34.8, - 34.9, - 35, - 35.2, - 35.3, - 35.4, - 35.5, - 35.7, - 35.8, - 21.6, - 22.2, - 22.8, - 23.6, - 24.4, - 25.2, - 25.9, - 26.7, - 27.3, - 27.8, - 28.4, - 29.1, - 29.7, - 30.3, - 30.8, - 31.4, - 31.8, - 32.2, - 32.5, - 32.8, - 33, - 33.2, - 33.6, - 34, - 34, - 34.2, - 34.5, - 34.7, - 34.6, - 34.6, - 34.7, - 34.8, - 34.8, - 34.8, - 34.7, - 34.7, - 34.7, - 34.6, - 34.4, - 34.2, - 34.1, - 33.8, - 33.6, - 33.5, - 33.2, - 32.9, - 32.6, - 32.3, - 31.8, - 31.1, - 30.3, - 29.3, - 28.4, - 27.8, - 27.2, - 26.8, - 26.5, - 26.2, - 25.8, - 25.6, - 25.8, - 26, - 26.1, - 26.1, - 26.4, - 26.7, - 26.9, - 27, - 27.1, - 27.4, - 27.8, - 28, - 28.3, - 28.6, - 28.9, - 29.3, - 29.6, - 29.8, - 30.3, - 30.7, - 30.8, - 30.9, - 30.9, - 31.2, - 31.2, - 31.4, - 31.8, - 31.8, - 31.9, - 31.9, - 32, - 32, - 31.6, - 31.6, - 31.6, - 31.5, - 31.5, - 31.5, - 31.4, - 31.4, - 31.5, - 31.5, - 31.5, - 31.5, - 31.4, - 31.5, - 31.4, - 31.4, - 31.4, - 31.3, - 31.2, - 31.2, - 31.1, - 30.9, - 30.8, - 30.8, - 30.7, - 30.6, - 30.4, - 30.2, - 30.1, - 29.9, - 29.8, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.8, - 28.7, - 28.6, - 28.6, - 28.5, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 27.9, - 27.8, - 27.7, - 27.7, - 27.7, - 27.7, - 27.7, - 27.7, - 27.7, - 27.7, - 27.4, - 27.2, - 26.9, - 26.7, - 26.5, - 26.5, - 26.4, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.2, - 26.1, - 26, - 26, - 26, - 26, - 25.9, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.9, - 25.9, - 26, - 26.1, - 26.2, - 26.4, - 26.5, - 26.6, - 26.7, - 26.8, - 27, - 27.1, - 27.3, - 27.5, - 27.8, - 28, - 28.3, - 28.6, - 28.8, - 28.8, - 28.9, - 29, - 29.2, - 29.3, - 29.4, - 29.6, - 29.8, - 30.1, - 30.5, - 30.8, - 31, - 31.2, - 31.5, - 31.8, - 32, - 32.3, - 32.5, - 32.6, - 32.8, - 32.9, - 33, - 33.1, - 33.1, - 33.2, - 33.3, - 33.4, - 33.5, - 33.6, - 33.7, - 33.8, - 33.8, - 33.9, - 34.1, - 34.3, - 34.3, - 34.3, - 34.4, - 34.4, - 34.4, - 34.5, - 34.6, - 34.7, - 34.8, - 34.9, - 35.1, - 35.2, - 35.3, - 35.5, - 35.6, - 21.7, - 22.5, - 23.1, - 23.7, - 24.6, - 25.5, - 26.2, - 26.9, - 27.6, - 28, - 28.6, - 29.3, - 29.9, - 30.5, - 31, - 31.5, - 32, - 32.4, - 32.8, - 33.1, - 33.3, - 33.5, - 33.8, - 34, - 34.1, - 34.3, - 34.5, - 34.6, - 34.7, - 34.7, - 34.7, - 34.9, - 35, - 35, - 35, - 35, - 34.9, - 34.9, - 34.8, - 34.5, - 34.3, - 34.1, - 33.9, - 33.8, - 33.5, - 33.3, - 33, - 32.8, - 32.4, - 31.8, - 30.9, - 30.1, - 29.3, - 28.6, - 28, - 27.6, - 27.3, - 27, - 26.5, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 27.1, - 27.4, - 27.6, - 27.8, - 27.9, - 28.3, - 28.7, - 29, - 29.2, - 29.4, - 29.6, - 29.9, - 30.2, - 30.6, - 31.1, - 31.4, - 31.4, - 31.4, - 31.6, - 31.9, - 32, - 32.1, - 32.5, - 32.5, - 32.5, - 32.5, - 32.5, - 32.4, - 32.2, - 32.1, - 32.1, - 32.2, - 32.2, - 32.2, - 32.1, - 32, - 32, - 32, - 32, - 32, - 31.9, - 31.9, - 31.9, - 31.8, - 31.8, - 31.8, - 31.7, - 31.7, - 31.6, - 31.3, - 31.2, - 31.1, - 31, - 30.9, - 30.7, - 30.5, - 30.4, - 30.2, - 30.1, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.8, - 27.8, - 27.8, - 27.8, - 27.6, - 27.2, - 27, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.2, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.7, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.9, - 26, - 26.1, - 26.2, - 26.3, - 26.5, - 26.6, - 26.6, - 26.7, - 27, - 27.1, - 27.3, - 27.5, - 27.8, - 28.1, - 28.4, - 28.6, - 28.5, - 28.8, - 28.9, - 29.2, - 29.2, - 29.3, - 29.5, - 29.8, - 30, - 30.3, - 30.6, - 30.9, - 31.1, - 31.3, - 31.6, - 31.9, - 32, - 32.2, - 32.5, - 32.7, - 32.7, - 32.7, - 32.8, - 32.8, - 32.8, - 32.9, - 33.1, - 33.1, - 33.2, - 33.3, - 33.3, - 33.4, - 33.6, - 33.8, - 33.9, - 33.9, - 33.9, - 33.9, - 34, - 34.1, - 34.2, - 34.3, - 34.4, - 34.6, - 34.7, - 34.9, - 35, - 35.1, - 35.3, - 35.4, - 22.1, - 22.8, - 23.4, - 23.9, - 24.8, - 25.7, - 26.4, - 27, - 27.7, - 28.3, - 28.9, - 29.6, - 30.2, - 30.7, - 31.2, - 31.8, - 32.3, - 32.7, - 33.1, - 33.4, - 33.7, - 33.9, - 34, - 34.2, - 34.4, - 34.6, - 34.7, - 34.8, - 34.9, - 35, - 35, - 35.1, - 35.1, - 35.2, - 35.2, - 35.2, - 35.2, - 35.3, - 35, - 34.7, - 34.6, - 34.3, - 34.2, - 34, - 33.8, - 33.7, - 33.4, - 33.2, - 32.8, - 32.3, - 31.6, - 30.9, - 30.3, - 29.5, - 28.9, - 28.4, - 28, - 27.7, - 27.3, - 27, - 26.9, - 27, - 27.1, - 27.3, - 27.7, - 28, - 28.2, - 28.4, - 28.6, - 29, - 29.4, - 29.8, - 30.1, - 30.3, - 30.4, - 30.7, - 31.1, - 31.6, - 31.9, - 32, - 32.1, - 32.2, - 32.3, - 32.5, - 32.6, - 32.7, - 32.8, - 33, - 33, - 33, - 33, - 32.9, - 32.8, - 32.8, - 32.7, - 32.7, - 32.8, - 32.7, - 32.6, - 32.6, - 32.5, - 32.5, - 32.5, - 32.5, - 32.4, - 32.5, - 32.4, - 32.3, - 32.2, - 32.2, - 32.1, - 32, - 31.9, - 31.7, - 31.5, - 31.4, - 31.2, - 31.1, - 31, - 30.8, - 30.6, - 30.4, - 30.3, - 30.2, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.9, - 28, - 27.9, - 27.8, - 27.7, - 27.3, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.2, - 26.2, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.8, - 25.8, - 25.9, - 26.1, - 26.1, - 26.3, - 26.4, - 26.4, - 26.6, - 26.7, - 26.8, - 26.9, - 27.1, - 27.3, - 27.5, - 27.8, - 28.1, - 28.2, - 28.4, - 28.5, - 28.8, - 29.1, - 29.2, - 29.2, - 29.4, - 29.6, - 29.8, - 30.2, - 30.4, - 30.7, - 31, - 31.1, - 31.4, - 31.6, - 31.8, - 32, - 32.1, - 32.4, - 32.3, - 32.3, - 32.4, - 32.4, - 32.5, - 32.6, - 32.8, - 32.9, - 32.9, - 33, - 33.1, - 33.2, - 33.3, - 33.3, - 33.4, - 33.5, - 33.6, - 33.7, - 33.7, - 33.9, - 34, - 34.1, - 34.2, - 34.3, - 34.5, - 34.7, - 34.9, - 34.9, - 35.1, - 35.2, - 22.5, - 23, - 23.6, - 24.2, - 25.1, - 25.9, - 26.6, - 27.3, - 27.9, - 28.6, - 29.3, - 30, - 30.6, - 31, - 31.5, - 32.1, - 32.6, - 32.9, - 33.3, - 33.7, - 33.9, - 34.1, - 34.3, - 34.4, - 34.6, - 34.8, - 34.9, - 35, - 35.5, - 35.5, - 35.2, - 35.2, - 35.3, - 35.5, - 35.5, - 35.5, - 35.4, - 35.4, - 35.3, - 35.1, - 34.8, - 34.6, - 34.5, - 34.3, - 34.1, - 34, - 33.8, - 33.5, - 33.2, - 32.8, - 32.3, - 31.7, - 31.1, - 30.5, - 29.9, - 29.3, - 28.8, - 28.4, - 28, - 27.6, - 27.3, - 27.4, - 27.6, - 27.9, - 28.2, - 28.5, - 28.7, - 28.9, - 29.1, - 29.7, - 30.2, - 30.5, - 30.7, - 31, - 31.2, - 31.5, - 32, - 32.5, - 32.7, - 32.9, - 33, - 33, - 33, - 33.1, - 33.1, - 33.3, - 33.3, - 33.5, - 33.5, - 33.5, - 33.7, - 33.7, - 33.4, - 33.4, - 33.3, - 33.2, - 33.2, - 33.3, - 33.2, - 33.1, - 33, - 32.9, - 32.9, - 33, - 33, - 32.9, - 32.8, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.1, - 31.9, - 31.8, - 31.6, - 31.5, - 31.3, - 31.2, - 31.1, - 30.8, - 30.6, - 30.5, - 30.4, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.5, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 28, - 28, - 28, - 27.9, - 27.8, - 27.4, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.3, - 26.2, - 26.2, - 26.1, - 26.1, - 26.1, - 26, - 25.8, - 25.6, - 25.4, - 25.4, - 25.5, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.8, - 25.8, - 25.9, - 26, - 26.1, - 26.2, - 26.3, - 26.4, - 26.5, - 26.5, - 26.7, - 26.7, - 26.9, - 27.1, - 27.2, - 27.4, - 27.7, - 27.9, - 28.1, - 28.3, - 28.6, - 28.9, - 28.9, - 29, - 29.1, - 29.3, - 29.7, - 30.1, - 30.3, - 30.5, - 30.7, - 30.8, - 31, - 31.3, - 31.4, - 31.6, - 31.8, - 32, - 32, - 31.9, - 32, - 32.1, - 32.3, - 32.4, - 32.5, - 32.6, - 32.6, - 32.6, - 32.8, - 32.8, - 32.9, - 32.9, - 33, - 33.2, - 33.3, - 33.4, - 33.5, - 33.6, - 33.7, - 33.9, - 34, - 34.1, - 34.3, - 34.5, - 34.6, - 34.8, - 34.9, - 35, - 22.8, - 23.2, - 23.9, - 24.6, - 25.4, - 26.2, - 27, - 27.7, - 28.2, - 28.9, - 29.6, - 30.3, - 30.9, - 31.3, - 31.8, - 32.3, - 32.7, - 33.1, - 33.5, - 33.8, - 34.2, - 34.4, - 34.5, - 34.7, - 34.8, - 35, - 35.1, - 35.1, - 35.4, - 35.5, - 35.3, - 35.5, - 35.7, - 35.8, - 35.8, - 35.7, - 35.7, - 35.7, - 35.6, - 35.5, - 35.1, - 34.9, - 34.7, - 34.6, - 34.4, - 34.3, - 34.1, - 33.9, - 33.7, - 33.4, - 33, - 32.4, - 31.9, - 31.5, - 30.9, - 30.3, - 29.7, - 29.2, - 28.8, - 28.3, - 27.9, - 27.9, - 28.2, - 28.5, - 28.8, - 29, - 29.2, - 29.4, - 29.7, - 30.3, - 30.8, - 31.1, - 31.4, - 31.7, - 31.9, - 32.3, - 33, - 33.3, - 33.4, - 33.7, - 33.8, - 33.6, - 33.5, - 33.7, - 33.8, - 33.8, - 33.9, - 34, - 34, - 34, - 34, - 34.2, - 34, - 34, - 33.8, - 33.8, - 33.7, - 33.7, - 33.7, - 33.7, - 33.6, - 33.4, - 33.3, - 33.3, - 33.3, - 33.3, - 33.2, - 33, - 33, - 32.8, - 32.7, - 32.6, - 32.4, - 32.2, - 32, - 31.8, - 31.7, - 31.6, - 31.4, - 31.2, - 31, - 30.8, - 30.7, - 30.6, - 30.4, - 30.3, - 30.2, - 30.1, - 29.9, - 29.8, - 29.7, - 29.6, - 29.6, - 29.5, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.1, - 28, - 28, - 28, - 28.1, - 28.3, - 28.2, - 28, - 27.8, - 27.5, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 26, - 26, - 25.9, - 25.7, - 25.6, - 25.4, - 25.4, - 25.4, - 25.5, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.8, - 25.9, - 26, - 26.1, - 26.2, - 26.3, - 26.4, - 26.4, - 26.5, - 26.6, - 26.7, - 26.8, - 27, - 27.2, - 27.4, - 27.6, - 27.7, - 28, - 28.5, - 28.7, - 28.7, - 28.9, - 29, - 29.3, - 29.6, - 29.9, - 30.1, - 30.3, - 30.4, - 30.6, - 30.7, - 30.9, - 31.1, - 31.2, - 31.4, - 31.6, - 31.6, - 31.7, - 31.7, - 31.8, - 31.9, - 32.1, - 32.1, - 32.2, - 32.2, - 32.3, - 32.4, - 32.4, - 32.5, - 32.6, - 32.7, - 32.9, - 33, - 33.1, - 33.2, - 33.4, - 33.5, - 33.7, - 33.8, - 34, - 34.1, - 34.3, - 34.4, - 34.6, - 34.7, - 34.9, - 23.1, - 23.5, - 24.1, - 24.9, - 25.7, - 26.5, - 27.3, - 27.9, - 28.5, - 29.1, - 29.8, - 30.4, - 31, - 31.5, - 32, - 32.4, - 32.8, - 33.2, - 33.6, - 33.9, - 34.3, - 34.5, - 34.6, - 34.8, - 35, - 35.1, - 35.2, - 35.3, - 35.3, - 35.4, - 35.5, - 35.7, - 35.8, - 35.9, - 35.9, - 36, - 35.9, - 35.8, - 35.7, - 35.5, - 35.4, - 35.2, - 35, - 34.9, - 34.7, - 34.6, - 34.4, - 34.3, - 34.1, - 33.9, - 33.6, - 33.2, - 32.9, - 32.4, - 31.8, - 31.4, - 30.7, - 30, - 29.5, - 29, - 28.7, - 28.7, - 28.9, - 29.2, - 29.5, - 29.6, - 29.7, - 29.9, - 30.3, - 30.9, - 31.3, - 31.7, - 32.1, - 32.3, - 32.4, - 32.9, - 33.7, - 34, - 34.2, - 34.4, - 34.4, - 34.3, - 34.3, - 34.4, - 34.6, - 34.5, - 34.5, - 34.6, - 34.6, - 34.6, - 34.5, - 34.5, - 34.7, - 34.7, - 34.5, - 34.4, - 34.3, - 34.2, - 34.2, - 34.2, - 34.2, - 34, - 33.8, - 33.7, - 33.5, - 33.5, - 33.5, - 33.4, - 33.3, - 33.2, - 33, - 32.9, - 32.8, - 32.5, - 32.3, - 32.1, - 31.9, - 31.8, - 31.6, - 31.4, - 31.3, - 31.1, - 30.9, - 30.8, - 30.6, - 30.5, - 30.4, - 30.3, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28.2, - 28.3, - 28.4, - 28.2, - 27.9, - 27.6, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 26, - 25.8, - 25.5, - 25.4, - 25.4, - 25.3, - 25.3, - 25.4, - 25.5, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.8, - 25.9, - 26, - 26.1, - 26.2, - 26.3, - 26.3, - 26.4, - 26.4, - 26.5, - 26.6, - 26.8, - 27, - 27.2, - 27.3, - 27.4, - 27.8, - 28.2, - 28.5, - 28.7, - 28.9, - 29.1, - 29.2, - 29.3, - 29.6, - 29.9, - 30.1, - 30.2, - 30.3, - 30.5, - 30.6, - 30.8, - 30.9, - 31.1, - 31.2, - 31.2, - 31.3, - 31.3, - 31.4, - 31.5, - 31.7, - 31.7, - 31.8, - 31.8, - 31.9, - 32, - 32.1, - 32.2, - 32.4, - 32.5, - 32.7, - 32.7, - 32.9, - 33, - 33.2, - 33.3, - 33.5, - 33.8, - 33.9, - 34, - 34.2, - 34.3, - 34.4, - 34.5, - 34.8, - 23.7, - 23.9, - 24.5, - 25.1, - 25.9, - 26.8, - 27.5, - 28, - 28.6, - 29.2, - 29.9, - 30.5, - 31.1, - 31.6, - 32.1, - 32.6, - 33, - 33.4, - 33.7, - 34.1, - 34.3, - 34.5, - 34.7, - 34.9, - 35, - 35.2, - 35.3, - 35.4, - 35.4, - 35.4, - 35.5, - 35.6, - 35.6, - 35.8, - 35.8, - 35.9, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.3, - 35.2, - 35, - 34.9, - 34.8, - 34.7, - 34.5, - 34.4, - 34.2, - 33.9, - 33.6, - 33.1, - 32.7, - 32.2, - 31.6, - 30.9, - 30.4, - 29.9, - 29.5, - 29.4, - 29.5, - 29.7, - 30.1, - 30.3, - 30.5, - 30.6, - 30.9, - 31.3, - 31.7, - 32, - 32.5, - 32.7, - 32.8, - 33.2, - 33.8, - 34.3, - 34.6, - 34.7, - 34.7, - 35, - 35.1, - 35.4, - 35.6, - 35.3, - 35.2, - 35.1, - 35.2, - 35.2, - 35.2, - 35, - 35, - 35, - 35, - 35, - 34.9, - 34.8, - 34.7, - 34.6, - 34.5, - 34.4, - 34.3, - 34.2, - 34, - 33.8, - 33.7, - 33.6, - 33.6, - 33.4, - 33.3, - 33.2, - 33, - 32.7, - 32.5, - 32.3, - 32.1, - 32, - 31.8, - 31.6, - 31.5, - 31.3, - 31.1, - 31, - 30.9, - 30.7, - 30.6, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.6, - 29.5, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.7, - 28.6, - 28.5, - 28.3, - 28.2, - 28.2, - 28.2, - 28.4, - 28.4, - 28.3, - 28.1, - 27.7, - 27.4, - 27.2, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 25.9, - 25.9, - 25.6, - 25.4, - 25.4, - 25.3, - 25.3, - 25.3, - 25.3, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.8, - 25.9, - 25.9, - 26, - 26.1, - 26.1, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 27, - 27.1, - 27.2, - 27.4, - 27.9, - 28.3, - 28.5, - 28.6, - 28.8, - 28.9, - 29, - 29.3, - 29.5, - 29.8, - 30, - 30.1, - 30.2, - 30.3, - 30.6, - 30.7, - 30.8, - 30.8, - 30.9, - 31, - 31, - 31.1, - 31.2, - 31.3, - 31.4, - 31.4, - 31.5, - 31.6, - 31.7, - 31.7, - 31.9, - 32, - 32.2, - 32.4, - 32.5, - 32.6, - 32.8, - 33, - 33.2, - 33.4, - 33.6, - 33.8, - 34, - 34, - 34.1, - 34.2, - 34.4, - 34.5, - 24, - 24.2, - 24.8, - 25.4, - 26.1, - 27, - 27.7, - 28.3, - 28.9, - 29.4, - 30, - 30.5, - 31.1, - 31.6, - 32.2, - 32.7, - 33.1, - 33.5, - 33.8, - 34.2, - 34.4, - 34.5, - 34.7, - 34.9, - 35, - 35.2, - 35.3, - 35.4, - 35.5, - 35.5, - 35.5, - 35.6, - 35.6, - 35.7, - 35.8, - 35.9, - 36.1, - 36.1, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.4, - 35.4, - 35.4, - 35.3, - 35.1, - 34.9, - 34.7, - 34.5, - 34.2, - 33.8, - 33.4, - 33, - 32.5, - 31.9, - 31.4, - 30.9, - 30.4, - 30.2, - 30.1, - 30.3, - 30.7, - 31.1, - 31.3, - 31.3, - 31.5, - 31.8, - 32.1, - 32.4, - 32.6, - 33, - 33.3, - 33.7, - 34.4, - 34.8, - 34.8, - 34.9, - 35.1, - 35.5, - 35.7, - 35.8, - 35.8, - 35.8, - 35.7, - 35.7, - 35.8, - 35.8, - 35.8, - 35.6, - 35.5, - 35.4, - 35.4, - 35.4, - 35.4, - 35.3, - 35.2, - 35, - 34.9, - 34.9, - 34.7, - 34.6, - 34.5, - 34.1, - 34, - 33.8, - 33.7, - 33.6, - 33.4, - 33.3, - 33.2, - 33, - 32.8, - 32.5, - 32.3, - 32.1, - 31.9, - 31.8, - 31.7, - 31.5, - 31.4, - 31.2, - 31, - 30.9, - 30.8, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.7, - 29.6, - 29.4, - 29.2, - 29.1, - 29, - 28.9, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.4, - 28.4, - 28.5, - 28.5, - 28.3, - 27.9, - 27.5, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.5, - 25.4, - 25.3, - 25.3, - 25.3, - 25.3, - 25.4, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.7, - 25.7, - 25.8, - 25.8, - 25.9, - 26, - 26.1, - 26.3, - 26.4, - 26.5, - 26.6, - 26.7, - 26.8, - 26.9, - 27.1, - 27.5, - 27.9, - 28.2, - 28.3, - 28.4, - 28.5, - 28.7, - 29.1, - 29.3, - 29.5, - 29.7, - 29.9, - 30.2, - 30.2, - 30.4, - 30.4, - 30.4, - 30.4, - 30.5, - 30.7, - 30.8, - 30.9, - 30.9, - 31, - 31.1, - 31.2, - 31.2, - 31.3, - 31.4, - 31.5, - 31.6, - 31.8, - 32, - 32.1, - 32.3, - 32.4, - 32.6, - 32.9, - 33.1, - 33.2, - 33.4, - 33.6, - 33.8, - 33.9, - 33.9, - 34, - 34.2, - 34.3, - 24.1, - 24.4, - 25.1, - 25.7, - 26.5, - 27.5, - 28.1, - 28.6, - 29.1, - 29.6, - 30.2, - 30.6, - 31.1, - 31.6, - 32.2, - 32.7, - 33.1, - 33.5, - 33.8, - 34.1, - 34.3, - 34.5, - 34.7, - 34.8, - 35, - 35.1, - 35.3, - 35.4, - 35.5, - 35.5, - 35.6, - 35.6, - 35.7, - 35.8, - 35.9, - 36.1, - 36, - 36.1, - 36, - 36, - 35.9, - 35.9, - 35.8, - 35.7, - 35.7, - 35.7, - 35.8, - 35.8, - 35.8, - 35.6, - 35.4, - 35.2, - 34.9, - 34.6, - 34.2, - 33.8, - 33.4, - 32.9, - 32.4, - 31.9, - 31.4, - 31, - 30.9, - 30.9, - 31.3, - 31.7, - 31.9, - 32, - 32.2, - 32.4, - 32.7, - 32.8, - 33, - 33.4, - 33.8, - 34.3, - 34.7, - 35.1, - 35.5, - 35.8, - 36, - 36.2, - 36.2, - 36.4, - 36.3, - 36.2, - 36.3, - 36.5, - 36.4, - 36.3, - 36.4, - 36.4, - 36.2, - 36, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.3, - 35.3, - 35, - 34.8, - 34.7, - 34.5, - 34.4, - 34.1, - 34, - 33.9, - 33.7, - 33.5, - 33.4, - 33.1, - 33, - 32.7, - 32.5, - 32.3, - 32.1, - 32, - 31.8, - 31.7, - 31.5, - 31.4, - 31.2, - 31, - 30.9, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.1, - 30, - 29.8, - 29.6, - 29.4, - 29.3, - 29.1, - 29, - 28.8, - 28.7, - 28.6, - 28.5, - 28.5, - 28.6, - 28.6, - 28.6, - 28.5, - 28.3, - 28, - 27.6, - 27.4, - 27.2, - 27.1, - 27, - 26.8, - 26.7, - 26.7, - 26.5, - 26.4, - 26.3, - 26.1, - 26, - 26, - 25.9, - 25.8, - 25.6, - 25.4, - 25.3, - 25.3, - 25.3, - 25.3, - 25.4, - 25.6, - 25.7, - 25.7, - 25.6, - 25.6, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.7, - 25.7, - 25.8, - 25.8, - 25.9, - 26, - 26.1, - 26.3, - 26.4, - 26.5, - 26.6, - 26.6, - 26.7, - 26.7, - 26.8, - 27.1, - 27.5, - 27.8, - 27.9, - 27.9, - 28.1, - 28.4, - 28.7, - 28.9, - 29.2, - 29.4, - 29.6, - 29.9, - 30.1, - 30.1, - 30.1, - 30.1, - 30.1, - 30.2, - 30.4, - 30.5, - 30.5, - 30.6, - 30.7, - 30.8, - 30.9, - 30.9, - 31, - 31.1, - 31.3, - 31.4, - 31.6, - 31.9, - 32, - 32.1, - 32.3, - 32.5, - 32.7, - 32.9, - 33.1, - 33.3, - 33.5, - 33.6, - 33.7, - 33.8, - 33.9, - 34, - 34.2, - 24.3, - 24.9, - 25.7, - 26.3, - 27, - 27.9, - 28.2, - 28.8, - 29.2, - 29.8, - 30.3, - 30.8, - 31.3, - 31.8, - 32.2, - 32.7, - 33.1, - 33.4, - 33.8, - 34, - 34.2, - 34.4, - 34.6, - 34.8, - 34.9, - 35.1, - 35.3, - 35.4, - 35.5, - 35.5, - 35.5, - 35.6, - 35.6, - 35.8, - 36.1, - 36.1, - 36.1, - 36.1, - 36.2, - 36.1, - 36, - 36.1, - 36, - 35.9, - 35.9, - 36, - 36.2, - 36.4, - 36.4, - 36.3, - 36.2, - 36.1, - 35.8, - 35.4, - 35, - 34.7, - 34.3, - 33.9, - 33.4, - 32.9, - 32.3, - 31.9, - 31.7, - 31.5, - 31.9, - 32.3, - 32.5, - 32.7, - 32.9, - 33.1, - 33.3, - 33.4, - 33.7, - 34.1, - 34.5, - 34.9, - 35.2, - 35.7, - 36.2, - 36.5, - 36.7, - 36.8, - 36.7, - 36.8, - 36.9, - 36.9, - 36.9, - 37, - 36.9, - 36.9, - 37, - 36.9, - 36.7, - 36.5, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.6, - 35.4, - 35.3, - 35.1, - 35, - 34.9, - 34.7, - 34.5, - 34.2, - 34.1, - 33.9, - 33.7, - 33.6, - 33.3, - 33.1, - 32.9, - 32.7, - 32.5, - 32.3, - 32.1, - 32, - 31.8, - 31.7, - 31.5, - 31.3, - 31.2, - 31, - 30.9, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.1, - 29.9, - 29.7, - 29.5, - 29.3, - 29.2, - 29, - 28.8, - 28.7, - 28.6, - 28.5, - 28.6, - 28.8, - 28.7, - 28.6, - 28.4, - 28, - 27.7, - 27.5, - 27.3, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.7, - 25.5, - 25.4, - 25.3, - 25.3, - 25.4, - 25.5, - 25.6, - 25.7, - 25.6, - 25.6, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.5, - 25.6, - 25.7, - 25.7, - 25.8, - 25.8, - 25.8, - 25.9, - 26, - 26, - 26.2, - 26.3, - 26.4, - 26.5, - 26.5, - 26.5, - 26.6, - 26.8, - 27.1, - 27.5, - 27.6, - 27.6, - 27.8, - 28.1, - 28.4, - 28.7, - 28.9, - 29.1, - 29.2, - 29.4, - 29.7, - 29.8, - 29.7, - 29.8, - 29.8, - 29.9, - 30.1, - 30.2, - 30.4, - 30.5, - 30.6, - 30.7, - 30.7, - 30.7, - 30.8, - 30.9, - 31, - 31.2, - 31.4, - 31.7, - 31.9, - 32, - 32.2, - 32.5, - 32.6, - 32.8, - 33.1, - 33.2, - 33.3, - 33.4, - 33.5, - 33.7, - 33.7, - 33.9, - 34, - 24.8, - 25.4, - 26, - 26.6, - 27.4, - 28.2, - 28.4, - 28.9, - 29.3, - 29.8, - 30.4, - 30.9, - 31.4, - 31.9, - 32.3, - 32.7, - 33, - 33.3, - 33.6, - 33.9, - 34.1, - 34.3, - 34.6, - 34.7, - 34.9, - 35, - 35.1, - 35.3, - 35.4, - 35.5, - 35.5, - 35.5, - 35.6, - 35.8, - 35.9, - 36.1, - 36.1, - 36.2, - 36.2, - 36.1, - 36.2, - 36.2, - 36.2, - 36.2, - 36.4, - 36.5, - 36.6, - 36.7, - 36.8, - 36.9, - 37, - 36.9, - 36.7, - 36.5, - 36.1, - 35.7, - 35.2, - 34.8, - 34.3, - 33.9, - 33.4, - 33, - 32.6, - 32.3, - 32.5, - 32.9, - 33.1, - 33.3, - 33.5, - 33.7, - 33.9, - 34.1, - 34.4, - 34.8, - 35.2, - 35.6, - 35.9, - 36.2, - 36.6, - 36.9, - 37.2, - 37.4, - 37.5, - 37.4, - 37.4, - 37.6, - 37.5, - 37.4, - 37.4, - 37.5, - 37.4, - 37.2, - 37.1, - 36.9, - 36.7, - 36.5, - 36.4, - 36.3, - 36.1, - 35.9, - 35.9, - 35.7, - 35.5, - 35.3, - 35.3, - 35.1, - 34.9, - 34.7, - 34.5, - 34.3, - 34.2, - 34, - 33.8, - 33.5, - 33.3, - 33.1, - 32.9, - 32.7, - 32.4, - 32.3, - 32.1, - 32, - 31.8, - 31.7, - 31.5, - 31.3, - 31.2, - 31, - 30.8, - 30.8, - 30.7, - 30.5, - 30.3, - 30.1, - 30, - 29.7, - 29.5, - 29.3, - 29.2, - 29, - 28.9, - 28.7, - 28.7, - 28.7, - 28.9, - 28.9, - 28.7, - 28.5, - 28.1, - 27.8, - 27.6, - 27.4, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.7, - 25.5, - 25.5, - 25.4, - 25.4, - 25.6, - 25.7, - 25.7, - 25.7, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.5, - 25.6, - 25.6, - 25.5, - 25.6, - 25.6, - 25.6, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.8, - 25.8, - 26, - 26.1, - 26.2, - 26.4, - 26.4, - 26.4, - 26.4, - 26.5, - 26.8, - 27.1, - 27.3, - 27.3, - 27.5, - 27.8, - 28.1, - 28.4, - 28.6, - 28.8, - 29, - 29.1, - 29.4, - 29.4, - 29.4, - 29.5, - 29.5, - 29.6, - 29.9, - 30.1, - 30.4, - 30.4, - 30.4, - 30.5, - 30.5, - 30.5, - 30.7, - 30.8, - 30.9, - 31.1, - 31.3, - 31.5, - 31.8, - 31.9, - 32.2, - 32.4, - 32.5, - 33, - 33.6, - 33.4, - 33.2, - 33.2, - 33.4, - 33.6, - 33.6, - 33.7, - 33.8, - 25.2, - 25.7, - 26.2, - 26.8, - 27.4, - 28.1, - 28.5, - 28.9, - 29.5, - 30, - 30.5, - 31, - 31.4, - 31.8, - 32.2, - 32.6, - 32.9, - 33.2, - 33.5, - 33.8, - 34, - 34.2, - 34.4, - 34.6, - 34.9, - 35, - 35.1, - 35.3, - 35.4, - 35.4, - 35.4, - 35.5, - 35.7, - 35.8, - 35.9, - 36, - 36.2, - 36.2, - 36.3, - 36.3, - 36.3, - 36.4, - 36.4, - 36.5, - 36.6, - 36.7, - 36.8, - 36.9, - 37.1, - 37.2, - 37.4, - 37.4, - 37.3, - 37.2, - 37, - 36.6, - 36.1, - 35.6, - 35.1, - 34.7, - 34.4, - 34, - 33.6, - 33.1, - 33.1, - 33.4, - 33.7, - 33.9, - 34.1, - 34.3, - 34.5, - 34.8, - 35.2, - 35.6, - 36, - 36.3, - 36.6, - 36.8, - 37.1, - 37.6, - 38, - 38.2, - 38, - 37.8, - 37.8, - 38, - 37.9, - 37.8, - 37.8, - 37.8, - 37.6, - 37.5, - 37.4, - 37.3, - 37, - 36.8, - 36.6, - 36.5, - 36.3, - 36.2, - 36.1, - 35.9, - 35.6, - 35.5, - 35.4, - 35.2, - 35, - 34.8, - 34.7, - 34.5, - 34.4, - 34.2, - 33.9, - 33.7, - 33.5, - 33.3, - 33, - 32.8, - 32.6, - 32.4, - 32.2, - 32.1, - 32, - 31.8, - 31.7, - 31.5, - 31.3, - 31.1, - 30.9, - 30.8, - 30.7, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.5, - 29.4, - 29.2, - 29, - 28.9, - 28.8, - 28.8, - 29, - 29, - 28.9, - 28.7, - 28.2, - 27.9, - 27.7, - 27.5, - 27.4, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 26, - 25.9, - 25.9, - 25.8, - 25.6, - 25.6, - 25.4, - 25.5, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.5, - 25.6, - 25.5, - 25.5, - 25.5, - 25.5, - 25.5, - 25.6, - 25.6, - 25.5, - 25.5, - 25.6, - 25.6, - 25.6, - 25.6, - 25.7, - 25.8, - 25.8, - 26, - 26.3, - 26.2, - 26.3, - 26.3, - 26.4, - 26.5, - 26.8, - 27, - 27.1, - 27.3, - 27.7, - 27.9, - 28.1, - 28.3, - 28.5, - 28.7, - 28.9, - 29.1, - 29.1, - 29.1, - 29.2, - 29.4, - 29.5, - 29.7, - 29.8, - 29.9, - 30, - 30.2, - 30.3, - 30.4, - 30.5, - 30.6, - 30.7, - 30.8, - 30.9, - 31.1, - 31.3, - 31.5, - 31.8, - 32, - 32.2, - 32.4, - 32.8, - 33.5, - 33.3, - 33.1, - 33.1, - 33.2, - 33.4, - 33.4, - 33.5, - 33.5, - 25.6, - 26, - 26.4, - 26.9, - 27.5, - 28.2, - 28.7, - 29.1, - 29.6, - 30.1, - 30.5, - 31.1, - 31.5, - 31.9, - 32.4, - 32.7, - 32.9, - 33.2, - 33.4, - 33.8, - 34.1, - 34.2, - 34.4, - 34.5, - 34.7, - 34.9, - 35, - 35.2, - 35.3, - 35.3, - 35.4, - 35.5, - 35.7, - 35.9, - 36, - 36, - 36.1, - 36.2, - 36.3, - 36.3, - 36.4, - 36.5, - 36.5, - 36.6, - 36.6, - 36.8, - 36.9, - 37.1, - 37.3, - 37.6, - 37.8, - 37.9, - 38, - 37.9, - 37.8, - 37.4, - 37, - 36.5, - 35.9, - 35.5, - 35.3, - 35.1, - 34.6, - 34.1, - 33.9, - 34.2, - 34.3, - 34.5, - 34.6, - 34.8, - 35, - 35.4, - 35.9, - 36.3, - 36.7, - 37, - 37.2, - 37.4, - 37.6, - 38, - 38.3, - 38.5, - 38.3, - 38.2, - 38.3, - 38.4, - 38.3, - 38.4, - 38.3, - 38.2, - 38, - 37.9, - 37.8, - 37.7, - 37.4, - 37.2, - 36.9, - 36.7, - 36.5, - 36.4, - 36.3, - 36.1, - 35.9, - 35.7, - 35.5, - 35.4, - 35.3, - 35.1, - 34.9, - 34.7, - 34.5, - 34.3, - 34, - 33.8, - 33.6, - 33.4, - 33.2, - 32.9, - 32.8, - 32.6, - 32.4, - 32.2, - 32.1, - 31.9, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.8, - 30.7, - 30.6, - 30.4, - 30.3, - 30.1, - 29.8, - 29.5, - 29.3, - 29.1, - 29, - 28.9, - 29, - 29.2, - 29.1, - 29.1, - 28.9, - 28.4, - 28, - 27.8, - 27.6, - 27.5, - 27.4, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.5, - 25.5, - 25.4, - 25.4, - 25.5, - 25.5, - 25.5, - 25.5, - 25.5, - 25.5, - 25.5, - 25.5, - 25.5, - 25.5, - 25.6, - 25.6, - 25.6, - 25.7, - 26, - 26.2, - 26.2, - 26.2, - 26.2, - 26.3, - 26.3, - 26.5, - 26.7, - 27, - 27.2, - 27.4, - 27.7, - 27.9, - 28, - 28.2, - 28.4, - 28.6, - 28.7, - 28.9, - 28.9, - 29.1, - 29.2, - 29.3, - 29.4, - 29.5, - 29.6, - 29.8, - 30, - 30.2, - 30.3, - 30.4, - 30.5, - 30.7, - 30.8, - 30.9, - 31.1, - 31.2, - 31.4, - 31.7, - 31.9, - 32.1, - 32.2, - 32.5, - 33, - 32.9, - 32.9, - 32.9, - 33, - 33.1, - 33.2, - 33.2, - 33.2, - 26.1, - 26.3, - 26.6, - 27.1, - 27.7, - 28.3, - 28.7, - 29.1, - 29.6, - 30.2, - 30.6, - 31.2, - 31.5, - 31.8, - 32.3, - 32.6, - 32.9, - 33.1, - 33.3, - 33.7, - 34, - 34.3, - 34.3, - 34.4, - 34.6, - 34.8, - 34.9, - 35.1, - 35.1, - 35.2, - 35.3, - 35.4, - 35.7, - 35.8, - 35.9, - 36, - 36.1, - 36.2, - 36.3, - 36.3, - 36.4, - 36.5, - 36.6, - 36.6, - 36.8, - 37, - 37.3, - 37.6, - 37.8, - 38, - 38.2, - 38.3, - 38.4, - 38.4, - 38.3, - 38.2, - 37.9, - 37.4, - 36.8, - 36.4, - 36.1, - 35.9, - 35.7, - 35.1, - 34.9, - 35.1, - 35, - 35, - 35.2, - 35.4, - 35.6, - 36.1, - 36.5, - 37, - 37.4, - 37.6, - 37.9, - 38.1, - 38.3, - 38.5, - 38.6, - 38.7, - 38.8, - 38.8, - 38.8, - 38.8, - 38.9, - 38.8, - 38.6, - 38.5, - 38.4, - 38.2, - 37.9, - 37.8, - 37.7, - 37.5, - 37.2, - 37, - 36.8, - 36.6, - 36.4, - 36.3, - 36.1, - 35.9, - 35.7, - 35.6, - 35.4, - 35.2, - 35, - 34.8, - 34.7, - 34.5, - 34.2, - 34, - 33.8, - 33.5, - 33.3, - 33.1, - 32.9, - 32.7, - 32.6, - 32.4, - 32.2, - 32, - 31.9, - 31.7, - 31.5, - 31.3, - 31.1, - 30.9, - 30.7, - 30.6, - 30.4, - 30.3, - 30.1, - 29.8, - 29.5, - 29.3, - 29.1, - 29.1, - 29.2, - 29.3, - 29.3, - 29.2, - 29, - 28.6, - 28.2, - 27.9, - 27.8, - 27.7, - 27.5, - 27.4, - 27.3, - 27.2, - 27, - 26.9, - 26.8, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 26, - 25.9, - 25.8, - 25.8, - 25.7, - 25.5, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.5, - 25.5, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.5, - 25.6, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.5, - 25.6, - 25.8, - 26, - 26.1, - 26.2, - 26.2, - 26.2, - 26.2, - 26.3, - 26.5, - 26.8, - 26.9, - 27.1, - 27.4, - 27.5, - 27.7, - 27.9, - 28.1, - 28.3, - 28.3, - 28.5, - 28.6, - 28.9, - 28.9, - 28.9, - 29.1, - 29.3, - 29.4, - 29.7, - 29.9, - 30, - 30.2, - 30.4, - 30.5, - 30.6, - 30.7, - 30.8, - 31, - 31.2, - 31.4, - 31.6, - 31.8, - 32, - 32.1, - 32.2, - 32.5, - 32.6, - 32.7, - 32.7, - 32.8, - 33, - 33, - 32.9, - 33, - 26.3, - 26.6, - 26.9, - 27.4, - 27.9, - 28.4, - 28.7, - 29.1, - 29.6, - 30.2, - 30.6, - 31, - 31.4, - 31.8, - 32.2, - 32.5, - 32.8, - 32.9, - 33.1, - 33.5, - 33.9, - 34, - 34.2, - 34.3, - 34.4, - 34.6, - 34.7, - 34.9, - 35, - 35.1, - 35.2, - 35.4, - 35.6, - 35.6, - 35.7, - 35.9, - 36.1, - 36.2, - 36.2, - 36.4, - 36.5, - 36.5, - 36.7, - 36.9, - 37.2, - 37.5, - 37.8, - 38.1, - 38.2, - 38.3, - 38.5, - 38.6, - 38.7, - 38.7, - 38.8, - 38.8, - 38.7, - 38.4, - 38, - 37.3, - 36.8, - 36.6, - 36.6, - 36.1, - 35.8, - 35.9, - 35.8, - 35.7, - 35.9, - 36.1, - 36.3, - 36.7, - 37.2, - 37.6, - 38, - 38.3, - 38.5, - 38.7, - 39, - 39.4, - 39.5, - 39.6, - 39.5, - 39.4, - 39.3, - 39.3, - 39.2, - 39.1, - 38.9, - 38.8, - 38.7, - 38.4, - 38.2, - 38, - 37.9, - 37.7, - 37.5, - 37.2, - 37, - 36.8, - 36.6, - 36.4, - 36.3, - 36.1, - 35.9, - 35.7, - 35.6, - 35.4, - 35.1, - 34.9, - 34.8, - 34.6, - 34.4, - 34.2, - 33.9, - 33.7, - 33.4, - 33.2, - 33, - 32.8, - 32.7, - 32.6, - 32.3, - 32.1, - 32, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.8, - 30.6, - 30.4, - 30.3, - 30.1, - 29.8, - 29.5, - 29.3, - 29.2, - 29.3, - 29.4, - 29.5, - 29.3, - 29.2, - 28.8, - 28.3, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 26, - 25.9, - 25.8, - 25.8, - 25.6, - 25.7, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.7, - 25.7, - 25.6, - 25.6, - 25.6, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.5, - 25.5, - 25.5, - 25.4, - 25.3, - 25.3, - 25.3, - 25.3, - 25.3, - 25.4, - 25.5, - 25.7, - 26, - 26.1, - 26.1, - 26.1, - 26.2, - 26.2, - 26.3, - 26.4, - 26.6, - 26.8, - 27, - 27.2, - 27.4, - 27.6, - 27.8, - 28, - 28.1, - 28.2, - 28.2, - 28.4, - 28.5, - 28.6, - 28.8, - 29.1, - 29.1, - 29.3, - 29.5, - 29.7, - 30, - 30.1, - 30.3, - 30.3, - 30.4, - 30.7, - 31, - 31, - 31.2, - 31.4, - 31.7, - 31.9, - 32, - 32.1, - 32.4, - 32.5, - 32.5, - 32.6, - 32.6, - 32.7, - 32.6, - 32.6, - 32.7, - 26.8, - 26.9, - 27.3, - 27.7, - 28.1, - 28.4, - 28.7, - 29.1, - 29.6, - 30, - 30.5, - 31, - 31.5, - 31.8, - 32.2, - 32.5, - 32.7, - 32.8, - 33, - 33.3, - 33.6, - 33.8, - 34, - 34.1, - 34.3, - 34.4, - 34.5, - 34.6, - 34.9, - 35, - 35.1, - 35.2, - 35.4, - 35.6, - 35.7, - 35.9, - 36.1, - 36.2, - 36.4, - 36.4, - 36.6, - 36.7, - 37.1, - 37.3, - 37.5, - 37.9, - 38.1, - 38.3, - 38.4, - 38.6, - 38.7, - 38.9, - 39, - 39.1, - 39.2, - 39.3, - 39.3, - 39.3, - 39.1, - 38.5, - 37.9, - 37.5, - 37.5, - 37.2, - 36.8, - 36.6, - 36.5, - 36.5, - 36.6, - 36.7, - 36.9, - 37.3, - 37.7, - 38.1, - 38.5, - 38.8, - 39.1, - 39.4, - 39.5, - 39.7, - 40, - 40.3, - 40.1, - 39.8, - 39.8, - 39.8, - 39.4, - 39.3, - 39.1, - 39, - 38.8, - 38.6, - 38.4, - 38.2, - 38, - 37.8, - 37.6, - 37.4, - 37.2, - 37, - 36.8, - 36.6, - 36.4, - 36.2, - 36, - 35.8, - 35.7, - 35.4, - 35.2, - 35, - 34.9, - 34.7, - 34.5, - 34.2, - 34, - 33.8, - 33.6, - 33.3, - 33.1, - 32.9, - 32.7, - 32.6, - 32.4, - 32.2, - 32, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 30, - 29.7, - 29.5, - 29.3, - 29.3, - 29.5, - 29.6, - 29.4, - 29.2, - 28.8, - 28.4, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.8, - 25.8, - 25.7, - 25.7, - 25.7, - 25.6, - 25.7, - 25.6, - 25.6, - 25.6, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.3, - 25.3, - 25.2, - 25.3, - 25.3, - 25.3, - 25.3, - 25.3, - 25.4, - 25.7, - 25.9, - 26, - 26.1, - 26.1, - 26.1, - 26.1, - 26.2, - 26.3, - 26.4, - 26.6, - 26.9, - 27, - 27.3, - 27.5, - 27.7, - 27.8, - 27.8, - 27.9, - 28, - 28.1, - 28.2, - 28.5, - 28.8, - 28.8, - 28.9, - 29.1, - 29.3, - 29.5, - 29.9, - 30, - 30, - 30.2, - 30.5, - 30.8, - 30.9, - 31.1, - 31.3, - 31.5, - 31.8, - 32, - 32, - 32.2, - 32.3, - 32.4, - 32.4, - 32.4, - 32.3, - 32.3, - 32.4, - 32.4, - 27.5, - 27.6, - 28, - 28.4, - 28.5, - 28.6, - 28.8, - 29.1, - 29.5, - 29.9, - 30.4, - 30.9, - 31.4, - 31.7, - 32.1, - 32.2, - 32.4, - 32.6, - 32.8, - 33.1, - 33.5, - 33.7, - 33.8, - 33.9, - 34.1, - 34.2, - 34.4, - 34.5, - 34.7, - 34.9, - 35.1, - 35.3, - 35.4, - 35.5, - 35.8, - 35.9, - 36.1, - 36.2, - 36.5, - 36.6, - 36.7, - 37.1, - 37.4, - 37.6, - 37.8, - 38.1, - 38.2, - 38.4, - 38.6, - 38.8, - 38.9, - 39.2, - 39.4, - 39.5, - 39.6, - 39.7, - 39.7, - 39.7, - 39.8, - 39.5, - 39, - 38.6, - 38.5, - 38.3, - 37.9, - 37.5, - 37.2, - 37.2, - 37.4, - 37.5, - 37.6, - 37.9, - 38.2, - 38.7, - 39, - 39.3, - 39.7, - 40, - 40.1, - 40.1, - 40.2, - 40.4, - 40.4, - 40.2, - 40.1, - 39.9, - 39.7, - 39.5, - 39.3, - 39.3, - 39.1, - 38.7, - 38.6, - 38.3, - 38.1, - 37.9, - 37.7, - 37.5, - 37.3, - 37.2, - 36.9, - 36.7, - 36.5, - 36.3, - 36.1, - 35.9, - 35.7, - 35.5, - 35.3, - 35.1, - 34.9, - 34.7, - 34.5, - 34.3, - 34.1, - 33.9, - 33.6, - 33.5, - 33.3, - 33.1, - 32.9, - 32.7, - 32.5, - 32.3, - 32.1, - 31.9, - 31.7, - 31.5, - 31.3, - 31.1, - 30.9, - 30.6, - 30.4, - 30.2, - 29.9, - 29.7, - 29.5, - 29.5, - 29.6, - 29.6, - 29.6, - 29.3, - 28.9, - 28.5, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.8, - 27.6, - 27.5, - 27.4, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.6, - 26.5, - 26.5, - 26.3, - 26.2, - 26.1, - 26.1, - 26, - 26, - 25.9, - 25.9, - 25.8, - 25.7, - 25.7, - 25.7, - 25.7, - 25.6, - 25.5, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.3, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.3, - 25.2, - 25.2, - 25.2, - 25.2, - 25.2, - 25.2, - 25.2, - 25.2, - 25.3, - 25.3, - 25.5, - 25.7, - 26, - 26, - 26, - 25.9, - 25.9, - 26, - 26.1, - 26.3, - 26.4, - 26.7, - 26.9, - 27.2, - 27.3, - 27.5, - 27.5, - 27.6, - 27.6, - 27.7, - 27.9, - 28.1, - 28.3, - 28.4, - 28.6, - 28.8, - 29, - 29.2, - 29.6, - 29.8, - 29.9, - 30.1, - 30.3, - 30.6, - 30.8, - 31, - 31.1, - 31.3, - 31.7, - 31.8, - 31.9, - 32, - 32, - 32.1, - 32.1, - 32.1, - 32, - 32, - 32, - 32, - 27.5, - 27.9, - 28.4, - 28.9, - 28.9, - 28.9, - 29, - 29.2, - 29.5, - 29.9, - 30.3, - 30.7, - 31, - 31.4, - 31.8, - 32, - 32.2, - 32.5, - 32.7, - 33, - 33.3, - 33.5, - 33.6, - 33.7, - 34, - 34.2, - 34.3, - 34.4, - 34.6, - 34.8, - 35, - 35.2, - 35.3, - 35.5, - 35.7, - 35.9, - 36.2, - 36.3, - 36.6, - 36.8, - 37.2, - 37.4, - 37.5, - 37.8, - 38, - 38.3, - 38.4, - 38.7, - 38.9, - 39.1, - 39.3, - 39.4, - 39.7, - 39.9, - 40, - 40.1, - 40.2, - 40.3, - 40.4, - 40.4, - 40, - 39.6, - 39.3, - 39.3, - 39, - 38.6, - 38.3, - 38.2, - 38.2, - 38.2, - 38.3, - 38.5, - 38.8, - 39.2, - 39.4, - 39.7, - 40, - 40.4, - 40.6, - 40.6, - 40.5, - 40.5, - 40.6, - 40.4, - 40.1, - 40, - 39.8, - 39.7, - 39.4, - 39.3, - 39.2, - 38.8, - 38.6, - 38.4, - 38.2, - 38.1, - 37.8, - 37.7, - 37.5, - 37.3, - 37, - 36.8, - 36.6, - 36.4, - 36.2, - 36, - 35.8, - 35.5, - 35.3, - 35.2, - 35, - 34.8, - 34.7, - 34.5, - 34.2, - 34, - 33.7, - 33.6, - 33.4, - 33.2, - 33, - 32.8, - 32.6, - 32.4, - 32.1, - 31.9, - 31.8, - 31.6, - 31.4, - 31.1, - 30.9, - 30.6, - 30.4, - 30.1, - 29.9, - 29.7, - 29.6, - 29.7, - 29.8, - 29.7, - 29.4, - 29, - 28.6, - 28.3, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.2, - 27.1, - 26.9, - 26.8, - 26.7, - 26.7, - 26.5, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26.1, - 26.1, - 26, - 26, - 25.9, - 25.8, - 25.7, - 25.7, - 25.7, - 25.6, - 25.6, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.3, - 25.2, - 25.2, - 25.2, - 25.1, - 25.2, - 25.2, - 25.2, - 25.2, - 25.2, - 25.2, - 25.2, - 25.2, - 25.3, - 25.5, - 25.7, - 25.8, - 25.8, - 25.8, - 25.8, - 25.9, - 26, - 26.2, - 26.4, - 26.7, - 26.9, - 27, - 27.1, - 27.2, - 27.2, - 27.3, - 27.4, - 27.6, - 27.8, - 27.9, - 28.1, - 28.3, - 28.5, - 28.7, - 29, - 29.3, - 29.6, - 29.8, - 30, - 30.2, - 30.5, - 30.7, - 30.8, - 31, - 31.1, - 31.4, - 31.5, - 31.7, - 31.8, - 31.9, - 31.9, - 31.8, - 31.8, - 31.7, - 31.7, - 31.7, - 31.7, - 27.9, - 28.3, - 28.8, - 29.7, - 29.8, - 29.5, - 29.4, - 29.6, - 29.8, - 30, - 30.3, - 30.6, - 30.9, - 31.2, - 31.5, - 31.8, - 32.2, - 32.4, - 32.6, - 32.8, - 33, - 33.3, - 33.4, - 33.6, - 33.8, - 34.1, - 34.3, - 34.4, - 34.5, - 34.7, - 34.9, - 35.1, - 35.4, - 35.5, - 35.9, - 36.1, - 36.3, - 36.7, - 36.8, - 37.1, - 37.3, - 37.5, - 37.9, - 38, - 38.2, - 38.5, - 38.7, - 39, - 39.3, - 39.5, - 39.6, - 39.8, - 40, - 40.2, - 40.4, - 40.6, - 40.8, - 40.9, - 41, - 41, - 40.8, - 40.3, - 39.9, - 39.9, - 39.8, - 39.5, - 39.2, - 39, - 39, - 39, - 39, - 39.1, - 39.3, - 39.6, - 39.9, - 40.1, - 40.4, - 40.7, - 40.9, - 40.8, - 40.7, - 40.7, - 40.6, - 40.5, - 40.3, - 40.1, - 40, - 39.8, - 39.6, - 39.3, - 39.1, - 38.9, - 38.7, - 38.5, - 38.3, - 38.1, - 37.9, - 37.7, - 37.6, - 37.3, - 37.1, - 36.9, - 36.7, - 36.5, - 36.2, - 36, - 35.8, - 35.6, - 35.4, - 35.2, - 35.1, - 35, - 34.8, - 34.6, - 34.3, - 34.1, - 33.9, - 33.6, - 33.4, - 33.3, - 33.1, - 32.8, - 32.6, - 32.4, - 32.2, - 32, - 31.8, - 31.6, - 31.4, - 31.1, - 30.8, - 30.6, - 30.3, - 30.1, - 29.9, - 29.7, - 29.8, - 29.9, - 29.8, - 29.6, - 29.2, - 28.8, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.8, - 27.8, - 27.7, - 27.7, - 27.5, - 27.3, - 27.2, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.5, - 26.4, - 26.4, - 26.3, - 26.2, - 26.1, - 26.1, - 26.1, - 26, - 26, - 25.9, - 25.8, - 25.7, - 25.7, - 25.6, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 25.1, - 25.2, - 25.3, - 25.6, - 25.6, - 25.6, - 25.7, - 25.7, - 25.8, - 25.9, - 26.1, - 26.4, - 26.5, - 26.5, - 26.6, - 26.8, - 26.9, - 27, - 27.1, - 27.2, - 27.3, - 27.5, - 27.7, - 27.9, - 28.2, - 28.4, - 28.6, - 28.9, - 29.1, - 29.5, - 29.7, - 29.8, - 30.1, - 30.6, - 30.8, - 30.7, - 30.8, - 31, - 31.1, - 31.3, - 31.5, - 31.7, - 31.7, - 31.6, - 31.6, - 31.5, - 31.4, - 31.4, - 31.4, - 31.4, - 28.6, - 29, - 29.6, - 30.4, - 30.3, - 30.1, - 30, - 30.1, - 30.2, - 30.4, - 30.5, - 30.7, - 31, - 31.2, - 31.5, - 31.8, - 32.1, - 32.3, - 32.5, - 32.7, - 32.9, - 33.1, - 33.3, - 33.4, - 33.6, - 33.8, - 34.1, - 34.2, - 34.5, - 34.7, - 34.8, - 35, - 35.3, - 35.6, - 36, - 36.2, - 36.4, - 36.7, - 36.9, - 37.2, - 37.6, - 37.7, - 38.1, - 38.2, - 38.4, - 38.7, - 39, - 39.2, - 39.6, - 39.9, - 40, - 40.2, - 40.4, - 40.6, - 40.9, - 41.1, - 41.3, - 41.5, - 41.6, - 41.6, - 41.5, - 41.1, - 40.6, - 40.4, - 40.2, - 40.1, - 39.9, - 39.7, - 39.6, - 39.6, - 39.6, - 39.7, - 39.9, - 40.1, - 40.4, - 40.6, - 40.8, - 41, - 41, - 41, - 40.9, - 40.8, - 40.6, - 40.6, - 40.5, - 40.4, - 40.2, - 39.9, - 39.7, - 39.5, - 39.2, - 39.1, - 38.9, - 38.6, - 38.4, - 38.2, - 38, - 37.8, - 37.6, - 37.4, - 37.1, - 36.9, - 36.7, - 36.5, - 36.3, - 36.1, - 35.9, - 35.7, - 35.5, - 35.3, - 35.2, - 35.1, - 34.8, - 34.6, - 34.3, - 34.1, - 33.9, - 33.7, - 33.6, - 33.3, - 33.1, - 32.9, - 32.7, - 32.5, - 32.2, - 32, - 31.8, - 31.6, - 31.3, - 31.1, - 30.8, - 30.6, - 30.3, - 30.1, - 29.9, - 29.9, - 30, - 29.9, - 29.7, - 29.3, - 28.9, - 28.6, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.9, - 27.9, - 27.8, - 27.8, - 27.8, - 27.6, - 27.5, - 27.3, - 27.1, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 26.1, - 26.1, - 26.1, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25, - 25, - 25, - 25, - 25.1, - 25.1, - 25, - 25, - 25, - 25.1, - 25.1, - 25.2, - 25.4, - 25.5, - 25.6, - 25.6, - 25.7, - 25.7, - 25.8, - 25.9, - 26.2, - 26.3, - 26.3, - 26.4, - 26.5, - 26.7, - 26.8, - 26.9, - 27, - 27.1, - 27.3, - 27.6, - 27.8, - 28.1, - 28.3, - 28.5, - 28.8, - 29, - 29.3, - 29.5, - 29.8, - 30.1, - 30.5, - 30.6, - 30.5, - 30.7, - 30.8, - 30.9, - 31.1, - 31.3, - 31.4, - 31.4, - 31.3, - 31.2, - 31.2, - 31.1, - 31.1, - 31, - 31, - 29.3, - 29.9, - 30.5, - 30.7, - 30.7, - 30.6, - 30.5, - 30.6, - 30.7, - 30.8, - 30.9, - 31, - 31.2, - 31.4, - 31.6, - 31.8, - 32, - 32.2, - 32.4, - 32.7, - 32.8, - 33, - 33.2, - 33.3, - 33.5, - 33.7, - 34, - 34.2, - 34.5, - 34.6, - 34.7, - 35, - 35.3, - 35.5, - 35.9, - 36.1, - 36.4, - 36.7, - 37, - 37.5, - 38, - 38.1, - 38.2, - 38.4, - 38.7, - 39, - 39.2, - 39.5, - 39.7, - 40.1, - 40.3, - 40.4, - 40.6, - 41.1, - 41.5, - 41.6, - 41.8, - 42, - 42.2, - 42.2, - 42.1, - 41.6, - 41.1, - 40.9, - 40.8, - 40.6, - 40.5, - 40.4, - 40.2, - 40.1, - 40.1, - 40.3, - 40.5, - 40.7, - 40.9, - 41, - 41.1, - 41.1, - 41.1, - 41, - 41, - 40.9, - 40.8, - 40.7, - 40.6, - 40.5, - 40.3, - 40, - 39.8, - 39.6, - 39.3, - 39.1, - 38.9, - 38.6, - 38.4, - 38.2, - 38, - 37.8, - 37.6, - 37.4, - 37.2, - 37, - 36.8, - 36.6, - 36.4, - 36.2, - 36, - 35.8, - 35.6, - 35.4, - 35.2, - 35.1, - 34.9, - 34.7, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.5, - 33.2, - 33, - 32.7, - 32.5, - 32.2, - 32, - 31.8, - 31.6, - 31.3, - 31, - 30.7, - 30.5, - 30.3, - 30.1, - 30.1, - 30.1, - 30.1, - 29.9, - 29.4, - 29, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.8, - 27.8, - 27.8, - 27.7, - 27.6, - 27.4, - 27.3, - 27.1, - 27, - 26.9, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.2, - 26.1, - 26.1, - 26, - 25.9, - 25.9, - 25.9, - 25.8, - 25.6, - 25.5, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.3, - 25.2, - 25.1, - 25.1, - 25.1, - 25, - 25, - 25, - 24.9, - 24.9, - 24.9, - 25, - 25, - 25, - 25, - 25, - 25, - 25.1, - 25.3, - 25.4, - 25.5, - 25.6, - 25.6, - 25.6, - 25.7, - 25.7, - 25.8, - 26, - 26.1, - 26.2, - 26.4, - 26.5, - 26.6, - 26.7, - 26.9, - 27, - 27.3, - 27.6, - 27.7, - 28, - 28.2, - 28.4, - 28.7, - 29, - 29.2, - 29.4, - 29.7, - 29.9, - 30.1, - 30.3, - 30.4, - 30.7, - 30.9, - 30.9, - 30.9, - 31, - 31, - 31, - 30.9, - 30.8, - 30.8, - 30.8, - 30.7, - 30.7, - 30.7, - 29.9, - 30.5, - 31.2, - 31.5, - 31.3, - 31.2, - 31.1, - 31, - 31.1, - 31.2, - 31.3, - 31.3, - 31.5, - 31.6, - 31.7, - 31.9, - 32.1, - 32.3, - 32.5, - 32.7, - 32.8, - 33, - 33.1, - 33.3, - 33.5, - 33.7, - 34, - 34.4, - 34.9, - 34.8, - 34.8, - 35.1, - 35.4, - 35.7, - 35.9, - 36.2, - 36.5, - 36.9, - 37.2, - 37.6, - 38, - 38.2, - 38.4, - 38.6, - 38.8, - 39.1, - 39.4, - 39.7, - 40, - 40.4, - 40.5, - 40.5, - 40.8, - 41.4, - 41.8, - 42, - 42.2, - 42.4, - 42.6, - 42.7, - 42.5, - 42.1, - 41.6, - 41.5, - 41.4, - 41.2, - 41, - 41, - 40.8, - 40.6, - 40.7, - 40.8, - 41, - 41.2, - 41.3, - 41.3, - 41.3, - 41.3, - 41.3, - 41.3, - 41.1, - 41.1, - 40.9, - 40.8, - 40.6, - 40.4, - 40.2, - 40, - 39.8, - 39.5, - 39.3, - 39.1, - 38.8, - 38.6, - 38.4, - 38.2, - 38, - 37.8, - 37.6, - 37.4, - 37.2, - 37, - 36.8, - 36.6, - 36.5, - 36.3, - 36, - 35.8, - 35.7, - 35.5, - 35.3, - 35.1, - 35, - 34.9, - 34.7, - 34.4, - 34.2, - 34, - 33.7, - 33.5, - 33.2, - 33, - 32.8, - 32.5, - 32.2, - 32, - 31.8, - 31.5, - 31.2, - 30.9, - 30.7, - 30.5, - 30.3, - 30.2, - 30.2, - 30.2, - 30.1, - 29.7, - 29.3, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.8, - 27.8, - 27.8, - 27.8, - 27.8, - 27.6, - 27.4, - 27.2, - 27.1, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.8, - 25.6, - 25.5, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.3, - 25.2, - 25.2, - 25, - 25.1, - 25.1, - 25, - 25, - 25, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 25, - 25, - 25.2, - 25.3, - 25.4, - 25.5, - 25.5, - 25.4, - 25.5, - 25.6, - 25.7, - 25.8, - 25.9, - 26.1, - 26.3, - 26.4, - 26.5, - 26.7, - 26.8, - 26.9, - 27.2, - 27.5, - 27.6, - 27.9, - 28.1, - 28.4, - 28.7, - 28.9, - 29.1, - 29.4, - 29.6, - 29.8, - 30, - 30.2, - 30.3, - 30.4, - 30.7, - 30.8, - 30.8, - 30.7, - 30.7, - 30.7, - 30.5, - 30.5, - 30.5, - 30.5, - 30.4, - 30.4, - 30.3, - 30.6, - 31.3, - 31.7, - 32, - 31.8, - 31.8, - 31.6, - 31.5, - 31.5, - 31.6, - 31.6, - 31.6, - 31.7, - 31.8, - 31.9, - 32, - 32.2, - 32.4, - 32.6, - 32.7, - 32.8, - 33, - 33.1, - 33.3, - 33.5, - 33.7, - 34, - 34.4, - 34.9, - 34.9, - 34.9, - 35.2, - 35.5, - 36, - 36.2, - 36.3, - 36.7, - 37.2, - 37.5, - 37.8, - 38.1, - 38.3, - 38.4, - 38.7, - 39.1, - 39.4, - 39.7, - 40.1, - 40.4, - 40.6, - 40.6, - 40.6, - 41.1, - 41.6, - 42, - 42.3, - 42.5, - 42.8, - 42.9, - 43.1, - 43.1, - 43, - 42.3, - 42, - 41.8, - 41.6, - 41.5, - 41.5, - 41.3, - 41.2, - 41.2, - 41.3, - 41.4, - 41.5, - 41.6, - 41.6, - 41.6, - 41.5, - 41.4, - 41.5, - 41.3, - 41.1, - 41, - 40.8, - 40.6, - 40.4, - 40.2, - 40, - 39.7, - 39.5, - 39.3, - 39, - 38.8, - 38.6, - 38.3, - 38.1, - 38, - 37.8, - 37.6, - 37.4, - 37.2, - 37, - 36.7, - 36.6, - 36.5, - 36.3, - 36, - 35.9, - 35.7, - 35.6, - 35.4, - 35.2, - 35.2, - 35, - 34.9, - 34.6, - 34.3, - 34.1, - 33.9, - 33.6, - 33.2, - 33, - 32.8, - 32.5, - 32.3, - 32, - 31.7, - 31.5, - 31.2, - 30.9, - 30.6, - 30.5, - 30.3, - 30.3, - 30.3, - 30.2, - 29.9, - 29.5, - 29.2, - 29, - 29, - 28.9, - 28.8, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.8, - 27.8, - 27.9, - 27.7, - 27.5, - 27.4, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.2, - 26, - 26, - 25.9, - 25.8, - 25.7, - 25.7, - 25.6, - 25.5, - 25.5, - 25.4, - 25.4, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 25, - 25, - 24.9, - 24.9, - 24.8, - 24.8, - 24.8, - 24.9, - 24.9, - 24.9, - 24.8, - 24.9, - 24.9, - 24.9, - 25, - 25, - 25.2, - 25.3, - 25.3, - 25.3, - 25.5, - 25.6, - 25.7, - 25.8, - 25.9, - 26, - 26.2, - 26.3, - 26.4, - 26.5, - 26.6, - 26.8, - 27, - 27.3, - 27.6, - 27.8, - 28, - 28.3, - 28.6, - 28.8, - 29, - 29.3, - 29.5, - 29.7, - 29.9, - 30.1, - 30.2, - 30.2, - 30.4, - 30.6, - 30.6, - 30.6, - 30.4, - 30.4, - 30.2, - 30.1, - 30.1, - 30.1, - 30.1, - 30, - 30, - 31.3, - 32, - 32.1, - 32.3, - 32.3, - 32.5, - 32.2, - 32, - 31.9, - 32, - 31.9, - 31.9, - 32, - 32, - 32.1, - 32.2, - 32.3, - 32.4, - 32.6, - 32.7, - 32.9, - 33, - 33.2, - 33.3, - 33.5, - 33.7, - 34.1, - 34.3, - 34.5, - 34.7, - 35.1, - 35.3, - 35.5, - 35.9, - 36.2, - 36.5, - 37, - 37.4, - 37.7, - 37.9, - 38.2, - 38.4, - 38.6, - 39, - 39.5, - 39.9, - 40.4, - 40.6, - 40.8, - 40.7, - 40.8, - 41, - 41.3, - 41.8, - 42.2, - 42.6, - 42.9, - 43.1, - 43.2, - 43.3, - 43.4, - 43.5, - 43, - 42.5, - 42.2, - 42, - 41.9, - 41.9, - 41.8, - 41.7, - 41.7, - 41.8, - 41.8, - 41.8, - 41.8, - 41.9, - 41.9, - 41.7, - 41.6, - 41.6, - 41.3, - 41.2, - 41, - 40.8, - 40.6, - 40.4, - 40.1, - 39.9, - 39.7, - 39.5, - 39.2, - 39, - 38.8, - 38.5, - 38.2, - 38, - 38, - 37.9, - 37.8, - 37.5, - 37.2, - 37.1, - 36.9, - 36.7, - 36.5, - 36.3, - 36.1, - 35.9, - 35.7, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 35.1, - 35, - 34.6, - 34.3, - 34, - 33.6, - 33.3, - 33, - 32.7, - 32.5, - 32.2, - 32, - 31.7, - 31.4, - 31.1, - 30.8, - 30.6, - 30.5, - 30.4, - 30.4, - 30.3, - 30, - 29.6, - 29.3, - 29.2, - 29.2, - 29.1, - 29.1, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.9, - 28.8, - 28.8, - 28.7, - 28.6, - 28.6, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.9, - 28, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 25.9, - 25.9, - 25.8, - 25.7, - 25.7, - 25.6, - 25.5, - 25.4, - 25.4, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25.1, - 25, - 25, - 24.9, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.7, - 24.8, - 24.8, - 24.8, - 24.9, - 25, - 25.1, - 25.2, - 25.3, - 25.4, - 25.5, - 25.7, - 25.8, - 25.9, - 26, - 26.1, - 26.2, - 26.3, - 26.5, - 26.6, - 26.9, - 27.2, - 27.5, - 27.7, - 28, - 28.2, - 28.5, - 28.7, - 28.9, - 29.2, - 29.4, - 29.6, - 29.8, - 30.1, - 30.2, - 30.1, - 30.1, - 30.3, - 30.3, - 30.3, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.7, - 29.7, - 29.7, - 29.7, - 31.9, - 32.6, - 32.5, - 32.7, - 32.8, - 32.9, - 32.7, - 32.4, - 32.3, - 32.3, - 32.3, - 32.2, - 32.2, - 32.2, - 32.2, - 32.3, - 32.4, - 32.5, - 32.6, - 32.7, - 32.9, - 33.1, - 33.2, - 33.4, - 33.6, - 33.8, - 34, - 34.3, - 34.4, - 34.7, - 35.1, - 35.5, - 35.6, - 36, - 36.3, - 36.6, - 37, - 37.4, - 37.7, - 38, - 38.3, - 38.6, - 39, - 39.5, - 40.2, - 40.5, - 40.7, - 40.9, - 40.9, - 41, - 41.1, - 41.3, - 41.9, - 42.3, - 42.7, - 43, - 43.1, - 43.3, - 43.4, - 43.6, - 43.6, - 43.6, - 43.3, - 42.9, - 42.8, - 42.5, - 42.4, - 42.2, - 42.2, - 42.1, - 42.1, - 42.1, - 42.2, - 42.1, - 41.9, - 41.9, - 41.9, - 41.7, - 41.6, - 41.6, - 41.3, - 41.1, - 41, - 40.8, - 40.6, - 40.3, - 40.1, - 39.9, - 39.7, - 39.4, - 39.1, - 38.9, - 38.7, - 38.4, - 38.2, - 38, - 37.9, - 38, - 37.8, - 37.4, - 37.1, - 37, - 36.8, - 36.7, - 36.5, - 36.3, - 36.2, - 36, - 35.8, - 35.6, - 35.4, - 35.2, - 35.1, - 35, - 35.1, - 35.1, - 34.8, - 34.4, - 34, - 33.5, - 33.2, - 32.9, - 32.6, - 32.4, - 32.2, - 31.9, - 31.6, - 31.4, - 31.1, - 30.8, - 30.6, - 30.6, - 30.5, - 30.5, - 30.2, - 29.8, - 29.5, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 29.1, - 29, - 29, - 29, - 29, - 29, - 29, - 28.9, - 28.9, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 28, - 28.1, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.4, - 27.3, - 27.2, - 27, - 26.9, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 26, - 25.9, - 25.8, - 25.7, - 25.7, - 25.5, - 25.4, - 25.4, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25, - 25, - 24.9, - 24.9, - 24.8, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.6, - 24.7, - 24.6, - 24.7, - 24.7, - 24.7, - 24.8, - 24.9, - 25, - 25.1, - 25.2, - 25.3, - 25.4, - 25.5, - 25.7, - 25.7, - 25.9, - 25.9, - 26, - 26.2, - 26.4, - 26.5, - 26.7, - 27.1, - 27.4, - 27.7, - 27.9, - 28.1, - 28.4, - 28.7, - 28.9, - 29.1, - 29.3, - 29.5, - 29.6, - 29.8, - 29.8, - 29.8, - 29.8, - 29.9, - 29.9, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.4, - 29.4, - 29.3, - 29.3, - 32.3, - 32.6, - 32.9, - 33.2, - 33.2, - 33.2, - 33.1, - 32.9, - 32.7, - 32.6, - 32.6, - 32.5, - 32.4, - 32.3, - 32.3, - 32.3, - 32.4, - 32.5, - 32.6, - 32.7, - 32.9, - 33.1, - 33.3, - 33.4, - 33.6, - 33.8, - 34.1, - 34.4, - 34.6, - 34.8, - 35.1, - 35.6, - 36, - 36.5, - 36.7, - 36.7, - 37.1, - 37.5, - 37.8, - 38.2, - 38.6, - 39, - 39.5, - 40.2, - 40.8, - 40.9, - 41, - 41.2, - 41.2, - 41.3, - 41.6, - 42.1, - 42.6, - 42.8, - 42.9, - 43, - 43.1, - 43.3, - 43.4, - 43.9, - 44, - 43.7, - 43.5, - 43.3, - 43.1, - 43, - 42.9, - 42.7, - 42.6, - 42.4, - 42.3, - 42.2, - 42.2, - 42.1, - 41.9, - 41.9, - 41.9, - 41.7, - 41.6, - 41.5, - 41.3, - 41.1, - 41, - 40.8, - 40.5, - 40.2, - 40, - 39.8, - 39.5, - 39.3, - 39, - 38.8, - 38.6, - 38.5, - 38.3, - 38, - 37.9, - 37.8, - 37.6, - 37.3, - 37.1, - 37, - 36.9, - 36.7, - 36.6, - 36.4, - 36.2, - 36.1, - 35.9, - 35.7, - 35.6, - 35.3, - 35.1, - 35, - 35, - 35, - 34.7, - 34.3, - 34, - 33.5, - 33.1, - 32.8, - 32.6, - 32.3, - 32.1, - 31.8, - 31.5, - 31.3, - 31, - 30.7, - 30.7, - 30.6, - 30.6, - 30.3, - 30, - 29.7, - 29.6, - 29.6, - 29.6, - 29.6, - 29.5, - 29.4, - 29.3, - 29.3, - 29.3, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29.3, - 29.2, - 29, - 28.9, - 28.9, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.2, - 28.2, - 28, - 27.9, - 27.9, - 27.8, - 27.7, - 27.6, - 27.4, - 27.3, - 27.1, - 27, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.7, - 25.6, - 25.6, - 25.5, - 25.4, - 25.3, - 25.3, - 25.2, - 25.1, - 25.1, - 25, - 24.9, - 24.9, - 24.8, - 24.7, - 24.6, - 24.6, - 24.6, - 24.6, - 24.5, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.7, - 24.7, - 24.8, - 24.9, - 24.9, - 25, - 25.1, - 25.2, - 25.4, - 25.5, - 25.6, - 25.7, - 25.9, - 26, - 26.1, - 26.3, - 26.4, - 26.6, - 27.1, - 27.4, - 27.6, - 27.8, - 28, - 28.3, - 28.6, - 28.8, - 28.9, - 29.1, - 29.3, - 29.5, - 29.5, - 29.6, - 29.6, - 29.7, - 29.8, - 29.7, - 29.6, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29.1, - 29, - 29, - 28.9, - 32.8, - 33, - 33.2, - 33.6, - 33.7, - 33.5, - 33.3, - 33.1, - 33, - 32.9, - 32.8, - 32.7, - 32.6, - 32.5, - 32.4, - 32.4, - 32.4, - 32.5, - 32.6, - 32.7, - 32.9, - 33.2, - 33.3, - 33.5, - 33.7, - 33.9, - 34.2, - 34.7, - 35.3, - 35.5, - 35.3, - 35.6, - 36, - 36.3, - 36.5, - 36.8, - 37.3, - 37.8, - 38.1, - 38.6, - 39, - 39.5, - 39.9, - 40.4, - 41, - 41.1, - 41.1, - 41.4, - 41.4, - 41.6, - 42, - 43.1, - 43.5, - 43.3, - 42.9, - 43, - 43.1, - 43.2, - 43.5, - 43.8, - 44, - 43.7, - 43.6, - 43.5, - 43.3, - 43.1, - 43.2, - 43.1, - 42.8, - 42.7, - 42.4, - 42.3, - 42.2, - 42.1, - 42, - 41.9, - 41.8, - 41.7, - 41.5, - 41.3, - 41.2, - 41, - 40.9, - 40.6, - 40.4, - 40.1, - 39.9, - 39.7, - 39.5, - 39.2, - 39, - 38.7, - 38.6, - 38.5, - 38.2, - 38, - 37.8, - 37.6, - 37.4, - 37.3, - 37.2, - 37, - 36.9, - 36.7, - 36.6, - 36.4, - 36.3, - 36.2, - 36, - 35.8, - 35.7, - 35.4, - 35.2, - 35, - 34.8, - 34.6, - 34.5, - 34.2, - 33.8, - 33.4, - 33.1, - 32.8, - 32.5, - 32.3, - 32, - 31.7, - 31.4, - 31.1, - 30.9, - 30.8, - 30.8, - 30.7, - 30.5, - 30.1, - 29.9, - 29.8, - 29.8, - 29.8, - 29.8, - 29.7, - 29.6, - 29.6, - 29.6, - 29.6, - 29.5, - 29.5, - 29.5, - 29.6, - 29.6, - 29.5, - 29.5, - 29.6, - 29.6, - 29.5, - 29.4, - 29.3, - 29.3, - 29.1, - 28.9, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.5, - 28.2, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.7, - 27.5, - 27.3, - 27.2, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.6, - 25.5, - 25.4, - 25.4, - 25.3, - 25.2, - 25.1, - 25.1, - 25, - 24.9, - 24.9, - 24.8, - 24.7, - 24.6, - 24.5, - 24.4, - 24.4, - 24.3, - 24.4, - 24.4, - 24.5, - 24.5, - 24.5, - 24.5, - 24.5, - 24.5, - 24.5, - 24.7, - 24.8, - 24.9, - 25, - 25.1, - 25.2, - 25.3, - 25.4, - 25.6, - 25.8, - 25.9, - 26, - 26.2, - 26.4, - 26.6, - 26.9, - 27.2, - 27.3, - 27.6, - 27.9, - 28.2, - 28.4, - 28.6, - 28.8, - 29, - 29.2, - 29.4, - 29.3, - 29.3, - 29.4, - 29.4, - 29.5, - 29.5, - 29.3, - 29.1, - 29.1, - 29.1, - 29, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 33.2, - 33.3, - 33.5, - 33.7, - 33.9, - 33.7, - 33.6, - 33.4, - 33.2, - 33.1, - 33, - 32.9, - 32.8, - 32.6, - 32.5, - 32.4, - 32.5, - 32.5, - 32.6, - 32.7, - 33, - 33.2, - 33.4, - 33.5, - 33.9, - 34.1, - 34.5, - 35.2, - 35.7, - 35.6, - 35.5, - 35.6, - 35.9, - 36.2, - 36.6, - 37.1, - 37.7, - 38.1, - 38.6, - 38.9, - 39.4, - 40, - 40.3, - 40.4, - 40.8, - 41, - 41.1, - 41.4, - 41.6, - 41.7, - 42, - 42.5, - 42.9, - 42.8, - 42.8, - 43, - 43.1, - 43.2, - 43.3, - 43.5, - 43.6, - 43.5, - 43.4, - 43.3, - 43.2, - 43.1, - 43.2, - 43.1, - 42.9, - 42.6, - 42.4, - 42.4, - 42.2, - 42.2, - 42, - 41.8, - 41.7, - 41.6, - 41.5, - 41.2, - 41.1, - 40.9, - 40.8, - 40.5, - 40.3, - 40, - 39.8, - 39.7, - 39.5, - 39.3, - 39, - 38.8, - 38.6, - 38.4, - 38.2, - 38, - 37.8, - 37.6, - 37.5, - 37.4, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.5, - 36.4, - 36.3, - 36.1, - 35.9, - 35.7, - 35.5, - 35.2, - 35, - 34.7, - 34.4, - 34.2, - 34, - 33.7, - 33.4, - 33.1, - 32.7, - 32.4, - 32.2, - 31.9, - 31.6, - 31.2, - 31, - 31, - 30.9, - 30.9, - 30.6, - 30.3, - 30.1, - 30, - 30, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.8, - 29.8, - 29.8, - 29.8, - 29.8, - 29.8, - 29.8, - 29.9, - 29.8, - 29.8, - 29.8, - 29.8, - 29.7, - 29.7, - 29.5, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.8, - 28.7, - 28.7, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.8, - 27.6, - 27.5, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.1, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25, - 24.9, - 24.8, - 24.8, - 24.7, - 24.6, - 24.4, - 24.3, - 24.2, - 24.2, - 24.2, - 24.3, - 24.3, - 24.3, - 24.3, - 24.3, - 24.3, - 24.4, - 24.4, - 24.6, - 24.7, - 24.8, - 24.9, - 25, - 25.1, - 25.2, - 25.3, - 25.5, - 25.6, - 25.8, - 26, - 26.2, - 26.4, - 26.6, - 26.9, - 27, - 27.2, - 27.5, - 27.8, - 28.1, - 28.3, - 28.5, - 28.7, - 28.8, - 28.9, - 29, - 29, - 29, - 29.1, - 29.1, - 29.2, - 29.2, - 29.1, - 29.1, - 29, - 28.8, - 28.7, - 28.5, - 28.4, - 28.3, - 28.3, - 28.2, - 33.6, - 33.6, - 33.7, - 33.8, - 33.9, - 33.9, - 33.9, - 33.8, - 33.5, - 33.3, - 33.1, - 33, - 32.9, - 32.7, - 32.6, - 32.5, - 32.5, - 32.5, - 32.6, - 32.8, - 33, - 33.2, - 33.4, - 33.6, - 34, - 34.4, - 34.8, - 35.2, - 35.6, - 35.6, - 35.5, - 35.7, - 36.1, - 36.4, - 36.8, - 37.4, - 38, - 38.5, - 38.9, - 39.6, - 39.9, - 40.2, - 40.4, - 40.6, - 40.9, - 41.1, - 41.3, - 41.5, - 41.7, - 41.9, - 42, - 42, - 42.3, - 42.5, - 42.7, - 42.9, - 43, - 43.1, - 43.2, - 43.3, - 43.3, - 43.4, - 43.3, - 43.1, - 43, - 43, - 43, - 42.9, - 42.8, - 42.6, - 42.4, - 42.4, - 42.2, - 42.1, - 42, - 41.8, - 41.7, - 41.5, - 41.4, - 41.2, - 41, - 40.8, - 40.6, - 40.4, - 40.1, - 39.9, - 39.7, - 39.6, - 39.4, - 39.2, - 39, - 38.8, - 38.6, - 38.4, - 38.2, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.3, - 37.2, - 37, - 37, - 37.1, - 36.7, - 36.5, - 36.3, - 36.1, - 35.9, - 35.7, - 35.5, - 35.2, - 34.9, - 34.6, - 34.3, - 34.1, - 33.8, - 33.6, - 33.3, - 33, - 32.7, - 32.4, - 32.1, - 31.8, - 31.4, - 31.1, - 31, - 31, - 31, - 30.8, - 30.5, - 30.3, - 30.2, - 30.2, - 30.2, - 30.2, - 30.1, - 30, - 30, - 30, - 30, - 30, - 30, - 30, - 30, - 30.1, - 30.1, - 30.1, - 30.1, - 30.1, - 30, - 29.9, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29, - 28.9, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 27.8, - 27.6, - 27.5, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 25.9, - 25.8, - 25.6, - 25.6, - 25.5, - 25.4, - 25.2, - 25.2, - 25.1, - 25, - 25, - 24.9, - 24.8, - 24.7, - 24.6, - 24.5, - 24.3, - 24.2, - 24.1, - 24.1, - 24.1, - 24.1, - 24.2, - 24.2, - 24.2, - 24.2, - 24.2, - 24.3, - 24.3, - 24.4, - 24.6, - 24.7, - 24.8, - 25, - 25, - 25.1, - 25.2, - 25.4, - 25.5, - 25.6, - 25.9, - 26.2, - 26.4, - 26.6, - 26.7, - 26.9, - 27.2, - 27.5, - 27.7, - 27.9, - 28.2, - 28.3, - 28.6, - 28.7, - 28.8, - 28.8, - 28.7, - 28.8, - 28.8, - 28.7, - 28.8, - 28.8, - 28.7, - 28.7, - 28.6, - 28.4, - 28.3, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 33.8, - 33.9, - 34, - 34.1, - 34.1, - 34.2, - 34.1, - 34, - 33.7, - 33.4, - 33.3, - 33.1, - 33, - 32.8, - 32.7, - 32.6, - 32.6, - 32.6, - 32.8, - 33, - 33.2, - 33.3, - 33.5, - 33.7, - 34.2, - 34.8, - 35.2, - 35.2, - 35.5, - 35.7, - 35.8, - 36.1, - 36.5, - 36.8, - 37.1, - 37.7, - 38.3, - 38.8, - 39.2, - 40.1, - 40.2, - 40.3, - 40.7, - 40.9, - 41.1, - 41.3, - 41.4, - 41.6, - 41.8, - 42, - 42.3, - 42.2, - 42.3, - 42.5, - 42.6, - 42.8, - 42.9, - 43, - 43, - 43, - 43.2, - 43.3, - 43.2, - 43, - 43, - 43, - 42.9, - 42.7, - 42.6, - 42.5, - 42.3, - 42.3, - 42.2, - 42, - 41.9, - 41.7, - 41.6, - 41.4, - 41.3, - 41.1, - 40.9, - 40.8, - 40.5, - 40.3, - 40.1, - 39.8, - 39.6, - 39.5, - 39.3, - 39.1, - 38.9, - 38.7, - 38.5, - 38.3, - 38.1, - 37.9, - 37.8, - 37.7, - 37.7, - 37.6, - 37.5, - 37.3, - 37.1, - 37.1, - 37.1, - 36.7, - 36.6, - 36.3, - 36.1, - 36, - 35.7, - 35.3, - 35.1, - 34.8, - 34.5, - 34.2, - 34, - 33.7, - 33.5, - 33.2, - 32.9, - 32.6, - 32.4, - 32, - 31.6, - 31.3, - 31.2, - 31.2, - 31.1, - 30.9, - 30.7, - 30.4, - 30.3, - 30.3, - 30.3, - 30.3, - 30.3, - 30.2, - 30.2, - 30.2, - 30.2, - 30.3, - 30.3, - 30.3, - 30.3, - 30.3, - 30.4, - 30.4, - 30.4, - 30.3, - 30.4, - 30.2, - 30.1, - 30.1, - 30, - 30, - 29.9, - 29.6, - 29.5, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.8, - 28.7, - 28.6, - 28.5, - 28.3, - 28.1, - 28, - 27.9, - 27.7, - 27.6, - 27.4, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.6, - 26.5, - 26.3, - 26.2, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.4, - 25.3, - 25.2, - 25.1, - 25, - 24.9, - 24.8, - 24.7, - 24.6, - 24.5, - 24.4, - 24.2, - 24.1, - 24, - 24, - 24, - 24, - 24, - 24, - 24, - 24.1, - 24.1, - 24.2, - 24.2, - 24.3, - 24.5, - 24.6, - 24.7, - 24.8, - 24.9, - 24.9, - 25.1, - 25.2, - 25.3, - 25.4, - 25.7, - 26.2, - 26.4, - 26.6, - 26.8, - 27.1, - 27.2, - 27.5, - 27.7, - 27.9, - 28.1, - 28.3, - 28.5, - 28.6, - 28.7, - 28.7, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.6, - 34, - 34.1, - 34.2, - 34.2, - 34.3, - 34.4, - 34.3, - 34, - 33.6, - 33.5, - 33.3, - 33.2, - 33.1, - 32.9, - 32.8, - 32.7, - 32.7, - 32.8, - 33, - 33.2, - 33.3, - 33.4, - 33.6, - 33.9, - 34.4, - 35.2, - 35.5, - 35.4, - 35.5, - 35.8, - 36.5, - 36.8, - 36.9, - 37, - 37.5, - 38.4, - 38.6, - 38.8, - 39.3, - 39.8, - 40.1, - 40.5, - 40.9, - 41.1, - 41.3, - 41.5, - 41.6, - 41.8, - 41.9, - 42, - 42.2, - 42.2, - 42.4, - 42.5, - 42.5, - 42.6, - 42.7, - 42.8, - 42.8, - 42.8, - 42.9, - 43, - 42.9, - 42.8, - 43, - 43.1, - 43, - 42.7, - 42.5, - 42.4, - 42.3, - 42.2, - 42.1, - 41.9, - 41.7, - 41.5, - 41.4, - 41.3, - 41.1, - 41, - 40.8, - 40.6, - 40.4, - 40.2, - 39.9, - 39.8, - 39.6, - 39.5, - 39.3, - 39.1, - 38.9, - 38.7, - 38.5, - 38.3, - 38.1, - 37.9, - 37.8, - 37.8, - 37.8, - 37.7, - 37.5, - 37.3, - 37.2, - 37, - 36.9, - 36.9, - 36.6, - 36.3, - 36.1, - 35.9, - 35.6, - 35.2, - 34.9, - 34.6, - 34.3, - 34, - 33.8, - 33.6, - 33.4, - 33.1, - 32.8, - 32.5, - 32.2, - 31.7, - 31.4, - 31.3, - 31.3, - 31.2, - 31.1, - 30.9, - 30.6, - 30.5, - 30.4, - 30.4, - 30.4, - 30.4, - 30.3, - 30.3, - 30.3, - 30.4, - 30.5, - 30.5, - 30.6, - 30.6, - 30.6, - 30.6, - 30.6, - 30.6, - 30.7, - 30.6, - 30.6, - 30.5, - 30.4, - 30.3, - 30.2, - 30.2, - 30.1, - 29.9, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.8, - 28.7, - 28.6, - 28.4, - 28.2, - 28.1, - 27.9, - 27.8, - 27.7, - 27.5, - 27.3, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.2, - 26.1, - 25.9, - 25.8, - 25.7, - 25.5, - 25.4, - 25.3, - 25.2, - 25.1, - 25, - 24.9, - 24.8, - 24.6, - 24.5, - 24.4, - 24.3, - 24.1, - 24, - 23.9, - 23.9, - 23.9, - 23.9, - 23.9, - 23.9, - 23.9, - 23.9, - 24, - 24, - 24.1, - 24.3, - 24.4, - 24.5, - 24.6, - 24.7, - 24.8, - 24.8, - 25, - 25.1, - 25.2, - 25.4, - 25.7, - 26.1, - 26.4, - 26.6, - 26.9, - 27.3, - 27.4, - 27.5, - 27.6, - 27.8, - 28, - 28.3, - 28.3, - 28.3, - 28.3, - 28.4, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.3, - 27.2, - 34.1, - 34.2, - 34.3, - 34.4, - 34.4, - 34.7, - 34.6, - 34, - 33.7, - 33.5, - 33.4, - 33.3, - 33.2, - 33.1, - 33, - 32.9, - 32.9, - 33.1, - 33.2, - 33.3, - 33.4, - 33.5, - 33.7, - 34.1, - 34.8, - 35.6, - 35.5, - 35.7, - 36, - 36.3, - 37, - 37.2, - 37.1, - 37.2, - 38, - 38.8, - 39, - 39.1, - 39.6, - 39.9, - 40.3, - 40.6, - 41, - 41.2, - 41.4, - 41.6, - 41.7, - 41.8, - 41.9, - 42, - 42.1, - 42.2, - 42.3, - 42.4, - 42.4, - 42.5, - 42.6, - 42.7, - 42.7, - 42.7, - 42.7, - 42.7, - 42.7, - 42.7, - 43, - 43, - 42.9, - 42.6, - 42.4, - 42.3, - 42.1, - 42.1, - 42, - 41.8, - 41.6, - 41.4, - 41.3, - 41.1, - 40.9, - 40.8, - 40.7, - 40.5, - 40.2, - 40.1, - 39.9, - 39.7, - 39.5, - 39.4, - 39.2, - 39, - 38.8, - 38.6, - 38.4, - 38.3, - 38.2, - 38, - 37.9, - 37.8, - 37.8, - 37.7, - 37.5, - 37.4, - 37.3, - 37.1, - 36.9, - 36.7, - 36.4, - 36.2, - 36, - 35.7, - 35.4, - 35, - 34.7, - 34.4, - 34.1, - 33.9, - 33.7, - 33.5, - 33.3, - 33, - 32.7, - 32.3, - 31.9, - 31.5, - 31.4, - 31.4, - 31.3, - 31.2, - 31, - 30.8, - 30.7, - 30.6, - 30.6, - 30.5, - 30.5, - 30.5, - 30.5, - 30.5, - 30.6, - 30.7, - 30.8, - 30.8, - 30.9, - 30.9, - 30.9, - 30.9, - 30.9, - 30.9, - 31, - 30.9, - 30.8, - 30.8, - 30.8, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.7, - 29.5, - 29.4, - 29.3, - 29.2, - 29, - 28.9, - 28.7, - 28.6, - 28.5, - 28.3, - 28.1, - 28, - 27.8, - 27.7, - 27.5, - 27.4, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.6, - 26.4, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.4, - 25.3, - 25.2, - 25, - 24.9, - 24.7, - 24.6, - 24.4, - 24.3, - 24.2, - 24.1, - 23.9, - 23.8, - 23.8, - 23.8, - 23.8, - 23.8, - 23.8, - 23.8, - 23.8, - 23.8, - 23.9, - 23.9, - 24, - 24.2, - 24.3, - 24.4, - 24.5, - 24.6, - 24.6, - 24.8, - 24.9, - 25, - 25.2, - 25.4, - 25.7, - 26, - 26.3, - 26.6, - 26.9, - 27.4, - 27.5, - 27.6, - 27.6, - 27.8, - 27.9, - 28, - 28, - 28, - 28, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.6, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 27, - 27, - 26.9, - 26.9, - 34.2, - 34.3, - 34.4, - 34.4, - 34.5, - 34.9, - 34.6, - 34.1, - 33.8, - 33.6, - 33.5, - 33.4, - 33.3, - 33.2, - 33.1, - 33.1, - 33.2, - 33.3, - 33.4, - 33.4, - 33.5, - 33.7, - 33.9, - 34.3, - 35.1, - 35.8, - 35.6, - 36, - 36.6, - 36.6, - 36.7, - 37, - 37.1, - 37.5, - 38.1, - 38.8, - 39, - 39.2, - 39.7, - 40, - 40.4, - 40.7, - 41, - 41.2, - 41.5, - 41.6, - 41.7, - 41.8, - 42, - 42.1, - 42.2, - 42.2, - 42.2, - 42.2, - 42.3, - 42.4, - 42.5, - 42.5, - 42.6, - 42.6, - 42.6, - 42.6, - 42.7, - 42.7, - 42.8, - 42.8, - 42.7, - 42.5, - 42.3, - 42.2, - 42, - 41.9, - 41.8, - 41.6, - 41.4, - 41.2, - 41.1, - 40.9, - 40.8, - 40.7, - 40.6, - 40.4, - 40.2, - 40, - 39.8, - 39.6, - 39.4, - 39.3, - 39.1, - 39, - 38.8, - 38.6, - 38.4, - 38.3, - 38.2, - 38, - 38, - 38, - 37.8, - 37.7, - 37.5, - 37.4, - 37.2, - 37, - 36.8, - 36.6, - 36.3, - 36, - 35.8, - 35.6, - 35.3, - 34.9, - 34.5, - 34.2, - 34, - 33.8, - 33.6, - 33.4, - 33.2, - 32.9, - 32.5, - 32.1, - 31.7, - 31.5, - 31.5, - 31.4, - 31.3, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.7, - 30.6, - 30.7, - 30.7, - 30.7, - 30.8, - 30.9, - 31, - 31, - 31.1, - 31.1, - 31.2, - 31.2, - 31.2, - 31.2, - 31.1, - 31.1, - 31, - 31, - 31, - 31, - 30.8, - 30.7, - 30.5, - 30.4, - 30.2, - 30.2, - 30.1, - 29.9, - 29.6, - 29.5, - 29.3, - 29.2, - 29.1, - 28.9, - 28.7, - 28.6, - 28.5, - 28.3, - 28.1, - 28, - 27.8, - 27.7, - 27.6, - 27.4, - 27.2, - 27.1, - 26.9, - 26.8, - 26.6, - 26.5, - 26.3, - 26.2, - 26.1, - 25.9, - 25.8, - 25.7, - 25.5, - 25.4, - 25.2, - 25, - 24.9, - 24.7, - 24.6, - 24.4, - 24.3, - 24.2, - 24.1, - 23.9, - 23.8, - 23.8, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.8, - 23.8, - 23.8, - 24, - 24.1, - 24.2, - 24.4, - 24.4, - 24.5, - 24.5, - 24.6, - 24.8, - 25, - 25.2, - 25.5, - 25.9, - 26.1, - 26.4, - 26.7, - 26.9, - 27.2, - 27.4, - 27.5, - 27.6, - 27.7, - 27.9, - 28, - 28, - 27.9, - 27.7, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 26.9, - 26.9, - 26.8, - 26.7, - 26.7, - 26.6, - 26.6, - 26.6, - 26.6, - 34.3, - 34.4, - 34.4, - 34.5, - 34.9, - 35.5, - 34.7, - 34.3, - 33.9, - 33.7, - 33.6, - 33.6, - 33.5, - 33.4, - 33.4, - 33.4, - 33.4, - 33.5, - 33.5, - 33.6, - 33.8, - 34, - 34.3, - 34.6, - 35, - 35.4, - 35.7, - 36.1, - 36.7, - 36.8, - 36.8, - 37, - 37.3, - 37.7, - 38.5, - 39.3, - 39.6, - 39.5, - 39.7, - 40.1, - 40.4, - 40.7, - 40.9, - 41.2, - 41.4, - 41.5, - 41.6, - 41.8, - 41.9, - 42, - 42.1, - 42.1, - 42.1, - 42.1, - 42.2, - 42.3, - 42.3, - 42.4, - 42.4, - 42.5, - 42.7, - 42.8, - 42.7, - 42.6, - 42.6, - 42.6, - 42.6, - 42.4, - 42.1, - 42, - 41.9, - 41.8, - 41.6, - 41.4, - 41.2, - 41, - 40.9, - 40.8, - 40.6, - 40.5, - 40.4, - 40.3, - 40.1, - 39.9, - 39.8, - 39.6, - 39.3, - 39.1, - 39, - 38.9, - 38.7, - 38.6, - 38.5, - 38.4, - 38.2, - 38.1, - 38, - 37.9, - 37.9, - 37.7, - 37.4, - 37.3, - 37.1, - 36.9, - 36.6, - 36.4, - 36.1, - 35.9, - 35.6, - 35.4, - 35.1, - 34.7, - 34.4, - 34.1, - 33.9, - 33.7, - 33.5, - 33.3, - 33, - 32.6, - 32.2, - 31.8, - 31.5, - 31.5, - 31.4, - 31.4, - 31.3, - 31.2, - 31.1, - 31.1, - 30.9, - 30.8, - 30.8, - 30.8, - 30.8, - 30.9, - 31, - 31.1, - 31.2, - 31.2, - 31.3, - 31.3, - 31.3, - 31.4, - 31.4, - 31.5, - 31.4, - 31.3, - 31.3, - 31.2, - 31.1, - 31.1, - 31.1, - 31, - 30.7, - 30.6, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 29.7, - 29.5, - 29.3, - 29.2, - 29.1, - 28.9, - 28.7, - 28.6, - 28.5, - 28.3, - 28.1, - 27.9, - 27.7, - 27.6, - 27.6, - 27.4, - 27.2, - 27, - 26.9, - 26.7, - 26.6, - 26.5, - 26.3, - 26.2, - 26, - 25.9, - 25.8, - 25.6, - 25.4, - 25.3, - 25.1, - 24.9, - 24.8, - 24.7, - 24.5, - 24.4, - 24.2, - 24.1, - 23.9, - 23.8, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.8, - 23.9, - 24, - 24.2, - 24.2, - 24.3, - 24.4, - 24.5, - 24.5, - 24.7, - 24.9, - 25.3, - 25.7, - 26.1, - 26.4, - 26.6, - 26.7, - 27, - 27.1, - 27.3, - 27.5, - 27.6, - 27.6, - 27.8, - 27.9, - 27.8, - 27.6, - 27.4, - 27.3, - 27.3, - 27.2, - 27.1, - 27, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.2, - 34.3, - 34.4, - 34.5, - 34.5, - 35.3, - 35.7, - 34.7, - 34.3, - 34, - 33.8, - 33.7, - 33.7, - 33.6, - 33.6, - 33.6, - 33.6, - 33.7, - 33.7, - 33.8, - 33.9, - 34.1, - 34.3, - 34.6, - 35, - 35.2, - 35.6, - 35.9, - 36.2, - 36.6, - 36.8, - 37.2, - 37.4, - 37.7, - 38.3, - 39, - 39.8, - 40.1, - 39.8, - 39.8, - 40.1, - 40.4, - 40.7, - 40.9, - 41.1, - 41.2, - 41.4, - 41.5, - 41.7, - 41.8, - 41.9, - 41.9, - 41.9, - 42, - 42, - 42.1, - 42.2, - 42.2, - 42.3, - 42.4, - 42.6, - 43.1, - 43.5, - 43, - 42.8, - 42.5, - 42.4, - 42.3, - 42.1, - 42, - 41.8, - 41.7, - 41.6, - 41.4, - 41.2, - 41, - 40.9, - 40.7, - 40.6, - 40.5, - 40.4, - 40.2, - 40.1, - 39.9, - 39.8, - 39.7, - 39.5, - 39.3, - 39.1, - 39, - 38.9, - 38.8, - 38.7, - 38.6, - 38.5, - 38.3, - 38.2, - 38.1, - 37.9, - 37.8, - 37.6, - 37.3, - 37.2, - 37, - 36.6, - 36.3, - 36.1, - 35.9, - 35.6, - 35.4, - 35.2, - 34.9, - 34.5, - 34.2, - 34, - 33.8, - 33.6, - 33.3, - 33.1, - 32.7, - 32.3, - 31.8, - 31.6, - 31.5, - 31.4, - 31.4, - 31.4, - 31.3, - 31.2, - 31.2, - 31.1, - 31, - 30.9, - 30.9, - 30.9, - 31, - 31.1, - 31.2, - 31.3, - 31.4, - 31.4, - 31.4, - 31.4, - 31.5, - 31.6, - 31.6, - 31.7, - 31.7, - 31.4, - 31.4, - 31.4, - 31.3, - 31.1, - 31.1, - 31, - 30.8, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 29.9, - 29.7, - 29.5, - 29.3, - 29.1, - 29.1, - 29, - 28.8, - 28.6, - 28.4, - 28.2, - 28, - 27.9, - 27.7, - 27.6, - 27.5, - 27.3, - 27.1, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.9, - 25.7, - 25.5, - 25.4, - 25.2, - 25, - 24.8, - 24.7, - 24.6, - 24.4, - 24.2, - 24.1, - 23.9, - 23.8, - 23.7, - 23.6, - 23.7, - 23.7, - 23.7, - 23.7, - 23.6, - 23.6, - 23.6, - 23.6, - 23.6, - 23.7, - 23.7, - 23.8, - 23.9, - 24.1, - 24.2, - 24.2, - 24.3, - 24.4, - 24.5, - 24.6, - 24.9, - 25.4, - 25.8, - 26.2, - 26.5, - 26.7, - 26.7, - 26.9, - 27.1, - 27.3, - 27.4, - 27.4, - 27.4, - 27.4, - 27.5, - 27.5, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.2, - 26.2, - 26.1, - 26.1, - 26, - 26, - 25.9, - 25.9, - 25.9, - 34.3, - 34.4, - 34.5, - 34.5, - 35, - 35.5, - 34.6, - 34.3, - 34, - 33.9, - 33.8, - 33.8, - 33.8, - 33.8, - 33.8, - 33.8, - 33.9, - 34, - 34.1, - 34.2, - 34.4, - 34.7, - 35, - 35.3, - 35.6, - 35.9, - 36.2, - 36.5, - 36.9, - 37.2, - 37.7, - 38, - 38.4, - 38.9, - 39.3, - 39.8, - 39.9, - 39.7, - 39.8, - 40.1, - 40.4, - 40.6, - 40.8, - 41, - 41.1, - 41.3, - 41.4, - 41.5, - 41.6, - 41.7, - 41.9, - 41.8, - 41.8, - 41.9, - 42, - 42.1, - 42.2, - 42.3, - 42.5, - 42.9, - 43.2, - 43.1, - 42.8, - 42.6, - 42.5, - 42.3, - 42.1, - 42, - 41.8, - 41.6, - 41.5, - 41.3, - 41.2, - 41, - 40.8, - 40.7, - 40.6, - 40.4, - 40.4, - 40.2, - 40.1, - 39.9, - 39.8, - 39.6, - 39.7, - 39.5, - 39.5, - 39.2, - 39, - 38.9, - 38.8, - 38.7, - 38.6, - 38.5, - 38.3, - 38.2, - 38.1, - 37.8, - 37.7, - 37.5, - 37.2, - 37, - 36.9, - 36.5, - 36.1, - 35.9, - 35.7, - 35.5, - 35.2, - 35, - 34.7, - 34.4, - 34.1, - 33.9, - 33.7, - 33.4, - 33.1, - 32.7, - 32.3, - 31.9, - 31.6, - 31.6, - 31.5, - 31.5, - 31.4, - 31.4, - 31.4, - 31.3, - 31.3, - 31.2, - 31, - 31, - 31, - 31.1, - 31.2, - 31.3, - 31.3, - 31.4, - 31.4, - 31.5, - 31.5, - 31.5, - 31.5, - 31.8, - 31.8, - 31.8, - 31.7, - 31.6, - 31.5, - 31.5, - 31.4, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.5, - 30.3, - 30.1, - 30, - 29.8, - 29.6, - 29.4, - 29.2, - 29, - 29, - 28.8, - 28.7, - 28.4, - 28.3, - 28.1, - 27.9, - 27.7, - 27.6, - 27.5, - 27.4, - 27.2, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.8, - 25.7, - 25.5, - 25.3, - 25.1, - 24.9, - 24.8, - 24.6, - 24.5, - 24.3, - 24.1, - 24, - 23.9, - 23.6, - 23.5, - 23.5, - 23.6, - 23.6, - 23.6, - 23.6, - 23.5, - 23.5, - 23.6, - 23.6, - 23.6, - 23.6, - 23.6, - 23.7, - 23.8, - 24, - 24.1, - 24.2, - 24.3, - 24.4, - 24.4, - 24.6, - 25.1, - 25.6, - 25.9, - 26.2, - 26.4, - 26.5, - 26.7, - 26.8, - 26.9, - 27.1, - 27.2, - 27.3, - 27.2, - 27.1, - 27.1, - 27.1, - 26.8, - 26.7, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.8, - 25.7, - 25.7, - 25.6, - 25.6, - 25.6, - 25.6, - 34.2, - 34.3, - 34.4, - 34.4, - 34.4, - 34.6, - 34.5, - 34.3, - 34.2, - 34, - 34, - 34, - 34, - 34, - 34, - 34.1, - 34.1, - 34.3, - 34.4, - 34.6, - 34.8, - 35.1, - 35.4, - 35.6, - 36, - 36.2, - 36.5, - 36.9, - 37.2, - 37.5, - 37.9, - 38.1, - 38.5, - 39.3, - 39.7, - 39.8, - 39.9, - 39.7, - 39.9, - 40.1, - 40.3, - 40.5, - 40.7, - 40.9, - 41, - 41.2, - 41.3, - 41.4, - 41.5, - 41.7, - 41.8, - 41.8, - 41.9, - 41.9, - 42.1, - 42.1, - 42.3, - 42.4, - 42.7, - 43, - 43, - 42.7, - 42.5, - 42.4, - 42.4, - 42.3, - 42, - 41.8, - 41.6, - 41.5, - 41.3, - 41.1, - 41, - 40.8, - 40.7, - 40.5, - 40.4, - 40.3, - 40.2, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.6, - 39.5, - 39.5, - 39.2, - 39.1, - 38.9, - 38.8, - 38.7, - 38.5, - 38.4, - 38.3, - 38.1, - 38, - 37.7, - 37.5, - 37.3, - 37.1, - 36.9, - 36.7, - 36.3, - 36, - 35.8, - 35.6, - 35.3, - 35.1, - 34.8, - 34.6, - 34.3, - 34.1, - 33.8, - 33.5, - 33.2, - 32.8, - 32.3, - 31.9, - 31.7, - 31.6, - 31.6, - 31.6, - 31.5, - 31.6, - 31.5, - 31.4, - 31.4, - 31.2, - 31.1, - 31.1, - 31.1, - 31.1, - 31.2, - 31.3, - 31.4, - 31.4, - 31.4, - 31.4, - 31.5, - 31.5, - 31.6, - 31.5, - 31.6, - 31.7, - 31.7, - 31.7, - 31.6, - 31.4, - 31.4, - 31.3, - 31.1, - 31, - 30.8, - 30.7, - 30.6, - 30.4, - 30.2, - 30, - 29.9, - 29.8, - 29.5, - 29.3, - 29.1, - 28.9, - 28.8, - 28.6, - 28.5, - 28.3, - 28.1, - 28, - 27.8, - 27.6, - 27.5, - 27.5, - 27.3, - 27.2, - 27, - 26.8, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.1, - 25.9, - 25.8, - 25.6, - 25.4, - 25.2, - 25, - 24.8, - 24.6, - 24.5, - 24.4, - 24.2, - 24, - 23.9, - 23.7, - 23.5, - 23.4, - 23.4, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.5, - 23.6, - 23.7, - 23.8, - 23.9, - 23.9, - 24.1, - 24.1, - 24.3, - 24.3, - 24.5, - 25.1, - 25.6, - 26, - 26.1, - 26.3, - 26.4, - 26.6, - 26.7, - 26.8, - 26.9, - 27, - 27.2, - 27.1, - 26.9, - 26.7, - 26.7, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.5, - 25.5, - 25.4, - 25.3, - 25.3, - 25.2, - 25.2, - 25.2, - 34, - 34.2, - 34.6, - 34.4, - 34.3, - 34.3, - 34.4, - 34.3, - 34.2, - 34.2, - 34.2, - 34.2, - 34.2, - 34.2, - 34.3, - 34.4, - 34.5, - 34.7, - 34.8, - 35, - 35.2, - 35.4, - 35.7, - 36, - 36.3, - 36.6, - 36.9, - 37.2, - 37.5, - 37.7, - 38.1, - 38.2, - 38.4, - 38.9, - 39.6, - 39.8, - 39.9, - 39.8, - 39.9, - 40.1, - 40.2, - 40.4, - 40.6, - 40.8, - 41, - 41.1, - 41.3, - 41.6, - 41.5, - 41.6, - 41.7, - 41.9, - 42, - 42, - 42.1, - 42.2, - 42.4, - 42.4, - 42.5, - 42.6, - 42.6, - 42.5, - 42.4, - 42.4, - 42.3, - 42.1, - 41.9, - 41.7, - 41.5, - 41.3, - 41.2, - 41, - 40.8, - 40.6, - 40.5, - 40.3, - 40.2, - 40, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.6, - 39.5, - 39.4, - 39.3, - 39.2, - 39.1, - 38.9, - 38.8, - 38.6, - 38.6, - 38.4, - 38.3, - 38.1, - 37.9, - 37.6, - 37.3, - 37.1, - 36.9, - 36.7, - 36.4, - 36.1, - 35.9, - 35.6, - 35.4, - 35.1, - 34.8, - 34.6, - 34.4, - 34.2, - 34, - 33.7, - 33.4, - 32.9, - 32.4, - 32, - 31.7, - 31.7, - 31.6, - 31.6, - 31.6, - 31.7, - 31.7, - 31.5, - 31.4, - 31.3, - 31.1, - 31.1, - 31.1, - 31.1, - 31.2, - 31.3, - 31.3, - 31.4, - 31.4, - 31.4, - 31.4, - 31.5, - 31.6, - 31.5, - 31.6, - 31.6, - 31.6, - 31.6, - 31.5, - 31.4, - 31.3, - 31.2, - 31.1, - 31, - 30.9, - 30.7, - 30.6, - 30.4, - 30.3, - 30.1, - 29.9, - 29.8, - 29.6, - 29.4, - 29.2, - 29, - 28.7, - 28.6, - 28.4, - 28.2, - 28.1, - 27.9, - 27.8, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.3, - 26.2, - 26, - 25.8, - 25.7, - 25.5, - 25.3, - 25.1, - 24.9, - 24.7, - 24.5, - 24.4, - 24.2, - 24.1, - 23.9, - 23.7, - 23.6, - 23.4, - 23.2, - 23.3, - 23.4, - 23.4, - 23.4, - 23.4, - 23.4, - 23.4, - 23.4, - 23.4, - 23.4, - 23.5, - 23.6, - 23.7, - 23.8, - 23.8, - 23.9, - 23.9, - 24, - 24.2, - 24.2, - 24.5, - 25.1, - 25.5, - 25.8, - 26, - 26.1, - 26.3, - 26.5, - 26.6, - 26.7, - 26.8, - 26.8, - 26.9, - 26.9, - 26.7, - 26.5, - 26.3, - 26.2, - 26, - 25.9, - 25.8, - 25.8, - 25.7, - 25.5, - 25.4, - 25.3, - 25.2, - 25.2, - 25.1, - 25.1, - 25, - 25, - 24.9, - 24.9, - 24.9, - 24.9, - 33.9, - 34, - 34.3, - 34.2, - 34.2, - 34.3, - 34.4, - 34.4, - 34.3, - 34.3, - 34.3, - 34.4, - 34.4, - 34.5, - 34.6, - 34.7, - 34.8, - 35, - 35.2, - 35.4, - 35.6, - 35.8, - 36.1, - 36.4, - 36.8, - 37.1, - 37.3, - 37.6, - 37.9, - 38.1, - 38.2, - 38.4, - 38.5, - 38.8, - 39.1, - 39.4, - 39.6, - 39.7, - 39.8, - 40, - 40.2, - 40.4, - 40.6, - 40.7, - 40.9, - 41, - 41.3, - 41.8, - 41.7, - 41.6, - 41.7, - 42, - 42, - 41.9, - 41.9, - 42, - 42.1, - 42.2, - 42.3, - 42.4, - 42.4, - 42.4, - 42.3, - 42.2, - 42.1, - 42, - 41.8, - 41.6, - 41.4, - 41.2, - 41, - 40.8, - 40.7, - 40.5, - 40.3, - 40.2, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.6, - 39.5, - 39.5, - 39.4, - 39.4, - 39.3, - 39.2, - 39.1, - 39, - 38.8, - 38.7, - 38.6, - 38.4, - 38.3, - 38.2, - 37.9, - 37.5, - 37.2, - 37, - 36.8, - 36.5, - 36.3, - 36, - 35.7, - 35.5, - 35.2, - 34.9, - 34.6, - 34.4, - 34.2, - 34, - 33.7, - 33.5, - 33, - 32.5, - 32, - 31.8, - 31.7, - 31.7, - 31.7, - 31.7, - 31.7, - 31.7, - 31.7, - 31.5, - 31.2, - 31.1, - 31, - 31, - 31.1, - 31.1, - 31.2, - 31.3, - 31.4, - 31.4, - 31.5, - 31.4, - 31.4, - 31.5, - 31.5, - 31.5, - 31.5, - 31.5, - 31.5, - 31.5, - 31.3, - 31.2, - 31.2, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.4, - 30.3, - 30.1, - 29.9, - 29.8, - 29.6, - 29.5, - 29.3, - 29.1, - 28.9, - 28.6, - 28.4, - 28.2, - 28.1, - 28, - 27.8, - 27.7, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 26.9, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 25.8, - 25.7, - 25.5, - 25.4, - 25.2, - 25, - 24.8, - 24.6, - 24.5, - 24.3, - 24.1, - 23.9, - 23.7, - 23.6, - 23.4, - 23.2, - 23.1, - 23.2, - 23.2, - 23.3, - 23.3, - 23.3, - 23.3, - 23.4, - 23.4, - 23.4, - 23.3, - 23.4, - 23.5, - 23.7, - 23.7, - 23.8, - 23.8, - 23.9, - 24, - 24.1, - 24.2, - 24.6, - 25.1, - 25.4, - 25.6, - 25.8, - 26, - 26.2, - 26.3, - 26.5, - 26.7, - 26.7, - 26.5, - 26.5, - 26.5, - 26.3, - 26.2, - 26, - 25.8, - 25.7, - 25.5, - 25.4, - 25.3, - 25.3, - 25.1, - 25, - 24.9, - 24.9, - 24.8, - 24.8, - 24.7, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.7, - 33.7, - 33.8, - 33.9, - 34, - 34, - 34.1, - 34.3, - 34.4, - 34.4, - 34.4, - 34.5, - 34.6, - 34.7, - 34.7, - 34.9, - 35, - 35.2, - 35.3, - 35.6, - 35.8, - 36, - 36.2, - 36.5, - 36.8, - 37.2, - 37.5, - 37.7, - 38, - 38.3, - 38.5, - 38.6, - 38.7, - 38.8, - 38.9, - 39.1, - 39.3, - 39.5, - 39.6, - 39.8, - 39.9, - 40.1, - 40.3, - 40.5, - 40.8, - 40.8, - 41, - 41.2, - 41.6, - 41.8, - 41.6, - 41.6, - 41.7, - 41.8, - 41.8, - 41.8, - 41.8, - 41.9, - 42, - 42.1, - 42.2, - 42.3, - 42.4, - 42.5, - 42.4, - 42.2, - 41.9, - 41.6, - 41.4, - 41.2, - 41, - 40.9, - 40.7, - 40.5, - 40.3, - 40.2, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.6, - 39.5, - 39.5, - 39.4, - 39.4, - 39.3, - 39.2, - 39.2, - 39.1, - 39, - 38.8, - 38.7, - 38.6, - 38.5, - 38.3, - 38.2, - 37.8, - 37.4, - 37.1, - 36.9, - 36.6, - 36.3, - 36.1, - 35.8, - 35.6, - 35.2, - 35, - 34.7, - 34.4, - 34.3, - 34.1, - 33.8, - 33.5, - 33.1, - 32.6, - 32.1, - 31.8, - 31.7, - 31.7, - 31.7, - 31.8, - 31.9, - 31.9, - 31.8, - 31.7, - 31.4, - 31.2, - 31, - 31, - 31, - 31.1, - 31.1, - 31.2, - 31.3, - 31.4, - 31.5, - 31.5, - 31.4, - 31.4, - 31.4, - 31.4, - 31.4, - 31.4, - 31.3, - 31.2, - 31.1, - 31.1, - 30.9, - 30.9, - 30.9, - 30.7, - 30.6, - 30.5, - 30.4, - 30.2, - 30.1, - 29.9, - 29.8, - 29.6, - 29.5, - 29.3, - 29.1, - 28.9, - 28.8, - 28.5, - 28.2, - 28.1, - 28, - 27.9, - 27.7, - 27.6, - 27.4, - 27.3, - 27.1, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.1, - 26, - 25.9, - 25.7, - 25.6, - 25.4, - 25.2, - 25.1, - 24.9, - 24.7, - 24.5, - 24.4, - 24.2, - 24, - 23.8, - 23.6, - 23.4, - 23.2, - 23, - 23, - 23, - 23.1, - 23.2, - 23.2, - 23.2, - 23.2, - 23.3, - 23.3, - 23.2, - 23.2, - 23.3, - 23.4, - 23.6, - 23.7, - 23.7, - 23.8, - 23.9, - 23.9, - 24, - 24.2, - 24.7, - 25.1, - 25.4, - 25.6, - 25.8, - 25.9, - 26, - 26.1, - 26.4, - 26.5, - 26.5, - 26.5, - 26.5, - 26.2, - 26, - 25.7, - 25.6, - 25.5, - 25.3, - 25.2, - 25.1, - 25, - 24.9, - 24.8, - 24.6, - 24.5, - 24.5, - 24.4, - 24.4, - 24.3, - 24.3, - 24.3, - 24.3, - 24.3, - 24.3, - 24.4, - 33.5, - 33.7, - 33.7, - 33.8, - 34, - 34.2, - 34.5, - 34.6, - 34.6, - 34.6, - 34.7, - 34.9, - 34.9, - 35, - 35.1, - 35.3, - 35.5, - 35.7, - 35.9, - 36.2, - 36.4, - 36.7, - 37, - 37.2, - 37.5, - 37.8, - 38.1, - 38.4, - 38.6, - 38.8, - 38.8, - 38.9, - 38.9, - 39, - 39.2, - 39.3, - 39.5, - 39.6, - 39.7, - 39.9, - 40, - 40.2, - 40.4, - 40.6, - 40.6, - 40.8, - 41, - 41.3, - 41.5, - 41.5, - 41.5, - 41.6, - 41.6, - 41.7, - 41.7, - 41.7, - 41.8, - 41.9, - 42, - 42, - 42.1, - 42.3, - 42.5, - 42.3, - 42.1, - 41.8, - 41.6, - 41.2, - 41, - 40.8, - 40.7, - 40.5, - 40.3, - 40.2, - 40, - 39.9, - 39.8, - 39.7, - 39.7, - 39.6, - 39.5, - 39.4, - 39.4, - 39.4, - 39.4, - 39.3, - 39.3, - 39.2, - 39.1, - 39, - 38.9, - 38.8, - 38.6, - 38.4, - 38.2, - 37.9, - 37.5, - 37.2, - 37, - 36.7, - 36.4, - 36.2, - 36, - 35.6, - 35.3, - 35, - 34.8, - 34.5, - 34.3, - 34.1, - 33.9, - 33.6, - 33.2, - 32.7, - 32.1, - 31.8, - 31.8, - 31.8, - 31.8, - 31.9, - 32, - 32.1, - 32.1, - 32, - 31.7, - 31.4, - 31.1, - 31, - 30.9, - 31, - 31, - 31, - 31.1, - 31.2, - 31.5, - 31.6, - 31.5, - 31.4, - 31.3, - 31.2, - 31.1, - 31.2, - 31.2, - 31.1, - 31, - 31, - 31.1, - 30.8, - 30.7, - 30.7, - 30.6, - 30.4, - 30.3, - 30.1, - 30, - 29.9, - 29.7, - 29.7, - 29.5, - 29.3, - 29.1, - 28.9, - 28.7, - 28.5, - 28.3, - 28.1, - 28, - 27.9, - 27.8, - 27.6, - 27.5, - 27.3, - 27.1, - 27, - 27, - 26.9, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 26, - 25.8, - 25.6, - 25.5, - 25.3, - 25.1, - 24.9, - 24.8, - 24.6, - 24.5, - 24.3, - 24.1, - 23.9, - 23.7, - 23.4, - 23.3, - 23, - 22.9, - 22.8, - 22.9, - 23, - 23.1, - 23.1, - 23.1, - 23.1, - 23.2, - 23.2, - 23.2, - 23.2, - 23.2, - 23.3, - 23.4, - 23.6, - 23.6, - 23.7, - 23.8, - 23.8, - 23.9, - 24.1, - 24.7, - 25.1, - 25.3, - 25.6, - 25.7, - 25.8, - 25.9, - 25.9, - 26.1, - 26.1, - 26.1, - 26.2, - 26.2, - 25.9, - 25.6, - 25.4, - 25.3, - 25.2, - 25.1, - 24.9, - 24.8, - 24.7, - 24.6, - 24.4, - 24.2, - 24.1, - 24.1, - 24.1, - 24, - 24, - 24, - 24, - 24, - 24, - 24.1, - 24.2, - 33.5, - 33.6, - 33.6, - 33.8, - 34, - 34.3, - 34.7, - 34.8, - 34.9, - 34.9, - 35.1, - 35.3, - 35.2, - 35.3, - 35.4, - 35.7, - 36, - 36.2, - 36.3, - 36.5, - 36.7, - 37, - 37.3, - 37.6, - 37.8, - 38.1, - 38.3, - 38.6, - 38.8, - 38.9, - 38.9, - 39, - 39, - 39.1, - 39.2, - 39.3, - 39.4, - 39.6, - 39.8, - 39.9, - 40, - 40.1, - 40.2, - 40.3, - 40.4, - 40.6, - 41, - 41.4, - 41.4, - 41.5, - 41.4, - 41.5, - 41.4, - 41.5, - 41.5, - 41.6, - 41.7, - 41.8, - 41.8, - 41.9, - 41.9, - 42, - 42.1, - 42, - 41.8, - 41.6, - 41.4, - 41, - 40.8, - 40.8, - 40.6, - 40.3, - 40.2, - 40.1, - 40, - 39.8, - 39.7, - 39.7, - 39.6, - 39.5, - 39.5, - 39.5, - 39.4, - 39.4, - 39.4, - 39.3, - 39.3, - 39.2, - 39.2, - 39.1, - 39, - 38.8, - 38.8, - 38.5, - 38, - 37.6, - 37.3, - 37, - 36.8, - 36.5, - 36.2, - 36, - 35.8, - 35.4, - 35.1, - 34.8, - 34.6, - 34.4, - 34.2, - 33.9, - 33.6, - 33.3, - 32.8, - 32.2, - 31.9, - 31.9, - 31.8, - 31.9, - 32, - 32.1, - 32.2, - 32.3, - 32.2, - 32, - 31.7, - 31.3, - 31.1, - 31, - 30.9, - 30.9, - 30.9, - 30.9, - 31, - 31.1, - 31.6, - 31.5, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.9, - 30.9, - 30.9, - 30.8, - 30.8, - 30.7, - 30.5, - 30.5, - 30.5, - 30.4, - 30.3, - 30.1, - 29.9, - 29.8, - 29.7, - 29.5, - 29.4, - 29.3, - 29, - 28.8, - 28.6, - 28.4, - 28.2, - 28.1, - 28, - 27.9, - 27.8, - 27.6, - 27.4, - 27.3, - 27.2, - 27, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.4, - 26.4, - 26.2, - 26.1, - 25.9, - 25.8, - 25.6, - 25.4, - 25.1, - 25, - 24.8, - 24.7, - 24.5, - 24.4, - 24.2, - 24, - 23.8, - 23.6, - 23.3, - 23.1, - 22.9, - 22.7, - 22.8, - 22.8, - 22.9, - 22.9, - 23, - 23, - 23, - 23.1, - 23.1, - 23.1, - 23.1, - 23.2, - 23.2, - 23.2, - 23.3, - 23.5, - 23.6, - 23.7, - 23.7, - 23.8, - 24.1, - 24.7, - 25, - 25.2, - 25.4, - 25.6, - 25.6, - 25.6, - 25.7, - 25.8, - 25.8, - 25.8, - 25.7, - 25.5, - 25.5, - 25.3, - 25.1, - 24.9, - 24.9, - 24.8, - 24.7, - 24.6, - 24.4, - 24.3, - 24.1, - 23.9, - 23.9, - 23.8, - 23.8, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.7, - 23.8, - 24, - 33.5, - 33.6, - 33.7, - 33.9, - 34.2, - 34.5, - 34.7, - 34.9, - 35.2, - 35.3, - 35.5, - 35.5, - 35.4, - 35.6, - 35.8, - 36.1, - 36.3, - 36.5, - 36.7, - 36.9, - 37.1, - 37.4, - 37.6, - 37.9, - 38.1, - 38.3, - 38.5, - 38.7, - 38.8, - 38.9, - 38.9, - 39, - 39, - 39.1, - 39.1, - 39.2, - 39.4, - 39.9, - 40, - 40, - 40, - 40.1, - 40.1, - 40.3, - 40.3, - 40.4, - 40.9, - 41.1, - 41.1, - 41.2, - 41.1, - 41.2, - 41.3, - 41.4, - 41.4, - 41.6, - 42, - 42.1, - 41.8, - 41.7, - 41.7, - 41.7, - 41.8, - 41.8, - 41.7, - 41.4, - 41.1, - 40.8, - 40.7, - 40.6, - 40.5, - 40.3, - 40.1, - 40.1, - 39.9, - 39.7, - 39.7, - 39.6, - 39.6, - 39.5, - 39.5, - 39.5, - 39.5, - 39.4, - 39.3, - 39.3, - 39.3, - 39.3, - 39.3, - 39.2, - 39, - 38.9, - 38.8, - 38.5, - 37.8, - 37.4, - 37.2, - 36.9, - 36.6, - 36.3, - 36, - 35.9, - 35.6, - 35.2, - 34.9, - 34.6, - 34.4, - 34.2, - 33.9, - 33.7, - 33.2, - 32.8, - 32.3, - 32, - 31.9, - 31.9, - 31.9, - 32.1, - 32.2, - 32.4, - 32.5, - 32.4, - 32.1, - 31.8, - 31.6, - 31.3, - 31.1, - 30.9, - 30.8, - 30.8, - 30.8, - 30.7, - 30.8, - 30.8, - 31.2, - 31.1, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.5, - 30.4, - 30.4, - 30.4, - 30.4, - 30.2, - 30.2, - 30.1, - 30.1, - 30, - 30, - 29.9, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29, - 28.7, - 28.4, - 28.2, - 28.1, - 28, - 27.8, - 27.7, - 27.7, - 27.5, - 27.4, - 27.2, - 27.1, - 27.1, - 27, - 26.9, - 26.8, - 26.8, - 26.7, - 26.5, - 26.4, - 26.4, - 26.3, - 26.1, - 25.9, - 25.7, - 25.4, - 25.2, - 25, - 24.9, - 24.7, - 24.5, - 24.4, - 24.2, - 24, - 23.8, - 23.7, - 23.4, - 23.2, - 23, - 22.8, - 22.6, - 22.7, - 22.7, - 22.7, - 22.8, - 22.8, - 22.8, - 22.9, - 23, - 23, - 23.1, - 23.1, - 23.2, - 23.2, - 23.2, - 23.2, - 23.4, - 23.5, - 23.5, - 23.6, - 23.7, - 24.1, - 24.5, - 24.8, - 25, - 25.1, - 25.2, - 25.2, - 25.3, - 25.4, - 25.4, - 25.4, - 25.4, - 25.4, - 25.2, - 25.2, - 25, - 24.8, - 24.6, - 24.5, - 24.5, - 24.4, - 24.2, - 24, - 23.9, - 23.7, - 23.6, - 23.5, - 23.5, - 23.4, - 23.4, - 23.3, - 23.3, - 23.4, - 23.4, - 23.5, - 23.6, - 23.8, - 33.6, - 33.9, - 33.9, - 34, - 34.3, - 34.5, - 34.7, - 34.9, - 35.3, - 35.6, - 35.7, - 35.7, - 35.7, - 35.9, - 36, - 36.2, - 36.6, - 36.8, - 37.1, - 37.2, - 37.4, - 37.7, - 38, - 38.2, - 38.4, - 38.5, - 38.6, - 38.8, - 38.8, - 38.9, - 38.9, - 38.9, - 39, - 39, - 39.2, - 39.2, - 39.7, - 40.4, - 39.8, - 39.8, - 39.9, - 40, - 40, - 40.2, - 40.2, - 40.3, - 40.5, - 40.7, - 40.9, - 41, - 40.9, - 41, - 41.1, - 41.3, - 41.4, - 41.6, - 42.1, - 42.1, - 41.7, - 41.7, - 41.7, - 41.7, - 41.7, - 41.6, - 41.4, - 41.2, - 40.8, - 40.7, - 40.5, - 40.4, - 40.3, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.6, - 39.6, - 39.6, - 39.5, - 39.5, - 39.5, - 39.4, - 39.4, - 39.3, - 39.3, - 39.3, - 39.2, - 39.1, - 38.9, - 38.7, - 38.5, - 38.2, - 37.7, - 37.3, - 37, - 36.7, - 36.4, - 36.1, - 35.9, - 35.7, - 35.4, - 35, - 34.7, - 34.5, - 34.3, - 34, - 33.7, - 33.3, - 32.8, - 32.4, - 32.1, - 32, - 32, - 32, - 32.2, - 32.4, - 32.6, - 32.6, - 32.5, - 32.2, - 31.9, - 31.7, - 31.5, - 31.3, - 31.1, - 30.9, - 30.7, - 30.7, - 30.6, - 30.6, - 30.5, - 30.5, - 30.6, - 30.7, - 30.6, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 30, - 29.9, - 30, - 30, - 29.9, - 29.8, - 29.7, - 29.7, - 29.7, - 29.7, - 29.7, - 29.5, - 29.3, - 29, - 29, - 28.9, - 28.6, - 28.3, - 28.1, - 27.9, - 27.9, - 27.7, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27.1, - 27, - 26.9, - 26.8, - 26.8, - 26.7, - 26.7, - 26.5, - 26.4, - 26.4, - 26.3, - 26, - 25.8, - 25.5, - 25.3, - 25.1, - 24.9, - 24.7, - 24.6, - 24.4, - 24.3, - 24.1, - 23.9, - 23.7, - 23.5, - 23.3, - 23.1, - 23, - 22.8, - 22.6, - 22.5, - 22.5, - 22.6, - 22.5, - 22.4, - 22.5, - 22.7, - 22.8, - 22.9, - 22.9, - 23, - 23.1, - 23.1, - 23.1, - 23.1, - 23.2, - 23.3, - 23.4, - 23.4, - 23.6, - 23.9, - 24.3, - 24.6, - 24.7, - 24.8, - 24.9, - 25, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25.1, - 25, - 24.8, - 24.7, - 24.4, - 24.3, - 24.2, - 24.1, - 24, - 23.8, - 23.7, - 23.5, - 23.4, - 23.3, - 23.2, - 23.1, - 23.1, - 23, - 23.1, - 23.1, - 23.1, - 23.2, - 23.3, - 23.4, - 23.6, - 33.6, - 34.1, - 34, - 34.3, - 34.5, - 34.6, - 34.8, - 35, - 35.4, - 35.7, - 35.9, - 36, - 36, - 36.1, - 36.2, - 36.5, - 36.7, - 36.9, - 37.2, - 37.5, - 37.8, - 38, - 38.3, - 38.4, - 38.6, - 38.7, - 38.7, - 38.7, - 38.8, - 38.8, - 38.9, - 38.9, - 38.9, - 39.1, - 39.2, - 39, - 39.5, - 40.2, - 39.5, - 39.5, - 39.7, - 39.8, - 39.9, - 40, - 40, - 40.1, - 40.3, - 40.5, - 40.6, - 40.7, - 40.9, - 41.1, - 41.1, - 41.2, - 41.3, - 41.5, - 41.6, - 41.7, - 41.7, - 41.8, - 41.7, - 41.5, - 41.3, - 41.2, - 41, - 40.9, - 40.6, - 40.5, - 40.4, - 40.3, - 40.1, - 40, - 39.9, - 39.8, - 39.8, - 39.7, - 39.6, - 39.6, - 39.6, - 39.6, - 39.5, - 39.5, - 39.5, - 39.5, - 39.4, - 39.3, - 39.3, - 39.2, - 39.1, - 39, - 38.8, - 38.5, - 38.2, - 37.9, - 37.6, - 37.2, - 36.8, - 36.4, - 36.1, - 35.8, - 35.6, - 35.3, - 35.1, - 34.8, - 34.5, - 34.4, - 34.1, - 33.8, - 33.4, - 32.9, - 32.5, - 32.2, - 32.1, - 32.1, - 32.1, - 32.3, - 32.5, - 32.7, - 32.8, - 32.7, - 32.4, - 32.2, - 31.9, - 31.7, - 31.6, - 31.3, - 31.1, - 30.9, - 30.7, - 30.6, - 30.5, - 30.4, - 30.4, - 30.3, - 30.2, - 30.3, - 30.2, - 30, - 29.9, - 29.8, - 29.8, - 29.8, - 29.7, - 29.6, - 29.6, - 29.6, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.2, - 28.9, - 28.8, - 28.6, - 28.5, - 28.2, - 28, - 27.8, - 27.7, - 27.7, - 27.6, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 27, - 27, - 26.9, - 26.8, - 26.8, - 26.7, - 26.6, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.7, - 25.4, - 25.2, - 25, - 24.8, - 24.6, - 24.5, - 24.3, - 24.2, - 24, - 23.8, - 23.6, - 23.5, - 23.3, - 23.1, - 22.9, - 22.8, - 22.6, - 22.5, - 22.4, - 22.3, - 22.3, - 22.2, - 22.3, - 22.5, - 22.7, - 22.8, - 22.9, - 22.9, - 23, - 23, - 23, - 23, - 23.1, - 23.2, - 23.3, - 23.3, - 23.5, - 23.8, - 24.1, - 24.4, - 24.5, - 24.7, - 24.8, - 24.9, - 24.9, - 25, - 25, - 25, - 24.9, - 24.8, - 24.7, - 24.6, - 24.4, - 24.2, - 24.1, - 23.9, - 23.7, - 23.6, - 23.4, - 23.3, - 23.2, - 23.1, - 22.9, - 22.8, - 22.8, - 22.8, - 22.8, - 22.9, - 22.9, - 22.9, - 23, - 23.1, - 23.1, - 23.3, - 33.1, - 33.5, - 34.1, - 34.7, - 34.5, - 34.6, - 34.8, - 35.1, - 35.3, - 35.7, - 36, - 36.3, - 36.3, - 36.4, - 36.6, - 36.8, - 37, - 37.1, - 37.4, - 37.7, - 38, - 38.3, - 38.4, - 38.5, - 38.6, - 38.6, - 38.7, - 38.7, - 38.7, - 38.8, - 38.8, - 38.7, - 38.7, - 38.8, - 38.9, - 38.7, - 38.8, - 39, - 39, - 39.1, - 39.3, - 39.6, - 39.7, - 39.7, - 39.8, - 39.9, - 40.2, - 40.3, - 40.4, - 40.6, - 40.8, - 41, - 41, - 41, - 41.2, - 41.3, - 41.6, - 41.8, - 41.6, - 41.6, - 41.4, - 41.3, - 41.1, - 40.9, - 40.8, - 40.7, - 40.5, - 40.4, - 40.2, - 40.1, - 40, - 39.9, - 39.9, - 39.8, - 39.7, - 39.7, - 39.7, - 39.7, - 39.8, - 39.6, - 39.6, - 39.6, - 39.5, - 39.5, - 39.4, - 39.3, - 39.3, - 39.2, - 39, - 38.8, - 38.6, - 38.4, - 38, - 37.7, - 37.3, - 36.9, - 36.5, - 36.1, - 35.8, - 35.5, - 35.3, - 35.1, - 34.9, - 34.6, - 34.4, - 34.2, - 33.9, - 33.5, - 33.1, - 32.6, - 32.3, - 32.2, - 32.2, - 32.2, - 32.4, - 32.6, - 32.7, - 32.8, - 32.7, - 32.6, - 32.5, - 32.2, - 31.9, - 31.7, - 31.6, - 31.3, - 31, - 30.8, - 30.6, - 30.5, - 30.3, - 30.2, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.6, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29.1, - 29.3, - 29.2, - 29.1, - 29.1, - 29, - 28.8, - 28.7, - 28.4, - 28.2, - 28, - 27.9, - 27.9, - 27.7, - 27.6, - 27.6, - 27.5, - 27.3, - 27.2, - 27.2, - 27.1, - 27.1, - 27, - 27, - 27, - 26.9, - 26.8, - 26.8, - 26.7, - 26.6, - 26.6, - 26.5, - 26.4, - 26.1, - 25.8, - 25.5, - 25.3, - 25.1, - 24.9, - 24.7, - 24.6, - 24.4, - 24.3, - 24.1, - 23.9, - 23.8, - 23.6, - 23.4, - 23.2, - 23, - 22.9, - 22.8, - 22.6, - 22.5, - 22.4, - 22.3, - 22.2, - 22.1, - 22.1, - 22.1, - 22.4, - 22.7, - 22.8, - 22.9, - 22.9, - 22.9, - 22.9, - 22.9, - 23.1, - 23.2, - 23.3, - 23.3, - 23.5, - 23.7, - 24, - 24.3, - 24.5, - 25, - 25, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.8, - 24.6, - 24.5, - 24.2, - 24, - 23.9, - 23.8, - 23.6, - 23.4, - 23.2, - 23.1, - 22.9, - 22.8, - 22.7, - 22.6, - 22.5, - 22.5, - 22.5, - 22.5, - 22.5, - 22.6, - 22.7, - 22.8, - 22.9, - 23, - 23.1, - 33, - 33.3, - 34.1, - 34.9, - 35, - 34.8, - 35, - 35.2, - 35.5, - 35.7, - 36, - 36.5, - 36.7, - 36.7, - 36.9, - 37.2, - 37.3, - 37.4, - 37.7, - 37.9, - 38.1, - 38.3, - 38.4, - 38.5, - 38.5, - 38.6, - 38.6, - 38.6, - 38.7, - 38.8, - 38.7, - 38.6, - 38.5, - 38.3, - 38.3, - 38.2, - 38.3, - 38.3, - 38.5, - 38.7, - 38.9, - 39.2, - 39.4, - 39.4, - 39.5, - 39.6, - 39.9, - 40.1, - 40.1, - 40.3, - 40.5, - 40.6, - 40.7, - 40.8, - 40.9, - 41.3, - 41.7, - 41.3, - 41.2, - 41.1, - 41.1, - 41, - 40.9, - 40.8, - 40.6, - 40.4, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.9, - 39.8, - 39.8, - 39.8, - 39.7, - 39.7, - 39.9, - 40, - 39.8, - 39.6, - 39.6, - 39.5, - 39.5, - 39.4, - 39.3, - 39.2, - 39.1, - 38.9, - 38.6, - 38.3, - 38.1, - 37.8, - 37.5, - 37, - 36.6, - 36.2, - 35.8, - 35.5, - 35.3, - 35.1, - 34.9, - 34.7, - 34.4, - 34.2, - 33.9, - 33.6, - 33.2, - 32.7, - 32.3, - 32.2, - 32.3, - 32.3, - 32.4, - 32.7, - 32.8, - 32.9, - 32.8, - 32.6, - 32.5, - 32.3, - 32.1, - 31.9, - 31.7, - 31.5, - 31.2, - 30.9, - 30.7, - 30.6, - 30.4, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 29, - 29.1, - 29, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.1, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 27.1, - 27, - 27, - 27, - 26.9, - 26.8, - 26.8, - 26.8, - 26.7, - 26.6, - 26.6, - 26.5, - 26.3, - 26.1, - 25.8, - 25.4, - 25.2, - 25, - 24.8, - 24.6, - 24.5, - 24.3, - 24.2, - 24, - 23.8, - 23.7, - 23.5, - 23.4, - 23.2, - 23, - 22.9, - 22.7, - 22.6, - 22.5, - 22.4, - 22.3, - 22.2, - 22, - 21.9, - 22, - 22.2, - 22.5, - 22.6, - 22.7, - 22.8, - 22.8, - 22.9, - 23, - 23, - 23.1, - 23.1, - 23.2, - 23.4, - 23.6, - 23.9, - 24.2, - 24.5, - 25.1, - 25, - 24.9, - 24.7, - 24.7, - 24.7, - 24.7, - 24.5, - 24.3, - 24.1, - 23.9, - 23.7, - 23.6, - 23.4, - 23.2, - 23, - 22.9, - 22.7, - 22.6, - 22.5, - 22.5, - 22.4, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.4, - 22.5, - 22.6, - 22.7, - 22.8, - 22.9, - 33, - 33.3, - 34, - 34.9, - 35.1, - 34.9, - 35.1, - 35.5, - 35.8, - 35.8, - 36, - 36.3, - 36.6, - 36.8, - 37, - 37.2, - 37.3, - 37.5, - 37.8, - 38, - 38.2, - 38.2, - 38.3, - 38.4, - 38.5, - 38.6, - 38.7, - 38.5, - 38.5, - 38.6, - 38.5, - 38.4, - 38.2, - 38.1, - 38, - 38, - 38, - 38, - 38.1, - 38.2, - 38.3, - 38.5, - 38.8, - 39, - 39.2, - 39.3, - 39.6, - 39.8, - 39.8, - 40, - 40.1, - 40.2, - 40.4, - 40.5, - 40.7, - 41, - 41.5, - 41.2, - 40.9, - 40.8, - 40.6, - 40.7, - 40.7, - 40.6, - 40.4, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.9, - 39.8, - 39.8, - 39.8, - 39.7, - 39.7, - 39.7, - 39.9, - 40, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.3, - 39.2, - 39.1, - 39, - 38.8, - 38.4, - 38.1, - 37.8, - 37.5, - 37.1, - 36.6, - 36.2, - 35.8, - 35.5, - 35.3, - 35.1, - 34.9, - 34.6, - 34.4, - 34.2, - 33.9, - 33.5, - 33.2, - 32.7, - 32.4, - 32.3, - 32.3, - 32.4, - 32.5, - 32.7, - 32.9, - 33, - 32.8, - 32.5, - 32.4, - 32.2, - 32.1, - 31.9, - 31.8, - 31.6, - 31.3, - 31, - 30.7, - 30.5, - 30.4, - 30.3, - 30.2, - 30, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 28.8, - 28.7, - 28.6, - 28.6, - 28.8, - 28.9, - 28.7, - 28.5, - 28.4, - 28.2, - 28, - 28, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.3, - 27.3, - 27.2, - 27.1, - 27.1, - 27, - 27, - 27, - 26.9, - 26.9, - 26.9, - 26.8, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.7, - 25.3, - 25.1, - 24.9, - 24.7, - 24.6, - 24.4, - 24.3, - 24.1, - 23.9, - 23.8, - 23.6, - 23.5, - 23.3, - 23.1, - 23, - 22.8, - 22.7, - 22.6, - 22.5, - 22.4, - 22.3, - 22.2, - 22.1, - 21.8, - 21.9, - 22.1, - 22.2, - 22.4, - 22.6, - 22.6, - 22.7, - 22.8, - 22.8, - 22.8, - 22.9, - 22.9, - 23, - 23.3, - 23.5, - 23.8, - 24.1, - 24.3, - 24.5, - 24.6, - 24.6, - 24.5, - 24.6, - 24.6, - 24.5, - 24.3, - 24.1, - 23.8, - 23.6, - 23.4, - 23.3, - 23.1, - 23, - 22.7, - 22.6, - 22.5, - 22.4, - 22.3, - 22.2, - 22.1, - 22, - 22, - 22, - 22, - 22.1, - 22.2, - 22.4, - 22.4, - 22.6, - 22.7, - 22.8, - 33, - 33.3, - 33.7, - 34.3, - 34.5, - 34.7, - 35, - 35.4, - 35.7, - 35.9, - 36.1, - 36.2, - 36.4, - 36.6, - 36.9, - 37.1, - 37.3, - 37.5, - 37.8, - 38.1, - 38.1, - 38.1, - 38.2, - 38.3, - 38.3, - 38.4, - 38.5, - 38.4, - 38.4, - 38.4, - 38.4, - 38.2, - 38.1, - 38, - 38, - 38, - 38, - 38, - 38, - 38.1, - 38.1, - 38.2, - 38.4, - 38.7, - 38.9, - 39.1, - 39.4, - 39.5, - 39.6, - 39.7, - 39.8, - 39.9, - 40.1, - 40.4, - 41, - 41.2, - 41.2, - 41.1, - 40.8, - 40.7, - 40.5, - 40.5, - 40.4, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.8, - 39.8, - 39.8, - 39.8, - 39.7, - 39.7, - 39.7, - 39.7, - 39.8, - 39.8, - 39.7, - 39.6, - 39.6, - 39.5, - 39.4, - 39.4, - 39.3, - 39, - 38.8, - 38.5, - 38.2, - 38, - 37.6, - 37.1, - 36.7, - 36.2, - 35.8, - 35.5, - 35.3, - 35, - 34.8, - 34.6, - 34.3, - 34.1, - 34, - 33.6, - 33.1, - 32.7, - 32.4, - 32.3, - 32.4, - 32.4, - 32.5, - 32.7, - 32.9, - 33.1, - 33.1, - 32.6, - 32.3, - 32.2, - 32, - 31.9, - 31.7, - 31.6, - 31.4, - 31.1, - 30.8, - 30.6, - 30.3, - 30.2, - 30.1, - 30, - 29.8, - 29.7, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 29, - 28.8, - 28.7, - 28.6, - 28.5, - 28.5, - 28.5, - 28.5, - 28.4, - 28.3, - 28.2, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27, - 27, - 26.9, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.7, - 25.4, - 25.2, - 25, - 24.8, - 24.6, - 24.5, - 24.3, - 24.2, - 24, - 23.8, - 23.7, - 23.6, - 23.4, - 23.3, - 23.1, - 22.9, - 22.8, - 22.7, - 22.6, - 22.5, - 22.5, - 22.3, - 22.2, - 22.1, - 21.9, - 21.8, - 21.9, - 22.1, - 22.3, - 22.5, - 22.5, - 22.6, - 22.6, - 22.7, - 22.8, - 22.8, - 22.8, - 22.8, - 23, - 23.3, - 23.6, - 24, - 24.1, - 24.2, - 24.3, - 24.4, - 24.3, - 24.4, - 24.5, - 24.3, - 24.1, - 23.9, - 23.6, - 23.4, - 23.2, - 23, - 22.9, - 22.7, - 22.5, - 22.3, - 22.2, - 22, - 22, - 21.9, - 21.8, - 21.7, - 21.7, - 21.8, - 21.8, - 21.9, - 22, - 22.1, - 22.2, - 22.4, - 22.5, - 22.6, - 33, - 33.3, - 33.6, - 34, - 34.3, - 34.6, - 35, - 35.3, - 35.6, - 35.9, - 36.1, - 36.3, - 36.5, - 36.7, - 36.9, - 37.1, - 37.4, - 37.5, - 37.6, - 37.9, - 37.9, - 37.8, - 37.9, - 38.1, - 38.2, - 38.2, - 38.3, - 38.4, - 38.5, - 38.5, - 38.5, - 38.2, - 38, - 38, - 38, - 38, - 38, - 38, - 38, - 38, - 38.1, - 38.1, - 38.2, - 38.4, - 38.7, - 38.9, - 39.3, - 39.3, - 39.2, - 39.3, - 39.5, - 39.6, - 39.8, - 40.3, - 41.2, - 41.5, - 40.9, - 40.7, - 40.6, - 40.6, - 40.4, - 40.2, - 40.1, - 40.1, - 39.9, - 39.9, - 39.9, - 39.8, - 39.8, - 39.8, - 39.8, - 39.8, - 39.7, - 39.8, - 39.8, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.5, - 39.4, - 39.3, - 39.4, - 39.5, - 39, - 38.5, - 38.3, - 38, - 37.7, - 37.3, - 36.9, - 36.3, - 35.8, - 35.5, - 35.3, - 35.1, - 34.8, - 34.5, - 34.2, - 34, - 33.8, - 33.6, - 33.2, - 32.7, - 32.5, - 32.4, - 32.4, - 32.5, - 32.5, - 32.6, - 32.8, - 33, - 32.9, - 32.6, - 32.2, - 32, - 31.9, - 31.8, - 31.7, - 31.5, - 31.3, - 31.1, - 30.9, - 30.6, - 30.3, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.5, - 28.5, - 28.4, - 28.2, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.1, - 27.1, - 27.1, - 27.1, - 27, - 27, - 27, - 26.8, - 26.8, - 26.6, - 26.4, - 26.2, - 26, - 25.8, - 25.5, - 25.3, - 25, - 24.9, - 24.7, - 24.5, - 24.4, - 24.2, - 24.1, - 23.9, - 23.8, - 23.7, - 23.5, - 23.4, - 23.2, - 23, - 22.9, - 22.7, - 22.6, - 22.6, - 22.5, - 22.4, - 22.3, - 22.2, - 22, - 21.9, - 21.7, - 21.7, - 22, - 22.2, - 22.3, - 22.4, - 22.5, - 22.5, - 22.5, - 22.6, - 22.8, - 22.9, - 22.8, - 22.9, - 23.1, - 23.3, - 23.6, - 23.9, - 23.9, - 24, - 24.1, - 24.1, - 24.1, - 24.1, - 24, - 23.8, - 23.6, - 23.4, - 23.2, - 23, - 22.8, - 22.7, - 22.5, - 22.2, - 22, - 21.9, - 21.7, - 21.6, - 21.6, - 21.5, - 21.5, - 21.5, - 21.5, - 21.6, - 21.7, - 21.8, - 21.9, - 22, - 22.1, - 22.3, - 22.3, - 33, - 33.3, - 33.9, - 34.2, - 34.4, - 34.7, - 35, - 35.3, - 35.7, - 35.9, - 36.1, - 36.3, - 36.5, - 36.7, - 36.9, - 37.2, - 37.3, - 37.5, - 37.5, - 37.6, - 37.6, - 37.7, - 37.9, - 38, - 38.1, - 38.2, - 38.3, - 38.5, - 38.6, - 38.7, - 38.7, - 38.4, - 38.1, - 38, - 37.9, - 37.9, - 38, - 38, - 38, - 38, - 38, - 38.1, - 38.1, - 38.3, - 38.4, - 38.6, - 38.8, - 38.9, - 39, - 39.1, - 39.2, - 39.3, - 39.4, - 39.8, - 40.5, - 40.9, - 40.5, - 40.2, - 40.3, - 40.3, - 40.2, - 40, - 40, - 40, - 39.8, - 39.7, - 39.7, - 39.7, - 39.7, - 39.8, - 39.9, - 39.8, - 39.9, - 40, - 39.9, - 39.8, - 39.8, - 39.6, - 39.6, - 39.6, - 39.6, - 39.5, - 39.4, - 39.2, - 39.1, - 39.1, - 38.7, - 38.3, - 38, - 37.7, - 37.3, - 37, - 36.6, - 36, - 35.6, - 35.3, - 35.1, - 34.9, - 34.5, - 34.2, - 33.9, - 33.7, - 33.5, - 33.2, - 32.8, - 32.6, - 32.6, - 32.5, - 32.5, - 32.5, - 32.6, - 32.7, - 32.9, - 32.8, - 32.5, - 32.2, - 31.9, - 31.7, - 31.5, - 31.4, - 31.4, - 31.2, - 31, - 30.9, - 30.7, - 30.4, - 30.1, - 29.9, - 29.8, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.3, - 28.1, - 28, - 27.8, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 26.9, - 26.8, - 26.6, - 26.3, - 26.1, - 26, - 25.8, - 25.6, - 25.4, - 25.1, - 24.9, - 24.8, - 24.6, - 24.4, - 24.3, - 24.1, - 24, - 23.8, - 23.7, - 23.6, - 23.5, - 23.3, - 23.1, - 22.9, - 22.8, - 22.6, - 22.6, - 22.5, - 22.5, - 22.4, - 22.2, - 22.1, - 22, - 21.9, - 21.6, - 21.6, - 21.8, - 22, - 22.1, - 22.2, - 22.3, - 22.3, - 22.4, - 22.4, - 22.5, - 22.6, - 22.7, - 22.7, - 22.9, - 23.1, - 23.4, - 23.7, - 23.7, - 23.8, - 23.8, - 23.9, - 23.9, - 23.8, - 23.6, - 23.5, - 23.3, - 23.1, - 22.9, - 22.7, - 22.6, - 22.4, - 22.2, - 22, - 21.7, - 21.6, - 21.5, - 21.4, - 21.3, - 21.2, - 21.3, - 21.3, - 21.3, - 21.4, - 21.5, - 21.6, - 21.7, - 21.8, - 21.8, - 21.9, - 21.9, - 33, - 33.4, - 34, - 34.1, - 34.5, - 34.8, - 35.1, - 35.4, - 35.8, - 35.9, - 36.1, - 36.4, - 36.6, - 36.9, - 37.1, - 37.3, - 37.4, - 37.4, - 37.4, - 37.5, - 37.6, - 37.7, - 38, - 38.1, - 38.1, - 38.2, - 38.4, - 38.7, - 38.8, - 38.9, - 38.8, - 38.7, - 38.4, - 38, - 37.9, - 37.9, - 37.9, - 38, - 38, - 38, - 38, - 38, - 38.1, - 38.2, - 38.3, - 38.5, - 38.6, - 38.8, - 38.8, - 38.9, - 39, - 39.1, - 39.2, - 39.3, - 39.6, - 40.1, - 40, - 39.9, - 40.4, - 40.3, - 40.1, - 40, - 40.2, - 40.2, - 39.9, - 39.6, - 39.6, - 39.6, - 39.6, - 39.7, - 39.7, - 39.7, - 39.8, - 40, - 40, - 39.8, - 39.8, - 39.6, - 39.6, - 39.5, - 39.5, - 39.5, - 39.3, - 39.1, - 38.9, - 38.6, - 38.3, - 37.9, - 37.6, - 37.2, - 36.9, - 36.6, - 36.1, - 35.6, - 35.3, - 35.1, - 34.9, - 34.6, - 34.2, - 33.9, - 33.7, - 33.4, - 33.2, - 32.9, - 32.6, - 32.6, - 32.6, - 32.6, - 32.5, - 32.5, - 32.6, - 32.7, - 32.7, - 32.4, - 32.1, - 31.9, - 31.6, - 31.3, - 31.2, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.2, - 29.9, - 29.8, - 29.6, - 29.6, - 29.4, - 29.2, - 29.1, - 29.1, - 29, - 28.9, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.3, - 28.2, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.5, - 27.4, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27, - 26.7, - 26.4, - 26.1, - 25.9, - 25.8, - 25.6, - 25.4, - 25.2, - 24.9, - 24.7, - 24.6, - 24.4, - 24.3, - 24.2, - 24.1, - 23.9, - 23.8, - 23.6, - 23.5, - 23.3, - 23.1, - 22.9, - 22.8, - 22.7, - 22.6, - 22.6, - 22.5, - 22.4, - 22.2, - 22.1, - 22, - 21.9, - 21.7, - 21.6, - 21.5, - 21.5, - 21.7, - 21.8, - 21.9, - 22, - 22, - 22.1, - 22.3, - 22.3, - 22.3, - 22.4, - 22.5, - 22.7, - 22.9, - 23.2, - 23.4, - 23.4, - 23.6, - 23.6, - 23.6, - 23.5, - 23.5, - 23.3, - 23.2, - 23, - 22.8, - 22.6, - 22.4, - 22.3, - 22.1, - 21.9, - 21.6, - 21.4, - 21.3, - 21.2, - 21.2, - 21.1, - 21, - 21, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.5, - 21.5, - 21.6, - 21.6, - 21.6, - 33.1, - 33.8, - 34.2, - 34.2, - 34.5, - 34.8, - 35.2, - 35.4, - 35.7, - 35.9, - 36.3, - 36.5, - 36.8, - 37, - 37.2, - 37.3, - 37.3, - 37.4, - 37.4, - 37.5, - 37.6, - 37.8, - 38.1, - 38.2, - 38.3, - 38.5, - 38.7, - 38.8, - 38.9, - 39, - 39, - 38.9, - 38.7, - 38.3, - 38, - 38, - 37.9, - 37.9, - 38, - 38, - 38, - 38, - 38, - 38.1, - 38.2, - 38.3, - 38.5, - 38.6, - 38.6, - 38.7, - 38.8, - 39, - 39.2, - 39.3, - 39.3, - 39.5, - 39.5, - 39.5, - 39.7, - 39.8, - 39.7, - 39.6, - 40, - 40.1, - 39.8, - 39.5, - 39.5, - 39.5, - 39.6, - 39.6, - 39.6, - 39.6, - 39.7, - 39.9, - 39.9, - 39.7, - 39.6, - 39.6, - 39.5, - 39.5, - 39.4, - 39.2, - 39.1, - 39.1, - 38.8, - 38.3, - 38, - 37.6, - 37.2, - 36.8, - 36.4, - 36.1, - 35.7, - 35.3, - 35, - 34.7, - 34.5, - 34.3, - 33.9, - 33.7, - 33.5, - 33.3, - 33, - 32.7, - 32.6, - 32.6, - 32.6, - 32.6, - 32.5, - 32.6, - 32.6, - 32.7, - 32.4, - 32, - 31.8, - 31.6, - 31.3, - 31, - 30.9, - 30.7, - 30.6, - 30.5, - 30.4, - 30.2, - 30.1, - 29.9, - 29.7, - 29.5, - 29.4, - 29.3, - 29.1, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.3, - 28.3, - 28.3, - 28.2, - 28.2, - 28.1, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.6, - 27.5, - 27.5, - 27.5, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.3, - 26, - 25.7, - 25.5, - 25.4, - 25.2, - 25, - 24.8, - 24.6, - 24.4, - 24.2, - 24.2, - 24, - 23.9, - 23.8, - 23.6, - 23.5, - 23.3, - 23.1, - 23, - 22.8, - 22.7, - 22.6, - 22.5, - 22.4, - 22.3, - 22.2, - 22, - 21.9, - 21.8, - 21.7, - 21.6, - 21.5, - 21.4, - 21.3, - 21.4, - 21.6, - 21.7, - 21.7, - 21.7, - 21.8, - 22, - 21.9, - 22.1, - 22.2, - 22.3, - 22.5, - 22.7, - 22.8, - 23, - 23.2, - 23.4, - 23.5, - 23.3, - 23.2, - 23.2, - 23, - 22.8, - 22.7, - 22.5, - 22.3, - 22.2, - 22, - 21.8, - 21.6, - 21.3, - 21.2, - 21, - 20.9, - 20.9, - 20.9, - 20.8, - 20.8, - 20.8, - 20.9, - 21, - 21.1, - 21.1, - 21.2, - 21.3, - 21.3, - 21.3, - 21.3, - 33.4, - 34.2, - 34.4, - 34.3, - 34.6, - 34.8, - 35.2, - 35.5, - 35.8, - 36.2, - 36.4, - 36.7, - 37, - 37.2, - 37.2, - 37.2, - 37.3, - 37.4, - 37.4, - 37.5, - 37.6, - 37.8, - 38.2, - 38.3, - 38.4, - 38.7, - 38.8, - 39, - 39.1, - 39.2, - 39.1, - 39, - 38.9, - 38.6, - 38.2, - 38, - 38, - 38, - 38, - 38, - 38, - 38, - 38, - 38.1, - 38.2, - 38.3, - 38.4, - 38.5, - 38.6, - 38.6, - 38.7, - 38.8, - 39, - 39.2, - 39.2, - 39.3, - 39.4, - 39.4, - 39.4, - 39.6, - 39.7, - 39.5, - 39.6, - 39.6, - 39.6, - 39.5, - 39.5, - 39.5, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.5, - 39.5, - 39.4, - 39.2, - 39, - 38.9, - 38.9, - 38.6, - 38.1, - 37.8, - 37.4, - 36.9, - 36.4, - 36, - 35.7, - 35.3, - 34.9, - 34.6, - 34.3, - 34.1, - 33.9, - 33.7, - 33.5, - 33.3, - 33.1, - 32.8, - 32.7, - 32.7, - 32.7, - 32.6, - 32.5, - 32.6, - 32.6, - 32.6, - 32.4, - 32, - 31.7, - 31.5, - 31.3, - 31.1, - 30.8, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 29.9, - 29.8, - 29.6, - 29.4, - 29.2, - 29.1, - 29, - 29.1, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.5, - 28.5, - 28.4, - 28.3, - 28.2, - 28.2, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 27.8, - 27.8, - 27.7, - 27.6, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 26.8, - 26.6, - 26.4, - 26.1, - 25.7, - 25.5, - 25.4, - 25.2, - 24.9, - 24.8, - 24.6, - 24.4, - 24.2, - 24.1, - 24.1, - 23.9, - 23.7, - 23.6, - 23.4, - 23.2, - 23.1, - 23, - 22.9, - 22.7, - 22.6, - 22.5, - 22.4, - 22.2, - 22.1, - 21.9, - 21.8, - 21.7, - 21.7, - 21.5, - 21.4, - 21.4, - 21.3, - 21.2, - 21.2, - 21.4, - 21.5, - 21.5, - 21.5, - 21.4, - 21.5, - 21.6, - 21.8, - 21.9, - 22, - 22.2, - 22.4, - 22.7, - 22.8, - 22.9, - 23.1, - 23.1, - 23, - 23, - 22.9, - 22.6, - 22.5, - 22.3, - 22.2, - 22, - 21.9, - 21.7, - 21.5, - 21.2, - 21.1, - 20.9, - 20.8, - 20.6, - 20.6, - 20.6, - 20.6, - 20.5, - 20.6, - 20.7, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.1, - 21.1, - 21.1, - 33.7, - 34.3, - 34.5, - 34.7, - 34.7, - 34.9, - 35.3, - 35.7, - 36.1, - 36.5, - 36.7, - 37, - 37, - 37.1, - 37.2, - 37.3, - 37.4, - 37.4, - 37.5, - 37.6, - 37.8, - 38, - 38.2, - 38.5, - 38.7, - 38.9, - 39, - 39.1, - 39.2, - 39.3, - 39.2, - 39.2, - 39, - 38.8, - 38.6, - 38.3, - 38.1, - 38, - 38, - 38, - 38, - 38, - 38, - 38.1, - 38.1, - 38.3, - 38.4, - 38.5, - 38.5, - 38.6, - 38.6, - 38.7, - 38.8, - 39, - 39.4, - 39.5, - 39.4, - 39.6, - 39.5, - 40, - 39.8, - 39.5, - 39.6, - 39.6, - 39.5, - 39.5, - 39.6, - 39.5, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.5, - 39.5, - 39.4, - 39.2, - 39, - 38.8, - 38.6, - 38.4, - 38.1, - 37.9, - 37.6, - 36.9, - 36.4, - 36, - 35.6, - 35.3, - 34.9, - 34.5, - 34.1, - 33.9, - 33.7, - 33.6, - 33.5, - 33.3, - 33.1, - 32.8, - 32.7, - 32.7, - 32.7, - 32.6, - 32.5, - 32.5, - 32.6, - 32.5, - 32.3, - 32, - 31.7, - 31.4, - 31.2, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 30.1, - 29.9, - 29.8, - 29.7, - 29.5, - 29.2, - 29, - 29, - 29, - 29, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28, - 28, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.5, - 27.4, - 27.4, - 27.4, - 27.4, - 27.4, - 27.4, - 27.3, - 27.2, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 26.9, - 26.8, - 26.6, - 26.4, - 26.1, - 25.7, - 25.4, - 25.3, - 25.2, - 25, - 24.7, - 24.5, - 24.4, - 24.1, - 24, - 24, - 23.9, - 23.7, - 23.5, - 23.3, - 23.2, - 23.1, - 22.9, - 22.8, - 22.7, - 22.6, - 22.5, - 22.3, - 22.1, - 22, - 21.8, - 21.7, - 21.6, - 21.5, - 21.4, - 21.3, - 21.3, - 21.2, - 21.1, - 21, - 21, - 21.2, - 21.3, - 21.3, - 21.2, - 21.2, - 21.2, - 21.3, - 21.5, - 21.6, - 21.7, - 21.9, - 22.2, - 22.4, - 22.5, - 22.6, - 22.8, - 22.8, - 22.8, - 22.7, - 22.6, - 22.4, - 22.2, - 22, - 21.8, - 21.7, - 21.5, - 21.3, - 21.1, - 20.9, - 20.8, - 20.6, - 20.5, - 20.4, - 20.3, - 20.3, - 20.3, - 20.3, - 20.4, - 20.4, - 20.5, - 20.6, - 20.7, - 20.8, - 20.8, - 20.8, - 20.8, - 20.8, - 33.5, - 33.9, - 34.4, - 34.9, - 34.9, - 35, - 35.4, - 35.9, - 36.3, - 36.5, - 36.8, - 37, - 37, - 37.1, - 37.2, - 37.3, - 37.4, - 37.6, - 37.7, - 37.7, - 38, - 38.3, - 38.6, - 38.8, - 38.9, - 39.2, - 39.2, - 39.3, - 39.4, - 39.5, - 39.5, - 39.4, - 39.2, - 39, - 38.8, - 38.7, - 38.4, - 38.1, - 38, - 38, - 38, - 38, - 38, - 38.1, - 38.1, - 38.3, - 38.5, - 38.5, - 38.5, - 38.6, - 38.6, - 38.7, - 38.8, - 38.9, - 39.2, - 39.3, - 39.3, - 39.6, - 39.5, - 39.7, - 39.6, - 39.4, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.4, - 39.2, - 39, - 38.8, - 38.6, - 38.4, - 38, - 37.6, - 37.4, - 37.2, - 36.5, - 36, - 35.5, - 35.2, - 34.9, - 34.5, - 34.1, - 33.8, - 33.7, - 33.6, - 33.5, - 33.3, - 33.1, - 32.9, - 32.8, - 32.8, - 32.7, - 32.6, - 32.5, - 32.5, - 32.5, - 32.4, - 32.2, - 31.9, - 31.7, - 31.4, - 31.2, - 31, - 30.8, - 30.6, - 30.3, - 30.2, - 30, - 29.8, - 29.6, - 29.5, - 29.5, - 29.1, - 28.9, - 28.9, - 28.9, - 29, - 29, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.6, - 27.7, - 27.5, - 27.5, - 27.4, - 27.4, - 27.4, - 27.4, - 27.4, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.3, - 27.2, - 27.1, - 26.9, - 26.7, - 26.5, - 26.4, - 26.1, - 25.7, - 25.4, - 25.2, - 25, - 24.9, - 24.7, - 24.5, - 24.3, - 24.1, - 23.9, - 23.9, - 24, - 23.7, - 23.4, - 23.3, - 23.1, - 23, - 22.9, - 22.7, - 22.6, - 22.5, - 22.4, - 22.3, - 22.1, - 21.9, - 21.7, - 21.5, - 21.4, - 21.3, - 21.2, - 21.1, - 21, - 21, - 20.9, - 20.9, - 20.9, - 20.9, - 21, - 21, - 21, - 21, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.5, - 21.7, - 22, - 22.2, - 22.4, - 22.4, - 22.5, - 22.5, - 22.5, - 22.4, - 22.3, - 22.1, - 21.9, - 21.7, - 21.5, - 21.3, - 21.2, - 21, - 20.8, - 20.6, - 20.5, - 20.3, - 20.2, - 20.1, - 20.1, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.4, - 20.5, - 20.5, - 20.5, - 20.5, - 20.6, - 20.6, - 33.2, - 33.6, - 34.1, - 34.8, - 34.8, - 35.1, - 35.3, - 35.8, - 36.3, - 36.5, - 36.7, - 36.8, - 37, - 37.2, - 37.2, - 37.3, - 37.4, - 37.7, - 37.8, - 38, - 38.3, - 38.5, - 38.8, - 38.9, - 39.1, - 39.2, - 39.4, - 39.5, - 39.6, - 39.6, - 39.6, - 39.5, - 39.4, - 39.2, - 38.9, - 38.9, - 38.7, - 38.4, - 38.1, - 38, - 38, - 38, - 38, - 38.1, - 38.2, - 38.3, - 38.5, - 38.6, - 38.6, - 38.6, - 38.6, - 38.7, - 38.8, - 38.9, - 39, - 39, - 39.1, - 39.2, - 39.3, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.4, - 39.4, - 39.3, - 39.2, - 39, - 38.8, - 38.6, - 38.4, - 38.1, - 37.7, - 37.2, - 36.9, - 36.5, - 36, - 35.5, - 35.2, - 34.9, - 34.5, - 34.2, - 33.8, - 33.7, - 33.6, - 33.5, - 33.3, - 33, - 32.9, - 32.8, - 32.8, - 32.8, - 32.7, - 32.6, - 32.4, - 32.4, - 32.3, - 32.1, - 31.8, - 31.6, - 31.4, - 31.2, - 30.9, - 30.8, - 30.6, - 30.3, - 30.1, - 30, - 29.7, - 29.6, - 29.4, - 29.2, - 29.1, - 28.9, - 28.9, - 28.9, - 29, - 29, - 29, - 28.9, - 28.7, - 28.6, - 28.5, - 28.3, - 28.3, - 28.3, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.4, - 27.4, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.4, - 26.1, - 25.9, - 25.7, - 25.4, - 25.1, - 24.9, - 24.8, - 24.7, - 24.4, - 24.2, - 24, - 23.8, - 23.8, - 23.9, - 23.8, - 23.5, - 23.2, - 23, - 22.9, - 22.7, - 22.6, - 22.5, - 22.4, - 22.3, - 22.2, - 22, - 21.8, - 21.6, - 21.4, - 21.2, - 21.1, - 21, - 20.9, - 20.8, - 20.7, - 20.7, - 20.7, - 20.7, - 20.7, - 20.7, - 20.7, - 20.7, - 20.8, - 20.9, - 21, - 21.1, - 21.2, - 21.3, - 21.4, - 21.5, - 21.7, - 22, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.3, - 22.2, - 22, - 21.9, - 21.6, - 21.4, - 21.2, - 21, - 20.8, - 20.6, - 20.5, - 20.3, - 20.2, - 20.1, - 20, - 19.9, - 19.8, - 19.8, - 19.8, - 19.9, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.4, - 20.4, - 33.2, - 33.5, - 34, - 34.5, - 34.6, - 34.9, - 35.3, - 35.8, - 36.4, - 36.6, - 36.7, - 36.8, - 37, - 37.3, - 37.3, - 37.3, - 37.5, - 37.8, - 38, - 38.2, - 38.6, - 38.8, - 38.9, - 39, - 39.2, - 39.4, - 39.5, - 39.7, - 39.8, - 39.8, - 39.8, - 39.7, - 39.4, - 39.2, - 39, - 38.9, - 38.9, - 38.7, - 38.5, - 38.2, - 38.1, - 38.1, - 38.1, - 38.1, - 38.3, - 38.4, - 38.5, - 38.6, - 38.6, - 38.6, - 38.6, - 38.7, - 38.7, - 38.8, - 38.9, - 39, - 39, - 39.1, - 39.2, - 39.2, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.5, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.3, - 39.3, - 39.1, - 38.9, - 38.6, - 38.4, - 38.1, - 37.9, - 37.6, - 37.3, - 36.9, - 36.4, - 36, - 35.6, - 35.2, - 34.9, - 34.5, - 34.2, - 33.9, - 33.7, - 33.6, - 33.5, - 33.3, - 33, - 32.9, - 32.8, - 32.8, - 32.8, - 32.8, - 32.6, - 32.4, - 32.3, - 32.2, - 32, - 31.8, - 31.5, - 31.4, - 31.1, - 30.8, - 30.7, - 30.6, - 30.3, - 30, - 29.9, - 29.7, - 29.5, - 29.3, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.5, - 28.4, - 28.3, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 28, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.2, - 27.2, - 27, - 27, - 26.8, - 26.6, - 26.5, - 26.3, - 25.9, - 25.6, - 25.4, - 25.2, - 24.9, - 24.8, - 24.7, - 24.5, - 24.4, - 24.2, - 23.9, - 23.7, - 23.7, - 23.7, - 23.6, - 23.4, - 23.2, - 23, - 22.8, - 22.6, - 22.5, - 22.4, - 22.3, - 22.1, - 22, - 21.9, - 21.7, - 21.5, - 21.3, - 21.1, - 21, - 20.8, - 20.7, - 20.6, - 20.5, - 20.4, - 20.4, - 20.5, - 20.5, - 20.5, - 20.6, - 20.5, - 20.6, - 20.6, - 20.7, - 20.9, - 21, - 21.1, - 21.2, - 21.2, - 21.3, - 21.6, - 21.9, - 22.1, - 22.1, - 22.1, - 22.2, - 22.1, - 22, - 21.9, - 21.7, - 21.5, - 21.3, - 21.1, - 20.9, - 20.6, - 20.5, - 20.3, - 20.2, - 20.1, - 20, - 19.9, - 19.8, - 19.7, - 19.6, - 19.6, - 19.7, - 19.7, - 19.7, - 19.8, - 19.8, - 19.9, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.2, - 33.1, - 33.5, - 33.8, - 34.2, - 34.5, - 34.8, - 35.2, - 35.8, - 36.3, - 36.5, - 36.7, - 36.9, - 37.1, - 37.4, - 37.4, - 37.6, - 37.8, - 38.1, - 38.4, - 38.6, - 38.9, - 39.1, - 39.3, - 39.3, - 39.4, - 39.5, - 39.6, - 39.8, - 39.9, - 39.9, - 39.9, - 39.8, - 39.6, - 39.4, - 39.2, - 39.1, - 39, - 38.9, - 38.7, - 38.5, - 38.3, - 38.2, - 38.2, - 38.3, - 38.4, - 38.5, - 38.6, - 38.6, - 38.7, - 38.7, - 38.6, - 38.6, - 38.7, - 38.8, - 38.9, - 39, - 39, - 39.1, - 39.2, - 39.2, - 39.3, - 39.3, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.5, - 39.4, - 39.3, - 39.2, - 39.3, - 39.4, - 38.9, - 38.4, - 38.1, - 37.9, - 37.6, - 37.3, - 37.1, - 36.7, - 36.4, - 36, - 35.6, - 35.2, - 34.8, - 34.5, - 34.1, - 33.9, - 33.7, - 33.6, - 33.4, - 33.3, - 33, - 32.9, - 32.9, - 32.8, - 32.8, - 32.8, - 32.6, - 32.4, - 32.1, - 31.9, - 31.8, - 31.6, - 31.4, - 31.1, - 30.9, - 30.8, - 30.5, - 30.4, - 30.3, - 30, - 29.8, - 29.6, - 29.5, - 29.3, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.3, - 28.2, - 28.2, - 28.2, - 28.1, - 28, - 28, - 28, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.6, - 27.6, - 27.6, - 27.6, - 27.6, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 26.9, - 26.9, - 26.8, - 26.7, - 26.5, - 26.3, - 26, - 25.8, - 25.5, - 25.2, - 25, - 24.8, - 24.6, - 24.5, - 24.3, - 24.2, - 24, - 23.8, - 23.7, - 23.6, - 23.6, - 23.6, - 23.4, - 23.1, - 22.9, - 22.7, - 22.6, - 22.4, - 22.3, - 22.1, - 21.9, - 21.8, - 21.6, - 21.5, - 21.3, - 21.1, - 21, - 20.8, - 20.7, - 20.6, - 20.4, - 20.3, - 20.2, - 20.2, - 20.2, - 20.2, - 20.3, - 20.3, - 20.4, - 20.3, - 20.4, - 20.5, - 20.6, - 20.6, - 20.8, - 20.8, - 20.8, - 20.9, - 21.1, - 21.5, - 21.8, - 21.8, - 21.8, - 21.9, - 22, - 22, - 21.7, - 21.5, - 21.4, - 21.2, - 21, - 20.7, - 20.5, - 20.3, - 20.2, - 20.1, - 19.9, - 19.9, - 19.7, - 19.6, - 19.5, - 19.4, - 19.4, - 19.4, - 19.5, - 19.5, - 19.6, - 19.6, - 19.6, - 19.7, - 19.7, - 19.8, - 19.9, - 19.9, - 19.9, - 19.9, - 33.1, - 33.5, - 33.7, - 34.2, - 34.5, - 34.8, - 35.3, - 35.8, - 36.1, - 36.3, - 36.6, - 37, - 37.3, - 37.4, - 37.5, - 37.9, - 38.2, - 38.6, - 38.8, - 39, - 39.3, - 39.5, - 39.7, - 39.7, - 39.7, - 39.9, - 40.1, - 40.1, - 40.1, - 40.2, - 40.1, - 40, - 39.9, - 39.6, - 39.3, - 39.2, - 39.1, - 39, - 38.8, - 38.7, - 38.5, - 38.4, - 38.4, - 38.4, - 38.5, - 38.5, - 38.6, - 38.7, - 38.7, - 38.7, - 38.6, - 38.7, - 38.7, - 38.9, - 38.9, - 39, - 39, - 39.1, - 39.2, - 39.2, - 39.2, - 39.3, - 39.4, - 39.5, - 39.5, - 39.4, - 39.4, - 39.3, - 39.3, - 39.3, - 39.3, - 39.2, - 39, - 38.9, - 39.1, - 38.9, - 38.4, - 37.9, - 37.7, - 37.4, - 37.1, - 36.9, - 36.6, - 36.3, - 36, - 35.6, - 35.2, - 34.9, - 34.5, - 34.1, - 33.8, - 33.7, - 33.5, - 33.4, - 33.3, - 33.1, - 32.9, - 32.9, - 32.9, - 32.8, - 32.7, - 32.5, - 32.2, - 31.9, - 31.6, - 31.3, - 31.3, - 31.2, - 30.9, - 30.7, - 30.6, - 30.5, - 30.2, - 30, - 29.9, - 29.7, - 29.5, - 29.4, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.6, - 27.6, - 27.6, - 27.5, - 27.6, - 27.5, - 27.5, - 27.5, - 27.3, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.5, - 26.4, - 26.1, - 25.9, - 25.7, - 25.5, - 25.2, - 24.9, - 24.7, - 24.5, - 24.4, - 24.2, - 24, - 23.9, - 23.7, - 23.6, - 23.5, - 23.5, - 23.6, - 23.5, - 23.2, - 22.9, - 22.7, - 22.5, - 22.3, - 22.1, - 22, - 21.8, - 21.6, - 21.5, - 21.3, - 21.2, - 21, - 20.8, - 20.7, - 20.5, - 20.4, - 20.2, - 20.2, - 20.1, - 20, - 20, - 20, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.2, - 20.3, - 20.3, - 20.3, - 20.4, - 20.4, - 20.5, - 20.7, - 20.9, - 21.1, - 21.4, - 21.5, - 21.5, - 21.6, - 21.7, - 21.7, - 21.4, - 21.2, - 21, - 20.9, - 20.6, - 20.4, - 20.2, - 20.1, - 19.9, - 19.9, - 19.8, - 19.7, - 19.5, - 19.4, - 19.3, - 19.2, - 19.2, - 19.2, - 19.2, - 19.3, - 19.4, - 19.4, - 19.5, - 19.5, - 19.5, - 19.6, - 19.7, - 19.7, - 19.7, - 19.8, - 33, - 33.5, - 33.7, - 34.2, - 34.6, - 35.1, - 35.5, - 35.8, - 36.1, - 36.3, - 36.6, - 37.1, - 37.5, - 37.6, - 37.9, - 38.4, - 38.9, - 39.2, - 39.3, - 39.5, - 39.7, - 39.9, - 40, - 40, - 40, - 40.1, - 40.3, - 40.3, - 40.4, - 40.4, - 40.3, - 40.2, - 40.2, - 39.8, - 39.5, - 39.3, - 39.2, - 39.1, - 39, - 38.8, - 38.6, - 38.5, - 38.4, - 38.5, - 38.5, - 38.6, - 38.7, - 38.8, - 38.7, - 38.7, - 38.7, - 38.7, - 38.8, - 38.9, - 39, - 39, - 39.1, - 39.2, - 39.2, - 39.3, - 39.3, - 39.3, - 39.4, - 39.6, - 39.5, - 39.3, - 39.3, - 39.2, - 39.2, - 39.1, - 39.1, - 39, - 38.9, - 38.8, - 38.5, - 38.3, - 37.8, - 37.5, - 37.2, - 36.9, - 36.7, - 36.5, - 36.2, - 35.9, - 35.6, - 35.2, - 34.9, - 34.5, - 34.2, - 33.8, - 33.7, - 33.6, - 33.4, - 33.3, - 33.1, - 33, - 33, - 32.9, - 32.8, - 32.6, - 32.4, - 32.1, - 31.7, - 31.4, - 31.1, - 31, - 31, - 30.8, - 30.6, - 30.4, - 30.3, - 30.1, - 29.9, - 29.7, - 29.5, - 29.4, - 29.3, - 29.2, - 29, - 29, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.4, - 27.3, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.2, - 26, - 25.8, - 25.6, - 25.4, - 25.3, - 25, - 24.8, - 24.6, - 24.4, - 24.2, - 24.1, - 23.9, - 23.7, - 23.5, - 23.4, - 23.4, - 23.4, - 23.4, - 23.2, - 22.9, - 22.7, - 22.5, - 22.3, - 22.1, - 21.8, - 21.7, - 21.5, - 21.3, - 21.2, - 21, - 20.8, - 20.7, - 20.5, - 20.4, - 20.2, - 20.1, - 20, - 19.9, - 19.9, - 19.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.8, - 19.9, - 20, - 20.1, - 20.2, - 20.2, - 20.2, - 20.2, - 20.2, - 20.4, - 20.5, - 20.7, - 20.9, - 21.1, - 21.3, - 21.3, - 21.3, - 21.3, - 21.3, - 21, - 20.8, - 20.7, - 20.5, - 20.3, - 20.1, - 19.9, - 19.8, - 19.7, - 19.6, - 19.5, - 19.4, - 19.2, - 19.1, - 19, - 19, - 19, - 19, - 19.1, - 19.1, - 19.2, - 19.2, - 19.3, - 19.3, - 19.4, - 19.4, - 19.4, - 19.5, - 19.5, - 19.6, - 32.9, - 33.4, - 34, - 34.4, - 34.8, - 35.3, - 35.6, - 35.9, - 36.2, - 36.4, - 36.8, - 37.4, - 37.7, - 37.9, - 38.3, - 38.9, - 39.2, - 39.3, - 39.6, - 39.7, - 39.7, - 39.9, - 40, - 40.1, - 40.1, - 40.2, - 40.3, - 40.5, - 40.6, - 40.6, - 40.6, - 40.5, - 40.5, - 40.1, - 39.7, - 39.5, - 39.3, - 39.2, - 39.1, - 38.9, - 38.8, - 38.6, - 38.5, - 38.5, - 38.6, - 38.7, - 38.8, - 38.8, - 38.7, - 38.7, - 38.7, - 38.8, - 38.9, - 38.9, - 39, - 39.1, - 39.1, - 39.2, - 39.3, - 39.4, - 39.4, - 39.4, - 39.3, - 39.3, - 39.3, - 39.2, - 39.2, - 39.2, - 39.1, - 39.1, - 38.9, - 38.8, - 38.8, - 38.6, - 38.1, - 37.8, - 37.4, - 37.1, - 36.8, - 36.6, - 36.3, - 36.2, - 35.9, - 35.6, - 35.3, - 34.9, - 34.6, - 34.2, - 33.9, - 33.7, - 33.7, - 33.5, - 33.3, - 33.1, - 33, - 33, - 32.9, - 32.8, - 32.6, - 32.3, - 31.9, - 31.5, - 31.4, - 31.3, - 31, - 30.9, - 30.9, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 28.5, - 28.3, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28, - 28.1, - 28, - 28, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 26, - 25.8, - 25.5, - 25.3, - 25.2, - 25.1, - 24.8, - 24.7, - 24.5, - 24.3, - 24.1, - 23.9, - 23.6, - 23.4, - 23.3, - 23.3, - 23.4, - 23.3, - 23.2, - 23, - 22.7, - 22.5, - 22.3, - 22.1, - 21.8, - 21.6, - 21.4, - 21.2, - 21, - 20.9, - 20.7, - 20.5, - 20.3, - 20.2, - 20, - 19.9, - 19.7, - 19.7, - 19.7, - 19.7, - 19.7, - 19.6, - 19.5, - 19.6, - 19.6, - 19.7, - 19.7, - 19.8, - 19.9, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.3, - 20.4, - 20.6, - 20.8, - 21, - 21.1, - 21.1, - 21.2, - 21.2, - 21, - 20.7, - 20.5, - 20.3, - 20.2, - 20, - 19.8, - 19.7, - 19.5, - 19.4, - 19.3, - 19.2, - 19.1, - 18.9, - 18.9, - 18.8, - 18.8, - 18.8, - 18.8, - 18.9, - 18.9, - 19, - 19.1, - 19.1, - 19.1, - 19.2, - 19.2, - 19.1, - 19.2, - 19.3, - 19.4, - 33, - 33.9, - 34.8, - 35.2, - 35.2, - 35.4, - 35.7, - 36.1, - 36.5, - 36.8, - 37.2, - 37.6, - 37.9, - 38.2, - 38.6, - 38.9, - 39.2, - 39.4, - 39.6, - 39.7, - 39.8, - 39.9, - 40, - 40.1, - 40.2, - 40.3, - 40.4, - 40.5, - 40.7, - 40.8, - 40.7, - 40.6, - 40.6, - 40.3, - 39.9, - 39.7, - 39.5, - 39.4, - 39.2, - 39.1, - 38.9, - 38.8, - 38.7, - 38.6, - 38.7, - 38.8, - 38.8, - 38.8, - 38.8, - 38.8, - 38.8, - 38.9, - 38.9, - 39, - 39.1, - 39.2, - 39.2, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.3, - 39.3, - 39.2, - 39.2, - 39.2, - 39.2, - 39.2, - 39.1, - 38.7, - 38.5, - 38.3, - 38, - 37.6, - 37.3, - 37.1, - 36.8, - 36.5, - 36.4, - 36.1, - 35.9, - 35.7, - 35.4, - 35, - 34.7, - 34.3, - 33.9, - 33.8, - 33.7, - 33.6, - 33.4, - 33.2, - 33.1, - 33.1, - 33, - 32.8, - 32.5, - 32.2, - 31.9, - 31.4, - 31.3, - 31.2, - 31.1, - 30.9, - 30.8, - 30.7, - 30.4, - 30.1, - 29.9, - 29.7, - 29.5, - 29.3, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.5, - 28.5, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 28, - 27.9, - 27.9, - 27.9, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.2, - 26, - 25.9, - 25.8, - 25.6, - 25.3, - 25.2, - 25.1, - 25, - 24.7, - 24.5, - 24.4, - 24.2, - 24, - 23.7, - 23.5, - 23.4, - 23.3, - 23.3, - 23.3, - 23.2, - 23, - 22.7, - 22.5, - 22.3, - 22.1, - 21.8, - 21.5, - 21.3, - 21.1, - 20.9, - 20.7, - 20.6, - 20.4, - 20.2, - 20, - 19.8, - 19.7, - 19.5, - 19.5, - 19.4, - 19.5, - 19.4, - 19.3, - 19.2, - 19.2, - 19.3, - 19.4, - 19.4, - 19.5, - 19.6, - 19.8, - 19.9, - 20, - 20, - 20.1, - 20.1, - 20.2, - 20.4, - 20.6, - 20.7, - 20.8, - 20.9, - 20.9, - 21, - 21, - 20.7, - 20.5, - 20.3, - 20, - 19.8, - 19.7, - 19.6, - 19.5, - 19.3, - 19.2, - 19.1, - 18.9, - 18.8, - 18.7, - 18.7, - 18.7, - 18.7, - 18.7, - 18.7, - 18.7, - 18.8, - 18.8, - 18.9, - 18.9, - 19, - 19, - 19, - 19, - 19, - 19.1, - 19.2, - 33.2, - 34.1, - 35, - 35.5, - 35.5, - 35.6, - 36, - 36.2, - 36.7, - 37.1, - 37.4, - 37.8, - 38, - 38.4, - 38.8, - 39, - 39.2, - 39.4, - 39.5, - 39.7, - 39.9, - 40, - 40.1, - 40.2, - 40.3, - 40.4, - 40.5, - 40.5, - 40.6, - 40.7, - 40.7, - 40.9, - 40.8, - 40.5, - 40.1, - 39.8, - 39.6, - 39.5, - 39.4, - 39.2, - 39.1, - 38.9, - 38.8, - 38.8, - 38.8, - 38.9, - 38.9, - 38.8, - 38.8, - 38.9, - 39, - 39, - 39, - 39.1, - 39.2, - 39.2, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.4, - 39.3, - 39.3, - 39.3, - 39.2, - 39.2, - 39.2, - 39.1, - 38.8, - 38.4, - 38.3, - 37.9, - 37.6, - 37.3, - 37.1, - 36.8, - 36.6, - 36.3, - 36.2, - 35.9, - 35.7, - 35.4, - 35.2, - 34.8, - 34.5, - 34.1, - 33.8, - 33.7, - 33.6, - 33.4, - 33.2, - 33.1, - 33.1, - 33.1, - 32.9, - 32.5, - 32.1, - 31.8, - 31.4, - 31.1, - 31.1, - 31.1, - 31, - 30.9, - 30.7, - 30.5, - 30.2, - 29.9, - 29.7, - 29.4, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.9, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 28.5, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 27.9, - 27.9, - 27.8, - 27.6, - 27.6, - 27.6, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.2, - 27, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.5, - 25.3, - 25.2, - 25.1, - 25.1, - 24.9, - 24.6, - 24.4, - 24.2, - 24, - 23.7, - 23.6, - 23.4, - 23.3, - 23.3, - 23.3, - 23.2, - 23.1, - 22.8, - 22.5, - 22.2, - 22, - 21.8, - 21.5, - 21.2, - 21, - 20.8, - 20.6, - 20.4, - 20.3, - 20.1, - 19.9, - 19.7, - 19.5, - 19.3, - 19.2, - 19.2, - 19.2, - 19.2, - 19, - 19, - 19, - 19, - 19.1, - 19.1, - 19.2, - 19.3, - 19.4, - 19.5, - 19.7, - 19.8, - 19.9, - 20, - 20, - 20.1, - 20.3, - 20.6, - 20.7, - 20.7, - 20.8, - 20.7, - 20.8, - 20.8, - 20.5, - 20.3, - 20, - 19.8, - 19.6, - 19.5, - 19.4, - 19.3, - 19.1, - 18.9, - 18.8, - 18.6, - 18.5, - 18.5, - 18.5, - 18.5, - 18.5, - 18.5, - 18.5, - 18.5, - 18.6, - 18.6, - 18.7, - 18.7, - 18.7, - 18.7, - 18.8, - 18.8, - 18.9, - 18.9, - 19, - 33.3, - 34.1, - 34.8, - 35.3, - 35.5, - 35.6, - 36, - 36.3, - 36.7, - 37.2, - 37.5, - 37.8, - 38.2, - 38.5, - 38.9, - 39.1, - 39.2, - 39.4, - 39.6, - 39.8, - 40, - 40.2, - 40.3, - 40.4, - 40.5, - 40.6, - 40.6, - 40.7, - 40.7, - 40.7, - 40.8, - 40.8, - 40.7, - 40.6, - 40.2, - 40, - 39.7, - 39.6, - 39.5, - 39.4, - 39.2, - 39.1, - 39, - 38.9, - 39, - 39, - 38.9, - 38.9, - 39, - 39, - 39.1, - 39.1, - 39.2, - 39.2, - 39.2, - 39.3, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.3, - 39.3, - 39.3, - 39.2, - 39.1, - 39.2, - 39.1, - 38.6, - 38.3, - 38.1, - 37.8, - 37.5, - 37.2, - 36.9, - 36.6, - 36.4, - 36.1, - 35.9, - 35.7, - 35.4, - 35.2, - 34.9, - 34.6, - 34.3, - 33.9, - 33.7, - 33.6, - 33.5, - 33.3, - 33.2, - 33.1, - 33.1, - 32.9, - 32.6, - 32.2, - 31.7, - 31.4, - 31.1, - 31, - 30.9, - 30.9, - 30.9, - 30.7, - 30.4, - 30.2, - 29.9, - 29.6, - 29.4, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.9, - 28.9, - 28.8, - 28.8, - 28.6, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.9, - 27.9, - 27.8, - 27.7, - 27.5, - 27.5, - 27.5, - 27.5, - 27.6, - 27.5, - 27.4, - 27.1, - 26.9, - 26.9, - 27, - 26.8, - 26.6, - 26.4, - 26.3, - 26.1, - 25.9, - 25.9, - 25.7, - 25.6, - 25.4, - 25.3, - 25.2, - 25.1, - 25.1, - 25, - 24.8, - 24.5, - 24.3, - 24.1, - 23.9, - 23.7, - 23.6, - 23.5, - 23.3, - 23.3, - 23.2, - 23, - 22.7, - 22.5, - 22.3, - 22.1, - 21.7, - 21.4, - 21.2, - 21, - 20.7, - 20.5, - 20.3, - 20.2, - 20, - 19.7, - 19.5, - 19.3, - 19.2, - 19.1, - 19, - 19, - 18.9, - 18.8, - 18.7, - 18.7, - 18.8, - 18.8, - 18.8, - 18.9, - 18.9, - 19, - 19.3, - 19.4, - 19.5, - 19.6, - 19.6, - 19.7, - 19.8, - 19.9, - 20.1, - 20.4, - 20.5, - 20.5, - 20.6, - 20.6, - 20.6, - 20.5, - 20.3, - 20, - 19.7, - 19.5, - 19.4, - 19.3, - 19.2, - 19.1, - 18.9, - 18.7, - 18.5, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.3, - 18.4, - 18.4, - 18.4, - 18.4, - 18.5, - 18.5, - 18.5, - 18.6, - 18.6, - 18.7, - 18.8, - 18.9, - 33.4, - 33.8, - 34.3, - 34.8, - 35.3, - 35.7, - 36.1, - 36.5, - 36.9, - 37.3, - 37.7, - 38.1, - 38.4, - 38.7, - 39, - 39.2, - 39.4, - 39.6, - 39.8, - 40, - 40.2, - 40.3, - 40.5, - 40.5, - 40.7, - 40.8, - 40.8, - 40.9, - 40.9, - 40.9, - 41, - 40.8, - 40.7, - 40.6, - 40.3, - 40, - 39.9, - 39.7, - 39.6, - 39.5, - 39.4, - 39.2, - 39.1, - 39, - 39.1, - 39.1, - 39, - 39, - 39.1, - 39.1, - 39.2, - 39.2, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.5, - 39.5, - 39.5, - 39.4, - 39.4, - 39.3, - 39.3, - 39.2, - 39.2, - 39.3, - 39.3, - 38.9, - 38.5, - 38.2, - 37.9, - 37.6, - 37.3, - 37.1, - 36.7, - 36.4, - 36.2, - 35.9, - 35.7, - 35.5, - 35.2, - 34.9, - 34.7, - 34.4, - 34.1, - 33.8, - 33.6, - 33.5, - 33.3, - 33.2, - 33.2, - 33.1, - 32.9, - 32.6, - 32.2, - 31.7, - 31.4, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.3, - 30.1, - 29.8, - 29.6, - 29.4, - 29.3, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.9, - 28.9, - 28.9, - 28.6, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28, - 27.9, - 27.8, - 27.8, - 27.8, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.3, - 27.2, - 27, - 26.9, - 26.8, - 26.8, - 26.9, - 26.7, - 26.4, - 26.2, - 26.1, - 25.9, - 25.8, - 25.8, - 25.6, - 25.4, - 25.3, - 25.2, - 25.1, - 25.1, - 25.1, - 24.9, - 24.7, - 24.6, - 24.3, - 24, - 23.8, - 23.7, - 23.5, - 23.4, - 23.3, - 23.1, - 22.9, - 22.6, - 22.3, - 22.3, - 22.2, - 21.9, - 21.4, - 21.1, - 20.9, - 20.7, - 20.4, - 20.2, - 19.9, - 19.7, - 19.5, - 19.3, - 19.2, - 19, - 18.9, - 18.9, - 18.8, - 18.7, - 18.6, - 18.6, - 18.6, - 18.5, - 18.6, - 18.6, - 18.7, - 18.8, - 18.8, - 18.8, - 19, - 19.1, - 19.1, - 19.2, - 19.3, - 19.4, - 19.5, - 19.7, - 19.9, - 20.1, - 20.2, - 20.3, - 20.3, - 20.3, - 20.2, - 20.1, - 19.9, - 19.7, - 19.5, - 19.4, - 19.3, - 19.1, - 19, - 18.9, - 18.7, - 18.5, - 18.4, - 18.3, - 18.3, - 18.2, - 18.2, - 18.2, - 18.2, - 18.2, - 18.2, - 18.2, - 18.2, - 18.2, - 18.2, - 18.2, - 18.3, - 18.3, - 18.4, - 18.5, - 18.5, - 18.6, - 18.7, - 33.4, - 33.9, - 34.4, - 35, - 35.4, - 35.9, - 36.3, - 36.7, - 37.1, - 37.5, - 37.9, - 38.3, - 38.7, - 39, - 39.3, - 39.5, - 39.7, - 39.9, - 40.1, - 40.2, - 40.4, - 40.5, - 40.6, - 40.8, - 41.1, - 41.5, - 41.3, - 41.1, - 41.1, - 41.1, - 41.1, - 40.9, - 40.9, - 40.7, - 40.4, - 40.2, - 40, - 39.9, - 39.8, - 39.6, - 39.5, - 39.4, - 39.2, - 39.2, - 39.2, - 39.2, - 39.1, - 39.1, - 39.2, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.4, - 39.3, - 39.3, - 39.2, - 39.1, - 39.1, - 39, - 38.6, - 38.3, - 38, - 37.8, - 37.5, - 37.2, - 36.9, - 36.6, - 36.3, - 36, - 35.7, - 35.4, - 35.2, - 34.9, - 34.7, - 34.4, - 34.1, - 33.9, - 33.7, - 33.5, - 33.3, - 33.3, - 33.3, - 33.2, - 32.9, - 32.6, - 32.2, - 31.8, - 31.4, - 31.1, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.3, - 30, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.9, - 28.9, - 28.9, - 28.8, - 28.6, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 27.9, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.3, - 27.3, - 27.2, - 27.1, - 26.9, - 26.9, - 26.9, - 26.8, - 26.7, - 26.7, - 26.5, - 26.3, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 25.3, - 25.2, - 25.1, - 25, - 25, - 25.1, - 24.8, - 24.7, - 24.5, - 24.3, - 24.1, - 23.9, - 23.7, - 23.5, - 23.5, - 23.3, - 23, - 22.7, - 22.4, - 22.2, - 22.1, - 22, - 21.6, - 21.1, - 20.7, - 20.4, - 20.2, - 19.9, - 19.6, - 19.4, - 19.2, - 19, - 18.9, - 18.8, - 18.8, - 18.7, - 18.7, - 18.5, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.4, - 18.5, - 18.5, - 18.6, - 18.6, - 18.8, - 18.8, - 18.8, - 18.9, - 19, - 19.3, - 19.3, - 19.5, - 19.7, - 19.9, - 20.1, - 20.1, - 20.1, - 20, - 19.9, - 19.7, - 19.6, - 19.5, - 19.4, - 19.3, - 19.2, - 19, - 18.8, - 18.7, - 18.5, - 18.4, - 18.3, - 18.2, - 18.1, - 18.1, - 18.1, - 18, - 18, - 18, - 18, - 18, - 18, - 18, - 18, - 18, - 18.1, - 18.2, - 18.3, - 18.3, - 18.3, - 18.4, - 18.6, - 33.5, - 34, - 34.7, - 35.3, - 35.6, - 36, - 36.5, - 36.9, - 37.4, - 37.8, - 38.2, - 38.6, - 38.9, - 39.3, - 39.5, - 39.7, - 39.9, - 40.2, - 40.3, - 40.4, - 40.6, - 40.7, - 40.9, - 41, - 41.5, - 42, - 41.8, - 41.3, - 41.2, - 41.2, - 41.1, - 40.9, - 40.9, - 40.7, - 40.5, - 40.3, - 40.1, - 40, - 39.8, - 39.8, - 39.7, - 39.5, - 39.4, - 39.3, - 39.3, - 39.3, - 39.3, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.5, - 39.5, - 39.5, - 39.6, - 39.6, - 39.6, - 39.6, - 39.5, - 39.5, - 39.4, - 39.4, - 39.3, - 39.2, - 39.1, - 39, - 38.8, - 38.5, - 38.1, - 37.8, - 37.5, - 37.3, - 37, - 36.8, - 36.5, - 36.2, - 35.8, - 35.5, - 35.2, - 34.9, - 34.7, - 34.4, - 34.2, - 34, - 33.8, - 33.6, - 33.4, - 33.4, - 33.4, - 33.3, - 33, - 32.6, - 32.2, - 31.8, - 31.5, - 31.2, - 30.9, - 30.7, - 30.5, - 30.4, - 30.3, - 30.2, - 30, - 29.7, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.9, - 28.9, - 28.8, - 28.7, - 28.7, - 28.5, - 28.5, - 28.5, - 28.4, - 28.4, - 28.2, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.9, - 26.9, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.1, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.4, - 25.2, - 25.1, - 25, - 25, - 25.1, - 24.8, - 24.7, - 24.5, - 24.3, - 24.1, - 23.9, - 23.8, - 23.6, - 23.5, - 23.3, - 22.8, - 22.4, - 22.1, - 22, - 21.9, - 21.7, - 21.2, - 20.7, - 20.3, - 19.9, - 19.6, - 19.3, - 19.1, - 18.9, - 18.8, - 18.7, - 18.6, - 18.6, - 18.6, - 18.5, - 18.4, - 18.3, - 18.3, - 18.3, - 18.3, - 18.3, - 18.3, - 18.3, - 18.3, - 18.4, - 18.4, - 18.5, - 18.5, - 18.6, - 18.7, - 18.8, - 18.7, - 18.7, - 18.9, - 19, - 19.2, - 19.4, - 19.7, - 19.9, - 19.8, - 19.8, - 19.7, - 19.6, - 19.5, - 19.4, - 19.3, - 19.3, - 19.3, - 19, - 18.8, - 18.6, - 18.4, - 18.3, - 18.2, - 18.1, - 18, - 18, - 18, - 18, - 17.9, - 17.9, - 17.8, - 17.8, - 17.8, - 17.8, - 17.8, - 17.8, - 17.9, - 17.9, - 18, - 18.1, - 18.1, - 18.2, - 18.2, - 18.3, - 33.6, - 34.1, - 34.7, - 35.3, - 35.8, - 36.3, - 36.8, - 37.2, - 37.7, - 38.1, - 38.5, - 38.8, - 39.1, - 39.4, - 39.7, - 39.9, - 40.1, - 40.4, - 40.6, - 40.7, - 40.9, - 41, - 41.1, - 41.2, - 41.5, - 42.1, - 42, - 41.8, - 41.7, - 41.4, - 41.1, - 40.9, - 40.8, - 40.7, - 40.5, - 40.4, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.5, - 39.5, - 39.6, - 39.6, - 39.6, - 39.6, - 39.7, - 39.7, - 39.7, - 39.6, - 39.6, - 39.6, - 39.5, - 39.4, - 39.4, - 39.2, - 39.1, - 39, - 38.7, - 38.5, - 38.1, - 37.7, - 37.3, - 37.1, - 36.9, - 36.6, - 36.3, - 36, - 35.6, - 35.3, - 35, - 34.7, - 34.5, - 34.2, - 34, - 33.9, - 33.7, - 33.5, - 33.5, - 33.5, - 33.3, - 33.1, - 32.7, - 32.3, - 31.8, - 31.5, - 31.2, - 31, - 30.7, - 30.5, - 30.3, - 30.2, - 30, - 29.8, - 29.7, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.8, - 28.9, - 28.8, - 28.8, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.8, - 26.8, - 26.6, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.6, - 25.5, - 25.3, - 25.2, - 25.1, - 25.1, - 25.1, - 24.9, - 24.7, - 24.6, - 24.5, - 24.2, - 24, - 23.8, - 23.7, - 23.5, - 23.1, - 22.6, - 22.3, - 22, - 21.8, - 21.6, - 21.5, - 21.1, - 20.5, - 20, - 19.5, - 19.2, - 19, - 18.9, - 18.7, - 18.6, - 18.6, - 18.5, - 18.5, - 18.4, - 18.3, - 18.3, - 18.3, - 18.3, - 18.3, - 18.2, - 18.3, - 18.3, - 18.3, - 18.3, - 18.4, - 18.4, - 18.4, - 18.5, - 18.5, - 18.6, - 18.7, - 18.6, - 18.6, - 18.6, - 18.7, - 18.9, - 19.3, - 19.4, - 19.5, - 19.5, - 19.6, - 19.5, - 19.4, - 19.3, - 19.2, - 19, - 19.2, - 19.2, - 18.8, - 18.6, - 18.4, - 18.2, - 18.1, - 18, - 17.9, - 17.9, - 17.9, - 17.9, - 17.8, - 17.7, - 17.7, - 17.7, - 17.6, - 17.6, - 17.6, - 17.6, - 17.6, - 17.7, - 17.8, - 17.9, - 18, - 18, - 18, - 18.1, - 18.2, - 33.8, - 34.3, - 34.9, - 35.5, - 36, - 36.5, - 37.1, - 37.5, - 37.9, - 38.3, - 38.7, - 39, - 39.3, - 39.6, - 39.9, - 40.1, - 40.3, - 40.5, - 40.7, - 41, - 41.1, - 41.3, - 41.4, - 41.4, - 41.5, - 41.7, - 41.8, - 42.1, - 42.4, - 41.9, - 41.2, - 41, - 40.9, - 40.7, - 40.6, - 40.5, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.6, - 39.6, - 39.7, - 39.6, - 39.6, - 39.6, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.6, - 39.6, - 39.5, - 39.5, - 39.3, - 39.2, - 39.2, - 39.1, - 38.9, - 38.4, - 38, - 37.6, - 37.3, - 37, - 36.7, - 36.5, - 36.2, - 35.9, - 35.5, - 35.1, - 34.8, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.6, - 33.6, - 33.6, - 33.4, - 33.2, - 32.8, - 32.4, - 32, - 31.6, - 31.3, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.5, - 29.4, - 29.3, - 29.3, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.9, - 28.8, - 28.8, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.4, - 28.4, - 28.4, - 28.4, - 28.2, - 28.2, - 28.1, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.9, - 26.7, - 26.6, - 26.6, - 26.5, - 26.5, - 26.3, - 26.1, - 25.9, - 25.9, - 25.8, - 25.7, - 25.7, - 25.7, - 25.6, - 25.5, - 25.3, - 25.2, - 25.1, - 25.1, - 24.9, - 24.8, - 24.6, - 24.5, - 24.2, - 23.9, - 23.8, - 23.7, - 23.4, - 23, - 22.5, - 22.2, - 21.9, - 21.6, - 21.4, - 21.3, - 20.9, - 20.3, - 19.7, - 19.2, - 19, - 18.9, - 18.7, - 18.6, - 18.5, - 18.5, - 18.5, - 18.4, - 18.4, - 18.3, - 18.3, - 18.3, - 18.2, - 18.2, - 18.2, - 18.2, - 18.3, - 18.3, - 18.3, - 18.3, - 18.2, - 18.2, - 18.3, - 18.4, - 18.4, - 18.4, - 18.4, - 18.5, - 18.4, - 18.5, - 18.7, - 18.9, - 19.1, - 19.2, - 19.4, - 19.4, - 19.3, - 19.2, - 19.1, - 19, - 18.9, - 19.1, - 19, - 18.5, - 18.4, - 18.2, - 18.1, - 18, - 17.8, - 17.8, - 17.8, - 17.8, - 17.7, - 17.6, - 17.6, - 17.5, - 17.5, - 17.4, - 17.4, - 17.4, - 17.4, - 17.5, - 17.5, - 17.6, - 17.7, - 17.8, - 17.8, - 17.9, - 17.9, - 18, - 34, - 34.6, - 35.1, - 35.7, - 36.3, - 36.8, - 37.3, - 37.8, - 38.2, - 38.6, - 38.9, - 39.3, - 39.6, - 39.8, - 40.1, - 40.3, - 40.6, - 40.8, - 41, - 41.3, - 41.4, - 41.5, - 41.5, - 41.6, - 41.6, - 41.6, - 41.7, - 42.1, - 42.4, - 42.2, - 41.5, - 41.2, - 41, - 40.8, - 40.7, - 40.5, - 40.4, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.7, - 39.7, - 39.8, - 39.7, - 39.8, - 39.7, - 39.7, - 39.8, - 39.8, - 39.8, - 39.8, - 39.7, - 39.7, - 39.7, - 39.7, - 39.6, - 39.6, - 39.5, - 39.4, - 39.5, - 39.4, - 39.1, - 38.8, - 38.4, - 38, - 37.6, - 37.3, - 37, - 36.7, - 36.4, - 36.1, - 35.8, - 35.4, - 35, - 34.7, - 34.4, - 34.1, - 33.9, - 33.8, - 33.7, - 33.6, - 33.6, - 33.6, - 33.3, - 33, - 32.5, - 32.1, - 31.8, - 31.5, - 31.2, - 30.9, - 30.7, - 30.4, - 30.2, - 30, - 29.8, - 29.7, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.9, - 28.9, - 28.8, - 28.9, - 28.8, - 28.8, - 28.7, - 28.9, - 28.9, - 29, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.4, - 28.5, - 28.4, - 28.2, - 28.1, - 28.1, - 28, - 27.9, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.9, - 26.8, - 26.7, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.7, - 25.7, - 25.5, - 25.5, - 25.5, - 25.5, - 25.3, - 25.3, - 25.2, - 25.1, - 24.8, - 24.6, - 24.5, - 24.2, - 23.9, - 23.8, - 23.6, - 23.3, - 22.9, - 22.5, - 22, - 21.7, - 21.4, - 21.3, - 21.1, - 20.5, - 19.8, - 19.3, - 19, - 18.8, - 18.7, - 18.7, - 18.6, - 18.5, - 18.5, - 18.4, - 18.4, - 18.3, - 18.3, - 18.3, - 18.2, - 18.2, - 18.2, - 18.2, - 18.2, - 18.2, - 18.1, - 18.1, - 18, - 17.8, - 17.8, - 18.1, - 18.2, - 18.2, - 18.3, - 18.3, - 18.3, - 18.3, - 18.3, - 18.3, - 18.5, - 18.7, - 18.9, - 19.1, - 19.1, - 19.1, - 19, - 19, - 18.9, - 18.7, - 18.8, - 18.6, - 18.3, - 18.2, - 18.1, - 18, - 17.8, - 17.7, - 17.7, - 17.6, - 17.6, - 17.5, - 17.5, - 17.4, - 17.3, - 17.3, - 17.3, - 17.3, - 17.3, - 17.3, - 17.3, - 17.4, - 17.4, - 17.5, - 17.6, - 17.6, - 17.7, - 17.8, - 17.8, - 34.3, - 34.9, - 35.5, - 36, - 36.6, - 37.1, - 37.6, - 38.1, - 38.5, - 38.9, - 39.2, - 39.5, - 39.8, - 40.1, - 40.3, - 40.6, - 40.8, - 41.1, - 41.3, - 41.5, - 41.6, - 41.7, - 41.7, - 41.7, - 41.7, - 41.7, - 41.7, - 42, - 42.1, - 41.9, - 41.5, - 41.2, - 41.1, - 40.9, - 40.8, - 40.7, - 40.5, - 40.4, - 40.3, - 40.2, - 40, - 40, - 40, - 39.9, - 39.8, - 39.8, - 39.8, - 39.8, - 39.8, - 39.9, - 39.9, - 39.8, - 39.8, - 39.9, - 39.9, - 39.8, - 39.8, - 39.8, - 39.7, - 39.7, - 39.6, - 39.6, - 39.7, - 39.7, - 39.6, - 39.5, - 39.1, - 38.6, - 38.4, - 38, - 37.6, - 37.3, - 37, - 36.7, - 36.4, - 36.1, - 35.7, - 35.3, - 35, - 34.6, - 34.3, - 34, - 33.8, - 33.7, - 33.7, - 33.7, - 33.7, - 33.5, - 33.1, - 32.7, - 32.3, - 32, - 31.7, - 31.4, - 31.1, - 30.8, - 30.6, - 30.3, - 30.1, - 29.9, - 29.7, - 29.5, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.9, - 28.9, - 28.8, - 28.7, - 28.9, - 29, - 29, - 28.8, - 28.8, - 28.8, - 28.6, - 28.4, - 28.4, - 28.4, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 27.7, - 27.6, - 27.6, - 27.5, - 27.4, - 27.3, - 27.3, - 27.1, - 27.1, - 27, - 26.9, - 26.9, - 26.8, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 25.6, - 25.6, - 25.5, - 25.4, - 25.3, - 25.2, - 25, - 24.8, - 24.6, - 24.6, - 24.2, - 23.9, - 23.6, - 23.5, - 23.2, - 22.8, - 22.5, - 22, - 21.6, - 21.3, - 21.2, - 21, - 20.1, - 19.4, - 19, - 18.9, - 18.8, - 18.7, - 18.6, - 18.6, - 18.5, - 18.5, - 18.4, - 18.4, - 18.3, - 18.3, - 18.3, - 18.2, - 18.2, - 18.2, - 18.2, - 18.1, - 18, - 17.9, - 17.9, - 17.7, - 17.5, - 17.6, - 17.9, - 18.1, - 18.1, - 18.2, - 18.2, - 18.2, - 18.2, - 18.2, - 18.1, - 18.2, - 18.5, - 18.7, - 18.8, - 18.9, - 18.8, - 18.8, - 18.7, - 18.7, - 18.5, - 18.4, - 18.3, - 18.2, - 18.1, - 18, - 17.8, - 17.7, - 17.6, - 17.6, - 17.5, - 17.4, - 17.4, - 17.3, - 17.3, - 17.2, - 17.2, - 17.1, - 17.1, - 17.2, - 17.1, - 17.1, - 17.1, - 17.3, - 17.3, - 17.4, - 17.5, - 17.5, - 17.6, - 17.7, - 34.6, - 35.2, - 35.8, - 36.3, - 36.9, - 37.4, - 37.9, - 38.4, - 38.8, - 39.2, - 39.5, - 39.8, - 40.1, - 40.3, - 40.6, - 40.8, - 41.1, - 41.3, - 41.5, - 41.7, - 41.8, - 41.8, - 41.8, - 41.9, - 41.9, - 41.9, - 41.9, - 42.2, - 42, - 41.7, - 41.4, - 41.2, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.6, - 40.5, - 40.3, - 40.2, - 40.1, - 40.1, - 40, - 39.9, - 39.9, - 39.9, - 39.9, - 39.9, - 40, - 40, - 40, - 39.9, - 39.9, - 39.9, - 40, - 39.9, - 39.8, - 39.7, - 39.7, - 39.7, - 39.8, - 40, - 39.9, - 39.5, - 39.2, - 38.9, - 38.6, - 38.4, - 38, - 37.6, - 37.2, - 36.9, - 36.7, - 36.4, - 36.1, - 35.7, - 35.3, - 34.9, - 34.6, - 34.2, - 33.9, - 33.8, - 33.8, - 33.8, - 33.8, - 33.6, - 33.3, - 32.8, - 32.4, - 32.1, - 31.9, - 31.6, - 31.3, - 31, - 30.7, - 30.5, - 30.2, - 30, - 29.8, - 29.7, - 29.5, - 29.4, - 29.3, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 29, - 28.9, - 28.9, - 29, - 29, - 28.9, - 28.8, - 28.8, - 28.8, - 28.7, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28.1, - 27.9, - 27.7, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.3, - 27.1, - 27, - 27, - 27, - 26.9, - 26.9, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26.1, - 26, - 26, - 25.9, - 25.8, - 25.6, - 25.7, - 25.7, - 25.5, - 25.4, - 25.4, - 25.2, - 25, - 24.8, - 24.7, - 24.6, - 24.2, - 23.9, - 23.6, - 23.4, - 23.2, - 22.7, - 22.5, - 22, - 21.5, - 21.3, - 21.1, - 20.7, - 19.8, - 19.2, - 18.9, - 18.8, - 18.7, - 18.7, - 18.6, - 18.6, - 18.5, - 18.5, - 18.5, - 18.4, - 18.4, - 18.3, - 18.3, - 18.2, - 18.2, - 18.2, - 18.1, - 18, - 17.8, - 17.5, - 17.5, - 17.4, - 17.5, - 17.5, - 17.6, - 17.9, - 18, - 18.1, - 18, - 18, - 18.1, - 18.1, - 18.1, - 18.1, - 18.3, - 18.5, - 18.6, - 18.7, - 18.6, - 18.6, - 18.5, - 18.4, - 18.3, - 18.2, - 18.1, - 18.1, - 17.9, - 17.8, - 17.7, - 17.6, - 17.5, - 17.4, - 17.4, - 17.3, - 17.2, - 17.1, - 17.1, - 17.1, - 17, - 17, - 16.9, - 17, - 16.9, - 16.9, - 17, - 17.1, - 17.1, - 17.2, - 17.3, - 17.3, - 17.4, - 17.4, - 34.6, - 35.4, - 36, - 36.5, - 37.1, - 37.7, - 38.2, - 38.6, - 39, - 39.5, - 39.8, - 40, - 40.3, - 40.5, - 40.8, - 41.1, - 41.3, - 41.5, - 41.7, - 41.8, - 41.9, - 41.9, - 42, - 42.1, - 42.1, - 42.1, - 42, - 42, - 42, - 41.7, - 41.5, - 41.3, - 41.2, - 41.1, - 40.9, - 40.8, - 40.8, - 40.7, - 40.6, - 40.5, - 40.4, - 40.3, - 40.2, - 40.1, - 40.1, - 40.1, - 40, - 40, - 40.1, - 40.1, - 40.1, - 40.1, - 40, - 40, - 40, - 40.1, - 40.1, - 39.9, - 39.8, - 39.7, - 39.7, - 39.9, - 39.9, - 39.7, - 39.4, - 39.2, - 38.9, - 38.6, - 38.4, - 38, - 37.6, - 37.2, - 36.9, - 36.7, - 36.4, - 36.1, - 35.7, - 35.4, - 35, - 34.6, - 34.2, - 34, - 33.9, - 34, - 33.9, - 33.7, - 33.4, - 33, - 32.6, - 32.3, - 32.1, - 31.8, - 31.5, - 31.3, - 31, - 30.7, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29.1, - 29, - 29, - 29, - 28.9, - 28.9, - 29, - 29, - 29, - 29, - 29.1, - 29.1, - 28.9, - 28.8, - 28.8, - 28.7, - 28.8, - 28.6, - 28.5, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.7, - 27.7, - 27.6, - 27.5, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.4, - 26.2, - 26.2, - 26.2, - 26.2, - 26, - 26, - 25.9, - 25.8, - 25.7, - 25.7, - 25.7, - 25.5, - 25.4, - 25.3, - 25.2, - 25, - 24.8, - 24.6, - 24.6, - 24.3, - 23.8, - 23.6, - 23.3, - 23, - 22.6, - 22.3, - 22.1, - 21.6, - 21.2, - 20.9, - 20.3, - 19.6, - 19.1, - 18.9, - 18.8, - 18.7, - 18.7, - 18.6, - 18.6, - 18.6, - 18.5, - 18.5, - 18.4, - 18.4, - 18.3, - 18.3, - 18.2, - 18.2, - 18.2, - 18.1, - 17.9, - 17.5, - 17.3, - 17.2, - 17.3, - 17.3, - 17.4, - 17.4, - 17.6, - 17.9, - 18, - 18.1, - 18, - 18, - 18, - 18, - 18, - 18.1, - 18.2, - 18.4, - 18.8, - 18.6, - 18.4, - 18.2, - 18.1, - 18.1, - 18, - 17.9, - 17.9, - 17.8, - 17.7, - 17.5, - 17.4, - 17.3, - 17.2, - 17.2, - 17.2, - 17.1, - 17, - 16.9, - 16.9, - 16.8, - 16.8, - 16.8, - 16.8, - 16.8, - 16.8, - 16.8, - 16.9, - 16.9, - 17, - 17.1, - 17.2, - 17.3, - 17.3, - 34.5, - 35.3, - 36, - 36.6, - 37.3, - 37.9, - 38.3, - 38.8, - 39.2, - 39.7, - 40, - 40.2, - 40.5, - 40.8, - 41, - 41.3, - 41.5, - 41.7, - 41.8, - 42, - 42, - 42, - 42.1, - 42.3, - 42.2, - 42.2, - 42, - 41.9, - 41.8, - 41.7, - 41.5, - 41.4, - 41.3, - 41.2, - 41, - 40.9, - 40.8, - 40.8, - 40.7, - 40.6, - 40.5, - 40.5, - 40.4, - 40.3, - 40.3, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.1, - 40.1, - 40.1, - 40.1, - 40, - 39.9, - 39.9, - 39.7, - 39.7, - 39.7, - 39.6, - 39.5, - 39.3, - 39, - 38.8, - 38.6, - 38.3, - 38, - 37.6, - 37.3, - 36.9, - 36.7, - 36.4, - 36.1, - 35.7, - 35.4, - 35.1, - 34.6, - 34.2, - 34.1, - 34.1, - 34, - 33.9, - 33.7, - 33.3, - 32.9, - 32.5, - 32.2, - 32.1, - 31.8, - 31.5, - 31.2, - 30.9, - 30.7, - 30.4, - 30.2, - 30, - 29.9, - 29.7, - 29.5, - 29.4, - 29.3, - 29.3, - 29.2, - 29.2, - 29.1, - 29.1, - 29, - 29, - 29, - 29, - 29, - 29.1, - 29.1, - 29.1, - 29.1, - 29.3, - 29.1, - 28.9, - 28.8, - 28.8, - 28.7, - 28.7, - 28.6, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 27, - 26.7, - 26.9, - 26.8, - 26.6, - 26.4, - 26.4, - 26.4, - 26.2, - 26.1, - 26.2, - 26.2, - 26, - 26, - 25.9, - 25.8, - 25.8, - 25.8, - 25.7, - 25.6, - 25.4, - 25.4, - 25.5, - 25.1, - 24.8, - 24.6, - 24.5, - 24.3, - 23.8, - 23.6, - 23.3, - 22.9, - 22.5, - 22.2, - 22, - 21.6, - 21.1, - 20.6, - 20, - 19.5, - 19.2, - 19, - 18.9, - 18.8, - 18.7, - 18.7, - 18.7, - 18.6, - 18.6, - 18.5, - 18.4, - 18.4, - 18.3, - 18.3, - 18.2, - 18.2, - 18.2, - 18, - 17.8, - 17.6, - 17.4, - 17.2, - 17.3, - 17.3, - 17.3, - 17.3, - 17.4, - 17.7, - 17.9, - 18.1, - 18, - 17.9, - 17.9, - 17.8, - 17.9, - 17.9, - 17.9, - 18, - 18.2, - 18.1, - 18.2, - 17.8, - 17.8, - 17.9, - 17.9, - 17.8, - 17.7, - 17.6, - 17.5, - 17.4, - 17.3, - 17.2, - 17.1, - 17, - 17, - 16.9, - 16.8, - 16.7, - 16.7, - 16.6, - 16.6, - 16.6, - 16.6, - 16.6, - 16.7, - 16.7, - 16.7, - 16.7, - 16.8, - 16.9, - 17, - 17.1, - 17.1, - 34.6, - 35.4, - 36.1, - 36.8, - 37.4, - 38, - 38.6, - 39, - 39.4, - 39.8, - 40.1, - 40.4, - 40.7, - 41, - 41.2, - 41.5, - 41.7, - 41.9, - 42, - 42.1, - 42.1, - 42.1, - 42.2, - 42.3, - 42.5, - 42.3, - 42.1, - 42, - 41.8, - 41.7, - 41.6, - 41.5, - 41.4, - 41.2, - 41, - 41, - 40.9, - 40.8, - 40.7, - 40.7, - 40.6, - 40.6, - 40.6, - 40.6, - 40.5, - 40.4, - 40.3, - 40.3, - 40.3, - 40.3, - 40.3, - 40.2, - 40.2, - 40.1, - 40.1, - 40, - 39.9, - 39.9, - 39.8, - 39.7, - 39.7, - 39.6, - 39.4, - 39.4, - 39.3, - 38.9, - 38.7, - 38.5, - 38.2, - 37.9, - 37.6, - 37.3, - 37, - 36.6, - 36.4, - 36.1, - 35.7, - 35.4, - 35, - 34.6, - 34.3, - 34.2, - 34.1, - 34, - 33.8, - 33.5, - 33.2, - 32.8, - 32.4, - 32.1, - 32, - 31.9, - 31.6, - 31.3, - 31, - 30.7, - 30.5, - 30.3, - 30.1, - 29.9, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29.2, - 29.2, - 29.2, - 29.1, - 29.1, - 29.1, - 29.1, - 29, - 29.1, - 29.2, - 29.2, - 29.2, - 29.1, - 29.1, - 29, - 28.8, - 28.8, - 28.8, - 28.8, - 28.6, - 28.5, - 28.5, - 28.4, - 28.3, - 28.1, - 28, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.4, - 27.4, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 27.1, - 27, - 26.7, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.2, - 26.2, - 26.2, - 26, - 25.9, - 25.9, - 25.9, - 25.8, - 25.8, - 25.6, - 25.6, - 25.6, - 25.6, - 25.6, - 25.2, - 24.7, - 24.5, - 24.4, - 24.3, - 23.9, - 23.6, - 23.3, - 22.9, - 22.5, - 22.2, - 21.9, - 21.4, - 20.9, - 20.5, - 20, - 19.6, - 19.3, - 19.1, - 19, - 18.9, - 18.8, - 18.8, - 18.7, - 18.7, - 18.6, - 18.5, - 18.4, - 18.3, - 18.3, - 18.2, - 18.2, - 18.1, - 18.1, - 18, - 17.8, - 17.7, - 17.4, - 17.3, - 17.3, - 17.3, - 17.3, - 17.3, - 17.4, - 17.5, - 17.6, - 17.8, - 17.8, - 17.8, - 17.7, - 17.7, - 17.7, - 17.7, - 17.7, - 17.7, - 17.7, - 17.7, - 17.7, - 17.6, - 17.7, - 17.6, - 17.6, - 17.6, - 17.5, - 17.4, - 17.3, - 17.2, - 17.1, - 17, - 17, - 16.8, - 16.7, - 16.7, - 16.6, - 16.6, - 16.5, - 16.5, - 16.4, - 16.4, - 16.4, - 16.4, - 16.4, - 16.5, - 16.5, - 16.6, - 16.8, - 16.9, - 16.9, - 16.9, - 16.8, - 34.7, - 35.6, - 36.3, - 37, - 37.7, - 38.3, - 38.8, - 39.2, - 39.6, - 39.9, - 40.3, - 40.6, - 40.9, - 41.1, - 41.4, - 41.6, - 41.8, - 42, - 42.1, - 42.1, - 42.2, - 42.1, - 42.2, - 42.5, - 42.8, - 42.4, - 42.2, - 42, - 42, - 41.9, - 41.7, - 41.7, - 41.4, - 41.3, - 41.1, - 41.1, - 41, - 41, - 40.9, - 40.8, - 40.7, - 40.7, - 40.6, - 40.7, - 40.6, - 40.5, - 40.4, - 40.4, - 40.4, - 40.3, - 40.3, - 40.3, - 40.2, - 40.2, - 40.1, - 40.1, - 39.9, - 39.8, - 39.7, - 39.7, - 39.6, - 39.5, - 39.3, - 39.3, - 39.2, - 38.9, - 38.7, - 38.5, - 38.3, - 38, - 37.7, - 37.3, - 37, - 36.6, - 36.3, - 36.1, - 35.8, - 35.4, - 34.9, - 34.5, - 34.4, - 34.3, - 34.2, - 34, - 33.7, - 33.4, - 33.1, - 32.7, - 32.3, - 32.1, - 32, - 31.9, - 31.7, - 31.4, - 31.1, - 30.8, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.4, - 29.3, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29.1, - 29.1, - 29.1, - 29, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.9, - 28.7, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.1, - 28, - 27.8, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.4, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.8, - 26.7, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.2, - 26, - 25.9, - 25.9, - 25.8, - 25.8, - 25.8, - 25.8, - 25.6, - 25.6, - 25.5, - 25.4, - 25, - 24.6, - 24.5, - 24.4, - 24.2, - 23.8, - 23.7, - 23.3, - 22.9, - 22.5, - 22.1, - 21.8, - 21.3, - 20.8, - 20.4, - 19.9, - 19.6, - 19.4, - 19.1, - 19, - 18.9, - 18.8, - 18.8, - 18.8, - 18.7, - 18.6, - 18.5, - 18.4, - 18.3, - 18.2, - 18.2, - 18.1, - 18.1, - 18.1, - 17.9, - 17.7, - 17.6, - 17.5, - 17.4, - 17.3, - 17.3, - 17.2, - 17.3, - 17.4, - 17.5, - 17.5, - 17.6, - 17.6, - 17.6, - 17.6, - 17.6, - 17.6, - 17.5, - 17.5, - 17.5, - 17.5, - 17.4, - 17.4, - 17.5, - 17.4, - 17.4, - 17.4, - 17.3, - 17.3, - 17.2, - 17.1, - 17, - 16.9, - 16.9, - 16.8, - 16.7, - 16.6, - 16.5, - 16.5, - 16.4, - 16.4, - 16.3, - 16.4, - 16.3, - 16.3, - 16.3, - 16.3, - 16.4, - 16.5, - 16.6, - 16.8, - 16.9, - 16.9, - 16.8, - 16.7, - 35, - 35.8, - 36.6, - 37.2, - 37.9, - 38.4, - 38.9, - 39.4, - 39.8, - 40.1, - 40.4, - 40.7, - 41, - 41.3, - 41.5, - 41.7, - 41.9, - 42, - 42.1, - 42.2, - 42.3, - 42.2, - 42.4, - 42.9, - 43, - 42.5, - 42.3, - 42.2, - 42.1, - 41.9, - 41.7, - 41.7, - 41.5, - 41.3, - 41.2, - 41.1, - 41.1, - 41, - 40.9, - 40.8, - 40.8, - 40.7, - 40.7, - 40.6, - 40.6, - 40.5, - 40.5, - 40.4, - 40.4, - 40.4, - 40.4, - 40.4, - 40.3, - 40.2, - 40.2, - 40.1, - 40, - 39.8, - 39.8, - 39.7, - 39.7, - 39.5, - 39.3, - 39.2, - 39, - 38.9, - 38.6, - 38.4, - 38.3, - 38, - 37.8, - 37.4, - 37, - 36.7, - 36.3, - 36, - 35.6, - 35.1, - 34.8, - 34.6, - 34.5, - 34.4, - 34.2, - 34, - 33.7, - 33.3, - 33, - 32.7, - 32.3, - 32.1, - 32, - 31.9, - 31.7, - 31.5, - 31.2, - 30.9, - 30.7, - 30.5, - 30.3, - 30.1, - 29.9, - 29.7, - 29.6, - 29.5, - 29.4, - 29.4, - 29.4, - 29.3, - 29.3, - 29.2, - 29.2, - 29.2, - 29.1, - 29.1, - 29.1, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28, - 27.9, - 27.7, - 27.7, - 27.8, - 27.6, - 27.6, - 27.5, - 27.4, - 27.4, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.8, - 26.8, - 26.7, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.2, - 26, - 26, - 25.9, - 25.9, - 25.8, - 25.7, - 25.5, - 25.4, - 25.5, - 25.4, - 25.2, - 24.8, - 24.5, - 24.4, - 24, - 23.6, - 23.5, - 23.3, - 23, - 22.5, - 22, - 21.6, - 21.1, - 20.6, - 20.2, - 19.9, - 19.7, - 19.4, - 19.2, - 19.1, - 19, - 18.9, - 18.9, - 18.8, - 18.8, - 18.7, - 18.4, - 18.3, - 18.2, - 18.2, - 18.1, - 18.1, - 18.1, - 18, - 17.9, - 17.8, - 17.6, - 17.5, - 17.3, - 17.3, - 17.2, - 17.2, - 17.2, - 17.3, - 17.4, - 17.5, - 17.4, - 17.5, - 17.5, - 17.5, - 17.7, - 17.7, - 17.5, - 17.4, - 17.4, - 17.3, - 17.3, - 17.3, - 17.3, - 17.2, - 17.2, - 17.2, - 17.1, - 17, - 17.1, - 17, - 16.8, - 16.8, - 16.6, - 16.5, - 16.5, - 16.4, - 16.3, - 16.3, - 16.2, - 16.2, - 16.2, - 16.2, - 16.1, - 16.1, - 16.1, - 16.2, - 16.3, - 16.3, - 16.4, - 16.7, - 16.9, - 16.9, - 16.8, - 16.7, - 35.2, - 36.1, - 36.8, - 37.5, - 38.1, - 38.6, - 39.1, - 39.5, - 39.9, - 40.2, - 40.6, - 40.9, - 41.2, - 41.4, - 41.6, - 41.9, - 42, - 42.1, - 42.2, - 42.3, - 42.3, - 42.6, - 42.9, - 43.1, - 43, - 42.5, - 42.3, - 42.3, - 42.1, - 41.9, - 41.7, - 41.6, - 41.5, - 41.4, - 41.3, - 41.2, - 41.1, - 41, - 40.9, - 40.8, - 40.8, - 40.7, - 40.7, - 40.6, - 40.6, - 40.5, - 40.5, - 40.5, - 40.4, - 40.4, - 40.5, - 40.4, - 40.4, - 40.3, - 40.2, - 40.3, - 40.2, - 39.9, - 39.8, - 39.8, - 39.7, - 39.6, - 39.4, - 39.2, - 38.9, - 38.8, - 38.5, - 38.3, - 38.2, - 37.9, - 37.7, - 37.3, - 37, - 36.6, - 36.3, - 35.9, - 35.4, - 34.9, - 34.7, - 34.6, - 34.6, - 34.4, - 34.1, - 33.9, - 33.6, - 33.3, - 33, - 32.7, - 32.4, - 32.1, - 32, - 31.9, - 31.8, - 31.6, - 31.3, - 31.1, - 30.8, - 30.6, - 30.4, - 30.3, - 30.1, - 30, - 29.8, - 29.7, - 29.7, - 29.6, - 29.7, - 29.7, - 29.6, - 29.5, - 29.3, - 29.3, - 29.2, - 29.1, - 29.1, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.7, - 27.7, - 27.9, - 27.7, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.3, - 27.2, - 27.1, - 27.1, - 27, - 27, - 26.9, - 26.9, - 26.8, - 26.8, - 26.7, - 26.6, - 26.5, - 26.5, - 26.3, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.4, - 25.4, - 25.3, - 25.2, - 24.8, - 24.5, - 24.3, - 23.9, - 23.6, - 23.4, - 23.3, - 23, - 22.4, - 21.9, - 21.4, - 20.9, - 20.5, - 20.2, - 19.8, - 19.6, - 19.4, - 19.2, - 19.1, - 19, - 19, - 18.9, - 18.9, - 18.9, - 18.8, - 18.5, - 18.3, - 18.3, - 18.2, - 18.1, - 18.1, - 18, - 17.9, - 17.8, - 17.8, - 17.5, - 17.4, - 17.3, - 17.3, - 17.3, - 17.2, - 17.2, - 17.3, - 17.4, - 17.4, - 17.4, - 17.4, - 17.4, - 17.4, - 17.4, - 17.4, - 17.4, - 17.4, - 17.3, - 17.2, - 17.2, - 17.2, - 17.2, - 17.1, - 17.1, - 16.9, - 16.9, - 16.8, - 16.8, - 16.8, - 16.6, - 16.6, - 16.5, - 16.4, - 16.3, - 16.3, - 16.2, - 16.1, - 16.1, - 16, - 16, - 16, - 16, - 16, - 16.1, - 16.1, - 16.2, - 16.3, - 16.3, - 16.6, - 16.7, - 16.8, - 16.6, - 16.4, - 35.4, - 36.3, - 37, - 37.7, - 38.3, - 38.8, - 39.2, - 39.6, - 40, - 40.3, - 40.6, - 41, - 41.3, - 41.5, - 41.8, - 42, - 42.1, - 42.2, - 42.3, - 42.3, - 42.4, - 43.1, - 43.7, - 43.4, - 42.9, - 42.5, - 42.3, - 42.1, - 42, - 42, - 41.8, - 41.7, - 41.5, - 41.4, - 41.3, - 41.3, - 41.2, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.7, - 40.6, - 40.6, - 40.6, - 40.6, - 40.5, - 40.5, - 40.5, - 40.6, - 40.5, - 40.5, - 40.4, - 40.3, - 40.5, - 40.3, - 40, - 39.9, - 39.8, - 39.8, - 39.7, - 39.4, - 39, - 38.8, - 38.6, - 38.4, - 38.2, - 38, - 37.8, - 37.6, - 37.3, - 37, - 36.6, - 36.1, - 35.6, - 35.1, - 34.9, - 34.8, - 34.7, - 34.7, - 34.4, - 34, - 33.7, - 33.5, - 33.2, - 33, - 32.7, - 32.4, - 32.2, - 32, - 31.9, - 31.8, - 31.6, - 31.4, - 31.2, - 30.9, - 30.7, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 30.1, - 30.1, - 30.1, - 30.1, - 30.1, - 30.1, - 30.1, - 29.9, - 29.7, - 29.6, - 29.4, - 29.2, - 29.2, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 27.8, - 27.7, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27.1, - 27.1, - 27, - 27, - 27, - 26.9, - 26.8, - 26.8, - 26.8, - 26.7, - 26.6, - 26.3, - 26.2, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.4, - 25.4, - 25.3, - 25.2, - 24.9, - 24.6, - 24.2, - 23.9, - 23.6, - 23.3, - 23.2, - 22.9, - 22.3, - 21.8, - 21.4, - 20.9, - 20.5, - 20.2, - 19.8, - 19.5, - 19.4, - 19.3, - 19.2, - 19.1, - 19, - 19, - 18.9, - 18.9, - 18.9, - 18.6, - 18.4, - 18.3, - 18.2, - 18.1, - 18, - 17.9, - 17.7, - 17.7, - 17.8, - 17.6, - 17.5, - 17.4, - 17.4, - 17.2, - 17.2, - 17.3, - 17.3, - 17.3, - 17.3, - 17.3, - 17.3, - 17.4, - 17.4, - 17.4, - 17.4, - 17.4, - 17.4, - 17.3, - 17.2, - 17.2, - 17.2, - 17.2, - 17.1, - 16.9, - 16.8, - 16.8, - 16.7, - 16.6, - 16.5, - 16.4, - 16.3, - 16.3, - 16.2, - 16.1, - 16.1, - 16, - 16, - 15.9, - 15.9, - 15.9, - 15.9, - 15.8, - 15.8, - 15.9, - 16, - 16.1, - 16.2, - 16.4, - 16.4, - 16.5, - 16.5, - 16.3, - 16.2, - 35.6, - 36.4, - 37.1, - 37.8, - 38.4, - 38.9, - 39.4, - 39.8, - 40.1, - 40.4, - 40.8, - 41.1, - 41.4, - 41.6, - 41.9, - 42.1, - 42.2, - 42.3, - 42.3, - 42.3, - 42.4, - 42.8, - 42.9, - 42.9, - 42.7, - 42.6, - 42.3, - 42.2, - 42.1, - 42, - 41.9, - 41.7, - 41.6, - 41.5, - 41.4, - 41.3, - 41.3, - 41.2, - 41.1, - 40.9, - 40.8, - 40.8, - 40.7, - 40.7, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.5, - 40.4, - 40.5, - 40.7, - 40.3, - 40, - 39.9, - 39.7, - 39.5, - 39.4, - 39.2, - 39, - 38.7, - 38.5, - 38.3, - 38.1, - 37.9, - 37.7, - 37.5, - 37.2, - 36.8, - 36.4, - 35.8, - 35.3, - 35, - 34.9, - 34.9, - 34.8, - 34.6, - 34.3, - 34, - 33.6, - 33.4, - 33.2, - 33, - 32.7, - 32.5, - 32.3, - 32.1, - 31.9, - 31.8, - 31.6, - 31.5, - 31.4, - 31.1, - 30.9, - 30.8, - 30.6, - 30.5, - 30.5, - 30.6, - 30.6, - 30.6, - 30.6, - 30.6, - 30.6, - 30.6, - 30.5, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.4, - 29.3, - 29.2, - 29.1, - 28.9, - 28.8, - 28.7, - 28.6, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 27.9, - 27.8, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.9, - 26.9, - 26.8, - 26.8, - 26.8, - 26.6, - 26.5, - 26.3, - 26.2, - 26.1, - 26, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.4, - 25.3, - 25.3, - 25.2, - 25, - 24.8, - 24.6, - 24.3, - 23.9, - 23.6, - 23.2, - 23, - 22.7, - 22.1, - 21.7, - 21.4, - 21, - 20.5, - 20.1, - 19.7, - 19.5, - 19.4, - 19.3, - 19.2, - 19.1, - 19.1, - 19, - 18.9, - 18.9, - 18.8, - 18.7, - 18.4, - 18.2, - 18.1, - 18, - 17.8, - 17.7, - 17.7, - 17.7, - 17.7, - 17.6, - 17.6, - 17.5, - 17.4, - 17.1, - 17.1, - 17.1, - 17.3, - 17.3, - 17.3, - 17.2, - 17.3, - 17.4, - 17.4, - 17.3, - 17.4, - 17.4, - 17.3, - 17.3, - 17.2, - 17.1, - 17.1, - 17.1, - 17, - 16.8, - 16.7, - 16.6, - 16.6, - 16.5, - 16.3, - 16.2, - 16.2, - 16.1, - 16, - 15.9, - 15.9, - 15.9, - 15.9, - 15.8, - 15.7, - 15.7, - 15.6, - 15.6, - 15.6, - 15.7, - 15.9, - 16, - 16.1, - 16.3, - 16.4, - 16.3, - 16.3, - 16.2, - 16.1, - 35.7, - 36.6, - 37.3, - 37.9, - 38.5, - 39, - 39.5, - 39.9, - 40.2, - 40.6, - 40.9, - 41.2, - 41.5, - 41.8, - 42, - 42.2, - 42.3, - 42.3, - 42.4, - 42.3, - 42.4, - 42.6, - 42.6, - 42.6, - 42.7, - 42.7, - 42.5, - 42.3, - 42.2, - 42, - 42, - 41.8, - 41.7, - 41.5, - 41.5, - 41.4, - 41.3, - 41.3, - 41.2, - 41, - 40.9, - 40.8, - 40.7, - 40.7, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.5, - 40.5, - 40.6, - 40.3, - 40, - 39.8, - 39.7, - 39.5, - 39.3, - 39.1, - 38.9, - 38.7, - 38.4, - 38.2, - 38, - 37.8, - 37.7, - 37.4, - 37.1, - 36.6, - 36, - 35.5, - 35.2, - 35, - 35, - 35, - 34.8, - 34.5, - 34.2, - 33.9, - 33.6, - 33.4, - 33.2, - 33, - 32.8, - 32.7, - 32.5, - 32.3, - 32, - 31.8, - 31.7, - 31.6, - 31.5, - 31.3, - 31.1, - 31, - 30.9, - 30.9, - 31, - 31, - 31, - 31, - 31, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.4, - 29.3, - 29.1, - 29, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.5, - 27.4, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.9, - 26.9, - 26.8, - 26.8, - 26.7, - 26.8, - 26.7, - 26.6, - 26.4, - 26.2, - 26.2, - 26.1, - 26, - 25.9, - 25.9, - 25.7, - 25.4, - 25.3, - 25.3, - 25.3, - 25.1, - 24.9, - 24.7, - 24.5, - 24.2, - 23.9, - 23.6, - 23.2, - 22.9, - 22.6, - 22, - 21.6, - 21.2, - 21, - 20.6, - 20.1, - 19.7, - 19.5, - 19.4, - 19.3, - 19.2, - 19.1, - 19.1, - 19, - 18.9, - 18.9, - 18.9, - 18.8, - 18.4, - 18, - 17.8, - 17.8, - 17.7, - 17.6, - 17.6, - 17.7, - 17.6, - 17.5, - 17.5, - 17.5, - 17.4, - 17.1, - 17.1, - 17, - 17.1, - 17.1, - 17.1, - 17.2, - 17.2, - 17.3, - 17.3, - 17.3, - 17.3, - 17.3, - 17.3, - 17.2, - 17.1, - 17, - 16.9, - 16.9, - 16.8, - 16.7, - 16.6, - 16.6, - 16.4, - 16.3, - 16.2, - 16.1, - 16, - 15.9, - 15.9, - 15.8, - 15.7, - 15.7, - 15.7, - 15.6, - 15.6, - 15.5, - 15.5, - 15.5, - 15.5, - 15.5, - 15.6, - 15.8, - 16, - 16, - 16.1, - 16.1, - 16, - 16, - 16, - 35.9, - 36.7, - 37.3, - 38, - 38.6, - 39.1, - 39.6, - 40, - 40.4, - 40.7, - 41, - 41.3, - 41.6, - 41.9, - 42.1, - 42.2, - 42.3, - 42.3, - 42.4, - 42.4, - 42.4, - 42.5, - 42.5, - 42.6, - 43, - 42.9, - 42.7, - 42.5, - 42.2, - 42.1, - 42, - 41.9, - 41.8, - 41.6, - 41.7, - 41.5, - 41.4, - 41.3, - 41.2, - 41.1, - 40.9, - 41, - 40.9, - 40.8, - 40.6, - 40.7, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.5, - 40.4, - 40.4, - 40.5, - 40.3, - 40, - 39.8, - 39.7, - 39.5, - 39.2, - 39, - 38.9, - 38.7, - 38.4, - 38.2, - 38, - 37.8, - 37.6, - 37.2, - 36.8, - 36.2, - 35.6, - 35.3, - 35.2, - 35.1, - 35.1, - 35.1, - 34.8, - 34.4, - 34.1, - 33.8, - 33.6, - 33.4, - 33.3, - 33.1, - 32.9, - 32.8, - 32.6, - 32.5, - 32.3, - 32.1, - 31.9, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.4, - 31.5, - 31.5, - 31.5, - 31.4, - 31.4, - 31.3, - 31.2, - 31.1, - 31, - 30.8, - 30.7, - 30.5, - 30.3, - 30.1, - 29.9, - 29.7, - 29.5, - 29.4, - 29.2, - 29, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.7, - 26.7, - 26.7, - 26.8, - 26.8, - 26.6, - 26.4, - 26.2, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.6, - 25.4, - 25.3, - 25.3, - 25.2, - 24.9, - 24.7, - 24.7, - 24.4, - 24.2, - 23.8, - 23.6, - 23.2, - 22.9, - 22.5, - 21.9, - 21.5, - 21.2, - 20.9, - 20.6, - 20, - 19.7, - 19.5, - 19.4, - 19.3, - 19.2, - 19.2, - 19.1, - 19, - 18.9, - 18.9, - 18.8, - 18.7, - 18.5, - 18, - 17.7, - 17.6, - 17.6, - 17.6, - 17.6, - 17.6, - 17.5, - 17.5, - 17.5, - 17.4, - 17.3, - 17.1, - 17, - 17, - 17, - 17, - 17, - 17.1, - 17.2, - 17.3, - 17.3, - 17.3, - 17.3, - 17.2, - 17.3, - 17.1, - 17, - 16.9, - 16.8, - 16.8, - 16.7, - 16.7, - 16.6, - 16.5, - 16.3, - 16.2, - 16.1, - 16, - 15.9, - 15.8, - 15.7, - 15.7, - 15.6, - 15.5, - 15.5, - 15.5, - 15.4, - 15.4, - 15.3, - 15.4, - 15.4, - 15.3, - 15.5, - 15.6, - 16, - 16, - 15.9, - 16, - 15.9, - 15.9, - 16, - 35.9, - 36.7, - 37.4, - 38.1, - 38.7, - 39.2, - 39.7, - 40.1, - 40.5, - 40.8, - 41.1, - 41.4, - 41.7, - 41.9, - 42.1, - 42.2, - 42.2, - 42.3, - 42.4, - 42.4, - 42.3, - 42.3, - 42.4, - 42.6, - 43, - 43.2, - 42.9, - 42.5, - 42.3, - 42.1, - 42.1, - 41.9, - 41.8, - 41.6, - 41.6, - 41.4, - 41.3, - 41.2, - 41.1, - 41, - 40.8, - 40.9, - 40.9, - 40.8, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.6, - 40.5, - 40.3, - 40.3, - 40.2, - 40.1, - 40, - 39.8, - 39.7, - 39.5, - 39.2, - 39, - 38.8, - 38.6, - 38.4, - 38.2, - 37.9, - 37.7, - 37.4, - 36.9, - 36.3, - 35.8, - 35.5, - 35.3, - 35.3, - 35.3, - 35.2, - 35.1, - 34.7, - 34.3, - 34, - 33.8, - 33.7, - 33.6, - 33.4, - 33.2, - 33.1, - 33, - 32.8, - 32.6, - 32.5, - 32.3, - 32.2, - 32.2, - 32.1, - 32, - 31.9, - 31.9, - 32, - 32, - 31.9, - 31.9, - 31.8, - 31.7, - 31.6, - 31.4, - 31.3, - 31.1, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.4, - 29.2, - 29, - 28.9, - 28.6, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.3, - 27.2, - 27.2, - 27.3, - 27.2, - 27.2, - 27.1, - 27.1, - 27, - 27.1, - 27, - 26.8, - 26.8, - 26.8, - 26.8, - 26.7, - 26.5, - 26.3, - 26.3, - 26.1, - 26.1, - 25.9, - 25.8, - 25.6, - 25.4, - 25.4, - 25.4, - 25.2, - 24.8, - 24.6, - 24.6, - 24.4, - 24.2, - 23.9, - 23.6, - 23.3, - 22.9, - 22.4, - 21.9, - 21.4, - 21.1, - 20.8, - 20.6, - 20.1, - 19.7, - 19.5, - 19.4, - 19.3, - 19.3, - 19.2, - 19.1, - 19, - 18.9, - 18.8, - 18.8, - 18.7, - 18.5, - 18, - 17.6, - 17.5, - 17.5, - 17.6, - 17.6, - 17.6, - 17.6, - 17.5, - 17.6, - 17.3, - 17.2, - 17.1, - 17, - 17, - 16.9, - 16.9, - 17, - 17, - 17.1, - 17.2, - 17.2, - 17.2, - 17.3, - 17.1, - 17.2, - 17.1, - 17, - 16.9, - 16.9, - 16.9, - 16.7, - 16.6, - 16.5, - 16.3, - 16.1, - 16, - 16, - 15.8, - 15.7, - 15.7, - 15.6, - 15.5, - 15.4, - 15.3, - 15.3, - 15.3, - 15.3, - 15.2, - 15.2, - 15.2, - 15.1, - 15, - 15.3, - 15.6, - 15.9, - 16, - 15.9, - 15.9, - 15.8, - 15.8, - 15.8, - 36, - 36.8, - 37.5, - 38.2, - 38.8, - 39.3, - 39.7, - 40.2, - 40.5, - 40.9, - 41.2, - 41.4, - 41.7, - 41.9, - 42, - 42.1, - 42.2, - 42.2, - 42.3, - 42.3, - 42.3, - 42.4, - 42.4, - 42.5, - 42.8, - 43.1, - 42.9, - 42.5, - 42.3, - 42.1, - 42.2, - 42, - 41.8, - 41.6, - 41.5, - 41.4, - 41.3, - 41.2, - 41.1, - 41, - 40.8, - 40.8, - 40.8, - 40.7, - 40.7, - 40.6, - 40.6, - 40.5, - 40.5, - 40.6, - 40.6, - 40.5, - 40.4, - 40.3, - 40.2, - 40.2, - 40, - 40, - 39.7, - 39.6, - 39.4, - 39.2, - 39, - 38.7, - 38.5, - 38.3, - 38, - 37.7, - 37.4, - 37, - 36.4, - 35.9, - 35.6, - 35.5, - 35.5, - 35.5, - 35.5, - 35.3, - 35, - 34.5, - 34.2, - 34, - 33.9, - 33.8, - 33.7, - 33.6, - 33.5, - 33.3, - 33.2, - 33, - 32.8, - 32.7, - 32.6, - 32.5, - 32.5, - 32.5, - 32.4, - 32.3, - 32.2, - 32.3, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.7, - 31.6, - 31.4, - 31.2, - 31.1, - 30.8, - 30.6, - 30.4, - 30.2, - 30, - 29.9, - 29.7, - 29.5, - 29.3, - 29.1, - 28.9, - 28.7, - 28.5, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 27.1, - 27.1, - 27.1, - 27, - 26.9, - 26.8, - 26.8, - 26.8, - 26.6, - 26.5, - 26.3, - 26.3, - 26.3, - 26.1, - 25.8, - 25.7, - 25.6, - 25.5, - 25.4, - 25.4, - 25.1, - 24.8, - 24.6, - 24.6, - 24.5, - 24.3, - 23.9, - 23.6, - 23.3, - 22.9, - 22.4, - 21.8, - 21.3, - 21, - 20.7, - 20.4, - 20, - 19.7, - 19.6, - 19.5, - 19.4, - 19.3, - 19.2, - 19.1, - 19, - 18.9, - 18.8, - 18.8, - 18.7, - 18.6, - 18, - 17.6, - 17.5, - 17.4, - 17.6, - 17.6, - 17.5, - 17.6, - 17.6, - 17.5, - 17.3, - 17.2, - 17.1, - 17, - 17, - 16.9, - 16.9, - 16.9, - 16.9, - 17.1, - 17.1, - 17, - 17.1, - 17.2, - 17, - 17.1, - 17, - 16.8, - 16.8, - 16.8, - 16.7, - 16.6, - 16.5, - 16.4, - 16.2, - 16, - 15.9, - 15.8, - 15.7, - 15.6, - 15.5, - 15.4, - 15.3, - 15.2, - 15.1, - 15.2, - 15.1, - 15.1, - 15, - 15, - 15, - 14.9, - 14.8, - 15.1, - 15.3, - 15.6, - 15.9, - 16, - 15.9, - 15.8, - 15.7, - 15.7, - 36.1, - 36.9, - 37.6, - 38.3, - 38.8, - 39.3, - 39.8, - 40.2, - 40.6, - 40.9, - 41.2, - 41.4, - 41.6, - 41.8, - 41.9, - 42, - 42.1, - 42.1, - 42.2, - 42.4, - 42.8, - 43, - 42.6, - 42.5, - 42.5, - 42.7, - 42.6, - 42.2, - 42.2, - 42.1, - 42.2, - 42.1, - 41.9, - 41.7, - 41.5, - 41.5, - 41.4, - 41.2, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.7, - 40.7, - 40.6, - 40.6, - 40.5, - 40.5, - 40.5, - 40.5, - 40.4, - 40.4, - 40.3, - 40.2, - 40.2, - 40, - 39.9, - 39.7, - 39.6, - 39.4, - 39.2, - 39, - 38.7, - 38.5, - 38.2, - 37.9, - 37.5, - 37.1, - 36.6, - 36, - 35.8, - 35.7, - 35.7, - 35.8, - 35.8, - 35.6, - 35.3, - 34.9, - 34.4, - 34.1, - 34, - 34, - 33.9, - 33.8, - 33.8, - 33.7, - 33.6, - 33.4, - 33.2, - 33.1, - 33, - 33, - 33, - 32.9, - 32.8, - 32.8, - 32.8, - 32.8, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32.1, - 31.9, - 31.7, - 31.5, - 31.4, - 31.1, - 30.8, - 30.6, - 30.4, - 30.2, - 30.1, - 29.9, - 29.7, - 29.5, - 29.3, - 29.1, - 28.9, - 28.7, - 28.5, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.6, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.4, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 27.1, - 27.1, - 27, - 27, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.4, - 26.3, - 26.1, - 25.8, - 25.7, - 25.6, - 25.6, - 25.5, - 25.4, - 25.1, - 24.8, - 24.6, - 24.6, - 24.5, - 24.3, - 23.9, - 23.5, - 23.2, - 22.9, - 22.3, - 21.7, - 21.3, - 20.9, - 20.5, - 20.2, - 19.9, - 19.7, - 19.7, - 19.6, - 19.5, - 19.4, - 19.3, - 19.1, - 19, - 18.9, - 18.8, - 18.8, - 18.7, - 18.5, - 17.9, - 17.7, - 17.5, - 17.5, - 17.5, - 17.6, - 17.6, - 17.7, - 17.6, - 17.5, - 17.4, - 17.3, - 17.1, - 17, - 16.9, - 16.9, - 16.9, - 16.9, - 16.8, - 16.9, - 17, - 16.9, - 17, - 17.3, - 17.1, - 16.9, - 16.8, - 16.7, - 16.6, - 16.7, - 16.6, - 16.5, - 16.4, - 16.2, - 16, - 15.8, - 15.8, - 15.6, - 15.5, - 15.5, - 15.4, - 15.3, - 15.1, - 15.1, - 15.1, - 15, - 15, - 14.9, - 14.9, - 14.8, - 14.8, - 14.7, - 14.6, - 14.8, - 15, - 15.4, - 15.7, - 15.9, - 16, - 16, - 15.8, - 15.7, - 36.1, - 37, - 37.7, - 38.3, - 38.9, - 39.3, - 39.8, - 40.2, - 40.6, - 40.9, - 41.2, - 41.4, - 41.5, - 41.7, - 41.8, - 41.9, - 42, - 42, - 42.1, - 42.2, - 42.6, - 42.9, - 42.8, - 42.6, - 42.4, - 42.4, - 42.3, - 42.2, - 42.1, - 42, - 42.1, - 41.9, - 41.9, - 41.8, - 41.6, - 41.5, - 41.4, - 41.2, - 41.2, - 41, - 40.9, - 40.8, - 40.7, - 40.7, - 40.6, - 40.6, - 40.6, - 40.5, - 40.5, - 40.5, - 40.5, - 40.5, - 40.5, - 40.4, - 40.3, - 40.1, - 40, - 39.8, - 39.6, - 39.5, - 39.3, - 39.2, - 39, - 38.6, - 38.4, - 38.1, - 37.7, - 37.3, - 36.7, - 36.2, - 35.9, - 35.8, - 35.8, - 36, - 36, - 35.9, - 35.6, - 35.2, - 34.8, - 34.4, - 34.2, - 34.2, - 34.2, - 34.1, - 34.1, - 34, - 33.9, - 34, - 33.8, - 33.5, - 33.4, - 33.4, - 33.4, - 33.4, - 33.4, - 33.2, - 33.3, - 33.3, - 33.4, - 33, - 32.8, - 32.7, - 32.5, - 32.3, - 32.1, - 31.9, - 31.8, - 31.6, - 31.4, - 31.1, - 30.9, - 30.6, - 30.4, - 30.3, - 30.1, - 29.9, - 29.7, - 29.5, - 29.3, - 29.1, - 28.8, - 28.7, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 27.1, - 27, - 27, - 26.9, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.3, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.6, - 25.6, - 25.4, - 25.1, - 24.8, - 24.7, - 24.6, - 24.4, - 24.2, - 23.9, - 23.6, - 23.3, - 22.8, - 22.3, - 21.7, - 21.2, - 20.9, - 20.5, - 20.1, - 19.9, - 19.8, - 19.7, - 19.6, - 19.5, - 19.4, - 19.3, - 19.1, - 19, - 18.9, - 18.9, - 18.8, - 18.7, - 18.3, - 17.8, - 17.7, - 17.6, - 17.6, - 17.6, - 17.6, - 17.6, - 17.6, - 17.5, - 17.5, - 17.5, - 17.3, - 17, - 16.9, - 16.9, - 16.9, - 16.9, - 16.9, - 16.8, - 16.8, - 16.8, - 16.9, - 16.9, - 17, - 16.9, - 17, - 16.8, - 16.7, - 16.5, - 16.6, - 16.5, - 16.3, - 16.3, - 16.1, - 15.9, - 15.7, - 15.6, - 15.5, - 15.4, - 15.3, - 15.2, - 15.1, - 15, - 15, - 15, - 14.9, - 14.9, - 14.8, - 14.7, - 14.7, - 14.6, - 14.4, - 14.4, - 14.6, - 14.8, - 15.1, - 15.6, - 16, - 16, - 15.8, - 15.8, - 15.8, - 36.1, - 37, - 37.7, - 38.3, - 38.8, - 39.4, - 39.8, - 40.2, - 40.5, - 40.8, - 41, - 41.2, - 41.4, - 41.6, - 41.7, - 41.8, - 41.8, - 41.9, - 42, - 42, - 42.1, - 42.4, - 43, - 42.8, - 42.5, - 42.3, - 42.2, - 42.1, - 42.1, - 41.9, - 42, - 41.9, - 41.9, - 41.9, - 41.6, - 41.5, - 41.4, - 41.3, - 41.2, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.6, - 40.5, - 40.5, - 40.5, - 40.5, - 40.5, - 40.5, - 40.6, - 40.6, - 40.5, - 40.3, - 40, - 39.9, - 39.8, - 39.6, - 39.4, - 39.2, - 39, - 38.8, - 38.5, - 38.2, - 37.8, - 37.3, - 36.9, - 36.3, - 36, - 36, - 36, - 36.1, - 36.2, - 36.2, - 35.9, - 35.4, - 35, - 34.8, - 34.6, - 34.5, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 34.1, - 34.1, - 34, - 33.9, - 33.9, - 33.8, - 33.7, - 33.8, - 33.8, - 33.6, - 33.6, - 33.7, - 33.6, - 33.3, - 33.1, - 33, - 32.7, - 32.4, - 32.2, - 32, - 31.8, - 31.7, - 31.4, - 31.1, - 30.9, - 30.7, - 30.5, - 30.3, - 30.1, - 29.9, - 29.7, - 29.6, - 29.3, - 29.1, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.4, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27.1, - 27, - 27, - 26.9, - 26.9, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.7, - 25.7, - 25.5, - 25.4, - 25, - 24.8, - 24.7, - 24.7, - 24.4, - 24.2, - 23.9, - 23.6, - 23.3, - 22.8, - 22.3, - 21.8, - 21.3, - 20.9, - 20.5, - 20.2, - 19.9, - 19.7, - 19.6, - 19.5, - 19.4, - 19.3, - 19.2, - 19.1, - 19, - 19, - 18.9, - 18.8, - 18.5, - 18, - 17.7, - 17.7, - 17.7, - 17.7, - 17.6, - 17.6, - 17.6, - 17.6, - 17.5, - 17.4, - 17.4, - 17.2, - 17, - 16.9, - 16.8, - 16.9, - 17, - 16.9, - 16.8, - 16.7, - 16.7, - 16.7, - 16.8, - 16.9, - 16.9, - 16.9, - 16.8, - 16.7, - 16.6, - 16.4, - 16.4, - 16.3, - 16.2, - 15.9, - 15.7, - 15.6, - 15.5, - 15.3, - 15.2, - 15.1, - 15.1, - 15, - 14.9, - 14.9, - 14.8, - 14.7, - 14.7, - 14.5, - 14.4, - 14.4, - 14.3, - 14.1, - 14.2, - 14.4, - 14.5, - 14.7, - 15.3, - 15.6, - 15.7, - 15.7, - 15.7, - 15.7, - 36.2, - 37.1, - 37.7, - 38.3, - 38.8, - 39.3, - 39.7, - 40.1, - 40.4, - 40.6, - 40.8, - 41, - 41.2, - 41.4, - 41.6, - 41.6, - 41.7, - 41.8, - 41.9, - 41.9, - 42.1, - 42.2, - 42.4, - 42.4, - 42.4, - 42.3, - 42.2, - 42.1, - 42, - 41.8, - 41.8, - 41.8, - 41.8, - 41.8, - 41.6, - 41.5, - 41.4, - 41.3, - 41.2, - 41.1, - 41, - 41, - 40.9, - 40.8, - 40.6, - 40.5, - 40.5, - 40.5, - 40.5, - 40.5, - 40.6, - 40.6, - 40.6, - 40.4, - 40.1, - 39.9, - 39.7, - 39.6, - 39.4, - 39.2, - 39, - 38.8, - 38.5, - 38.2, - 37.9, - 37.3, - 36.8, - 36.4, - 36.2, - 36.1, - 36.1, - 36.2, - 36.4, - 36.4, - 36.2, - 35.8, - 35.3, - 35, - 34.8, - 34.7, - 34.7, - 34.6, - 34.5, - 34.5, - 34.4, - 34.3, - 34.3, - 34.3, - 34.3, - 34.3, - 34.2, - 34.2, - 34.1, - 34.1, - 34.1, - 33.9, - 33.8, - 33.8, - 33.8, - 33.5, - 33.2, - 33, - 32.8, - 32.6, - 32.3, - 32.1, - 32, - 31.7, - 31.4, - 31.1, - 30.8, - 30.6, - 30.4, - 30.3, - 30.1, - 29.9, - 29.8, - 29.6, - 29.3, - 29.1, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.5, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 27, - 27, - 26.9, - 26.9, - 26.8, - 26.6, - 26.5, - 26.3, - 26.1, - 26.1, - 25.9, - 25.8, - 25.7, - 25.7, - 25.7, - 25.5, - 25.2, - 25, - 24.8, - 24.7, - 24.6, - 24.5, - 24.2, - 23.9, - 23.6, - 23.3, - 22.8, - 22.4, - 21.9, - 21.5, - 21, - 20.6, - 20.2, - 19.9, - 19.7, - 19.6, - 19.4, - 19.3, - 19.3, - 19.2, - 19.1, - 19.1, - 19, - 18.8, - 18.6, - 18, - 17.8, - 17.7, - 17.7, - 17.7, - 17.7, - 17.6, - 17.6, - 17.6, - 17.5, - 17.5, - 17.4, - 17.3, - 17.2, - 17.1, - 17, - 16.9, - 17, - 17, - 16.7, - 16.7, - 16.7, - 16.7, - 16.7, - 16.8, - 16.8, - 16.8, - 16.7, - 16.7, - 16.7, - 16.6, - 16.4, - 16.2, - 16.1, - 16, - 15.8, - 15.6, - 15.4, - 15.3, - 15.2, - 15.1, - 15.1, - 15, - 14.9, - 14.8, - 14.8, - 14.7, - 14.5, - 14.5, - 14.5, - 14.3, - 14.3, - 14.2, - 14, - 14, - 14.3, - 14.3, - 14.6, - 15.1, - 15.2, - 15.3, - 15.4, - 15.5, - 15.5, - 36.2, - 37, - 37.7, - 38.2, - 38.7, - 39.2, - 39.6, - 39.9, - 40.2, - 40.4, - 40.6, - 40.9, - 41.2, - 41.3, - 41.4, - 41.5, - 41.5, - 41.6, - 41.7, - 42.2, - 42.7, - 42.6, - 42.2, - 42.1, - 42.1, - 42.1, - 42, - 42, - 41.8, - 41.7, - 41.8, - 41.8, - 41.8, - 41.7, - 41.5, - 41.5, - 41.3, - 41.3, - 41.3, - 41.1, - 40.9, - 40.9, - 40.8, - 40.8, - 40.6, - 40.5, - 40.4, - 40.4, - 40.3, - 40.5, - 40.5, - 40.4, - 40.5, - 40.3, - 39.9, - 39.7, - 39.7, - 39.5, - 39.3, - 38.9, - 38.7, - 38.5, - 38.2, - 37.9, - 37.4, - 36.8, - 36.5, - 36.3, - 36.2, - 36.2, - 36.3, - 36.4, - 36.5, - 36.5, - 36.1, - 35.6, - 35.2, - 35, - 34.9, - 34.8, - 34.8, - 34.7, - 34.6, - 34.6, - 34.5, - 34.5, - 34.5, - 34.5, - 34.5, - 34.5, - 34.5, - 34.4, - 34.4, - 34.3, - 34.3, - 34.2, - 34, - 33.9, - 33.9, - 33.8, - 33.3, - 33, - 32.8, - 32.6, - 32.4, - 32.1, - 31.9, - 31.7, - 31.4, - 31.1, - 30.8, - 30.6, - 30.4, - 30.2, - 30.1, - 29.9, - 29.7, - 29.6, - 29.4, - 29.1, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.9, - 26.9, - 26.9, - 26.7, - 26.5, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.8, - 25.8, - 25.7, - 25.6, - 25.4, - 25.1, - 25, - 24.9, - 24.8, - 24.7, - 24.4, - 24.2, - 23.8, - 23.6, - 23.4, - 22.9, - 22.4, - 22, - 21.6, - 21.2, - 20.7, - 20.3, - 20, - 19.8, - 19.6, - 19.4, - 19.3, - 19.2, - 19.2, - 19.1, - 19.1, - 19, - 18.6, - 18.1, - 17.8, - 17.7, - 17.7, - 17.7, - 17.7, - 17.7, - 17.6, - 17.6, - 17.5, - 17.5, - 17.5, - 17.4, - 17.3, - 17.2, - 17.1, - 17.1, - 17.1, - 17, - 16.7, - 16.6, - 16.6, - 16.6, - 16.7, - 16.7, - 16.7, - 16.8, - 16.9, - 16.8, - 16.7, - 16.6, - 16.5, - 16.4, - 16.2, - 16.1, - 15.9, - 15.6, - 15.5, - 15.3, - 15.3, - 15.2, - 15.1, - 15, - 15, - 14.9, - 14.7, - 14.7, - 14.6, - 14.5, - 14.5, - 14.4, - 14.3, - 14.2, - 14.1, - 13.9, - 13.8, - 14.1, - 14.2, - 14.4, - 14.8, - 15.2, - 15.2, - 15.4, - 15.4, - 15.4, - 36.2, - 37, - 37.6, - 38.1, - 38.6, - 39.1, - 39.5, - 39.7, - 40, - 40.2, - 40.5, - 40.8, - 41, - 41.1, - 41.2, - 41.3, - 41.5, - 41.6, - 41.9, - 42.2, - 42.4, - 42.4, - 42, - 41.9, - 41.8, - 41.9, - 41.9, - 41.9, - 41.7, - 41.7, - 41.8, - 41.9, - 41.8, - 41.6, - 41.5, - 41.4, - 41.3, - 41.3, - 41.3, - 41.1, - 40.9, - 40.8, - 40.7, - 40.6, - 40.5, - 40.4, - 40.4, - 40.3, - 40.2, - 40.3, - 40.4, - 40.3, - 40.3, - 40.1, - 39.9, - 39.7, - 39.6, - 39.4, - 39, - 38.8, - 38.5, - 38.3, - 38, - 37.4, - 36.9, - 36.5, - 36.4, - 36.4, - 36.3, - 36.4, - 36.6, - 36.6, - 36.6, - 36.3, - 35.9, - 35.4, - 35.2, - 35.1, - 35, - 34.9, - 34.9, - 34.8, - 34.8, - 34.7, - 34.8, - 34.8, - 34.8, - 34.8, - 34.7, - 34.7, - 34.7, - 34.6, - 34.6, - 34.5, - 34.4, - 34.3, - 34.2, - 34, - 33.9, - 33.7, - 33.4, - 33, - 32.8, - 32.6, - 32.4, - 32.1, - 31.9, - 31.7, - 31.4, - 31.1, - 30.8, - 30.6, - 30.3, - 30.1, - 30, - 29.9, - 29.7, - 29.5, - 29.3, - 29.1, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.7, - 27.6, - 27.6, - 27.8, - 27.7, - 27.6, - 27.4, - 27.5, - 27.4, - 27.4, - 27.3, - 27.1, - 27.1, - 27.1, - 27, - 27, - 26.9, - 26.8, - 26.8, - 26.7, - 26.4, - 26.3, - 26.2, - 26, - 26, - 25.9, - 25.8, - 25.8, - 25.8, - 25.6, - 25.4, - 25.1, - 25, - 24.9, - 24.8, - 24.6, - 24.3, - 24, - 23.7, - 23.6, - 23.4, - 23.1, - 22.6, - 22.1, - 21.7, - 21.3, - 20.8, - 20.3, - 20.1, - 19.8, - 19.5, - 19.4, - 19.3, - 19.2, - 19, - 19, - 19, - 18.8, - 18.3, - 17.8, - 17.8, - 17.7, - 17.7, - 17.7, - 17.7, - 17.7, - 17.6, - 17.6, - 17.5, - 17.5, - 17.5, - 17.4, - 17.3, - 17.2, - 17.1, - 17.1, - 17, - 16.8, - 16.6, - 16.5, - 16.6, - 16.6, - 16.7, - 16.7, - 16.7, - 16.7, - 16.8, - 16.9, - 16.7, - 16.5, - 16.4, - 16.3, - 16.3, - 16.1, - 15.8, - 15.5, - 15.4, - 15.3, - 15.1, - 15.1, - 15.1, - 15, - 14.9, - 14.8, - 14.7, - 14.6, - 14.5, - 14.4, - 14.5, - 14.4, - 14.2, - 14.2, - 13.9, - 13.7, - 13.7, - 14, - 14.1, - 14.1, - 14.4, - 14.9, - 15.1, - 15.3, - 15.3, - 15.3, - 36.1, - 36.9, - 37.4, - 38, - 38.5, - 38.9, - 39.3, - 39.5, - 39.8, - 40, - 40.3, - 40.7, - 40.7, - 40.8, - 41, - 41.2, - 41.4, - 41.8, - 42.1, - 41.9, - 41.8, - 41.8, - 41.7, - 41.7, - 41.6, - 41.6, - 41.7, - 41.7, - 41.6, - 41.7, - 41.8, - 41.8, - 41.7, - 41.6, - 41.5, - 41.3, - 41.2, - 41.2, - 41.2, - 41.1, - 41, - 40.7, - 40.6, - 40.5, - 40.4, - 40.3, - 40.3, - 40.2, - 40.1, - 40.1, - 40.2, - 40.1, - 40.1, - 40, - 39.7, - 39.5, - 39.3, - 39, - 38.8, - 38.6, - 38.3, - 38.1, - 37.7, - 37, - 36.7, - 36.5, - 36.5, - 36.5, - 36.5, - 36.7, - 36.7, - 36.7, - 36.4, - 36, - 35.6, - 35.4, - 35.3, - 35.2, - 35.1, - 35, - 35, - 35, - 35, - 34.9, - 35, - 35, - 35, - 35, - 35, - 34.8, - 34.8, - 34.7, - 34.7, - 34.6, - 34.5, - 34.3, - 34.2, - 34, - 33.8, - 33.7, - 33.4, - 33, - 32.8, - 32.5, - 32.3, - 32, - 31.8, - 31.7, - 31.4, - 31.1, - 30.8, - 30.5, - 30.2, - 30, - 29.8, - 29.7, - 29.6, - 29.4, - 29.2, - 29, - 28.8, - 28.6, - 28.5, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.7, - 27.6, - 27.5, - 27.7, - 27.7, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.1, - 27.1, - 27.1, - 27, - 26.9, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.2, - 26.2, - 26, - 26, - 25.9, - 25.8, - 25.8, - 25.8, - 25.7, - 25.3, - 25.1, - 25.1, - 24.9, - 24.8, - 24.6, - 24.3, - 24, - 23.7, - 23.6, - 23.4, - 23.1, - 22.6, - 22.2, - 21.7, - 21.3, - 21, - 20.5, - 20.2, - 19.8, - 19.5, - 19.4, - 19.2, - 19, - 18.9, - 18.7, - 18.7, - 18.6, - 18.3, - 17.9, - 17.8, - 17.7, - 17.7, - 17.7, - 17.7, - 17.7, - 17.7, - 17.6, - 17.5, - 17.5, - 17.5, - 17.4, - 17.3, - 17.3, - 17.2, - 17, - 17, - 16.7, - 16.6, - 16.5, - 16.6, - 16.6, - 16.7, - 16.7, - 16.7, - 16.7, - 16.7, - 16.8, - 16.7, - 16.6, - 16.4, - 16.2, - 16.1, - 15.9, - 15.6, - 15.4, - 15.5, - 15.3, - 15.1, - 15, - 15, - 14.9, - 14.9, - 14.8, - 14.7, - 14.6, - 14.5, - 14.4, - 14.4, - 14.3, - 14.1, - 14, - 13.6, - 13.4, - 13.6, - 13.8, - 13.9, - 14, - 14.2, - 14.5, - 14.8, - 14.9, - 15, - 15.1, - 36.1, - 36.8, - 37.4, - 37.9, - 38.4, - 38.8, - 39.1, - 39.3, - 39.6, - 39.9, - 40.1, - 40.4, - 40.5, - 40.7, - 40.9, - 41, - 41.2, - 41.5, - 41.7, - 41.6, - 41.6, - 41.6, - 41.5, - 41.6, - 41.5, - 41.4, - 41.5, - 41.5, - 41.6, - 41.7, - 41.7, - 41.7, - 41.6, - 41.6, - 41.4, - 41.2, - 41.1, - 41.2, - 41.1, - 41, - 40.9, - 40.7, - 40.6, - 40.4, - 40.3, - 40.3, - 40.2, - 40.1, - 40, - 40, - 39.9, - 39.9, - 39.9, - 39.9, - 39.6, - 39.2, - 39, - 38.8, - 38.6, - 38.3, - 38.1, - 37.7, - 37.2, - 36.8, - 36.6, - 36.6, - 36.6, - 36.6, - 36.7, - 36.8, - 36.8, - 36.5, - 36.1, - 35.8, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 35.1, - 35.1, - 35.1, - 35.1, - 35.1, - 35.1, - 35.1, - 35.2, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.8, - 34.6, - 34.5, - 34.4, - 34.2, - 34, - 33.8, - 33.6, - 33.4, - 33.1, - 32.8, - 32.5, - 32.1, - 31.9, - 31.7, - 31.5, - 31.3, - 31.1, - 30.7, - 30.4, - 30.1, - 29.8, - 29.7, - 29.5, - 29.4, - 29.2, - 29.1, - 28.9, - 28.7, - 28.5, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.9, - 27.7, - 27.7, - 27.7, - 27.9, - 27.7, - 27.6, - 27.6, - 27.4, - 27.3, - 27.4, - 27.3, - 27.1, - 27.1, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 26, - 25.9, - 25.9, - 25.8, - 25.8, - 25.5, - 25.2, - 25, - 24.9, - 24.8, - 24.6, - 24.5, - 24.3, - 23.9, - 23.6, - 23.4, - 23.2, - 22.6, - 22.2, - 21.8, - 21.3, - 21, - 20.7, - 20.3, - 19.8, - 19.4, - 19.2, - 19, - 18.8, - 18.7, - 18.4, - 18.3, - 18.2, - 18.1, - 17.9, - 17.8, - 17.8, - 17.7, - 17.7, - 17.7, - 17.7, - 17.7, - 17.6, - 17.5, - 17.4, - 17.5, - 17.4, - 17.4, - 17.3, - 17.1, - 16.9, - 16.9, - 16.6, - 16.6, - 16.5, - 16.6, - 16.6, - 16.6, - 16.7, - 16.7, - 16.7, - 16.6, - 16.6, - 16.6, - 16.5, - 16.3, - 16.1, - 15.9, - 15.7, - 15.5, - 15.5, - 15.5, - 15.3, - 15.1, - 15, - 15, - 14.9, - 14.8, - 14.7, - 14.6, - 14.6, - 14.5, - 14.3, - 14.3, - 14.2, - 14.1, - 13.8, - 13.3, - 13.2, - 13.3, - 13.6, - 13.7, - 13.9, - 14.2, - 14.4, - 14.6, - 14.7, - 14.8, - 14.9, - 36, - 36.7, - 37.3, - 37.8, - 38.3, - 38.7, - 39, - 39.2, - 39.5, - 39.9, - 40.2, - 40.2, - 40.4, - 40.6, - 40.7, - 40.9, - 41, - 41.2, - 41.4, - 41.5, - 41.6, - 41.5, - 41.4, - 41.5, - 41.4, - 41.4, - 41.4, - 41.4, - 41.5, - 41.6, - 41.5, - 41.5, - 41.5, - 41.5, - 41.3, - 41.1, - 41, - 41, - 41, - 40.9, - 40.8, - 40.7, - 40.5, - 40.3, - 40.2, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.6, - 39.4, - 39, - 38.8, - 38.6, - 38.3, - 38.1, - 37.7, - 37.2, - 36.9, - 36.7, - 36.7, - 36.7, - 36.7, - 36.8, - 36.8, - 36.7, - 36.5, - 36.2, - 35.9, - 35.7, - 35.5, - 35.4, - 35.3, - 35.3, - 35.2, - 35.2, - 35.2, - 35.2, - 35.2, - 35.2, - 35.2, - 35.3, - 35.3, - 35.3, - 35.2, - 35.1, - 35, - 34.8, - 34.7, - 34.6, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.5, - 33.4, - 33, - 32.6, - 32.3, - 32, - 31.8, - 31.6, - 31.4, - 31.2, - 30.9, - 30.6, - 30.2, - 29.9, - 29.7, - 29.5, - 29.4, - 29.2, - 29.1, - 28.9, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.9, - 27.8, - 27.8, - 27.8, - 27.7, - 27.7, - 27.7, - 27.7, - 27.4, - 27.4, - 27.3, - 27.3, - 27.2, - 27, - 27, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26.1, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.6, - 25.4, - 25.1, - 24.9, - 24.8, - 24.7, - 24.5, - 24.4, - 24.3, - 24, - 23.6, - 23.2, - 22.7, - 22.2, - 21.7, - 21.3, - 21, - 20.7, - 20.3, - 19.9, - 19.4, - 19, - 18.8, - 18.7, - 18.5, - 18.3, - 18.2, - 18.1, - 18, - 17.9, - 17.9, - 17.8, - 17.8, - 17.8, - 17.7, - 17.7, - 17.7, - 17.6, - 17.5, - 17.5, - 17.5, - 17.5, - 17.4, - 17.3, - 17, - 16.9, - 16.8, - 16.6, - 16.5, - 16.5, - 16.6, - 16.6, - 16.7, - 16.7, - 16.8, - 16.7, - 16.5, - 16.5, - 16.4, - 16.4, - 16.3, - 16, - 15.9, - 15.7, - 15.6, - 15.6, - 15.4, - 15.3, - 15.1, - 15.1, - 14.9, - 14.8, - 14.8, - 14.7, - 14.5, - 14.5, - 14.4, - 14.2, - 14.2, - 14.1, - 14, - 13.4, - 13.1, - 13, - 13.1, - 13.4, - 13.5, - 13.7, - 14, - 14.3, - 14.5, - 14.6, - 14.7, - 14.8, - 36, - 36.7, - 37.3, - 37.8, - 38.2, - 38.5, - 38.8, - 39.2, - 39.4, - 39.8, - 40.1, - 40.2, - 40.5, - 40.6, - 40.6, - 40.7, - 40.9, - 41, - 41.2, - 41.3, - 41.6, - 41.4, - 41.4, - 41.4, - 41.4, - 41.4, - 41.3, - 41.4, - 41.4, - 41.4, - 41.4, - 41.4, - 41.3, - 41.3, - 41.2, - 41, - 40.9, - 40.9, - 40.9, - 40.8, - 40.7, - 40.6, - 40.4, - 40.2, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.3, - 39.2, - 39.1, - 38.8, - 38.5, - 38.3, - 38.1, - 37.8, - 37.3, - 36.9, - 36.8, - 36.8, - 36.7, - 36.8, - 36.9, - 36.9, - 36.7, - 36.3, - 36.1, - 35.9, - 35.7, - 35.6, - 35.5, - 35.5, - 35.4, - 35.4, - 35.3, - 35.3, - 35.3, - 35.3, - 35.2, - 35.3, - 35.3, - 35.4, - 35.4, - 35.3, - 35.2, - 35.2, - 35, - 34.8, - 34.7, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.7, - 33.5, - 33.2, - 32.8, - 32.5, - 32.1, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.7, - 30.3, - 30, - 29.8, - 29.6, - 29.4, - 29.2, - 29.1, - 28.9, - 28.8, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.7, - 27.5, - 27.7, - 27.6, - 27.5, - 27.6, - 27.7, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 27, - 27.1, - 26.9, - 26.7, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.1, - 26.1, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.7, - 25.6, - 25.3, - 25, - 24.8, - 24.7, - 24.5, - 24.4, - 24.3, - 24.2, - 23.9, - 23.4, - 22.9, - 22.3, - 21.8, - 21.4, - 21.1, - 20.8, - 20.3, - 19.8, - 19.5, - 19, - 18.8, - 18.7, - 18.6, - 18.5, - 18.2, - 18.1, - 18, - 18, - 17.9, - 17.8, - 17.8, - 17.8, - 17.8, - 17.8, - 17.7, - 17.7, - 17.6, - 17.5, - 17.5, - 17.5, - 17.5, - 17.3, - 17.1, - 17, - 16.7, - 16.5, - 16.5, - 16.6, - 16.6, - 16.7, - 16.7, - 16.7, - 16.7, - 16.6, - 16.5, - 16.4, - 16.3, - 16.3, - 16.1, - 15.9, - 15.8, - 15.7, - 15.6, - 15.6, - 15.4, - 15.2, - 15.1, - 15.1, - 15, - 14.8, - 14.8, - 14.7, - 14.5, - 14.4, - 14.4, - 14.2, - 14, - 13.8, - 13.5, - 13.1, - 13, - 12.9, - 12.9, - 13.2, - 13.4, - 13.5, - 13.5, - 13.8, - 14.2, - 14.5, - 14.6, - 14.6, - 36, - 36.7, - 37.2, - 37.6, - 38, - 38.3, - 38.6, - 39, - 39.2, - 39.5, - 40, - 40.4, - 40.8, - 40.5, - 40.5, - 40.6, - 40.8, - 40.9, - 41, - 41.1, - 41.4, - 41.4, - 41.3, - 41.3, - 41.2, - 41.3, - 41.3, - 41.3, - 41.3, - 41.3, - 41.3, - 41.2, - 41.2, - 41.2, - 41.1, - 41, - 40.9, - 40.7, - 40.7, - 40.6, - 40.6, - 40.5, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.9, - 39.8, - 39.7, - 39.5, - 39.4, - 39.2, - 39, - 38.8, - 38.6, - 38.3, - 38.1, - 37.8, - 37.3, - 37, - 36.9, - 36.9, - 36.8, - 36.9, - 37.1, - 37.1, - 36.9, - 36.4, - 36.1, - 36, - 35.8, - 35.7, - 35.6, - 35.6, - 35.5, - 35.5, - 35.5, - 35.5, - 35.4, - 35.4, - 35.3, - 35.3, - 35.3, - 35.3, - 35.4, - 35.4, - 35.3, - 35.3, - 35.2, - 35.1, - 34.8, - 34.6, - 34.4, - 34.2, - 34, - 33.8, - 33.6, - 33.4, - 33.2, - 32.9, - 32.6, - 32.3, - 32, - 31.6, - 31.4, - 31.2, - 31.1, - 30.8, - 30.5, - 30.2, - 29.9, - 29.7, - 29.4, - 29.2, - 29.1, - 28.9, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.3, - 28.3, - 28.2, - 28.2, - 28.2, - 28.1, - 28, - 28, - 28, - 28, - 28, - 27.8, - 27.4, - 27.6, - 27.3, - 27.4, - 27.6, - 27.5, - 27.7, - 27.5, - 27.3, - 27.4, - 27.2, - 27.1, - 27.1, - 27.1, - 26.9, - 26.8, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26, - 26, - 25.9, - 25.9, - 25.9, - 25.8, - 25.7, - 25.6, - 25.4, - 25.1, - 24.9, - 24.7, - 24.5, - 24.4, - 24.4, - 24.3, - 24.1, - 23.7, - 23.1, - 22.6, - 22, - 21.5, - 21.2, - 20.9, - 20.4, - 19.8, - 19.5, - 19.1, - 18.9, - 18.8, - 18.7, - 18.5, - 18.3, - 18.3, - 18.3, - 18.1, - 18, - 17.9, - 17.8, - 17.8, - 17.8, - 17.7, - 17.7, - 17.7, - 17.6, - 17.5, - 17.5, - 17.5, - 17.4, - 17.3, - 17.2, - 17, - 16.7, - 16.6, - 16.5, - 16.5, - 16.6, - 16.7, - 16.7, - 16.7, - 16.7, - 16.6, - 16.4, - 16.4, - 16.2, - 16.1, - 15.9, - 15.8, - 15.8, - 15.8, - 15.6, - 15.5, - 15.4, - 15.2, - 15, - 15, - 14.9, - 14.9, - 14.8, - 14.6, - 14.5, - 14.3, - 14.3, - 14, - 13.8, - 13.4, - 13.2, - 13, - 12.9, - 12.8, - 12.8, - 12.9, - 13.1, - 13.2, - 13.3, - 13.4, - 13.4, - 13.9, - 14.1, - 14.2, - 36.1, - 36.6, - 37.1, - 37.5, - 37.9, - 38.2, - 38.4, - 38.7, - 38.9, - 39.3, - 39.9, - 40.2, - 40.5, - 40.2, - 40.3, - 40.5, - 40.6, - 40.7, - 40.9, - 40.9, - 41.1, - 41.2, - 41.2, - 41.1, - 41.1, - 41.1, - 41.1, - 41.2, - 41.2, - 41.2, - 41.1, - 41.1, - 41, - 41, - 40.9, - 40.9, - 40.8, - 40.6, - 40.5, - 40.4, - 40.4, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.8, - 39.8, - 39.6, - 39.4, - 39.3, - 39.2, - 38.9, - 38.7, - 38.4, - 38.2, - 37.9, - 37.4, - 37.1, - 37, - 36.9, - 36.9, - 36.9, - 37.1, - 37.2, - 37, - 36.6, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.7, - 35.7, - 35.7, - 35.6, - 35.6, - 35.5, - 35.5, - 35.5, - 35.4, - 35.4, - 35.3, - 35.4, - 35.4, - 35.3, - 35.4, - 35.3, - 35.1, - 34.9, - 34.7, - 34.5, - 34.2, - 34, - 33.9, - 33.7, - 33.4, - 33.2, - 33.1, - 32.8, - 32.4, - 32.1, - 31.8, - 31.4, - 31.1, - 30.9, - 30.8, - 30.5, - 30.3, - 30, - 29.8, - 29.5, - 29.3, - 29.1, - 28.9, - 28.8, - 28.6, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.2, - 28.2, - 28.1, - 28, - 28.1, - 28, - 28, - 28, - 28.1, - 28.1, - 27.9, - 27.5, - 27.6, - 27.2, - 27.3, - 27.5, - 27.3, - 27.5, - 27.4, - 27.3, - 27.4, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.2, - 26, - 25.9, - 25.9, - 25.9, - 25.8, - 25.8, - 25.7, - 25.7, - 25.6, - 25.1, - 24.9, - 24.8, - 24.6, - 24.5, - 24.4, - 24.3, - 24.1, - 23.7, - 23.2, - 22.7, - 22.2, - 21.7, - 21.3, - 20.9, - 20.4, - 19.9, - 19.6, - 19.4, - 19.2, - 18.9, - 18.7, - 18.5, - 18.5, - 18.7, - 18.6, - 18.4, - 18, - 17.9, - 17.8, - 17.8, - 17.8, - 17.7, - 17.7, - 17.7, - 17.6, - 17.6, - 17.6, - 17.5, - 17.3, - 17.2, - 17.2, - 17.1, - 16.8, - 16.6, - 16.6, - 16.6, - 16.6, - 16.8, - 16.8, - 16.8, - 16.7, - 16.6, - 16.4, - 16.2, - 16.1, - 16, - 15.9, - 15.8, - 15.7, - 15.6, - 15.6, - 15.5, - 15.4, - 15.2, - 15.1, - 15, - 14.9, - 14.8, - 14.8, - 14.6, - 14.5, - 14.4, - 14.2, - 13.9, - 13.8, - 13.5, - 13.2, - 13, - 12.9, - 12.7, - 12.7, - 12.6, - 12.6, - 12.8, - 12.9, - 12.7, - 12.6, - 13.2, - 13.7, - 13.8, - 36.1, - 36.5, - 37, - 37.3, - 37.7, - 38, - 38.3, - 38.5, - 38.8, - 39.1, - 39.5, - 39.7, - 39.9, - 40, - 40.2, - 40.4, - 40.5, - 40.6, - 40.8, - 40.8, - 40.8, - 41, - 41.1, - 41, - 41, - 41, - 41.1, - 41.1, - 41.1, - 41, - 40.9, - 40.9, - 40.8, - 40.8, - 40.6, - 40.6, - 40.5, - 40.4, - 40.3, - 40.2, - 40.2, - 40.1, - 40.1, - 40, - 39.9, - 39.9, - 39.8, - 39.6, - 39.5, - 39.5, - 39.2, - 39.1, - 38.9, - 38.7, - 38.5, - 38.3, - 37.9, - 37.5, - 37.1, - 37, - 37, - 37, - 37, - 37.1, - 37.2, - 37.1, - 36.7, - 36.3, - 36.1, - 36, - 36, - 35.9, - 35.8, - 35.8, - 35.8, - 35.7, - 35.7, - 35.6, - 35.6, - 35.5, - 35.5, - 35.4, - 35.4, - 35.4, - 35.4, - 35.3, - 35.2, - 35.2, - 35.2, - 35, - 34.8, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.5, - 33.3, - 33, - 32.8, - 32.5, - 32.1, - 31.8, - 31.5, - 31.2, - 30.9, - 30.7, - 30.5, - 30.2, - 30, - 29.8, - 29.6, - 29.4, - 29.1, - 28.9, - 28.8, - 28.6, - 28.5, - 28.5, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28.1, - 28.1, - 28, - 28, - 28, - 28, - 28, - 28, - 28, - 27.7, - 27.7, - 27.3, - 27.4, - 27.4, - 27.4, - 27.5, - 27.4, - 27.3, - 27.3, - 27.2, - 27.3, - 27.2, - 27, - 26.9, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.2, - 25.9, - 25.8, - 25.8, - 25.8, - 25.8, - 25.7, - 25.7, - 25.5, - 25.3, - 25, - 24.8, - 24.7, - 24.6, - 24.4, - 24.2, - 24, - 23.6, - 23.3, - 22.8, - 22.3, - 21.8, - 21.5, - 21.1, - 20.6, - 20.1, - 19.8, - 19.8, - 19.7, - 19.3, - 18.8, - 18.7, - 18.9, - 19, - 18.9, - 18.5, - 18.1, - 17.9, - 17.8, - 17.8, - 17.8, - 17.8, - 17.7, - 17.7, - 17.7, - 17.6, - 17.6, - 17.5, - 17.3, - 17.2, - 17.1, - 17.1, - 16.8, - 16.6, - 16.5, - 16.6, - 16.7, - 16.8, - 17, - 16.9, - 16.7, - 16.6, - 16.5, - 16.2, - 16, - 15.9, - 15.9, - 15.8, - 15.6, - 15.5, - 15.5, - 15.5, - 15.3, - 15.2, - 15.2, - 15, - 14.8, - 14.8, - 14.8, - 14.6, - 14.5, - 14.4, - 14.3, - 14.1, - 13.9, - 13.5, - 13.3, - 13, - 12.9, - 12.7, - 12.6, - 12.5, - 12.5, - 12.6, - 12.5, - 12.2, - 12.2, - 12.6, - 13.2, - 13.4, - 36, - 36.3, - 36.7, - 37.1, - 37.4, - 37.8, - 38.1, - 38.4, - 38.7, - 39, - 39.3, - 39.5, - 39.7, - 39.9, - 40.1, - 40.3, - 40.4, - 40.5, - 40.6, - 40.7, - 40.8, - 40.9, - 40.9, - 40.8, - 40.8, - 40.8, - 40.9, - 41, - 40.8, - 40.7, - 40.7, - 40.6, - 40.6, - 40.6, - 40.4, - 40.4, - 40.3, - 40.2, - 40.2, - 40.1, - 40, - 39.9, - 39.9, - 39.9, - 39.8, - 39.8, - 39.7, - 39.5, - 39.4, - 39.3, - 39.1, - 38.9, - 38.7, - 38.6, - 38.3, - 38, - 37.5, - 37.2, - 37.1, - 37.1, - 37.1, - 37.1, - 37.1, - 37.2, - 37.2, - 36.9, - 36.5, - 36.3, - 36.1, - 36.1, - 36, - 35.9, - 35.9, - 35.9, - 35.9, - 35.8, - 35.7, - 35.7, - 35.7, - 35.6, - 35.5, - 35.5, - 35.4, - 35.4, - 35.4, - 35.2, - 35.1, - 35, - 35, - 34.9, - 34.6, - 34.4, - 34.2, - 34, - 33.8, - 33.5, - 33.3, - 33.1, - 32.8, - 32.5, - 32.1, - 31.8, - 31.5, - 31.3, - 31, - 30.7, - 30.5, - 30.3, - 30, - 29.8, - 29.5, - 29.3, - 29.1, - 28.9, - 28.7, - 28.6, - 28.6, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28.2, - 28.1, - 28, - 27.9, - 28, - 28, - 27.9, - 27.9, - 27.9, - 28, - 27.9, - 27.9, - 27.7, - 27.6, - 27.4, - 27.6, - 27.6, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.6, - 26.4, - 26.3, - 26.2, - 26.3, - 26.3, - 26, - 25.8, - 25.8, - 25.8, - 25.8, - 25.8, - 25.7, - 25.5, - 25.2, - 25, - 24.8, - 24.8, - 24.8, - 24.4, - 24.3, - 24, - 23.6, - 23.4, - 22.9, - 22.5, - 22, - 21.7, - 21.1, - 20.9, - 20.4, - 20, - 20, - 19.9, - 19.7, - 19.2, - 19, - 19.2, - 19.2, - 18.9, - 18.4, - 18.2, - 18, - 17.9, - 17.8, - 17.8, - 17.8, - 17.8, - 17.8, - 17.7, - 17.6, - 17.6, - 17.6, - 17.3, - 17.2, - 17.2, - 17.1, - 16.9, - 16.5, - 16.5, - 16.5, - 16.7, - 16.9, - 17.1, - 17, - 16.8, - 16.7, - 16.5, - 16.3, - 16, - 15.9, - 15.8, - 15.7, - 15.6, - 15.4, - 15.4, - 15.3, - 15.3, - 15.2, - 15.1, - 14.9, - 14.9, - 14.9, - 14.8, - 14.6, - 14.5, - 14.4, - 14.2, - 14, - 13.7, - 13.5, - 13.3, - 13.2, - 13.1, - 12.9, - 12.7, - 12.6, - 12.6, - 12.4, - 12.1, - 11.9, - 11.8, - 12.1, - 12.5, - 12.8, - 35.7, - 36.1, - 36.5, - 36.9, - 37.2, - 37.6, - 37.9, - 38.3, - 38.6, - 38.9, - 39.2, - 39.4, - 39.7, - 39.9, - 40.1, - 40.2, - 40.3, - 40.4, - 40.5, - 40.6, - 40.7, - 40.7, - 40.7, - 40.6, - 40.6, - 40.6, - 40.6, - 40.7, - 40.5, - 40.4, - 40.5, - 40.5, - 40.5, - 40.3, - 40.2, - 40.1, - 40.1, - 40, - 40, - 40, - 39.9, - 39.8, - 39.7, - 39.7, - 39.5, - 39.5, - 39.5, - 39.4, - 39.3, - 39.2, - 39.1, - 38.8, - 38.6, - 38.4, - 38, - 37.6, - 37.3, - 37.2, - 37.2, - 37.2, - 37.2, - 37.1, - 37.2, - 37.2, - 37, - 36.6, - 36.4, - 36.2, - 36.2, - 36.1, - 36, - 36, - 36, - 36, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.6, - 35.5, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35, - 34.8, - 34.8, - 34.8, - 34.5, - 34.4, - 34.2, - 34.1, - 33.7, - 33.3, - 33, - 32.8, - 32.6, - 32.2, - 31.8, - 31.5, - 31.2, - 31, - 30.8, - 30.5, - 30.3, - 30, - 29.7, - 29.5, - 29.3, - 29.1, - 28.9, - 28.7, - 28.6, - 28.5, - 28.5, - 28.3, - 28.2, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 27.7, - 27.7, - 27.9, - 28, - 28, - 28, - 28, - 28, - 27.8, - 27.9, - 28, - 27.8, - 27.6, - 27.6, - 27.6, - 27.5, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.8, - 26.6, - 26.6, - 26.5, - 26.4, - 26.2, - 26.2, - 26.3, - 26.1, - 25.9, - 25.8, - 25.8, - 25.8, - 25.8, - 25.6, - 25.4, - 25.3, - 25.1, - 24.9, - 24.9, - 24.7, - 24.3, - 24.2, - 23.9, - 23.7, - 23.4, - 23.1, - 22.5, - 22, - 21.6, - 21.2, - 21, - 20.6, - 20.3, - 20.2, - 20.1, - 19.9, - 19.7, - 19.5, - 19.5, - 19.5, - 19.1, - 18.6, - 18.5, - 18.3, - 17.9, - 17.9, - 18, - 18, - 17.9, - 17.9, - 17.8, - 17.7, - 17.7, - 17.6, - 17.5, - 17.4, - 17.3, - 17.2, - 17, - 16.6, - 16.5, - 16.5, - 16.7, - 16.9, - 17.1, - 17, - 16.9, - 16.8, - 16.6, - 16.4, - 16.1, - 15.9, - 15.8, - 15.7, - 15.6, - 15.5, - 15.4, - 15.3, - 15.2, - 15.3, - 15.2, - 14.9, - 14.9, - 14.8, - 14.6, - 14.7, - 14.5, - 14.3, - 14.1, - 14, - 13.8, - 13.6, - 13.4, - 13.3, - 13.1, - 13, - 12.8, - 12.6, - 12.5, - 12.2, - 11.4, - 11.2, - 11.4, - 11.3, - 11.6, - 11.9, - 35.4, - 35.8, - 36.2, - 36.6, - 37, - 37.4, - 37.7, - 38.1, - 38.4, - 38.8, - 39.1, - 39.3, - 39.6, - 39.8, - 40, - 40.1, - 40.2, - 40.3, - 40.4, - 40.4, - 40.4, - 40.5, - 40.5, - 40.3, - 40.4, - 40.3, - 40.3, - 40.2, - 40.3, - 40.2, - 40.2, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.9, - 39.9, - 39.8, - 39.8, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.3, - 39.2, - 39.2, - 39.2, - 39, - 38.8, - 38.6, - 38.5, - 38.2, - 37.7, - 37.4, - 37.3, - 37.3, - 37.3, - 37.2, - 37.2, - 37.2, - 37.2, - 37, - 36.7, - 36.5, - 36.3, - 36.3, - 36.2, - 36.1, - 36.1, - 36, - 36, - 36, - 35.9, - 35.8, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35, - 34.8, - 34.7, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 33.8, - 33.3, - 33, - 32.8, - 32.5, - 32.3, - 31.9, - 31.6, - 31.3, - 31.1, - 30.8, - 30.6, - 30.3, - 30.1, - 29.8, - 29.5, - 29.2, - 29, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.2, - 28.2, - 28.2, - 28.2, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.8, - 27.6, - 27.5, - 27.6, - 27.9, - 28.1, - 28.1, - 28.1, - 27.8, - 27.6, - 27.7, - 27.9, - 27.9, - 27.6, - 27.6, - 27.6, - 27.5, - 27.3, - 27.2, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.9, - 26.9, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.4, - 26.3, - 26.2, - 25.9, - 25.8, - 25.8, - 25.8, - 25.7, - 25.6, - 25.4, - 25.4, - 25.2, - 25, - 25, - 24.8, - 24.4, - 24.1, - 23.9, - 23.7, - 23.4, - 23.2, - 22.6, - 22.1, - 21.6, - 21.3, - 21, - 20.7, - 20.5, - 20.3, - 20.2, - 20, - 19.9, - 19.8, - 19.7, - 19.6, - 19.5, - 19.2, - 19.1, - 18.7, - 18.1, - 18, - 18.2, - 18.1, - 18.1, - 18.1, - 18, - 17.8, - 17.8, - 17.8, - 17.7, - 17.5, - 17.5, - 17.3, - 17.1, - 16.9, - 16.7, - 16.7, - 16.8, - 16.9, - 17, - 16.9, - 16.8, - 16.7, - 16.8, - 16.6, - 16.2, - 16, - 15.8, - 15.6, - 15.6, - 15.5, - 15.4, - 15.3, - 15.2, - 15.2, - 15.1, - 14.9, - 14.8, - 14.7, - 14.6, - 14.6, - 14.5, - 14.4, - 14.3, - 14.2, - 14, - 13.8, - 13.6, - 13.4, - 13.1, - 12.9, - 12.8, - 12.7, - 12.5, - 12.2, - 11.6, - 10.9, - 10.6, - 10.6, - 10.7, - 10.8, - 35.2, - 35.6, - 36, - 36.4, - 36.8, - 37.2, - 37.6, - 37.9, - 38.3, - 38.6, - 39, - 39.2, - 39.5, - 39.7, - 39.9, - 40, - 40.1, - 40.1, - 40.1, - 40.1, - 40.2, - 40.2, - 40.1, - 40.1, - 40.1, - 40.1, - 40, - 40, - 40.1, - 40, - 40, - 40, - 39.9, - 39.9, - 39.9, - 39.8, - 39.8, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.5, - 39.3, - 39.2, - 39.2, - 39, - 39, - 38.9, - 38.7, - 38.6, - 38.4, - 38.2, - 37.8, - 37.5, - 37.4, - 37.4, - 37.3, - 37.3, - 37.3, - 37.3, - 37.3, - 37.1, - 36.9, - 36.6, - 36.4, - 36.3, - 36.3, - 36.3, - 36.2, - 36.1, - 36.1, - 36, - 36, - 35.9, - 35.8, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.1, - 35, - 34.9, - 34.7, - 34.6, - 34.4, - 34.2, - 34.1, - 34, - 33.7, - 33.4, - 33.1, - 32.8, - 32.6, - 32.3, - 32, - 31.7, - 31.4, - 31.1, - 30.9, - 30.6, - 30.4, - 30.2, - 29.9, - 29.6, - 29.3, - 29, - 28.8, - 28.7, - 28.6, - 28.4, - 28.2, - 28.2, - 28.1, - 28, - 28.2, - 28.3, - 28.3, - 28.4, - 28.2, - 28, - 27.8, - 27.6, - 27.6, - 27.5, - 27.5, - 27.7, - 28.1, - 28.1, - 28, - 27.9, - 27.7, - 27.7, - 27.8, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.4, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.7, - 26.5, - 26.5, - 26.3, - 26.4, - 26.4, - 26.3, - 26, - 25.8, - 25.8, - 25.8, - 25.8, - 25.6, - 25.4, - 25.3, - 25.4, - 25.1, - 25, - 24.9, - 24.5, - 24.2, - 24, - 23.9, - 23.5, - 23.2, - 22.7, - 22.2, - 21.8, - 21.4, - 21, - 20.8, - 20.6, - 20.5, - 20.3, - 20.1, - 20, - 19.9, - 19.8, - 19.7, - 19.6, - 19.5, - 19.4, - 19.1, - 18.7, - 18.5, - 18.5, - 18.4, - 18.2, - 18.1, - 18, - 18, - 17.9, - 17.9, - 17.8, - 17.7, - 17.6, - 17.5, - 17.3, - 17.1, - 17, - 16.9, - 16.9, - 16.9, - 16.8, - 16.6, - 16.5, - 16.5, - 16.6, - 16.6, - 16.4, - 16.1, - 15.9, - 15.7, - 15.6, - 15.5, - 15.4, - 15.3, - 15.2, - 15.1, - 15, - 14.9, - 14.9, - 14.8, - 14.6, - 14.6, - 14.5, - 14.5, - 14.3, - 14.2, - 14, - 13.8, - 13.6, - 13.5, - 13.3, - 12.9, - 12.7, - 12.7, - 12.5, - 12.2, - 11.8, - 11.4, - 10.9, - 10.5, - 10.3, - 10.1, - 35, - 35.4, - 35.9, - 36.3, - 36.7, - 37.1, - 37.4, - 37.8, - 38.1, - 38.5, - 38.8, - 39.1, - 39.3, - 39.6, - 39.7, - 39.8, - 39.9, - 39.9, - 39.9, - 39.9, - 39.9, - 40, - 40, - 39.9, - 39.9, - 39.9, - 39.8, - 39.7, - 39.8, - 39.7, - 39.8, - 39.7, - 39.6, - 39.6, - 39.6, - 39.8, - 39.7, - 39.6, - 39.6, - 39.5, - 39.6, - 39.5, - 39.4, - 39.2, - 39, - 39, - 38.9, - 38.8, - 38.7, - 38.6, - 38.4, - 38.2, - 37.8, - 37.6, - 37.4, - 37.4, - 37.4, - 37.4, - 37.4, - 37.4, - 37.4, - 37.3, - 36.9, - 36.8, - 36.6, - 36.5, - 36.4, - 36.4, - 36.4, - 36.4, - 36.2, - 36.1, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35, - 34.9, - 34.7, - 34.6, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.5, - 33.1, - 32.8, - 32.6, - 32.4, - 32.1, - 31.9, - 31.6, - 31.3, - 31, - 30.7, - 30.5, - 30.2, - 30, - 29.7, - 29.4, - 29.1, - 28.9, - 28.7, - 28.6, - 28.6, - 28.3, - 28.1, - 28.1, - 28.3, - 28.2, - 28.2, - 28.4, - 28.4, - 28.3, - 28.2, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.6, - 27.8, - 28.1, - 28, - 27.9, - 28, - 27.9, - 27.8, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.1, - 25.8, - 25.8, - 25.8, - 25.7, - 25.6, - 25.4, - 25.3, - 25.1, - 24.9, - 24.9, - 24.8, - 24.6, - 24.3, - 24.1, - 23.9, - 23.6, - 23.2, - 22.8, - 22.4, - 22, - 21.6, - 21.2, - 21, - 20.9, - 20.7, - 20.5, - 20.3, - 20.1, - 20, - 19.9, - 19.8, - 19.7, - 19.6, - 19.5, - 19.4, - 19.2, - 19.1, - 18.9, - 18.6, - 18.3, - 18.2, - 18.1, - 18.1, - 18, - 17.9, - 17.9, - 17.9, - 17.8, - 17.6, - 17.4, - 17.2, - 17.1, - 17, - 16.9, - 16.8, - 16.6, - 16.4, - 16.4, - 16.4, - 16.4, - 16.6, - 16.6, - 16.2, - 15.9, - 15.8, - 15.6, - 15.5, - 15.4, - 15.3, - 15.2, - 15, - 15, - 14.9, - 14.9, - 14.8, - 14.8, - 14.6, - 14.4, - 14.4, - 14.2, - 14.1, - 14, - 13.8, - 13.7, - 13.5, - 13.3, - 13.1, - 12.8, - 12.7, - 12.6, - 12.4, - 11.9, - 11.6, - 11.2, - 10.7, - 10.1, - 9.9, - 34.9, - 35.3, - 35.7, - 36.2, - 36.6, - 36.9, - 37.3, - 37.7, - 38, - 38.3, - 38.6, - 38.9, - 39.1, - 39.3, - 39.5, - 39.6, - 39.7, - 39.7, - 39.7, - 39.7, - 39.8, - 39.8, - 39.8, - 39.7, - 39.7, - 39.6, - 39.6, - 39.6, - 39.6, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.4, - 39.5, - 39.5, - 39.6, - 39.4, - 39.3, - 39.2, - 39.2, - 39.2, - 39, - 38.9, - 38.9, - 38.7, - 38.6, - 38.6, - 38.4, - 38.2, - 37.8, - 37.6, - 37.5, - 37.4, - 37.4, - 37.4, - 37.5, - 37.5, - 37.5, - 37.5, - 37.2, - 36.9, - 36.8, - 36.7, - 36.7, - 36.6, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 35.9, - 35.8, - 35.8, - 35.7, - 35.6, - 35.5, - 35.3, - 35.2, - 35.1, - 35, - 34.8, - 34.7, - 34.6, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.5, - 33.3, - 32.9, - 32.6, - 32.4, - 32.2, - 31.9, - 31.7, - 31.4, - 31.2, - 30.8, - 30.5, - 30.3, - 30, - 29.8, - 29.5, - 29.2, - 29, - 28.8, - 28.6, - 28.6, - 28.6, - 28.4, - 28.2, - 28.1, - 28.2, - 28.3, - 28.3, - 28.3, - 28.4, - 28.4, - 28.1, - 27.9, - 27.8, - 27.7, - 27.8, - 27.8, - 27.9, - 28, - 28.1, - 27.9, - 27.9, - 27.9, - 27.9, - 27.8, - 27.9, - 27.8, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.2, - 27.1, - 27.1, - 27, - 26.9, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.2, - 26.1, - 26.2, - 26.3, - 26.1, - 25.8, - 25.8, - 25.8, - 25.6, - 25.6, - 25.6, - 25.4, - 25.1, - 25.1, - 24.9, - 24.7, - 24.6, - 24.3, - 24.1, - 23.8, - 23.6, - 23.3, - 22.9, - 22.5, - 22.2, - 21.8, - 21.5, - 21.2, - 21.1, - 20.9, - 20.7, - 20.7, - 20.4, - 20.1, - 20, - 19.9, - 19.8, - 19.7, - 19.5, - 19.5, - 19.4, - 19.2, - 19.1, - 18.8, - 18.5, - 18.3, - 18.2, - 18.1, - 18.1, - 18, - 18, - 17.9, - 17.9, - 17.8, - 17.5, - 17.2, - 17.1, - 16.9, - 16.8, - 16.7, - 16.5, - 16.4, - 16.4, - 16.4, - 16.4, - 16.6, - 16.6, - 16.2, - 16, - 15.9, - 15.6, - 15.4, - 15.4, - 15.3, - 15.2, - 15.1, - 15, - 15, - 14.9, - 14.9, - 14.8, - 14.6, - 14.5, - 14.4, - 14.2, - 14.1, - 14, - 13.9, - 13.8, - 13.6, - 13.3, - 13.2, - 12.8, - 12.7, - 12.7, - 12.6, - 12.2, - 11.7, - 11.3, - 10.9, - 9.9, - 9.3, - 34.7, - 35.2, - 35.6, - 36, - 36.4, - 36.8, - 37.2, - 37.5, - 37.9, - 38.2, - 38.5, - 38.7, - 38.9, - 39.1, - 39.3, - 39.4, - 39.4, - 39.5, - 39.5, - 39.5, - 39.5, - 39.6, - 39.5, - 39.5, - 39.4, - 39.3, - 39.3, - 39.3, - 39.3, - 39.2, - 39.2, - 39.3, - 39.4, - 39.4, - 39.3, - 39.3, - 39.3, - 39.3, - 39.2, - 39.1, - 39, - 39, - 38.9, - 38.8, - 38.7, - 38.6, - 38.5, - 38.5, - 38.4, - 38.1, - 37.8, - 37.6, - 37.5, - 37.5, - 37.5, - 37.5, - 37.6, - 37.6, - 37.6, - 37.7, - 37.6, - 37.2, - 37, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 35.9, - 35.8, - 35.8, - 35.7, - 35.5, - 35.4, - 35.2, - 35.1, - 35, - 34.8, - 34.7, - 34.6, - 34.4, - 34.3, - 34.2, - 34, - 33.8, - 33.6, - 33.4, - 33.1, - 32.7, - 32.5, - 32.2, - 32, - 31.8, - 31.5, - 31.3, - 31, - 30.6, - 30.3, - 30.1, - 29.8, - 29.5, - 29.3, - 29.1, - 28.9, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28.2, - 28.2, - 28.3, - 28.4, - 28.2, - 28, - 27.9, - 27.8, - 27.8, - 27.9, - 28.1, - 28.2, - 28.1, - 28.1, - 28, - 28, - 28, - 27.9, - 27.8, - 27.8, - 27.6, - 27.6, - 27.7, - 27.7, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.7, - 26.5, - 26.3, - 26.1, - 26.2, - 26.3, - 26.1, - 25.9, - 25.8, - 25.7, - 25.7, - 25.7, - 25.5, - 25.2, - 25, - 24.9, - 24.9, - 24.7, - 24.5, - 24.4, - 24.1, - 23.9, - 23.6, - 23.4, - 23, - 22.7, - 22.4, - 22, - 21.7, - 21.4, - 21.1, - 20.9, - 20.8, - 20.8, - 20.6, - 20.3, - 20.1, - 20, - 19.9, - 19.7, - 19.5, - 19.5, - 19.4, - 19.3, - 19.1, - 18.9, - 18.7, - 18.5, - 18.3, - 18.2, - 18.1, - 18.1, - 18, - 17.9, - 17.9, - 17.9, - 17.8, - 17.4, - 17.1, - 16.9, - 16.8, - 16.7, - 16.6, - 16.4, - 16.4, - 16.4, - 16.4, - 16.6, - 16.7, - 16.3, - 16.1, - 16, - 15.7, - 15.5, - 15.3, - 15.2, - 15.2, - 15.1, - 15, - 15, - 15, - 14.9, - 14.9, - 14.6, - 14.5, - 14.4, - 14.3, - 14.1, - 14, - 13.9, - 13.8, - 13.6, - 13.5, - 13.2, - 12.9, - 12.7, - 12.6, - 12.6, - 12.4, - 11.8, - 11.3, - 10.9, - 10.1, - 9.3, - 34.6, - 35.1, - 35.5, - 35.9, - 36.3, - 36.7, - 37.1, - 37.4, - 37.8, - 38.1, - 38.4, - 38.6, - 38.8, - 38.9, - 39, - 39.1, - 39.4, - 39.4, - 39.3, - 39.2, - 39.2, - 39.3, - 39.3, - 39.2, - 39.1, - 39, - 39, - 39.1, - 39.1, - 39, - 39.1, - 39.3, - 39.3, - 39.3, - 39.1, - 39, - 39.2, - 39.1, - 39, - 38.9, - 38.9, - 38.9, - 38.8, - 38.7, - 38.6, - 38.5, - 38.4, - 38.3, - 38, - 37.8, - 37.6, - 37.5, - 37.5, - 37.6, - 37.6, - 37.7, - 37.8, - 37.8, - 37.8, - 37.7, - 37.5, - 37.3, - 37.2, - 37.1, - 37.1, - 36.9, - 36.8, - 36.7, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.3, - 35.1, - 35, - 34.8, - 34.7, - 34.6, - 34.4, - 34.3, - 34.2, - 34.1, - 33.9, - 33.7, - 33.5, - 33.3, - 33, - 32.7, - 32.4, - 32.2, - 31.9, - 31.7, - 31.4, - 31.2, - 30.9, - 30.5, - 30.2, - 29.9, - 29.7, - 29.4, - 29.1, - 28.9, - 28.8, - 28.7, - 28.6, - 28.4, - 28.4, - 28.5, - 28.5, - 28.3, - 28.2, - 28, - 27.9, - 28, - 28.1, - 28.3, - 28.3, - 27.9, - 27.8, - 27.8, - 27.9, - 28, - 28.2, - 28.2, - 28.1, - 28.2, - 28.1, - 28, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.4, - 27.5, - 27.4, - 27.2, - 27.1, - 27.1, - 27.1, - 26.9, - 26.8, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.1, - 25.9, - 25.8, - 25.7, - 25.7, - 25.6, - 25.5, - 25.1, - 24.9, - 24.9, - 24.9, - 24.7, - 24.6, - 24.5, - 24.2, - 23.9, - 23.7, - 23.5, - 23.1, - 22.9, - 22.6, - 22.2, - 21.9, - 21.5, - 21.2, - 21, - 20.9, - 20.8, - 20.7, - 20.5, - 20.4, - 20.3, - 20.1, - 19.8, - 19.6, - 19.5, - 19.5, - 19.3, - 19.1, - 18.9, - 18.8, - 18.6, - 18.5, - 18.3, - 18.2, - 18.1, - 18, - 18, - 17.9, - 17.9, - 17.8, - 17.5, - 17.2, - 17, - 16.9, - 16.8, - 16.7, - 16.5, - 16.4, - 16.4, - 16.4, - 16.6, - 16.9, - 16.5, - 16.3, - 16, - 15.8, - 15.5, - 15.4, - 15.2, - 15.2, - 15.2, - 15.1, - 15, - 15, - 15, - 14.9, - 14.7, - 14.6, - 14.5, - 14.3, - 14.2, - 14, - 14, - 13.9, - 13.7, - 13.6, - 13.4, - 13.1, - 12.8, - 12.6, - 12.6, - 12.6, - 12, - 11.4, - 10.9, - 10.4, - 10.1, - 34.5, - 35, - 35.4, - 35.8, - 36.2, - 36.6, - 37, - 37.3, - 37.7, - 38, - 38.3, - 38.4, - 38.6, - 38.7, - 38.8, - 39.3, - 39.5, - 39.3, - 39, - 38.9, - 38.9, - 38.9, - 38.9, - 38.9, - 38.8, - 38.8, - 38.8, - 38.8, - 38.8, - 38.8, - 38.9, - 39.1, - 39.1, - 39.2, - 39.1, - 38.9, - 39, - 39, - 38.8, - 38.8, - 38.7, - 38.7, - 38.7, - 38.6, - 38.5, - 38.4, - 38.3, - 38, - 37.8, - 37.6, - 37.6, - 37.6, - 37.6, - 37.6, - 37.7, - 37.9, - 38, - 38, - 37.8, - 37.7, - 37.6, - 37.4, - 37.3, - 37.2, - 37.2, - 37, - 36.8, - 36.7, - 36.6, - 36.4, - 36.3, - 36.2, - 36, - 35.9, - 35.8, - 35.6, - 35.5, - 35.4, - 35.2, - 35.1, - 34.9, - 34.7, - 34.6, - 34.5, - 34.3, - 34.2, - 34.2, - 34, - 33.9, - 33.7, - 33.4, - 33.3, - 33, - 32.6, - 32.4, - 32.2, - 31.9, - 31.6, - 31.3, - 31, - 30.7, - 30.3, - 30.1, - 29.8, - 29.5, - 29.3, - 29, - 28.8, - 28.7, - 28.7, - 28.5, - 28.4, - 28.4, - 28.4, - 28.5, - 28.4, - 28.3, - 28.1, - 27.9, - 28, - 28.1, - 28.3, - 28.3, - 28.1, - 27.9, - 27.9, - 28, - 28.1, - 28.1, - 28.2, - 28.1, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.1, - 27, - 27, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.5, - 26.3, - 26.3, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.6, - 25.6, - 25.4, - 25.1, - 24.9, - 24.9, - 24.9, - 24.9, - 24.9, - 24.7, - 24.2, - 23.9, - 23.7, - 23.6, - 23.3, - 23.1, - 22.9, - 22.5, - 22.1, - 21.7, - 21.4, - 21.1, - 20.9, - 20.8, - 20.7, - 20.6, - 20.5, - 20.3, - 20.2, - 19.9, - 19.7, - 19.6, - 19.5, - 19.3, - 19.1, - 19, - 18.9, - 18.7, - 18.6, - 18.5, - 18.3, - 18.2, - 18.1, - 18, - 18, - 17.9, - 17.9, - 17.7, - 17.4, - 17.2, - 17, - 16.9, - 16.8, - 16.6, - 16.5, - 16.5, - 16.6, - 16.7, - 16.8, - 16.6, - 16.5, - 16.2, - 15.9, - 15.6, - 15.5, - 15.3, - 15.2, - 15.2, - 15.1, - 15, - 15, - 14.9, - 14.9, - 14.8, - 14.7, - 14.5, - 14.4, - 14.2, - 14, - 14, - 14.1, - 13.8, - 13.7, - 13.5, - 13.3, - 12.8, - 12.6, - 12.6, - 12.6, - 12.3, - 11.6, - 11, - 10.5, - 10.2, - 34.3, - 34.8, - 35.3, - 35.7, - 36, - 36.5, - 36.9, - 37.3, - 37.6, - 37.9, - 38.1, - 38.2, - 38.3, - 38.4, - 38.9, - 39.5, - 39.3, - 38.8, - 38.7, - 38.6, - 38.7, - 38.8, - 38.7, - 38.6, - 38.6, - 38.6, - 38.6, - 38.7, - 38.7, - 38.7, - 38.7, - 38.8, - 38.9, - 39, - 38.9, - 38.8, - 38.8, - 38.8, - 38.7, - 38.6, - 38.6, - 38.6, - 38.5, - 38.4, - 38.4, - 38.3, - 38.1, - 37.8, - 37.6, - 37.6, - 37.6, - 37.7, - 37.7, - 37.8, - 37.9, - 38, - 38, - 37.9, - 37.7, - 37.6, - 37.6, - 37.5, - 37.4, - 37.3, - 37.1, - 37, - 36.9, - 36.7, - 36.6, - 36.4, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.4, - 35.3, - 35.1, - 35, - 34.8, - 34.7, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 34, - 33.9, - 33.7, - 33.4, - 33.2, - 32.9, - 32.6, - 32.3, - 32, - 31.8, - 31.4, - 31.2, - 30.9, - 30.6, - 30.3, - 30, - 29.7, - 29.5, - 29.2, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.4, - 28.4, - 28.5, - 28.4, - 28.4, - 28.3, - 28.3, - 28.1, - 28.1, - 28.2, - 28.3, - 28.4, - 28.4, - 28.2, - 28.1, - 28.2, - 28.3, - 28.2, - 28.1, - 28.2, - 28.1, - 27.9, - 28, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.4, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 26, - 25.9, - 25.7, - 25.8, - 25.7, - 25.6, - 25.4, - 25.1, - 24.9, - 24.8, - 24.8, - 24.7, - 24.9, - 24.7, - 24.2, - 23.9, - 23.7, - 23.5, - 23.4, - 23.2, - 23.1, - 23, - 22.7, - 22.1, - 21.6, - 21.2, - 21, - 20.9, - 20.8, - 20.7, - 20.5, - 20.4, - 20.2, - 20, - 19.8, - 19.7, - 19.6, - 19.3, - 19.1, - 19, - 19, - 18.9, - 18.8, - 18.6, - 18.4, - 18.3, - 18.2, - 18.1, - 18, - 18, - 18, - 18, - 17.7, - 17.4, - 17.4, - 17.1, - 17, - 16.9, - 16.8, - 16.7, - 16.8, - 16.8, - 16.9, - 16.6, - 16.6, - 16.5, - 16.1, - 15.7, - 15.6, - 15.4, - 15.3, - 15.2, - 15.1, - 15, - 15, - 15, - 14.9, - 14.9, - 14.7, - 14.6, - 14.5, - 14.4, - 14.1, - 14, - 14.2, - 14, - 13.8, - 13.6, - 13.4, - 13, - 12.6, - 12.6, - 12.5, - 12.5, - 11.8, - 11, - 10.6, - 10.2, - 34.1, - 34.6, - 35.1, - 35.5, - 35.9, - 36.3, - 36.7, - 37.1, - 37.4, - 37.7, - 37.8, - 38, - 38.1, - 38.3, - 38.8, - 39, - 38.6, - 38.4, - 38.4, - 38.5, - 38.5, - 38.4, - 38.5, - 38.5, - 38.5, - 38.5, - 38.5, - 38.6, - 38.6, - 38.6, - 38.7, - 38.7, - 38.7, - 38.7, - 38.7, - 38.7, - 38.8, - 38.6, - 38.5, - 38.5, - 38.5, - 38.4, - 38.4, - 38.3, - 38.2, - 38.1, - 37.9, - 37.7, - 37.6, - 37.6, - 37.7, - 37.8, - 37.8, - 37.9, - 38.1, - 38, - 37.9, - 37.7, - 37.6, - 37.6, - 37.5, - 37.4, - 37.4, - 37.3, - 37.2, - 37.1, - 36.9, - 36.7, - 36.5, - 36.3, - 36.1, - 36, - 35.9, - 35.8, - 35.6, - 35.5, - 35.4, - 35.2, - 35, - 34.9, - 34.7, - 34.5, - 34.4, - 34.3, - 34.2, - 34.2, - 34.1, - 34, - 33.9, - 33.7, - 33.4, - 33.1, - 32.8, - 32.5, - 32.2, - 31.9, - 31.6, - 31.3, - 31, - 30.8, - 30.5, - 30.2, - 30, - 29.7, - 29.5, - 29.2, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 28.5, - 28.5, - 28.5, - 28.3, - 28.4, - 28.5, - 28.3, - 28.4, - 28.3, - 28.3, - 28.5, - 28.5, - 28.4, - 28.3, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28.2, - 28.1, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 27.8, - 27.8, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.8, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 26, - 25.9, - 25.8, - 25.8, - 25.6, - 25.5, - 25.3, - 25, - 24.9, - 24.8, - 24.6, - 24.7, - 24.7, - 24.7, - 24.4, - 23.9, - 23.7, - 23.5, - 23.3, - 23.2, - 23.1, - 23.1, - 23, - 22.7, - 22.1, - 21.5, - 21.2, - 21.1, - 20.9, - 20.8, - 20.6, - 20.5, - 20.3, - 20.1, - 19.9, - 19.7, - 19.6, - 19.4, - 19.3, - 19.2, - 19.1, - 19, - 18.9, - 18.8, - 18.5, - 18.3, - 18.2, - 18.2, - 18.1, - 18, - 18, - 18.1, - 17.9, - 17.8, - 17.7, - 17.4, - 17.2, - 17.2, - 17.1, - 17, - 16.9, - 16.9, - 17, - 16.8, - 16.7, - 16.5, - 16.2, - 15.9, - 15.6, - 15.5, - 15.6, - 15.3, - 15.2, - 15, - 15.1, - 15.1, - 14.9, - 15, - 14.9, - 14.8, - 14.7, - 14.5, - 14.2, - 14.2, - 14.2, - 14.1, - 14, - 13.7, - 13.4, - 13.1, - 12.7, - 12.6, - 12.5, - 12.4, - 11.9, - 11.1, - 10.6, - 10.1, - 33.9, - 34.4, - 34.8, - 35.3, - 35.7, - 36.1, - 36.5, - 36.9, - 37.2, - 37.4, - 37.6, - 37.7, - 37.8, - 38.2, - 38.4, - 38.2, - 38.1, - 38.2, - 38.2, - 38.3, - 38.3, - 38.3, - 38.3, - 38.3, - 38.4, - 38.4, - 38.5, - 38.5, - 38.6, - 38.7, - 38.6, - 38.7, - 38.6, - 38.6, - 38.6, - 38.6, - 38.5, - 38.5, - 38.4, - 38.4, - 38.4, - 38.4, - 38.3, - 38.2, - 38, - 37.8, - 37.8, - 37.6, - 37.7, - 37.7, - 37.8, - 37.9, - 38, - 38.1, - 38.1, - 38, - 37.8, - 37.6, - 37.6, - 37.5, - 37.4, - 37.4, - 37.3, - 37.2, - 37.3, - 37.2, - 36.9, - 36.6, - 36.4, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.2, - 35, - 34.8, - 34.6, - 34.4, - 34.3, - 34.2, - 34.2, - 34.2, - 34.1, - 34, - 33.9, - 33.7, - 33.4, - 33, - 32.7, - 32.5, - 32.1, - 31.8, - 31.5, - 31.2, - 30.9, - 30.7, - 30.5, - 30.2, - 30, - 29.7, - 29.5, - 29.2, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.5, - 28.6, - 28.6, - 28.5, - 28.4, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.5, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28.2, - 28.2, - 28.2, - 28, - 28, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27.1, - 27.1, - 27, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26.1, - 25.8, - 25.7, - 25.5, - 25.4, - 25.2, - 25, - 24.8, - 24.7, - 24.6, - 24.6, - 24.6, - 24.7, - 24.4, - 24, - 23.7, - 23.5, - 23.3, - 23.2, - 23.1, - 23.1, - 23, - 22.9, - 22.6, - 22.1, - 21.7, - 21.5, - 21.2, - 21, - 20.8, - 20.6, - 20.4, - 20.3, - 20.1, - 19.9, - 19.8, - 19.6, - 19.5, - 19.4, - 19.2, - 19.2, - 19.1, - 18.9, - 18.6, - 18.5, - 18.3, - 18.3, - 18.2, - 18.1, - 18.1, - 18.1, - 18.1, - 18, - 17.9, - 17.7, - 17.5, - 17.4, - 17.3, - 17.1, - 17, - 17, - 17, - 17, - 16.7, - 16.6, - 16.3, - 16.1, - 15.9, - 15.6, - 15.7, - 15.4, - 15.2, - 15, - 15, - 15.1, - 15, - 15, - 14.9, - 14.8, - 14.8, - 14.5, - 14.4, - 14.3, - 14.3, - 14.1, - 14, - 13.8, - 13.5, - 13.1, - 12.8, - 12.7, - 12.6, - 12.2, - 11.8, - 11.2, - 10.7, - 10.2, - 33.7, - 34.1, - 34.6, - 35, - 35.5, - 35.9, - 36.3, - 36.6, - 36.9, - 37.1, - 37.3, - 37.5, - 37.7, - 37.8, - 37.8, - 37.8, - 37.9, - 38.1, - 38.2, - 38.2, - 38.2, - 38.2, - 38.2, - 38.3, - 38.3, - 38.4, - 38.5, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.5, - 38.5, - 38.4, - 38.4, - 38.3, - 38.3, - 38.2, - 38.2, - 38.2, - 38, - 37.8, - 37.7, - 37.7, - 37.7, - 37.7, - 37.8, - 37.9, - 38, - 38.1, - 38.1, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.5, - 37.4, - 37.3, - 37.2, - 37.1, - 37.2, - 37.4, - 36.9, - 36.5, - 36.3, - 36.1, - 36, - 35.8, - 35.8, - 35.7, - 35.5, - 35.4, - 35.3, - 35.1, - 35, - 34.8, - 34.6, - 34.4, - 34.2, - 34.2, - 34.2, - 34.2, - 34.1, - 34, - 33.8, - 33.6, - 33.4, - 33.1, - 32.7, - 32.5, - 32.2, - 31.9, - 31.6, - 31.2, - 30.9, - 30.7, - 30.4, - 30.2, - 30, - 29.7, - 29.5, - 29.2, - 29.1, - 28.9, - 28.8, - 28.7, - 28.7, - 28.7, - 28.5, - 28.5, - 28.6, - 28.6, - 28.5, - 28.5, - 28.5, - 28.5, - 28.4, - 28.3, - 28.4, - 28.5, - 28.7, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.3, - 28.3, - 28.3, - 28.2, - 28, - 28, - 28, - 28, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.3, - 27.2, - 27.1, - 27.1, - 27.1, - 27, - 26.8, - 26.6, - 26.5, - 26.3, - 26.2, - 26.1, - 26, - 25.9, - 25.7, - 25.5, - 25.3, - 25.2, - 25, - 24.7, - 24.7, - 24.6, - 24.6, - 24.6, - 24.6, - 24.5, - 24.1, - 23.8, - 23.5, - 23.3, - 23.2, - 23.1, - 23, - 23, - 22.9, - 22.8, - 22.6, - 22.3, - 22, - 21.7, - 21.4, - 21.2, - 21.1, - 20.9, - 20.6, - 20.5, - 20.2, - 20, - 19.8, - 19.8, - 19.7, - 19.5, - 19.3, - 19.2, - 19.1, - 18.8, - 18.6, - 18.5, - 18.4, - 18.3, - 18.2, - 18.1, - 18.1, - 18.1, - 18.2, - 17.9, - 17.7, - 17.7, - 17.5, - 17.5, - 17.3, - 17.2, - 17.1, - 17.1, - 17, - 16.8, - 16.6, - 16.5, - 16.3, - 16, - 15.8, - 15.8, - 15.5, - 15.2, - 15.1, - 15, - 15.1, - 15.1, - 15, - 15, - 14.9, - 14.8, - 14.7, - 14.6, - 14.5, - 14.4, - 14.2, - 14, - 13.9, - 13.5, - 13.1, - 13, - 12.9, - 12.7, - 12.2, - 11.7, - 11.2, - 10.8, - 10.3, - 33.4, - 33.9, - 34.3, - 34.8, - 35.2, - 35.6, - 36, - 36.3, - 36.6, - 36.8, - 37.1, - 37.3, - 37.6, - 37.5, - 37.5, - 37.7, - 38, - 38.2, - 38.2, - 38.1, - 38.1, - 38.2, - 38.2, - 38.3, - 38.4, - 38.5, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.5, - 38.5, - 38.4, - 38.3, - 38.3, - 38.2, - 38.2, - 38.1, - 38, - 37.8, - 37.7, - 37.7, - 37.7, - 37.8, - 37.8, - 37.9, - 37.9, - 38, - 38.1, - 38, - 37.8, - 37.7, - 37.6, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37, - 36.9, - 36.9, - 37.1, - 36.9, - 36.5, - 36.2, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.3, - 35.2, - 35.1, - 34.9, - 34.8, - 34.6, - 34.4, - 34.3, - 34.2, - 34.2, - 34.2, - 34.2, - 34.1, - 33.8, - 33.6, - 33.4, - 33.1, - 32.8, - 32.5, - 32.2, - 31.9, - 31.6, - 31.3, - 31, - 30.7, - 30.4, - 30.2, - 29.9, - 29.7, - 29.4, - 29.2, - 29.1, - 29, - 28.9, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.6, - 28.7, - 28.7, - 28.5, - 28.5, - 28.5, - 28.5, - 28.5, - 28.5, - 28.6, - 28.7, - 28.6, - 28.5, - 28.5, - 28.5, - 28.4, - 28.3, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 28, - 28, - 28, - 27.9, - 27.8, - 27.8, - 27.8, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 26, - 25.9, - 25.7, - 25.5, - 25.3, - 25.1, - 24.9, - 24.7, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.4, - 23.9, - 23.5, - 23.3, - 23.2, - 23.1, - 23.1, - 23, - 22.9, - 22.8, - 22.7, - 22.6, - 22.5, - 22.1, - 21.8, - 21.8, - 21.7, - 21.5, - 21.1, - 20.9, - 20.8, - 20.5, - 20.2, - 20.1, - 20, - 19.8, - 19.7, - 19.5, - 19.3, - 19, - 18.9, - 18.7, - 18.5, - 18.4, - 18.3, - 18.2, - 18.2, - 18.3, - 18.3, - 18.2, - 17.9, - 17.8, - 17.7, - 17.6, - 17.5, - 17.4, - 17.3, - 17.3, - 17.2, - 17, - 16.7, - 16.5, - 16.5, - 16.1, - 16, - 15.8, - 15.6, - 15.2, - 15.1, - 15, - 15, - 15.1, - 15.1, - 15, - 14.8, - 14.8, - 14.9, - 14.7, - 14.7, - 14.4, - 14.3, - 14, - 13.7, - 13.4, - 13.1, - 13, - 12.9, - 12.9, - 12.3, - 11.7, - 11.3, - 10.9, - 10.5, - 33.2, - 33.7, - 34.1, - 34.5, - 35, - 35.3, - 35.7, - 36, - 36.3, - 36.7, - 36.9, - 37.1, - 37.3, - 37.3, - 37.4, - 37.6, - 37.8, - 37.9, - 38, - 38.1, - 38.2, - 38.3, - 38.4, - 38.5, - 38.6, - 38.7, - 38.8, - 38.8, - 38.7, - 38.7, - 38.7, - 38.6, - 38.6, - 38.6, - 38.5, - 38.5, - 38.4, - 38.3, - 38.2, - 38.2, - 38.1, - 38, - 37.8, - 37.7, - 37.7, - 37.7, - 37.7, - 37.7, - 37.9, - 37.9, - 38, - 38, - 37.9, - 37.8, - 37.7, - 37.6, - 37.6, - 37.6, - 37.5, - 37.4, - 37.2, - 37.1, - 36.9, - 36.7, - 36.6, - 36.6, - 36.5, - 36.3, - 36.1, - 36, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.3, - 35.1, - 35, - 34.9, - 34.8, - 34.7, - 34.5, - 34.4, - 34.3, - 34.2, - 34.2, - 34.2, - 34.1, - 33.8, - 33.6, - 33.4, - 33.1, - 32.8, - 32.5, - 32.2, - 31.9, - 31.6, - 31.3, - 31.1, - 30.8, - 30.5, - 30.2, - 29.9, - 29.7, - 29.4, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.6, - 28.5, - 28.7, - 28.6, - 28.6, - 28.7, - 28.6, - 28.5, - 28.4, - 28.5, - 28.5, - 28.4, - 28.3, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 28, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.3, - 27.3, - 27.2, - 27.1, - 27.2, - 27.1, - 26.9, - 26.6, - 26.5, - 26.4, - 26.3, - 26.1, - 25.9, - 25.8, - 25.7, - 25.5, - 25.3, - 25.1, - 24.8, - 24.7, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.5, - 24.2, - 23.6, - 23.4, - 23.2, - 23.1, - 23.1, - 23, - 23, - 22.8, - 22.8, - 22.7, - 22.6, - 22.4, - 22.2, - 22, - 21.8, - 21.6, - 21.3, - 21.2, - 21.1, - 21.1, - 20.6, - 20.4, - 20.3, - 20.2, - 20, - 19.8, - 19.6, - 19.4, - 19.1, - 19, - 18.9, - 18.6, - 18.4, - 18.3, - 18.3, - 18.3, - 18.4, - 18.3, - 18.1, - 17.9, - 17.9, - 17.7, - 17.6, - 17.6, - 17.5, - 17.4, - 17.4, - 17.2, - 16.9, - 16.6, - 16.5, - 16.3, - 16.1, - 16, - 15.7, - 15.3, - 15.2, - 15.1, - 15, - 14.9, - 15, - 14.9, - 14.8, - 14.7, - 14.7, - 14.8, - 14.9, - 14.6, - 14.2, - 14, - 13.7, - 13.5, - 13.3, - 13.2, - 13, - 12.9, - 12.3, - 11.8, - 11.4, - 11, - 10.6, - 33.3, - 33.9, - 34, - 34.4, - 34.9, - 35.2, - 35.6, - 35.9, - 36.2, - 36.6, - 36.8, - 36.9, - 37.1, - 37.3, - 37.6, - 37.7, - 37.9, - 38, - 38.1, - 38.3, - 38.4, - 38.5, - 38.8, - 38.8, - 38.9, - 39, - 39.3, - 39.2, - 39.1, - 39, - 38.8, - 38.7, - 38.6, - 38.5, - 38.5, - 38.4, - 38.3, - 38.3, - 38.2, - 38.1, - 38, - 37.8, - 37.7, - 37.7, - 37.7, - 37.7, - 37.7, - 37.8, - 37.9, - 37.9, - 37.9, - 37.8, - 37.7, - 37.6, - 37.6, - 37.5, - 37.5, - 37.5, - 37.3, - 37.2, - 37.1, - 36.9, - 36.8, - 36.6, - 36.4, - 36.4, - 36.3, - 36.2, - 36, - 35.9, - 35.8, - 35.6, - 35.5, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.6, - 34.5, - 34.4, - 34.3, - 34.2, - 34.2, - 34, - 33.9, - 33.6, - 33.4, - 33.1, - 32.8, - 32.6, - 32.2, - 31.9, - 31.6, - 31.3, - 31.1, - 30.8, - 30.5, - 30.2, - 29.9, - 29.7, - 29.4, - 29.3, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.8, - 28.7, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.6, - 28.5, - 28.4, - 28.5, - 28.5, - 28.4, - 28.3, - 28.4, - 28.4, - 28.2, - 28.1, - 28.1, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.7, - 27.6, - 27.5, - 27.6, - 27.4, - 27.3, - 27.3, - 27.2, - 27.2, - 27.2, - 27, - 26.8, - 26.6, - 26.5, - 26.3, - 26.1, - 25.9, - 25.8, - 25.6, - 25.5, - 25.3, - 25.2, - 24.8, - 24.7, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.6, - 24.5, - 24.2, - 23.7, - 23.5, - 23.3, - 23.2, - 23.1, - 23, - 22.9, - 22.8, - 22.8, - 22.8, - 22.7, - 22.5, - 22.3, - 22, - 21.8, - 21.6, - 21.4, - 21.4, - 21.4, - 21.4, - 21.1, - 20.7, - 20.5, - 20.4, - 20.4, - 20.2, - 19.9, - 19.7, - 19.4, - 19.2, - 19.1, - 19, - 18.8, - 18.5, - 18.4, - 18.4, - 18.4, - 18.4, - 18.3, - 18.1, - 18, - 17.8, - 17.8, - 17.7, - 17.5, - 17.5, - 17.5, - 17.3, - 17.1, - 16.8, - 16.7, - 16.5, - 16.2, - 16, - 15.8, - 15.4, - 15.3, - 15.1, - 15, - 14.9, - 14.9, - 14.8, - 14.8, - 14.8, - 14.8, - 14.8, - 14.9, - 14.8, - 14.3, - 14, - 13.8, - 13.6, - 13.5, - 13.5, - 13.1, - 12.7, - 12.3, - 11.9, - 11.5, - 11, - 10.7, - 33.3, - 33.8, - 34, - 34.3, - 34.8, - 35.2, - 35.7, - 36, - 36.2, - 36.6, - 36.8, - 36.9, - 37.2, - 37.5, - 37.7, - 37.9, - 38.1, - 38.3, - 38.4, - 38.6, - 38.7, - 38.8, - 39.1, - 39.1, - 39.2, - 39.4, - 39.7, - 39.8, - 39.6, - 39.3, - 39.1, - 38.9, - 38.7, - 38.6, - 38.5, - 38.4, - 38.3, - 38.2, - 38.1, - 38, - 37.8, - 37.7, - 37.7, - 37.7, - 37.7, - 37.7, - 37.7, - 37.8, - 37.8, - 37.7, - 37.7, - 37.6, - 37.6, - 37.6, - 37.5, - 37.5, - 37.4, - 37.3, - 37.1, - 37, - 36.9, - 36.7, - 36.6, - 36.4, - 36.3, - 36.2, - 36.2, - 36.1, - 36, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.7, - 34.6, - 34.5, - 34.3, - 34.3, - 34.2, - 34, - 33.8, - 33.6, - 33.4, - 33.1, - 32.9, - 32.6, - 32.3, - 31.9, - 31.6, - 31.4, - 31.1, - 30.8, - 30.5, - 30.2, - 30, - 29.7, - 29.5, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.7, - 28.8, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.7, - 28.7, - 28.5, - 28.5, - 28.4, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 28, - 27.8, - 27.6, - 27.7, - 27.5, - 27.4, - 27.4, - 27.2, - 27.2, - 27.1, - 27.2, - 26.8, - 26.7, - 26.6, - 26.4, - 26.1, - 25.9, - 25.8, - 25.6, - 25.5, - 25.4, - 25.2, - 24.9, - 24.7, - 24.6, - 24.6, - 24.6, - 24.6, - 24.5, - 24.5, - 24.5, - 24.2, - 23.8, - 23.6, - 23.4, - 23.2, - 23.1, - 23, - 22.9, - 22.8, - 22.8, - 22.8, - 22.7, - 22.4, - 22.2, - 22, - 21.8, - 21.7, - 21.5, - 21.5, - 21.4, - 21.4, - 21.4, - 21, - 20.7, - 20.5, - 20.4, - 20.4, - 20.2, - 20, - 19.7, - 19.4, - 19.3, - 19.2, - 19.2, - 19, - 18.7, - 18.5, - 18.4, - 18.5, - 18.4, - 18.2, - 18.1, - 18, - 17.9, - 17.8, - 17.7, - 17.7, - 17.6, - 17.3, - 17.1, - 16.9, - 16.8, - 16.6, - 16.3, - 16.1, - 15.8, - 15.6, - 15.4, - 15.2, - 15.1, - 14.9, - 14.8, - 14.8, - 14.8, - 14.9, - 14.9, - 14.9, - 14.8, - 14.7, - 14.4, - 14.2, - 13.9, - 13.7, - 13.6, - 13.5, - 13, - 12.8, - 12.4, - 11.9, - 11.5, - 11.1, - 10.8, - 33.2, - 33.6, - 33.9, - 34.3, - 34.8, - 35.2, - 35.8, - 36.1, - 36.3, - 36.6, - 36.9, - 37.1, - 37.3, - 37.6, - 37.9, - 38.1, - 38.3, - 38.5, - 38.7, - 38.8, - 38.9, - 39.1, - 39.2, - 39.3, - 39.5, - 39.7, - 40, - 40.2, - 39.9, - 39.5, - 39.2, - 38.9, - 38.7, - 38.6, - 38.5, - 38.4, - 38.3, - 38.2, - 38.1, - 37.9, - 37.8, - 37.8, - 37.8, - 37.7, - 37.7, - 37.7, - 37.8, - 37.8, - 37.7, - 37.6, - 37.6, - 37.5, - 37.5, - 37.5, - 37.4, - 37.3, - 37.2, - 37, - 36.9, - 36.8, - 36.7, - 36.5, - 36.4, - 36.3, - 36.2, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.3, - 35.3, - 35.2, - 35.1, - 35, - 34.9, - 34.9, - 34.8, - 34.7, - 34.5, - 34.4, - 34.3, - 34.1, - 33.9, - 33.6, - 33.6, - 33.4, - 33.1, - 32.9, - 32.7, - 32.4, - 32, - 31.7, - 31.4, - 31.2, - 30.9, - 30.6, - 30.3, - 30, - 29.7, - 29.4, - 29.3, - 29.2, - 29.1, - 29.1, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.9, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.7, - 28.5, - 28.4, - 28.4, - 28.5, - 28.4, - 28.3, - 28.3, - 28.4, - 28.3, - 28.3, - 28.2, - 28.3, - 28.2, - 28.2, - 28, - 28, - 27.9, - 27.9, - 28, - 28.1, - 28.1, - 27.8, - 27.7, - 27.5, - 27.4, - 27.4, - 27.3, - 27.2, - 27.2, - 27.2, - 26.8, - 26.7, - 26.6, - 26.4, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.4, - 25.3, - 25, - 24.8, - 24.7, - 24.7, - 24.7, - 24.6, - 24.6, - 24.7, - 24.5, - 24.1, - 23.9, - 23.7, - 23.6, - 23.4, - 23.2, - 23.1, - 23, - 22.9, - 22.8, - 22.8, - 22.7, - 22.4, - 22.2, - 22, - 21.9, - 21.8, - 21.6, - 21.5, - 21.5, - 21.4, - 21.4, - 21.2, - 20.9, - 20.6, - 20.5, - 20.5, - 20.3, - 20.2, - 19.9, - 19.6, - 19.4, - 19.4, - 19.4, - 19.3, - 19.1, - 18.8, - 18.6, - 18.5, - 18.5, - 18.3, - 18.2, - 18.1, - 18, - 17.9, - 17.8, - 17.7, - 17.5, - 17.3, - 17.1, - 17, - 17, - 16.8, - 16.5, - 16.2, - 16, - 15.9, - 15.5, - 15.4, - 15.2, - 15, - 14.9, - 14.8, - 14.8, - 14.8, - 14.8, - 14.9, - 14.9, - 14.8, - 14.5, - 14.3, - 13.9, - 13.7, - 13.6, - 13.5, - 13.1, - 12.8, - 12.5, - 12.1, - 11.6, - 11.2, - 10.8, - 33.1, - 33.6, - 34.1, - 34.5, - 34.9, - 35.4, - 35.8, - 36.1, - 36.5, - 36.7, - 37, - 37.2, - 37.5, - 37.8, - 38.1, - 38.4, - 38.6, - 38.8, - 39, - 39.2, - 39.2, - 39.4, - 39.5, - 39.6, - 39.7, - 39.8, - 40, - 40, - 39.9, - 39.6, - 39.3, - 38.9, - 38.7, - 38.6, - 38.5, - 38.4, - 38.3, - 38.1, - 37.9, - 37.8, - 37.8, - 37.8, - 37.8, - 37.7, - 37.6, - 37.6, - 37.7, - 37.6, - 37.5, - 37.5, - 37.4, - 37.4, - 37.3, - 37.3, - 37.2, - 37.1, - 37, - 36.9, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.4, - 35.3, - 35.3, - 35.2, - 35.1, - 35, - 35, - 34.9, - 34.8, - 34.7, - 34.5, - 34.4, - 34.3, - 34.1, - 33.8, - 33.5, - 33.5, - 33.4, - 33.1, - 33, - 32.8, - 32.4, - 32.1, - 31.8, - 31.5, - 31.2, - 30.9, - 30.6, - 30.4, - 30.1, - 29.8, - 29.5, - 29.3, - 29.3, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.5, - 28.4, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.3, - 28.3, - 28.3, - 28.2, - 28.2, - 28.2, - 28.1, - 28.1, - 28, - 28, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.6, - 27.4, - 27.3, - 27.3, - 27.3, - 27.2, - 26.9, - 26.7, - 26.6, - 26.3, - 26.2, - 26, - 25.9, - 25.8, - 25.7, - 25.5, - 25.4, - 25.2, - 25, - 24.9, - 24.8, - 24.8, - 24.8, - 24.8, - 24.7, - 24.4, - 24.2, - 24, - 23.8, - 23.7, - 23.5, - 23.4, - 23.2, - 23.1, - 23, - 22.9, - 22.8, - 22.7, - 22.5, - 22.2, - 22, - 22, - 21.9, - 21.7, - 21.6, - 21.5, - 21.4, - 21.3, - 21.2, - 21.1, - 20.7, - 20.6, - 20.6, - 20.4, - 20.2, - 20.1, - 19.9, - 19.6, - 19.5, - 19.4, - 19.4, - 19.3, - 19.1, - 18.8, - 18.7, - 18.6, - 18.4, - 18.3, - 18.2, - 18.1, - 18, - 17.9, - 17.7, - 17.6, - 17.4, - 17.3, - 17.1, - 17.1, - 16.9, - 16.7, - 16.4, - 16.2, - 16.1, - 15.7, - 15.5, - 15.3, - 15.1, - 15, - 15, - 14.9, - 14.8, - 14.7, - 14.9, - 14.9, - 14.8, - 14.5, - 14.3, - 14, - 13.8, - 13.7, - 13.5, - 13.2, - 12.9, - 12.5, - 12.1, - 11.6, - 11.3, - 11, - 33.3, - 33.9, - 34.4, - 34.7, - 35.2, - 35.6, - 35.9, - 36.2, - 36.6, - 36.8, - 37.1, - 37.5, - 37.8, - 38.1, - 38.4, - 38.7, - 38.9, - 39, - 39.2, - 39.4, - 39.5, - 39.7, - 39.7, - 39.8, - 39.8, - 40, - 40, - 39.9, - 39.8, - 39.7, - 39.2, - 38.9, - 38.7, - 38.6, - 38.5, - 38.4, - 38.2, - 38, - 37.8, - 37.8, - 37.8, - 37.7, - 37.7, - 37.6, - 37.5, - 37.5, - 37.5, - 37.4, - 37.3, - 37.3, - 37.2, - 37.2, - 37.1, - 37, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.2, - 36.1, - 36, - 35.9, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35.1, - 35.1, - 35, - 35, - 34.9, - 34.8, - 34.7, - 34.4, - 34.2, - 34.1, - 33.8, - 33.5, - 33.4, - 33.4, - 33.2, - 33, - 32.9, - 32.5, - 32.1, - 31.8, - 31.6, - 31.3, - 31, - 30.7, - 30.4, - 30.1, - 29.8, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.9, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.6, - 28.7, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.3, - 28.3, - 28.3, - 28.3, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 28, - 28, - 27.9, - 27.9, - 27.9, - 27.9, - 27.8, - 27.8, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 26.9, - 26.8, - 26.6, - 26.5, - 26.3, - 26.1, - 26, - 26, - 25.9, - 25.7, - 25.6, - 25.4, - 25.2, - 25.1, - 24.9, - 24.8, - 24.8, - 24.9, - 24.7, - 24.4, - 24.3, - 24.1, - 24, - 23.8, - 23.6, - 23.5, - 23.3, - 23.1, - 23, - 23, - 22.9, - 22.7, - 22.5, - 22.2, - 22.1, - 22, - 22, - 21.8, - 21.7, - 21.6, - 21.5, - 21.4, - 21.3, - 21.2, - 20.9, - 20.6, - 20.6, - 20.5, - 20.3, - 20.2, - 20.1, - 19.9, - 19.6, - 19.5, - 19.5, - 19.4, - 19.2, - 19, - 18.8, - 18.7, - 18.6, - 18.4, - 18.3, - 18.2, - 18.1, - 17.9, - 17.9, - 17.7, - 17.5, - 17.4, - 17.2, - 17.1, - 17, - 16.8, - 16.5, - 16.4, - 16.2, - 15.9, - 15.6, - 15.5, - 15.2, - 15.1, - 15, - 15, - 14.9, - 14.8, - 14.9, - 14.8, - 14.7, - 14.4, - 14.2, - 14.1, - 14, - 13.9, - 13.5, - 13.1, - 12.9, - 12.6, - 12.2, - 11.7, - 11.3, - 11, - 33.8, - 34.3, - 34.8, - 35.1, - 35.6, - 36, - 36.1, - 36.5, - 36.8, - 37.1, - 37.4, - 37.7, - 38, - 38.4, - 38.7, - 38.9, - 39.1, - 39.2, - 39.4, - 39.6, - 39.7, - 39.8, - 39.9, - 39.9, - 39.9, - 39.9, - 40, - 40, - 39.8, - 39.5, - 39.2, - 38.8, - 38.6, - 38.5, - 38.4, - 38.3, - 38, - 37.9, - 37.8, - 37.8, - 37.7, - 37.6, - 37.5, - 37.5, - 37.4, - 37.3, - 37.3, - 37.2, - 37.1, - 37.1, - 37, - 37, - 36.9, - 36.8, - 36.7, - 36.7, - 36.6, - 36.5, - 36.5, - 36.4, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.3, - 35.2, - 35.1, - 35, - 35, - 35, - 34.9, - 34.7, - 34.4, - 34.2, - 34, - 33.8, - 33.5, - 33.4, - 33.3, - 33.2, - 33.1, - 32.9, - 32.5, - 32.2, - 31.9, - 31.6, - 31.4, - 31.1, - 30.8, - 30.5, - 30.2, - 29.9, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29.1, - 29, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.7, - 28.7, - 28.6, - 28.5, - 28.5, - 28.6, - 28.6, - 28.6, - 28.6, - 28.5, - 28.4, - 28.5, - 28.5, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.3, - 28.3, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.8, - 27.9, - 27.6, - 27.5, - 27.5, - 27.4, - 27.2, - 26.9, - 26.8, - 26.8, - 26.6, - 26.5, - 26.3, - 26.2, - 26.1, - 26.1, - 25.9, - 25.7, - 25.6, - 25.3, - 25.2, - 25, - 24.9, - 24.8, - 24.9, - 24.8, - 24.6, - 24.4, - 24.2, - 24.1, - 23.9, - 23.7, - 23.6, - 23.3, - 23.1, - 23, - 22.9, - 22.8, - 22.7, - 22.5, - 22.2, - 22.1, - 22.1, - 22, - 21.9, - 21.9, - 21.8, - 21.6, - 21.5, - 21.5, - 21.3, - 21.1, - 20.9, - 20.7, - 20.6, - 20.5, - 20.3, - 20.2, - 20, - 19.9, - 19.6, - 19.6, - 19.5, - 19.4, - 19.1, - 19, - 18.8, - 18.7, - 18.6, - 18.4, - 18.3, - 18.2, - 18.1, - 17.9, - 17.7, - 17.6, - 17.4, - 17.3, - 17.1, - 17, - 16.8, - 16.6, - 16.5, - 16.4, - 16.1, - 15.7, - 15.5, - 15.4, - 15.1, - 15.1, - 15.2, - 15.1, - 15, - 15, - 14.8, - 14.6, - 14.5, - 14.4, - 14.2, - 14.1, - 14, - 13.6, - 13.3, - 12.9, - 12.6, - 12.3, - 11.9, - 11.4, - 11.1, - 34, - 34.5, - 35, - 35.4, - 35.9, - 36.2, - 36.6, - 36.9, - 37.2, - 37.5, - 37.8, - 38.1, - 38.4, - 38.6, - 38.9, - 39.1, - 39.3, - 39.5, - 39.6, - 39.7, - 39.8, - 39.9, - 40, - 40, - 39.9, - 39.8, - 39.8, - 40, - 39.8, - 39.4, - 39, - 38.8, - 38.6, - 38.4, - 38.3, - 38.1, - 37.9, - 37.9, - 37.8, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.3, - 37.2, - 37.1, - 37.1, - 37, - 37, - 36.9, - 36.8, - 36.8, - 36.7, - 36.6, - 36.6, - 36.5, - 36.5, - 36.4, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.9, - 35.8, - 35.7, - 35.7, - 35.7, - 35.6, - 35.6, - 35.5, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35.1, - 35.1, - 35.1, - 35, - 34.9, - 34.8, - 34.5, - 34.2, - 34, - 33.8, - 33.5, - 33.3, - 33.3, - 33.1, - 33.1, - 32.9, - 32.6, - 32.3, - 32, - 31.7, - 31.4, - 31.1, - 30.8, - 30.5, - 30.2, - 29.9, - 29.7, - 29.5, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.7, - 28.7, - 28.6, - 28.6, - 28.5, - 28.4, - 28.4, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 27.9, - 27.8, - 27.8, - 27.8, - 27.8, - 27.8, - 27.7, - 27.5, - 27.5, - 27.5, - 27.4, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.5, - 25.3, - 25.1, - 24.9, - 24.9, - 24.9, - 24.9, - 24.7, - 24.6, - 24.4, - 24.2, - 24, - 23.7, - 23.5, - 23.3, - 23.1, - 23, - 22.9, - 22.8, - 22.7, - 22.5, - 22.3, - 22.2, - 22.1, - 22.1, - 22, - 22, - 22, - 21.9, - 21.7, - 21.5, - 21.4, - 21.2, - 21, - 20.8, - 20.7, - 20.6, - 20.5, - 20.3, - 20.2, - 20.1, - 19.8, - 19.6, - 19.6, - 19.5, - 19.3, - 19.1, - 19, - 18.9, - 18.8, - 18.6, - 18.4, - 18.3, - 18.2, - 18.1, - 17.9, - 17.6, - 17.4, - 17.3, - 17.2, - 17.1, - 16.9, - 16.7, - 16.5, - 16.4, - 16.2, - 15.9, - 15.7, - 15.5, - 15.3, - 15.1, - 15.1, - 15.1, - 15.1, - 15, - 14.9, - 14.6, - 14.5, - 14.5, - 14.3, - 14.2, - 14, - 13.7, - 13.3, - 13, - 12.6, - 12.3, - 11.9, - 11.5, - 11.1, - 34.2, - 34.8, - 35.4, - 35.9, - 36.3, - 36.8, - 37.5, - 37.7, - 37.9, - 38.2, - 38.3, - 38.5, - 38.7, - 38.9, - 39.1, - 39.3, - 39.5, - 39.6, - 39.8, - 39.9, - 39.9, - 40, - 40, - 40, - 39.9, - 39.8, - 39.8, - 39.9, - 39.6, - 39.2, - 38.8, - 38.6, - 38.5, - 38.3, - 38.1, - 38, - 37.9, - 37.8, - 37.7, - 37.6, - 37.4, - 37.4, - 37.3, - 37.3, - 37.2, - 37.1, - 37.1, - 36.9, - 36.9, - 36.9, - 36.9, - 36.8, - 36.7, - 36.6, - 36.6, - 36.5, - 36.4, - 36.4, - 36.3, - 36.3, - 36.2, - 36.1, - 36.1, - 36, - 35.9, - 35.9, - 35.8, - 35.7, - 35.7, - 35.7, - 35.7, - 35.6, - 35.6, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35.1, - 35.1, - 35, - 35, - 34.8, - 34.5, - 34.3, - 34, - 33.8, - 33.5, - 33.3, - 33.2, - 33.1, - 33.1, - 32.9, - 32.7, - 32.4, - 32, - 31.7, - 31.4, - 31.1, - 30.9, - 30.6, - 30.3, - 30, - 29.8, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29.1, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.7, - 28.7, - 28.7, - 28.6, - 28.5, - 28.4, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.2, - 28.2, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.8, - 27.8, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.1, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.5, - 25.3, - 25.1, - 25, - 24.9, - 24.9, - 24.9, - 24.9, - 24.8, - 24.5, - 24, - 23.7, - 23.5, - 23.3, - 23.2, - 23.1, - 22.9, - 22.8, - 22.7, - 22.5, - 22.4, - 22.3, - 22.2, - 22.2, - 22.1, - 22.1, - 22.1, - 22, - 21.9, - 21.5, - 21.4, - 21.3, - 21.2, - 21, - 20.9, - 20.8, - 20.7, - 20.5, - 20.3, - 20.2, - 20, - 19.7, - 19.6, - 19.6, - 19.4, - 19.2, - 19.1, - 19.1, - 18.9, - 18.7, - 18.6, - 18.4, - 18.3, - 18.1, - 18, - 17.7, - 17.5, - 17.4, - 17.3, - 17.1, - 17, - 16.8, - 16.7, - 16.6, - 16.3, - 16.1, - 15.9, - 15.6, - 15.4, - 15.1, - 15, - 15, - 15.1, - 15.1, - 15, - 14.7, - 14.6, - 14.6, - 14.5, - 14.2, - 14.1, - 13.8, - 13.3, - 13, - 12.6, - 12.3, - 11.9, - 11.6, - 11.2, - 34.5, - 35.2, - 36, - 36.5, - 37, - 37.4, - 37.9, - 38.1, - 38.3, - 38.4, - 38.6, - 38.7, - 38.9, - 39.1, - 39.3, - 39.4, - 39.6, - 39.7, - 39.8, - 39.9, - 39.9, - 40, - 40, - 39.9, - 39.9, - 39.7, - 39.8, - 39.7, - 39.4, - 39.1, - 38.7, - 38.5, - 38.4, - 38.2, - 38, - 38, - 37.9, - 37.8, - 37.6, - 37.5, - 37.4, - 37.3, - 37.3, - 37.2, - 37.1, - 37, - 37, - 36.9, - 36.8, - 36.8, - 36.8, - 36.7, - 36.7, - 36.6, - 36.5, - 36.4, - 36.4, - 36.3, - 36.2, - 36.2, - 36.1, - 36.1, - 36.1, - 36, - 35.9, - 35.9, - 35.8, - 35.8, - 35.8, - 35.7, - 35.7, - 35.7, - 35.6, - 35.6, - 35.5, - 35.5, - 35.4, - 35.3, - 35.3, - 35.2, - 35.1, - 35, - 34.9, - 34.7, - 34.5, - 34.3, - 34.1, - 33.8, - 33.6, - 33.4, - 33.2, - 33.1, - 33.1, - 32.9, - 32.8, - 32.4, - 32, - 31.7, - 31.4, - 31.2, - 30.9, - 30.6, - 30.4, - 30.1, - 29.9, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.3, - 29.2, - 29.2, - 29.1, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.6, - 28.6, - 28.4, - 28.4, - 28.3, - 28.1, - 28.1, - 28.2, - 28.3, - 28.2, - 28.1, - 27.9, - 27.8, - 27.8, - 27.8, - 27.7, - 27.7, - 27.8, - 27.8, - 27.6, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.2, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 25.9, - 25.7, - 25.4, - 25.2, - 25, - 24.9, - 24.8, - 24.8, - 24.8, - 24.8, - 24.7, - 24.4, - 23.9, - 23.6, - 23.4, - 23.3, - 23.1, - 23, - 22.9, - 22.7, - 22.6, - 22.4, - 22.3, - 22.2, - 22.2, - 22.2, - 22.3, - 22.2, - 22.1, - 21.9, - 21.6, - 21.4, - 21.4, - 21.3, - 21.1, - 21.1, - 20.9, - 20.8, - 20.7, - 20.5, - 20.3, - 20.1, - 19.9, - 19.7, - 19.6, - 19.5, - 19.3, - 19.2, - 19.2, - 19.1, - 18.9, - 18.7, - 18.5, - 18.3, - 18.1, - 18, - 17.8, - 17.6, - 17.5, - 17.3, - 17.2, - 17, - 16.9, - 16.7, - 16.7, - 16.4, - 16.2, - 16, - 15.7, - 15.4, - 15.1, - 15, - 15, - 15, - 15, - 15, - 14.9, - 14.8, - 14.9, - 14.8, - 14.3, - 14, - 13.7, - 13.2, - 13, - 12.7, - 12.3, - 12, - 11.7, - 11.3, - 34.9, - 35.7, - 36.4, - 36.8, - 37.6, - 38.1, - 38.3, - 38.3, - 38.4, - 38.5, - 38.7, - 38.9, - 39, - 39.2, - 39.4, - 39.5, - 39.7, - 39.8, - 39.9, - 40, - 40, - 39.9, - 39.9, - 39.9, - 39.8, - 39.6, - 39.6, - 39.5, - 39.2, - 38.9, - 38.6, - 38.4, - 38.2, - 38, - 38, - 38, - 37.8, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37.2, - 37.1, - 37, - 36.9, - 36.9, - 36.8, - 36.8, - 36.7, - 36.7, - 36.7, - 36.6, - 36.5, - 36.5, - 36.4, - 36.3, - 36.3, - 36.2, - 36.2, - 36.2, - 36.1, - 36.1, - 36, - 36, - 35.9, - 35.9, - 35.8, - 35.8, - 35.8, - 35.7, - 35.7, - 35.7, - 35.7, - 35.6, - 35.5, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35, - 34.9, - 34.7, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.4, - 33.3, - 33.2, - 33.1, - 33, - 32.8, - 32.4, - 32.1, - 31.8, - 31.5, - 31.2, - 31, - 30.7, - 30.5, - 30.3, - 30, - 29.8, - 29.7, - 29.5, - 29.4, - 29.3, - 29.3, - 29.3, - 29.2, - 29.2, - 29.2, - 29.1, - 29.1, - 28.9, - 28.7, - 28.8, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.8, - 28.8, - 28.6, - 28.6, - 28.5, - 28.4, - 28.3, - 28.1, - 28, - 28.3, - 28.4, - 28.2, - 28.2, - 27.9, - 27.8, - 27.7, - 27.8, - 27.8, - 27.8, - 27.8, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.1, - 27, - 26.8, - 26.7, - 26.6, - 26.6, - 26.4, - 26.4, - 26.2, - 26, - 25.9, - 25.6, - 25.3, - 25.1, - 24.9, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.7, - 24.4, - 23.9, - 23.6, - 23.4, - 23.3, - 23.1, - 23, - 22.8, - 22.8, - 22.7, - 22.5, - 22.4, - 22.3, - 22.2, - 22.4, - 22.4, - 22.1, - 21.9, - 21.8, - 21.6, - 21.5, - 21.4, - 21.3, - 21.2, - 21.1, - 20.9, - 20.7, - 20.6, - 20.4, - 20.2, - 20, - 19.9, - 19.6, - 19.5, - 19.4, - 19.3, - 19.2, - 19.1, - 19, - 18.8, - 18.7, - 18.5, - 18.3, - 18.1, - 18, - 17.7, - 17.5, - 17.4, - 17.2, - 17.1, - 16.9, - 16.8, - 16.7, - 16.5, - 16.3, - 16.1, - 15.7, - 15.6, - 15.3, - 15, - 14.9, - 14.9, - 14.9, - 15, - 15, - 15, - 15.2, - 15, - 14.4, - 13.9, - 13.5, - 13.2, - 13, - 12.6, - 12.3, - 12, - 11.8, - 11.5, - 35.2, - 35.9, - 36.5, - 37, - 37.8, - 38.3, - 38.4, - 38.5, - 38.5, - 38.8, - 39, - 39.1, - 39.2, - 39.4, - 39.5, - 39.6, - 39.8, - 40, - 40.2, - 40.3, - 40, - 39.9, - 39.8, - 39.8, - 39.6, - 39.5, - 39.4, - 39.2, - 38.9, - 38.7, - 38.4, - 38.2, - 38.1, - 38.1, - 38, - 37.9, - 37.7, - 37.6, - 37.4, - 37.3, - 37.2, - 37.1, - 37.1, - 37, - 36.9, - 36.8, - 36.8, - 36.7, - 36.7, - 36.7, - 36.7, - 36.6, - 36.6, - 36.5, - 36.5, - 36.4, - 36.3, - 36.3, - 36.3, - 36.2, - 36.2, - 36.1, - 36.1, - 36.1, - 36, - 36, - 35.9, - 35.9, - 35.9, - 35.8, - 35.8, - 35.7, - 35.7, - 35.7, - 35.7, - 35.6, - 35.5, - 35.5, - 35.4, - 35.4, - 35.2, - 35, - 34.9, - 34.6, - 34.4, - 34.3, - 34.1, - 33.9, - 33.7, - 33.5, - 33.3, - 33.2, - 33.1, - 33.1, - 32.8, - 32.5, - 32.1, - 31.8, - 31.5, - 31.3, - 31, - 30.8, - 30.6, - 30.4, - 30.1, - 29.8, - 29.7, - 29.6, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29.2, - 29.2, - 29.1, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 28.6, - 28.7, - 28.7, - 28.7, - 28.7, - 28.8, - 28.9, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.2, - 28.2, - 28.3, - 28.4, - 28.2, - 28.2, - 28, - 27.8, - 27.7, - 27.8, - 27.8, - 27.8, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.8, - 25.5, - 25.3, - 25, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.7, - 24.7, - 24.5, - 24, - 23.7, - 23.5, - 23.3, - 23.1, - 22.9, - 22.9, - 22.9, - 22.9, - 22.7, - 22.4, - 22.3, - 22.3, - 22.4, - 22.2, - 22, - 21.9, - 21.8, - 21.7, - 21.5, - 21.5, - 21.4, - 21.3, - 21.1, - 20.9, - 20.8, - 20.6, - 20.3, - 20.1, - 20, - 19.8, - 19.5, - 19.4, - 19.4, - 19.3, - 19.2, - 19, - 18.9, - 18.8, - 18.7, - 18.4, - 18.3, - 18.1, - 17.8, - 17.6, - 17.5, - 17.3, - 17.2, - 17.1, - 16.9, - 16.8, - 16.6, - 16.4, - 16.2, - 15.8, - 15.7, - 15.5, - 15.2, - 15, - 14.9, - 14.9, - 14.9, - 14.9, - 14.9, - 15.1, - 15, - 14.5, - 13.9, - 13.6, - 13.3, - 12.9, - 12.6, - 12.3, - 12.1, - 11.8, - 11.6, - 35.5, - 36.2, - 36.8, - 37.4, - 37.9, - 38.4, - 38.8, - 38.9, - 38.9, - 39.1, - 39.3, - 39.4, - 39.4, - 39.5, - 39.6, - 39.7, - 39.8, - 40, - 40.2, - 40.1, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.3, - 39.1, - 38.9, - 38.7, - 38.5, - 38.3, - 38.2, - 38.1, - 38.1, - 38, - 37.8, - 37.6, - 37.5, - 37.4, - 37.3, - 37.1, - 37.1, - 37, - 36.9, - 36.9, - 36.8, - 36.8, - 36.8, - 36.8, - 36.7, - 36.7, - 36.7, - 36.7, - 36.6, - 36.5, - 36.4, - 36.4, - 36.3, - 36.3, - 36.2, - 36.2, - 36.2, - 36.1, - 36.1, - 36.1, - 36, - 36, - 35.9, - 35.9, - 35.8, - 35.8, - 35.8, - 35.7, - 35.7, - 35.7, - 35.7, - 35.6, - 35.5, - 35.5, - 35.4, - 35.3, - 35.1, - 34.9, - 34.7, - 34.5, - 34.3, - 34.1, - 34, - 33.7, - 33.5, - 33.4, - 33.2, - 33.1, - 33, - 32.8, - 32.6, - 32.2, - 31.9, - 31.6, - 31.3, - 31.1, - 30.9, - 30.7, - 30.5, - 30.1, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29, - 28.9, - 28.9, - 28.7, - 28.6, - 28.5, - 28.5, - 28.6, - 28.6, - 28.7, - 28.7, - 28.7, - 28.8, - 28.8, - 28.6, - 28.5, - 28.5, - 28.5, - 28.4, - 28.3, - 28.3, - 28.4, - 28.2, - 28.2, - 28, - 27.9, - 27.8, - 27.8, - 27.8, - 27.9, - 28, - 27.8, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.4, - 27.4, - 27.2, - 27, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.3, - 26.2, - 26, - 25.7, - 25.4, - 25.1, - 24.9, - 24.8, - 24.8, - 24.8, - 24.7, - 24.7, - 24.7, - 24.7, - 24.5, - 24.1, - 23.8, - 23.6, - 23.3, - 23.1, - 23, - 23.2, - 23.2, - 22.9, - 22.6, - 22.4, - 22.3, - 22.3, - 22.2, - 22, - 21.9, - 21.8, - 21.8, - 21.7, - 21.5, - 21.5, - 21.4, - 21.2, - 21, - 20.9, - 20.7, - 20.5, - 20.3, - 20.1, - 19.9, - 19.6, - 19.5, - 19.5, - 19.4, - 19.3, - 19.1, - 19, - 18.9, - 18.7, - 18.6, - 18.4, - 18.1, - 18, - 17.7, - 17.6, - 17.5, - 17.3, - 17.1, - 16.9, - 16.7, - 16.6, - 16.5, - 16.2, - 15.9, - 15.8, - 15.7, - 15.6, - 15.3, - 15.1, - 14.9, - 14.9, - 14.9, - 15, - 15, - 15, - 14.5, - 13.9, - 13.7, - 13.3, - 13, - 12.6, - 12.4, - 12.1, - 11.9, - 11.7, - 35.8, - 36.5, - 37.1, - 37.7, - 38.1, - 38.5, - 38.8, - 39, - 39.2, - 39.4, - 39.5, - 39.7, - 39.8, - 39.9, - 39.9, - 39.8, - 39.9, - 39.9, - 39.9, - 39.9, - 39.8, - 39.6, - 39.6, - 39.6, - 39.3, - 39.1, - 38.9, - 38.8, - 38.6, - 38.4, - 38.2, - 38.2, - 38.1, - 38, - 37.9, - 37.7, - 37.6, - 37.5, - 37.3, - 37.2, - 37.1, - 37, - 37, - 36.9, - 36.9, - 36.9, - 36.9, - 36.9, - 36.9, - 36.8, - 36.8, - 36.8, - 36.7, - 36.6, - 36.5, - 36.5, - 36.4, - 36.4, - 36.3, - 36.3, - 36.3, - 36.2, - 36.2, - 36.1, - 36.1, - 36.1, - 36, - 36, - 36, - 35.9, - 35.9, - 35.8, - 35.8, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.5, - 35.5, - 35.3, - 35.1, - 34.9, - 34.8, - 34.7, - 34.4, - 34.2, - 34, - 33.7, - 33.5, - 33.4, - 33.2, - 33, - 32.9, - 32.9, - 32.7, - 32.4, - 32, - 31.7, - 31.4, - 31.2, - 31, - 30.8, - 30.6, - 30.2, - 29.9, - 29.8, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29.1, - 29, - 29, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.5, - 28.6, - 28.7, - 28.7, - 28.7, - 28.8, - 28.6, - 28.5, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.2, - 28.2, - 28, - 28, - 27.9, - 27.8, - 28, - 28, - 28, - 27.9, - 27.7, - 27.7, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.4, - 27.3, - 27.2, - 27.1, - 27.1, - 26.9, - 26.7, - 26.6, - 26.4, - 26.4, - 26.2, - 25.9, - 25.6, - 25.3, - 25, - 24.9, - 24.8, - 24.8, - 24.7, - 24.7, - 24.7, - 24.7, - 24.7, - 24.4, - 23.9, - 23.7, - 23.4, - 23.3, - 23.3, - 23.3, - 23.2, - 23, - 22.8, - 22.6, - 22.4, - 22.3, - 22.3, - 22.1, - 21.9, - 21.9, - 21.9, - 21.8, - 21.7, - 21.6, - 21.5, - 21.3, - 21.1, - 20.9, - 20.8, - 20.6, - 20.4, - 20.1, - 20, - 19.7, - 19.6, - 19.6, - 19.6, - 19.4, - 19.1, - 19, - 19, - 18.8, - 18.7, - 18.5, - 18.3, - 18.1, - 17.9, - 17.6, - 17.5, - 17.3, - 17.1, - 16.9, - 16.8, - 16.7, - 16.5, - 16.3, - 16, - 15.9, - 15.8, - 15.7, - 15.5, - 15.4, - 15.1, - 15, - 15.1, - 15, - 15, - 14.8, - 14.3, - 14, - 13.7, - 13.4, - 13.1, - 12.7, - 12.5, - 12.2, - 11.9, - 11.9, - 36, - 36.8, - 37.4, - 38, - 38.4, - 38.7, - 39, - 39.2, - 39.4, - 39.6, - 39.8, - 40, - 40.1, - 40.1, - 40.1, - 39.9, - 39.8, - 39.8, - 39.7, - 39.8, - 39.7, - 39.5, - 39.5, - 39.3, - 39.1, - 39, - 38.8, - 38.7, - 38.4, - 38.3, - 38.2, - 38.2, - 38.1, - 38, - 37.9, - 37.7, - 37.6, - 37.5, - 37.3, - 37.2, - 37.1, - 37.1, - 37, - 37, - 37, - 37, - 37, - 37, - 37, - 36.9, - 36.9, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.5, - 36.5, - 36.4, - 36.4, - 36.3, - 36.3, - 36.2, - 36.2, - 36.2, - 36.1, - 36.1, - 36.1, - 36.1, - 36, - 36, - 36, - 35.9, - 35.8, - 35.8, - 35.7, - 35.6, - 35.5, - 35.7, - 35.6, - 35.3, - 35.1, - 34.9, - 34.8, - 34.7, - 34.5, - 34.2, - 34, - 33.7, - 33.6, - 33.4, - 33.2, - 33.1, - 33, - 32.9, - 32.7, - 32.4, - 32.1, - 31.8, - 31.6, - 31.3, - 31.1, - 30.8, - 30.6, - 30.3, - 30, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29.1, - 29.1, - 29.1, - 28.9, - 28.6, - 28.5, - 28.4, - 28.5, - 28.6, - 28.7, - 28.7, - 28.8, - 28.7, - 28.7, - 28.7, - 28.5, - 28.3, - 28.2, - 28.3, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 27.8, - 27.7, - 27.7, - 27.7, - 27.6, - 27.6, - 27.5, - 27.3, - 27.4, - 27.4, - 27.3, - 27.3, - 27.1, - 26.9, - 26.8, - 26.5, - 26.4, - 26.3, - 26.1, - 25.9, - 25.5, - 25.2, - 25, - 24.9, - 24.8, - 24.7, - 24.7, - 24.7, - 24.7, - 24.8, - 24.5, - 24.1, - 23.8, - 23.6, - 23.5, - 23.5, - 23.5, - 23.3, - 23.1, - 22.9, - 22.8, - 22.6, - 22.4, - 22.2, - 22.2, - 22.2, - 22.1, - 22, - 21.8, - 21.8, - 21.7, - 21.6, - 21.4, - 21.1, - 21, - 20.8, - 20.7, - 20.4, - 20.2, - 20, - 19.8, - 19.7, - 19.6, - 19.6, - 19.4, - 19.2, - 19.2, - 19, - 18.9, - 18.7, - 18.5, - 18.6, - 18.5, - 18.2, - 17.8, - 17.5, - 17.3, - 17.2, - 17, - 16.8, - 16.8, - 16.7, - 16.3, - 16.1, - 16, - 15.9, - 15.8, - 15.6, - 15.5, - 15.3, - 15.2, - 15.2, - 15.1, - 15, - 14.7, - 14.3, - 14, - 13.7, - 13.4, - 13.2, - 12.8, - 12.5, - 12.3, - 12.1, - 12, - 36.4, - 37.1, - 37.7, - 38.4, - 38.8, - 39.1, - 39.3, - 39.5, - 39.7, - 39.8, - 39.9, - 40.1, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.6, - 39.6, - 39.5, - 39.3, - 39.2, - 39.1, - 39, - 38.9, - 38.6, - 38.4, - 38.3, - 38.3, - 38.3, - 38.2, - 38.1, - 38, - 37.9, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37.1, - 37.1, - 37.1, - 37.1, - 37.1, - 37.1, - 37.1, - 37.1, - 37, - 37, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.6, - 36.6, - 36.5, - 36.5, - 36.4, - 36.4, - 36.3, - 36.3, - 36.3, - 36.2, - 36.2, - 36.2, - 36.1, - 36.1, - 36, - 36, - 35.9, - 35.8, - 35.8, - 35.7, - 35.6, - 35.6, - 35.6, - 35.5, - 35.2, - 35.1, - 34.9, - 34.8, - 34.7, - 34.5, - 34.3, - 34, - 33.8, - 33.6, - 33.4, - 33.2, - 33.1, - 33, - 32.9, - 32.7, - 32.4, - 32.1, - 31.8, - 31.6, - 31.4, - 31.2, - 30.9, - 30.6, - 30.3, - 30, - 29.8, - 29.7, - 29.7, - 29.6, - 29.5, - 29.4, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29.2, - 29.2, - 29.2, - 29.1, - 28.8, - 28.6, - 28.5, - 28.5, - 28.6, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.5, - 28.4, - 28.3, - 28.4, - 28.4, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28.2, - 28.2, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.7, - 27.7, - 27.7, - 27.7, - 27.6, - 27.4, - 27.3, - 27.3, - 27.3, - 27.3, - 27.2, - 26.9, - 26.9, - 26.7, - 26.5, - 26.4, - 26.2, - 26, - 25.8, - 25.4, - 25.1, - 24.9, - 24.9, - 24.8, - 24.7, - 24.7, - 24.7, - 24.8, - 24.7, - 24.3, - 24, - 23.8, - 23.7, - 23.6, - 23.6, - 23.4, - 23.2, - 22.9, - 22.9, - 22.8, - 22.6, - 22.4, - 22.3, - 22.2, - 22.2, - 22.1, - 21.9, - 22, - 21.8, - 21.7, - 21.4, - 21.2, - 21.1, - 21, - 20.7, - 20.5, - 20.3, - 20.1, - 19.9, - 19.8, - 19.7, - 19.6, - 19.4, - 19.3, - 19.2, - 19.1, - 19, - 18.8, - 18.6, - 18.7, - 18.5, - 18.3, - 18.1, - 17.8, - 17.5, - 17.2, - 17.1, - 16.9, - 16.8, - 16.8, - 16.5, - 16.3, - 16.2, - 16, - 15.9, - 15.8, - 15.7, - 15.7, - 15.5, - 15.4, - 15.3, - 15.1, - 14.8, - 14.4, - 14.1, - 13.8, - 13.5, - 13.2, - 12.8, - 12.6, - 12.4, - 12.1, - 12, - 36.6, - 37.3, - 38, - 38.6, - 39, - 39.4, - 39.7, - 39.9, - 39.9, - 40, - 40.4, - 40.4, - 40.1, - 39.9, - 39.8, - 39.7, - 39.6, - 39.6, - 39.5, - 39.4, - 39.3, - 39.2, - 39.1, - 39, - 38.9, - 38.7, - 38.4, - 38.4, - 38.4, - 38.4, - 38.3, - 38.2, - 38.1, - 38, - 37.9, - 37.8, - 37.6, - 37.5, - 37.4, - 37.4, - 37.3, - 37.2, - 37.2, - 37.2, - 37.2, - 37.2, - 37.1, - 37.2, - 37.2, - 37.1, - 37.1, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.7, - 36.8, - 36.6, - 36.6, - 36.5, - 36.5, - 36.5, - 36.4, - 36.4, - 36.3, - 36.3, - 36.3, - 36.2, - 36.2, - 36.1, - 36, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.5, - 35.4, - 35.2, - 35.1, - 35, - 34.8, - 34.7, - 34.6, - 34.3, - 34.1, - 33.9, - 33.8, - 33.6, - 33.4, - 33.2, - 33.1, - 32.9, - 32.7, - 32.4, - 32.2, - 31.9, - 31.7, - 31.5, - 31.2, - 31, - 30.7, - 30.3, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.5, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29, - 28.7, - 28.6, - 28.5, - 28.6, - 28.7, - 28.7, - 28.8, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 28.2, - 28.2, - 28.2, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.7, - 27.7, - 27.7, - 27.7, - 27.6, - 27.4, - 27.3, - 27.3, - 27.2, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.4, - 26.1, - 25.9, - 25.7, - 25.4, - 25.1, - 24.9, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.8, - 24.5, - 24.2, - 24, - 23.9, - 23.8, - 23.6, - 23.6, - 23.4, - 23.1, - 22.9, - 22.8, - 22.8, - 22.7, - 22.6, - 22.4, - 22.4, - 22.3, - 22.2, - 22.1, - 22, - 21.7, - 21.5, - 21.4, - 21.3, - 21.1, - 20.8, - 20.5, - 20.4, - 20.2, - 20.1, - 19.9, - 19.8, - 19.6, - 19.4, - 19.4, - 19.3, - 19.1, - 19.1, - 18.9, - 18.7, - 18.6, - 18.5, - 18.3, - 18.2, - 18, - 17.9, - 17.4, - 17, - 16.9, - 16.9, - 16.8, - 16.7, - 16.5, - 16.4, - 16.2, - 16, - 15.9, - 15.8, - 15.9, - 15.8, - 15.6, - 15.4, - 15.3, - 15, - 14.5, - 14.2, - 14, - 13.7, - 13.4, - 12.9, - 12.6, - 12.4, - 12.2, - 12, - 36.8, - 37.4, - 38.1, - 38.7, - 39.1, - 39.5, - 39.8, - 40, - 40, - 40.2, - 40.8, - 40.4, - 40.1, - 39.9, - 39.7, - 39.6, - 39.5, - 39.4, - 39.3, - 39.2, - 39.2, - 39.1, - 39, - 38.8, - 38.6, - 38.5, - 38.4, - 38.5, - 38.5, - 38.4, - 38.3, - 38.2, - 38.2, - 38, - 37.9, - 37.8, - 37.7, - 37.6, - 37.5, - 37.4, - 37.4, - 37.3, - 37.3, - 37.3, - 37.3, - 37.3, - 37.2, - 37.3, - 37.3, - 37.2, - 37.2, - 37.1, - 37.1, - 37, - 36.9, - 36.9, - 36.9, - 36.9, - 36.8, - 36.7, - 36.7, - 36.7, - 36.6, - 36.6, - 36.5, - 36.4, - 36.4, - 36.3, - 36.3, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.4, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.7, - 34.5, - 34.2, - 34.1, - 33.9, - 33.8, - 33.6, - 33.4, - 33.2, - 33, - 32.8, - 32.6, - 32.3, - 32, - 31.8, - 31.5, - 31.3, - 31, - 30.7, - 30.4, - 30.2, - 30, - 29.9, - 29.9, - 29.7, - 29.6, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.2, - 29.3, - 29.3, - 29.2, - 28.9, - 28.7, - 28.6, - 28.6, - 28.6, - 28.7, - 28.8, - 29, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 28.5, - 28.4, - 28.5, - 28.5, - 28.4, - 28.3, - 28.2, - 28.2, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.7, - 27.6, - 27.6, - 27.5, - 27.4, - 27.4, - 27.4, - 27.3, - 27.1, - 27, - 27, - 26.9, - 26.6, - 26.5, - 26.3, - 26.1, - 25.9, - 25.6, - 25.2, - 25, - 24.9, - 24.8, - 24.9, - 24.9, - 24.9, - 24.8, - 24.6, - 24.4, - 24.1, - 24.1, - 24, - 23.8, - 23.6, - 23.5, - 23.3, - 23, - 22.8, - 22.7, - 22.7, - 22.7, - 22.7, - 22.7, - 22.5, - 22.3, - 22.2, - 22.1, - 21.9, - 21.6, - 21.4, - 21.4, - 21.2, - 20.9, - 20.6, - 20.5, - 20.3, - 20.1, - 20, - 19.8, - 19.7, - 19.5, - 19.5, - 19.4, - 19.1, - 19, - 18.9, - 18.8, - 18.7, - 18.6, - 18.4, - 18.3, - 18.1, - 18, - 17.7, - 17.2, - 17, - 16.9, - 16.7, - 16.7, - 16.7, - 16.4, - 16.3, - 16.1, - 16, - 15.9, - 15.9, - 15.9, - 15.8, - 15.6, - 15.4, - 15.1, - 14.6, - 14.3, - 14.1, - 13.8, - 13.5, - 13.1, - 12.6, - 12.4, - 12.2, - 12, - 37, - 37.6, - 38.2, - 38.8, - 39.2, - 39.5, - 39.8, - 39.9, - 40, - 40, - 40.3, - 40.2, - 40, - 39.8, - 39.7, - 39.5, - 39.4, - 39.3, - 39.2, - 39, - 39, - 38.9, - 38.8, - 38.6, - 38.5, - 38.5, - 38.5, - 38.5, - 38.5, - 38.4, - 38.4, - 38.3, - 38.2, - 38.1, - 38, - 37.9, - 37.8, - 37.7, - 37.6, - 37.6, - 37.5, - 37.5, - 37.4, - 37.5, - 37.5, - 37.4, - 37.4, - 37.4, - 37.5, - 37.4, - 37.3, - 37.3, - 37.2, - 37.1, - 37.1, - 37, - 37, - 36.9, - 36.9, - 36.9, - 36.9, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.5, - 36.4, - 36.4, - 36.4, - 36.4, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.5, - 35.4, - 35.2, - 35.1, - 35.1, - 34.9, - 34.8, - 34.8, - 34.6, - 34.4, - 34.2, - 34.1, - 34, - 33.8, - 33.5, - 33.3, - 33.1, - 32.8, - 32.7, - 32.4, - 32.2, - 31.9, - 31.6, - 31.3, - 31.1, - 30.7, - 30.5, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.7, - 29.6, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.3, - 29.4, - 29.3, - 29.2, - 29.1, - 28.9, - 28.7, - 28.6, - 28.6, - 28.7, - 28.7, - 28.9, - 28.8, - 28.7, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.8, - 27.7, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.4, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.7, - 26.5, - 26.2, - 26.1, - 25.8, - 25.6, - 25.2, - 25, - 24.9, - 24.9, - 24.9, - 25, - 24.9, - 24.7, - 24.5, - 24.4, - 24.2, - 24.1, - 24, - 23.7, - 23.6, - 23.4, - 23.2, - 22.9, - 22.7, - 22.7, - 22.7, - 22.7, - 22.8, - 22.7, - 22.5, - 22.3, - 22.1, - 21.9, - 21.7, - 21.5, - 21.4, - 21.3, - 20.9, - 20.7, - 20.6, - 20.5, - 20.2, - 20.1, - 19.8, - 19.6, - 19.6, - 19.5, - 19.4, - 19.2, - 19, - 18.9, - 18.8, - 18.8, - 18.7, - 18.5, - 18.4, - 18.2, - 17.9, - 17.8, - 17.4, - 17.1, - 17, - 16.8, - 16.7, - 16.7, - 16.5, - 16.3, - 16.2, - 16.1, - 16, - 16, - 16, - 15.8, - 15.8, - 15.6, - 15.4, - 14.8, - 14.5, - 14.3, - 14, - 13.6, - 13.2, - 12.8, - 12.5, - 12.2, - 12.1, - 37.1, - 37.8, - 38.4, - 38.9, - 39.2, - 39.5, - 39.7, - 39.8, - 39.9, - 39.9, - 39.9, - 39.8, - 39.8, - 39.6, - 39.5, - 39.4, - 39.2, - 39.1, - 39, - 38.9, - 38.8, - 38.7, - 38.6, - 38.5, - 38.5, - 38.5, - 38.5, - 38.5, - 38.5, - 38.5, - 38.5, - 38.4, - 38.3, - 38.2, - 38.1, - 38.1, - 37.9, - 37.8, - 37.7, - 37.7, - 37.6, - 37.6, - 37.6, - 37.6, - 37.7, - 37.6, - 37.6, - 37.6, - 37.6, - 37.5, - 37.4, - 37.4, - 37.3, - 37.4, - 37.5, - 37.3, - 37.1, - 37.1, - 37.1, - 37.2, - 37.2, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.6, - 36.5, - 36.5, - 36.4, - 36.4, - 36.2, - 36.1, - 36, - 35.8, - 35.7, - 35.6, - 35.5, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.7, - 34.5, - 34.4, - 34.2, - 34.1, - 33.9, - 33.6, - 33.3, - 33.1, - 32.9, - 32.7, - 32.5, - 32.3, - 32, - 31.7, - 31.4, - 31.1, - 30.8, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 30.1, - 30, - 29.9, - 29.9, - 29.8, - 29.7, - 29.7, - 29.7, - 29.6, - 29.6, - 29.6, - 29.6, - 29.6, - 29.5, - 29.3, - 29.3, - 29.2, - 29.1, - 28.9, - 28.7, - 28.7, - 28.7, - 28.8, - 29, - 28.8, - 28.6, - 28.6, - 28.7, - 28.6, - 28.5, - 28.5, - 28.5, - 28.5, - 28.5, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.6, - 27.5, - 27.5, - 27.4, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.7, - 26.5, - 26.4, - 26.2, - 26, - 25.8, - 25.6, - 25.3, - 25.1, - 25, - 25, - 24.9, - 25, - 24.8, - 24.7, - 24.5, - 24.4, - 24.2, - 24.1, - 23.9, - 23.7, - 23.5, - 23.4, - 23.1, - 22.8, - 22.7, - 22.7, - 22.8, - 22.7, - 22.7, - 22.8, - 22.5, - 22.2, - 22, - 21.7, - 21.5, - 21.5, - 21.3, - 21, - 20.7, - 20.7, - 20.6, - 20.3, - 20.1, - 19.9, - 19.7, - 19.6, - 19.5, - 19.4, - 19.3, - 19.1, - 19, - 18.9, - 18.9, - 18.8, - 18.7, - 18.5, - 18.2, - 18.1, - 17.9, - 17.7, - 17.3, - 17.1, - 16.9, - 16.7, - 16.6, - 16.6, - 16.6, - 16.4, - 16.3, - 16.2, - 16.2, - 16.1, - 15.9, - 16, - 16, - 15.8, - 15.2, - 14.7, - 14.4, - 14.2, - 13.8, - 13.4, - 13, - 12.7, - 12.4, - 12.2, - 37.3, - 38, - 38.5, - 38.9, - 39.3, - 39.5, - 39.6, - 39.8, - 39.9, - 39.9, - 39.9, - 39.8, - 39.7, - 39.5, - 39.4, - 39.2, - 39.1, - 39, - 38.9, - 38.8, - 38.7, - 38.6, - 38.6, - 38.6, - 38.6, - 38.5, - 38.5, - 38.6, - 38.6, - 38.6, - 38.5, - 38.5, - 38.4, - 38.3, - 38.2, - 38.1, - 38.1, - 37.9, - 37.9, - 37.8, - 37.8, - 37.8, - 37.8, - 37.8, - 37.8, - 37.8, - 37.8, - 37.7, - 37.7, - 37.6, - 37.5, - 37.6, - 37.5, - 37.7, - 37.7, - 37.5, - 37.4, - 37.5, - 37.4, - 37.3, - 37.3, - 37.2, - 37, - 36.9, - 36.8, - 36.7, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.6, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.6, - 34.5, - 34.3, - 34.2, - 34, - 33.7, - 33.4, - 33.2, - 32.9, - 32.8, - 32.6, - 32.3, - 32, - 31.7, - 31.4, - 31.1, - 30.9, - 30.8, - 30.6, - 30.5, - 30.4, - 30.3, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.9, - 29.8, - 29.8, - 29.7, - 29.7, - 29.7, - 29.7, - 29.6, - 29.5, - 29.5, - 29.3, - 29.3, - 29.2, - 29.1, - 28.9, - 28.8, - 28.8, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 28.6, - 28.5, - 28.5, - 28.6, - 28.6, - 28.6, - 28.5, - 28.5, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.2, - 27.1, - 27.1, - 26.9, - 26.8, - 26.6, - 26.5, - 26.3, - 26.1, - 26, - 25.9, - 25.6, - 25.4, - 25.2, - 25.1, - 25, - 25, - 25, - 24.8, - 24.6, - 24.5, - 24.3, - 24.2, - 24.1, - 23.8, - 23.6, - 23.5, - 23.3, - 23, - 22.8, - 22.8, - 22.9, - 22.8, - 22.8, - 22.8, - 22.7, - 22.4, - 22.1, - 21.9, - 21.7, - 21.6, - 21.4, - 21.2, - 20.9, - 20.7, - 20.6, - 20.4, - 20.2, - 20, - 19.8, - 19.7, - 19.6, - 19.5, - 19.3, - 19.2, - 19.1, - 19, - 18.9, - 18.8, - 18.8, - 18.6, - 18.3, - 18.2, - 18, - 17.8, - 17.4, - 17.2, - 17.1, - 16.7, - 16.6, - 16.5, - 16.5, - 16.4, - 16.3, - 16.3, - 16.3, - 16.3, - 16.2, - 16.2, - 16.1, - 15.9, - 15.7, - 15.1, - 14.7, - 14.3, - 14, - 13.6, - 13.3, - 12.9, - 12.7, - 12.6, - 37.6, - 38.2, - 38.6, - 39, - 39.3, - 39.4, - 39.6, - 39.7, - 39.8, - 39.9, - 39.9, - 39.8, - 39.6, - 39.5, - 39.3, - 39.2, - 39, - 38.9, - 38.8, - 38.7, - 38.7, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.7, - 38.7, - 38.7, - 38.6, - 38.4, - 38.3, - 38.3, - 38.2, - 38.1, - 38.1, - 38.1, - 38.1, - 38, - 37.9, - 37.9, - 38, - 38, - 38, - 38, - 37.9, - 37.9, - 37.7, - 37.6, - 37.6, - 37.6, - 37.7, - 37.7, - 37.6, - 37.4, - 37.5, - 37.4, - 37.3, - 37.3, - 37.2, - 37.1, - 37, - 36.9, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35.1, - 35, - 34.8, - 34.7, - 34.6, - 34.4, - 34.2, - 34, - 33.8, - 33.6, - 33.3, - 33.1, - 32.8, - 32.6, - 32.4, - 32.1, - 31.8, - 31.4, - 31.2, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.5, - 30.4, - 30.3, - 30.2, - 30.1, - 30.1, - 30, - 30, - 29.9, - 29.8, - 29.7, - 29.7, - 29.6, - 29.6, - 29.5, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 29, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 28.6, - 28.6, - 28.5, - 28.6, - 28.6, - 28.6, - 28.6, - 28.5, - 28.5, - 28.5, - 28.4, - 28.5, - 28.4, - 28.3, - 28.3, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.7, - 27.7, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.6, - 26.5, - 26.3, - 26.1, - 26, - 25.9, - 25.6, - 25.6, - 25.4, - 25.1, - 25, - 25, - 24.9, - 24.7, - 24.5, - 24.4, - 24.3, - 24.2, - 23.9, - 23.8, - 23.6, - 23.5, - 23.5, - 23.3, - 23.2, - 23, - 22.9, - 23, - 23.1, - 22.9, - 22.6, - 22.2, - 22, - 21.9, - 21.8, - 21.6, - 21.4, - 21.1, - 20.8, - 20.6, - 20.5, - 20.3, - 20.1, - 19.9, - 19.8, - 19.7, - 19.6, - 19.5, - 19.4, - 19.3, - 19.1, - 19.1, - 18.9, - 18.8, - 18.7, - 18.5, - 18.3, - 18.1, - 17.8, - 17.6, - 17.4, - 17.2, - 16.9, - 16.7, - 16.5, - 16.4, - 16.3, - 16.2, - 16.2, - 16.1, - 16.1, - 16, - 16, - 16, - 16, - 15.8, - 15.4, - 15, - 14.7, - 14.2, - 13.8, - 13.6, - 13.1, - 12.9, - 12.8, - 37.8, - 38.3, - 38.7, - 39, - 39.3, - 39.4, - 39.5, - 39.6, - 39.8, - 39.8, - 39.8, - 39.7, - 39.6, - 39.4, - 39.2, - 39, - 38.9, - 38.8, - 38.7, - 38.7, - 38.6, - 38.7, - 38.7, - 38.7, - 38.6, - 38.7, - 38.7, - 38.7, - 38.7, - 38.7, - 38.8, - 38.6, - 38.5, - 38.4, - 38.4, - 38.3, - 38.2, - 38.2, - 38.2, - 38.1, - 38.1, - 38.1, - 38.1, - 38.2, - 38.2, - 38.1, - 38.1, - 38, - 37.9, - 37.9, - 37.8, - 37.7, - 37.7, - 37.7, - 37.7, - 37.6, - 37.7, - 37.7, - 37.5, - 37.4, - 37.3, - 37.2, - 37.1, - 37, - 37, - 36.9, - 36.9, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.8, - 35.8, - 35.7, - 35.6, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35, - 34.9, - 34.8, - 34.6, - 34.5, - 34.3, - 34.1, - 33.8, - 33.6, - 33.3, - 33.1, - 32.9, - 32.7, - 32.4, - 32.2, - 31.8, - 31.6, - 31.4, - 31.2, - 31.1, - 31, - 30.9, - 30.9, - 30.8, - 30.7, - 30.5, - 30.5, - 30.4, - 30.3, - 30.2, - 30.2, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.6, - 29.5, - 29.3, - 29.3, - 29.2, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.7, - 28.7, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 26.9, - 26.7, - 26.6, - 26.4, - 26.3, - 26.1, - 26, - 25.8, - 25.7, - 25.7, - 25.5, - 25.2, - 25, - 25, - 24.8, - 24.7, - 24.5, - 24.4, - 24.3, - 24.1, - 24, - 23.9, - 23.6, - 23.5, - 23.4, - 23.5, - 23.4, - 23.1, - 23, - 23, - 23, - 22.8, - 22.5, - 22.2, - 22, - 21.9, - 21.7, - 21.4, - 21.2, - 21, - 20.8, - 20.6, - 20.4, - 20.2, - 20, - 19.9, - 19.8, - 19.7, - 19.6, - 19.5, - 19.4, - 19.2, - 19.2, - 19, - 18.9, - 18.8, - 18.6, - 18.4, - 18.1, - 17.9, - 17.8, - 17.6, - 17.3, - 17, - 16.8, - 16.6, - 16.5, - 16.3, - 16.2, - 16.1, - 16.1, - 16, - 16, - 16, - 16, - 16.2, - 15.8, - 15.5, - 15.2, - 14.9, - 14.4, - 14, - 13.7, - 13.4, - 13.2, - 13, - 37.9, - 38.3, - 38.8, - 39.1, - 39.3, - 39.4, - 39.5, - 39.6, - 39.6, - 39.6, - 39.6, - 39.5, - 39.4, - 39.2, - 39, - 38.9, - 38.8, - 38.8, - 38.7, - 38.7, - 38.7, - 38.7, - 38.8, - 38.8, - 38.7, - 38.8, - 38.8, - 38.8, - 38.7, - 38.7, - 38.7, - 38.6, - 38.5, - 38.5, - 38.4, - 38.3, - 38.3, - 38.2, - 38.3, - 38.3, - 38.3, - 38.3, - 38.3, - 38.4, - 38.5, - 38.5, - 38.3, - 38.1, - 38.1, - 38, - 38, - 37.9, - 37.9, - 37.8, - 37.8, - 37.7, - 37.7, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.5, - 35.4, - 35.4, - 35.3, - 35.1, - 34.9, - 34.8, - 34.7, - 34.5, - 34.4, - 34.2, - 33.9, - 33.6, - 33.4, - 33.2, - 32.9, - 32.7, - 32.5, - 32.2, - 32, - 31.7, - 31.6, - 31.5, - 31.4, - 31.4, - 31.3, - 31.1, - 31, - 30.9, - 30.7, - 30.6, - 30.6, - 30.5, - 30.4, - 30.3, - 30.3, - 30.3, - 30.2, - 30.1, - 29.9, - 29.8, - 29.7, - 29.7, - 29.6, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.8, - 28.7, - 28.6, - 28.6, - 28.6, - 28.6, - 28.6, - 28.7, - 28.8, - 28.8, - 28.7, - 28.6, - 28.6, - 28.5, - 28.4, - 28.5, - 28.4, - 28.4, - 28.3, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.4, - 27.3, - 27.2, - 27.2, - 27, - 26.8, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.9, - 25.9, - 25.7, - 25.6, - 25.4, - 25.2, - 25.1, - 25, - 24.8, - 24.6, - 24.5, - 24.4, - 24.3, - 24, - 24, - 24, - 23.7, - 23.5, - 23.4, - 23.4, - 23.3, - 23.3, - 23.2, - 23.1, - 23, - 22.7, - 22.4, - 22.1, - 22, - 21.8, - 21.5, - 21.3, - 21.1, - 20.9, - 20.7, - 20.5, - 20.4, - 20.2, - 20, - 19.9, - 19.8, - 19.8, - 19.7, - 19.5, - 19.3, - 19.2, - 19.1, - 19, - 18.9, - 18.8, - 18.5, - 18.3, - 18.1, - 17.9, - 17.8, - 17.5, - 17.2, - 16.9, - 16.7, - 16.5, - 16.4, - 16.3, - 16.2, - 16.2, - 16.1, - 16, - 16.1, - 16.2, - 16, - 15.7, - 15.6, - 15.3, - 15.1, - 14.7, - 14.3, - 13.9, - 13.6, - 13.4, - 13.3, - 37.9, - 38.4, - 38.8, - 39.1, - 39.3, - 39.4, - 39.5, - 39.6, - 39.6, - 39.4, - 39.2, - 39.2, - 39.2, - 39.1, - 39, - 38.9, - 38.8, - 38.8, - 38.8, - 38.8, - 38.8, - 38.8, - 38.9, - 38.8, - 38.9, - 38.9, - 38.9, - 38.9, - 38.8, - 38.8, - 38.7, - 38.7, - 38.6, - 38.6, - 38.5, - 38.4, - 38.4, - 38.4, - 38.4, - 38.4, - 38.4, - 38.4, - 38.5, - 38.6, - 38.7, - 38.7, - 38.6, - 38.3, - 38.2, - 38.2, - 38.1, - 38, - 38, - 38, - 37.9, - 37.9, - 37.8, - 37.7, - 37.7, - 37.6, - 37.5, - 37.4, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.3, - 36.2, - 36.1, - 36.1, - 36, - 35.9, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.5, - 35.5, - 35.4, - 35.2, - 34.9, - 34.9, - 34.7, - 34.6, - 34.4, - 34.2, - 34, - 33.7, - 33.5, - 33.3, - 33.1, - 32.8, - 32.6, - 32.3, - 32.1, - 31.9, - 31.8, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.4, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.7, - 29.6, - 29.4, - 29.4, - 29.3, - 29.2, - 29, - 29, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.6, - 28.6, - 28.7, - 28.7, - 28.6, - 28.6, - 28.6, - 28.6, - 28.5, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 26.9, - 26.8, - 26.6, - 26.5, - 26.3, - 26.1, - 26, - 26, - 25.9, - 25.7, - 25.6, - 25.4, - 25.3, - 25.2, - 25.1, - 24.8, - 24.7, - 24.6, - 24.5, - 24.2, - 24, - 23.9, - 23.8, - 23.6, - 23.5, - 23.4, - 23.3, - 23.3, - 23.3, - 23.2, - 23.3, - 23, - 22.5, - 22.2, - 22.1, - 21.9, - 21.7, - 21.4, - 21.2, - 21, - 20.9, - 20.7, - 20.6, - 20.3, - 20.2, - 20, - 19.9, - 19.9, - 19.8, - 19.6, - 19.4, - 19.3, - 19.2, - 19, - 18.9, - 18.8, - 18.8, - 18.7, - 18.4, - 18.1, - 17.9, - 17.8, - 17.5, - 17.2, - 17, - 16.8, - 16.7, - 16.6, - 16.5, - 16.4, - 16.3, - 16.2, - 16.2, - 16.2, - 15.8, - 15.7, - 15.5, - 15.4, - 15.2, - 14.9, - 14.7, - 14.2, - 13.8, - 13.6, - 13.5, - 37.9, - 38.4, - 38.7, - 39.1, - 39.2, - 39.3, - 39.4, - 39.5, - 39.5, - 39.3, - 39.2, - 39.2, - 39.1, - 39, - 39, - 38.9, - 38.9, - 38.9, - 38.9, - 38.9, - 38.9, - 39, - 39, - 39, - 39, - 39.1, - 39, - 39, - 39, - 38.9, - 38.8, - 38.7, - 38.7, - 38.6, - 38.5, - 38.5, - 38.5, - 38.5, - 38.5, - 38.6, - 38.6, - 38.5, - 38.6, - 38.8, - 38.8, - 38.8, - 38.6, - 38.5, - 38.4, - 38.3, - 38.2, - 38.2, - 38.1, - 38.1, - 38.1, - 38.1, - 38, - 37.9, - 37.8, - 37.7, - 37.5, - 37.4, - 37.2, - 37.1, - 37, - 37, - 36.8, - 36.7, - 36.6, - 36.4, - 36.4, - 36.3, - 36.2, - 36.1, - 36.1, - 36, - 35.9, - 35.8, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.6, - 35.4, - 35.3, - 35, - 35, - 34.9, - 34.6, - 34.4, - 34.3, - 34.1, - 33.8, - 33.6, - 33.4, - 33.2, - 32.9, - 32.7, - 32.5, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 31.6, - 31.6, - 31.5, - 31.3, - 31.2, - 31, - 30.9, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.5, - 30.4, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.9, - 29.8, - 29.6, - 29.5, - 29.3, - 29.2, - 29.1, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.6, - 28.6, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.5, - 28.4, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.3, - 27.1, - 27, - 26.9, - 26.7, - 26.6, - 26.5, - 26.3, - 26.1, - 26.1, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.3, - 25.2, - 25, - 24.9, - 24.7, - 24.6, - 24.4, - 24.1, - 23.9, - 23.7, - 23.6, - 23.6, - 23.6, - 23.5, - 23.4, - 23.4, - 23.4, - 23.4, - 23.3, - 22.7, - 22.4, - 22.2, - 22, - 21.8, - 21.6, - 21.4, - 21.2, - 21, - 20.9, - 20.7, - 20.5, - 20.3, - 20.1, - 20, - 19.9, - 19.8, - 19.7, - 19.5, - 19.4, - 19.3, - 19.1, - 18.9, - 18.9, - 18.9, - 18.9, - 18.7, - 18.4, - 18.2, - 18, - 17.8, - 17.6, - 17.5, - 17.5, - 17.5, - 17.4, - 17.2, - 16.9, - 16.5, - 16.4, - 16.3, - 16.2, - 15.8, - 15.7, - 15.7, - 15.5, - 15.2, - 15, - 14.8, - 14.4, - 14, - 13.8, - 13.7, - 38, - 38.4, - 38.7, - 39, - 39.1, - 39.3, - 39.4, - 39.4, - 39.3, - 39.2, - 39.2, - 39.2, - 39.2, - 39, - 38.9, - 38.9, - 38.9, - 39, - 39, - 39, - 39.1, - 39.1, - 39.1, - 39.2, - 39.2, - 39.2, - 39.1, - 39.2, - 39, - 39, - 38.9, - 38.8, - 38.7, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.7, - 38.7, - 38.8, - 38.6, - 38.7, - 38.8, - 38.9, - 38.9, - 38.7, - 38.6, - 38.5, - 38.4, - 38.4, - 38.3, - 38.2, - 38.2, - 38.2, - 38.2, - 38.1, - 38, - 37.9, - 37.8, - 37.5, - 37.3, - 37.2, - 37.1, - 37, - 37, - 36.8, - 36.7, - 36.6, - 36.5, - 36.4, - 36.4, - 36.3, - 36.2, - 36.2, - 36.1, - 36, - 35.9, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.6, - 35.5, - 35.3, - 35.1, - 35, - 34.9, - 34.6, - 34.4, - 34.3, - 34.2, - 33.9, - 33.7, - 33.5, - 33.3, - 33, - 32.8, - 32.6, - 32.5, - 32.4, - 32.3, - 32.1, - 32.1, - 32, - 31.9, - 31.8, - 31.7, - 31.5, - 31.4, - 31.2, - 31.1, - 31, - 31, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.9, - 28.8, - 28.8, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.4, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 27.9, - 27.8, - 27.8, - 27.7, - 27.6, - 27.6, - 27.5, - 27.4, - 27.3, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.2, - 26.1, - 26.1, - 25.9, - 25.7, - 25.6, - 25.6, - 25.5, - 25.2, - 25.1, - 25, - 24.8, - 24.7, - 24.6, - 24.2, - 24, - 23.8, - 23.6, - 23.6, - 23.6, - 23.5, - 23.5, - 23.5, - 23.4, - 23.4, - 23.3, - 23, - 22.6, - 22.5, - 22.2, - 21.9, - 21.7, - 21.5, - 21.3, - 21.2, - 21, - 20.9, - 20.6, - 20.3, - 20.2, - 20.1, - 20, - 19.9, - 19.7, - 19.6, - 19.5, - 19.4, - 19.3, - 19.1, - 19, - 18.9, - 18.9, - 18.8, - 18.6, - 18.5, - 18.2, - 18, - 17.9, - 17.7, - 17.7, - 17.6, - 17.5, - 17.4, - 17.5, - 17, - 16.6, - 16.5, - 16.2, - 16, - 15.8, - 15.7, - 15.5, - 15.4, - 15.1, - 15, - 14.8, - 14.4, - 14, - 13.8, - 38, - 38.3, - 38.6, - 38.8, - 38.9, - 39.1, - 39.3, - 39.3, - 39.3, - 39.2, - 39.2, - 39.1, - 39.1, - 39, - 39, - 39, - 39.1, - 39.1, - 39.1, - 39.1, - 39.2, - 39.2, - 39.2, - 39.3, - 39.3, - 39.3, - 39.4, - 39.4, - 39.2, - 39, - 38.9, - 38.8, - 38.8, - 38.8, - 38.8, - 38.7, - 38.7, - 38.7, - 38.7, - 38.8, - 38.8, - 38.8, - 39, - 39.1, - 39.1, - 38.9, - 38.8, - 38.7, - 38.6, - 38.5, - 38.5, - 38.4, - 38.3, - 38.3, - 38.3, - 38.2, - 38, - 38, - 37.9, - 37.7, - 37.4, - 37.3, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.5, - 36.5, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.9, - 35.8, - 35.7, - 35.6, - 35.6, - 35.5, - 35.4, - 35.1, - 34.9, - 34.8, - 34.7, - 34.5, - 34.3, - 34.2, - 34, - 33.9, - 33.7, - 33.4, - 33.2, - 33, - 32.8, - 32.7, - 32.6, - 32.5, - 32.3, - 32.2, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 31.6, - 31.5, - 31.3, - 31.2, - 31.2, - 31.1, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.7, - 30.5, - 30.4, - 30.3, - 30.2, - 30.1, - 29.9, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.7, - 28.7, - 28.8, - 28.8, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.5, - 28.3, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 27.7, - 27.6, - 27.5, - 27.5, - 27.5, - 27.5, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26, - 25.8, - 25.6, - 25.6, - 25.6, - 25.4, - 25.3, - 25.1, - 25, - 24.8, - 24.8, - 24.5, - 24.2, - 23.9, - 23.7, - 23.6, - 23.7, - 23.7, - 23.5, - 23.5, - 23.5, - 23.5, - 23.4, - 23.2, - 22.8, - 22.6, - 22.5, - 22.1, - 21.8, - 21.7, - 21.5, - 21.3, - 21.2, - 21, - 20.7, - 20.5, - 20.3, - 20.2, - 20.1, - 20, - 19.8, - 19.7, - 19.5, - 19.5, - 19.4, - 19.2, - 19.1, - 19, - 19, - 18.9, - 18.7, - 18.5, - 18.3, - 18, - 17.8, - 17.6, - 17.5, - 17.4, - 17.3, - 17.2, - 17.3, - 17.4, - 17, - 16.8, - 16.5, - 16.3, - 16, - 15.8, - 15.6, - 15.5, - 15.3, - 15.1, - 15, - 14.8, - 14.5, - 14, - 37.9, - 38.2, - 38.4, - 38.6, - 38.7, - 38.8, - 39, - 39.1, - 39.2, - 39.2, - 39.2, - 39.1, - 39, - 39, - 39, - 39, - 39.1, - 39.1, - 39.2, - 39.3, - 39.4, - 39.4, - 39.4, - 39.5, - 39.4, - 39.4, - 39.5, - 39.4, - 39.2, - 39, - 39, - 39, - 39, - 39, - 38.9, - 39, - 38.9, - 38.9, - 38.9, - 38.9, - 39, - 39.3, - 39.4, - 39.3, - 39.2, - 39.1, - 39, - 38.8, - 38.7, - 38.6, - 38.5, - 38.5, - 38.4, - 38.3, - 38.3, - 38.2, - 37.9, - 37.9, - 37.8, - 37.6, - 37.4, - 37.2, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.5, - 36.5, - 36.5, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.4, - 35.1, - 35, - 34.8, - 34.7, - 34.5, - 34.3, - 34.2, - 34.2, - 34, - 33.8, - 33.6, - 33.4, - 33.2, - 33, - 32.9, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.7, - 31.6, - 31.5, - 31.4, - 31.3, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.8, - 30.6, - 30.5, - 30.4, - 30.4, - 30.3, - 30.1, - 30, - 29.8, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.5, - 27.4, - 27.3, - 27.1, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.8, - 25.6, - 25.7, - 25.5, - 25.4, - 25.3, - 25.1, - 24.9, - 24.8, - 24.7, - 24.5, - 24.4, - 24.1, - 23.8, - 23.7, - 23.7, - 23.6, - 23.5, - 23.5, - 23.7, - 23.7, - 23.3, - 23, - 22.7, - 22.6, - 22.3, - 22, - 21.8, - 21.6, - 21.5, - 21.4, - 21.2, - 20.9, - 20.7, - 20.6, - 20.4, - 20.2, - 20.1, - 19.9, - 19.8, - 19.6, - 19.5, - 19.4, - 19.3, - 19.2, - 19.1, - 19, - 18.9, - 18.8, - 18.5, - 18.3, - 18, - 17.8, - 17.6, - 17.5, - 17.4, - 17.3, - 17.2, - 17.2, - 17.4, - 17.5, - 17.2, - 16.7, - 16.5, - 16.2, - 15.8, - 15.5, - 15.4, - 15.3, - 15.1, - 15, - 15, - 14.7, - 14.4, - 37.7, - 38, - 38.3, - 38.5, - 38.6, - 38.8, - 38.9, - 39.1, - 39.1, - 39.2, - 39.2, - 39.1, - 39.1, - 39, - 39, - 39.1, - 39.1, - 39.1, - 39.2, - 39.4, - 39.6, - 39.6, - 39.6, - 39.7, - 39.7, - 39.5, - 39.4, - 39.3, - 39.2, - 39.1, - 39.1, - 39.1, - 39.1, - 39.1, - 39.1, - 39.1, - 39.2, - 39.2, - 39.2, - 39.2, - 39.3, - 39.4, - 39.5, - 39.5, - 39.4, - 39.3, - 39.1, - 38.9, - 38.8, - 38.8, - 38.7, - 38.6, - 38.4, - 38.3, - 38.2, - 38.1, - 37.9, - 37.8, - 37.6, - 37.5, - 37.4, - 37.2, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.6, - 36.5, - 36.5, - 36.5, - 36.5, - 36.4, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.2, - 35, - 34.9, - 34.7, - 34.6, - 34.4, - 34.3, - 34.1, - 34, - 33.9, - 33.7, - 33.5, - 33.3, - 33.2, - 33.1, - 32.9, - 32.8, - 32.7, - 32.6, - 32.5, - 32.5, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 31.7, - 31.5, - 31.3, - 31.2, - 31.2, - 31.1, - 31, - 30.9, - 30.7, - 30.6, - 30.5, - 30.5, - 30.4, - 30.3, - 30.3, - 30.1, - 29.9, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29.1, - 29.1, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.9, - 28.8, - 28.8, - 28.7, - 28.7, - 28.7, - 28.7, - 28.7, - 28.6, - 28.5, - 28.5, - 28.4, - 28.2, - 28.2, - 28.1, - 28, - 28, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.4, - 26.2, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.4, - 25.2, - 25.1, - 24.9, - 24.7, - 24.6, - 24.5, - 24.4, - 24.1, - 23.8, - 23.7, - 23.7, - 23.6, - 23.5, - 23.7, - 23.7, - 23.3, - 23.2, - 23, - 22.7, - 22.4, - 22.1, - 22, - 21.8, - 21.6, - 21.5, - 21.4, - 21.1, - 21, - 20.8, - 20.6, - 20.4, - 20.2, - 20, - 19.9, - 19.8, - 19.6, - 19.6, - 19.4, - 19.4, - 19.3, - 19.2, - 19, - 18.9, - 18.7, - 18.5, - 18.1, - 17.9, - 17.7, - 17.5, - 17.4, - 17.3, - 17.3, - 17.2, - 17.4, - 17.5, - 17.2, - 16.9, - 16.8, - 16.4, - 15.8, - 15.5, - 15.3, - 15.1, - 15, - 15, - 15, - 14.9, - 14.7, - 37.7, - 38, - 38.3, - 38.5, - 38.7, - 39, - 39.1, - 39.2, - 39.2, - 39.3, - 39.3, - 39.2, - 39.1, - 39, - 39.1, - 39.1, - 39.2, - 39.2, - 39.3, - 39.5, - 39.6, - 39.7, - 39.8, - 39.8, - 39.8, - 39.6, - 39.4, - 39.3, - 39.2, - 39.2, - 39.2, - 39.3, - 39.4, - 39.3, - 39.4, - 39.3, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.5, - 39.4, - 39.2, - 39, - 38.9, - 38.9, - 38.8, - 38.8, - 38.7, - 38.3, - 38.2, - 38, - 37.9, - 37.7, - 37.6, - 37.4, - 37.3, - 37.2, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.7, - 36.6, - 36.6, - 36.6, - 36.6, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.2, - 35.1, - 34.9, - 34.8, - 34.7, - 34.5, - 34.3, - 34.2, - 34.1, - 33.9, - 33.8, - 33.7, - 33.4, - 33.3, - 33.3, - 33.2, - 33.1, - 33, - 32.9, - 32.7, - 32.6, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32, - 31.9, - 31.9, - 31.7, - 31.6, - 31.5, - 31.3, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.6, - 30.5, - 30.4, - 30.4, - 30.2, - 30.1, - 29.8, - 29.6, - 29.6, - 29.4, - 29.3, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29.2, - 29.1, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 28.5, - 28.5, - 28.4, - 28.3, - 28.3, - 28.2, - 28.1, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.6, - 27.5, - 27.3, - 27.3, - 27.2, - 27, - 26.9, - 26.7, - 26.7, - 26.5, - 26.4, - 26.3, - 26.1, - 26, - 25.9, - 25.6, - 25.5, - 25.5, - 25.3, - 25.2, - 25, - 24.7, - 24.6, - 24.6, - 24.6, - 24.3, - 23.9, - 23.8, - 23.7, - 23.6, - 23.5, - 23.5, - 23.6, - 23.4, - 23.2, - 23.2, - 22.8, - 22.5, - 22.3, - 22.1, - 21.9, - 21.7, - 21.6, - 21.5, - 21.3, - 21.1, - 20.9, - 20.8, - 20.6, - 20.3, - 20.2, - 20.1, - 20, - 19.9, - 19.8, - 19.8, - 19.7, - 19.6, - 19.4, - 19.2, - 19, - 18.9, - 18.7, - 18.4, - 18.2, - 17.8, - 17.5, - 17.4, - 17.4, - 17.4, - 17.4, - 17.4, - 17.4, - 17.2, - 17.1, - 16.8, - 16.6, - 16.3, - 15.8, - 15.3, - 15.1, - 15, - 15, - 15, - 14.9, - 14.8, - 37.7, - 38.1, - 38.5, - 38.8, - 39, - 39.3, - 39.3, - 39.3, - 39.3, - 39.3, - 39.3, - 39.2, - 39.1, - 39.1, - 39.2, - 39.2, - 39.2, - 39.2, - 39.4, - 39.5, - 39.6, - 39.7, - 39.7, - 39.7, - 39.7, - 39.6, - 39.4, - 39.3, - 39.3, - 39.3, - 39.4, - 39.4, - 39.5, - 39.4, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.5, - 39.6, - 39.6, - 39.6, - 39.5, - 39.5, - 39.4, - 39.2, - 39.1, - 38.9, - 38.9, - 38.9, - 38.8, - 38.8, - 38.4, - 38.1, - 38, - 37.8, - 37.7, - 37.6, - 37.4, - 37.3, - 37.2, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.7, - 36.7, - 36.8, - 36.7, - 36.6, - 36.6, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.8, - 35.8, - 35.7, - 35.5, - 35.3, - 35.2, - 35, - 34.9, - 34.8, - 34.7, - 34.6, - 34.4, - 34.2, - 34.1, - 34, - 33.9, - 33.8, - 33.7, - 33.6, - 33.5, - 33.5, - 33.3, - 33.2, - 33.1, - 33, - 32.8, - 32.8, - 32.7, - 32.5, - 32.4, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.6, - 30.5, - 30.3, - 30.3, - 30.2, - 29.8, - 29.7, - 29.6, - 29.4, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.3, - 29.2, - 29.1, - 29.1, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.6, - 28.6, - 28.5, - 28.5, - 28.4, - 28.5, - 28.4, - 28.2, - 28.1, - 28.1, - 27.9, - 27.8, - 27.7, - 27.6, - 27.6, - 27.4, - 27.3, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 26, - 25.7, - 25.5, - 25.5, - 25.4, - 25.2, - 25.1, - 24.8, - 24.6, - 24.6, - 24.6, - 24.4, - 24.1, - 23.9, - 23.8, - 23.6, - 23.6, - 23.6, - 23.5, - 23.5, - 23.3, - 23.1, - 22.8, - 22.6, - 22.4, - 22.2, - 22.1, - 21.9, - 21.7, - 21.6, - 21.4, - 21.3, - 21.1, - 20.9, - 20.7, - 20.5, - 20.3, - 20.2, - 20.2, - 20.1, - 20.2, - 20.5, - 20.1, - 19.9, - 19.6, - 19.4, - 19.3, - 19, - 18.8, - 18.6, - 18.4, - 18, - 17.7, - 17.5, - 17.5, - 17.5, - 17.5, - 17.6, - 17.5, - 17.3, - 17, - 16.7, - 16.5, - 16.6, - 16.5, - 15.8, - 15.2, - 15, - 15, - 15, - 15, - 14.8, - 37.7, - 38.3, - 38.7, - 39, - 39.3, - 39.4, - 39.4, - 39.5, - 39.5, - 39.4, - 39.3, - 39.3, - 39.2, - 39.1, - 39.2, - 39.2, - 39.1, - 39.2, - 39.3, - 39.4, - 39.5, - 39.6, - 39.5, - 39.5, - 39.6, - 39.6, - 39.5, - 39.3, - 39.3, - 39.4, - 39.5, - 39.5, - 39.5, - 39.4, - 39.6, - 39.6, - 39.7, - 39.7, - 39.7, - 39.7, - 39.8, - 39.9, - 39.9, - 39.7, - 39.5, - 39.4, - 39.2, - 39.1, - 38.9, - 38.9, - 38.8, - 38.7, - 38.6, - 38.3, - 38.1, - 38, - 37.8, - 37.7, - 37.5, - 37.4, - 37.3, - 37.2, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.8, - 36.8, - 36.7, - 36.8, - 36.8, - 36.7, - 36.7, - 36.7, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.8, - 35.8, - 35.8, - 35.6, - 35.4, - 35.3, - 35.1, - 35, - 34.9, - 34.7, - 34.6, - 34.5, - 34.4, - 34.2, - 34.1, - 34, - 33.9, - 33.8, - 33.8, - 33.7, - 33.7, - 33.6, - 33.5, - 33.4, - 33.2, - 33.1, - 33, - 32.9, - 32.7, - 32.6, - 32.6, - 32.5, - 32.3, - 32.2, - 32.1, - 32.1, - 31.9, - 31.8, - 31.7, - 31.6, - 31.4, - 31.4, - 31.3, - 31.1, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.3, - 30.5, - 30.5, - 30.2, - 29.9, - 29.8, - 29.6, - 29.5, - 29.4, - 29.3, - 29.3, - 29.3, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29.1, - 29.1, - 29, - 29, - 28.9, - 29, - 29, - 28.9, - 28.9, - 28.8, - 28.7, - 28.6, - 28.7, - 28.6, - 28.4, - 28.4, - 28.4, - 28.3, - 28.1, - 28.1, - 28, - 28, - 27.8, - 27.7, - 27.7, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.5, - 26.5, - 26.3, - 26.1, - 26, - 25.8, - 25.6, - 25.4, - 25.3, - 25.2, - 25.1, - 24.9, - 24.7, - 24.6, - 24.6, - 24.6, - 24.3, - 24, - 23.9, - 23.7, - 23.7, - 23.7, - 23.6, - 23.5, - 23.3, - 23.1, - 22.8, - 22.7, - 22.5, - 22.4, - 22.2, - 22, - 21.8, - 21.6, - 21.6, - 21.5, - 21.3, - 21.1, - 21, - 20.7, - 20.5, - 20.4, - 20.4, - 20.3, - 20.3, - 20.4, - 20.4, - 20.3, - 19.8, - 19.5, - 19.4, - 19.2, - 19, - 18.8, - 18.6, - 18.4, - 18.1, - 17.8, - 17.8, - 17.8, - 17.7, - 17.7, - 17.6, - 17.3, - 17.1, - 16.7, - 16.5, - 16.6, - 16.7, - 16.3, - 15.5, - 15.1, - 15, - 15, - 15, - 14.8, - 37.9, - 38.3, - 38.7, - 39, - 39.3, - 39.5, - 39.5, - 39.5, - 39.7, - 39.5, - 39.3, - 39.3, - 39.2, - 39.2, - 39.2, - 39.1, - 39.1, - 39.2, - 39.3, - 39.3, - 39.4, - 39.5, - 39.4, - 39.4, - 39.5, - 39.6, - 39.5, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.5, - 39.5, - 39.7, - 39.8, - 39.9, - 39.9, - 39.9, - 39.9, - 40, - 40, - 40, - 39.8, - 39.6, - 39.4, - 39.3, - 39.1, - 39, - 38.9, - 38.8, - 38.6, - 38.4, - 38.2, - 38.1, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37.1, - 37, - 37, - 36.9, - 36.9, - 36.8, - 36.8, - 36.8, - 36.8, - 36.8, - 36.7, - 36.7, - 36.5, - 36.4, - 36.3, - 36.3, - 36.2, - 36, - 35.9, - 35.7, - 35.7, - 35.6, - 35.5, - 35.3, - 35.2, - 35.1, - 35, - 34.9, - 34.7, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 34, - 33.9, - 33.9, - 33.8, - 33.8, - 33.7, - 33.6, - 33.6, - 33.4, - 33.3, - 33.1, - 33.1, - 33, - 32.8, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32.2, - 32, - 31.9, - 31.8, - 31.7, - 31.6, - 31.7, - 31.5, - 31.1, - 31, - 30.9, - 30.8, - 30.6, - 30.4, - 30.4, - 30.5, - 30.4, - 30.2, - 30, - 29.8, - 29.7, - 29.6, - 29.4, - 29.4, - 29.4, - 29.4, - 29.5, - 29.4, - 29.4, - 29.3, - 29.2, - 29.2, - 29.2, - 29.2, - 29.1, - 29, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.8, - 28.8, - 28.7, - 28.6, - 28.5, - 28.4, - 28.5, - 28.4, - 28.2, - 28.2, - 28.1, - 28, - 27.9, - 27.8, - 27.7, - 27.5, - 27.4, - 27.4, - 27.2, - 27.1, - 27, - 26.9, - 26.6, - 26.5, - 26.4, - 26.2, - 26.1, - 25.8, - 25.6, - 25.5, - 25.4, - 25.3, - 25.2, - 25, - 24.8, - 24.7, - 24.6, - 24.6, - 24.5, - 24.2, - 23.9, - 23.8, - 23.7, - 23.7, - 23.6, - 23.5, - 23.3, - 23.1, - 22.9, - 22.8, - 22.6, - 22.5, - 22.3, - 22.1, - 21.9, - 21.7, - 21.6, - 21.5, - 21.4, - 21.3, - 21.2, - 21, - 20.7, - 20.6, - 20.4, - 20.4, - 20.3, - 20.3, - 20.4, - 20.5, - 20.1, - 19.7, - 19.5, - 19.3, - 19.1, - 19.1, - 18.9, - 18.7, - 18.4, - 18.3, - 18.1, - 18.1, - 18, - 17.8, - 17.7, - 17.4, - 17.1, - 16.8, - 16.6, - 16.6, - 16.6, - 16.6, - 15.9, - 15.4, - 15.2, - 15, - 14.9, - 14.8, - 38, - 38.4, - 38.6, - 38.9, - 39.1, - 39.3, - 39.4, - 39.5, - 39.6, - 39.5, - 39.3, - 39.2, - 39.2, - 39.2, - 39.1, - 39.1, - 39.1, - 39.2, - 39.2, - 39.3, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.5, - 39.6, - 39.5, - 39.5, - 39.6, - 39.7, - 39.8, - 39.9, - 40, - 40, - 40.1, - 40.1, - 40.1, - 40, - 39.9, - 39.7, - 39.6, - 39.5, - 39.4, - 39.2, - 39.1, - 38.9, - 38.7, - 38.6, - 38.4, - 38.2, - 38.1, - 38, - 37.9, - 37.8, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37.2, - 37.2, - 37.1, - 37, - 37, - 37, - 36.9, - 36.9, - 36.9, - 36.9, - 36.7, - 36.6, - 36.6, - 36.5, - 36.4, - 36.3, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.7, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 34.1, - 34, - 33.9, - 33.8, - 33.8, - 33.7, - 33.7, - 33.6, - 33.4, - 33.4, - 33.3, - 33.1, - 33, - 32.9, - 32.8, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32.1, - 32, - 32, - 31.9, - 31.8, - 31.7, - 31.3, - 31.1, - 31, - 31, - 30.9, - 30.5, - 30.5, - 30.5, - 30.5, - 30.4, - 30.2, - 30, - 29.9, - 29.7, - 29.6, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.3, - 29.3, - 29.3, - 29.2, - 29.2, - 29.2, - 29.2, - 29.1, - 29, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.8, - 28.6, - 28.6, - 28.6, - 28.5, - 28.4, - 28.3, - 28.3, - 28.2, - 28, - 28, - 27.9, - 27.6, - 27.5, - 27.5, - 27.3, - 27.2, - 27.1, - 26.9, - 26.7, - 26.5, - 26.5, - 26.3, - 26.1, - 25.9, - 25.7, - 25.6, - 25.6, - 25.4, - 25.3, - 25.2, - 25, - 24.8, - 24.7, - 24.6, - 24.5, - 24.3, - 24.1, - 24, - 23.8, - 23.7, - 23.7, - 23.5, - 23.4, - 23.2, - 23, - 22.9, - 22.7, - 22.6, - 22.4, - 22.2, - 22, - 21.8, - 21.7, - 21.6, - 21.5, - 21.3, - 21.2, - 21.2, - 20.9, - 20.7, - 20.5, - 20.4, - 20.3, - 20.2, - 20.3, - 20.5, - 20.4, - 20, - 19.7, - 19.5, - 19.4, - 19.3, - 19.2, - 19, - 18.7, - 18.5, - 18.4, - 18.3, - 18.1, - 18.1, - 17.9, - 17.7, - 17.4, - 17.1, - 16.9, - 16.7, - 16.8, - 16.6, - 16.1, - 15.7, - 15.4, - 15.2, - 15, - 14.9, - 38.1, - 38.4, - 38.7, - 38.9, - 39.1, - 39.2, - 39.3, - 39.4, - 39.3, - 39.3, - 39.3, - 39.2, - 39.2, - 39.1, - 39.1, - 39.1, - 39.2, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.5, - 39.5, - 39.4, - 39.4, - 39.5, - 39.6, - 39.6, - 39.6, - 39.7, - 39.9, - 40, - 40.1, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.1, - 40, - 39.8, - 39.6, - 39.6, - 39.4, - 39.2, - 39.1, - 38.9, - 38.7, - 38.6, - 38.4, - 38.3, - 38.2, - 38.1, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.4, - 37.4, - 37.3, - 37.2, - 37.1, - 37.1, - 37, - 37, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.6, - 35.5, - 35.3, - 35.1, - 35.1, - 35, - 34.9, - 34.8, - 34.7, - 34.5, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 34.1, - 34, - 33.9, - 33.8, - 33.8, - 33.7, - 33.6, - 33.5, - 33.4, - 33.3, - 33.2, - 33.1, - 33, - 32.8, - 32.7, - 32.6, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32.2, - 32, - 32, - 31.9, - 31.7, - 31.5, - 31.3, - 31.2, - 31.2, - 30.9, - 30.7, - 30.5, - 30.5, - 30.6, - 30.4, - 30.2, - 30.1, - 29.9, - 29.9, - 29.8, - 29.8, - 29.7, - 29.7, - 29.6, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.4, - 29.3, - 29.3, - 29.1, - 29.1, - 29, - 29, - 28.9, - 28.9, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.1, - 28, - 28, - 27.8, - 27.6, - 27.5, - 27.4, - 27.2, - 27.1, - 27, - 26.8, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 25.8, - 25.7, - 25.6, - 25.5, - 25.4, - 25.2, - 25, - 24.9, - 24.8, - 24.6, - 24.6, - 24.4, - 24.2, - 24.1, - 23.9, - 23.8, - 23.7, - 23.6, - 23.5, - 23.3, - 23.1, - 23, - 22.8, - 22.7, - 22.5, - 22.3, - 22.2, - 22, - 21.9, - 21.8, - 21.7, - 21.6, - 21.4, - 21.2, - 21.1, - 20.8, - 20.6, - 20.5, - 20.4, - 20.3, - 20.3, - 20.4, - 20.5, - 20.1, - 19.8, - 19.7, - 19.6, - 19.6, - 19.5, - 19.3, - 19.1, - 18.9, - 18.7, - 18.5, - 18.3, - 18.1, - 18.1, - 17.8, - 17.6, - 17.4, - 17.2, - 17.1, - 16.9, - 16.5, - 16.1, - 15.6, - 15.2, - 15.1, - 15, - 14.9, - 38.1, - 38.5, - 38.8, - 39, - 39.1, - 39.2, - 39.3, - 39.4, - 39.3, - 39.3, - 39.2, - 39.1, - 39.1, - 39.1, - 39, - 39.2, - 39.3, - 39.3, - 39.3, - 39.4, - 39.3, - 39.4, - 39.5, - 39.5, - 39.5, - 39.6, - 39.5, - 39.5, - 39.6, - 39.7, - 39.7, - 39.9, - 40.1, - 40.3, - 40.3, - 40.3, - 40.3, - 40.4, - 40.3, - 40.2, - 40.2, - 40.1, - 40.1, - 39.9, - 39.6, - 39.5, - 39.4, - 39.3, - 39.1, - 39, - 38.8, - 38.6, - 38.5, - 38.4, - 38.3, - 38.2, - 38.1, - 37.9, - 37.8, - 37.7, - 37.6, - 37.5, - 37.5, - 37.5, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37.2, - 37.2, - 37.1, - 36.9, - 36.8, - 36.8, - 36.8, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.7, - 35.7, - 35.6, - 35.6, - 35.4, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.8, - 34.7, - 34.6, - 34.5, - 34.5, - 34.4, - 34.3, - 34.3, - 34.2, - 34.1, - 34, - 33.9, - 33.8, - 33.7, - 33.7, - 33.6, - 33.6, - 33.4, - 33.3, - 33.2, - 33.1, - 32.9, - 32.8, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.3, - 32.2, - 32.2, - 32.2, - 31.8, - 31.7, - 31.6, - 31.4, - 31.2, - 31.1, - 31, - 30.7, - 30.6, - 30.6, - 30.6, - 30.4, - 30.2, - 30.1, - 30, - 30, - 30, - 29.9, - 29.9, - 29.9, - 29.8, - 29.7, - 29.6, - 29.6, - 29.6, - 29.6, - 29.5, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.4, - 28.4, - 28.3, - 28.2, - 28, - 28, - 27.8, - 27.7, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 26.9, - 26.7, - 26.5, - 26.4, - 26.3, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.5, - 25.3, - 25.2, - 25.1, - 24.9, - 24.8, - 24.7, - 24.5, - 24.3, - 24.2, - 24, - 23.9, - 23.8, - 23.7, - 23.5, - 23.4, - 23.2, - 23.1, - 22.9, - 22.7, - 22.6, - 22.5, - 22.3, - 22.2, - 22.1, - 21.9, - 21.8, - 21.8, - 21.6, - 21.3, - 21.1, - 21, - 20.8, - 20.6, - 20.5, - 20.4, - 20.3, - 20.4, - 20.4, - 20.2, - 20, - 19.9, - 19.8, - 19.8, - 19.7, - 19.6, - 19.3, - 19.1, - 18.8, - 18.5, - 18.3, - 18.2, - 18.1, - 17.9, - 17.6, - 17.3, - 17.2, - 17.1, - 16.8, - 16.1, - 15.6, - 15.3, - 15.2, - 15.3, - 15.1, - 15, - 38.2, - 38.6, - 38.8, - 39, - 39.1, - 39.2, - 39.3, - 39.3, - 39.1, - 39.1, - 39.1, - 39.1, - 39.1, - 39, - 39, - 39.2, - 39.3, - 39.3, - 39.4, - 39.4, - 39.3, - 39.5, - 39.5, - 39.5, - 39.6, - 39.7, - 39.7, - 39.7, - 39.8, - 39.9, - 40, - 40.3, - 40.5, - 40.7, - 40.6, - 40.5, - 40.5, - 40.5, - 40.5, - 40.4, - 40.3, - 40.2, - 40.1, - 39.9, - 39.7, - 39.5, - 39.4, - 39.3, - 39.2, - 39, - 38.8, - 38.7, - 38.5, - 38.4, - 38.4, - 38.2, - 38.2, - 38.1, - 37.9, - 37.9, - 37.8, - 37.8, - 37.8, - 37.8, - 37.8, - 37.6, - 37.5, - 37.4, - 37.3, - 37.3, - 37.3, - 37.2, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.4, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.8, - 35.6, - 35.4, - 35.2, - 35.1, - 35, - 34.9, - 34.9, - 34.8, - 34.7, - 34.6, - 34.6, - 34.6, - 34.5, - 34.5, - 34.4, - 34.2, - 34.1, - 34, - 34, - 33.9, - 33.9, - 33.8, - 33.8, - 33.6, - 33.5, - 33.4, - 33.2, - 33.1, - 33, - 33, - 32.9, - 32.8, - 32.5, - 32.4, - 32.3, - 32.3, - 32.2, - 32.3, - 32.1, - 31.9, - 31.7, - 31.6, - 31.4, - 31.3, - 31.2, - 31.1, - 30.8, - 30.7, - 30.7, - 30.6, - 30.4, - 30.2, - 30.1, - 30.1, - 30.1, - 30, - 30, - 30, - 30, - 30, - 29.9, - 29.8, - 29.7, - 29.7, - 29.6, - 29.6, - 29.6, - 29.6, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.3, - 29.1, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.4, - 28.2, - 28.1, - 28, - 27.9, - 27.7, - 27.6, - 27.5, - 27.4, - 27.2, - 27.1, - 27, - 26.8, - 26.7, - 26.5, - 26.3, - 26.2, - 26.1, - 25.9, - 25.8, - 25.7, - 25.6, - 25.5, - 25.3, - 25.2, - 25.1, - 25, - 24.8, - 24.5, - 24.5, - 24.4, - 24.1, - 24, - 23.8, - 23.7, - 23.6, - 23.5, - 23.4, - 23.2, - 23, - 22.8, - 22.7, - 22.7, - 22.6, - 22.3, - 22.2, - 22, - 21.9, - 21.9, - 21.8, - 21.6, - 21.4, - 21.2, - 21, - 20.8, - 20.6, - 20.5, - 20.5, - 20.5, - 20.4, - 20.3, - 20.2, - 20.1, - 20.1, - 19.9, - 19.8, - 19.7, - 19.6, - 19.4, - 19.1, - 18.8, - 18.5, - 18.2, - 18.1, - 18, - 17.7, - 17.3, - 17.1, - 16.8, - 16.2, - 15.6, - 15.4, - 15.5, - 15.7, - 15.8, - 15.7, - 15.6, - 38.2, - 38.6, - 38.8, - 39, - 39.1, - 39.2, - 39.3, - 39.2, - 39, - 39.1, - 39.1, - 39.1, - 39.1, - 39, - 39.1, - 39.2, - 39.3, - 39.3, - 39.3, - 39.4, - 39.5, - 39.6, - 39.6, - 39.7, - 39.7, - 39.8, - 39.9, - 39.8, - 40, - 40.2, - 40.3, - 40.6, - 40.8, - 40.9, - 40.9, - 40.7, - 40.7, - 40.7, - 40.5, - 40.4, - 40.4, - 40.2, - 40, - 40, - 39.7, - 39.6, - 39.5, - 39.4, - 39.2, - 39.1, - 39, - 38.8, - 38.6, - 38.5, - 38.5, - 38.4, - 38.3, - 38.2, - 38.2, - 38.2, - 38.1, - 38, - 38, - 38, - 38, - 37.9, - 37.7, - 37.7, - 37.5, - 37.3, - 37.2, - 37.2, - 37, - 37, - 36.8, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 36, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.3, - 35.1, - 35.1, - 35, - 35, - 34.9, - 34.8, - 34.7, - 34.7, - 34.7, - 34.6, - 34.6, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 34, - 34, - 34, - 34, - 33.9, - 33.7, - 33.5, - 33.4, - 33.3, - 33.2, - 33.2, - 33.2, - 33.1, - 32.8, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32.3, - 32.1, - 31.9, - 31.8, - 31.5, - 31.3, - 31.3, - 31.2, - 31, - 30.9, - 30.9, - 30.8, - 30.5, - 30.3, - 30.3, - 30.2, - 30.2, - 30.2, - 30.2, - 30.2, - 30.2, - 30.1, - 30, - 30, - 29.9, - 29.9, - 29.8, - 29.8, - 29.7, - 29.7, - 29.6, - 29.5, - 29.5, - 29.5, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.7, - 28.7, - 28.6, - 28.5, - 28.3, - 28.1, - 28, - 27.9, - 27.8, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.5, - 25.3, - 25.2, - 25.1, - 24.9, - 24.7, - 24.5, - 24.4, - 24.2, - 24, - 23.9, - 23.8, - 23.7, - 23.7, - 23.6, - 23.4, - 23.2, - 23, - 22.9, - 22.8, - 22.8, - 22.6, - 22.4, - 22.2, - 22.1, - 22, - 21.9, - 21.8, - 21.7, - 21.5, - 21.3, - 21, - 20.8, - 20.7, - 20.7, - 20.7, - 20.6, - 20.4, - 20.3, - 20.3, - 20.3, - 20.1, - 20, - 19.9, - 19.8, - 19.8, - 19.6, - 19.4, - 18.9, - 18.4, - 18.2, - 18, - 17.7, - 16.9, - 16.8, - 16.3, - 15.8, - 15.6, - 15.5, - 15.7, - 15.9, - 16.2, - 16.1, - 16, - 38.2, - 38.5, - 38.8, - 39, - 39.1, - 39.2, - 39.1, - 39, - 39, - 39, - 39.1, - 39.1, - 39.1, - 39.1, - 39.2, - 39.3, - 39.3, - 39.4, - 39.4, - 39.5, - 39.7, - 39.8, - 39.8, - 39.8, - 39.8, - 39.9, - 39.9, - 40, - 40.2, - 40.4, - 40.6, - 40.8, - 41, - 41, - 41, - 40.8, - 40.8, - 40.8, - 40.6, - 40.4, - 40.4, - 40.3, - 40.1, - 39.9, - 39.7, - 39.7, - 39.7, - 39.6, - 39.3, - 39.2, - 39, - 38.9, - 38.8, - 38.7, - 38.6, - 38.5, - 38.5, - 38.5, - 38.5, - 38.6, - 38.4, - 38.3, - 38.2, - 38.1, - 38.1, - 38, - 37.9, - 37.8, - 37.7, - 37.4, - 37.3, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.8, - 36.7, - 36.5, - 36.4, - 36.2, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 35, - 35, - 34.9, - 34.9, - 34.8, - 34.8, - 34.7, - 34.6, - 34.6, - 34.6, - 34.4, - 34.3, - 34.2, - 34.1, - 34, - 33.9, - 33.9, - 33.9, - 33.9, - 33.8, - 33.6, - 33.5, - 33.3, - 33.3, - 33.3, - 33.3, - 33.1, - 32.9, - 32.7, - 32.5, - 32.4, - 32.3, - 32.2, - 32.2, - 32, - 32, - 31.7, - 31.5, - 31.3, - 31.2, - 31.2, - 31, - 31, - 31, - 30.6, - 30.5, - 30.4, - 30.3, - 30.3, - 30.3, - 30.3, - 30.3, - 30.2, - 30.2, - 30.2, - 30.1, - 30.1, - 30, - 29.9, - 29.8, - 29.8, - 29.7, - 29.7, - 29.7, - 29.6, - 29.6, - 29.6, - 29.6, - 29.5, - 29.4, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 28.8, - 28.7, - 28.5, - 28.3, - 28.2, - 28.1, - 27.9, - 27.8, - 27.7, - 27.6, - 27.5, - 27.4, - 27.2, - 27.1, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.4, - 26.2, - 26.1, - 26, - 25.8, - 25.7, - 25.6, - 25.5, - 25.4, - 25.2, - 25, - 24.9, - 24.7, - 24.5, - 24.3, - 24.1, - 24.1, - 23.9, - 23.8, - 23.8, - 23.7, - 23.6, - 23.4, - 23.1, - 23, - 23, - 22.9, - 22.8, - 22.6, - 22.4, - 22.3, - 22.2, - 22.1, - 22, - 21.9, - 21.7, - 21.6, - 21.3, - 21.1, - 20.9, - 20.9, - 20.8, - 20.7, - 20.6, - 20.5, - 20.4, - 20.3, - 20.1, - 20, - 19.9, - 19.9, - 19.9, - 19.8, - 19.8, - 19.7, - 19, - 18.3, - 18.1, - 18.1, - 17.2, - 16.5, - 16.2, - 16.1, - 15.9, - 15.9, - 15.9, - 16, - 16.3, - 16.4, - 16.2, - 38, - 38.4, - 38.7, - 38.9, - 39.1, - 39.1, - 38.9, - 38.8, - 38.9, - 39, - 39.1, - 39.1, - 39.1, - 39.2, - 39.3, - 39.5, - 39.6, - 39.6, - 39.6, - 39.7, - 39.8, - 39.8, - 39.9, - 39.8, - 39.9, - 40, - 40.1, - 40.2, - 40.4, - 40.6, - 40.9, - 41, - 41, - 41, - 41, - 40.9, - 40.9, - 40.8, - 40.6, - 40.5, - 40.4, - 40.3, - 40.2, - 40, - 39.8, - 39.8, - 39.8, - 39.7, - 39.4, - 39.2, - 39.1, - 39, - 38.9, - 38.8, - 38.8, - 38.7, - 38.6, - 38.6, - 38.5, - 38.6, - 38.6, - 38.4, - 38.3, - 38.2, - 38.2, - 38.1, - 37.9, - 37.8, - 37.7, - 37.5, - 37.4, - 37.3, - 37.2, - 37.1, - 37, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.5, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 35, - 35, - 35, - 34.9, - 34.9, - 34.9, - 34.8, - 34.7, - 34.7, - 34.6, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 33.9, - 33.9, - 34, - 34, - 33.8, - 33.6, - 33.5, - 33.5, - 33.5, - 33.4, - 33.3, - 33.2, - 33, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32, - 31.8, - 31.6, - 31.5, - 31.4, - 31.3, - 31, - 31, - 31.1, - 30.8, - 30.6, - 30.5, - 30.5, - 30.4, - 30.4, - 30.3, - 30.3, - 30.3, - 30.3, - 30.3, - 30.3, - 30.2, - 30.1, - 30, - 30, - 29.9, - 29.9, - 29.8, - 29.8, - 29.7, - 29.7, - 29.7, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.3, - 28.2, - 28.1, - 28, - 27.8, - 27.7, - 27.5, - 27.4, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.2, - 26.1, - 25.9, - 25.8, - 25.7, - 25.6, - 25.4, - 25.4, - 25.2, - 25, - 24.8, - 24.6, - 24.4, - 24.3, - 24.2, - 24.1, - 24, - 23.9, - 23.9, - 23.7, - 23.5, - 23.2, - 23.1, - 23, - 23, - 22.9, - 22.7, - 22.6, - 22.5, - 22.3, - 22.2, - 22.1, - 22, - 21.9, - 21.8, - 21.6, - 21.3, - 21.2, - 21.1, - 21, - 20.9, - 20.8, - 20.7, - 20.6, - 20.5, - 20.3, - 20.2, - 20, - 19.9, - 20, - 20, - 20, - 20, - 19.7, - 18.7, - 18.3, - 18.3, - 18.2, - 17.7, - 17.2, - 16.9, - 16.7, - 16.7, - 16.7, - 16.2, - 16.5, - 16.6, - 16.5, - 37.9, - 38.3, - 38.6, - 38.8, - 38.9, - 38.8, - 38.7, - 38.7, - 38.9, - 39, - 39.1, - 39.1, - 39.2, - 39.3, - 39.6, - 39.7, - 39.7, - 39.8, - 39.8, - 39.7, - 39.9, - 40, - 40, - 40, - 40.1, - 40.1, - 40.2, - 40.3, - 40.5, - 40.7, - 40.8, - 41, - 41.1, - 41.2, - 41.2, - 41.1, - 41, - 40.8, - 40.6, - 40.5, - 40.4, - 40.3, - 40.2, - 40.1, - 39.9, - 39.8, - 39.8, - 39.7, - 39.5, - 39.4, - 39.3, - 39.2, - 39.1, - 39, - 38.9, - 38.8, - 38.7, - 38.7, - 38.6, - 38.9, - 39, - 38.6, - 38.4, - 38.3, - 38.3, - 38.1, - 38.1, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37.1, - 37.1, - 37, - 37, - 36.9, - 36.8, - 36.6, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 35.1, - 35.1, - 35, - 34.9, - 35, - 34.9, - 34.9, - 34.9, - 34.8, - 34.6, - 34.5, - 34.4, - 34.4, - 34.2, - 34.1, - 34, - 34, - 34, - 33.9, - 33.8, - 33.7, - 33.6, - 33.6, - 33.6, - 33.4, - 33.4, - 33.3, - 33, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.1, - 32, - 31.8, - 31.6, - 31.6, - 31.5, - 31.3, - 31.1, - 31.1, - 31, - 30.8, - 30.7, - 30.6, - 30.5, - 30.5, - 30.5, - 30.4, - 30.4, - 30.4, - 30.3, - 30.3, - 30.3, - 30.3, - 30.3, - 30.1, - 30.1, - 30, - 29.9, - 29.9, - 29.9, - 29.8, - 29.8, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 28.9, - 28.7, - 28.6, - 28.5, - 28.4, - 28.3, - 28.1, - 28, - 27.9, - 27.7, - 27.6, - 27.5, - 27.3, - 27.2, - 27.1, - 26.9, - 26.9, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.2, - 26.1, - 25.9, - 25.8, - 25.6, - 25.5, - 25.4, - 25.3, - 25.1, - 24.9, - 24.8, - 24.6, - 24.4, - 24.3, - 24.3, - 24.2, - 24.1, - 24, - 23.8, - 23.6, - 23.4, - 23.3, - 23.2, - 23.1, - 23, - 22.9, - 22.8, - 22.6, - 22.5, - 22.4, - 22.3, - 22.3, - 22.1, - 22, - 21.8, - 21.6, - 21.4, - 21.3, - 21.2, - 21.1, - 21, - 20.9, - 20.7, - 20.6, - 20.5, - 20.3, - 20.1, - 20, - 20.1, - 20, - 19.9, - 20, - 19.8, - 19.3, - 18.7, - 18.4, - 18.4, - 18.3, - 17.8, - 17.5, - 17.4, - 17.3, - 17.2, - 16.8, - 16.7, - 16.7, - 16.6, - 38, - 38.3, - 38.5, - 38.6, - 38.5, - 38.5, - 38.6, - 38.8, - 39, - 39, - 39.1, - 39.2, - 39.4, - 39.6, - 39.8, - 40, - 40, - 40, - 40, - 39.9, - 40, - 40.2, - 40.1, - 40.2, - 40.3, - 40.3, - 40.4, - 40.5, - 40.7, - 40.8, - 41, - 41.2, - 41.2, - 41.3, - 41.3, - 41.2, - 40.9, - 40.8, - 40.7, - 40.6, - 40.5, - 40.5, - 40.3, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.4, - 39.3, - 39.1, - 39.1, - 39, - 38.9, - 38.8, - 38.8, - 39, - 38.9, - 38.6, - 38.5, - 38.4, - 38.3, - 38.2, - 38.2, - 38.1, - 37.9, - 37.8, - 37.7, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37.2, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.6, - 36.4, - 36.2, - 36.1, - 36, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.3, - 35.2, - 35.2, - 35.2, - 35.2, - 35.1, - 35, - 35, - 35, - 34.9, - 34.9, - 34.8, - 34.7, - 34.6, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 34.1, - 34.1, - 34, - 33.8, - 33.7, - 33.7, - 33.7, - 33.6, - 33.5, - 33.5, - 33.4, - 33, - 32.8, - 32.7, - 32.6, - 32.5, - 32.4, - 32.2, - 31.9, - 31.8, - 31.7, - 31.6, - 31.4, - 31.2, - 31.2, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.7, - 30.7, - 30.6, - 30.6, - 30.5, - 30.5, - 30.4, - 30.3, - 30.3, - 30.4, - 30.2, - 30.1, - 30.1, - 30.2, - 30.1, - 30, - 30, - 29.9, - 29.9, - 29.8, - 29.8, - 29.7, - 29.5, - 29.3, - 29.2, - 29.2, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.4, - 28.2, - 28.1, - 28, - 27.9, - 27.7, - 27.6, - 27.4, - 27.3, - 27.2, - 27, - 27, - 26.9, - 26.7, - 26.7, - 26.5, - 26.5, - 26.3, - 26.2, - 26, - 25.8, - 25.7, - 25.6, - 25.6, - 25.4, - 25.3, - 25.1, - 25, - 24.8, - 24.6, - 24.5, - 24.4, - 24.4, - 24.2, - 24.1, - 24, - 23.8, - 23.7, - 23.6, - 23.5, - 23.3, - 23.2, - 23.1, - 22.9, - 22.8, - 22.7, - 22.6, - 22.5, - 22.4, - 22.2, - 22.1, - 21.9, - 21.7, - 21.6, - 21.5, - 21.5, - 21.4, - 21.2, - 21.1, - 21, - 20.8, - 20.7, - 20.5, - 20.4, - 20.2, - 20.1, - 20, - 20, - 19.9, - 19.9, - 19.6, - 19.1, - 18.7, - 18.7, - 18.7, - 18.1, - 17.7, - 17.7, - 17.6, - 17.4, - 17.2, - 17, - 16.9, - 16.8, - 37.9, - 38.1, - 38.3, - 38.4, - 38.4, - 38.4, - 38.6, - 38.8, - 39, - 39.1, - 39.2, - 39.3, - 39.6, - 39.8, - 40, - 40.1, - 40.2, - 40.3, - 40.3, - 40.1, - 40.2, - 40.4, - 40.3, - 40.3, - 40.4, - 40.4, - 40.6, - 40.8, - 40.9, - 41.1, - 41.2, - 41.3, - 41.3, - 41.2, - 41.3, - 41.1, - 40.9, - 40.8, - 40.8, - 40.7, - 40.7, - 40.7, - 40.5, - 40.3, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.4, - 39.2, - 39.1, - 39.1, - 39, - 38.9, - 38.8, - 38.8, - 38.7, - 38.6, - 38.5, - 38.4, - 38.4, - 38.3, - 38.2, - 38.1, - 38, - 38, - 37.9, - 37.9, - 37.8, - 37.7, - 37.5, - 37.4, - 37.3, - 37.3, - 37.3, - 37.2, - 37.1, - 37, - 37, - 36.8, - 36.6, - 36.5, - 36.2, - 36.1, - 35.9, - 35.9, - 35.7, - 35.6, - 35.5, - 35.4, - 35.4, - 35.4, - 35.3, - 35.3, - 35.2, - 35.2, - 35.1, - 35, - 35, - 35, - 35, - 34.9, - 34.8, - 34.7, - 34.6, - 34.5, - 34.4, - 34.4, - 34.3, - 34.2, - 34.1, - 34, - 33.9, - 33.8, - 33.8, - 33.7, - 33.6, - 33.6, - 33.6, - 33.4, - 33.1, - 32.9, - 32.8, - 32.7, - 32.5, - 32.3, - 32.1, - 31.9, - 31.8, - 31.7, - 31.5, - 31.3, - 31.3, - 31.2, - 31.2, - 31, - 30.9, - 30.9, - 30.9, - 30.8, - 30.7, - 30.7, - 30.7, - 30.6, - 30.5, - 30.5, - 30.4, - 30.3, - 30.3, - 30.2, - 30.2, - 30.3, - 30.2, - 30.2, - 30.2, - 30.1, - 30, - 29.9, - 29.9, - 29.8, - 29.6, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.4, - 28.2, - 28.1, - 28, - 27.9, - 27.7, - 27.5, - 27.4, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.2, - 26, - 25.8, - 25.7, - 25.7, - 25.6, - 25.4, - 25.2, - 25.1, - 25, - 24.8, - 24.7, - 24.6, - 24.6, - 24.4, - 24.3, - 24.2, - 24.1, - 24, - 23.9, - 23.9, - 23.7, - 23.5, - 23.3, - 23.2, - 23, - 23, - 22.9, - 22.7, - 22.5, - 22.3, - 22.1, - 22, - 21.9, - 21.8, - 21.7, - 21.7, - 21.5, - 21.4, - 21.2, - 21.1, - 21, - 20.9, - 20.9, - 20.9, - 20.6, - 20.2, - 20, - 20, - 20, - 20.1, - 20, - 19.5, - 19, - 19, - 18.9, - 18.6, - 18.3, - 18, - 17.7, - 17.6, - 17.4, - 17.2, - 17.1, - 17, - 37.7, - 37.9, - 38.1, - 38.3, - 38.3, - 38.3, - 38.6, - 38.9, - 39, - 39.1, - 39.3, - 39.5, - 39.8, - 40.1, - 40.2, - 40.3, - 40.4, - 40.5, - 40.5, - 40.4, - 40.4, - 40.5, - 40.5, - 40.5, - 40.6, - 40.7, - 40.8, - 41, - 41.1, - 41.2, - 41.3, - 41.3, - 41.3, - 41.4, - 41.4, - 41.2, - 41, - 40.9, - 40.8, - 40.7, - 40.8, - 40.7, - 40.6, - 40.4, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.3, - 39.2, - 39.1, - 39.1, - 39, - 38.9, - 38.8, - 38.7, - 38.7, - 38.6, - 38.5, - 38.4, - 38.4, - 38.3, - 38.2, - 38.2, - 38.2, - 38.1, - 38.2, - 38.1, - 37.9, - 37.7, - 37.6, - 37.5, - 37.5, - 37.5, - 37.4, - 37.3, - 37.2, - 37.1, - 37, - 36.8, - 36.7, - 36.5, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.5, - 35.5, - 35.5, - 35.4, - 35.4, - 35.3, - 35.3, - 35.2, - 35.1, - 35, - 35, - 35, - 35, - 34.9, - 34.8, - 34.6, - 34.6, - 34.5, - 34.4, - 34.3, - 34.3, - 34.2, - 34, - 33.9, - 33.8, - 33.8, - 33.8, - 33.7, - 33.7, - 33.6, - 33.4, - 33.2, - 33, - 32.9, - 32.6, - 32.5, - 32.3, - 32, - 31.9, - 31.8, - 31.6, - 31.4, - 31.4, - 31.3, - 31.2, - 31.2, - 31.1, - 31, - 31, - 30.9, - 30.8, - 30.8, - 30.7, - 30.6, - 30.6, - 30.5, - 30.5, - 30.4, - 30.5, - 30.4, - 30.3, - 30.3, - 30.2, - 30.3, - 30.2, - 30.2, - 30.1, - 30, - 30, - 29.9, - 29.7, - 29.6, - 29.4, - 29.4, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.3, - 28.2, - 28.2, - 28, - 27.8, - 27.6, - 27.4, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.1, - 25.9, - 25.9, - 26, - 25.8, - 25.7, - 25.4, - 25.3, - 25.2, - 25.1, - 25.1, - 25, - 24.8, - 24.7, - 24.6, - 24.5, - 24.4, - 24.3, - 24.2, - 24.1, - 24.1, - 23.9, - 23.6, - 23.5, - 23.3, - 23.2, - 23, - 22.8, - 22.6, - 22.4, - 22.2, - 22.2, - 22.1, - 22, - 22, - 21.9, - 21.7, - 21.6, - 21.5, - 21.3, - 21.1, - 21, - 21.2, - 21.3, - 21.2, - 20.8, - 20.3, - 20, - 20, - 20, - 20.1, - 19.9, - 19.4, - 19.1, - 19.1, - 18.8, - 18.6, - 18.4, - 18.1, - 17.8, - 17.6, - 17.4, - 17.2, - 17.1, - 37.5, - 37.7, - 37.9, - 38.2, - 38.1, - 38.3, - 38.8, - 38.9, - 39, - 39.1, - 39.4, - 39.6, - 40, - 40.3, - 40.3, - 40.4, - 40.5, - 40.7, - 40.6, - 40.6, - 40.6, - 40.6, - 40.7, - 40.7, - 40.7, - 40.9, - 41.1, - 41.2, - 41.3, - 41.3, - 41.5, - 41.5, - 41.4, - 41.5, - 41.5, - 41.3, - 41.1, - 41, - 40.9, - 40.8, - 40.8, - 40.8, - 40.7, - 40.5, - 40.4, - 40.3, - 40.1, - 39.9, - 39.8, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.3, - 39.2, - 39.1, - 39, - 38.9, - 38.8, - 38.8, - 38.7, - 38.7, - 38.6, - 38.5, - 38.5, - 38.5, - 38.4, - 38.4, - 38.4, - 38.3, - 38.3, - 38.3, - 38.1, - 38, - 37.9, - 37.8, - 37.7, - 37.7, - 37.5, - 37.4, - 37.3, - 37.2, - 37.1, - 36.9, - 36.7, - 36.5, - 36.3, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.7, - 35.7, - 35.6, - 35.6, - 35.6, - 35.7, - 35.6, - 35.4, - 35.3, - 35.2, - 35.1, - 35.1, - 35.1, - 35.1, - 35, - 34.9, - 34.8, - 34.7, - 34.6, - 34.6, - 34.5, - 34.4, - 34.3, - 34.1, - 34, - 33.9, - 33.9, - 33.9, - 33.7, - 33.6, - 33.7, - 33.7, - 33.5, - 33.3, - 33.1, - 32.9, - 32.8, - 32.5, - 32.2, - 32.1, - 31.9, - 31.7, - 31.6, - 31.5, - 31.5, - 31.4, - 31.3, - 31.2, - 31.2, - 31.1, - 31, - 30.9, - 30.9, - 30.8, - 30.8, - 30.7, - 30.8, - 30.7, - 30.5, - 30.5, - 30.5, - 30.5, - 30.4, - 30.4, - 30.4, - 30.4, - 30.3, - 30.2, - 30.2, - 30.1, - 29.9, - 29.8, - 29.7, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 28.9, - 28.8, - 28.7, - 28.7, - 28.5, - 28.3, - 28.2, - 28.1, - 27.8, - 27.6, - 27.5, - 27.4, - 27.4, - 27.3, - 27.3, - 27.2, - 27, - 26.9, - 26.8, - 26.6, - 26.4, - 26.4, - 26.2, - 26.1, - 26, - 26.1, - 26.1, - 25.9, - 25.7, - 25.5, - 25.4, - 25.5, - 25.4, - 25.3, - 25.2, - 25, - 24.9, - 24.8, - 24.8, - 24.8, - 24.7, - 24.6, - 24.3, - 24.1, - 24, - 23.8, - 23.6, - 23.4, - 23.2, - 23, - 22.8, - 22.6, - 22.5, - 22.4, - 22.4, - 22.3, - 22.2, - 22.1, - 22, - 21.9, - 21.8, - 21.6, - 21.4, - 21.3, - 21.4, - 21.4, - 21.4, - 21.2, - 20.8, - 20.3, - 20.1, - 20.1, - 20, - 20, - 19.7, - 19.5, - 19.3, - 19.1, - 18.8, - 18.6, - 18.4, - 18.1, - 17.8, - 17.6, - 17.5, - 17.3, - 37.3, - 37.6, - 37.9, - 38, - 38.1, - 38.5, - 38.8, - 38.9, - 39, - 39.2, - 39.5, - 39.8, - 40.2, - 40.4, - 40.5, - 40.8, - 40.8, - 40.9, - 40.9, - 40.9, - 40.9, - 40.9, - 40.9, - 40.8, - 40.8, - 41, - 41.2, - 41.4, - 41.5, - 41.5, - 41.6, - 41.7, - 41.6, - 41.5, - 41.5, - 41.5, - 41.3, - 41.1, - 41, - 40.9, - 40.9, - 40.9, - 40.8, - 40.7, - 40.5, - 40.4, - 40.3, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.3, - 39.2, - 39.1, - 39, - 38.9, - 38.9, - 38.8, - 38.8, - 38.7, - 38.7, - 38.7, - 38.6, - 38.7, - 38.6, - 38.7, - 38.5, - 38.4, - 38.3, - 38.3, - 38.2, - 38.1, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37, - 36.8, - 36.6, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.8, - 35.8, - 35.8, - 35.8, - 35.8, - 35.8, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.3, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.7, - 34.7, - 34.6, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 34, - 34, - 33.9, - 33.8, - 33.9, - 33.8, - 33.7, - 33.5, - 33.3, - 33.1, - 33, - 32.7, - 32.4, - 32.2, - 32, - 31.8, - 31.7, - 31.6, - 31.6, - 31.5, - 31.4, - 31.3, - 31.3, - 31.3, - 31.2, - 31.1, - 31.1, - 31.1, - 31, - 30.9, - 30.8, - 30.8, - 30.8, - 30.7, - 30.7, - 30.6, - 30.6, - 30.5, - 30.4, - 30.4, - 30.4, - 30.3, - 30.3, - 30.3, - 30.2, - 30, - 29.8, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.4, - 28.2, - 27.9, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 27, - 26.9, - 26.6, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 26, - 26, - 26, - 25.9, - 25.7, - 25.7, - 25.6, - 25.5, - 25.6, - 25.3, - 25.1, - 25, - 25.1, - 25.1, - 25, - 24.9, - 24.6, - 24.4, - 24.3, - 24.1, - 23.9, - 23.6, - 23.4, - 23.3, - 23.1, - 22.9, - 22.8, - 22.7, - 22.6, - 22.6, - 22.4, - 22.3, - 22.2, - 22.1, - 22, - 21.8, - 21.5, - 21.4, - 21.4, - 21.4, - 21.4, - 21.3, - 21.1, - 20.7, - 20.3, - 20.2, - 20.1, - 20.1, - 20.1, - 19.9, - 19.6, - 19.4, - 19.2, - 18.8, - 18.6, - 18.3, - 18.1, - 17.9, - 17.7, - 17.5, - 37.1, - 37.5, - 37.8, - 37.9, - 38.1, - 38.6, - 38.8, - 38.9, - 39, - 39.3, - 39.6, - 40, - 40.3, - 40.5, - 41, - 41.2, - 41.2, - 41.2, - 41.2, - 41.1, - 41.1, - 41.2, - 41.2, - 41, - 40.9, - 41.1, - 41.4, - 41.6, - 41.6, - 41.6, - 41.7, - 41.8, - 41.9, - 41.8, - 41.6, - 41.5, - 41.4, - 41.2, - 41.1, - 41, - 41, - 41, - 40.9, - 40.8, - 40.5, - 40.5, - 40.4, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.4, - 39.4, - 39.3, - 39.2, - 39.1, - 39, - 39, - 38.9, - 38.9, - 38.8, - 38.8, - 38.8, - 38.8, - 38.8, - 38.8, - 38.9, - 38.7, - 38.5, - 38.4, - 38.3, - 38.3, - 38.3, - 38.2, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.4, - 37.2, - 37, - 36.8, - 36.7, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 36, - 36, - 36, - 35.9, - 35.9, - 36, - 35.9, - 35.8, - 35.6, - 35.6, - 35.6, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 35.1, - 34.9, - 34.8, - 34.7, - 34.7, - 34.7, - 34.6, - 34.4, - 34.4, - 34.3, - 34.2, - 34.1, - 34, - 34, - 34, - 34, - 33.8, - 33.6, - 33.4, - 33.3, - 33.2, - 32.9, - 32.6, - 32.3, - 32.1, - 32, - 31.8, - 31.7, - 31.7, - 31.6, - 31.5, - 31.5, - 31.4, - 31.3, - 31.3, - 31.2, - 31.2, - 31.2, - 31.1, - 31.1, - 31, - 30.9, - 30.8, - 30.8, - 30.8, - 30.8, - 30.8, - 30.6, - 30.6, - 30.5, - 30.5, - 30.4, - 30.4, - 30.4, - 30.4, - 30.2, - 30, - 29.9, - 29.8, - 29.7, - 29.5, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.5, - 28.2, - 28, - 27.8, - 27.7, - 27.6, - 27.6, - 27.6, - 27.5, - 27.4, - 27.3, - 27.1, - 27.1, - 27, - 26.8, - 26.5, - 26.3, - 26.2, - 26.2, - 26.1, - 26, - 26, - 26, - 26.1, - 25.9, - 25.7, - 25.7, - 25.7, - 25.8, - 26, - 25.7, - 25.4, - 25.5, - 25.4, - 25.2, - 25.1, - 24.9, - 24.7, - 24.6, - 24.3, - 24.1, - 24, - 23.9, - 23.7, - 23.6, - 23.3, - 23.1, - 22.9, - 22.8, - 22.7, - 22.7, - 22.6, - 22.5, - 22.2, - 22.1, - 21.9, - 21.7, - 21.5, - 21.4, - 21.4, - 21.5, - 21.4, - 21.2, - 21, - 20.7, - 20.5, - 20.3, - 20.2, - 20.2, - 20.2, - 19.9, - 19.6, - 19.3, - 19, - 18.8, - 18.6, - 18.4, - 18.1, - 17.9, - 17.7, - 37, - 37.4, - 37.6, - 37.9, - 38.3, - 38.7, - 38.8, - 38.9, - 39.1, - 39.5, - 39.8, - 40.2, - 40.5, - 40.7, - 41.1, - 41.3, - 41.4, - 41.4, - 41.4, - 41.3, - 41.2, - 41.3, - 41.3, - 41.2, - 41.2, - 41.3, - 41.5, - 41.7, - 41.7, - 41.8, - 42, - 42.1, - 42.1, - 42, - 41.8, - 41.6, - 41.5, - 41.4, - 41.3, - 41.1, - 41, - 41, - 40.9, - 40.8, - 40.6, - 40.5, - 40.4, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.3, - 39.2, - 39.2, - 39.1, - 39.1, - 39, - 39, - 39, - 39, - 39, - 39, - 39, - 39, - 39, - 38.8, - 38.7, - 38.5, - 38.4, - 38.4, - 38.5, - 38.4, - 38.3, - 38, - 37.8, - 37.6, - 37.5, - 37.4, - 37.4, - 37.2, - 36.8, - 36.8, - 36.7, - 36.6, - 36.5, - 36.3, - 36.2, - 36.2, - 36.1, - 36.1, - 36, - 36, - 36, - 36, - 36, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.3, - 35.3, - 35.2, - 35.2, - 35.1, - 35, - 34.9, - 34.9, - 34.8, - 34.7, - 34.6, - 34.4, - 34.3, - 34.3, - 34.2, - 34.1, - 34.1, - 34.1, - 34, - 34, - 33.7, - 33.5, - 33.5, - 33.2, - 33.1, - 32.8, - 32.5, - 32.3, - 32.1, - 31.9, - 31.8, - 31.8, - 31.7, - 31.7, - 31.6, - 31.5, - 31.5, - 31.4, - 31.3, - 31.2, - 31.1, - 31.1, - 31.2, - 31.2, - 31.1, - 31, - 30.9, - 30.9, - 30.9, - 30.9, - 30.9, - 30.8, - 30.7, - 30.7, - 30.6, - 30.5, - 30.5, - 30.5, - 30.3, - 30.1, - 30, - 29.9, - 29.8, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.6, - 28.4, - 28.1, - 28, - 27.9, - 27.8, - 27.8, - 27.7, - 27.6, - 27.6, - 27.5, - 27.3, - 27.1, - 27, - 26.9, - 26.7, - 26.5, - 26.3, - 26.3, - 26.3, - 26.2, - 26.1, - 26.1, - 26.1, - 25.9, - 25.8, - 25.8, - 25.7, - 25.9, - 26, - 25.9, - 25.7, - 25.9, - 25.6, - 25.4, - 25.3, - 25.1, - 24.9, - 24.7, - 24.6, - 24.4, - 24.4, - 24.4, - 24.1, - 24, - 23.8, - 23.2, - 23.1, - 23, - 22.9, - 22.8, - 22.8, - 22.6, - 22.4, - 22.2, - 22, - 22, - 21.9, - 21.7, - 21.6, - 21.5, - 21.5, - 21.4, - 21.2, - 20.9, - 20.8, - 20.7, - 20.6, - 20.5, - 20.2, - 20, - 19.8, - 19.6, - 19.3, - 19, - 18.8, - 18.6, - 18.5, - 18.2, - 17.9, - 36.8, - 37.2, - 37.5, - 38, - 38.4, - 38.7, - 38.8, - 38.9, - 39.3, - 39.6, - 40, - 40.3, - 40.5, - 40.7, - 40.9, - 41.2, - 41.4, - 41.5, - 41.4, - 41.4, - 41.4, - 41.5, - 41.4, - 41.5, - 41.7, - 41.7, - 41.7, - 41.8, - 41.9, - 42.1, - 42.2, - 42.2, - 42.1, - 42, - 41.9, - 41.8, - 41.6, - 41.6, - 41.4, - 41.2, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.6, - 40.4, - 40.3, - 40.2, - 40, - 40, - 39.8, - 39.7, - 39.7, - 39.6, - 39.5, - 39.4, - 39.3, - 39.3, - 39.2, - 39.2, - 39.2, - 39.1, - 39.1, - 39.1, - 39.2, - 39.3, - 39.3, - 39.2, - 39.1, - 38.9, - 38.8, - 38.6, - 38.5, - 38.5, - 38.5, - 38.5, - 38.3, - 38.1, - 37.9, - 37.7, - 37.6, - 37.4, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.3, - 36.3, - 36.2, - 36.2, - 36.1, - 36.1, - 36, - 35.9, - 36, - 36, - 35.9, - 35.8, - 35.8, - 35.7, - 35.6, - 35.4, - 35.4, - 35.4, - 35.4, - 35.3, - 35.2, - 35.2, - 35.2, - 35.1, - 34.9, - 34.8, - 34.6, - 34.4, - 34.3, - 34.3, - 34.2, - 34.2, - 34.2, - 34.1, - 34, - 33.8, - 33.6, - 33.5, - 33.3, - 33.1, - 32.9, - 32.6, - 32.4, - 32.2, - 32, - 31.9, - 31.9, - 31.9, - 31.9, - 31.9, - 31.8, - 31.7, - 31.6, - 31.4, - 31.2, - 31.2, - 31.2, - 31.2, - 31.3, - 31.2, - 31.2, - 31.1, - 31.1, - 31, - 31, - 31, - 30.9, - 30.9, - 30.8, - 30.7, - 30.7, - 30.6, - 30.6, - 30.5, - 30.3, - 30.2, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.6, - 28.5, - 28.3, - 28.1, - 28, - 28, - 27.9, - 27.9, - 27.8, - 27.7, - 27.7, - 27.6, - 27.3, - 27.1, - 27.1, - 26.9, - 26.7, - 26.5, - 26.5, - 26.5, - 26.4, - 26.4, - 26.3, - 26.3, - 26.1, - 26, - 25.9, - 25.8, - 25.8, - 25.8, - 25.8, - 25.7, - 25.7, - 25.5, - 25.3, - 25.4, - 25.5, - 25.1, - 24.9, - 24.8, - 24.6, - 24.5, - 24.5, - 24.3, - 24.3, - 24.2, - 23.5, - 23.3, - 23.2, - 23.1, - 22.9, - 22.9, - 22.7, - 22.6, - 22.4, - 22.2, - 22.2, - 22.1, - 22, - 21.9, - 21.7, - 21.7, - 21.5, - 21.3, - 21.1, - 20.9, - 20.9, - 20.8, - 20.7, - 20.4, - 20.1, - 19.9, - 19.8, - 19.6, - 19.3, - 19.1, - 18.9, - 18.7, - 18.4, - 18.1, - 36.8, - 37.1, - 37.5, - 38.1, - 38.6, - 38.7, - 38.7, - 38.9, - 39.3, - 39.8, - 40.1, - 40.3, - 40.5, - 40.7, - 40.8, - 41, - 41.4, - 41.5, - 41.4, - 41.5, - 41.6, - 41.6, - 41.6, - 41.9, - 42.2, - 41.9, - 41.9, - 41.9, - 42, - 42.2, - 42.3, - 42.3, - 42.2, - 42.1, - 42, - 42, - 41.8, - 41.8, - 41.6, - 41.4, - 41.2, - 41.1, - 41, - 40.9, - 40.8, - 40.6, - 40.5, - 40.3, - 40.2, - 40, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.4, - 39.3, - 39.3, - 39.3, - 39.3, - 39.3, - 39.3, - 39.3, - 39.4, - 39.5, - 39.5, - 39.5, - 39.3, - 39, - 38.8, - 38.7, - 38.7, - 38.6, - 38.5, - 38.4, - 38.3, - 38.2, - 37.9, - 37.7, - 37.6, - 37.4, - 37.2, - 37.1, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.4, - 36.3, - 36.2, - 36.1, - 36.1, - 36.1, - 36, - 35.9, - 36, - 36.1, - 36, - 35.9, - 35.9, - 35.9, - 35.8, - 35.6, - 35.6, - 35.5, - 35.5, - 35.4, - 35.4, - 35.3, - 35.3, - 35.3, - 35.2, - 35.1, - 34.9, - 34.7, - 34.5, - 34.4, - 34.3, - 34.3, - 34.3, - 34.2, - 34.1, - 33.9, - 33.6, - 33.5, - 33.4, - 33.1, - 32.9, - 32.7, - 32.5, - 32.3, - 32.1, - 32.1, - 32, - 32, - 32, - 32, - 32, - 31.9, - 31.9, - 31.8, - 31.6, - 31.4, - 31.5, - 31.4, - 31.3, - 31.3, - 31.3, - 31.2, - 31.2, - 31.2, - 31.1, - 31.1, - 31.1, - 31, - 31, - 31, - 30.9, - 30.9, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.2, - 30, - 29.8, - 29.6, - 29.6, - 29.4, - 29.3, - 29.4, - 29.2, - 28.8, - 28.6, - 28.5, - 28.3, - 28.2, - 28.1, - 28, - 28, - 28, - 27.9, - 27.8, - 27.8, - 27.6, - 27.3, - 27.2, - 27, - 27, - 26.8, - 26.7, - 26.5, - 26.5, - 26.5, - 26.5, - 26.5, - 26.3, - 26.3, - 26.2, - 26, - 25.8, - 25.7, - 25.7, - 25.7, - 25.5, - 25.4, - 25.3, - 25.4, - 25.5, - 25.3, - 24.9, - 24.7, - 24.6, - 24.5, - 24.4, - 24.2, - 24.2, - 24.2, - 23.8, - 23.5, - 23.4, - 23.3, - 23.1, - 23.1, - 22.9, - 22.8, - 22.6, - 22.4, - 22.4, - 22.3, - 22.1, - 22, - 21.9, - 21.9, - 21.7, - 21.5, - 21.3, - 21.2, - 21, - 20.9, - 20.9, - 20.7, - 20.4, - 20.1, - 19.9, - 19.8, - 19.6, - 19.4, - 19.2, - 19.1, - 18.8, - 18.5, - 36.9, - 37.2, - 37.7, - 38.2, - 38.6, - 38.6, - 38.7, - 39, - 39.4, - 39.8, - 40.1, - 40.3, - 40.5, - 40.7, - 40.8, - 40.9, - 41.3, - 41.4, - 41.4, - 41.5, - 41.7, - 41.7, - 41.7, - 42, - 42.1, - 42, - 42, - 42.1, - 42.1, - 42.3, - 42.4, - 42.3, - 42.2, - 42.3, - 42.2, - 42.1, - 42, - 41.8, - 41.7, - 41.4, - 41.3, - 41.1, - 41, - 40.9, - 40.9, - 40.7, - 40.5, - 40.4, - 40.3, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.6, - 39.5, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.5, - 39.5, - 39.6, - 39.7, - 39.7, - 39.7, - 39.6, - 39.4, - 39, - 38.9, - 38.8, - 38.8, - 38.7, - 38.6, - 38.4, - 38.3, - 38.2, - 37.9, - 37.7, - 37.6, - 37.4, - 37.3, - 37.3, - 37.2, - 37.1, - 37, - 37, - 36.9, - 36.7, - 36.5, - 36.3, - 36.1, - 36, - 35.9, - 35.9, - 36, - 36, - 36, - 36.1, - 36.1, - 36.1, - 36.1, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.6, - 35.5, - 35.5, - 35.4, - 35.3, - 35.3, - 35.3, - 35.2, - 35, - 34.8, - 34.6, - 34.5, - 34.4, - 34.4, - 34.3, - 34.2, - 34, - 33.8, - 33.6, - 33.5, - 33.2, - 33, - 32.7, - 32.6, - 32.4, - 32.2, - 32.2, - 32.2, - 32.2, - 32.2, - 32.1, - 32.1, - 32.1, - 32, - 32, - 32, - 31.7, - 31.6, - 31.5, - 31.5, - 31.5, - 31.5, - 31.3, - 31.3, - 31.3, - 31.3, - 31.3, - 31.2, - 31.2, - 31.3, - 31.1, - 31, - 31, - 30.9, - 30.9, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.1, - 29.9, - 29.8, - 29.6, - 29.5, - 29.5, - 29.5, - 29.2, - 28.9, - 28.7, - 28.5, - 28.3, - 28.2, - 28.2, - 28.1, - 28.1, - 28, - 27.9, - 27.9, - 27.8, - 27.5, - 27.3, - 27.1, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.6, - 26.5, - 26.4, - 26.4, - 26.3, - 26.3, - 26, - 25.9, - 25.9, - 25.7, - 25.6, - 25.5, - 25.4, - 25.4, - 25.4, - 25.4, - 25.1, - 24.8, - 24.7, - 24.5, - 24.3, - 24.2, - 24.2, - 24.2, - 24, - 23.7, - 23.6, - 23.5, - 23.4, - 23.3, - 23.1, - 23, - 22.8, - 22.6, - 22.6, - 22.4, - 22.3, - 22.2, - 22.1, - 22, - 21.9, - 21.8, - 21.7, - 21.6, - 21.4, - 21.2, - 21.1, - 20.9, - 20.6, - 20.4, - 20.1, - 20, - 19.8, - 19.6, - 19.4, - 19.3, - 19.2, - 19, - 37, - 37.4, - 37.9, - 38.3, - 38.5, - 38.5, - 38.7, - 39, - 39.4, - 39.8, - 40, - 40.2, - 40.5, - 40.6, - 40.7, - 40.9, - 41.3, - 41.5, - 41.4, - 41.6, - 41.7, - 41.8, - 41.8, - 42, - 42.1, - 42.1, - 42.2, - 42.2, - 42.3, - 42.4, - 42.4, - 42.3, - 42.3, - 42.4, - 42.4, - 42.2, - 42.1, - 41.9, - 41.7, - 41.5, - 41.2, - 41.1, - 41, - 41, - 40.9, - 40.8, - 40.6, - 40.4, - 40.3, - 40.1, - 40, - 39.9, - 39.8, - 39.8, - 39.7, - 39.6, - 39.6, - 39.5, - 39.5, - 39.5, - 39.5, - 39.6, - 39.6, - 39.7, - 39.8, - 39.8, - 39.8, - 39.7, - 39.6, - 39.4, - 39.2, - 39.1, - 38.9, - 38.8, - 38.7, - 38.6, - 38.4, - 38.3, - 38.1, - 37.9, - 37.7, - 37.6, - 37.4, - 37.4, - 37.3, - 37.3, - 37.2, - 37.1, - 37.1, - 36.9, - 36.8, - 36.5, - 36.3, - 36.1, - 36, - 35.9, - 35.9, - 36, - 36, - 36, - 36.1, - 36.2, - 36.3, - 36.3, - 36.3, - 36.3, - 36.2, - 36, - 35.9, - 35.9, - 35.8, - 35.7, - 35.6, - 35.6, - 35.5, - 35.5, - 35.5, - 35.4, - 35.3, - 35.1, - 34.8, - 34.7, - 34.6, - 34.5, - 34.4, - 34.3, - 34.1, - 34.1, - 33.8, - 33.6, - 33.4, - 33.1, - 32.8, - 32.6, - 32.4, - 32.3, - 32.3, - 32.3, - 32.2, - 32.2, - 32.2, - 32.1, - 32.1, - 32.1, - 32.1, - 32, - 31.9, - 31.9, - 31.9, - 31.7, - 31.5, - 31.5, - 31.4, - 31.4, - 31.4, - 31.4, - 31.5, - 31.4, - 31.3, - 31.3, - 31.2, - 31.2, - 31.2, - 31.1, - 31.1, - 31, - 30.9, - 30.8, - 30.6, - 30.5, - 30.4, - 30.2, - 30, - 30, - 29.8, - 29.7, - 29.7, - 29.7, - 29.3, - 28.9, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.1, - 28.1, - 28, - 28.1, - 27.9, - 27.5, - 27.3, - 27.2, - 27.1, - 27.1, - 27, - 26.9, - 26.9, - 26.8, - 26.7, - 26.6, - 26.5, - 26.5, - 26.5, - 26.4, - 26.1, - 26, - 25.9, - 25.8, - 25.6, - 25.5, - 25.5, - 25.4, - 25.4, - 25.3, - 25, - 24.8, - 24.6, - 24.4, - 24.3, - 24.3, - 24.2, - 24.1, - 23.9, - 23.8, - 23.7, - 23.6, - 23.4, - 23.3, - 23.1, - 23, - 22.9, - 22.8, - 22.6, - 22.5, - 22.3, - 22.2, - 22.2, - 22.1, - 22, - 21.9, - 21.8, - 21.6, - 21.5, - 21.2, - 21, - 20.8, - 20.7, - 20.4, - 20.2, - 20, - 19.9, - 19.6, - 19.3, - 19.2, - 19.2, - 37.2, - 37.6, - 38, - 38.4, - 38.4, - 38.5, - 38.7, - 39, - 39.5, - 39.7, - 40, - 40.2, - 40.4, - 40.6, - 40.7, - 40.9, - 41.4, - 41.5, - 41.5, - 41.7, - 41.8, - 41.9, - 41.9, - 42.1, - 42.2, - 42.2, - 42.3, - 42.3, - 42.4, - 42.4, - 42.4, - 42.3, - 42.3, - 42.4, - 42.4, - 42.3, - 42.1, - 41.9, - 41.7, - 41.4, - 41.2, - 41.1, - 41, - 41, - 40.8, - 40.7, - 40.5, - 40.4, - 40.3, - 40.1, - 40, - 39.9, - 39.9, - 39.8, - 39.7, - 39.7, - 39.6, - 39.6, - 39.6, - 39.6, - 39.7, - 39.7, - 39.8, - 39.9, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.4, - 39.4, - 39.3, - 39, - 38.9, - 38.8, - 38.6, - 38.4, - 38.3, - 38.1, - 37.9, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.3, - 37.2, - 37.2, - 37.1, - 37, - 36.9, - 36.6, - 36.4, - 36.1, - 36, - 35.9, - 35.9, - 36, - 36, - 36, - 36.1, - 36.2, - 36.4, - 36.5, - 36.5, - 36.4, - 36.4, - 36.3, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.6, - 35.6, - 35.6, - 35.6, - 35.5, - 35.3, - 35.1, - 34.9, - 34.7, - 34.6, - 34.6, - 34.5, - 34.3, - 34, - 33.9, - 33.9, - 33.5, - 33.2, - 33, - 32.8, - 32.5, - 32.4, - 32.4, - 32.3, - 32.3, - 32.3, - 32.2, - 32.2, - 32.2, - 32.2, - 32.2, - 32.1, - 32.1, - 32, - 32, - 31.9, - 31.9, - 31.8, - 31.7, - 31.7, - 31.6, - 31.6, - 31.5, - 31.5, - 31.5, - 31.4, - 31.5, - 31.4, - 31.3, - 31.3, - 31.2, - 31.2, - 31.1, - 31, - 30.9, - 30.7, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 29.9, - 29.8, - 29.8, - 29.7, - 29.4, - 29.2, - 29, - 28.8, - 28.6, - 28.5, - 28.4, - 28.3, - 28.2, - 28.2, - 28.2, - 28.1, - 28, - 27.7, - 27.5, - 27.3, - 27.3, - 27.2, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.7, - 26.6, - 26.5, - 26.5, - 26.3, - 26.1, - 26, - 25.9, - 25.7, - 25.6, - 25.5, - 25.3, - 25.3, - 25.1, - 24.9, - 24.8, - 24.6, - 24.4, - 24.3, - 24.3, - 24.1, - 24, - 24, - 23.9, - 23.8, - 23.6, - 23.4, - 23.3, - 23.1, - 23.1, - 23, - 22.9, - 22.7, - 22.5, - 22.4, - 22.3, - 22.2, - 22, - 21.9, - 21.8, - 21.8, - 21.7, - 21.5, - 21.2, - 21, - 20.9, - 20.7, - 20.5, - 20.3, - 20, - 19.8, - 19.6, - 19.3, - 19.4, - 37.3, - 37.7, - 38.1, - 38.2, - 38.3, - 38.4, - 38.7, - 39.1, - 39.6, - 39.7, - 40, - 40.1, - 40.4, - 40.6, - 40.7, - 40.9, - 41.2, - 41.4, - 41.5, - 41.7, - 41.9, - 42, - 42, - 42.1, - 42.2, - 42.2, - 42.3, - 42.3, - 42.4, - 42.3, - 42.4, - 42.4, - 42.3, - 42.3, - 42.2, - 42.1, - 41.9, - 41.8, - 41.6, - 41.3, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.6, - 40.5, - 40.3, - 40.2, - 40.1, - 40.1, - 40, - 39.9, - 39.9, - 39.8, - 39.8, - 39.7, - 39.7, - 39.7, - 39.8, - 39.9, - 39.9, - 39.9, - 39.9, - 40, - 40, - 39.9, - 39.8, - 39.6, - 39.4, - 39.4, - 39.3, - 39.1, - 38.9, - 38.8, - 38.6, - 38.4, - 38.3, - 38, - 37.9, - 37.8, - 37.7, - 37.6, - 37.5, - 37.5, - 37.4, - 37.3, - 37.2, - 37.2, - 37.1, - 36.9, - 36.7, - 36.6, - 36.2, - 36, - 36, - 36, - 36, - 36.1, - 36.1, - 36.1, - 36.3, - 36.5, - 36.6, - 36.6, - 36.6, - 36.6, - 36.5, - 36.3, - 36.2, - 36, - 36, - 35.9, - 35.8, - 35.8, - 35.7, - 35.7, - 35.7, - 35.6, - 35.4, - 35.2, - 35, - 34.9, - 34.8, - 34.6, - 34.5, - 34.4, - 34.1, - 34, - 34, - 33.6, - 33.3, - 33.1, - 32.9, - 32.8, - 32.5, - 32.4, - 32.4, - 32.3, - 32.3, - 32.3, - 32.3, - 32.3, - 32.3, - 32.2, - 32.2, - 32.2, - 32.2, - 32.1, - 32, - 32, - 31.9, - 31.9, - 32, - 31.9, - 31.8, - 31.7, - 31.7, - 31.6, - 31.6, - 31.6, - 31.6, - 31.5, - 31.5, - 31.4, - 31.4, - 31.3, - 31.2, - 31.1, - 31, - 30.8, - 30.7, - 30.6, - 30.5, - 30.4, - 30.2, - 30, - 29.9, - 29.8, - 29.7, - 29.5, - 29.3, - 29.1, - 28.9, - 28.9, - 28.7, - 28.5, - 28.4, - 28.3, - 28.3, - 28.3, - 28.3, - 28.2, - 27.9, - 27.7, - 27.6, - 27.5, - 27.4, - 27.3, - 27.3, - 27.2, - 27, - 26.8, - 26.8, - 26.7, - 26.7, - 26.6, - 26.6, - 26.3, - 26.2, - 26.1, - 26.1, - 26, - 25.6, - 25.5, - 25.4, - 25.3, - 25.1, - 24.9, - 24.8, - 24.6, - 24.4, - 24.3, - 24.3, - 24.1, - 24.1, - 24, - 24.1, - 24, - 23.7, - 23.4, - 23.3, - 23.2, - 23.2, - 23.2, - 22.9, - 22.7, - 22.6, - 22.5, - 22.3, - 22.1, - 22, - 21.9, - 21.9, - 21.9, - 21.8, - 21.6, - 21.1, - 21, - 20.9, - 20.7, - 20.6, - 20.5, - 20.2, - 19.9, - 19.5, - 19.3, - 37.3, - 37.7, - 38, - 38.1, - 38.2, - 38.4, - 38.7, - 39.2, - 39.6, - 39.8, - 40, - 40.1, - 40.4, - 40.6, - 40.9, - 41.1, - 41.2, - 41.3, - 41.6, - 41.8, - 42, - 42, - 42.1, - 42.2, - 42.3, - 42.3, - 42.4, - 42.5, - 42.4, - 42.5, - 42.6, - 42.5, - 42.4, - 42.2, - 42, - 41.9, - 41.8, - 41.5, - 41.3, - 41.1, - 41, - 40.8, - 40.7, - 40.7, - 40.6, - 40.5, - 40.4, - 40.3, - 40.2, - 40.2, - 40.1, - 40.1, - 40, - 40, - 39.9, - 39.9, - 39.8, - 39.9, - 39.9, - 40.1, - 40.2, - 40.2, - 40.1, - 40, - 40, - 40.1, - 40, - 39.9, - 39.7, - 39.4, - 39.4, - 39.3, - 39.1, - 38.9, - 38.8, - 38.6, - 38.3, - 38.1, - 38, - 37.9, - 37.8, - 37.7, - 37.7, - 37.6, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37.1, - 37.1, - 37, - 36.8, - 36.4, - 36.2, - 36.1, - 36.2, - 36.2, - 36.2, - 36.2, - 36.3, - 36.5, - 36.7, - 36.7, - 36.7, - 36.7, - 36.6, - 36.7, - 36.6, - 36.4, - 36.2, - 36.2, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.8, - 35.7, - 35.5, - 35.4, - 35.2, - 35, - 34.9, - 34.7, - 34.6, - 34.5, - 34.3, - 34.1, - 34, - 33.8, - 33.5, - 33.2, - 33.1, - 32.9, - 32.7, - 32.5, - 32.5, - 32.5, - 32.4, - 32.4, - 32.5, - 32.5, - 32.4, - 32.4, - 32.4, - 32.3, - 32.3, - 32.3, - 32.2, - 32.1, - 32.1, - 32.1, - 32.1, - 32, - 32, - 31.9, - 31.9, - 31.9, - 31.9, - 31.8, - 31.8, - 31.7, - 31.7, - 31.7, - 31.6, - 31.5, - 31.4, - 31.3, - 31.2, - 31.1, - 30.9, - 30.7, - 30.6, - 30.6, - 30.4, - 30.2, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 28.9, - 28.8, - 28.7, - 28.6, - 28.4, - 28.3, - 28.4, - 28.4, - 28.3, - 28, - 27.9, - 27.7, - 27.7, - 27.6, - 27.5, - 27.4, - 27.3, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.7, - 26.5, - 26.3, - 26.2, - 26.2, - 26.2, - 25.8, - 25.7, - 25.6, - 25.4, - 25.3, - 25.1, - 24.9, - 24.7, - 24.6, - 24.4, - 24.3, - 24.2, - 24.2, - 24.1, - 24.1, - 24.1, - 23.9, - 23.7, - 23.5, - 23.4, - 23.4, - 23.4, - 23.2, - 23, - 23, - 22.8, - 22.6, - 22.3, - 22.1, - 21.9, - 21.9, - 21.8, - 21.9, - 21.9, - 21.5, - 21.3, - 21, - 20.8, - 20.8, - 20.8, - 20.7, - 20.1, - 19.8, - 19.5, - 37.3, - 37.6, - 37.9, - 38.1, - 38.3, - 38.6, - 38.8, - 39.3, - 39.5, - 39.9, - 40.1, - 40.2, - 40.5, - 40.8, - 41, - 41.2, - 41.4, - 41.5, - 41.6, - 41.8, - 41.9, - 42, - 42.2, - 42.4, - 42.5, - 42.6, - 42.6, - 42.6, - 42.5, - 42.6, - 42.6, - 42.4, - 42.3, - 42.1, - 41.8, - 41.6, - 41.5, - 41.2, - 41.1, - 41.1, - 41, - 40.7, - 40.6, - 40.6, - 40.5, - 40.4, - 40.4, - 40.3, - 40.2, - 40.1, - 40.1, - 40.1, - 40.1, - 40.1, - 40.1, - 40, - 40, - 40, - 40.1, - 40.2, - 40.4, - 40.3, - 40.3, - 40.2, - 40, - 40, - 39.9, - 39.8, - 39.8, - 39.5, - 39.3, - 39.2, - 39.1, - 39.1, - 38.7, - 38.5, - 38.3, - 38.1, - 38, - 37.9, - 37.9, - 37.8, - 37.7, - 37.7, - 37.7, - 37.6, - 37.5, - 37.4, - 37.3, - 37.2, - 37.2, - 37.2, - 37, - 36.5, - 36.4, - 36.4, - 36.4, - 36.4, - 36.4, - 36.4, - 36.5, - 36.6, - 36.7, - 36.8, - 36.7, - 36.7, - 36.7, - 36.7, - 36.8, - 36.7, - 36.4, - 36.4, - 36.3, - 36.2, - 36.2, - 36.1, - 36, - 35.9, - 35.7, - 35.6, - 35.5, - 35.4, - 35.2, - 35, - 34.9, - 34.8, - 34.6, - 34.4, - 34.3, - 34.1, - 34, - 33.7, - 33.5, - 33.2, - 33.1, - 33, - 32.9, - 32.8, - 32.7, - 32.6, - 32.6, - 32.6, - 32.6, - 32.6, - 32.6, - 32.6, - 32.6, - 32.4, - 32.4, - 32.3, - 32.3, - 32.2, - 32.2, - 32.3, - 32.3, - 32.1, - 32.1, - 32.1, - 32, - 32.1, - 32, - 31.9, - 31.9, - 31.8, - 31.8, - 31.7, - 31.7, - 31.6, - 31.5, - 31.4, - 31.3, - 31.1, - 31, - 30.8, - 30.7, - 30.5, - 30.4, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.6, - 29.5, - 29.4, - 29.2, - 29, - 28.9, - 28.8, - 28.6, - 28.5, - 28.5, - 28.5, - 28.4, - 28.2, - 28.2, - 28.2, - 28.1, - 28.1, - 28, - 27.8, - 27.6, - 27.5, - 27.4, - 27.2, - 27, - 26.9, - 26.7, - 26.5, - 26.4, - 26.4, - 26.3, - 26.2, - 26, - 25.9, - 25.8, - 25.6, - 25.5, - 25.3, - 25.1, - 24.9, - 24.7, - 24.6, - 24.5, - 24.4, - 24.3, - 24.2, - 24.2, - 24.2, - 24.1, - 23.9, - 23.6, - 23.5, - 23.6, - 23.6, - 23.6, - 23.3, - 23.2, - 23, - 22.7, - 22.6, - 22.4, - 22.1, - 21.9, - 21.9, - 21.9, - 21.9, - 21.7, - 21.5, - 21.2, - 20.9, - 20.8, - 20.7, - 20.7, - 20.4, - 20, - 19.7, - 37.2, - 37.6, - 37.8, - 38, - 38.4, - 38.7, - 39, - 39.3, - 39.7, - 40, - 40.2, - 40.3, - 40.6, - 41, - 41.2, - 41.3, - 41.6, - 41.6, - 41.7, - 41.8, - 41.9, - 42.1, - 42.2, - 42.3, - 42.4, - 42.5, - 42.5, - 42.5, - 42.3, - 42.3, - 42.3, - 42.3, - 42, - 41.8, - 41.4, - 41.3, - 41.1, - 41, - 40.9, - 40.8, - 40.8, - 40.6, - 40.5, - 40.5, - 40.4, - 40.4, - 40.3, - 40.3, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.2, - 40.3, - 40.3, - 40.3, - 40.4, - 40.3, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.4, - 39.2, - 39.1, - 39.1, - 38.9, - 38.5, - 38.3, - 38.2, - 38.1, - 38, - 37.9, - 37.9, - 37.8, - 37.8, - 37.7, - 37.7, - 37.6, - 37.5, - 37.5, - 37.3, - 37.3, - 37.4, - 37.3, - 37, - 36.7, - 36.6, - 36.6, - 36.7, - 36.7, - 36.6, - 36.6, - 36.7, - 36.8, - 36.8, - 36.8, - 36.8, - 36.8, - 36.8, - 36.9, - 36.8, - 36.6, - 36.5, - 36.4, - 36.3, - 36.3, - 36.3, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.5, - 35.3, - 35.2, - 35, - 34.9, - 34.7, - 34.6, - 34.5, - 34.3, - 34.1, - 33.8, - 33.6, - 33.4, - 33.2, - 33.2, - 33.2, - 33.1, - 33, - 32.9, - 32.8, - 32.8, - 32.8, - 32.7, - 32.7, - 32.7, - 32.8, - 32.8, - 32.7, - 32.5, - 32.5, - 32.4, - 32.5, - 32.5, - 32.4, - 32.4, - 32.4, - 32.4, - 32.3, - 32.3, - 32.3, - 32.1, - 32.1, - 32, - 32, - 31.9, - 31.9, - 31.8, - 31.7, - 31.6, - 31.5, - 31.4, - 31.2, - 30.9, - 30.7, - 30.6, - 30.6, - 30.5, - 30.3, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.5, - 29.3, - 29.2, - 29.2, - 29, - 28.9, - 28.8, - 28.7, - 28.6, - 28.5, - 28.6, - 28.6, - 28.6, - 28.5, - 28.4, - 28.2, - 28, - 27.9, - 27.8, - 27.5, - 27.3, - 27.1, - 26.9, - 26.7, - 26.6, - 26.5, - 26.5, - 26.3, - 26.2, - 26.1, - 25.9, - 25.8, - 25.7, - 25.5, - 25.3, - 25.1, - 24.9, - 24.7, - 24.6, - 24.6, - 24.4, - 24.3, - 24.2, - 24.2, - 24.2, - 24.1, - 23.8, - 23.7, - 23.7, - 23.7, - 23.6, - 23.5, - 23.4, - 23.4, - 23.1, - 22.9, - 22.6, - 22.4, - 22.2, - 22.1, - 22.1, - 22.1, - 22, - 21.6, - 21.3, - 21.1, - 20.9, - 20.8, - 20.8, - 20.7, - 20.2, - 20, - 37.2, - 37.6, - 37.8, - 38.1, - 38.4, - 38.7, - 39, - 39.5, - 39.8, - 40, - 40.2, - 40.4, - 40.6, - 41, - 41.3, - 41.5, - 41.6, - 41.7, - 41.9, - 42, - 42.1, - 42.1, - 42.2, - 42.3, - 42.3, - 42.2, - 42.1, - 42.1, - 42, - 41.9, - 42, - 41.9, - 41.6, - 41.3, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.6, - 40.6, - 40.5, - 40.5, - 40.4, - 40.4, - 40.3, - 40.4, - 40.4, - 40.3, - 40.3, - 40.3, - 40.3, - 40.3, - 40.3, - 40.4, - 40.4, - 40.4, - 40.4, - 40.4, - 40.3, - 40.3, - 40.3, - 40.3, - 40.3, - 40.2, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.3, - 39.1, - 38.9, - 38.8, - 38.5, - 38.3, - 38.2, - 38.2, - 38.1, - 38, - 38, - 37.9, - 37.8, - 37.8, - 37.8, - 37.8, - 37.6, - 37.5, - 37.4, - 37.4, - 37.5, - 37.5, - 37.3, - 37.2, - 36.9, - 36.9, - 36.9, - 36.8, - 36.8, - 36.7, - 36.8, - 36.9, - 36.9, - 36.9, - 36.9, - 36.9, - 36.9, - 37, - 37, - 36.8, - 36.6, - 36.5, - 36.4, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 36, - 35.9, - 35.7, - 35.5, - 35.3, - 35.2, - 35.1, - 34.9, - 34.7, - 34.6, - 34.4, - 34.2, - 34, - 33.7, - 33.6, - 33.5, - 33.5, - 33.4, - 33.4, - 33.3, - 33.2, - 33, - 33, - 33, - 32.9, - 32.9, - 32.9, - 32.9, - 32.9, - 32.9, - 32.8, - 32.7, - 32.6, - 32.7, - 32.7, - 32.6, - 32.7, - 32.7, - 32.6, - 32.6, - 32.5, - 32.5, - 32.4, - 32.3, - 32.3, - 32.2, - 32.1, - 32.1, - 32, - 31.8, - 31.7, - 31.6, - 31.6, - 31.4, - 31.2, - 30.9, - 30.7, - 30.7, - 30.6, - 30.5, - 30.4, - 30.3, - 30.1, - 30, - 30, - 30, - 29.9, - 29.8, - 29.8, - 29.6, - 29.3, - 29.2, - 29.2, - 29.1, - 29.1, - 29.1, - 29, - 29, - 29, - 28.9, - 28.7, - 28.5, - 28.4, - 28.2, - 28, - 27.8, - 27.6, - 27.5, - 27.3, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.3, - 26.1, - 26, - 25.9, - 25.6, - 25.4, - 25.2, - 25, - 24.9, - 24.8, - 24.8, - 24.7, - 24.5, - 24.3, - 24.3, - 24.3, - 24.2, - 24, - 23.8, - 23.7, - 23.7, - 23.6, - 23.6, - 23.6, - 23.7, - 23.5, - 23.2, - 22.9, - 22.7, - 22.5, - 22.3, - 22.2, - 22.2, - 22.2, - 22.1, - 21.8, - 21.5, - 21.3, - 21.1, - 21.1, - 21.2, - 20.6, - 20.2, - 37.2, - 37.6, - 37.8, - 38.1, - 38.5, - 38.9, - 39.2, - 39.5, - 39.8, - 40, - 40.2, - 40.5, - 40.8, - 41.4, - 41.5, - 41.6, - 41.8, - 42, - 42.1, - 42.1, - 42.1, - 42.1, - 42.2, - 42.3, - 42.2, - 42, - 41.9, - 41.8, - 41.8, - 41.8, - 41.7, - 41.5, - 41.3, - 41.1, - 40.9, - 40.9, - 40.8, - 40.7, - 40.6, - 40.6, - 40.5, - 40.5, - 40.4, - 40.4, - 40.4, - 40.4, - 40.4, - 40.5, - 40.5, - 40.5, - 40.5, - 40.5, - 40.5, - 40.5, - 40.6, - 40.6, - 40.8, - 40.8, - 40.7, - 40.6, - 40.5, - 40.4, - 40.4, - 40.3, - 40.2, - 40.1, - 39.9, - 39.9, - 39.8, - 39.7, - 39.5, - 39.3, - 39.1, - 39, - 38.7, - 38.5, - 38.4, - 38.3, - 38.2, - 38.1, - 38.1, - 38, - 38, - 37.9, - 37.8, - 37.8, - 37.8, - 37.7, - 37.7, - 37.6, - 37.5, - 37.6, - 37.6, - 37.6, - 37.5, - 37.3, - 37.1, - 37, - 36.9, - 36.9, - 36.9, - 37, - 37.1, - 37, - 36.9, - 36.9, - 37, - 37, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.6, - 36.4, - 36.3, - 36.2, - 36.1, - 36.1, - 36, - 35.9, - 35.7, - 35.5, - 35.4, - 35.2, - 35.1, - 34.9, - 34.7, - 34.6, - 34.4, - 34.1, - 33.9, - 33.8, - 33.9, - 33.9, - 33.8, - 33.7, - 33.6, - 33.5, - 33.4, - 33.3, - 33.3, - 33.2, - 33.2, - 33.2, - 33.3, - 33.2, - 33.1, - 32.9, - 32.9, - 32.8, - 32.8, - 32.8, - 32.8, - 32.9, - 32.9, - 32.9, - 32.8, - 32.7, - 32.6, - 32.6, - 32.6, - 32.6, - 32.5, - 32.4, - 32.3, - 32.1, - 32, - 31.9, - 31.7, - 31.6, - 31.6, - 31.4, - 31.2, - 31, - 30.9, - 30.7, - 30.6, - 30.6, - 30.5, - 30.3, - 30.2, - 30.2, - 30.2, - 30.2, - 30.1, - 30.2, - 30.1, - 29.8, - 29.5, - 29.5, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.5, - 28.3, - 28.2, - 28.1, - 28, - 27.7, - 27.6, - 27.5, - 27.2, - 27.1, - 26.8, - 26.6, - 26.5, - 26.4, - 26.3, - 26.2, - 25.9, - 25.6, - 25.4, - 25.3, - 25.1, - 25, - 24.9, - 24.8, - 24.6, - 24.4, - 24.4, - 24.4, - 24.4, - 24.3, - 24, - 23.8, - 23.7, - 23.7, - 23.8, - 23.7, - 23.6, - 23.5, - 23.3, - 23.1, - 22.9, - 22.7, - 22.6, - 22.4, - 22.3, - 22.2, - 22.2, - 22.2, - 22, - 21.7, - 21.5, - 21.3, - 21.4, - 20.9, - 20.5, - 37.3, - 37.6, - 37.8, - 38.1, - 38.6, - 38.9, - 39.2, - 39.5, - 39.8, - 40, - 40.3, - 40.6, - 41.1, - 41.6, - 41.7, - 41.9, - 42, - 42.1, - 42.1, - 42.1, - 42, - 42, - 42, - 42, - 41.9, - 41.8, - 41.8, - 41.8, - 41.6, - 41.5, - 41.4, - 41.3, - 41.1, - 40.9, - 40.8, - 40.9, - 40.9, - 40.6, - 40.5, - 40.4, - 40.4, - 40.4, - 40.5, - 40.4, - 40.5, - 40.5, - 40.6, - 40.7, - 40.7, - 40.8, - 40.8, - 40.8, - 40.8, - 40.9, - 40.8, - 41, - 41.1, - 41.1, - 41, - 40.9, - 40.8, - 40.6, - 40.5, - 40.4, - 40.3, - 40.2, - 40, - 39.9, - 39.8, - 39.7, - 39.5, - 39.3, - 39.2, - 39, - 38.8, - 38.6, - 38.5, - 38.4, - 38.3, - 38.2, - 38.2, - 38.1, - 38, - 38, - 37.9, - 37.8, - 37.8, - 37.7, - 37.7, - 37.7, - 37.7, - 37.7, - 37.7, - 37.7, - 37.8, - 37.5, - 37.3, - 37.1, - 37, - 37, - 37.1, - 37.1, - 37.1, - 37, - 36.9, - 36.9, - 37, - 37.2, - 37.2, - 37.2, - 37, - 36.9, - 36.9, - 36.8, - 36.7, - 36.6, - 36.4, - 36.3, - 36.2, - 36.2, - 36.1, - 36, - 35.8, - 35.7, - 35.5, - 35.4, - 35.2, - 35.1, - 34.9, - 34.8, - 34.6, - 34.4, - 34.2, - 34.1, - 34.2, - 34.1, - 34, - 33.9, - 33.8, - 33.7, - 33.6, - 33.5, - 33.5, - 33.5, - 33.5, - 33.5, - 33.5, - 33.4, - 33.4, - 33.2, - 33.2, - 33.1, - 33, - 33, - 33.1, - 33.1, - 33.1, - 33, - 33, - 32.9, - 32.8, - 32.8, - 32.8, - 32.7, - 32.6, - 32.6, - 32.5, - 32.3, - 32.2, - 32.1, - 32, - 31.8, - 31.6, - 31.5, - 31.3, - 31.1, - 31, - 30.9, - 30.8, - 30.7, - 30.6, - 30.5, - 30.3, - 30.3, - 30.3, - 30.3, - 30.2, - 30.3, - 30.2, - 30, - 29.8, - 29.7, - 29.7, - 29.7, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.6, - 28.5, - 28.5, - 28.4, - 28.1, - 27.9, - 27.8, - 27.6, - 27.4, - 27, - 26.8, - 26.6, - 26.6, - 26.6, - 26.5, - 26.2, - 26, - 25.9, - 25.7, - 25.4, - 25.2, - 25, - 24.9, - 24.8, - 24.6, - 24.5, - 24.5, - 24.5, - 24.4, - 24.2, - 24.1, - 24, - 24, - 24, - 24.1, - 23.8, - 23.6, - 23.6, - 23.5, - 23.1, - 22.9, - 22.8, - 22.8, - 22.6, - 22.2, - 22.2, - 22.3, - 22.3, - 22.2, - 21.9, - 21.5, - 21.5, - 21.1, - 20.8, - 37.4, - 37.6, - 37.8, - 38.1, - 38.6, - 39, - 39.3, - 39.6, - 39.8, - 40.1, - 40.5, - 40.8, - 41.2, - 41.6, - 41.7, - 41.9, - 42.1, - 42.1, - 42, - 41.9, - 41.8, - 41.7, - 41.8, - 41.7, - 41.7, - 41.6, - 41.7, - 41.6, - 41.3, - 41.2, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.7, - 40.6, - 40.5, - 40.4, - 40.4, - 40.4, - 40.5, - 40.5, - 40.5, - 40.6, - 40.7, - 40.8, - 40.9, - 41, - 41, - 41, - 41.2, - 41.3, - 41.2, - 41.1, - 41.3, - 41.3, - 41.3, - 41.2, - 41.2, - 41.2, - 41, - 40.7, - 40.5, - 40.4, - 40.3, - 40.2, - 40, - 39.9, - 39.7, - 39.6, - 39.4, - 39.3, - 39.1, - 38.9, - 38.7, - 38.6, - 38.5, - 38.4, - 38.3, - 38.2, - 38.2, - 38.1, - 38, - 38, - 37.9, - 37.9, - 37.8, - 37.8, - 37.9, - 37.8, - 37.8, - 37.8, - 37.9, - 37.9, - 37.7, - 37.5, - 37.3, - 37.2, - 37.1, - 37.2, - 37.2, - 37.1, - 37, - 36.9, - 36.9, - 36.9, - 37.2, - 37.4, - 37.3, - 37.1, - 37, - 36.9, - 36.9, - 36.8, - 36.7, - 36.5, - 36.5, - 36.3, - 36.2, - 36.2, - 36.1, - 36, - 35.8, - 35.7, - 35.6, - 35.4, - 35.3, - 35.2, - 35, - 34.8, - 34.7, - 34.6, - 34.5, - 34.5, - 34.5, - 34.4, - 34.2, - 34.1, - 34, - 33.9, - 33.8, - 33.8, - 33.8, - 33.7, - 33.7, - 33.7, - 33.7, - 33.7, - 33.5, - 33.5, - 33.5, - 33.4, - 33.3, - 33.3, - 33.3, - 33.2, - 33.2, - 33.3, - 33.2, - 33.1, - 33.1, - 33, - 32.9, - 32.8, - 32.8, - 32.6, - 32.5, - 32.4, - 32.3, - 32.2, - 32.1, - 31.8, - 31.5, - 31.4, - 31.3, - 31.1, - 31, - 30.9, - 30.8, - 30.8, - 30.7, - 30.5, - 30.5, - 30.4, - 30.4, - 30.4, - 30.3, - 30.3, - 30.1, - 29.9, - 29.9, - 29.9, - 30, - 30, - 30, - 29.8, - 29.6, - 29.5, - 29.4, - 29.3, - 29.1, - 29, - 28.9, - 28.8, - 28.7, - 28.5, - 28.3, - 28.2, - 28, - 27.9, - 27.5, - 27.2, - 27, - 26.8, - 26.7, - 26.6, - 26.6, - 26.5, - 26.3, - 26.2, - 26.1, - 25.9, - 25.6, - 25.4, - 25.2, - 25, - 24.8, - 24.6, - 24.6, - 24.6, - 24.4, - 24.3, - 24.4, - 24.4, - 24.2, - 24.3, - 24.3, - 24.1, - 23.7, - 23.8, - 23.9, - 23.6, - 23.3, - 22.9, - 22.8, - 22.7, - 22.4, - 22.2, - 22.2, - 22.2, - 22.2, - 22.2, - 21.7, - 21.5, - 21.5, - 21.1, - 37.3, - 37.5, - 37.8, - 38.1, - 38.6, - 39, - 39.3, - 39.6, - 39.8, - 40.2, - 40.5, - 40.8, - 41.1, - 41.4, - 41.6, - 41.9, - 42, - 42, - 41.8, - 41.7, - 41.6, - 41.5, - 41.5, - 41.6, - 41.5, - 41.4, - 41.3, - 41.3, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.6, - 40.6, - 40.5, - 40.5, - 40.4, - 40.4, - 40.5, - 40.5, - 40.6, - 40.7, - 40.7, - 40.8, - 41, - 41.1, - 41.2, - 41.4, - 41.5, - 41.5, - 41.6, - 41.6, - 41.6, - 41.5, - 41.6, - 41.6, - 41.6, - 41.6, - 41.6, - 41.5, - 41.2, - 40.9, - 40.7, - 40.6, - 40.4, - 40.3, - 40.1, - 40, - 39.8, - 39.7, - 39.5, - 39.4, - 39.2, - 39, - 38.8, - 38.7, - 38.6, - 38.4, - 38.4, - 38.3, - 38.3, - 38.2, - 38.1, - 38.1, - 38, - 38, - 38, - 38, - 37.9, - 37.9, - 38, - 38, - 38, - 38, - 38, - 37.9, - 37.8, - 37.5, - 37.3, - 37.3, - 37.1, - 37, - 37, - 37, - 37, - 37.2, - 37.6, - 37.6, - 37.5, - 37.3, - 37.2, - 37.1, - 37, - 36.9, - 36.8, - 36.7, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.3, - 35.2, - 35.2, - 35, - 34.7, - 34.6, - 34.5, - 34.4, - 34.4, - 34.4, - 34.3, - 34.2, - 34, - 34, - 34, - 33.9, - 34, - 34, - 33.9, - 33.8, - 33.8, - 33.8, - 33.8, - 33.7, - 33.6, - 33.5, - 33.4, - 33.4, - 33.4, - 33.4, - 33.4, - 33.3, - 33.2, - 33.1, - 33.1, - 33.1, - 32.9, - 32.8, - 32.6, - 32.5, - 32.4, - 32.4, - 32.5, - 31.9, - 31.6, - 31.5, - 31.4, - 31.2, - 31.1, - 31, - 31, - 30.9, - 30.7, - 30.6, - 30.5, - 30.5, - 30.5, - 30.4, - 30.4, - 30.2, - 30.1, - 30.1, - 30.1, - 30.2, - 30.2, - 30.2, - 30.1, - 30, - 29.9, - 29.7, - 29.6, - 29.4, - 29.3, - 29.2, - 29.1, - 28.9, - 28.7, - 28.5, - 28.3, - 28.2, - 28, - 27.7, - 27.4, - 27.2, - 27.1, - 26.9, - 26.8, - 26.6, - 26.5, - 26.4, - 26.2, - 26, - 26, - 25.9, - 25.7, - 25.5, - 25.4, - 25, - 24.7, - 24.7, - 24.6, - 24.5, - 24.4, - 24.5, - 24.7, - 24.6, - 24.5, - 24.5, - 24.3, - 24.1, - 24, - 24, - 23.9, - 23.7, - 23.2, - 23, - 23, - 22.8, - 22.4, - 22.2, - 22.2, - 22.2, - 22.3, - 22.2, - 21.7, - 21.7, - 21.4, - 37.3, - 37.5, - 37.8, - 38.2, - 38.7, - 39.1, - 39.3, - 39.5, - 39.8, - 40.2, - 40.6, - 40.9, - 41.2, - 41.4, - 41.7, - 41.9, - 41.9, - 41.7, - 41.6, - 41.5, - 41.3, - 41.3, - 41.3, - 41.3, - 41.2, - 41.1, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.7, - 40.6, - 40.5, - 40.4, - 40.4, - 40.5, - 40.5, - 40.6, - 40.7, - 40.7, - 40.7, - 40.8, - 40.9, - 41, - 41.3, - 41.5, - 41.5, - 41.7, - 41.9, - 41.9, - 41.8, - 42, - 42, - 41.9, - 41.9, - 41.9, - 41.9, - 41.8, - 41.8, - 41.6, - 41.3, - 41.1, - 40.9, - 40.7, - 40.6, - 40.4, - 40.2, - 40.1, - 39.9, - 39.8, - 39.6, - 39.5, - 39.3, - 39.1, - 38.9, - 38.8, - 38.7, - 38.6, - 38.5, - 38.5, - 38.5, - 38.4, - 38.3, - 38.2, - 38.2, - 38.2, - 38.2, - 38.2, - 38.1, - 38.1, - 38.1, - 38.1, - 38.1, - 38.1, - 38.2, - 38.2, - 38.1, - 37.9, - 37.5, - 37.4, - 37.2, - 37.1, - 37.2, - 37.2, - 37.3, - 37.7, - 37.9, - 37.9, - 37.7, - 37.6, - 37.4, - 37.4, - 37.2, - 37.1, - 36.9, - 36.9, - 36.8, - 36.8, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.9, - 35.9, - 35.9, - 35.7, - 35.6, - 35.3, - 35.1, - 34.9, - 34.9, - 34.8, - 34.7, - 34.6, - 34.6, - 34.4, - 34.3, - 34.2, - 34.2, - 34.2, - 34.2, - 34.2, - 34.2, - 34.2, - 34.1, - 34, - 34, - 34, - 33.8, - 33.8, - 33.7, - 33.7, - 33.7, - 33.7, - 33.6, - 33.5, - 33.4, - 33.4, - 33.4, - 33.3, - 33.2, - 33, - 32.9, - 32.8, - 32.6, - 32.5, - 32.5, - 32.3, - 31.9, - 31.8, - 31.6, - 31.5, - 31.3, - 31.3, - 31.2, - 31.1, - 31, - 30.7, - 30.6, - 30.6, - 30.6, - 30.6, - 30.7, - 30.7, - 30.6, - 30.6, - 30.6, - 30.6, - 30.6, - 30.6, - 30.5, - 30.4, - 30.3, - 30.2, - 30, - 29.8, - 29.5, - 29.4, - 29.2, - 29, - 28.8, - 28.6, - 28.4, - 28.3, - 28.1, - 27.8, - 27.6, - 27.4, - 27.3, - 27.1, - 26.9, - 26.8, - 26.7, - 26.6, - 26.4, - 26.1, - 26, - 25.9, - 25.9, - 25.8, - 25.7, - 25.3, - 24.9, - 24.8, - 24.7, - 24.6, - 24.6, - 24.9, - 25.1, - 25.2, - 24.8, - 24.8, - 24.8, - 24.6, - 24.4, - 24, - 23.9, - 23.8, - 23.6, - 23.5, - 23.5, - 23.2, - 22.9, - 22.6, - 22.4, - 22.4, - 22.4, - 22.1, - 22, - 21.9, - 21.6, - 37.3, - 37.5, - 37.9, - 38.3, - 38.8, - 39.1, - 39.2, - 39.5, - 39.8, - 40.2, - 40.6, - 40.9, - 41.1, - 41.3, - 41.6, - 41.7, - 41.6, - 41.4, - 41.3, - 41.2, - 41.1, - 41.2, - 41.2, - 41.1, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.7, - 40.6, - 40.5, - 40.4, - 40.5, - 40.5, - 40.6, - 40.6, - 40.7, - 40.9, - 40.9, - 41, - 41, - 41.1, - 41.2, - 41.3, - 41.5, - 41.7, - 41.7, - 41.8, - 42, - 42.1, - 42, - 42.2, - 42.2, - 42.1, - 42.1, - 42.1, - 42.1, - 42, - 42, - 41.7, - 41.4, - 41.2, - 41, - 40.9, - 40.7, - 40.5, - 40.3, - 40.1, - 40, - 39.9, - 39.8, - 39.6, - 39.5, - 39.3, - 39.1, - 39, - 38.9, - 38.8, - 38.8, - 38.8, - 38.8, - 38.7, - 38.5, - 38.4, - 38.4, - 38.4, - 38.4, - 38.4, - 38.4, - 38.3, - 38.3, - 38.3, - 38.4, - 38.4, - 38.4, - 38.4, - 38.3, - 38.1, - 37.7, - 37.6, - 37.5, - 37.5, - 37.5, - 37.5, - 37.9, - 38.2, - 38.2, - 38.2, - 38, - 37.8, - 37.6, - 37.5, - 37.4, - 37.2, - 37.2, - 37.1, - 37.1, - 37, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.6, - 36.4, - 36.4, - 36.4, - 36.4, - 36.4, - 36.3, - 36.1, - 35.8, - 35.7, - 35.5, - 35.3, - 35.2, - 35.2, - 35.1, - 35, - 34.9, - 34.7, - 34.6, - 34.5, - 34.4, - 34.4, - 34.4, - 34.5, - 34.4, - 34.4, - 34.3, - 34.3, - 34.2, - 34.2, - 34.1, - 34, - 34.1, - 34.1, - 34, - 34, - 33.9, - 33.7, - 33.6, - 33.6, - 33.6, - 33.5, - 33.5, - 33.3, - 33.2, - 33.1, - 32.9, - 32.7, - 32.7, - 32.5, - 32.4, - 32.2, - 31.9, - 31.7, - 31.6, - 31.5, - 31.4, - 31.4, - 31.3, - 31.1, - 30.8, - 30.7, - 30.8, - 30.9, - 31.1, - 31.3, - 31.3, - 31.4, - 31.3, - 31.3, - 31.2, - 31.1, - 31, - 30.8, - 30.6, - 30.5, - 30.3, - 30, - 29.8, - 29.6, - 29.4, - 29.2, - 29.1, - 28.9, - 28.7, - 28.5, - 28.3, - 28.1, - 27.9, - 27.7, - 27.6, - 27.4, - 27.2, - 27.1, - 27, - 26.9, - 26.7, - 26.5, - 26.2, - 26.1, - 26, - 26, - 25.9, - 25.6, - 25.2, - 25.1, - 25.1, - 24.8, - 24.8, - 25.3, - 25.6, - 25.7, - 25.7, - 25.3, - 25.2, - 25, - 25, - 24.5, - 24.3, - 24.2, - 24.2, - 24.1, - 24, - 23.8, - 23.4, - 23.2, - 22.9, - 22.9, - 22.7, - 22.4, - 22.1, - 22.1, - 21.9, - 37.2, - 37.5, - 37.9, - 38.3, - 38.8, - 39.1, - 39.3, - 39.5, - 39.8, - 40.1, - 40.5, - 40.8, - 40.9, - 41.2, - 41.4, - 41.4, - 41.3, - 41.3, - 41.1, - 41, - 41, - 41.1, - 41, - 40.9, - 40.9, - 40.8, - 40.7, - 40.6, - 40.6, - 40.6, - 40.5, - 40.5, - 40.6, - 40.7, - 40.9, - 41, - 41, - 41, - 41.2, - 41.3, - 41.3, - 41.3, - 41.5, - 41.6, - 41.8, - 41.9, - 42, - 42, - 42, - 42.1, - 42.2, - 42.2, - 42.3, - 42.4, - 42.4, - 42.4, - 42.5, - 42.4, - 42.3, - 42.2, - 41.9, - 41.5, - 41.3, - 41.1, - 41, - 40.9, - 40.7, - 40.5, - 40.3, - 40.2, - 40.1, - 39.9, - 39.9, - 39.7, - 39.5, - 39.4, - 39.4, - 39.2, - 39.1, - 39.1, - 39.1, - 39, - 39, - 38.9, - 38.7, - 38.7, - 38.7, - 38.7, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.6, - 38.7, - 38.7, - 38.6, - 38.3, - 37.9, - 37.8, - 37.8, - 37.9, - 37.9, - 38.1, - 38.4, - 38.5, - 38.5, - 38.4, - 38.1, - 37.9, - 37.8, - 37.7, - 37.5, - 37.4, - 37.4, - 37.3, - 37.3, - 37.3, - 37.2, - 37.2, - 37.1, - 37, - 37, - 36.9, - 36.9, - 36.9, - 36.9, - 36.8, - 36.7, - 36.6, - 36.5, - 36.3, - 36.1, - 35.8, - 35.6, - 35.5, - 35.4, - 35.4, - 35.4, - 35.2, - 35, - 35, - 34.9, - 34.8, - 34.8, - 34.7, - 34.7, - 34.7, - 34.7, - 34.6, - 34.5, - 34.4, - 34.4, - 34.4, - 34.3, - 34.3, - 34.4, - 34.4, - 34.3, - 34.2, - 34, - 33.9, - 33.7, - 33.7, - 33.6, - 33.6, - 33.5, - 33.3, - 33.1, - 33, - 32.9, - 32.8, - 32.7, - 32.6, - 32.5, - 32.2, - 31.9, - 31.8, - 31.7, - 31.7, - 31.6, - 31.6, - 31.5, - 31.3, - 31.1, - 31.2, - 31.3, - 31.5, - 31.7, - 31.8, - 31.9, - 31.8, - 31.8, - 31.7, - 31.4, - 31.2, - 31.1, - 30.9, - 30.7, - 30.5, - 30.3, - 30.2, - 30.1, - 30, - 29.8, - 29.5, - 29.3, - 29, - 28.8, - 28.6, - 28.5, - 28.3, - 28.2, - 28, - 27.7, - 27.5, - 27.4, - 27.2, - 27, - 26.9, - 26.7, - 26.5, - 26.3, - 26.2, - 26.1, - 26, - 25.8, - 25.6, - 25.6, - 25.6, - 25.2, - 25.3, - 25.7, - 25.8, - 25.9, - 26.1, - 26.1, - 25.8, - 25.6, - 25.5, - 25.3, - 24.9, - 24.8, - 24.6, - 24.5, - 24.3, - 24.1, - 23.9, - 23.7, - 23.3, - 23.1, - 23.1, - 22.8, - 22.5, - 22.2, - 22, - 37.2, - 37.5, - 37.9, - 38.2, - 38.8, - 39.1, - 39.3, - 39.5, - 39.8, - 40.2, - 40.5, - 40.7, - 40.8, - 40.8, - 40.9, - 40.9, - 41, - 40.9, - 40.9, - 40.8, - 40.8, - 40.9, - 40.8, - 40.8, - 40.8, - 40.7, - 40.7, - 40.6, - 40.6, - 40.7, - 40.7, - 40.8, - 40.9, - 41.1, - 41.3, - 41.5, - 41.5, - 41.5, - 41.7, - 41.6, - 41.7, - 41.8, - 42, - 42.2, - 42.3, - 42.4, - 42.6, - 42.6, - 42.5, - 42.4, - 42.4, - 42.6, - 42.6, - 42.7, - 42.8, - 42.8, - 42.8, - 42.7, - 42.6, - 42.5, - 42.1, - 41.7, - 41.5, - 41.3, - 41.2, - 41.1, - 41, - 40.8, - 40.6, - 40.4, - 40.3, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.7, - 39.7, - 39.5, - 39.4, - 39.3, - 39.3, - 39.3, - 39.3, - 39.2, - 39.1, - 39.1, - 39.1, - 39, - 38.9, - 38.9, - 38.9, - 38.9, - 38.9, - 39, - 39, - 39, - 38.9, - 38.7, - 38.3, - 38, - 38.1, - 38.3, - 38.4, - 38.7, - 38.8, - 38.7, - 38.8, - 38.6, - 38.3, - 38.1, - 37.9, - 37.8, - 37.6, - 37.6, - 37.6, - 37.6, - 37.5, - 37.5, - 37.5, - 37.6, - 37.6, - 37.5, - 37.5, - 37.5, - 37.4, - 37.4, - 37.2, - 37.2, - 37.1, - 37.1, - 37, - 36.8, - 36.5, - 36.3, - 36, - 35.9, - 35.7, - 35.5, - 35.5, - 35.4, - 35.3, - 35.2, - 35.2, - 35.2, - 35.1, - 35, - 35, - 34.9, - 34.9, - 34.9, - 34.8, - 34.7, - 34.6, - 34.6, - 34.6, - 34.6, - 34.7, - 34.6, - 34.5, - 34.5, - 34.3, - 34.1, - 34, - 33.9, - 33.7, - 33.7, - 33.6, - 33.4, - 33.2, - 33.1, - 33, - 32.8, - 32.8, - 32.7, - 32.6, - 32.3, - 32.1, - 32, - 32, - 32, - 31.9, - 31.8, - 31.7, - 31.6, - 31.5, - 31.6, - 31.7, - 31.8, - 32, - 32.1, - 32, - 32, - 32, - 31.9, - 31.7, - 31.6, - 31.5, - 31.4, - 31.1, - 30.9, - 30.7, - 30.6, - 30.6, - 30.4, - 30.2, - 29.9, - 29.6, - 29.3, - 29.1, - 28.9, - 28.8, - 28.7, - 28.5, - 28.4, - 28.1, - 27.9, - 27.7, - 27.5, - 27.4, - 27.2, - 27, - 26.8, - 26.7, - 26.5, - 26.4, - 26.2, - 26.1, - 25.9, - 25.8, - 25.8, - 25.8, - 25.9, - 26, - 26, - 26.1, - 26.1, - 26.2, - 26.3, - 26.2, - 26.2, - 26, - 25.4, - 25.2, - 25.1, - 25.1, - 24.8, - 24.4, - 24.2, - 23.8, - 23.6, - 23.3, - 23.3, - 23.1, - 22.9, - 22.7, - 22.4, - 37.2, - 37.6, - 38, - 38.3, - 38.6, - 39, - 39.2, - 39.5, - 39.7, - 40.1, - 40.4, - 40.7, - 40.7, - 40.7, - 40.6, - 40.6, - 40.6, - 40.6, - 40.7, - 40.7, - 40.8, - 40.8, - 40.8, - 40.8, - 40.8, - 40.8, - 40.8, - 40.8, - 40.9, - 40.9, - 41, - 41.1, - 41.3, - 41.6, - 41.8, - 42, - 42, - 42, - 42.1, - 42.1, - 42.1, - 42.3, - 42.5, - 42.8, - 42.9, - 42.9, - 42.9, - 43, - 42.9, - 42.9, - 42.9, - 42.9, - 42.9, - 42.9, - 43, - 43, - 43, - 42.9, - 42.8, - 42.8, - 42.4, - 41.9, - 41.7, - 41.6, - 41.5, - 41.4, - 41.2, - 41, - 40.9, - 40.7, - 40.5, - 40.3, - 40.2, - 40.1, - 40, - 40, - 40, - 40, - 39.9, - 39.7, - 39.7, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.6, - 39.4, - 39.3, - 39.3, - 39.6, - 39.5, - 39.3, - 39.2, - 39.2, - 39.2, - 39.2, - 39.1, - 38.7, - 38.4, - 38.5, - 38.7, - 38.9, - 39, - 39, - 39, - 38.9, - 38.8, - 38.6, - 38.3, - 38.1, - 37.9, - 37.8, - 37.8, - 37.8, - 37.9, - 37.9, - 38.1, - 38, - 38.1, - 38.4, - 38.3, - 38.3, - 38.2, - 37.9, - 37.8, - 37.6, - 37.5, - 37.5, - 37.5, - 37.4, - 37.2, - 36.9, - 36.7, - 36.4, - 36.2, - 36, - 35.9, - 35.8, - 35.6, - 35.5, - 35.4, - 35.5, - 35.4, - 35.4, - 35.3, - 35.2, - 35.2, - 35.2, - 35, - 35, - 35, - 34.9, - 34.8, - 34.8, - 34.9, - 34.9, - 34.8, - 34.7, - 34.6, - 34.5, - 34.3, - 34.3, - 34.2, - 34, - 33.8, - 33.7, - 33.5, - 33.4, - 33.3, - 33.1, - 33, - 32.9, - 32.8, - 32.7, - 32.5, - 32.4, - 32.3, - 32.2, - 32.2, - 32.2, - 32.1, - 32, - 32, - 31.9, - 31.9, - 31.9, - 32, - 32, - 32.1, - 32.1, - 32.1, - 32, - 32, - 32.1, - 32, - 32, - 31.8, - 31.6, - 31.4, - 31.3, - 31.1, - 30.9, - 30.7, - 30.5, - 30.3, - 30, - 29.7, - 29.4, - 29.2, - 29.1, - 29, - 28.9, - 28.7, - 28.5, - 28.3, - 28.1, - 27.9, - 27.7, - 27.5, - 27.3, - 27.1, - 26.9, - 26.8, - 26.7, - 26.5, - 26.4, - 26.3, - 26.1, - 26, - 26.1, - 26.2, - 26.2, - 26.3, - 26.3, - 26.2, - 26.3, - 26.4, - 26.3, - 26.4, - 26.3, - 26, - 25.8, - 25.5, - 25.3, - 25.1, - 24.8, - 24.6, - 24.2, - 23.8, - 23.7, - 23.5, - 23.2, - 23.1, - 23, - 22.9, - 37.1, - 37.5, - 37.9, - 38.2, - 38.6, - 38.8, - 39.1, - 39.4, - 39.5, - 39.8, - 40.2, - 40.4, - 40.4, - 40.4, - 40.4, - 40.4, - 40.5, - 40.5, - 40.7, - 40.8, - 40.8, - 40.8, - 40.9, - 41, - 41, - 41, - 41, - 41.1, - 41.2, - 41.4, - 41.5, - 41.7, - 41.8, - 42.1, - 42.2, - 42.4, - 42.5, - 42.6, - 42.6, - 42.7, - 42.8, - 42.8, - 43, - 43.3, - 43.3, - 43.2, - 43.2, - 43.3, - 43.4, - 43.4, - 43.3, - 43.2, - 43.1, - 43.1, - 43.2, - 43.1, - 43.1, - 43.2, - 43, - 42.8, - 42.5, - 42.2, - 42, - 41.9, - 41.8, - 41.7, - 41.5, - 41.4, - 41.2, - 41, - 40.8, - 40.7, - 40.5, - 40.4, - 40.3, - 40.2, - 40.2, - 40.2, - 40.2, - 40.1, - 40, - 40, - 39.9, - 39.8, - 39.9, - 40, - 39.9, - 39.9, - 40, - 39.8, - 39.8, - 40, - 39.9, - 39.7, - 39.6, - 39.5, - 39.5, - 39.6, - 39.5, - 39, - 38.9, - 39.1, - 39.2, - 39.3, - 39.2, - 39.4, - 39.4, - 39.3, - 39.2, - 38.8, - 38.6, - 38.3, - 38.1, - 38.1, - 38.1, - 38.2, - 38.3, - 38.5, - 38.7, - 38.7, - 38.7, - 38.7, - 38.8, - 38.8, - 38.8, - 38.4, - 38.1, - 38, - 37.9, - 37.8, - 37.7, - 37.6, - 37.5, - 37.3, - 37, - 36.7, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.8, - 35.6, - 35.7, - 35.7, - 35.7, - 35.6, - 35.5, - 35.5, - 35.5, - 35.4, - 35.3, - 35.2, - 35.2, - 35.1, - 35.1, - 35.1, - 35.1, - 35.1, - 35, - 34.9, - 34.8, - 34.6, - 34.5, - 34.4, - 34.4, - 34.1, - 33.8, - 33.7, - 33.6, - 33.5, - 33.3, - 33.2, - 33, - 33, - 32.9, - 32.7, - 32.6, - 32.5, - 32.5, - 32.5, - 32.5, - 32.5, - 32.5, - 32.6, - 32.5, - 32.2, - 32.1, - 32.1, - 32.1, - 32.1, - 32.1, - 32.2, - 32.2, - 32.3, - 32.5, - 32.5, - 32.3, - 32.2, - 32.2, - 32, - 31.8, - 31.5, - 31.2, - 31.1, - 30.9, - 30.7, - 30.4, - 30.1, - 29.9, - 29.6, - 29.4, - 29.3, - 29.2, - 29, - 28.8, - 28.6, - 28.4, - 28.3, - 28.1, - 27.8, - 27.6, - 27.3, - 27.2, - 27.1, - 27, - 26.8, - 26.7, - 26.6, - 26.5, - 26.4, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.3, - 26.4, - 26.5, - 26.5, - 26.5, - 26.3, - 26.1, - 26, - 26, - 25.8, - 25.5, - 25.3, - 24.9, - 24.7, - 24.2, - 23.9, - 23.7, - 23.5, - 23.4, - 23.3, - 23, - 37.1, - 37.5, - 37.9, - 38.3, - 38.6, - 38.7, - 39, - 39.3, - 39.4, - 39.5, - 39.8, - 39.9, - 40.1, - 40, - 40.1, - 40.3, - 40.4, - 40.4, - 40.5, - 40.6, - 40.7, - 40.9, - 41.1, - 41.2, - 41.3, - 41.3, - 41.4, - 41.6, - 41.6, - 41.8, - 42, - 42.3, - 42.4, - 42.6, - 42.8, - 42.9, - 43, - 43.2, - 43.2, - 43.3, - 43.4, - 43.4, - 43.5, - 43.6, - 43.7, - 43.5, - 43.6, - 43.7, - 43.8, - 43.7, - 43.6, - 43.5, - 43.4, - 43.4, - 43.5, - 43.4, - 43.3, - 43.3, - 43.2, - 43, - 42.7, - 42.4, - 42.2, - 42.1, - 42, - 41.9, - 41.8, - 41.7, - 41.5, - 41.3, - 41.1, - 40.9, - 40.8, - 40.7, - 40.6, - 40.5, - 40.4, - 40.4, - 40.5, - 40.4, - 40.4, - 40.4, - 40.3, - 40.2, - 40.3, - 40.2, - 40.2, - 40.3, - 40.4, - 40.4, - 40.3, - 40.4, - 40.5, - 40.4, - 40.1, - 40, - 39.9, - 39.9, - 39.9, - 39.5, - 39.4, - 39.6, - 39.6, - 39.6, - 39.7, - 39.7, - 39.7, - 39.6, - 39.4, - 39.1, - 38.8, - 38.6, - 38.4, - 38.5, - 38.5, - 38.5, - 38.8, - 38.9, - 39, - 39.1, - 39.1, - 39.1, - 38.9, - 39, - 39, - 38.8, - 38.4, - 38.3, - 38.2, - 38, - 37.9, - 37.8, - 37.7, - 37.7, - 37.4, - 37.1, - 36.8, - 36.6, - 36.5, - 36.5, - 36.5, - 36.3, - 36, - 35.9, - 36, - 35.9, - 36, - 35.9, - 35.8, - 35.8, - 35.7, - 35.5, - 35.5, - 35.4, - 35.3, - 35.3, - 35.3, - 35.2, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.8, - 34.7, - 34.6, - 34.4, - 34.1, - 33.9, - 33.7, - 33.7, - 33.6, - 33.4, - 33.2, - 33.2, - 33, - 32.9, - 32.8, - 32.7, - 32.7, - 32.7, - 32.8, - 32.8, - 32.8, - 32.7, - 32.7, - 32.5, - 32.3, - 32.2, - 32.2, - 32.2, - 32.2, - 32.3, - 32.4, - 32.6, - 32.8, - 32.8, - 32.7, - 32.7, - 32.6, - 32.5, - 32.2, - 31.8, - 31.6, - 31.4, - 31.2, - 31.1, - 30.8, - 30.5, - 30.3, - 30, - 29.8, - 29.6, - 29.4, - 29.2, - 29, - 28.8, - 28.7, - 28.5, - 28.3, - 28.1, - 27.9, - 27.6, - 27.4, - 27.3, - 27.2, - 27, - 26.9, - 26.9, - 26.7, - 26.6, - 26.4, - 26.3, - 26.3, - 26.3, - 26.4, - 26.5, - 26.6, - 26.6, - 26.6, - 26.4, - 26.3, - 26, - 26, - 26.1, - 26.1, - 25.9, - 25.8, - 25.2, - 25, - 24.7, - 24.3, - 24, - 23.8, - 23.7, - 23.7, - 23.4, - 37.2, - 37.6, - 37.9, - 38.2, - 38.4, - 38.5, - 38.7, - 39.1, - 39.2, - 39.3, - 39.5, - 39.5, - 39.7, - 39.8, - 40.1, - 40.2, - 40.2, - 40.3, - 40.4, - 40.6, - 40.8, - 41.1, - 41.3, - 41.4, - 41.5, - 41.8, - 42, - 42.2, - 42.3, - 42.4, - 42.7, - 42.8, - 43.3, - 43.4, - 43.5, - 43.6, - 43.7, - 43.7, - 43.9, - 44, - 44, - 44, - 44, - 44.1, - 44, - 43.9, - 43.9, - 44, - 44, - 44, - 44, - 43.9, - 43.8, - 43.6, - 43.7, - 43.6, - 43.4, - 43.4, - 43.3, - 43.1, - 42.9, - 42.7, - 42.5, - 42.3, - 42.2, - 42.1, - 42, - 41.9, - 41.8, - 41.5, - 41.3, - 41.2, - 41.1, - 40.9, - 40.8, - 40.8, - 40.8, - 40.8, - 40.8, - 40.8, - 40.8, - 40.7, - 40.7, - 40.7, - 40.6, - 40.5, - 40.5, - 40.6, - 40.6, - 40.8, - 40.8, - 40.9, - 40.9, - 40.8, - 40.7, - 40.5, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 40, - 40, - 40, - 40.1, - 40, - 40, - 39.8, - 39.6, - 39.2, - 38.9, - 38.9, - 38.8, - 38.9, - 38.9, - 39.1, - 39.3, - 39.4, - 39.4, - 39.5, - 39.4, - 39.4, - 39.3, - 39.2, - 39.2, - 39.1, - 38.8, - 38.6, - 38.5, - 38.4, - 38.2, - 38.1, - 38, - 37.9, - 37.7, - 37.4, - 37.1, - 36.9, - 36.9, - 36.8, - 36.8, - 36.7, - 36.5, - 36.1, - 36.2, - 36.2, - 36.2, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.7, - 35.6, - 35.6, - 35.6, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 35, - 35, - 35, - 34.8, - 34.5, - 34.3, - 34.1, - 34, - 33.9, - 33.8, - 33.7, - 33.4, - 33.3, - 33.3, - 33.2, - 33.1, - 33, - 32.9, - 33, - 33, - 32.9, - 32.9, - 32.8, - 32.8, - 32.8, - 32.7, - 32.6, - 32.6, - 32.6, - 32.6, - 32.6, - 32.6, - 32.8, - 33.1, - 33.1, - 33, - 32.9, - 32.9, - 32.9, - 32.6, - 32.2, - 32, - 31.8, - 31.6, - 31.4, - 31.2, - 30.9, - 30.7, - 30.4, - 30.2, - 29.9, - 29.7, - 29.5, - 29.3, - 29.1, - 28.9, - 28.7, - 28.5, - 28.4, - 28.2, - 27.9, - 27.6, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.7, - 26.6, - 26.6, - 26.5, - 26.5, - 26.5, - 26.5, - 26.6, - 26.6, - 26.6, - 26.6, - 26.4, - 26.1, - 26, - 26, - 25.9, - 25.8, - 25.5, - 25.1, - 24.9, - 24.7, - 24.5, - 24.3, - 24.1, - 24.1, - 23.9, - 37.2, - 37.6, - 37.9, - 38.1, - 38.2, - 38.4, - 38.6, - 38.9, - 39, - 39.1, - 39.3, - 39.4, - 39.5, - 39.7, - 39.9, - 40, - 40.2, - 40.4, - 40.5, - 40.7, - 40.9, - 41.2, - 41.4, - 41.7, - 41.9, - 42.2, - 42.5, - 42.8, - 42.9, - 43.1, - 43.2, - 43.5, - 43.8, - 43.9, - 44, - 44.1, - 44.2, - 44.2, - 44.3, - 44.4, - 44.4, - 44.5, - 44.6, - 44.5, - 44.5, - 44.4, - 44.3, - 44.3, - 44.4, - 44.4, - 44.4, - 44.3, - 44.1, - 43.9, - 43.8, - 43.7, - 43.6, - 43.5, - 43.4, - 43.2, - 43, - 42.9, - 42.7, - 42.5, - 42.4, - 42.3, - 42.1, - 42.1, - 42.1, - 41.9, - 41.7, - 41.6, - 41.5, - 41.5, - 41.4, - 41.2, - 41.2, - 41.2, - 41.2, - 41.2, - 41.2, - 41.1, - 41.1, - 41, - 41, - 40.9, - 40.9, - 40.9, - 40.9, - 41.1, - 41.2, - 41.1, - 41.1, - 41, - 41, - 41, - 40.8, - 40.6, - 40.5, - 40.4, - 40.4, - 40.5, - 40.4, - 40.4, - 40.3, - 40.3, - 40.3, - 40.2, - 40.1, - 39.6, - 39.2, - 39.1, - 39.2, - 39.3, - 39.4, - 39.5, - 39.7, - 39.9, - 39.9, - 39.9, - 39.9, - 40, - 39.7, - 39.5, - 39.5, - 39.4, - 39.2, - 38.9, - 38.8, - 38.8, - 38.7, - 38.5, - 38.3, - 38.1, - 38, - 37.8, - 37.5, - 37.3, - 37.2, - 37.1, - 37.1, - 37, - 36.9, - 36.7, - 36.4, - 36.4, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36.1, - 36, - 35.9, - 35.8, - 35.8, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.4, - 35.3, - 35.3, - 35.3, - 35.2, - 34.8, - 34.4, - 34.4, - 34.3, - 34.1, - 34, - 34, - 33.8, - 33.6, - 33.5, - 33.4, - 33.3, - 33.3, - 33.2, - 33.2, - 33.2, - 33.1, - 33, - 33, - 33.1, - 33.1, - 33.1, - 33.1, - 33.1, - 33.1, - 33, - 32.9, - 33, - 33.1, - 33.3, - 33.4, - 33.2, - 33.2, - 33.1, - 33.1, - 32.9, - 32.6, - 32.3, - 32.1, - 32, - 31.8, - 31.6, - 31.3, - 31, - 30.7, - 30.5, - 30.3, - 30.1, - 29.9, - 29.7, - 29.4, - 29.1, - 28.9, - 28.8, - 28.6, - 28.4, - 28.2, - 28, - 27.7, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27, - 26.9, - 26.8, - 26.8, - 26.8, - 26.8, - 26.9, - 26.8, - 26.8, - 26.6, - 26.6, - 26.5, - 26.5, - 26.3, - 26.1, - 26, - 25.7, - 25.7, - 25.5, - 25.2, - 24.9, - 24.9, - 24.8, - 24.6, - 24.5, - 24.4, - 24.2, - 37.2, - 37.6, - 37.9, - 38, - 38.2, - 38.4, - 38.5, - 38.6, - 38.8, - 38.9, - 39.3, - 39.4, - 39.5, - 39.6, - 39.8, - 40.2, - 40.3, - 40.4, - 40.5, - 40.8, - 41.2, - 41.4, - 41.7, - 42, - 42.3, - 42.7, - 43, - 43.2, - 43.3, - 43.5, - 43.8, - 44.1, - 44.2, - 44.2, - 44.3, - 44.5, - 44.6, - 44.6, - 44.7, - 44.7, - 44.7, - 44.8, - 45, - 44.9, - 44.9, - 44.8, - 44.7, - 44.7, - 44.7, - 44.6, - 44.6, - 44.6, - 44.3, - 44, - 43.9, - 43.8, - 43.7, - 43.6, - 43.5, - 43.3, - 43.2, - 43.1, - 43, - 42.8, - 42.6, - 42.5, - 42.4, - 42.4, - 42.4, - 42.3, - 42.2, - 42.1, - 42, - 42.1, - 42.2, - 42, - 41.7, - 41.7, - 41.6, - 41.6, - 41.6, - 41.5, - 41.4, - 41.4, - 41.5, - 41.5, - 41.3, - 41.2, - 41.2, - 41.4, - 41.5, - 41.5, - 41.5, - 41.5, - 41.4, - 41.4, - 41.3, - 41.1, - 41, - 40.9, - 40.8, - 40.8, - 40.8, - 40.8, - 40.7, - 40.7, - 40.6, - 40.5, - 40.6, - 40.2, - 39.6, - 39.5, - 39.5, - 39.7, - 40, - 40, - 40, - 40, - 40.2, - 40.3, - 40.4, - 40.4, - 40.3, - 40, - 39.8, - 39.7, - 39.6, - 39.2, - 39.1, - 39, - 39, - 38.9, - 38.8, - 38.6, - 38.3, - 38.2, - 37.9, - 37.7, - 37.6, - 37.6, - 37.7, - 37.5, - 37.4, - 37.2, - 36.8, - 36.6, - 36.6, - 36.6, - 36.5, - 36.4, - 36.3, - 36.3, - 36.2, - 36.2, - 36.1, - 36.1, - 36, - 36, - 35.9, - 35.8, - 35.8, - 35.7, - 35.6, - 35.6, - 35.6, - 35.4, - 35.2, - 35, - 34.8, - 34.7, - 34.5, - 34.2, - 34.2, - 34.1, - 33.9, - 33.7, - 33.7, - 33.6, - 33.6, - 33.5, - 33.5, - 33.5, - 33.4, - 33.3, - 33.3, - 33.3, - 33.4, - 33.5, - 33.5, - 33.5, - 33.4, - 33.4, - 33.3, - 33.4, - 33.4, - 33.4, - 33.4, - 33.4, - 33.3, - 33.3, - 33.3, - 33.2, - 32.9, - 32.7, - 32.5, - 32.4, - 32.2, - 31.9, - 31.7, - 31.3, - 31.1, - 30.9, - 30.7, - 30.5, - 30.2, - 30, - 29.8, - 29.5, - 29.2, - 29, - 28.8, - 28.6, - 28.5, - 28.3, - 28.1, - 27.8, - 27.6, - 27.5, - 27.4, - 27.3, - 27.2, - 27.1, - 27.1, - 27.1, - 27.1, - 27.1, - 27.2, - 27.3, - 27.2, - 26.9, - 26.7, - 26.5, - 26.5, - 26.4, - 26.3, - 26.1, - 25.9, - 25.9, - 25.8, - 25.4, - 25.1, - 24.8, - 24.8, - 24.6, - 24.5, - 24.5, - 24.4, - 37.2, - 37.4, - 37.7, - 37.9, - 38.1, - 38.2, - 38.3, - 38.5, - 38.7, - 38.9, - 39.2, - 39.3, - 39.5, - 39.8, - 40.1, - 40.2, - 40.3, - 40.5, - 40.7, - 41.1, - 41.4, - 41.7, - 42, - 42.3, - 42.7, - 43, - 43.2, - 43.5, - 43.7, - 43.9, - 44.2, - 44.5, - 44.6, - 44.7, - 44.8, - 44.8, - 45, - 45, - 45, - 45, - 45, - 45, - 45.1, - 45.2, - 45.1, - 45.1, - 45.1, - 44.9, - 44.8, - 44.8, - 44.7, - 44.7, - 44.5, - 44.2, - 44.1, - 43.9, - 43.8, - 43.8, - 43.7, - 43.6, - 43.4, - 43.3, - 43.1, - 43, - 42.9, - 42.9, - 42.7, - 42.7, - 42.7, - 42.6, - 42.6, - 42.5, - 42.5, - 42.4, - 42.5, - 42.6, - 42.5, - 42.4, - 42.4, - 42.3, - 42.1, - 42, - 41.9, - 41.8, - 41.9, - 41.9, - 41.7, - 41.6, - 41.5, - 41.6, - 41.8, - 41.8, - 41.9, - 41.9, - 41.9, - 41.9, - 41.7, - 41.6, - 41.5, - 41.3, - 41.2, - 41.2, - 41.2, - 41.2, - 41.2, - 41.1, - 41, - 40.9, - 40.9, - 40.6, - 40.1, - 39.9, - 40, - 40.1, - 40.3, - 40.5, - 40.6, - 40.4, - 40.4, - 40.5, - 40.5, - 40.6, - 40.6, - 40.4, - 40.2, - 40, - 39.9, - 39.7, - 39.5, - 39.3, - 39.2, - 39.2, - 39.1, - 38.9, - 38.7, - 38.4, - 38.2, - 38.1, - 38, - 37.9, - 37.9, - 37.8, - 37.7, - 37.7, - 37.4, - 37, - 36.9, - 36.9, - 36.7, - 36.6, - 36.6, - 36.5, - 36.4, - 36.4, - 36.5, - 36.4, - 36.4, - 36.3, - 36.2, - 36, - 36, - 35.9, - 35.8, - 35.8, - 35.8, - 35.7, - 35.6, - 35.5, - 35.3, - 35.1, - 34.9, - 34.6, - 34.5, - 34.4, - 34.1, - 34, - 33.9, - 33.9, - 33.8, - 33.8, - 33.8, - 33.8, - 33.7, - 33.7, - 33.7, - 33.7, - 33.8, - 33.9, - 33.9, - 33.9, - 33.8, - 33.7, - 33.7, - 33.7, - 33.6, - 33.5, - 33.5, - 33.4, - 33.3, - 33.4, - 33.5, - 33.5, - 33.3, - 33.1, - 32.9, - 32.8, - 32.5, - 32.2, - 31.9, - 31.7, - 31.4, - 31.2, - 31, - 30.7, - 30.5, - 30.3, - 30.1, - 29.8, - 29.5, - 29.3, - 29.1, - 28.9, - 28.8, - 28.6, - 28.4, - 28.2, - 27.9, - 27.7, - 27.6, - 27.5, - 27.5, - 27.4, - 27.4, - 27.4, - 27.5, - 27.5, - 27.5, - 27.5, - 27.6, - 27.5, - 27.2, - 26.9, - 26.7, - 26.6, - 26.4, - 26.3, - 26.2, - 26.1, - 26, - 25.6, - 25.3, - 24.9, - 24.8, - 24.7, - 24.6, - 24.5, - 24.4, - 37.2, - 37.5, - 37.6, - 37.8, - 38, - 38.1, - 38.2, - 38.4, - 38.7, - 38.9, - 39.1, - 39.3, - 39.6, - 39.9, - 40.2, - 40.4, - 40.4, - 40.5, - 40.8, - 41.2, - 41.6, - 42, - 42.2, - 42.6, - 43, - 43.3, - 43.5, - 43.7, - 44, - 44.2, - 44.5, - 44.8, - 45, - 45.2, - 45.4, - 45.3, - 45.3, - 45.3, - 45.3, - 45.3, - 45.3, - 45.3, - 45.3, - 45.3, - 45.2, - 45.2, - 45.2, - 45.1, - 45, - 44.9, - 44.8, - 44.8, - 44.6, - 44.4, - 44.2, - 44.1, - 44, - 43.9, - 43.8, - 43.8, - 43.6, - 43.5, - 43.4, - 43.3, - 43.1, - 43.1, - 43, - 42.9, - 42.9, - 42.9, - 42.9, - 42.9, - 42.9, - 42.9, - 42.9, - 43.1, - 43.1, - 43.1, - 43.2, - 43.1, - 42.9, - 42.6, - 42.4, - 42.3, - 42.3, - 42.2, - 42.2, - 42.1, - 42, - 41.9, - 41.9, - 42.1, - 42.1, - 42.1, - 42.1, - 42, - 42, - 41.9, - 41.9, - 41.7, - 41.6, - 41.6, - 41.6, - 41.6, - 41.6, - 41.4, - 41.2, - 41.2, - 41.1, - 41, - 40.5, - 40.4, - 40.5, - 40.7, - 40.9, - 41.1, - 41.2, - 41.2, - 41, - 40.8, - 40.8, - 40.7, - 40.7, - 40.6, - 40.5, - 40.4, - 40.3, - 40.1, - 39.9, - 39.7, - 39.5, - 39.5, - 39.4, - 39.2, - 39, - 38.8, - 38.5, - 38.4, - 38.3, - 38.2, - 38.2, - 38.1, - 38, - 38, - 37.9, - 37.6, - 37.4, - 37.2, - 37.1, - 36.9, - 36.8, - 36.8, - 36.7, - 36.7, - 36.7, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.1, - 36.1, - 36, - 35.9, - 35.9, - 35.9, - 35.8, - 35.6, - 35.4, - 35.2, - 35, - 34.8, - 34.7, - 34.5, - 34.3, - 34.3, - 34.2, - 34.1, - 34.1, - 34.2, - 34.1, - 34, - 34.1, - 34.1, - 34.2, - 34.2, - 34.3, - 34.4, - 34.3, - 34.2, - 34.1, - 34, - 34, - 33.9, - 33.8, - 33.7, - 33.6, - 33.5, - 33.6, - 33.6, - 33.7, - 33.7, - 33.6, - 33.3, - 33.1, - 32.9, - 32.6, - 32.3, - 32, - 31.7, - 31.5, - 31.3, - 31, - 30.8, - 30.6, - 30.3, - 30.1, - 29.8, - 29.6, - 29.4, - 29.3, - 29.1, - 28.9, - 28.7, - 28.5, - 28.3, - 28.1, - 27.9, - 27.9, - 27.9, - 27.8, - 27.7, - 27.7, - 27.7, - 27.8, - 27.9, - 27.9, - 27.9, - 27.7, - 27.4, - 27.1, - 27.1, - 26.9, - 26.7, - 26.6, - 26.5, - 26.3, - 26.2, - 25.9, - 25.5, - 25.1, - 24.8, - 24.7, - 24.6, - 24.5, - 24.4, - 37.2, - 37.4, - 37.6, - 37.8, - 38, - 38, - 38.2, - 38.4, - 38.7, - 39, - 39.2, - 39.4, - 39.7, - 39.9, - 40.2, - 40.4, - 40.6, - 40.7, - 41, - 41.4, - 41.9, - 42.3, - 42.5, - 42.9, - 43.3, - 43.5, - 43.8, - 44, - 44.2, - 44.5, - 44.7, - 45, - 45.3, - 45.6, - 45.6, - 45.7, - 45.7, - 45.6, - 45.6, - 45.7, - 45.7, - 45.7, - 45.6, - 45.5, - 45.5, - 45.4, - 45.4, - 45.3, - 45.2, - 45.1, - 45, - 44.8, - 44.7, - 44.5, - 44.4, - 44.3, - 44.2, - 44.1, - 44, - 43.9, - 43.9, - 43.8, - 43.7, - 43.6, - 43.4, - 43.3, - 43.4, - 43.2, - 43.2, - 43.2, - 43.3, - 43.3, - 43.5, - 43.4, - 43.4, - 43.6, - 43.6, - 43.8, - 43.9, - 43.8, - 43.7, - 43.4, - 43.1, - 42.8, - 42.7, - 42.6, - 42.6, - 42.5, - 42.4, - 42.3, - 42.3, - 42.3, - 42.4, - 42.4, - 42.5, - 42.4, - 42.3, - 42.1, - 42.1, - 42, - 42, - 42, - 42, - 42, - 41.9, - 41.7, - 41.5, - 41.4, - 41.4, - 41.3, - 41, - 40.9, - 41.2, - 41.3, - 41.4, - 41.5, - 41.5, - 41.6, - 41.5, - 41.3, - 41.2, - 41, - 41, - 40.9, - 40.8, - 40.7, - 40.6, - 40.4, - 40.2, - 40, - 39.8, - 39.8, - 39.7, - 39.6, - 39.4, - 39.2, - 38.9, - 38.8, - 38.7, - 38.6, - 38.6, - 38.4, - 38.4, - 38.3, - 38.3, - 38.2, - 37.9, - 37.6, - 37.4, - 37.4, - 37.1, - 37, - 37, - 36.9, - 36.9, - 36.8, - 36.7, - 36.7, - 36.6, - 36.5, - 36.4, - 36.3, - 36.3, - 36.2, - 36.1, - 36, - 36, - 36, - 35.9, - 35.7, - 35.6, - 35.3, - 35, - 34.9, - 34.7, - 34.6, - 34.5, - 34.5, - 34.5, - 34.5, - 34.5, - 34.4, - 34.4, - 34.5, - 34.5, - 34.6, - 34.6, - 34.6, - 34.7, - 34.7, - 34.6, - 34.5, - 34.4, - 34.3, - 34.2, - 34.1, - 34, - 34, - 33.9, - 33.8, - 33.8, - 33.8, - 33.8, - 33.9, - 33.7, - 33.5, - 33.2, - 32.9, - 32.6, - 32.4, - 32, - 31.8, - 31.5, - 31.4, - 31.1, - 30.9, - 30.6, - 30.4, - 30.2, - 30, - 29.8, - 29.6, - 29.3, - 29.1, - 29, - 28.8, - 28.6, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.1, - 28, - 27.9, - 27.9, - 28.1, - 28.1, - 28.1, - 27.9, - 27.7, - 27.5, - 27.4, - 27.2, - 27.1, - 26.9, - 26.7, - 26.5, - 26.4, - 26.2, - 25.9, - 25.3, - 24.9, - 24.9, - 24.8, - 24.6, - 24.5, - 37.2, - 37.4, - 37.6, - 37.7, - 37.9, - 37.9, - 38.1, - 38.4, - 38.7, - 38.9, - 39.2, - 39.4, - 39.7, - 40.1, - 40.3, - 40.5, - 40.9, - 41.1, - 41.3, - 41.8, - 42.1, - 42.4, - 42.7, - 43, - 43.4, - 43.7, - 43.8, - 44.1, - 44.3, - 44.5, - 44.8, - 45.1, - 45.4, - 45.7, - 45.8, - 45.8, - 45.8, - 45.8, - 45.8, - 46, - 46, - 45.9, - 45.8, - 45.7, - 45.6, - 45.6, - 45.7, - 45.6, - 45.4, - 45.2, - 45.1, - 44.9, - 44.8, - 44.6, - 44.5, - 44.4, - 44.3, - 44.3, - 44.2, - 44.1, - 44, - 43.9, - 43.8, - 43.8, - 43.6, - 43.5, - 43.5, - 43.5, - 43.5, - 43.6, - 43.6, - 43.7, - 43.9, - 43.9, - 44, - 44.1, - 44.2, - 44.3, - 44.4, - 44.3, - 44.4, - 44.2, - 43.9, - 43.5, - 43.2, - 43.2, - 43.1, - 42.9, - 42.9, - 42.8, - 42.6, - 42.7, - 42.7, - 42.7, - 42.8, - 42.8, - 42.7, - 42.5, - 42.4, - 42.3, - 42.3, - 42.3, - 42.2, - 42.2, - 42.1, - 42, - 41.7, - 41.7, - 41.6, - 41.5, - 41.5, - 41.4, - 41.6, - 41.8, - 41.7, - 41.7, - 41.7, - 41.6, - 41.6, - 41.6, - 41.5, - 41.4, - 41.3, - 41.2, - 41.2, - 41.1, - 41, - 40.8, - 40.6, - 40.4, - 40.3, - 40.1, - 40, - 39.9, - 39.7, - 39.5, - 39.3, - 39.2, - 39, - 38.9, - 38.9, - 38.8, - 38.7, - 38.6, - 38.5, - 38.5, - 38.4, - 38.2, - 37.8, - 37.7, - 37.5, - 37.4, - 37.3, - 37.3, - 37.2, - 37.1, - 37, - 36.8, - 36.7, - 36.7, - 36.6, - 36.6, - 36.6, - 36.6, - 36.6, - 36.3, - 36.3, - 36.3, - 36.2, - 36.1, - 35.8, - 35.5, - 35.3, - 35.1, - 35, - 34.9, - 34.8, - 34.8, - 34.7, - 34.7, - 34.8, - 34.8, - 34.9, - 34.9, - 35.1, - 35.1, - 35.1, - 35.1, - 35, - 35, - 35, - 34.9, - 34.8, - 34.7, - 34.6, - 34.5, - 34.5, - 34.5, - 34.3, - 34.1, - 34, - 33.9, - 33.9, - 33.9, - 33.8, - 33.7, - 33.6, - 33.3, - 33, - 32.7, - 32.4, - 32.2, - 31.9, - 31.7, - 31.5, - 31.3, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 29.9, - 29.6, - 29.4, - 29.2, - 29.1, - 28.9, - 28.8, - 28.8, - 28.6, - 28.5, - 28.4, - 28.4, - 28.3, - 28.2, - 28.2, - 28.2, - 28.2, - 28.2, - 28, - 27.9, - 27.7, - 27.5, - 27.4, - 27.3, - 27.1, - 26.9, - 26.8, - 26.8, - 26.6, - 26.3, - 25.8, - 25.4, - 25.1, - 24.9, - 24.8, - 24.7, - 37.2, - 37.4, - 37.5, - 37.7, - 37.9, - 38, - 38.2, - 38.4, - 38.7, - 39, - 39.3, - 39.6, - 39.9, - 40.3, - 40.6, - 40.9, - 41.4, - 41.5, - 41.7, - 42.1, - 42.5, - 42.6, - 42.9, - 43.4, - 43.6, - 43.9, - 44.2, - 44.4, - 44.6, - 44.8, - 45, - 45.3, - 45.5, - 45.7, - 45.9, - 45.9, - 45.9, - 45.8, - 45.8, - 45.9, - 46, - 45.9, - 45.7, - 45.7, - 45.6, - 45.5, - 45.6, - 45.6, - 45.5, - 45.3, - 45.2, - 45, - 45, - 44.8, - 44.7, - 44.6, - 44.5, - 44.5, - 44.4, - 44.3, - 44.2, - 44.2, - 44, - 43.9, - 43.8, - 43.7, - 43.7, - 43.9, - 43.9, - 43.8, - 43.9, - 44.1, - 44.3, - 44.4, - 44.5, - 44.5, - 44.6, - 44.6, - 44.6, - 44.6, - 44.6, - 44.5, - 44.3, - 44.1, - 43.9, - 43.7, - 43.6, - 43.4, - 43.2, - 43.1, - 43, - 42.9, - 42.9, - 42.9, - 43, - 42.9, - 42.9, - 42.8, - 42.7, - 42.6, - 42.6, - 42.5, - 42.4, - 42.4, - 42.3, - 42.2, - 42.1, - 42.1, - 41.9, - 41.9, - 41.8, - 41.8, - 41.9, - 42, - 42, - 41.9, - 41.8, - 41.6, - 41.6, - 41.6, - 41.6, - 41.7, - 41.6, - 41.5, - 41.4, - 41.4, - 41.2, - 41.1, - 41, - 40.8, - 40.6, - 40.5, - 40.3, - 40.2, - 40.1, - 39.8, - 39.6, - 39.5, - 39.4, - 39.3, - 39.1, - 39.1, - 39, - 38.9, - 38.9, - 38.9, - 38.8, - 38.6, - 38.3, - 38, - 37.8, - 37.7, - 37.6, - 37.5, - 37.5, - 37.4, - 37.3, - 37.1, - 37, - 37, - 36.9, - 36.9, - 36.9, - 36.9, - 36.8, - 36.7, - 36.5, - 36.5, - 36.4, - 36.2, - 35.9, - 35.7, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 35.1, - 35, - 35, - 35, - 35.2, - 35.3, - 35.5, - 35.7, - 35.7, - 35.4, - 35.3, - 35.3, - 35.2, - 35.2, - 35.1, - 35, - 35, - 35, - 35, - 34.9, - 34.8, - 34.7, - 34.5, - 34.3, - 34.1, - 34, - 34, - 33.9, - 33.9, - 33.9, - 33.7, - 33.4, - 33, - 32.8, - 32.6, - 32.3, - 32.1, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.8, - 30.6, - 30.3, - 29.9, - 29.7, - 29.6, - 29.4, - 29.2, - 29.2, - 29.1, - 29, - 28.8, - 28.7, - 28.7, - 28.6, - 28.6, - 28.5, - 28.2, - 28.3, - 28.3, - 28.3, - 28.1, - 27.9, - 27.7, - 27.6, - 27.5, - 27.2, - 27.1, - 27.1, - 27.1, - 27, - 26.7, - 26.1, - 25.8, - 25.6, - 25.2, - 24.9, - 24.8, - 37.1, - 37.4, - 37.6, - 37.8, - 38, - 38.2, - 38.4, - 38.5, - 38.9, - 39.3, - 39.5, - 39.8, - 40.2, - 40.6, - 40.9, - 41.3, - 41.6, - 41.8, - 42, - 42.3, - 42.8, - 43, - 43.3, - 43.7, - 44, - 44.4, - 44.7, - 44.9, - 44.9, - 45.1, - 45.3, - 45.5, - 45.7, - 45.8, - 46, - 46, - 45.9, - 45.8, - 45.7, - 45.8, - 45.8, - 45.8, - 45.6, - 45.5, - 45.5, - 45.5, - 45.5, - 45.5, - 45.4, - 45.3, - 45.3, - 45.1, - 45, - 44.9, - 44.8, - 44.7, - 44.6, - 44.6, - 44.5, - 44.4, - 44.4, - 44.4, - 44.3, - 44.1, - 44.1, - 44, - 44, - 44.2, - 44.2, - 44.2, - 44.3, - 44.3, - 44.4, - 44.6, - 44.7, - 44.7, - 44.8, - 44.8, - 44.8, - 44.7, - 44.6, - 44.7, - 44.5, - 44.3, - 44.2, - 44, - 43.8, - 43.8, - 43.6, - 43.4, - 43.3, - 43.2, - 43.1, - 43.1, - 43, - 43, - 43.1, - 43, - 43, - 42.9, - 42.8, - 42.8, - 42.6, - 42.6, - 42.5, - 42.4, - 42.4, - 42.3, - 42.3, - 42.2, - 42.2, - 42.1, - 42.2, - 42.1, - 42.1, - 42, - 41.9, - 41.7, - 41.7, - 41.7, - 41.7, - 41.7, - 41.7, - 41.8, - 41.9, - 41.8, - 41.5, - 41.4, - 41.2, - 41.1, - 40.9, - 40.7, - 40.5, - 40.4, - 40.2, - 40, - 39.8, - 39.7, - 39.5, - 39.5, - 39.4, - 39.3, - 39.3, - 39.3, - 39.3, - 39.1, - 39.1, - 39, - 38.9, - 38.6, - 38.2, - 38, - 37.8, - 37.7, - 37.7, - 37.7, - 37.6, - 37.4, - 37.3, - 37.2, - 37.2, - 37.2, - 37.2, - 37.1, - 37, - 37, - 36.8, - 36.8, - 36.7, - 36.5, - 36.3, - 36, - 35.8, - 35.6, - 35.5, - 35.5, - 35.4, - 35.3, - 35.2, - 35.2, - 35.4, - 35.6, - 35.8, - 36, - 36.1, - 36, - 35.8, - 35.6, - 35.5, - 35.5, - 35.5, - 35.4, - 35.3, - 35.3, - 35.3, - 35.3, - 35.2, - 35.1, - 34.9, - 34.8, - 34.6, - 34.5, - 34.3, - 34.2, - 34.1, - 34, - 34, - 33.9, - 33.7, - 33.5, - 33.3, - 33.1, - 32.7, - 32.5, - 32.2, - 31.9, - 31.7, - 31.4, - 31.3, - 31.2, - 30.9, - 30.6, - 30.3, - 30.1, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 28.8, - 28.5, - 28.6, - 28.6, - 28.5, - 28.4, - 28.2, - 28.1, - 28, - 27.8, - 27.6, - 27.4, - 27.3, - 27.2, - 27.1, - 26.8, - 26.4, - 26.1, - 25.9, - 25.6, - 25.3, - 25.1, - 37.2, - 37.4, - 37.6, - 37.9, - 38.1, - 38.3, - 38.6, - 38.9, - 39.3, - 39.6, - 39.9, - 40.2, - 40.5, - 40.9, - 41.3, - 41.5, - 41.9, - 42.1, - 42.4, - 42.6, - 43, - 43.2, - 43.6, - 43.9, - 44.4, - 44.8, - 45.1, - 45.3, - 45.3, - 45.4, - 45.4, - 45.5, - 45.7, - 45.9, - 45.9, - 45.9, - 45.8, - 45.7, - 45.6, - 45.6, - 45.6, - 45.5, - 45.5, - 45.5, - 45.4, - 45.5, - 45.5, - 45.4, - 45.3, - 45.3, - 45.2, - 45.1, - 45.1, - 45.1, - 45, - 44.9, - 44.9, - 44.8, - 44.7, - 44.6, - 44.5, - 44.5, - 44.4, - 44.4, - 44.3, - 44.3, - 44.3, - 44.3, - 44.5, - 44.7, - 44.7, - 44.6, - 44.7, - 44.9, - 45, - 45, - 45, - 45, - 45, - 44.9, - 44.7, - 44.8, - 44.7, - 44.5, - 44.4, - 44.3, - 44.2, - 44.1, - 44, - 43.8, - 43.7, - 43.6, - 43.6, - 43.5, - 43.3, - 43.1, - 43.1, - 43.2, - 43.2, - 43.1, - 43, - 42.9, - 42.8, - 42.7, - 42.7, - 42.6, - 42.5, - 42.5, - 42.4, - 42.3, - 42.3, - 42.3, - 42.3, - 42.2, - 42.1, - 42.1, - 41.9, - 41.9, - 41.8, - 41.8, - 41.8, - 41.8, - 41.8, - 42, - 42.2, - 42.4, - 42.1, - 41.8, - 41.5, - 41.3, - 41.1, - 41, - 40.9, - 40.7, - 40.6, - 40.3, - 40.1, - 40, - 39.9, - 39.7, - 39.6, - 39.6, - 39.5, - 39.6, - 39.6, - 39.4, - 39.3, - 39.3, - 39.2, - 39, - 38.5, - 38.3, - 38, - 37.8, - 37.8, - 37.7, - 37.7, - 37.6, - 37.5, - 37.5, - 37.5, - 37.5, - 37.4, - 37.3, - 37.1, - 37.1, - 37.1, - 37.1, - 37.1, - 37, - 36.7, - 36.4, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.6, - 35.5, - 35.6, - 35.7, - 36, - 36.2, - 36.4, - 36.4, - 36.3, - 36.1, - 35.9, - 35.8, - 35.8, - 35.8, - 35.8, - 35.7, - 35.7, - 35.6, - 35.5, - 35.4, - 35.4, - 35.3, - 35.1, - 34.9, - 34.8, - 34.6, - 34.4, - 34.3, - 34.2, - 34.1, - 34, - 33.9, - 33.7, - 33.6, - 33.4, - 33.2, - 32.9, - 32.7, - 32.4, - 32.1, - 31.8, - 31.6, - 31.5, - 31.2, - 30.9, - 30.6, - 30.4, - 30.3, - 30.2, - 30.1, - 29.9, - 29.9, - 29.8, - 29.6, - 29.5, - 29.4, - 29.1, - 29.1, - 29.1, - 28.9, - 29, - 29, - 28.9, - 28.8, - 28.6, - 28.5, - 28.4, - 28.2, - 28, - 27.8, - 27.6, - 27.4, - 27.1, - 26.9, - 26.6, - 26.4, - 26.3, - 26, - 25.5, - 25.4, - 37.3, - 37.6, - 37.8, - 38, - 38.2, - 38.5, - 38.9, - 39.3, - 39.7, - 40, - 40.3, - 40.5, - 40.8, - 41.2, - 41.6, - 41.8, - 42, - 42.3, - 42.5, - 42.8, - 43.1, - 43.5, - 43.9, - 44.2, - 44.6, - 44.9, - 45.3, - 45.5, - 45.5, - 45.4, - 45.4, - 45.5, - 45.6, - 45.7, - 45.7, - 45.7, - 45.7, - 45.5, - 45.5, - 45.5, - 45.5, - 45.4, - 45.4, - 45.4, - 45.4, - 45.5, - 45.5, - 45.4, - 45.3, - 45.3, - 45.2, - 45.3, - 45.3, - 45.1, - 45.1, - 45.1, - 45.1, - 45, - 44.8, - 44.8, - 44.7, - 44.7, - 44.6, - 44.6, - 44.6, - 44.5, - 44.6, - 44.6, - 44.7, - 44.9, - 45, - 45, - 45, - 45.1, - 45.1, - 45.1, - 45.1, - 45.1, - 45.1, - 45.1, - 44.9, - 44.9, - 44.9, - 44.7, - 44.7, - 44.6, - 44.5, - 44.4, - 44.3, - 44.1, - 44, - 43.9, - 43.8, - 43.7, - 43.5, - 43.3, - 43.2, - 43.2, - 43.2, - 43.3, - 43.3, - 43.2, - 43, - 42.8, - 42.8, - 42.7, - 42.6, - 42.6, - 42.7, - 42.6, - 42.6, - 42.6, - 42.5, - 42.4, - 42.1, - 42.1, - 42.1, - 42.1, - 42, - 41.9, - 41.9, - 42, - 42, - 42.2, - 42.4, - 42.5, - 42.4, - 42.2, - 42, - 41.7, - 41.5, - 41.3, - 41.2, - 41.1, - 40.9, - 40.8, - 40.7, - 40.5, - 40.3, - 40.2, - 40, - 40, - 39.9, - 39.8, - 39.7, - 39.7, - 39.6, - 39.5, - 39.4, - 39.2, - 38.8, - 38.6, - 38.3, - 38.1, - 37.9, - 37.9, - 37.9, - 37.8, - 37.8, - 37.7, - 37.7, - 37.8, - 37.7, - 37.6, - 37.5, - 37.3, - 37.3, - 37.3, - 37.3, - 37.3, - 37, - 36.7, - 36.5, - 36.2, - 36.1, - 35.9, - 35.8, - 35.8, - 35.8, - 36, - 36.2, - 36.3, - 36.5, - 36.7, - 36.7, - 36.6, - 36.6, - 36.5, - 36.4, - 36.3, - 36.2, - 36.2, - 36.1, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.6, - 35.3, - 35.2, - 35, - 34.8, - 34.7, - 34.6, - 34.5, - 34.4, - 34.2, - 34.1, - 33.9, - 33.7, - 33.6, - 33.4, - 33.2, - 33, - 32.8, - 32.6, - 32.3, - 32, - 31.8, - 31.5, - 31.3, - 31, - 30.8, - 30.6, - 30.4, - 30.2, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.3, - 29.2, - 29.2, - 29.2, - 29.1, - 28.9, - 28.7, - 28.6, - 28.4, - 28.2, - 28.1, - 27.9, - 27.7, - 27.6, - 27.3, - 27, - 26.7, - 26.6, - 26.5, - 26.4, - 25.9, - 25.5, - 37.4, - 37.7, - 38, - 38.3, - 38.5, - 38.9, - 39.3, - 39.6, - 40, - 40.3, - 40.6, - 40.8, - 41.1, - 41.4, - 41.8, - 42, - 42.2, - 42.4, - 42.6, - 42.9, - 43.2, - 43.7, - 44, - 44.3, - 44.6, - 44.8, - 45.1, - 45.3, - 45.5, - 45.5, - 45.3, - 45.4, - 45.5, - 45.5, - 45.5, - 45.4, - 45.4, - 45.3, - 45.3, - 45.3, - 45.4, - 45.4, - 45.4, - 45.5, - 45.4, - 45.4, - 45.4, - 45.4, - 45.4, - 45.4, - 45.4, - 45.3, - 45.4, - 45.4, - 45.3, - 45.3, - 45.3, - 45.2, - 45, - 45, - 44.9, - 44.9, - 44.9, - 44.9, - 44.8, - 44.8, - 44.9, - 45, - 45, - 45, - 45.2, - 45.2, - 45.2, - 45.3, - 45.3, - 45.2, - 45.2, - 45.2, - 45.2, - 45.2, - 45.2, - 45.1, - 45.1, - 45, - 45, - 44.9, - 44.8, - 44.6, - 44.5, - 44.4, - 44.3, - 44.2, - 44, - 43.8, - 43.7, - 43.7, - 43.6, - 43.5, - 43.4, - 43.4, - 43.4, - 43.3, - 43.2, - 43, - 42.9, - 42.8, - 42.8, - 42.8, - 42.8, - 42.9, - 42.8, - 42.8, - 42.8, - 42.7, - 42.6, - 42.5, - 42.5, - 42.5, - 42.4, - 42.4, - 42.4, - 42.4, - 42.6, - 42.7, - 42.7, - 42.7, - 42.6, - 42.5, - 42.4, - 42, - 41.7, - 41.5, - 41.4, - 41.3, - 41.3, - 41.2, - 41, - 41, - 40.8, - 40.5, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.9, - 39.8, - 39.6, - 39.5, - 39.2, - 38.9, - 38.5, - 38.2, - 38.1, - 38.1, - 38.1, - 38.1, - 38.2, - 38.1, - 37.9, - 37.9, - 37.9, - 37.9, - 37.9, - 37.8, - 37.7, - 37.5, - 37.5, - 37.4, - 37.2, - 36.9, - 36.7, - 36.5, - 36.4, - 36.3, - 36.1, - 36.1, - 36.3, - 36.5, - 36.6, - 36.7, - 36.8, - 36.9, - 36.9, - 36.9, - 36.9, - 36.9, - 36.9, - 36.7, - 36.6, - 36.6, - 36.6, - 36.5, - 36.3, - 36.2, - 36, - 35.8, - 35.7, - 35.5, - 35.4, - 35.3, - 35.2, - 35, - 34.9, - 34.8, - 34.7, - 34.5, - 34.4, - 34.1, - 33.9, - 33.8, - 33.5, - 33.3, - 33.1, - 32.9, - 32.8, - 32.6, - 32.3, - 32.2, - 31.9, - 31.6, - 31.4, - 31.1, - 30.9, - 30.7, - 30.5, - 30.3, - 30.1, - 30.1, - 30, - 29.9, - 29.8, - 29.7, - 29.6, - 29.5, - 29.4, - 29.2, - 29.2, - 29.1, - 29, - 28.9, - 28.8, - 28.6, - 28.5, - 28.3, - 28.1, - 28, - 27.8, - 27.6, - 27.3, - 27, - 26.6, - 26.5, - 26.5, - 26.3, - 25.9, - 37.6, - 37.9, - 38.2, - 38.6, - 38.9, - 39.2, - 39.8, - 40.1, - 40.3, - 40.5, - 40.7, - 41.1, - 41.4, - 41.6, - 41.9, - 42.2, - 42.5, - 42.6, - 42.6, - 43, - 43.3, - 43.6, - 43.9, - 44.2, - 44.5, - 44.6, - 44.8, - 45.1, - 45.4, - 45.4, - 45.3, - 45.2, - 45.3, - 45.3, - 45.2, - 45.2, - 45.2, - 45.2, - 45.2, - 45.3, - 45.4, - 45.4, - 45.4, - 45.4, - 45.4, - 45.4, - 45.4, - 45.5, - 45.5, - 45.4, - 45.4, - 45.4, - 45.4, - 45.5, - 45.5, - 45.5, - 45.4, - 45.3, - 45.2, - 45.2, - 45.1, - 45.1, - 45.2, - 45.3, - 45.3, - 45.4, - 45.4, - 45.5, - 45.4, - 45.5, - 45.5, - 45.5, - 45.4, - 45.5, - 45.4, - 45.4, - 45.4, - 45.5, - 45.4, - 45.4, - 45.4, - 45.3, - 45.3, - 45.2, - 45.1, - 45.1, - 45, - 45, - 44.9, - 44.7, - 44.6, - 44.4, - 44.3, - 44.2, - 44.2, - 44.3, - 44.2, - 44, - 43.9, - 43.7, - 43.6, - 43.5, - 43.4, - 43.2, - 43.1, - 43, - 43, - 43, - 43, - 43, - 43.1, - 43.1, - 43.1, - 43.1, - 43.1, - 43, - 42.9, - 42.9, - 43, - 43, - 43, - 42.9, - 43, - 43.1, - 43, - 42.9, - 42.8, - 42.7, - 42.6, - 42.2, - 41.9, - 41.7, - 41.6, - 41.5, - 41.3, - 41.2, - 41.1, - 41.1, - 41, - 40.9, - 40.7, - 40.5, - 40.3, - 40.2, - 40.1, - 40, - 40, - 40.1, - 39.9, - 39.7, - 39.5, - 39.1, - 38.7, - 38.4, - 38.3, - 38.4, - 38.4, - 38.5, - 38.4, - 38.4, - 38.3, - 38.3, - 38.2, - 38.2, - 38.2, - 38.1, - 37.9, - 37.8, - 37.7, - 37.6, - 37.4, - 37.2, - 37, - 36.8, - 36.7, - 36.6, - 36.6, - 36.7, - 36.8, - 36.9, - 37, - 37.1, - 37.2, - 37.2, - 37.2, - 37.2, - 37.1, - 37.1, - 37.2, - 37.2, - 37, - 37, - 36.9, - 36.8, - 36.7, - 36.6, - 36.4, - 36.1, - 35.9, - 35.8, - 35.6, - 35.5, - 35.5, - 35.3, - 35.2, - 35.1, - 35, - 34.9, - 34.8, - 34.6, - 34.4, - 34.1, - 33.9, - 33.6, - 33.3, - 33.1, - 32.9, - 32.8, - 32.7, - 32.5, - 32.3, - 32, - 31.8, - 31.5, - 31.2, - 31, - 30.9, - 30.6, - 30.4, - 30.2, - 30.1, - 30.1, - 30.1, - 30, - 29.9, - 29.7, - 29.6, - 29.4, - 29.4, - 29.4, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.7, - 28.5, - 28.3, - 28.2, - 28.1, - 27.9, - 27.7, - 27.3, - 26.8, - 26.6, - 26.5, - 26.2, - 37.9, - 38.3, - 38.7, - 39, - 39.3, - 39.6, - 40.1, - 40.5, - 40.5, - 40.7, - 40.9, - 41.2, - 41.5, - 41.7, - 41.9, - 42.2, - 42.4, - 42.6, - 42.6, - 42.8, - 43.2, - 43.4, - 43.7, - 44, - 44.2, - 44.4, - 44.6, - 44.8, - 45.1, - 45.1, - 45.1, - 45.1, - 45.1, - 45.1, - 45.1, - 45.1, - 45.1, - 45.2, - 45.2, - 45.2, - 45.3, - 45.4, - 45.4, - 45.4, - 45.4, - 45.5, - 45.5, - 45.5, - 45.5, - 45.5, - 45.5, - 45.4, - 45.5, - 45.5, - 45.5, - 45.5, - 45.6, - 45.6, - 45.6, - 45.6, - 45.6, - 45.6, - 45.8, - 45.9, - 45.9, - 46, - 46.1, - 45.9, - 45.9, - 46, - 46, - 46, - 45.8, - 45.7, - 45.7, - 45.6, - 45.7, - 45.7, - 45.6, - 45.6, - 45.6, - 45.6, - 45.5, - 45.4, - 45.3, - 45.3, - 45.3, - 45.2, - 45.2, - 45.1, - 45, - 44.8, - 44.7, - 44.6, - 44.6, - 44.5, - 44.6, - 44.5, - 44.3, - 44.1, - 43.9, - 43.8, - 43.6, - 43.4, - 43.4, - 43.3, - 43.3, - 43.3, - 43.2, - 43.3, - 43.3, - 43.3, - 43.4, - 43.4, - 43.5, - 43.4, - 43.5, - 43.4, - 43.4, - 43.5, - 43.5, - 43.4, - 43.4, - 43.3, - 43.1, - 43.1, - 43, - 42.9, - 42.8, - 42.5, - 42.2, - 42, - 41.8, - 41.7, - 41.5, - 41.4, - 41.2, - 41.1, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.4, - 40.2, - 40.1, - 40.2, - 40.2, - 40.1, - 39.9, - 39.7, - 39.4, - 39, - 38.8, - 38.7, - 38.7, - 38.7, - 38.9, - 38.9, - 38.7, - 38.7, - 38.7, - 38.7, - 38.6, - 38.4, - 38.4, - 38.2, - 38.2, - 38, - 37.8, - 37.7, - 37.5, - 37.3, - 37.2, - 37.1, - 37, - 37.2, - 37.3, - 37.3, - 37.3, - 37.3, - 37.5, - 37.5, - 37.6, - 37.6, - 37.6, - 37.5, - 37.4, - 37.4, - 37.5, - 37.5, - 37.3, - 37.2, - 37.1, - 37, - 36.8, - 36.6, - 36.4, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.3, - 35.2, - 35.1, - 34.9, - 34.7, - 34.5, - 34.2, - 34, - 33.6, - 33.3, - 33, - 32.9, - 32.8, - 32.7, - 32.6, - 32.4, - 32.2, - 31.9, - 31.6, - 31.3, - 31.2, - 31, - 30.9, - 30.6, - 30.5, - 30.3, - 30.3, - 30.2, - 30.1, - 29.9, - 29.8, - 29.8, - 29.7, - 29.7, - 29.7, - 29.5, - 29.4, - 29.3, - 29.2, - 29, - 28.8, - 28.7, - 28.7, - 28.6, - 28.5, - 28.3, - 28.1, - 27.5, - 27.1, - 26.7, - 26.4, - 38.5, - 38.9, - 39.2, - 39.6, - 39.9, - 40.1, - 40.4, - 40.6, - 40.7, - 40.8, - 41, - 41.2, - 41.5, - 41.6, - 41.6, - 41.9, - 42.2, - 42.4, - 42.6, - 42.7, - 43, - 43.2, - 43.5, - 43.7, - 44, - 44.2, - 44.4, - 44.5, - 44.7, - 44.8, - 44.9, - 44.9, - 44.9, - 44.9, - 45, - 45, - 45.1, - 45.2, - 45.2, - 45.2, - 45.3, - 45.3, - 45.3, - 45.4, - 45.5, - 45.5, - 45.5, - 45.5, - 45.5, - 45.6, - 45.6, - 45.6, - 45.6, - 45.7, - 45.7, - 45.7, - 45.8, - 45.9, - 46, - 46, - 46, - 46, - 46.1, - 46.2, - 46.3, - 46.4, - 46.5, - 46.4, - 46.3, - 46.4, - 46.3, - 46.2, - 46.1, - 46, - 46, - 46, - 46, - 45.9, - 46, - 45.9, - 45.9, - 45.9, - 45.8, - 45.7, - 45.6, - 45.5, - 45.5, - 45.3, - 45.2, - 45.3, - 45.3, - 45.1, - 44.9, - 44.8, - 44.7, - 44.6, - 44.6, - 44.5, - 44.4, - 44.4, - 44.3, - 44.1, - 43.9, - 43.8, - 43.8, - 43.7, - 43.6, - 43.5, - 43.5, - 43.5, - 43.6, - 43.6, - 43.6, - 43.7, - 43.7, - 43.8, - 43.9, - 43.7, - 43.8, - 43.7, - 43.7, - 43.7, - 43.7, - 43.5, - 43.3, - 43.3, - 43.3, - 43.2, - 43.1, - 42.9, - 42.7, - 42.5, - 42.2, - 42, - 41.9, - 41.8, - 41.6, - 41.4, - 41.3, - 41.2, - 41.2, - 41.1, - 41, - 40.8, - 40.6, - 40.4, - 40.3, - 40.3, - 40.2, - 40, - 39.8, - 39.6, - 39.5, - 39.4, - 39.2, - 39, - 39.1, - 39.3, - 39.4, - 39.2, - 38.9, - 38.9, - 38.9, - 38.9, - 38.8, - 38.7, - 38.6, - 38.4, - 38.3, - 38.1, - 37.9, - 37.8, - 37.7, - 37.6, - 37.5, - 37.6, - 37.7, - 37.9, - 37.8, - 37.8, - 37.8, - 37.9, - 37.9, - 38, - 38, - 37.9, - 37.9, - 37.8, - 37.7, - 37.7, - 37.7, - 37.6, - 37.4, - 37.3, - 37.2, - 37.1, - 36.8, - 36.7, - 36.5, - 36.3, - 36.2, - 36, - 35.9, - 35.8, - 35.7, - 35.6, - 35.4, - 35.4, - 35.3, - 35.2, - 35, - 34.7, - 34.5, - 34.3, - 34, - 33.7, - 33.4, - 33.1, - 32.9, - 32.7, - 32.6, - 32.5, - 32.4, - 32.3, - 32.1, - 31.8, - 31.5, - 31.4, - 31.2, - 31, - 30.8, - 30.7, - 30.5, - 30.4, - 30.3, - 30.1, - 30, - 30, - 30, - 30.1, - 30, - 29.8, - 29.7, - 29.6, - 29.4, - 29.2, - 29.1, - 29, - 29, - 28.9, - 28.7, - 28.6, - 28.5, - 28.2, - 28, - 27.3, - 26.7, - 38.8, - 39.2, - 39.6, - 40, - 40.3, - 40.6, - 40.9, - 41, - 40.9, - 40.9, - 40.9, - 41, - 41.1, - 41.2, - 41.3, - 41.6, - 41.9, - 42.1, - 42.3, - 42.5, - 42.8, - 43, - 43.4, - 43.5, - 43.6, - 43.9, - 44, - 44.2, - 44.4, - 44.6, - 44.8, - 44.8, - 44.8, - 44.8, - 44.9, - 45, - 45.1, - 45.1, - 45.2, - 45.3, - 45.3, - 45.4, - 45.4, - 45.5, - 45.6, - 45.6, - 45.7, - 45.6, - 45.6, - 45.7, - 45.7, - 45.7, - 45.7, - 45.8, - 45.8, - 45.9, - 46, - 46.2, - 46.2, - 46.3, - 46.3, - 46.3, - 46.4, - 46.4, - 46.5, - 46.6, - 46.6, - 46.7, - 46.6, - 46.6, - 46.6, - 46.6, - 46.4, - 46.4, - 46.3, - 46.3, - 46.2, - 46.2, - 46.2, - 46.2, - 46.1, - 46.1, - 46, - 45.9, - 45.8, - 45.7, - 45.6, - 45.5, - 45.3, - 45.2, - 45.2, - 45.2, - 45, - 44.9, - 44.7, - 44.6, - 44.5, - 44.5, - 44.5, - 44.5, - 44.5, - 44.4, - 44.3, - 44.2, - 44.1, - 44.1, - 44, - 43.9, - 43.8, - 43.8, - 43.9, - 44, - 44, - 44, - 44.1, - 44.1, - 44.1, - 44, - 44, - 44, - 44, - 43.9, - 43.9, - 43.8, - 43.8, - 43.7, - 43.6, - 43.5, - 43.4, - 43.2, - 43, - 42.8, - 42.6, - 42.4, - 42.2, - 42.1, - 42, - 41.8, - 41.7, - 41.6, - 41.6, - 41.5, - 41.2, - 41, - 40.9, - 40.8, - 40.7, - 40.5, - 40.3, - 40.1, - 39.9, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.5, - 39.6, - 39.6, - 39.7, - 39.5, - 39.2, - 39.2, - 39.2, - 39.1, - 39, - 38.9, - 38.7, - 38.6, - 38.4, - 38.3, - 38.2, - 38.2, - 38.1, - 38.1, - 38.1, - 38.2, - 38.3, - 38.3, - 38.3, - 38.3, - 38.3, - 38.3, - 38.4, - 38.3, - 38.3, - 38.3, - 38.3, - 38.2, - 38.1, - 38, - 37.9, - 37.7, - 37.5, - 37.4, - 37.3, - 37.2, - 37, - 36.8, - 36.6, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.8, - 35.7, - 35.6, - 35.5, - 35.4, - 35.2, - 35, - 34.7, - 34.5, - 34.2, - 34, - 33.8, - 33.5, - 33.1, - 32.8, - 32.6, - 32.5, - 32.5, - 32.4, - 32.4, - 32.2, - 31.9, - 31.7, - 31.6, - 31.4, - 31.2, - 31, - 30.8, - 30.7, - 30.6, - 30.4, - 30.2, - 30.2, - 30.3, - 30.3, - 30.2, - 30.1, - 29.9, - 29.8, - 29.7, - 29.5, - 29.3, - 29.2, - 29.1, - 29, - 28.9, - 28.9, - 28.8, - 28.6, - 28.4, - 27.9, - 27, - 38.6, - 39, - 39.3, - 39.6, - 40, - 40.4, - 40.7, - 40.8, - 40.7, - 40.6, - 40.6, - 40.6, - 40.7, - 40.8, - 41.1, - 41.5, - 41.7, - 41.9, - 42.1, - 42.3, - 42.4, - 42.7, - 42.9, - 43.2, - 43.3, - 43.6, - 43.8, - 44, - 44.2, - 44.4, - 44.5, - 44.6, - 44.6, - 44.7, - 44.8, - 44.9, - 45, - 45.1, - 45.2, - 45.3, - 45.4, - 45.4, - 45.5, - 45.6, - 45.6, - 45.7, - 45.7, - 45.9, - 45.9, - 45.8, - 45.8, - 45.8, - 45.8, - 45.8, - 46, - 46, - 46.1, - 46.2, - 46.3, - 46.5, - 46.5, - 46.6, - 46.7, - 46.7, - 46.7, - 46.8, - 46.9, - 46.9, - 46.9, - 47, - 47, - 46.9, - 46.8, - 46.7, - 46.6, - 46.6, - 46.6, - 46.5, - 46.5, - 46.4, - 46.3, - 46.2, - 46.2, - 46.1, - 46, - 45.9, - 45.8, - 45.7, - 45.5, - 45.3, - 45.2, - 45.2, - 45.2, - 45.1, - 45, - 45, - 44.9, - 44.8, - 44.8, - 44.7, - 44.7, - 44.7, - 44.7, - 44.6, - 44.5, - 44.4, - 44.2, - 44.3, - 44.4, - 44.2, - 44.2, - 44.2, - 44.3, - 44.4, - 44.5, - 44.5, - 44.4, - 44.4, - 44.4, - 44.4, - 44.3, - 44.3, - 44.3, - 44.3, - 44.3, - 44.2, - 44.1, - 43.9, - 43.7, - 43.5, - 43.2, - 43, - 43, - 42.8, - 42.6, - 42.4, - 42.3, - 42.1, - 42, - 41.9, - 41.8, - 41.9, - 41.7, - 41.4, - 41.2, - 40.9, - 40.8, - 40.6, - 40.4, - 40.3, - 40.1, - 40.2, - 40.2, - 40.1, - 40, - 39.9, - 39.9, - 39.9, - 39.9, - 39.9, - 39.8, - 39.6, - 39.5, - 39.4, - 39.4, - 39.3, - 39.2, - 39, - 38.9, - 38.8, - 38.7, - 38.7, - 38.5, - 38.5, - 38.7, - 38.6, - 38.7, - 38.8, - 38.8, - 38.9, - 38.8, - 38.8, - 38.7, - 38.7, - 38.8, - 38.7, - 38.6, - 38.6, - 38.7, - 38.4, - 38.4, - 38.2, - 38, - 37.8, - 37.7, - 37.6, - 37.4, - 37.3, - 37.1, - 37, - 36.8, - 36.6, - 36.5, - 36.4, - 36.2, - 36.1, - 35.9, - 35.8, - 35.7, - 35.6, - 35.4, - 35.2, - 34.9, - 34.7, - 34.5, - 34.3, - 34, - 33.8, - 33.5, - 33.2, - 32.8, - 32.6, - 32.5, - 32.4, - 32.3, - 32.3, - 32.1, - 32, - 31.8, - 31.7, - 31.5, - 31.3, - 31.1, - 30.9, - 30.9, - 30.8, - 30.4, - 30.5, - 30.5, - 30.4, - 30.5, - 30.4, - 30.3, - 30.2, - 30, - 29.9, - 29.8, - 29.7, - 29.5, - 29.3, - 29.2, - 29, - 28.9, - 28.8, - 28.6, - 28, - 27.4, - 38.4, - 38.6, - 38.9, - 39, - 39.4, - 39.8, - 40.2, - 40.2, - 40.1, - 40.1, - 40.2, - 40.3, - 40.3, - 40.5, - 40.7, - 41, - 41.2, - 41.5, - 41.6, - 41.7, - 42, - 42.3, - 42.6, - 42.9, - 43.1, - 43.3, - 43.5, - 43.7, - 44, - 44.1, - 44.3, - 44.4, - 44.5, - 44.7, - 44.8, - 44.9, - 45, - 45.1, - 45.2, - 45.3, - 45.5, - 45.5, - 45.6, - 45.6, - 45.7, - 45.7, - 45.7, - 45.9, - 45.9, - 45.9, - 45.9, - 46, - 46, - 46.1, - 46.3, - 46.2, - 46.3, - 46.4, - 46.4, - 46.6, - 46.8, - 46.9, - 46.9, - 46.9, - 47, - 47.2, - 47.2, - 47.3, - 47.2, - 47.2, - 47.5, - 47.4, - 47.2, - 47.2, - 47.1, - 47, - 46.9, - 46.9, - 46.7, - 46.5, - 46.5, - 46.4, - 46.3, - 46.2, - 46.2, - 46.1, - 46.1, - 46, - 45.9, - 45.6, - 45.5, - 45.5, - 45.5, - 45.4, - 45.5, - 45.5, - 45.3, - 45.3, - 45.2, - 45.1, - 45.1, - 45.1, - 45.1, - 45.1, - 44.9, - 44.8, - 44.7, - 44.7, - 44.8, - 44.8, - 44.7, - 44.6, - 44.7, - 44.8, - 44.8, - 44.9, - 44.8, - 44.8, - 44.9, - 44.7, - 44.6, - 44.6, - 44.6, - 44.7, - 44.6, - 44.4, - 44.3, - 44.1, - 43.9, - 43.6, - 43.5, - 43.4, - 43.3, - 43.1, - 42.9, - 42.7, - 42.5, - 42.4, - 42.2, - 42.2, - 42, - 41.9, - 41.8, - 41.6, - 41.3, - 41.1, - 40.8, - 40.7, - 40.7, - 40.7, - 40.5, - 40.5, - 40.6, - 40.6, - 40.6, - 40.5, - 40.5, - 40.4, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.6, - 39.6, - 39.5, - 39.3, - 39.2, - 39.1, - 39.1, - 39, - 38.9, - 39, - 39.1, - 39.1, - 39.2, - 39.2, - 39.3, - 39.4, - 39.2, - 39.2, - 39.2, - 39.1, - 39.1, - 39, - 38.9, - 38.9, - 38.9, - 38.8, - 38.7, - 38.4, - 38.3, - 38.1, - 37.9, - 37.9, - 37.7, - 37.5, - 37.4, - 37.3, - 37.2, - 37, - 36.8, - 36.7, - 36.6, - 36.5, - 36.3, - 36.1, - 36, - 35.8, - 35.6, - 35.4, - 35.1, - 34.9, - 34.7, - 34.5, - 34.3, - 34.1, - 33.8, - 33.5, - 33.2, - 32.9, - 32.7, - 32.5, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 31.7, - 31.5, - 31.3, - 31.3, - 31.1, - 30.8, - 30.6, - 30.7, - 30.7, - 30.7, - 30.7, - 30.6, - 30.5, - 30.4, - 30.3, - 30.2, - 30.2, - 30.2, - 30.1, - 30.1, - 29.9, - 29.6, - 29.3, - 29.2, - 29, - 28.4, - 27.7, - 38.1, - 38.4, - 38.5, - 38.5, - 38.8, - 39.4, - 39.5, - 39.5, - 39.5, - 39.6, - 39.8, - 39.9, - 40, - 40.2, - 40.4, - 40.5, - 40.7, - 40.9, - 41.1, - 41.5, - 41.6, - 42, - 42.4, - 42.6, - 42.8, - 43, - 43.4, - 43.7, - 43.8, - 43.9, - 44.1, - 44.3, - 44.4, - 44.6, - 44.8, - 44.9, - 45, - 45.1, - 45.3, - 45.4, - 45.5, - 45.5, - 45.6, - 45.6, - 45.6, - 45.7, - 45.8, - 45.9, - 46, - 46, - 46.1, - 46.2, - 46.2, - 46.3, - 46.4, - 46.4, - 46.5, - 46.6, - 46.7, - 46.9, - 47, - 47.1, - 47.1, - 47.2, - 47.4, - 47.5, - 47.6, - 47.6, - 47.6, - 47.6, - 47.6, - 47.6, - 47.6, - 47.6, - 47.5, - 47.4, - 47.2, - 47.1, - 46.9, - 46.8, - 46.7, - 46.6, - 46.4, - 46.4, - 46.5, - 46.5, - 46.4, - 46.3, - 46.3, - 46.1, - 46, - 45.9, - 46, - 45.9, - 45.8, - 45.7, - 45.6, - 45.6, - 45.5, - 45.5, - 45.6, - 45.6, - 45.6, - 45.6, - 45.6, - 45.6, - 45.3, - 45.1, - 45.1, - 45.1, - 45.1, - 45, - 45, - 45, - 45.1, - 45.1, - 45.1, - 45.1, - 45, - 45, - 44.9, - 44.9, - 44.9, - 44.9, - 44.8, - 44.7, - 44.5, - 44.3, - 44.2, - 44, - 43.8, - 43.7, - 43.5, - 43.4, - 43.2, - 43, - 42.8, - 42.6, - 42.5, - 42.5, - 42.4, - 42.1, - 41.9, - 41.8, - 41.5, - 41.3, - 41, - 40.9, - 41, - 41.2, - 41.2, - 41, - 40.9, - 40.9, - 40.9, - 40.9, - 40.8, - 40.8, - 40.7, - 40.6, - 40.5, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.6, - 39.4, - 39.4, - 39.4, - 39.5, - 39.6, - 39.6, - 39.6, - 39.6, - 39.7, - 39.7, - 39.6, - 39.5, - 39.5, - 39.4, - 39.4, - 39.3, - 39.1, - 39.1, - 39.2, - 39, - 38.8, - 38.6, - 38.5, - 38.4, - 38.2, - 38.2, - 38.1, - 37.9, - 37.8, - 37.7, - 37.6, - 37.4, - 37.2, - 37.1, - 37, - 36.8, - 36.7, - 36.5, - 36.3, - 36.1, - 35.9, - 35.7, - 35.4, - 35.2, - 35, - 34.8, - 34.5, - 34.3, - 34.1, - 33.8, - 33.6, - 33.3, - 33, - 32.7, - 32.5, - 32.3, - 32.2, - 32.1, - 32, - 31.9, - 31.8, - 31.6, - 31.5, - 31.4, - 31.1, - 31, - 31.1, - 31.1, - 31, - 30.9, - 30.8, - 30.9, - 30.7, - 30.5, - 30.4, - 30.3, - 30.3, - 30.4, - 30.4, - 30.4, - 30.3, - 30, - 29.7, - 29.6, - 29.3, - 28.7, - 27.9, - 37.9, - 38.2, - 38.2, - 38.3, - 38.5, - 38.7, - 38.9, - 39, - 38.9, - 39.1, - 39.4, - 39.6, - 39.8, - 39.9, - 40.1, - 40.2, - 40.5, - 40.7, - 40.9, - 41.3, - 41.5, - 41.8, - 42.1, - 42.4, - 42.6, - 42.9, - 43.3, - 43.6, - 43.7, - 43.9, - 44.1, - 44.2, - 44.4, - 44.6, - 44.8, - 44.9, - 45, - 45.2, - 45.3, - 45.4, - 45.5, - 45.6, - 45.7, - 45.7, - 45.8, - 45.9, - 46, - 46.1, - 46.2, - 46.2, - 46.3, - 46.4, - 46.4, - 46.6, - 46.6, - 46.6, - 46.7, - 46.8, - 46.9, - 47, - 47.2, - 47.4, - 47.3, - 47.5, - 47.7, - 47.9, - 48, - 48, - 48, - 48, - 47.8, - 47.8, - 47.8, - 47.7, - 47.6, - 47.7, - 47.6, - 47.4, - 47.3, - 47.1, - 47.1, - 47, - 46.9, - 46.8, - 46.8, - 46.8, - 46.7, - 46.6, - 46.5, - 46.4, - 46.3, - 46.4, - 46.3, - 46.2, - 46.1, - 46, - 45.9, - 45.9, - 45.8, - 45.8, - 45.8, - 45.9, - 45.9, - 45.9, - 45.9, - 45.9, - 45.9, - 45.7, - 45.5, - 45.4, - 45.4, - 45.3, - 45.2, - 45.3, - 45.3, - 45.4, - 45.5, - 45.5, - 45.5, - 45.3, - 45.2, - 45.2, - 45.1, - 45, - 45, - 44.9, - 44.8, - 44.6, - 44.4, - 44.3, - 44.1, - 44, - 43.8, - 43.7, - 43.6, - 43.4, - 43.2, - 43, - 42.8, - 42.8, - 42.7, - 42.5, - 42.3, - 42.1, - 41.7, - 41.4, - 41.3, - 41.3, - 41.4, - 41.5, - 41.5, - 41.5, - 41.3, - 41.2, - 41.1, - 41, - 41, - 41, - 41, - 40.9, - 40.9, - 40.8, - 40.5, - 40.4, - 40.3, - 40.2, - 40.2, - 40.1, - 40, - 39.9, - 39.9, - 40, - 40.1, - 40, - 40.1, - 40.2, - 40.2, - 40.1, - 40, - 40, - 39.9, - 39.8, - 39.8, - 39.8, - 39.7, - 39.6, - 39.4, - 39.3, - 39.3, - 39.2, - 39.1, - 38.9, - 38.8, - 38.7, - 38.5, - 38.4, - 38.3, - 38.3, - 38.2, - 38.1, - 37.9, - 37.8, - 37.7, - 37.5, - 37.3, - 37.2, - 37, - 36.8, - 36.7, - 36.5, - 36.2, - 36, - 35.8, - 35.6, - 35.3, - 35.1, - 34.8, - 34.6, - 34.3, - 34.1, - 33.8, - 33.7, - 33.4, - 33.1, - 32.9, - 32.6, - 32.4, - 32.3, - 32.2, - 32.1, - 31.9, - 31.8, - 31.6, - 31.3, - 31.2, - 31.5, - 31.5, - 31.5, - 31.5, - 31.3, - 31.1, - 31, - 31, - 30.8, - 30.6, - 30.5, - 30.4, - 30.3, - 30.4, - 30.5, - 30.4, - 30.1, - 29.8, - 29.7, - 29.4, - 28.9, - 28.2, - 37.6, - 37.8, - 38.1, - 38.2, - 38.3, - 38.4, - 38.6, - 38.8, - 38.8, - 38.9, - 39.2, - 39.3, - 39.5, - 39.7, - 39.8, - 39.9, - 40.1, - 40.4, - 40.7, - 41.1, - 41.3, - 41.6, - 41.9, - 42.2, - 42.6, - 43, - 43.3, - 43.5, - 43.6, - 43.9, - 44.1, - 44.2, - 44.4, - 44.6, - 44.7, - 44.9, - 45.1, - 45.3, - 45.4, - 45.5, - 45.5, - 45.6, - 45.7, - 45.8, - 45.9, - 46.1, - 46.2, - 46.4, - 46.5, - 46.6, - 46.8, - 46.8, - 46.9, - 46.9, - 46.9, - 46.9, - 47, - 47.1, - 47.2, - 47.3, - 47.4, - 47.5, - 47.6, - 47.7, - 48, - 48.1, - 48.2, - 48.1, - 48.3, - 48.3, - 48.1, - 48, - 48, - 47.9, - 47.8, - 47.8, - 47.8, - 47.7, - 47.6, - 47.4, - 47.5, - 47.5, - 47.4, - 47.2, - 47, - 47, - 46.9, - 46.8, - 46.8, - 46.7, - 46.6, - 46.6, - 46.6, - 46.6, - 46.4, - 46.4, - 46.3, - 46.3, - 46.2, - 46.2, - 46.1, - 46, - 46.1, - 46.1, - 46.2, - 46.1, - 46.1, - 46.1, - 45.9, - 45.8, - 45.7, - 45.7, - 45.6, - 45.5, - 45.6, - 45.7, - 45.7, - 45.7, - 45.7, - 45.6, - 45.5, - 45.5, - 45.3, - 45.3, - 45.2, - 45.1, - 45, - 45, - 44.7, - 44.7, - 44.5, - 44.3, - 44.1, - 43.9, - 43.8, - 43.6, - 43.6, - 43.5, - 43.4, - 43.2, - 43, - 42.9, - 42.9, - 42.6, - 42, - 41.6, - 41.7, - 41.8, - 41.7, - 41.7, - 41.7, - 41.7, - 41.7, - 41.6, - 41.5, - 41.4, - 41.4, - 41.3, - 41.3, - 41.3, - 41.2, - 41.1, - 40.9, - 40.8, - 40.6, - 40.5, - 40.5, - 40.5, - 40.3, - 40.3, - 40.4, - 40.5, - 40.6, - 40.6, - 40.7, - 40.8, - 40.7, - 40.6, - 40.6, - 40.5, - 40.3, - 40.1, - 40.1, - 40.1, - 40, - 40, - 40, - 39.7, - 39.6, - 39.4, - 39.3, - 39.2, - 39, - 39, - 38.9, - 38.8, - 38.7, - 38.7, - 38.5, - 38.4, - 38.3, - 38.1, - 38, - 37.8, - 37.6, - 37.4, - 37.2, - 37.1, - 36.9, - 36.7, - 36.6, - 36.3, - 36.1, - 35.9, - 35.6, - 35.4, - 35.1, - 34.8, - 34.5, - 34.3, - 34.1, - 33.9, - 33.7, - 33.5, - 33.2, - 33, - 32.8, - 32.6, - 32.6, - 32.4, - 32.2, - 32.1, - 31.7, - 31.3, - 31.5, - 31.8, - 31.9, - 31.8, - 31.8, - 31.7, - 31.5, - 31.4, - 31.2, - 31.1, - 30.9, - 30.7, - 30.6, - 30.4, - 30.3, - 30.3, - 30.3, - 30.3, - 30.1, - 29.8, - 29.5, - 29.1, - 28.6, - 37.3, - 37.5, - 37.8, - 37.9, - 38.1, - 38.4, - 38.7, - 38.8, - 38.9, - 38.9, - 39, - 39.2, - 39.3, - 39.5, - 39.6, - 39.7, - 39.8, - 40, - 40.4, - 40.9, - 41.3, - 41.6, - 42, - 42.3, - 42.5, - 43, - 43.2, - 43.4, - 43.6, - 43.8, - 44, - 44.2, - 44.3, - 44.6, - 44.8, - 45, - 45.2, - 45.3, - 45.4, - 45.5, - 45.6, - 45.8, - 45.9, - 46.1, - 46.2, - 46.3, - 46.4, - 46.7, - 46.9, - 47.1, - 47.2, - 47.4, - 47.4, - 47.3, - 47.3, - 47.3, - 47.4, - 47.4, - 47.5, - 47.6, - 47.7, - 47.8, - 47.9, - 48.1, - 48.2, - 48.3, - 48.3, - 48.4, - 48.4, - 48.4, - 48.3, - 48.1, - 48.1, - 48.1, - 48.1, - 48, - 48, - 47.9, - 47.8, - 47.8, - 47.8, - 47.8, - 47.6, - 47.5, - 47.4, - 47.4, - 47.4, - 47.3, - 47.2, - 47.2, - 47.1, - 47.1, - 47.1, - 47.1, - 47, - 46.9, - 46.9, - 46.8, - 46.7, - 46.6, - 46.4, - 46.2, - 46.2, - 46.2, - 46.2, - 46.2, - 46.3, - 46.3, - 46.2, - 46.1, - 46, - 46, - 46, - 45.9, - 45.9, - 45.9, - 46, - 46, - 46, - 46, - 45.9, - 45.7, - 45.6, - 45.5, - 45.4, - 45.4, - 45.2, - 45.1, - 45.1, - 45, - 44.7, - 44.6, - 44.5, - 44.3, - 44, - 43.9, - 43.8, - 43.8, - 43.7, - 43.5, - 43.3, - 43.2, - 43, - 42.7, - 42.4, - 42, - 42.1, - 42.3, - 42.4, - 42.2, - 41.9, - 41.9, - 42, - 42, - 41.8, - 41.7, - 41.6, - 41.6, - 41.6, - 41.5, - 41.5, - 41.4, - 41.2, - 41.1, - 41, - 40.9, - 41, - 40.9, - 40.8, - 40.9, - 41, - 41.1, - 41.2, - 41.2, - 41.3, - 41.3, - 41.2, - 41.1, - 41.1, - 40.9, - 40.7, - 40.4, - 40.4, - 40.4, - 40.4, - 40.3, - 40.3, - 39.9, - 39.8, - 39.7, - 39.6, - 39.5, - 39.4, - 39.4, - 39.3, - 39.2, - 39.1, - 39.1, - 39, - 38.8, - 38.6, - 38.5, - 38.3, - 38.1, - 37.9, - 37.7, - 37.5, - 37.3, - 37.1, - 37, - 36.8, - 36.6, - 36.3, - 36.1, - 35.9, - 35.6, - 35.3, - 35, - 34.8, - 34.5, - 34.3, - 34.1, - 33.9, - 33.8, - 33.6, - 33.3, - 33.2, - 33.1, - 33, - 32.8, - 32.5, - 32.2, - 31.8, - 31.6, - 31.8, - 31.9, - 32.1, - 32.1, - 32.1, - 32, - 31.9, - 31.8, - 31.7, - 31.6, - 31.5, - 31.2, - 31, - 31, - 30.8, - 30.6, - 30.4, - 30.3, - 30.1, - 30, - 29.7, - 29.3, - 28.8, - 37.2, - 37.4, - 37.5, - 37.6, - 37.9, - 38.3, - 38.7, - 38.9, - 39, - 39.1, - 39, - 39.1, - 39.2, - 39.3, - 39.5, - 39.6, - 39.7, - 40, - 40.2, - 40.6, - 41.2, - 41.7, - 42, - 42.3, - 42.5, - 42.8, - 43.1, - 43.3, - 43.6, - 43.9, - 44.1, - 44.2, - 44.4, - 44.6, - 44.8, - 45.1, - 45.3, - 45.4, - 45.6, - 45.8, - 45.8, - 45.8, - 46, - 46.1, - 46.3, - 46.4, - 46.6, - 46.8, - 47, - 47.3, - 47.6, - 47.6, - 47.7, - 47.8, - 47.8, - 47.9, - 47.9, - 47.8, - 47.8, - 47.9, - 48, - 48.1, - 48.2, - 48.5, - 48.6, - 48.5, - 48.5, - 48.5, - 48.6, - 48.6, - 48.5, - 48.4, - 48.4, - 48.4, - 48.3, - 48.3, - 48.2, - 48.2, - 48.1, - 48.1, - 48.1, - 48, - 47.9, - 47.9, - 47.9, - 47.9, - 47.9, - 47.8, - 47.8, - 47.7, - 47.7, - 47.7, - 47.6, - 47.6, - 47.7, - 47.5, - 47.3, - 47.2, - 47, - 46.9, - 46.7, - 46.6, - 46.5, - 46.4, - 46.3, - 46.4, - 46.4, - 46.4, - 46.4, - 46.4, - 46.3, - 46.3, - 46.3, - 46.2, - 46.2, - 46.2, - 46.3, - 46.4, - 46.3, - 46.3, - 46.1, - 46, - 46.1, - 45.9, - 45.8, - 45.7, - 45.6, - 45.4, - 45.2, - 45.2, - 45, - 44.8, - 44.7, - 44.5, - 44.3, - 44.1, - 44, - 43.9, - 43.8, - 43.8, - 43.7, - 43.4, - 43.2, - 43, - 42.7, - 42.4, - 42.6, - 42.9, - 43, - 42.8, - 42.5, - 42.3, - 42.4, - 42.4, - 42.3, - 42.2, - 42.1, - 41.9, - 41.9, - 41.8, - 41.8, - 41.7, - 41.6, - 41.6, - 41.6, - 41.5, - 41.4, - 41.4, - 41.3, - 41.4, - 41.5, - 41.6, - 41.6, - 41.6, - 41.6, - 41.6, - 41.5, - 41.4, - 41.4, - 41.1, - 40.9, - 40.7, - 40.7, - 40.7, - 40.7, - 40.6, - 40.5, - 40.2, - 40.1, - 40.1, - 40, - 39.9, - 39.8, - 39.7, - 39.6, - 39.6, - 39.5, - 39.4, - 39.2, - 39.1, - 38.9, - 38.8, - 38.6, - 38.4, - 38.3, - 38, - 37.8, - 37.6, - 37.4, - 37.2, - 37, - 36.8, - 36.6, - 36.4, - 36.1, - 35.9, - 35.7, - 35.4, - 35.1, - 34.8, - 34.6, - 34.4, - 34.2, - 34, - 33.8, - 33.7, - 33.6, - 33.5, - 33.4, - 33.2, - 32.7, - 32.3, - 32.2, - 31.9, - 32, - 32.3, - 32.4, - 32.3, - 32.2, - 32.2, - 32.1, - 32, - 32, - 31.9, - 31.8, - 31.7, - 31.3, - 31.2, - 31.1, - 30.9, - 30.7, - 30.5, - 30.2, - 30, - 29.8, - 29.5, - 29.2, - 37.3, - 37.4, - 37.6, - 37.8, - 38.1, - 38.5, - 38.9, - 39, - 39.2, - 39.1, - 39.1, - 39.1, - 39.2, - 39.2, - 39.4, - 39.5, - 39.7, - 39.9, - 40.2, - 40.5, - 41.1, - 41.6, - 41.7, - 42, - 42.3, - 42.6, - 42.9, - 43.2, - 43.6, - 43.9, - 44.1, - 44.3, - 44.5, - 44.7, - 45, - 45.3, - 45.4, - 45.6, - 45.7, - 45.8, - 45.9, - 46, - 46.2, - 46.3, - 46.5, - 46.6, - 46.9, - 47.1, - 47.3, - 47.6, - 47.8, - 47.9, - 48, - 48.2, - 48.3, - 48.4, - 48.5, - 48.4, - 48.3, - 48.3, - 48.4, - 48.4, - 48.5, - 48.6, - 48.7, - 48.7, - 48.7, - 48.8, - 48.7, - 48.8, - 48.6, - 48.6, - 48.7, - 48.7, - 48.6, - 48.5, - 48.5, - 48.4, - 48.3, - 48.3, - 48.5, - 48.4, - 48.3, - 48.4, - 48.4, - 48.3, - 48.3, - 48.4, - 48.4, - 48.4, - 48.3, - 48.3, - 48.3, - 48.2, - 48.2, - 48.1, - 47.8, - 47.5, - 47.4, - 47.2, - 47, - 46.9, - 46.8, - 46.8, - 46.7, - 46.6, - 46.6, - 46.7, - 46.7, - 46.6, - 46.6, - 46.6, - 46.6, - 46.5, - 46.4, - 46.5, - 46.6, - 46.6, - 46.6, - 46.6, - 46.4, - 46.4, - 46.4, - 46.3, - 46.1, - 46.1, - 46, - 45.7, - 45.5, - 45.3, - 45.2, - 45.1, - 45, - 44.8, - 44.7, - 44.5, - 44.4, - 44.2, - 44.1, - 44, - 43.9, - 43.7, - 43.5, - 43.4, - 43.1, - 42.8, - 42.9, - 43.1, - 43.3, - 43.3, - 43.2, - 43, - 42.8, - 42.8, - 42.7, - 42.6, - 42.5, - 42.3, - 42.2, - 42.2, - 42.1, - 42.1, - 42, - 42, - 42, - 42, - 41.9, - 41.8, - 41.8, - 41.9, - 42, - 42, - 41.9, - 42, - 42, - 41.9, - 41.8, - 41.7, - 41.6, - 41.4, - 41.1, - 41, - 40.9, - 40.9, - 40.9, - 40.8, - 40.7, - 40.5, - 40.5, - 40.5, - 40.4, - 40.3, - 40.2, - 40.1, - 40, - 39.9, - 39.7, - 39.6, - 39.5, - 39.4, - 39.2, - 39, - 38.9, - 38.7, - 38.5, - 38.3, - 38.1, - 37.9, - 37.7, - 37.5, - 37.2, - 37, - 36.8, - 36.6, - 36.4, - 36.2, - 35.9, - 35.6, - 35.3, - 35.1, - 34.8, - 34.6, - 34.4, - 34.2, - 34.1, - 34, - 34, - 33.9, - 33.7, - 33.6, - 33, - 32.7, - 32.6, - 32.4, - 32.3, - 32.6, - 32.7, - 32.7, - 32.6, - 32.6, - 32.6, - 32.4, - 32.1, - 32, - 32, - 31.9, - 31.5, - 31.3, - 31.2, - 31, - 30.9, - 30.7, - 30.5, - 30.3, - 29.9, - 29.6, - 29.4, - 37.3, - 37.4, - 37.6, - 37.9, - 38.4, - 38.7, - 38.9, - 39.1, - 39.2, - 39.2, - 39.2, - 39.2, - 39.2, - 39.2, - 39.3, - 39.5, - 39.6, - 39.9, - 40.2, - 40.6, - 41.2, - 41.5, - 41.7, - 41.9, - 42.1, - 42.4, - 42.7, - 43.1, - 43.7, - 44, - 44.1, - 44.4, - 44.6, - 44.9, - 45.2, - 45.5, - 45.6, - 45.7, - 45.8, - 45.9, - 46.1, - 46.3, - 46.4, - 46.7, - 46.9, - 47.1, - 47.3, - 47.6, - 47.7, - 47.8, - 48.2, - 48.3, - 48.3, - 48.5, - 48.6, - 48.8, - 49, - 48.9, - 48.8, - 48.9, - 48.9, - 48.8, - 48.8, - 48.9, - 48.9, - 48.9, - 49, - 49, - 48.9, - 48.9, - 48.9, - 48.9, - 48.9, - 48.9, - 48.9, - 48.8, - 48.8, - 48.8, - 48.8, - 48.8, - 48.8, - 48.8, - 48.8, - 49, - 48.9, - 48.8, - 48.8, - 48.8, - 48.9, - 49, - 48.9, - 48.9, - 49.1, - 48.8, - 48.7, - 48.5, - 48.3, - 48, - 47.8, - 47.6, - 47.5, - 47.3, - 47.2, - 47.1, - 47, - 47, - 47, - 46.9, - 46.9, - 46.9, - 46.9, - 46.9, - 47, - 46.9, - 46.9, - 46.8, - 46.8, - 46.8, - 46.8, - 46.8, - 46.7, - 46.5, - 46.5, - 46.4, - 46.4, - 46.4, - 46.3, - 46.2, - 45.9, - 45.7, - 45.6, - 45.5, - 45.4, - 45.2, - 45, - 44.9, - 44.8, - 44.6, - 44.4, - 44.3, - 44.1, - 44, - 43.9, - 43.7, - 43.4, - 43.1, - 43.2, - 43.3, - 43.4, - 43.4, - 43.6, - 43.6, - 43.3, - 43.2, - 43.1, - 43, - 42.8, - 42.7, - 42.6, - 42.6, - 42.5, - 42.4, - 42.3, - 42.2, - 42.3, - 42.4, - 42.4, - 42.4, - 42.4, - 42.4, - 42.4, - 42.4, - 42.3, - 42.3, - 42.3, - 42.3, - 42.2, - 42.1, - 41.9, - 41.7, - 41.4, - 41.3, - 41.1, - 41.1, - 41.1, - 41.1, - 41.1, - 41, - 40.9, - 40.8, - 40.8, - 40.7, - 40.6, - 40.6, - 40.5, - 40.4, - 40.2, - 40, - 39.8, - 39.7, - 39.5, - 39.4, - 39.2, - 39.1, - 38.9, - 38.7, - 38.5, - 38.2, - 38, - 37.7, - 37.5, - 37.3, - 37.1, - 36.9, - 36.7, - 36.4, - 36.1, - 35.8, - 35.6, - 35.4, - 35.1, - 34.9, - 34.7, - 34.5, - 34.4, - 34.3, - 34.2, - 34.2, - 34, - 33.9, - 33.6, - 33.2, - 33.1, - 32.9, - 32.8, - 33.1, - 33.3, - 33.3, - 33.1, - 33, - 33, - 32.8, - 32.5, - 32.3, - 32.2, - 32.3, - 32, - 31.8, - 31.6, - 31.4, - 31.2, - 31, - 30.7, - 30.6, - 30.2, - 29.9, - 29.7, - 37.2, - 37.5, - 37.7, - 37.9, - 38.3, - 38.6, - 38.9, - 39.1, - 39.2, - 39.2, - 39.2, - 39.2, - 39.2, - 39.3, - 39.4, - 39.5, - 39.7, - 40, - 40.3, - 40.7, - 41.2, - 41.5, - 41.6, - 41.8, - 42.1, - 42.4, - 42.7, - 43.1, - 43.7, - 44, - 44.2, - 44.5, - 44.7, - 45, - 45.4, - 45.6, - 45.8, - 45.9, - 46, - 46.1, - 46.4, - 46.6, - 46.8, - 47.1, - 47.3, - 47.4, - 47.7, - 47.9, - 48, - 48.2, - 48.4, - 48.6, - 48.8, - 49, - 49.1, - 49.1, - 49.3, - 49.4, - 49.4, - 49.3, - 49.3, - 49.1, - 49.1, - 49.1, - 49.2, - 49.1, - 49.2, - 49.2, - 49.1, - 49.1, - 49.1, - 49.3, - 49.2, - 49.1, - 49.3, - 49.3, - 49.2, - 49.3, - 49.4, - 49.4, - 49.3, - 49.3, - 49.4, - 49.5, - 49.3, - 49.3, - 49.3, - 49.3, - 49.3, - 49.3, - 49.4, - 49.3, - 49.4, - 49.3, - 49, - 48.7, - 48.6, - 48.5, - 48.4, - 48.2, - 48, - 47.8, - 47.7, - 47.6, - 47.4, - 47.4, - 47.4, - 47.3, - 47.3, - 47.3, - 47.3, - 47.3, - 47.2, - 47.2, - 47.2, - 47.2, - 47.1, - 47.1, - 47, - 47.1, - 47, - 46.8, - 46.8, - 46.7, - 46.7, - 46.6, - 46.6, - 46.5, - 46.3, - 46.1, - 46, - 45.9, - 45.7, - 45.5, - 45.3, - 45.2, - 45.1, - 44.9, - 44.8, - 44.6, - 44.5, - 44.3, - 44.2, - 44, - 43.7, - 43.4, - 43.4, - 43.5, - 43.6, - 43.7, - 43.8, - 43.8, - 43.7, - 43.6, - 43.5, - 43.4, - 43.3, - 43.2, - 43.1, - 43, - 42.8, - 42.7, - 42.7, - 42.6, - 42.6, - 42.8, - 42.9, - 42.9, - 42.9, - 42.9, - 42.8, - 42.8, - 42.7, - 42.7, - 42.7, - 42.7, - 42.6, - 42.5, - 42.3, - 42.1, - 41.9, - 41.7, - 41.5, - 41.5, - 41.5, - 41.5, - 41.5, - 41.4, - 41.2, - 41.2, - 41.2, - 41.2, - 41.1, - 41, - 40.9, - 40.8, - 40.7, - 40.5, - 40.2, - 40, - 39.9, - 39.7, - 39.5, - 39.4, - 39.2, - 39, - 38.8, - 38.5, - 38.2, - 38, - 37.7, - 37.5, - 37.3, - 37.1, - 36.9, - 36.6, - 36.4, - 36.2, - 35.9, - 35.8, - 35.5, - 35.3, - 35.1, - 34.9, - 34.7, - 34.5, - 34.3, - 34.2, - 34.3, - 34.4, - 34.2, - 33.8, - 33.8, - 33.8, - 33.6, - 33.7, - 34, - 33.9, - 33.6, - 33.4, - 33.3, - 33, - 32.9, - 32.7, - 32.7, - 32.8, - 32.6, - 32.2, - 32, - 31.8, - 31.6, - 31.3, - 31, - 30.7, - 30.4, - 30.2, - 29.9, - 37.2, - 37.5, - 37.7, - 38, - 38.3, - 38.6, - 38.9, - 39.1, - 39.2, - 39.2, - 39.2, - 39.2, - 39.3, - 39.4, - 39.5, - 39.6, - 39.8, - 40.1, - 40.3, - 40.7, - 41.3, - 41.4, - 41.5, - 41.7, - 42.1, - 42.5, - 42.8, - 43.2, - 43.7, - 44.1, - 44.4, - 44.7, - 45, - 45.4, - 45.7, - 45.9, - 46, - 46.2, - 46.3, - 46.3, - 46.7, - 46.9, - 47.1, - 47.4, - 47.6, - 47.8, - 48, - 48.3, - 48.5, - 48.6, - 48.8, - 48.9, - 49.1, - 49.3, - 49.4, - 49.4, - 49.6, - 49.8, - 49.8, - 49.7, - 49.6, - 49.5, - 49.5, - 49.5, - 49.5, - 49.4, - 49.4, - 49.4, - 49.3, - 49.3, - 49.5, - 49.7, - 49.6, - 49.6, - 49.8, - 49.8, - 49.6, - 49.7, - 49.8, - 49.8, - 49.9, - 49.9, - 49.9, - 49.9, - 49.7, - 49.8, - 49.8, - 49.7, - 49.6, - 49.5, - 49.6, - 49.6, - 49.6, - 49.6, - 49.5, - 49.3, - 49, - 48.9, - 48.8, - 48.7, - 48.6, - 48.4, - 48.3, - 48.1, - 47.9, - 47.8, - 47.8, - 47.8, - 47.8, - 47.7, - 47.7, - 47.6, - 47.6, - 47.6, - 47.6, - 47.5, - 47.5, - 47.4, - 47.3, - 47.3, - 47.2, - 47.1, - 47.1, - 47, - 46.9, - 46.9, - 47, - 47.1, - 46.9, - 46.5, - 46.4, - 46.3, - 46.1, - 45.8, - 45.6, - 45.5, - 45.4, - 45.2, - 45.1, - 44.9, - 44.8, - 44.7, - 44.5, - 44.4, - 44.1, - 43.8, - 43.7, - 43.9, - 43.9, - 44, - 44.2, - 44.2, - 44.1, - 43.9, - 43.9, - 43.8, - 43.8, - 43.7, - 43.6, - 43.5, - 43.2, - 43.1, - 43.2, - 43.1, - 43, - 43.1, - 43.3, - 43.3, - 43.4, - 43.4, - 43.3, - 43.2, - 43.2, - 43.1, - 43, - 43, - 42.9, - 42.8, - 42.7, - 42.7, - 42.6, - 42.3, - 42, - 42, - 42, - 42, - 41.8, - 41.7, - 41.6, - 41.5, - 41.5, - 41.5, - 41.4, - 41.3, - 41.2, - 41.1, - 41, - 40.9, - 40.6, - 40.4, - 40.2, - 40, - 39.8, - 39.6, - 39.5, - 39.3, - 39.1, - 38.7, - 38.4, - 38.2, - 37.9, - 37.8, - 37.6, - 37.4, - 37.2, - 36.9, - 36.7, - 36.5, - 36.3, - 36.1, - 35.9, - 35.7, - 35.4, - 35.2, - 35, - 34.7, - 34.5, - 34.4, - 34.5, - 34.7, - 34.7, - 34.5, - 34.6, - 34.7, - 34.6, - 34.4, - 34.4, - 34.4, - 34.2, - 33.9, - 33.5, - 33.4, - 33.2, - 33.1, - 33, - 33, - 32.9, - 32.6, - 32.3, - 32, - 31.9, - 31.7, - 31.4, - 30.9, - 30.7, - 30.5, - 30.2, - 37.4, - 37.6, - 37.8, - 38.1, - 38.4, - 38.8, - 39, - 39.1, - 39.2, - 39.1, - 39.1, - 39.2, - 39.3, - 39.4, - 39.5, - 39.7, - 39.9, - 40.2, - 40.4, - 40.7, - 41.2, - 41.3, - 41.5, - 41.8, - 42.2, - 42.7, - 42.9, - 43.3, - 43.8, - 44.3, - 44.6, - 45.1, - 45.4, - 45.8, - 46, - 46.2, - 46.3, - 46.4, - 46.6, - 46.7, - 47.1, - 47.3, - 47.5, - 47.7, - 47.9, - 48.2, - 48.5, - 48.7, - 49, - 49.1, - 49.2, - 49.2, - 49.3, - 49.6, - 49.7, - 49.8, - 49.9, - 50, - 50.1, - 49.9, - 49.7, - 49.7, - 49.7, - 49.7, - 49.7, - 49.7, - 49.7, - 49.7, - 49.7, - 49.7, - 49.8, - 49.9, - 50, - 50, - 50.2, - 50.4, - 50.2, - 50.1, - 50.1, - 50.2, - 50.3, - 50.3, - 50.3, - 50.2, - 50.1, - 50.1, - 50.1, - 50, - 49.8, - 49.8, - 49.9, - 49.9, - 49.9, - 49.8, - 49.8, - 49.7, - 49.5, - 49.2, - 49.2, - 49.2, - 49.1, - 49.1, - 49, - 48.7, - 48.6, - 48.3, - 48.2, - 48.3, - 48.4, - 48.3, - 48.1, - 48, - 48, - 47.9, - 47.9, - 47.8, - 47.8, - 47.7, - 47.6, - 47.6, - 47.5, - 47.4, - 47.3, - 47.2, - 47.2, - 47.2, - 47.3, - 47.4, - 47.6, - 47.2, - 46.9, - 46.6, - 46.3, - 46, - 45.9, - 45.7, - 45.6, - 45.6, - 45.4, - 45.3, - 45.2, - 45.1, - 45, - 44.8, - 44.6, - 44.4, - 44.3, - 44.4, - 44.4, - 44.4, - 44.5, - 44.5, - 44.5, - 44.3, - 44.2, - 44.2, - 44.2, - 44.1, - 44, - 43.8, - 43.7, - 43.6, - 43.6, - 43.6, - 43.4, - 43.4, - 43.6, - 43.7, - 43.8, - 43.8, - 43.7, - 43.6, - 43.6, - 43.5, - 43.4, - 43.3, - 43.2, - 43.1, - 43, - 43, - 43, - 42.8, - 42.6, - 42.5, - 42.4, - 42.4, - 42.2, - 42.1, - 42, - 41.8, - 41.8, - 41.8, - 41.7, - 41.6, - 41.5, - 41.3, - 41.2, - 41.1, - 40.9, - 40.7, - 40.6, - 40.4, - 40.1, - 39.9, - 39.7, - 39.5, - 39.2, - 39, - 38.7, - 38.5, - 38.3, - 38.1, - 37.9, - 37.7, - 37.5, - 37.2, - 37, - 36.8, - 36.6, - 36.4, - 36.3, - 36.1, - 35.8, - 35.6, - 35.3, - 35, - 34.9, - 34.7, - 34.7, - 34.9, - 34.9, - 34.9, - 34.9, - 34.9, - 35, - 34.9, - 34.7, - 34.5, - 34.4, - 34.2, - 33.8, - 33.7, - 33.6, - 33.4, - 33.4, - 33.1, - 32.9, - 32.7, - 32.5, - 32.3, - 32.1, - 31.8, - 31.5, - 31.3, - 31.1, - 30.8, - 30.5, - 37.8, - 38, - 38.2, - 38.5, - 38.7, - 38.8, - 39, - 39.1, - 39.1, - 39.1, - 39.2, - 39.2, - 39.3, - 39.5, - 39.8, - 40.1, - 40.3, - 40.6, - 40.8, - 41, - 41.2, - 41.4, - 41.6, - 41.9, - 42.2, - 42.7, - 43, - 43.5, - 44, - 44.5, - 45, - 45.3, - 45.7, - 46.1, - 46.4, - 46.4, - 46.6, - 46.8, - 46.9, - 47.1, - 47.5, - 47.7, - 48, - 48.2, - 48.3, - 48.6, - 48.8, - 49, - 49.2, - 49.3, - 49.4, - 49.5, - 49.6, - 49.8, - 49.9, - 50.2, - 50.3, - 50.3, - 50.2, - 50.1, - 49.9, - 49.8, - 49.9, - 49.9, - 50, - 50, - 50.1, - 50.1, - 50.2, - 50.2, - 50.3, - 50.4, - 50.4, - 50.4, - 50.5, - 50.6, - 50.6, - 50.5, - 50.5, - 50.5, - 50.6, - 50.6, - 50.6, - 50.6, - 50.5, - 50.4, - 50.5, - 50.4, - 50.2, - 50.2, - 50.3, - 50.2, - 50.2, - 50.1, - 50.1, - 50, - 50, - 49.7, - 49.6, - 49.6, - 49.5, - 49.4, - 49.4, - 49.2, - 49.1, - 48.9, - 48.7, - 48.8, - 48.8, - 48.8, - 48.6, - 48.5, - 48.5, - 48.4, - 48.3, - 48.5, - 48.5, - 48.5, - 48.3, - 48.1, - 48.1, - 47.9, - 47.6, - 47.5, - 47.5, - 47.5, - 47.4, - 47.4, - 47.5, - 47.4, - 47.1, - 46.9, - 46.5, - 46.2, - 46.1, - 46, - 46, - 46, - 45.9, - 45.8, - 45.8, - 45.7, - 45.6, - 45.4, - 45.2, - 45, - 44.9, - 44.9, - 44.9, - 45, - 44.9, - 44.8, - 44.7, - 44.6, - 44.6, - 44.5, - 44.4, - 44.4, - 44.4, - 44.2, - 44.1, - 43.9, - 43.8, - 43.8, - 43.7, - 43.7, - 43.9, - 44, - 44.1, - 44.1, - 44.1, - 44.1, - 44, - 43.9, - 43.9, - 43.7, - 43.5, - 43.5, - 43.5, - 43.4, - 43.3, - 43.2, - 43.1, - 43, - 42.9, - 42.8, - 42.8, - 42.6, - 42.4, - 42.2, - 42.2, - 42.1, - 42, - 41.9, - 41.8, - 41.7, - 41.6, - 41.5, - 41.3, - 41.1, - 40.9, - 40.8, - 40.5, - 40.2, - 39.9, - 39.7, - 39.5, - 39.2, - 39, - 38.8, - 38.7, - 38.5, - 38.3, - 38.1, - 37.9, - 37.6, - 37.3, - 37.1, - 36.9, - 36.8, - 36.6, - 36.5, - 36.2, - 36.1, - 35.8, - 35.5, - 35.3, - 35.1, - 35, - 35, - 35, - 35.1, - 35.2, - 35.2, - 35.2, - 35, - 34.9, - 34.7, - 34.5, - 34.3, - 33.9, - 33.8, - 33.7, - 33.6, - 33.5, - 33.4, - 33.2, - 32.9, - 32.7, - 32.7, - 32.5, - 32.4, - 32, - 31.6, - 31.5, - 31.3, - 31, - 38.3, - 38.4, - 38.5, - 38.7, - 38.8, - 38.9, - 39.1, - 39.2, - 39.2, - 39.2, - 39.3, - 39.3, - 39.5, - 39.8, - 40.3, - 40.7, - 40.9, - 41.2, - 41.4, - 41.5, - 41.5, - 41.7, - 41.9, - 42.2, - 42.6, - 42.9, - 43.2, - 43.6, - 44.1, - 44.6, - 45.2, - 45.6, - 45.9, - 46.2, - 46.5, - 46.8, - 47.1, - 47.3, - 47.4, - 47.5, - 47.9, - 48.1, - 48.4, - 48.6, - 48.8, - 49, - 49.2, - 49.3, - 49.4, - 49.4, - 49.6, - 49.8, - 49.9, - 50, - 50.2, - 50.4, - 50.5, - 50.4, - 50.4, - 50.3, - 50.2, - 50.1, - 50.1, - 50.2, - 50.2, - 50.4, - 50.5, - 50.6, - 50.8, - 50.9, - 50.9, - 50.9, - 51, - 50.9, - 51, - 50.9, - 50.9, - 51, - 51, - 51, - 51, - 50.9, - 50.9, - 50.9, - 50.9, - 50.9, - 50.8, - 50.7, - 50.5, - 50.5, - 50.6, - 50.5, - 50.5, - 50.4, - 50.2, - 50.2, - 50.2, - 50, - 50, - 49.9, - 49.8, - 49.8, - 49.7, - 49.6, - 49.5, - 49.4, - 49.3, - 49.2, - 49.1, - 49.3, - 49.3, - 49.2, - 49.2, - 49.1, - 48.9, - 48.8, - 48.9, - 48.9, - 48.9, - 48.6, - 48.4, - 48.5, - 48.3, - 48.2, - 48.1, - 48, - 47.8, - 47.7, - 47.6, - 47.5, - 47.3, - 47.1, - 46.8, - 46.6, - 46.5, - 46.5, - 46.4, - 46.4, - 46.4, - 46.3, - 46.3, - 46.2, - 46.1, - 46, - 45.8, - 45.5, - 45.3, - 45.3, - 45.4, - 45.5, - 45.4, - 45.2, - 45.2, - 45, - 44.8, - 44.8, - 44.8, - 44.7, - 44.6, - 44.6, - 44.5, - 44.3, - 44.1, - 44, - 44, - 44, - 44.2, - 44.2, - 44.3, - 44.3, - 44.4, - 44.4, - 44.4, - 44.3, - 44.3, - 44.2, - 44.1, - 44, - 44, - 43.9, - 43.7, - 43.6, - 43.6, - 43.5, - 43.3, - 43.2, - 43.1, - 43, - 42.7, - 42.6, - 42.5, - 42.4, - 42.4, - 42.2, - 42.1, - 42, - 41.9, - 41.8, - 41.7, - 41.5, - 41.3, - 41.1, - 40.9, - 40.6, - 40.3, - 40, - 39.8, - 39.6, - 39.3, - 39.2, - 39.1, - 38.9, - 38.7, - 38.4, - 38.2, - 37.9, - 37.6, - 37.4, - 37.3, - 37.1, - 37, - 36.8, - 36.7, - 36.5, - 36.3, - 36, - 35.7, - 35.5, - 35.3, - 35.2, - 35.2, - 35.3, - 35.4, - 35.5, - 35.3, - 35, - 34.9, - 34.9, - 34.7, - 34.6, - 34.3, - 34, - 33.9, - 33.8, - 33.7, - 33.6, - 33.5, - 33.2, - 32.9, - 32.9, - 32.8, - 32.8, - 32.7, - 32.3, - 32, - 31.7, - 31.4, - 38.8, - 38.8, - 38.9, - 38.9, - 39.1, - 39.1, - 39.2, - 39.3, - 39.3, - 39.3, - 39.4, - 39.5, - 39.6, - 40, - 40.6, - 40.9, - 41.2, - 41.4, - 41.7, - 41.9, - 42, - 42, - 42.3, - 42.8, - 43.3, - 43.5, - 43.7, - 44, - 44.4, - 44.8, - 45.3, - 45.7, - 46.1, - 46.4, - 46.8, - 47.2, - 47.6, - 47.7, - 47.7, - 48, - 48.3, - 48.6, - 48.8, - 48.9, - 49.2, - 49.3, - 49.6, - 49.7, - 49.7, - 49.8, - 49.9, - 50, - 50.1, - 50.2, - 50.4, - 50.5, - 50.5, - 50.6, - 50.7, - 50.7, - 50.6, - 50.4, - 50.5, - 50.6, - 50.7, - 50.8, - 51, - 51.1, - 51.3, - 51.5, - 51.5, - 51.5, - 51.4, - 51.4, - 51.3, - 51.4, - 51.5, - 51.5, - 51.5, - 51.4, - 51.4, - 51.3, - 51.3, - 51.4, - 51.3, - 51.1, - 51.1, - 50.9, - 50.8, - 50.8, - 50.8, - 50.7, - 50.7, - 50.6, - 50.5, - 50.4, - 50.4, - 50.3, - 50.3, - 50.2, - 50.1, - 50, - 50, - 49.9, - 49.8, - 49.8, - 49.7, - 49.6, - 49.5, - 49.5, - 49.6, - 49.6, - 49.6, - 49.6, - 49.6, - 49.3, - 49.1, - 49.1, - 49.1, - 49.1, - 48.9, - 48.9, - 48.9, - 48.7, - 48.6, - 48.5, - 48.4, - 48.2, - 47.8, - 47.6, - 47.5, - 47.3, - 47.2, - 47.1, - 47.1, - 47, - 46.9, - 46.9, - 46.8, - 46.8, - 46.7, - 46.7, - 46.6, - 46.5, - 46.3, - 46, - 45.7, - 45.8, - 45.9, - 45.8, - 45.7, - 45.6, - 45.5, - 45.4, - 45.3, - 45.2, - 45.2, - 45.1, - 45, - 44.9, - 44.8, - 44.7, - 44.6, - 44.4, - 44.2, - 44.2, - 44.5, - 44.6, - 44.6, - 44.7, - 44.7, - 44.7, - 44.7, - 44.7, - 44.6, - 44.6, - 44.7, - 44.4, - 44.4, - 44.3, - 44.3, - 44.1, - 44, - 43.9, - 43.8, - 43.8, - 43.5, - 43.4, - 43.2, - 43, - 42.9, - 42.7, - 42.7, - 42.6, - 42.5, - 42.4, - 42.2, - 42.1, - 42, - 41.8, - 41.6, - 41.4, - 41.2, - 40.9, - 40.6, - 40.4, - 40.1, - 39.9, - 39.7, - 39.6, - 39.4, - 39.2, - 38.9, - 38.8, - 38.5, - 38.2, - 38, - 37.8, - 37.6, - 37.5, - 37.3, - 37.2, - 37, - 36.8, - 36.7, - 36.4, - 36.2, - 36, - 35.8, - 35.7, - 35.6, - 35.5, - 35.6, - 35.6, - 35.5, - 35.2, - 35.1, - 35, - 34.9, - 34.7, - 34.4, - 34.2, - 34, - 34, - 33.9, - 33.7, - 33.7, - 33.6, - 33.3, - 33, - 33, - 32.9, - 32.9, - 32.8, - 32.5, - 32.1, - 31.7, - 39.2, - 39.3, - 39.3, - 39.3, - 39.4, - 39.4, - 39.4, - 39.4, - 39.4, - 39.5, - 39.6, - 39.7, - 39.8, - 40.3, - 40.9, - 41.3, - 41.6, - 41.8, - 42, - 42.2, - 42.4, - 42.5, - 42.9, - 43.4, - 43.9, - 44, - 44.2, - 44.5, - 44.8, - 45.2, - 45.7, - 46.1, - 46.4, - 46.8, - 47.1, - 47.5, - 47.8, - 48, - 48.1, - 48.4, - 48.6, - 48.9, - 49.1, - 49.2, - 49.5, - 49.7, - 49.9, - 50, - 50.1, - 50.2, - 50.2, - 50.3, - 50.4, - 50.5, - 50.5, - 50.6, - 50.7, - 50.9, - 50.9, - 50.9, - 50.9, - 50.8, - 50.9, - 51.2, - 51.3, - 51.3, - 51.4, - 51.5, - 51.7, - 51.8, - 51.9, - 51.9, - 51.8, - 51.8, - 51.7, - 51.7, - 51.8, - 51.9, - 51.9, - 51.9, - 51.7, - 51.6, - 51.7, - 51.8, - 51.6, - 51.4, - 51.3, - 51.1, - 51, - 51, - 51, - 51, - 51, - 51, - 50.8, - 50.7, - 50.6, - 50.5, - 50.4, - 50.3, - 50.2, - 50.2, - 50.2, - 50.1, - 50.1, - 50, - 50, - 49.9, - 49.9, - 49.8, - 49.8, - 49.8, - 49.8, - 49.8, - 49.8, - 49.6, - 49.5, - 49.4, - 49.3, - 49.3, - 49.3, - 49.3, - 49.2, - 49.1, - 48.9, - 48.8, - 48.5, - 48.4, - 48.2, - 47.9, - 47.7, - 47.7, - 47.7, - 47.6, - 47.5, - 47.5, - 47.4, - 47.3, - 47.3, - 47.3, - 47.3, - 47.2, - 47.1, - 46.9, - 46.6, - 46.4, - 46.3, - 46.4, - 46.3, - 46.1, - 46, - 45.9, - 45.8, - 45.8, - 45.7, - 45.6, - 45.4, - 45.4, - 45.3, - 45.3, - 45.3, - 45.2, - 45.1, - 44.8, - 44.6, - 44.5, - 44.7, - 45, - 45.1, - 45.1, - 45.2, - 45.3, - 45.2, - 45.1, - 45.1, - 45, - 45, - 44.8, - 44.7, - 44.7, - 44.7, - 44.6, - 44.4, - 44.2, - 44.1, - 44.1, - 44, - 43.8, - 43.6, - 43.4, - 43.2, - 43, - 43, - 42.9, - 42.8, - 42.8, - 42.6, - 42.5, - 42.3, - 42.1, - 41.9, - 41.7, - 41.5, - 41.3, - 41, - 40.7, - 40.4, - 40.2, - 40, - 39.9, - 39.7, - 39.5, - 39.3, - 39.1, - 38.9, - 38.7, - 38.5, - 38.2, - 38.1, - 37.9, - 37.7, - 37.5, - 37.4, - 37.2, - 37, - 36.8, - 36.6, - 36.4, - 36.3, - 36.2, - 36.1, - 36, - 35.9, - 35.9, - 35.7, - 35.5, - 35.2, - 35, - 34.9, - 34.8, - 34.5, - 34.3, - 34.2, - 34.1, - 34.1, - 34.1, - 34.1, - 34.1, - 33.9, - 33.4, - 33.2, - 33, - 33.1, - 33.1, - 32.9, - 32.4, - 32, - 39.6, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.7, - 39.8, - 39.9, - 40.2, - 40.7, - 41.3, - 41.7, - 42, - 42.2, - 42.4, - 42.6, - 42.9, - 43.3, - 43.8, - 44.2, - 44.5, - 44.5, - 44.7, - 45.1, - 45.4, - 45.7, - 46.2, - 46.6, - 46.9, - 47.2, - 47.6, - 47.8, - 48, - 48.1, - 48.4, - 48.7, - 48.9, - 49.2, - 49.4, - 49.5, - 49.7, - 50, - 50.2, - 50.3, - 50.4, - 50.4, - 50.5, - 50.5, - 50.5, - 50.7, - 50.7, - 50.7, - 50.8, - 51, - 51.1, - 51.2, - 51.2, - 51.3, - 51.4, - 51.7, - 51.9, - 52, - 52, - 52.1, - 52.1, - 52.2, - 52.2, - 52.2, - 52.2, - 52.1, - 52.1, - 52.1, - 52.1, - 52.2, - 52.2, - 52.2, - 52, - 51.9, - 51.9, - 51.8, - 51.8, - 51.7, - 51.6, - 51.4, - 51.3, - 51.3, - 51.3, - 51.3, - 51.2, - 51.2, - 51.1, - 50.9, - 50.9, - 50.8, - 50.7, - 50.5, - 50.4, - 50.4, - 50.5, - 50.5, - 50.4, - 50.3, - 50.2, - 50.2, - 50.2, - 50.1, - 50, - 50, - 49.9, - 49.9, - 49.9, - 49.9, - 49.8, - 49.7, - 49.7, - 49.5, - 49.5, - 49.4, - 49.4, - 49.2, - 49, - 48.9, - 48.7, - 48.5, - 48.3, - 48.2, - 48.1, - 48.1, - 48, - 48, - 48, - 47.9, - 47.9, - 47.8, - 47.8, - 47.8, - 47.7, - 47.6, - 47.4, - 47.3, - 47.1, - 46.8, - 46.9, - 47, - 46.8, - 46.4, - 46.1, - 46.1, - 46, - 46, - 45.9, - 45.8, - 45.7, - 45.6, - 45.6, - 45.6, - 45.8, - 45.8, - 45.5, - 45.3, - 45.1, - 45, - 45, - 45.2, - 45.4, - 45.4, - 45.5, - 45.6, - 45.6, - 45.5, - 45.5, - 45.5, - 45.3, - 45.2, - 45.1, - 45, - 45, - 45, - 44.8, - 44.6, - 44.5, - 44.4, - 44.3, - 44.2, - 44, - 43.8, - 43.6, - 43.4, - 43.4, - 43.3, - 43.2, - 43.1, - 42.9, - 42.7, - 42.5, - 42.3, - 42.2, - 42, - 41.8, - 41.6, - 41.3, - 41, - 40.7, - 40.5, - 40.3, - 40.2, - 40, - 39.8, - 39.7, - 39.5, - 39.3, - 39.1, - 38.9, - 38.7, - 38.6, - 38.4, - 38.2, - 38, - 37.8, - 37.6, - 37.4, - 37.2, - 37, - 36.8, - 36.6, - 36.5, - 36.5, - 36.4, - 36.3, - 36.2, - 36, - 35.8, - 35.5, - 35.1, - 34.9, - 34.8, - 34.7, - 34.5, - 34.4, - 34.3, - 34.5, - 34.6, - 34.6, - 34.5, - 34.5, - 34.2, - 33.7, - 33.3, - 33.2, - 33.3, - 33.1, - 32.7, - 32.4, - 40, - 40.1, - 40.3, - 40.3, - 40.2, - 40, - 39.9, - 40, - 40, - 40, - 40.1, - 40.3, - 40.6, - 41.1, - 41.7, - 42, - 42.5, - 42.7, - 43, - 43.2, - 43.5, - 43.8, - 44.2, - 44.6, - 44.8, - 44.9, - 45.1, - 45.7, - 46, - 46.3, - 46.7, - 47.1, - 47.5, - 47.7, - 48, - 48.1, - 48.1, - 48.3, - 48.6, - 49, - 49.3, - 49.6, - 49.8, - 49.9, - 50.1, - 50.4, - 50.5, - 50.5, - 50.6, - 50.6, - 50.7, - 50.7, - 50.7, - 50.9, - 51.1, - 51, - 51.1, - 51.2, - 51.3, - 51.4, - 51.5, - 51.7, - 51.9, - 52.2, - 52.4, - 52.5, - 52.6, - 52.5, - 52.6, - 52.7, - 52.6, - 52.6, - 52.5, - 52.5, - 52.5, - 52.6, - 52.5, - 52.4, - 52.4, - 52.4, - 52.3, - 52.2, - 52.1, - 52, - 52, - 52.1, - 52.2, - 52, - 51.9, - 51.8, - 51.6, - 51.6, - 51.5, - 51.4, - 51.4, - 51.3, - 51.3, - 51.2, - 51, - 50.9, - 50.9, - 50.9, - 50.8, - 50.7, - 50.7, - 50.7, - 50.6, - 50.5, - 50.4, - 50.4, - 50.3, - 50.2, - 50.2, - 50.2, - 50.1, - 50.1, - 50, - 49.9, - 49.9, - 49.8, - 49.8, - 49.7, - 49.6, - 49.5, - 49.3, - 49.1, - 48.9, - 48.8, - 48.7, - 48.6, - 48.5, - 48.5, - 48.5, - 48.5, - 48.4, - 48.4, - 48.3, - 48.2, - 48.1, - 48.1, - 48.1, - 47.9, - 47.9, - 47.9, - 47.8, - 47.7, - 47.5, - 47.4, - 47.1, - 46.6, - 46.2, - 46.1, - 46.1, - 46.1, - 46.1, - 46.1, - 46.1, - 45.9, - 45.8, - 45.9, - 46.1, - 46, - 45.8, - 45.6, - 45.4, - 45.4, - 45.4, - 45.4, - 45.6, - 45.9, - 46, - 46.1, - 46.1, - 46, - 45.9, - 45.9, - 45.8, - 45.6, - 45.5, - 45.4, - 45.4, - 45.4, - 45.2, - 45, - 44.8, - 44.7, - 44.6, - 44.5, - 44.3, - 44.1, - 43.9, - 43.8, - 43.8, - 43.8, - 43.6, - 43.4, - 43.2, - 43, - 42.8, - 42.6, - 42.5, - 42.4, - 42.2, - 41.9, - 41.6, - 41.3, - 41, - 40.7, - 40.6, - 40.5, - 40.3, - 40.1, - 39.9, - 39.8, - 39.7, - 39.5, - 39.3, - 39.2, - 39, - 38.8, - 38.6, - 38.4, - 38.2, - 38, - 37.8, - 37.6, - 37.4, - 37.1, - 36.9, - 36.8, - 36.8, - 36.7, - 36.6, - 36.4, - 36.2, - 35.9, - 35.7, - 35.3, - 35, - 34.9, - 34.7, - 34.6, - 34.5, - 34.5, - 34.6, - 34.7, - 34.8, - 34.7, - 34.7, - 34.7, - 34.4, - 33.8, - 33.3, - 33.3, - 33.2, - 33, - 32.8, - 40.5, - 40.7, - 40.8, - 40.9, - 40.8, - 40.7, - 40.5, - 40.5, - 40.5, - 40.6, - 40.7, - 41, - 41.5, - 41.7, - 42.1, - 42.4, - 42.8, - 43.2, - 43.6, - 43.8, - 44, - 44.3, - 44.8, - 45.1, - 45.2, - 45.2, - 45.6, - 46, - 46.3, - 46.7, - 47, - 47.5, - 47.9, - 48.2, - 48.3, - 48.4, - 48.6, - 48.8, - 49.1, - 49.4, - 49.8, - 50.1, - 50.2, - 50.3, - 50.5, - 50.7, - 50.7, - 50.8, - 50.9, - 50.9, - 50.9, - 51, - 51, - 51.2, - 51.4, - 51.4, - 51.5, - 51.5, - 51.6, - 51.7, - 51.9, - 52.1, - 52.3, - 52.5, - 52.8, - 52.9, - 53, - 52.9, - 52.9, - 53, - 53, - 52.9, - 52.9, - 52.9, - 52.8, - 52.8, - 52.8, - 52.8, - 52.7, - 52.6, - 52.5, - 52.4, - 52.3, - 52.2, - 52.2, - 52.4, - 52.5, - 52.4, - 52.5, - 52.4, - 52.2, - 52, - 51.8, - 51.7, - 51.6, - 51.7, - 51.8, - 51.7, - 51.5, - 51.3, - 51.3, - 51.3, - 51.3, - 51.2, - 51.1, - 51, - 50.9, - 50.9, - 50.8, - 50.8, - 50.6, - 50.5, - 50.5, - 50.5, - 50.5, - 50.4, - 50.3, - 50.2, - 50.1, - 50, - 50, - 50, - 49.9, - 49.8, - 49.6, - 49.4, - 49.2, - 49.1, - 49, - 49, - 48.9, - 48.9, - 48.9, - 49, - 48.9, - 48.8, - 48.7, - 48.6, - 48.5, - 48.5, - 48.4, - 48.2, - 48.3, - 48.4, - 48.4, - 48.2, - 48, - 47.7, - 47.4, - 46.9, - 46.5, - 46.3, - 46.2, - 46.3, - 46.4, - 46.4, - 46.3, - 46.2, - 46.2, - 46.3, - 46.3, - 46.2, - 46.1, - 46, - 45.9, - 45.9, - 45.8, - 45.8, - 45.9, - 46.1, - 46.4, - 46.5, - 46.4, - 46.4, - 46.3, - 46.2, - 46.2, - 46.1, - 45.9, - 45.8, - 45.7, - 45.7, - 45.6, - 45.4, - 45.2, - 45.1, - 45, - 44.8, - 44.6, - 44.4, - 44.2, - 44.1, - 44.1, - 44, - 43.8, - 43.6, - 43.5, - 43.3, - 43.1, - 43, - 42.9, - 42.8, - 42.6, - 42.4, - 42, - 41.7, - 41.5, - 41.1, - 40.9, - 40.8, - 40.6, - 40.4, - 40.3, - 40.2, - 40, - 39.9, - 39.7, - 39.6, - 39.4, - 39.2, - 39, - 38.8, - 38.6, - 38.4, - 38.2, - 38, - 37.7, - 37.5, - 37.3, - 37.1, - 37, - 36.9, - 36.8, - 36.6, - 36.3, - 36.1, - 35.9, - 35.6, - 35.3, - 35, - 34.9, - 34.7, - 34.7, - 34.7, - 34.7, - 34.7, - 34.7, - 34.7, - 34.7, - 34.6, - 34.6, - 34.4, - 33.7, - 33.5, - 33.4, - 33.1, - 33.1, - 40.8, - 41.1, - 41.3, - 41.5, - 41.5, - 41.5, - 41.3, - 41.3, - 41.3, - 41.4, - 41.6, - 42.3, - 42.6, - 42.7, - 42.8, - 43, - 43.3, - 43.7, - 44, - 44.3, - 44.5, - 44.8, - 45.2, - 45.5, - 45.6, - 45.8, - 46.1, - 46.4, - 46.5, - 47, - 47.3, - 47.8, - 48.3, - 48.7, - 48.9, - 49, - 49.2, - 49.5, - 49.7, - 49.9, - 50.2, - 50.4, - 50.6, - 50.7, - 50.9, - 51.1, - 51.2, - 51.2, - 51.1, - 51.2, - 51.2, - 51.3, - 51.4, - 51.5, - 51.6, - 51.7, - 51.8, - 51.9, - 52, - 52.2, - 52.3, - 52.5, - 52.6, - 52.9, - 53.2, - 53.3, - 53.3, - 53.3, - 53.3, - 53.3, - 53.3, - 53.3, - 53.3, - 53.2, - 53.1, - 53.1, - 53.1, - 53, - 53, - 52.9, - 52.8, - 52.7, - 52.8, - 52.7, - 52.7, - 52.7, - 52.6, - 52.7, - 52.6, - 52.5, - 52.4, - 52.4, - 52.2, - 52, - 51.9, - 51.9, - 52, - 52, - 52, - 51.8, - 51.7, - 51.7, - 51.7, - 51.8, - 51.7, - 51.5, - 51.4, - 51.3, - 51.2, - 51.1, - 50.9, - 50.9, - 50.9, - 50.9, - 50.9, - 50.8, - 50.7, - 50.6, - 50.4, - 50.2, - 50.2, - 50.2, - 50.1, - 50.1, - 50, - 49.7, - 49.5, - 49.5, - 49.5, - 49.4, - 49.4, - 49.4, - 49.4, - 49.4, - 49.3, - 49.2, - 49.1, - 49.1, - 49, - 48.8, - 48.7, - 48.6, - 48.7, - 48.8, - 48.7, - 48.5, - 48.2, - 48.1, - 47.8, - 47.3, - 46.9, - 46.6, - 46.4, - 46.6, - 46.7, - 46.8, - 46.8, - 46.6, - 46.6, - 46.6, - 46.6, - 46.5, - 46.5, - 46.4, - 46.3, - 46.3, - 46.3, - 46.3, - 46.3, - 46.4, - 46.6, - 46.7, - 46.7, - 46.7, - 46.6, - 46.5, - 46.5, - 46.5, - 46.3, - 46.1, - 46.1, - 46, - 45.9, - 45.7, - 45.6, - 45.4, - 45.3, - 45.2, - 45, - 44.7, - 44.5, - 44.4, - 44.4, - 44.2, - 44.1, - 44, - 43.8, - 43.6, - 43.5, - 43.4, - 43.2, - 43.1, - 42.9, - 42.7, - 42.4, - 42.1, - 41.9, - 41.6, - 41.3, - 41.1, - 41, - 40.8, - 40.7, - 40.6, - 40.4, - 40.3, - 40.1, - 40, - 39.8, - 39.6, - 39.5, - 39.3, - 39.1, - 38.9, - 38.6, - 38.4, - 38.2, - 37.9, - 37.6, - 37.5, - 37.2, - 37.1, - 37, - 36.8, - 36.6, - 36.4, - 36.2, - 35.8, - 35.5, - 35.3, - 35.2, - 35, - 34.9, - 34.9, - 34.7, - 34.7, - 34.7, - 34.8, - 34.7, - 34.7, - 34.8, - 34.6, - 34, - 33.7, - 33.7, - 33.4, - 33.2, - 41.2, - 41.4, - 41.6, - 41.8, - 42, - 42, - 42, - 42, - 42.2, - 42.3, - 42.6, - 43.1, - 43.5, - 43.7, - 43.8, - 43.8, - 43.9, - 44.3, - 44.5, - 44.8, - 45, - 45.4, - 45.7, - 45.9, - 46.1, - 46.3, - 46.7, - 46.9, - 47, - 47.3, - 47.6, - 48.2, - 48.6, - 49, - 49.3, - 49.5, - 50, - 50.2, - 50.3, - 50.3, - 50.5, - 50.8, - 50.9, - 51, - 51.2, - 51.3, - 51.5, - 51.6, - 51.5, - 51.5, - 51.7, - 51.7, - 51.8, - 52, - 52.1, - 52.1, - 52.2, - 52.3, - 52.6, - 52.7, - 52.8, - 52.9, - 53.1, - 53.4, - 53.6, - 53.8, - 53.9, - 53.8, - 53.7, - 53.7, - 53.5, - 53.6, - 53.5, - 53.4, - 53.3, - 53.3, - 53.3, - 53.2, - 53.2, - 53.2, - 53.2, - 53.2, - 53.2, - 53.2, - 53.1, - 53.1, - 52.8, - 52.7, - 52.6, - 52.5, - 52.5, - 52.5, - 52.4, - 52.4, - 52.3, - 52.2, - 52.2, - 52.3, - 52.4, - 52.3, - 52.2, - 52.2, - 52.2, - 52.3, - 52.5, - 52.1, - 51.8, - 51.8, - 51.7, - 51.6, - 51.5, - 51.4, - 51.4, - 51.5, - 51.6, - 51.4, - 51.3, - 51.1, - 50.9, - 50.7, - 50.5, - 50.5, - 50.5, - 50.5, - 50.4, - 50.1, - 50, - 49.9, - 49.9, - 49.9, - 49.9, - 49.8, - 49.8, - 49.8, - 49.7, - 49.7, - 49.6, - 49.6, - 49.4, - 49.3, - 49.1, - 49, - 49.1, - 49.1, - 49, - 48.9, - 48.7, - 48.5, - 48.2, - 47.7, - 47.3, - 47, - 46.8, - 47, - 47.2, - 47.2, - 47.1, - 47, - 47, - 46.9, - 46.8, - 46.8, - 46.8, - 46.8, - 46.7, - 46.6, - 46.7, - 46.7, - 46.8, - 46.9, - 46.9, - 47, - 47.1, - 47.1, - 47.1, - 47, - 46.8, - 46.8, - 46.6, - 46.4, - 46.3, - 46.3, - 46.1, - 46, - 46, - 45.8, - 45.6, - 45.5, - 45.3, - 45, - 44.8, - 44.8, - 44.7, - 44.5, - 44.5, - 44.3, - 44.1, - 43.9, - 43.7, - 43.6, - 43.5, - 43.3, - 43.2, - 43.1, - 42.8, - 42.5, - 42.3, - 42, - 41.7, - 41.6, - 41.4, - 41.3, - 41.1, - 41, - 40.9, - 40.7, - 40.5, - 40.3, - 40.2, - 40, - 39.9, - 39.7, - 39.5, - 39.3, - 39.1, - 38.8, - 38.5, - 38.3, - 38, - 37.8, - 37.4, - 37.4, - 37.4, - 37.2, - 36.9, - 36.7, - 36.5, - 36.1, - 35.7, - 35.6, - 35.5, - 35.3, - 35.2, - 35.1, - 35, - 34.8, - 34.9, - 35, - 35, - 35.1, - 35, - 34.7, - 34.2, - 34, - 34, - 33.8, - 33.5, - 41.6, - 41.7, - 41.8, - 42.1, - 42.2, - 42.4, - 42.5, - 42.7, - 42.8, - 43.1, - 43.6, - 44, - 44.2, - 44.5, - 44.7, - 44.9, - 44.9, - 45, - 45.2, - 45.4, - 45.7, - 45.9, - 46.2, - 46.3, - 46.5, - 46.7, - 47, - 47.3, - 47.5, - 47.7, - 48, - 48.5, - 48.9, - 49.3, - 49.7, - 50, - 50.4, - 50.6, - 50.7, - 50.9, - 51, - 51, - 51.2, - 51.3, - 51.5, - 51.6, - 51.8, - 51.9, - 51.8, - 52, - 52.1, - 52.1, - 52.1, - 52.3, - 52.5, - 52.6, - 52.7, - 53, - 53.1, - 53.2, - 53.5, - 53.7, - 53.8, - 53.9, - 54.1, - 54.3, - 54.3, - 54.2, - 54.1, - 54, - 53.9, - 53.9, - 53.9, - 53.7, - 53.6, - 53.6, - 53.5, - 53.5, - 53.4, - 53.4, - 53.4, - 53.5, - 53.5, - 53.5, - 53.3, - 53.2, - 53, - 52.9, - 52.8, - 52.7, - 52.6, - 52.7, - 52.7, - 52.7, - 52.7, - 52.4, - 52.5, - 52.6, - 52.7, - 52.8, - 52.8, - 52.6, - 52.6, - 52.7, - 52.7, - 52.6, - 52.3, - 52.2, - 52.1, - 52.1, - 52, - 52, - 52.1, - 52.1, - 52, - 51.9, - 51.8, - 51.6, - 51.4, - 51.1, - 50.9, - 50.8, - 50.8, - 50.8, - 50.7, - 50.5, - 50.4, - 50.3, - 50.3, - 50.3, - 50.3, - 50.3, - 50.2, - 50.2, - 50.2, - 50.1, - 50, - 49.9, - 49.8, - 49.6, - 49.4, - 49.4, - 49.5, - 49.4, - 49.4, - 49.4, - 49.2, - 49, - 48.5, - 48, - 47.6, - 47.4, - 47.6, - 47.8, - 47.8, - 47.7, - 47.5, - 47.4, - 47.4, - 47.3, - 47.2, - 47.1, - 47.1, - 47.1, - 47.1, - 47.1, - 47.1, - 47.1, - 47.2, - 47.3, - 47.2, - 47.2, - 47.4, - 47.4, - 47.5, - 47.5, - 47.2, - 47.1, - 46.9, - 46.8, - 46.6, - 46.5, - 46.5, - 46.4, - 46.2, - 46.1, - 46, - 45.8, - 45.6, - 45.3, - 45.2, - 45.1, - 45, - 45, - 44.8, - 44.7, - 44.5, - 44.3, - 44.1, - 43.9, - 43.8, - 43.7, - 43.5, - 43.4, - 43.2, - 42.9, - 42.6, - 42.4, - 42.2, - 42, - 41.9, - 41.7, - 41.6, - 41.4, - 41.3, - 41.1, - 40.9, - 40.7, - 40.5, - 40.4, - 40.3, - 40.1, - 39.9, - 39.6, - 39.4, - 39.1, - 38.9, - 38.6, - 38.3, - 38.1, - 37.6, - 37.7, - 37.7, - 37.4, - 37.1, - 36.9, - 36.8, - 36.4, - 36.1, - 36.1, - 35.9, - 35.7, - 35.5, - 35.5, - 35.4, - 35.2, - 35.2, - 35.3, - 35.3, - 35.3, - 35.2, - 34.9, - 34.5, - 34.3, - 34.2, - 34.1, - 33.8, - 42, - 42, - 42.1, - 42.3, - 42.5, - 42.7, - 43.1, - 43.2, - 43.4, - 43.8, - 44.4, - 44.7, - 44.9, - 45.2, - 45.4, - 45.7, - 45.8, - 45.9, - 45.8, - 45.9, - 46.2, - 46.6, - 46.7, - 46.8, - 46.9, - 47.1, - 47.3, - 47.5, - 47.8, - 48.1, - 48.5, - 48.9, - 49.3, - 49.7, - 50.1, - 50.5, - 50.8, - 51.1, - 51.3, - 51.4, - 51.5, - 51.4, - 51.5, - 51.7, - 51.8, - 52, - 52.1, - 52.2, - 52.2, - 52.4, - 52.4, - 52.4, - 52.5, - 52.7, - 52.9, - 53, - 53.2, - 53.5, - 53.6, - 53.7, - 54, - 54.3, - 54.4, - 54.5, - 54.6, - 54.6, - 54.5, - 54.4, - 54.3, - 54.2, - 54.2, - 54.1, - 54.1, - 54, - 53.9, - 53.9, - 53.8, - 53.7, - 53.7, - 53.7, - 53.7, - 53.6, - 53.6, - 53.5, - 53.4, - 53.3, - 53.3, - 53.2, - 53.1, - 53, - 53, - 53, - 53.2, - 53.1, - 52.9, - 52.8, - 52.8, - 52.9, - 52.9, - 52.9, - 53, - 52.9, - 52.9, - 52.9, - 52.9, - 52.8, - 52.6, - 52.5, - 52.5, - 52.4, - 52.4, - 52.5, - 52.5, - 52.6, - 52.4, - 52.3, - 52.2, - 52, - 51.8, - 51.5, - 51.4, - 51.2, - 51.1, - 51, - 51, - 50.9, - 50.8, - 50.8, - 50.8, - 50.8, - 50.9, - 50.8, - 50.7, - 50.6, - 50.6, - 50.5, - 50.4, - 50.4, - 50.3, - 50.1, - 49.9, - 49.9, - 49.8, - 49.8, - 49.8, - 49.8, - 49.6, - 49.3, - 48.8, - 48.3, - 47.9, - 47.9, - 48.2, - 48.4, - 48.4, - 48.3, - 47.9, - 47.8, - 47.7, - 47.6, - 47.5, - 47.5, - 47.5, - 47.4, - 47.5, - 47.5, - 47.5, - 47.5, - 47.6, - 47.6, - 47.6, - 47.6, - 47.7, - 47.8, - 47.8, - 47.7, - 47.6, - 47.4, - 47.3, - 47.1, - 46.9, - 46.7, - 46.7, - 46.6, - 46.5, - 46.5, - 46.4, - 46.3, - 46, - 45.7, - 45.5, - 45.4, - 45.4, - 45.3, - 45.2, - 45.1, - 44.9, - 44.7, - 44.5, - 44.2, - 44.1, - 44, - 43.9, - 43.7, - 43.6, - 43.3, - 43, - 42.8, - 42.6, - 42.4, - 42.3, - 42.2, - 42, - 41.9, - 41.7, - 41.5, - 41.3, - 41.1, - 41, - 40.8, - 40.7, - 40.5, - 40.2, - 40, - 39.7, - 39.4, - 39.2, - 38.9, - 38.6, - 38.3, - 37.8, - 37.9, - 38, - 37.8, - 37.4, - 37.2, - 37.1, - 36.8, - 36.6, - 36.5, - 36.4, - 36.2, - 36, - 35.8, - 35.7, - 35.6, - 35.4, - 35.3, - 35.3, - 35.4, - 35.4, - 35.2, - 34.8, - 34.4, - 34.4, - 34.3, - 34, - 42.4, - 42.4, - 42.5, - 42.7, - 43, - 43.3, - 43.7, - 44, - 44.2, - 44.6, - 45.1, - 45.4, - 45.6, - 45.9, - 46.2, - 46.5, - 46.6, - 46.9, - 46.8, - 46.8, - 46.9, - 47.1, - 47.3, - 47.4, - 47.4, - 47.4, - 47.5, - 47.8, - 48.1, - 48.5, - 48.9, - 49.2, - 49.6, - 50, - 50.5, - 50.8, - 51.1, - 51.5, - 51.6, - 51.8, - 51.9, - 51.8, - 51.9, - 52.1, - 52.2, - 52.3, - 52.4, - 52.5, - 52.5, - 52.6, - 52.7, - 52.8, - 52.9, - 53.2, - 53.3, - 53.5, - 53.7, - 53.8, - 54, - 54.2, - 54.5, - 54.7, - 54.9, - 55, - 55, - 54.9, - 54.8, - 54.7, - 54.7, - 54.6, - 54.4, - 54.3, - 54.3, - 54.3, - 54.3, - 54.2, - 54.2, - 54.2, - 54.1, - 54.1, - 54, - 53.9, - 53.8, - 53.8, - 53.7, - 53.7, - 53.5, - 53.5, - 53.4, - 53.3, - 53.3, - 53.3, - 53.3, - 53.3, - 53.2, - 53.1, - 53, - 53, - 53, - 53, - 53, - 53.1, - 53, - 53.1, - 53.1, - 53.1, - 53, - 53, - 53.1, - 52.9, - 52.9, - 52.8, - 52.8, - 52.7, - 52.8, - 52.7, - 52.6, - 52.4, - 52.1, - 51.9, - 51.7, - 51.7, - 51.6, - 51.4, - 51.5, - 51.4, - 51.4, - 51.3, - 51.3, - 51.3, - 51.4, - 51.2, - 51.1, - 51, - 51, - 50.9, - 50.9, - 50.8, - 50.7, - 50.5, - 50.4, - 50.3, - 50.3, - 50.2, - 50.2, - 50.2, - 50, - 49.6, - 49.1, - 48.7, - 48.4, - 48.4, - 48.6, - 48.7, - 48.7, - 48.6, - 48.3, - 48.2, - 48.1, - 48, - 47.9, - 47.9, - 47.9, - 47.9, - 47.8, - 47.9, - 47.9, - 47.9, - 47.9, - 47.9, - 47.9, - 47.9, - 48, - 48.1, - 48.1, - 48.1, - 47.9, - 47.8, - 47.6, - 47.5, - 47.2, - 47, - 46.9, - 46.8, - 46.8, - 46.8, - 46.8, - 46.7, - 46.4, - 46.1, - 45.8, - 45.8, - 45.7, - 45.6, - 45.5, - 45.4, - 45.2, - 45.1, - 44.9, - 44.6, - 44.4, - 44.3, - 44.1, - 44, - 43.9, - 43.7, - 43.4, - 43.2, - 43.1, - 42.9, - 42.8, - 42.7, - 42.5, - 42.3, - 42.1, - 41.9, - 41.7, - 41.5, - 41.3, - 41.2, - 41, - 40.8, - 40.5, - 40.3, - 40.1, - 39.8, - 39.5, - 39.2, - 38.9, - 38.5, - 38, - 38.1, - 38.3, - 38.1, - 37.8, - 37.6, - 37.6, - 37.4, - 37.2, - 36.9, - 36.8, - 36.6, - 36.4, - 36.3, - 36.1, - 35.9, - 35.6, - 35.4, - 35.4, - 35.5, - 35.6, - 35.5, - 35.3, - 34.7, - 34.5, - 34.5, - 34.3, - 42.9, - 42.9, - 43, - 43.5, - 43.9, - 44.1, - 44.5, - 44.8, - 45.1, - 45.5, - 45.9, - 46.1, - 46.3, - 46.5, - 46.8, - 47, - 47.3, - 47.6, - 47.7, - 47.5, - 47.6, - 47.5, - 47.5, - 47.7, - 47.7, - 47.7, - 47.9, - 48.2, - 48.5, - 48.8, - 49.2, - 49.7, - 50.1, - 50.4, - 50.8, - 51.2, - 51.5, - 51.7, - 51.8, - 52, - 52.1, - 52.2, - 52.4, - 52.4, - 52.5, - 52.6, - 52.6, - 52.7, - 52.8, - 52.9, - 53, - 53.2, - 53.4, - 53.7, - 53.9, - 54.2, - 54.3, - 54.5, - 54.7, - 54.9, - 55, - 55.2, - 55.2, - 55.3, - 55.2, - 55.2, - 55.1, - 55.1, - 55, - 54.9, - 54.8, - 54.7, - 54.6, - 54.6, - 54.6, - 54.5, - 54.5, - 54.6, - 54.5, - 54.4, - 54.4, - 54.3, - 54.2, - 54.1, - 54.1, - 54, - 53.9, - 53.9, - 53.8, - 53.7, - 53.6, - 53.6, - 53.5, - 53.5, - 53.5, - 53.4, - 53.3, - 53.2, - 53.2, - 53.2, - 53.2, - 53.2, - 53.1, - 53.1, - 53.1, - 53.2, - 53.4, - 53.4, - 53.5, - 53.4, - 53.3, - 53.2, - 53, - 53, - 53, - 53, - 52.9, - 52.6, - 52.3, - 52.2, - 52.1, - 52, - 52.1, - 52, - 51.9, - 51.9, - 51.8, - 51.7, - 51.7, - 51.7, - 51.6, - 51.6, - 51.6, - 51.6, - 51.4, - 51.4, - 51.3, - 51.2, - 51.1, - 51, - 51, - 50.9, - 50.7, - 50.6, - 50.6, - 50.6, - 50.4, - 49.9, - 49.4, - 49.3, - 48.9, - 49, - 49, - 48.9, - 48.9, - 48.9, - 48.7, - 48.6, - 48.6, - 48.4, - 48.4, - 48.5, - 48.4, - 48.4, - 48.3, - 48.2, - 48.2, - 48.2, - 48.3, - 48.3, - 48.3, - 48.3, - 48.3, - 48.4, - 48.4, - 48.3, - 48.2, - 48.1, - 48, - 47.9, - 47.7, - 47.5, - 47.2, - 47.1, - 47.1, - 47.1, - 47.1, - 47, - 46.8, - 46.5, - 46.2, - 46.2, - 46.1, - 45.9, - 45.8, - 45.6, - 45.5, - 45.4, - 45.3, - 45.1, - 44.9, - 44.6, - 44.4, - 44.3, - 44.1, - 44, - 43.8, - 43.7, - 43.5, - 43.4, - 43.2, - 43.1, - 42.9, - 42.7, - 42.5, - 42.3, - 42.1, - 41.9, - 41.8, - 41.6, - 41.3, - 41.1, - 40.8, - 40.6, - 40.3, - 40.1, - 39.8, - 39.5, - 39.2, - 38.7, - 38.2, - 38.4, - 38.6, - 38.5, - 38.3, - 38.1, - 38, - 37.9, - 37.7, - 37.4, - 37.2, - 37, - 36.8, - 36.7, - 36.5, - 36.2, - 36, - 35.6, - 35.5, - 35.6, - 35.7, - 35.6, - 35.6, - 35, - 34.7, - 34.7, - 34.5, - 43.7, - 43.7, - 43.9, - 44.3, - 44.7, - 44.9, - 45.2, - 45.6, - 46, - 46.2, - 46.5, - 46.8, - 47, - 47.3, - 47.6, - 47.8, - 47.8, - 47.9, - 48, - 48, - 47.9, - 47.9, - 47.9, - 48.1, - 48.2, - 48.3, - 48.5, - 48.7, - 49, - 49.3, - 49.6, - 50, - 50.4, - 50.7, - 51.1, - 51.4, - 51.8, - 52, - 52.2, - 52.4, - 52.5, - 52.6, - 52.7, - 52.6, - 52.6, - 52.7, - 52.8, - 52.8, - 53.1, - 53.3, - 53.5, - 53.6, - 53.9, - 54.2, - 54.5, - 54.7, - 54.9, - 55, - 55.3, - 55.4, - 55.4, - 55.5, - 55.4, - 55.4, - 55.4, - 55.4, - 55.4, - 55.3, - 55.3, - 55.2, - 55.2, - 55.1, - 55, - 54.9, - 54.9, - 54.9, - 54.8, - 54.9, - 54.8, - 54.8, - 54.7, - 54.7, - 54.6, - 54.6, - 54.4, - 54.4, - 54.4, - 54.3, - 54.2, - 54, - 53.9, - 53.9, - 53.9, - 53.8, - 53.8, - 53.8, - 53.7, - 53.6, - 53.5, - 53.5, - 53.5, - 53.4, - 53.4, - 53.3, - 53.4, - 53.5, - 53.6, - 53.7, - 53.8, - 53.7, - 53.6, - 53.6, - 53.4, - 53.3, - 53.2, - 53.1, - 53.1, - 52.8, - 52.6, - 52.4, - 52.4, - 52.4, - 52.4, - 52.3, - 52.3, - 52.2, - 52.2, - 52.2, - 52.1, - 52.1, - 52, - 52, - 52, - 52.1, - 52, - 51.9, - 51.8, - 51.7, - 51.5, - 51.4, - 51.4, - 51.3, - 51, - 50.8, - 50.8, - 50.8, - 50.8, - 50.6, - 50.2, - 49.9, - 49.5, - 49.6, - 49.5, - 49.5, - 49.5, - 49.4, - 49.2, - 49.1, - 49, - 49, - 49.1, - 49, - 48.9, - 48.8, - 48.8, - 48.7, - 48.6, - 48.6, - 48.6, - 48.6, - 48.6, - 48.6, - 48.7, - 48.8, - 48.7, - 48.7, - 48.6, - 48.5, - 48.4, - 48.3, - 48.1, - 48, - 47.8, - 47.6, - 47.4, - 47.3, - 47.3, - 47.2, - 47.1, - 46.8, - 46.6, - 46.5, - 46.5, - 46.4, - 46.2, - 46.1, - 45.9, - 45.7, - 45.6, - 45.5, - 45.3, - 45.1, - 44.9, - 44.8, - 44.6, - 44.5, - 44.3, - 44.1, - 44, - 43.8, - 43.7, - 43.5, - 43.3, - 43.1, - 42.9, - 42.7, - 42.5, - 42.3, - 42.1, - 41.9, - 41.7, - 41.4, - 41.1, - 40.8, - 40.6, - 40.3, - 40.1, - 39.7, - 39.4, - 39, - 38.5, - 38.7, - 38.9, - 38.9, - 38.9, - 38.8, - 38.6, - 38.4, - 38.2, - 37.9, - 37.7, - 37.5, - 37.3, - 37.2, - 36.9, - 36.6, - 36.4, - 36.1, - 35.8, - 35.9, - 35.8, - 35.8, - 35.6, - 35.2, - 35.1, - 35.1, - 34.8, - 44.8, - 44.8, - 44.8, - 45.1, - 45.4, - 45.7, - 46, - 46.4, - 46.7, - 47.1, - 47.5, - 47.8, - 47.9, - 48, - 48.3, - 48.5, - 48.6, - 48.4, - 48.5, - 48.4, - 48.5, - 48.4, - 48.5, - 48.6, - 48.7, - 48.8, - 49, - 49.2, - 49.4, - 49.6, - 49.9, - 50.3, - 50.6, - 50.9, - 51.4, - 51.8, - 52.2, - 52.5, - 52.8, - 53, - 53.1, - 53.2, - 53, - 52.9, - 52.9, - 53, - 53.1, - 53.2, - 53.4, - 53.7, - 53.9, - 54.1, - 54.4, - 54.8, - 54.9, - 55.1, - 55.3, - 55.5, - 55.7, - 55.7, - 55.7, - 55.6, - 55.6, - 55.6, - 55.5, - 55.6, - 55.6, - 55.6, - 55.5, - 55.5, - 55.5, - 55.4, - 55.3, - 55.2, - 55.2, - 55.2, - 55.1, - 55.2, - 55.1, - 55.1, - 55, - 55, - 55, - 55, - 54.9, - 54.8, - 54.8, - 54.6, - 54.6, - 54.4, - 54.3, - 54.3, - 54.3, - 54.3, - 54.3, - 54.2, - 54.1, - 54.1, - 54, - 53.9, - 53.8, - 53.8, - 53.9, - 53.9, - 53.9, - 54, - 54, - 54, - 54, - 54, - 53.9, - 53.9, - 53.7, - 53.4, - 53.3, - 53.3, - 53.2, - 53.1, - 52.9, - 52.7, - 52.7, - 52.7, - 52.7, - 52.7, - 52.7, - 52.7, - 52.6, - 52.7, - 52.7, - 52.6, - 52.5, - 52.5, - 52.5, - 52.5, - 52.5, - 52.5, - 52.3, - 52.1, - 52, - 51.9, - 51.8, - 51.6, - 51.4, - 51.2, - 51.2, - 51.2, - 51.2, - 51.1, - 50.9, - 50.6, - 50.3, - 50.2, - 50.1, - 50.1, - 50, - 50, - 49.7, - 49.6, - 49.5, - 49.5, - 49.5, - 49.5, - 49.4, - 49.3, - 49.2, - 49.1, - 49.1, - 49, - 49, - 48.9, - 48.9, - 48.9, - 48.9, - 49, - 49, - 49, - 48.9, - 48.9, - 48.7, - 48.6, - 48.5, - 48.4, - 48.3, - 48.1, - 47.9, - 47.6, - 47.5, - 47.6, - 47.5, - 47.1, - 46.9, - 46.9, - 46.8, - 46.8, - 46.7, - 46.6, - 46.4, - 46.2, - 45.9, - 45.8, - 45.6, - 45.5, - 45.3, - 45.3, - 45.1, - 44.9, - 44.7, - 44.5, - 44.4, - 44.3, - 44.1, - 43.9, - 43.7, - 43.5, - 43.3, - 43.1, - 42.9, - 42.7, - 42.5, - 42.3, - 42, - 41.8, - 41.4, - 41.1, - 40.9, - 40.6, - 40.4, - 40.1, - 39.8, - 39.5, - 39.1, - 39, - 39.2, - 39.2, - 39.2, - 39.2, - 39.1, - 38.9, - 38.6, - 38.4, - 38.2, - 38, - 37.7, - 37.6, - 37.4, - 37.1, - 36.9, - 36.6, - 36.3, - 36.2, - 36.2, - 35.9, - 35.7, - 35.7, - 35.7, - 35.6, - 35.1, - 46, - 46, - 46, - 46.1, - 46.2, - 46.4, - 46.7, - 47.1, - 47.4, - 47.8, - 48.3, - 48.6, - 48.6, - 48.8, - 48.9, - 49.1, - 49.2, - 49.1, - 49.1, - 49.1, - 49.2, - 49.2, - 49.2, - 49.2, - 49.2, - 49.2, - 49.3, - 49.5, - 49.7, - 49.9, - 50.2, - 50.7, - 51, - 51.3, - 51.8, - 52.2, - 52.6, - 53.1, - 53.4, - 53.6, - 53.7, - 53.8, - 53.6, - 53.6, - 53.5, - 53.5, - 53.6, - 53.7, - 53.9, - 54.1, - 54.4, - 54.6, - 54.8, - 55.1, - 55.3, - 55.5, - 55.6, - 55.8, - 55.9, - 55.9, - 56, - 55.9, - 55.8, - 55.8, - 55.8, - 55.7, - 55.7, - 55.8, - 55.7, - 55.8, - 55.7, - 55.7, - 55.7, - 55.6, - 55.6, - 55.5, - 55.5, - 55.4, - 55.4, - 55.3, - 55.3, - 55.3, - 55.4, - 55.4, - 55.3, - 55.3, - 55.2, - 55.1, - 55, - 54.9, - 54.8, - 54.7, - 54.7, - 54.7, - 54.7, - 54.6, - 54.6, - 54.6, - 54.5, - 54.4, - 54.3, - 54.3, - 54.3, - 54.4, - 54.5, - 54.5, - 54.5, - 54.4, - 54.2, - 54.2, - 54.2, - 54.1, - 53.8, - 53.7, - 53.5, - 53.5, - 53.5, - 53.5, - 53.4, - 53.3, - 53.2, - 53.3, - 53.3, - 53.4, - 53.3, - 53.2, - 53.2, - 53.2, - 53.2, - 53.1, - 53, - 53, - 53.1, - 53.1, - 53.1, - 53.1, - 52.9, - 52.6, - 52.5, - 52.3, - 52.1, - 52, - 51.8, - 51.6, - 51.5, - 51.4, - 51.4, - 51.4, - 51.3, - 51.1, - 51, - 50.8, - 50.7, - 50.6, - 50.5, - 50.6, - 50.4, - 50.2, - 50.1, - 50, - 49.9, - 49.9, - 49.8, - 49.7, - 49.7, - 49.6, - 49.5, - 49.4, - 49.3, - 49.3, - 49.2, - 49.2, - 49.2, - 49.3, - 49.3, - 49.3, - 49.2, - 49.1, - 49, - 48.9, - 48.8, - 48.8, - 48.9, - 48.6, - 48.3, - 48, - 47.9, - 47.8, - 47.8, - 47.5, - 47.3, - 47.4, - 47.3, - 47.2, - 47.1, - 47, - 46.8, - 46.6, - 46.3, - 46.2, - 46.1, - 45.9, - 45.8, - 45.7, - 45.5, - 45.4, - 45.2, - 45, - 44.8, - 44.6, - 44.4, - 44.3, - 44.1, - 43.9, - 43.7, - 43.5, - 43.3, - 43.1, - 42.9, - 42.7, - 42.4, - 42.1, - 41.8, - 41.5, - 41.2, - 40.9, - 40.7, - 40.4, - 40.2, - 40, - 39.7, - 39.3, - 39.5, - 39.5, - 39.5, - 39.4, - 39.3, - 39.1, - 39, - 38.8, - 38.6, - 38.4, - 38.1, - 37.9, - 37.8, - 37.5, - 37.3, - 37.1, - 36.9, - 36.6, - 36.6, - 36.1, - 36, - 36.1, - 36.1, - 36.1, - 35.7, - 47.2, - 47.1, - 47.1, - 47, - 46.9, - 47.1, - 47.4, - 47.8, - 48.1, - 48.4, - 48.8, - 49, - 49.2, - 49.4, - 49.5, - 49.6, - 49.7, - 49.9, - 49.9, - 49.9, - 49.9, - 49.9, - 50, - 50, - 50.1, - 50, - 50, - 50.2, - 50.4, - 50.6, - 50.7, - 51, - 51.5, - 51.8, - 52.3, - 52.8, - 53.1, - 53.5, - 53.8, - 54.1, - 54.2, - 54.4, - 54.2, - 54.3, - 54.3, - 54.1, - 54.2, - 54.4, - 54.6, - 54.7, - 54.9, - 55.2, - 55.4, - 55.6, - 55.8, - 55.9, - 56.1, - 56.3, - 56.3, - 56.3, - 56.2, - 56.1, - 56.1, - 56, - 56, - 56, - 56, - 56, - 56, - 56, - 56, - 56, - 56, - 56, - 56, - 55.9, - 55.9, - 55.8, - 55.7, - 55.6, - 55.7, - 55.7, - 55.8, - 55.7, - 55.7, - 55.8, - 55.8, - 55.6, - 55.5, - 55.3, - 55.2, - 55.1, - 55, - 55, - 55.1, - 55.1, - 55.2, - 55.1, - 55, - 54.9, - 54.8, - 54.7, - 54.7, - 54.8, - 55, - 55, - 54.8, - 54.7, - 54.7, - 54.6, - 54.6, - 54.4, - 54.2, - 54, - 53.9, - 53.8, - 53.9, - 54, - 54, - 53.9, - 53.9, - 53.9, - 54, - 54.1, - 54, - 53.9, - 53.8, - 53.7, - 53.6, - 53.6, - 53.5, - 53.5, - 53.6, - 53.7, - 53.6, - 53.6, - 53.5, - 53.3, - 53.2, - 52.8, - 52.5, - 52.4, - 52.1, - 52, - 51.9, - 51.7, - 51.7, - 51.8, - 51.7, - 51.5, - 51.4, - 51.3, - 51.1, - 51, - 50.9, - 50.9, - 50.9, - 50.7, - 50.6, - 50.6, - 50.5, - 50.5, - 50.4, - 50.2, - 50.2, - 50.1, - 50, - 49.8, - 49.7, - 49.6, - 49.5, - 49.5, - 49.6, - 49.6, - 49.7, - 49.6, - 49.5, - 49.4, - 49.3, - 49.3, - 49.1, - 49.1, - 49.1, - 48.9, - 48.7, - 48.5, - 48.5, - 48.3, - 48.1, - 47.8, - 47.8, - 47.9, - 47.8, - 47.6, - 47.5, - 47.3, - 47.2, - 47, - 46.8, - 46.7, - 46.5, - 46.4, - 46.2, - 46.1, - 45.9, - 45.8, - 45.6, - 45.4, - 45.3, - 45, - 44.8, - 44.6, - 44.5, - 44.3, - 44.1, - 43.9, - 43.7, - 43.5, - 43.2, - 43, - 42.7, - 42.4, - 42.2, - 41.8, - 41.5, - 41.2, - 41, - 40.8, - 40.5, - 40.4, - 40, - 39.8, - 39.9, - 39.9, - 39.9, - 39.8, - 39.6, - 39.3, - 39.3, - 39.2, - 39, - 38.7, - 38.3, - 38.2, - 38.1, - 37.8, - 37.6, - 37.4, - 37.2, - 37, - 36.7, - 36.4, - 36.2, - 36.3, - 36.4, - 36.4, - 36.2, - 48.3, - 48.2, - 48, - 47.8, - 47.8, - 47.9, - 48.2, - 48.5, - 48.7, - 48.9, - 49.2, - 49.4, - 49.7, - 50, - 50.3, - 50.6, - 50.9, - 50.9, - 51.1, - 51.1, - 51, - 51.1, - 51.2, - 51.3, - 51.2, - 51.2, - 51.3, - 51.4, - 51.5, - 51.6, - 51.6, - 51.7, - 52, - 52.4, - 52.8, - 53.2, - 53.7, - 54.2, - 54.4, - 54.5, - 54.7, - 55.1, - 55, - 54.9, - 54.8, - 54.8, - 54.8, - 55.1, - 55.3, - 55.4, - 55.5, - 55.8, - 56.1, - 56.2, - 56.3, - 56.4, - 56.5, - 56.5, - 56.6, - 56.7, - 56.5, - 56.4, - 56.3, - 56.3, - 56.4, - 56.3, - 56.3, - 56.3, - 56.3, - 56.4, - 56.3, - 56.4, - 56.4, - 56.4, - 56.4, - 56.3, - 56.2, - 56.2, - 56.1, - 56.1, - 56, - 56.1, - 56.2, - 56.2, - 56.2, - 56.2, - 56.2, - 56.1, - 55.9, - 55.8, - 55.7, - 55.6, - 55.5, - 55.5, - 55.5, - 55.6, - 55.6, - 55.6, - 55.5, - 55.4, - 55.3, - 55.2, - 55.2, - 55.3, - 55.4, - 55.3, - 55.1, - 55.1, - 55.1, - 55, - 54.9, - 54.9, - 54.7, - 54.5, - 54.3, - 54.3, - 54.4, - 54.5, - 54.5, - 54.5, - 54.4, - 54.5, - 54.5, - 54.6, - 54.6, - 54.5, - 54.4, - 54.3, - 54.1, - 54, - 54, - 53.9, - 54.1, - 54.2, - 54.1, - 54.1, - 54.1, - 54, - 53.9, - 53.5, - 53.1, - 52.8, - 52.7, - 52.6, - 52.6, - 52.4, - 52.3, - 52.1, - 52, - 51.8, - 51.6, - 51.5, - 51.4, - 51.4, - 51.3, - 51.4, - 51.3, - 51.2, - 51.3, - 51.4, - 51.2, - 51.1, - 51.1, - 50.9, - 50.7, - 50.6, - 50.5, - 50.3, - 50.1, - 50, - 49.9, - 49.9, - 50, - 50, - 50, - 50, - 49.9, - 49.7, - 49.6, - 49.5, - 49.4, - 49.4, - 49.3, - 49.2, - 49.1, - 48.9, - 49, - 48.8, - 48.6, - 48.2, - 48.3, - 48.3, - 48.1, - 48, - 47.9, - 47.8, - 47.6, - 47.3, - 47.1, - 46.9, - 46.8, - 46.6, - 46.6, - 46.5, - 46.4, - 46.1, - 45.9, - 45.8, - 45.6, - 45.4, - 45.1, - 45, - 44.8, - 44.7, - 44.5, - 44.3, - 44.1, - 43.8, - 43.6, - 43.3, - 43, - 42.8, - 42.5, - 42.2, - 41.8, - 41.5, - 41.3, - 41.1, - 41, - 40.8, - 40.4, - 40.4, - 40.4, - 40.3, - 40.3, - 40.2, - 40.1, - 39.9, - 39.7, - 39.6, - 39.3, - 39, - 38.8, - 38.6, - 38.4, - 38.1, - 37.9, - 37.6, - 37.3, - 37.1, - 36.8, - 36.7, - 36.5, - 36.5, - 36.6, - 36.5, - 36.4, - 49.1, - 49, - 48.8, - 48.6, - 48.5, - 48.5, - 48.7, - 48.9, - 49.1, - 49.3, - 49.6, - 49.9, - 50.2, - 50.6, - 51.1, - 51.5, - 51.6, - 51.7, - 51.9, - 52.2, - 52.2, - 52.2, - 52.4, - 52.5, - 52.5, - 52.3, - 52.3, - 52.3, - 52.4, - 52.5, - 52.6, - 52.6, - 52.8, - 53, - 53.3, - 53.9, - 54.5, - 55, - 55.2, - 55.3, - 55.4, - 55.4, - 55.4, - 55.4, - 55.4, - 55.4, - 55.5, - 55.6, - 55.8, - 56, - 56.2, - 56.4, - 56.6, - 56.7, - 56.7, - 56.8, - 56.8, - 56.7, - 56.7, - 56.8, - 56.8, - 56.8, - 56.6, - 56.6, - 56.7, - 56.7, - 56.7, - 56.7, - 56.7, - 56.8, - 56.8, - 56.9, - 56.8, - 56.8, - 56.8, - 56.8, - 56.7, - 56.7, - 56.8, - 56.7, - 56.7, - 56.7, - 56.6, - 56.6, - 56.5, - 56.5, - 56.5, - 56.4, - 56.3, - 56.2, - 56.1, - 56, - 55.9, - 56, - 56, - 56, - 56, - 56.1, - 56.1, - 56, - 56, - 55.8, - 55.7, - 55.7, - 55.7, - 55.7, - 55.6, - 55.6, - 55.5, - 55.4, - 55.4, - 55.3, - 55.2, - 55, - 55, - 55, - 55, - 55, - 55, - 55, - 54.9, - 55, - 55, - 55.1, - 55.1, - 55, - 54.9, - 54.8, - 54.7, - 54.5, - 54.6, - 54.4, - 54.5, - 54.4, - 54.3, - 54.4, - 54.5, - 54.3, - 54.2, - 54, - 53.5, - 53.3, - 53.1, - 53, - 52.9, - 52.8, - 52.6, - 52.5, - 52.3, - 52.2, - 52, - 51.8, - 51.7, - 51.7, - 51.8, - 51.8, - 51.8, - 51.9, - 52, - 52.1, - 52, - 51.8, - 51.6, - 51.5, - 51.2, - 51.1, - 50.9, - 50.7, - 50.5, - 50.3, - 50.2, - 50.3, - 50.3, - 50.4, - 50.4, - 50.2, - 50.2, - 50.1, - 50, - 50, - 49.8, - 49.8, - 49.7, - 49.6, - 49.5, - 49.3, - 49.2, - 49.1, - 48.8, - 48.6, - 48.7, - 48.7, - 48.5, - 48.4, - 48.4, - 48.2, - 48, - 47.7, - 47.4, - 47.2, - 47.1, - 46.9, - 46.8, - 46.8, - 46.7, - 46.5, - 46.3, - 46.1, - 45.9, - 45.8, - 45.6, - 45.4, - 45.2, - 45.1, - 44.9, - 44.6, - 44.4, - 44.2, - 43.9, - 43.7, - 43.4, - 43.1, - 42.8, - 42.6, - 42.2, - 41.9, - 41.7, - 41.5, - 41.3, - 41.1, - 40.8, - 41, - 41, - 40.8, - 40.8, - 40.7, - 40.6, - 40.5, - 40.3, - 40, - 39.7, - 39.5, - 39.2, - 39.1, - 38.8, - 38.6, - 38.3, - 37.9, - 37.6, - 37.3, - 37.1, - 37.1, - 36.9, - 36.7, - 36.7, - 36.7, - 36.7, - 49.7, - 49.6, - 49.6, - 49.5, - 49.3, - 49.1, - 49.1, - 49.3, - 49.5, - 49.8, - 50.1, - 50.5, - 50.9, - 51.2, - 51.6, - 52, - 52.2, - 52.4, - 52.8, - 53.1, - 53.4, - 53.6, - 53.7, - 53.7, - 53.7, - 53.7, - 53.4, - 53.2, - 53.2, - 53.3, - 53.3, - 53.4, - 53.5, - 53.7, - 54.1, - 54.4, - 55, - 55.5, - 55.8, - 56.1, - 56.2, - 56.3, - 56.2, - 56.2, - 56.2, - 56.3, - 56.2, - 56.2, - 56.3, - 56.5, - 56.7, - 57, - 57.2, - 57.2, - 57.2, - 57.3, - 57.2, - 57.1, - 57, - 56.9, - 57, - 57, - 57, - 57, - 57.1, - 57.1, - 57.1, - 57.1, - 57.1, - 57.2, - 57.2, - 57.2, - 57.2, - 57.3, - 57.2, - 57.3, - 57.2, - 57.3, - 57.3, - 57.3, - 57.4, - 57.4, - 57.2, - 57, - 56.9, - 56.8, - 56.7, - 56.7, - 56.6, - 56.6, - 56.5, - 56.4, - 56.5, - 56.5, - 56.5, - 56.5, - 56.5, - 56.6, - 56.7, - 56.6, - 56.6, - 56.5, - 56.3, - 56.1, - 56, - 55.9, - 55.9, - 55.9, - 55.9, - 55.8, - 55.8, - 55.6, - 55.6, - 55.6, - 55.6, - 55.6, - 55.6, - 55.5, - 55.5, - 55.5, - 55.5, - 55.4, - 55.4, - 55.5, - 55.5, - 55.5, - 55.4, - 55.3, - 55.2, - 55.1, - 55, - 54.9, - 54.8, - 54.7, - 54.5, - 54.5, - 54.5, - 54.4, - 54.4, - 54.2, - 53.9, - 53.6, - 53.3, - 53.1, - 53, - 53, - 52.9, - 52.8, - 52.7, - 52.5, - 52.3, - 52.1, - 51.9, - 51.9, - 52, - 52.1, - 52.2, - 52.4, - 52.5, - 52.3, - 52.5, - 52.6, - 52.2, - 52, - 51.6, - 51.3, - 51.3, - 51.1, - 50.9, - 50.6, - 50.6, - 50.6, - 50.7, - 50.7, - 50.6, - 50.5, - 50.5, - 50.4, - 50.4, - 50.3, - 50.2, - 50.2, - 50.2, - 50, - 49.8, - 49.7, - 49.5, - 49.3, - 49, - 49.1, - 49.2, - 49.1, - 49, - 48.9, - 48.9, - 48.6, - 48.4, - 48.1, - 47.9, - 47.7, - 47.6, - 47.3, - 47.1, - 47, - 46.9, - 46.8, - 46.6, - 46.5, - 46.3, - 46.1, - 45.9, - 45.8, - 45.6, - 45.4, - 45.2, - 45, - 44.8, - 44.6, - 44.4, - 44.1, - 43.8, - 43.5, - 43.2, - 42.9, - 42.6, - 42.3, - 42.1, - 41.9, - 41.7, - 41.4, - 41.2, - 41.4, - 41.5, - 41.5, - 41.4, - 41.3, - 41.2, - 41, - 40.8, - 40.5, - 40.2, - 39.9, - 39.7, - 39.5, - 39.2, - 39, - 38.7, - 38.3, - 38, - 37.7, - 37.5, - 37.4, - 37.2, - 37, - 36.9, - 36.9, - 36.9 - ] + "raw16": "heightmap.r16", + "min": 9.3, + "max": 6562.8 } } }, diff --git a/packages/terrain/starter/tundra/world.ts b/packages/terrain/starter/tundra/world.ts index 83b6c83fb7..f5fa72259d 100644 --- a/packages/terrain/starter/tundra/world.ts +++ b/packages/terrain/starter/tundra/world.ts @@ -1,31 +1,20 @@ -// The tundra starter world, as the game that copied it draws and collides with it. -// -// `bake.mjs` wrote a world package into `world/`: heightmap, splat masks, placements and the CC0 boulder. -// The engine's `WorldCells` streams it. `loadTerrainSplat` textures its ground. -// Everything that decides how the world looks, or what stops the player, is in this folder for the game to edit. -import type { IComputeDriven, ICtx } from "@threenative/core"; +import type { ICtx } from "@threenative/core"; +import { type IWorldPackage, loadTerrainSplat } from "@threenative/core/world"; +import type { IPhysicsContext } from "@threenative/physics"; import { - Heightfield, - type IWorldCellsLoadOptions, - type IWorldPackage, - WorldCells, - heightSamplerFromHeightmap, - loadTerrainSplat, - loadWorldHeightmap, -} from "@threenative/core/world"; -import { CollisionShape3D, type IPhysicsContext, RigidBody3D } from "@threenative/physics"; + type IStarterWorld, + type IStarterWorldSpec, + PropColliders as StarterPropColliders, + addStarterWorld, +} from "@threenative/terrain/starter"; import { EquirectangularReflectionMapping, - type Material, type Mesh, type MeshStandardMaterial, - Object3D, + type Object3D, type Texture, - Vector3, } from "three"; -import { HDRLoader } from "three/addons/loaders/HDRLoader.js"; import { type ITundraWater, type ITundraWaterFile, addTundraWater } from "./water.js"; - /** Where the game's asset source holds the baked `world/` folder. */ export const TUNDRA_URL = "terrain/tundra/world.json"; @@ -43,324 +32,71 @@ export const PROP_SKY_LIGHT = 0.5; /** Metres around `follow` within which props collide. Beyond that, props only draw. */ export const PROP_COLLIDER_REACH = 60; -export interface ITundraWorld { - readonly world: WorldCells; - /** One heightfield for the whole world, in place before `addTundra` resolves. */ - readonly ground: RigidBody3D; - /** The prop colliders near `follow`. They stream with it (see `PROP_COLLIDER_REACH`). */ - readonly colliders: PropColliders; - /** The heightfield the ground stands on: `heightAt(x, z)` reads the terrain under any point. */ - readonly field: Heightfield; - /** Every placed prop's record, eight floats each, as the bake wrote it. */ - readonly placements: Float32Array; - /** The world's extent in metres, as the bake wrote it. */ - readonly extent: IWorldPackage["extent"]; - /** The kettle lakes the bake wrote, as circles on the ground: the scene keeps its spawn off their shores. */ +export interface ITundraLake { + readonly at: readonly [number, number]; + readonly radius: number; +} +export interface ITundraWorld extends IStarterWorld { readonly lakes: readonly ITundraLake[]; - /** How many meltwater rivers the bake wrote. They are drawn, and counted into the playtest. */ readonly rivers: number; - /** The lakes and rivers as they were drawn. `dispose` removes their meshes. */ readonly water: ITundraWater; } - -/** One kettle lake as the scene reads it: where its centre is and how far its shore reaches. */ -export interface ITundraLake { - readonly at: readonly [number, number]; - readonly radius: number; +const spec: IStarterWorldSpec<{ + dispose(): void; + water: ITundraWater; + lakes: readonly ITundraLake[]; + rivers: number; +}> = { + name: "Tundra", + url: TUNDRA_URL, + colliders: COLLIDERS, + colliderReach: PROP_COLLIDER_REACH, + loadSurface: (ctx, url) => loadTerrainSplat({ assets: ctx.assets, url }), + lighting: { sky: "sky.hdr", lods: false, dress: dressProp }, + streaming: { + ring: 5, + impostors: false, + gpuScene: false, + shadows: { cast: true, receive: true }, + budgets: { residentCells: 64, instances: 40_000, bytes: 64_000_000 }, + terrain: { streamRadius: 6 }, + }, + water: (ctx, file, field) => { + const data = file as ITundraWaterFile; + const water = addTundraWater(ctx, data, field); + return { + water, + lakes: data.lakes.map((lake) => ({ at: lake.at, radius: lake.radius })), + rivers: data.rivers.length, + dispose: () => water.dispose(), + }; + }, +}; +export class PropColliders extends StarterPropColliders { + constructor( + ctx: ICtx, IPhysicsContext>, + follow: Object3D, + manifest: IWorldPackage, + records: Float32Array, + ) { + super(ctx, follow, manifest, records, COLLIDERS, PROP_COLLIDER_REACH, "Tundra"); + } } - -/** - * Stream the tundra world around `follow` (usually the player or the camera) and give it collision. - * `whileCurrent` stops a departed scene's async load from attaching anything. Its resources are - * released on cancellation, failure and when the game's entity registry ends this scene. - */ export async function addTundra( ctx: ICtx, IPhysicsContext>, follow: Object3D, url = TUNDRA_URL, whileCurrent: () => boolean = () => true, ): Promise { - const assertCurrent = () => { - if (whileCurrent()) return; - const error = new Error("Tundra world: its scene is no longer current."); - error.name = "AbortError"; - throw error; - }; - let surface: Material | undefined; - let releaseLighting: (() => void) | undefined; - let world: WorldCells | undefined; - let ground: RigidBody3D | undefined; - let colliders: PropColliders | undefined; - let water: ITundraWater | undefined; - let released = false; - const dispose = () => { - if (released) return; - released = true; - colliders?.detach(); - colliders?.removeFromParent(); - ground?.dispose(); - ground?.object?.removeFromParent(); - water?.dispose(); - world?.dispose(); - surface?.dispose(); - releaseLighting?.(); - }; - try { - assertCurrent(); - const manifest = (await (await fetchAsset(ctx, url, assertCurrent)).json()) as IWorldPackage; - assertCurrent(); - const base = url.slice(0, url.lastIndexOf("/") + 1); - const records = await ( - await fetchAsset(ctx, base + manifest.placements, assertCurrent) - ).arrayBuffer(); - assertCurrent(); - const [heightmapUrl] = await ctx.assets.resolve(base + manifest.terrain.heightmap); - assertCurrent(); - if (heightmapUrl === undefined) throw new Error("Tundra world: its heightmap is not served."); - const heightmap = await loadWorldHeightmap(heightmapUrl); - assertCurrent(); - surface = await loadTerrainSplat({ assets: ctx.assets, url }); - assertCurrent(); - releaseLighting = await lightProps(ctx, url, manifest, assertCurrent); - assertCurrent(); - const options: IWorldCellsLoadOptions = { - url, - assets: ctx.assets, - data: { manifest, placements: records, heightmap }, - surface, - follow, - ring: 5, - impostors: false, - gpuScene: false, - shadows: { cast: true, receive: true }, - budgets: { residentCells: 64, instances: 40_000, bytes: 64_000_000 }, - terrain: { streamRadius: 6 }, - }; - world = await WorldCells.load(options); - assertCurrent(); - const field = tundraField(manifest, heightmap); - ground = groundCollider(ctx, manifest, field); - const waterFile = (await ( - await fetchAsset(ctx, `${base}water.json`, assertCurrent) - ).json()) as ITundraWaterFile; - assertCurrent(); - const lakeWater = addTundraWater(ctx, waterFile, field); - water = lakeWater; - const props = new Float32Array(records); - colliders = new PropColliders(ctx, follow, manifest, props); - ctx.add(world); - if (ground.object !== undefined) ctx.add(ground.object); - ctx.add(colliders); - ctx.entities.add(`tundra-world.${world.uuid}`, { dispose }); - return { - world, - ground, - colliders, - field, - placements: props, - extent: manifest.extent, - lakes: waterFile.lakes.map((lake) => ({ at: lake.at, radius: lake.radius })), - rivers: waterFile.rivers.length, - water: lakeWater, - }; - } catch (error) { - dispose(); - throw error; - } + const result = await addStarterWorld(ctx, follow, spec, url, whileCurrent); + if (!result.extra) throw new Error("Tundra world: missing water."); + return { ...result, ...result.extra }; } - -// Every prop gets the sky as its own envMap before WorldCells adopts the shared cached models. -// Without it, a boulder lit by the sun alone draws flat. -async function lightProps( - ctx: ICtx, IPhysicsContext>, - url: string, - manifest: IWorldPackage, - assertCurrent: () => void, -): Promise<() => void> { - const base = url.slice(0, url.lastIndexOf("/") + 1); - const [skyUrl] = await ctx.assets.resolve(`${base}sky.hdr`); - assertCurrent(); - if (skyUrl === undefined) throw new Error("Tundra world: 'sky.hdr' is not served."); - const sky = await new HDRLoader().loadAsync(skyUrl); - const originals = new Map(); - const dispose = () => { - for (const [material, original] of originals) { - if (material.envMap !== sky) continue; - material.envMap = original.envMap; - material.envMapIntensity = original.intensity; - } - sky.dispose(); - }; - try { - assertCurrent(); - sky.mapping = EquirectangularReflectionMapping; - for (const asset of Object.values(manifest.assets)) { - const model = await ctx.assets.model<{ scene: Object3D }>(base + asset.glb); - assertCurrent(); - model.scene.traverse((object) => { - const mesh = object as Mesh; - for (const material of [mesh.material ?? []].flat() as MeshStandardMaterial[]) - if (!originals.has(material)) - originals.set(material, { - envMap: material.envMap, - intensity: material.envMapIntensity, - }); - dressProp(mesh, sky); - }); - } - return dispose; - } catch (error) { - dispose(); - throw error; - } -} - /** One prop mesh: the sky as its envMap. */ function dressProp(mesh: Mesh, sky: Texture): void { for (const material of [mesh.material ?? []].flat() as MeshStandardMaterial[]) { + sky.mapping = EquirectangularReflectionMapping; material.envMap = sky; material.envMapIntensity = PROP_SKY_LIGHT; } } - -// The streamed terrain tiles arrive over several frames. A player spawned before its tile would -// fall through. One heightfield for the whole package avoids that, at a few hundred kilobytes. -/** The kit's one heightfield: the collider stands on it. */ -function tundraField(manifest: IWorldPackage, heightmap: Uint16Array): Heightfield { - const { extent, terrain } = manifest; - return Heightfield.fromSampler({ - columns: terrain.columns, - rows: terrain.rows, - width: extent.sizeX, - depth: extent.sizeZ, - origin: { x: extent.minX + extent.sizeX / 2, z: extent.minZ + extent.sizeZ / 2 }, - sampleHeight: heightSamplerFromHeightmap(terrain, extent, heightmap), - }); -} - -function groundCollider( - ctx: ICtx, IPhysicsContext>, - manifest: IWorldPackage, - field: Heightfield, -): RigidBody3D { - const { extent } = manifest; - const anchor = new Object3D(); - anchor.position.set(extent.minX + extent.sizeX / 2, 0, extent.minZ + extent.sizeZ / 2); - return new RigidBody3D({ - object: anchor, - physics: ctx.physics, - type: "fixed", - entity: "terrain", - shape: CollisionShape3D.heightfield(field.rows, field.columns, field.toColliderHeights(), { - x: extent.sizeX, - y: 1, - z: extent.sizeZ, - }), - }); -} - -/** - * Fixed bodies for the props within `PROP_COLLIDER_REACH` of `follow`, rebuilt when it has moved a few - * metres. Thousands of resident bodies cost Rapier about 0.5 s per 8 s (measured). A hundred do not. - */ -export class PropColliders extends Object3D implements IComputeDriven { - readonly warmupNodes: readonly unknown[] = []; - readonly processCadence = "render" as const; - readonly #live = new Map(); - readonly #runs: { asset: string; offset: number; count: number }[] = []; - readonly #at = { x: Number.NaN, z: Number.NaN }; - readonly #followPosition = new Vector3(); - #released = false; - - constructor( - private readonly ctx: ICtx, IPhysicsContext>, - private readonly follow: Object3D, - manifest: IWorldPackage, - private readonly records: Float32Array, - ) { - super(); - this.name = "tundra-prop-colliders"; - for (const cell of manifest.cells) - for (const run of cell.runs) if (COLLIDERS[run.asset]) this.#runs.push(run); - try { - this.#rebuild(); - } catch (error) { - this.detach(); - throw error; - } - } - - /** How many prop bodies exist right now. */ - get active(): number { - return this.#live.size; - } - - get released(): boolean { - return this.#released; - } - - attachRenderer(): void {} - - process(): void { - if (this.#released) return; - const p = this.follow.getWorldPosition(this.#followPosition); - if (Math.hypot(p.x - this.#at.x, p.z - this.#at.z) > 4 || Number.isNaN(this.#at.x)) - this.#rebuild(); - } - - detach(): void { - for (const body of this.#live.values()) body.dispose(); - this.#live.clear(); - this.#released = true; - } - - #rebuild(): void { - const { x: fx, z: fz } = this.follow.getWorldPosition(this.#followPosition); - this.#at.x = fx; - this.#at.z = fz; - const keep = new Set(); - for (const run of this.#runs) { - const shape = COLLIDERS[run.asset]; - if (!shape) continue; - for (let i = run.offset; i < run.offset + run.count; i++) { - const x = this.records[i * 8] as number; - const z = this.records[i * 8 + 2] as number; - if (Math.hypot(x - fx, z - fz) > PROP_COLLIDER_REACH) continue; - keep.add(i); - if (this.#live.has(i)) continue; - const collider = CollisionShape3D.sphere( - shape.sphere * (this.records[i * 8 + 7] as number), - ); - this.#live.set( - i, - new RigidBody3D({ - physics: this.ctx.physics, - type: "fixed", - entity: `${run.asset}.${String(i)}`, - position: { x, y: this.records[i * 8 + 1] as number, z }, - shape: collider, - }), - ); - } - } - for (const [i, body] of this.#live) - if (!keep.has(i)) { - body.dispose(); - this.#live.delete(i); - } - } -} - -async function fetchAsset( - ctx: ICtx, IPhysicsContext>, - url: string, - assertCurrent: () => void, -): Promise { - const [resolved] = await ctx.assets.resolve(url); - assertCurrent(); - if (resolved === undefined) throw new Error(`Tundra world: '${url}' is not served.`); - const response = await fetch(resolved); - assertCurrent(); - if (!response.ok) throw new Error(`Tundra world: '${url}' answered ${String(response.status)}.`); - return response; -} diff --git a/packages/terrain/tsconfig.json b/packages/terrain/tsconfig.json index bffa2e3c87..df663f8ec8 100644 --- a/packages/terrain/tsconfig.json +++ b/packages/terrain/tsconfig.json @@ -1,8 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "types": ["node"], - "allowJs": true + "types": ["node"] }, - "include": ["src/**/*.ts", "src/editor/**/*.js", "__tests__/**/*.ts"] + "include": ["src/**/*.ts", "__tests__/**/*.ts"] } diff --git a/packages/terrain/tsup.config.ts b/packages/terrain/tsup.config.ts index ada3a8d4a2..b0423f1069 100644 --- a/packages/terrain/tsup.config.ts +++ b/packages/terrain/tsup.config.ts @@ -5,18 +5,22 @@ import { defineConfig } from "tsup"; export default defineConfig({ entry: { index: "src/index.ts", + kit: "src/kit.ts", + starter: "src/starter.ts", three: "src/three.ts", export: "src/export.ts", "editor/server": "src/editor/server.ts", "editor/index": "src/editor/index.ts", - "editor/worker": "src/editor/worker.js", + "editor/worker": "src/editor/worker.ts", + "mcp/server": "src/mcp/server.ts", + "mcp/cook-child": "src/mcp/cook-child.ts", }, format: ["esm"], target: "node20", dts: true, sourcemap: false, clean: true, - external: ["three"], + external: ["three", "@threenative/core", "@threenative/physics"], splitting: false, treeshake: true, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9cc76044a..f417afcab3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,7 +117,7 @@ patchedDependencies: hash: e5decbc72f46c0747e26e2ca910e92936f157872011d75fbd32d3d3bcf0d3c1a path: patches/@tailwindcss__node@4.3.3.patch three@0.185.1: - hash: 00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437 + hash: d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3 path: packages/core/patches/three@0.185.1.patch tsup@8.5.1: hash: bdcba0cb0e0023b6ee572aae9dd40456c0c3bb0edd9c2abd290caed6ebef0770 @@ -145,6 +145,9 @@ importers: '@playwright/test': specifier: 1.62.1 version: 1.62.1 + '@threenative/assets': + specifier: workspace:* + version: link:packages/assets '@types/node': specifier: 20.19.43 version: 20.19.43 @@ -198,7 +201,7 @@ importers: version: 19.2.0(react@19.2.0) three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@tailwindcss/vite': specifier: 'catalog:' @@ -245,7 +248,7 @@ importers: dependencies: three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: vite: specifier: 'catalog:' @@ -264,7 +267,7 @@ importers: version: link:../../packages/physics three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -289,7 +292,7 @@ importers: version: link:../../packages/core three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@threenative/playtest': specifier: workspace:* @@ -323,7 +326,7 @@ importers: version: link:../../packages/playtest three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -354,7 +357,7 @@ importers: version: link:../../packages/core three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -382,7 +385,7 @@ importers: version: link:../../packages/physics three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@threenative/playtest': specifier: workspace:* @@ -410,7 +413,7 @@ importers: version: link:../../packages/playtest three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -429,7 +432,7 @@ importers: version: link:../../packages/physics three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -448,7 +451,7 @@ importers: dependencies: three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -473,7 +476,7 @@ importers: version: 0.33.0(react@19.2.0) three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@tailwindcss/vite': specifier: 'catalog:' @@ -522,7 +525,7 @@ importers: version: link:../../packages/physics three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/react': specifier: 'catalog:' @@ -556,7 +559,7 @@ importers: version: link:../../packages/core three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -578,11 +581,11 @@ importers: version: 0.2.0(@types/react@19.2.0)(react@19.2.0) three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@threenative/playtest': specifier: 0.2.0 - version: 0.2.0(playwright@1.62.1)(three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437)) + version: 0.2.0(playwright@1.62.1)(three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3)) '@types/three': specifier: 0.185.3 version: 0.185.3 @@ -610,7 +613,7 @@ importers: version: link:../../packages/core three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -638,7 +641,7 @@ importers: version: link:../../packages/playtest three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -663,7 +666,7 @@ importers: version: link:../../packages/playtest three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -685,7 +688,7 @@ importers: version: link:../../packages/core three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -707,7 +710,7 @@ importers: version: link:../../packages/core three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -729,7 +732,7 @@ importers: version: link:../../packages/core three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -757,7 +760,7 @@ importers: version: link:../../packages/playtest three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -782,7 +785,7 @@ importers: version: link:../../packages/playtest three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@threenative/runtime-native': specifier: workspace:* @@ -810,7 +813,7 @@ importers: version: link:../../packages/procedural-animals three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@threenative/assets': specifier: workspace:* @@ -838,7 +841,7 @@ importers: version: link:../../packages/playtest three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@threenative/assets': specifier: workspace:* @@ -872,7 +875,7 @@ importers: version: link:../../packages/core three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/three': specifier: 0.185.3 @@ -897,7 +900,7 @@ importers: version: link:../../packages/physics three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@threenative/terrain': specifier: workspace:* @@ -928,7 +931,7 @@ importers: version: link:../../packages/core three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@threenative/playtest': specifier: workspace:* @@ -961,6 +964,9 @@ importers: '@gltf-transform/functions': specifier: 'catalog:' version: 4.4.2(@types/node@20.19.43) + '@threenative/terrain': + specifier: workspace:* + version: link:../terrain '@wasm-audio-decoders/ogg-vorbis': specifier: 0.1.20 version: 0.1.20 @@ -984,10 +990,10 @@ importers: version: 0.35.4(@types/node@20.19.43) three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) three-mesh-bvh: specifier: 'catalog:' - version: 0.9.14(three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437)) + version: 0.9.14(three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3)) wasm-media-encoders: specifier: 0.7.0 version: 0.7.0 @@ -1045,10 +1051,10 @@ importers: dependencies: three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) three-mesh-bvh: specifier: 'catalog:' - version: 0.9.14(three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437)) + version: 0.9.14(three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3)) threenative-asset-mcp: specifier: 0.12.0 version: 0.12.0(@types/node@20.19.43) @@ -1124,7 +1130,7 @@ importers: version: 1.1.1 three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@threenative/assets': specifier: workspace:* @@ -1197,7 +1203,7 @@ importers: version: 0.3.22 three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) tsup: specifier: 8.5.1 version: 8.5.1(patch_hash=bdcba0cb0e0023b6ee572aae9dd40456c0c3bb0edd9c2abd290caed6ebef0770)(@typescript/typescript6@6.0.2)(jiti@2.7.0)(postcss@8.5.25)(tsx@4.23.5) @@ -1228,7 +1234,7 @@ importers: version: 0.3.22 three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) tsup: specifier: 8.5.1 version: 8.5.1(patch_hash=bdcba0cb0e0023b6ee572aae9dd40456c0c3bb0edd9c2abd290caed6ebef0770)(@typescript/typescript6@6.0.2)(jiti@2.7.0)(postcss@8.5.25)(tsx@4.23.5) @@ -1259,7 +1265,7 @@ importers: version: 0.3.22 three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) tsup: specifier: 8.5.1 version: 8.5.1(patch_hash=bdcba0cb0e0023b6ee572aae9dd40456c0c3bb0edd9c2abd290caed6ebef0770)(@typescript/typescript6@6.0.2)(jiti@2.7.0)(postcss@8.5.25)(tsx@4.23.5) @@ -1271,10 +1277,10 @@ importers: dependencies: procedural-animals: specifier: 'catalog:' - version: https://codeload.github.com/majidmanzarpour/threejs-procedural-animals/tar.gz/c95ae49346aa8e140a924376cec6cf0073d99512(three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437)) + version: https://codeload.github.com/majidmanzarpour/threejs-procedural-animals/tar.gz/c95ae49346aa8e140a924376cec6cf0073d99512(three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3)) three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@threenative/assets': specifier: workspace:* @@ -1302,7 +1308,7 @@ importers: dependencies: three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/node': specifier: 20.19.43 @@ -1349,7 +1355,7 @@ importers: version: 0.3.22 three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) vitest: specifier: 'catalog:' version: 4.1.10(@types/node@20.19.43)(@vitest/coverage-v8@4.1.10)(vite@8.2.0(patch_hash=c99095f3002ba7f0997f348d6c1bcf5952587ed5dee18c058269c0d634d146c1)(@types/node@20.19.43)(esbuild@0.27.0)(jiti@2.7.0)(tsx@4.23.5)) @@ -1376,7 +1382,7 @@ importers: version: 0.3.22 three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) tsup: specifier: 'catalog:' version: 8.5.1(patch_hash=bdcba0cb0e0023b6ee572aae9dd40456c0c3bb0edd9c2abd290caed6ebef0770)(@typescript/typescript6@6.0.2)(jiti@2.7.0)(postcss@8.5.25)(tsx@4.23.5) @@ -1394,7 +1400,7 @@ importers: version: 0.8.3 three: specifier: 'catalog:' - version: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + version: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) devDependencies: '@types/node': specifier: 20.19.43 @@ -4749,8 +4755,8 @@ snapshots: '@threenative/core@0.2.0(@types/react@19.2.0)(react@19.2.0)': dependencies: - three: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) - three-mesh-bvh: 0.9.14(three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437)) + three: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) + three-mesh-bvh: 0.9.14(three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3)) zustand: 5.0.14(@types/react@19.2.0)(react@19.2.0) transitivePeerDependencies: - '@types/react' @@ -4758,19 +4764,19 @@ snapshots: - react - use-sync-external-store - '@threenative/playtest@0.2.0(playwright@1.62.1)(three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437))': + '@threenative/playtest@0.2.0(playwright@1.62.1)(three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3))': dependencies: pngjs: 7.0.0 optionalDependencies: playwright: 1.62.1 - three: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + three: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) - '@threenative/playtest@0.3.2(playwright@1.62.0)(three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437))': + '@threenative/playtest@0.3.2(playwright@1.62.0)(three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3))': dependencies: pngjs: 7.0.0 optionalDependencies: playwright: 1.62.0 - three: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + three: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) '@threenative/runtime-native@0.2.0': dependencies: @@ -5684,9 +5690,9 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - procedural-animals@https://codeload.github.com/majidmanzarpour/threejs-procedural-animals/tar.gz/c95ae49346aa8e140a924376cec6cf0073d99512(three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437)): + procedural-animals@https://codeload.github.com/majidmanzarpour/threejs-procedural-animals/tar.gz/c95ae49346aa8e140a924376cec6cf0073d99512(three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3)): dependencies: - three: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + three: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) proj4@2.22.0(geotiff@3.0.5): dependencies: @@ -5963,11 +5969,11 @@ snapshots: dependencies: any-promise: 1.3.0 - three-mesh-bvh@0.9.14(three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437)): + three-mesh-bvh@0.9.14(three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3)): dependencies: - three: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + three: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) - three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437): {} + three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3): {} threenative-asset-mcp@0.12.0(@types/node@20.19.43): dependencies: @@ -5975,11 +5981,11 @@ snapshots: '@gltf-transform/extensions': 4.4.2 '@gltf-transform/functions': 4.4.2(@types/node@20.19.43) '@modelcontextprotocol/server': 2.0.0 - '@threenative/playtest': 0.3.2(playwright@1.62.0)(three@0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437)) + '@threenative/playtest': 0.3.2(playwright@1.62.0)(three@0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3)) '@zip.js/zip.js': 2.8.34 playwright: 1.62.0 sharp: 0.35.4(@types/node@20.19.43) - three: 0.185.1(patch_hash=00ce98ec952e72dff1f8f10e10ecc691f27365e9ae7a7482becdad1614181437) + three: 0.185.1(patch_hash=d386666194a0af4523abdee104516dcdeb2d682808b9cbfb6ed3b3ebf1774df3) zod: 4.4.3 transitivePeerDependencies: - '@types/node' diff --git a/scripts/__tests__/budgets.spec.ts b/scripts/__tests__/budgets.spec.ts index 42688dfa7e..0b9c5fe046 100644 --- a/scripts/__tests__/budgets.spec.ts +++ b/scripts/__tests__/budgets.spec.ts @@ -1,4 +1,5 @@ import { execFile } from "node:child_process"; +import { randomBytes } from "node:crypto"; import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; import path from "node:path"; import { promisify } from "node:util"; @@ -13,6 +14,7 @@ import { enforceBudgets, measureNativeCensusAreas, nativeCensusDrift, + terrainTarballErrors, } from "../check-budgets"; const temporaryRoots: string[] = []; @@ -748,3 +750,23 @@ describe("budget gate", () => { expect(budgetErrors(report)).toEqual([]); }); }); + +it("caps the compressed terrain tarball at 2 MB and requires a build", async () => { + const root = await fixtureRoot(); + const terrain = path.join(root, "packages/terrain"); + await mkdir(terrain, { recursive: true }); + await writeFile( + path.join(terrain, "package.json"), + JSON.stringify({ name: "@threenative/terrain", version: "0.1.0", files: ["dist"] }), + ); + expect(await terrainTarballErrors(root)).toEqual([ + "terrain tarball budget: build @threenative/terrain first", + ]); + await mkdir(path.join(terrain, "dist")); + await writeFile(path.join(terrain, "dist/kit.js"), "export {};\n"); + expect(await terrainTarballErrors(root)).toEqual([]); + await writeFile(path.join(terrain, "dist/large.bin"), randomBytes(2_000_000)); + expect(await terrainTarballErrors(root)).toEqual([ + expect.stringMatching(/terrain packed tarball .* must be < 2000000 B/u), + ]); +}); diff --git a/scripts/__tests__/ci-needs.spec.ts b/scripts/__tests__/ci-needs.spec.ts index 7e324d6459..e23a15a934 100644 --- a/scripts/__tests__/ci-needs.spec.ts +++ b/scripts/__tests__/ci-needs.spec.ts @@ -1,8 +1,9 @@ import { spawnSync } from "node:child_process"; -import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { readFile } from "node:fs/promises"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import { integrationEvidence } from "../../test-support/ci-integration-fixture.js"; import { makeTempDirSync } from "../../test-support/temp-dir.js"; import { formatJobTimings, formatRunSummary, summaryRows } from "../ci-run-summary.js"; import { ciJobGraph, ciNeedsFindings, declaredNeeds, jobSections } from "../ci-workflow.js"; @@ -415,6 +416,7 @@ function boardLegs(): string[] { "test-native", "test-browser", "test-playtest", + "strata", "golden-path-template (starter)", "template-nonvisual (rain, 1/1)", "golden-path", @@ -891,19 +893,8 @@ describe("PRD-481 the runner class is compared per job, because every board is m }); }); -/** The verdict gate on a narrowed plan, with every job's own result supplied by the caller. */ -function verifyNarrowedPlan( - fixture: IReuseFixture, - plan: Record, - results: Record, -) { - const jobs = plan.jobs as Record; - const needs = { - scope: { result: "success", outputs: { plan: JSON.stringify(plan) } }, - ...Object.fromEntries( - Object.entries(jobs).map(([name]) => [name, { result: results[name] ?? "skipped" }]), - ), - }; +/** The verdict gate on a non-reuse plan, with every job's own result supplied by the caller. */ +function verifyNeedsVerdict(fixture: IReuseFixture, needs: Record) { return spawnSync(process.execPath, [path.join(repo, "scripts/ci-required.mjs")], { cwd: fixture.root, encoding: "utf8", @@ -922,6 +913,22 @@ function verifyNarrowedPlan( }); } +/** The verdict gate on a non-reuse plan, with every job's own result supplied by the caller. */ +function verifyNarrowedPlan( + fixture: IReuseFixture, + plan: Record, + results: Record, +) { + const jobs = plan.jobs as Record; + const needs = { + scope: { result: "success", outputs: { plan: JSON.stringify(plan) } }, + ...Object.fromEntries( + Object.entries(jobs).map(([name]) => [name, { result: results[name] ?? "skipped" }]), + ), + }; + return verifyNeedsVerdict(fixture, needs); +} + describe("a develop push qualifies its tree and still warms the caches every pull request reads", () => { // GitHub scopes a pull request's cache to `refs/pull/N/merge`, which no other pull request can // read. Only a run on the base branch publishes something every pull request into develop reads, @@ -1026,6 +1033,86 @@ describe("a develop push qualifies its tree and still warms the caches every pul expect(red.status).toBe(1); expect(red.stderr).toContain("CI_REQUIRED_JOB_NOT_SUCCESS: test-native"); }); + + it("enforces selected strata in ci-required verdict: rejects missing, skipped, failed, and unmapped, and accepts success", () => { + const planResult = spawnSync( + process.execPath, + ["scripts/ci-change-scope.mjs", "--event", "push", "--format", "json"], + { cwd: repo, encoding: "utf8" }, + ); + expect(planResult.status, planResult.stderr).toBe(0); + const plan = JSON.parse(planResult.stdout) as Record; + const jobs = plan.jobs as Record; + expect(jobs.strata?.required).toBe(true); + + function runWithNeeds(targetPlan: Record, needs: Record) { + const directory = makeTempDirSync("ci-strata-verdict-"); + try { + const evidence = integrationEvidence( + repo, + directory, + targetPlan as unknown as Parameters[2], + ).env; + return spawnSync(process.execPath, ["scripts/ci-required.mjs"], { + cwd: repo, + encoding: "utf8", + env: { + ...Object.fromEntries( + Object.entries(process.env).filter(([key]) => !key.startsWith("TN_CI_")), + ), + ...evidence, + TN_CI_NEEDS: JSON.stringify(needs), + }, + }); + } finally { + rmSync(directory, { recursive: true, force: true }); + } + } + + const baseNeeds: Record = { + scope: { result: "success", outputs: { plan: JSON.stringify(plan) } }, + ...Object.fromEntries( + Object.entries(jobs).map(([name, job]) => [ + name, + { result: job.required ? "success" : "skipped" }, + ]), + ), + }; + + // 1. Success when strata succeeds + const successResult = runWithNeeds(plan, baseNeeds); + expect(successResult.status, successResult.stdout + successResult.stderr).toBe(0); + expect(successResult.stdout).toContain("strata: required (success)"); + + // 2. Rejected when strata is missing from needs + const missingNeeds = Object.fromEntries( + Object.entries(baseNeeds).filter(([name]) => name !== "strata"), + ); + const missingResult = runWithNeeds(plan, missingNeeds); + expect(missingResult.status).toBe(1); + expect(missingResult.stderr).toContain("CI_REQUIRED_JOB_NOT_SUCCESS: strata (missing)"); + + // 3. Rejected when strata skipped + const skippedNeeds = { ...baseNeeds, strata: { result: "skipped" } }; + const skippedResult = runWithNeeds(plan, skippedNeeds); + expect(skippedResult.status).toBe(1); + expect(skippedResult.stderr).toContain("CI_REQUIRED_JOB_NOT_SUCCESS: strata (skipped)"); + + // 4. Rejected when strata failed + const failedNeeds = { ...baseNeeds, strata: { result: "failure" } }; + const failedResult = runWithNeeds(plan, failedNeeds); + expect(failedResult.status).toBe(1); + expect(failedResult.stderr).toContain("CI_REQUIRED_JOB_NOT_SUCCESS: strata (failure)"); + + // 5. Rejected when strata is in needs but unmapped in plan.jobs + const unmappedNeeds = { + ...baseNeeds, + "strata-unmapped": { result: "success" }, + }; + const unmappedResult = runWithNeeds(plan, unmappedNeeds); + expect(unmappedResult.status).toBe(1); + expect(unmappedResult.stderr).toContain("CI_REQUIRED_UNMAPPED_JOB: strata-unmapped"); + }); }); describe("unproven matrix reuse runs the normal board", () => { diff --git a/scripts/__tests__/ci-structure.spec.ts b/scripts/__tests__/ci-structure.spec.ts index 7e5cf38589..eec92339b7 100644 --- a/scripts/__tests__/ci-structure.spec.ts +++ b/scripts/__tests__/ci-structure.spec.ts @@ -697,6 +697,25 @@ function classifyScope( } describe("CI pipeline structure", () => { + it("checks generated Three.js patches before publishing even cached workspace builds", async () => { + const ci = await readFile(path.join(repo, ".github/workflows/ci.yml"), "utf8"); + const build = requiredJob(ci, "build-artifacts"); + const step = "Verify the generated Three.js patch has not drifted"; + expect(build).toContain(`- name: ${step}`); + expect(build.indexOf(step)).toBeLessThan( + build.indexOf("uses: ./.github/actions/workspace-dist"), + ); + expect(build).toContain("needs.scope.outputs.selection == 'warm'"); + const check = build.slice( + build.indexOf(` - name: ${step}`), + build.indexOf(" - uses: ./.github/actions/workspace-dist"), + ); + expect(check).toContain("shell: bash"); + expect(check).toContain("run: time pnpm --filter @threenative/core patch:generate --check"); + expect(check).not.toMatch(/continue-on-error:|if:|\|\||--archive/u); + expect(ci.match(/patch:generate --check/gu)).toHaveLength(1); + }); + it("uses one fail-closed scope decision before expensive CI and native jobs", async () => { const ci = await readFile(path.join(repo, ".github/workflows/ci.yml"), "utf8"); const scope = requiredJob(ci, "scope"); @@ -3587,6 +3606,77 @@ describe("PRD-373 selective feature verification", () => { } }); + describe("strata terrain CI selection", () => { + it.each([ + "packages/terrain/src/index.ts", + "examples/strata-terrain-preview/src/game.ts", + "packages/core/src/world.ts", + "packages/core/src/world-cells.ts", + "packages/core/__tests__/world-cells-terrain.spec.ts", + ])("selects strata job when %s changes on develop PR", async (path) => { + const fixture = await scopeFixture(); + try { + const head = await commitScopeChange( + fixture, + path, + "export const terrainTouch = true;\n", + "terrain change", + ); + const plan = classifyScope(fixture.root, fixture.base, head, ["--target", "develop"]); + expect(plan.selection).toBe("full"); + const jobs = plan.jobs as Record; + expect(jobs.strata?.required).toBe(true); + } finally { + await removeFixture(fixture.root); + } + }); + + it("skips strata job for an unrelated template change on develop PR", async () => { + const fixture = await scopeFixture(); + try { + const head = await commitScopeChange( + fixture, + "packages/create-threenative/templates/rain/src/game.ts", + "export const rainTouch = true;\n", + "unrelated template", + ); + const plan = classifyScope(fixture.root, fixture.base, head, ["--target", "develop"]); + const jobs = plan.jobs as Record; + expect(jobs.strata?.required).toBe(false); + } finally { + await removeFixture(fixture.root); + } + }); + + it("runs every strata proof lane, gated by the scope plan", async () => { + const ci = await readFile(path.join(repo, ".github/workflows/ci.yml"), "utf8"); + const strata = requiredJob(ci, "strata"); + expect(strata).toContain("fromJSON(needs.scope.outputs.plan).jobs.strata.required"); + for (const lane of ["web", "editor", "authored", "custom", "export"]) { + expect(strata).toContain(`test:terrain:${lane}`); + } + expect(strata).toContain("test:consumer"); + }); + + it("requires strata job in full qualification mode", async () => { + const fixture = await scopeFixture(); + try { + const head = await commitScopeChange( + fixture, + "packages/create-threenative/templates/rain/src/game.ts", + "export const rainTouch = true;\n", + "unrelated template", + ); + const plan = classifyScope(fixture.root, fixture.base, head, ["--target", "main"]); + expect(plan.qualification).toBe(true); + const jobs = plan.jobs as Record; + expect(jobs.strata?.required).toBe(true); + } finally { + await removeFixture(fixture.root); + } + }); + }); + it("a fresh install cannot stop on the interactive node_modules purge prompt", () => { // A checkout whose node_modules was not created by this pnpm makes `pnpm install` ask before // purging it. Agents run without a TTY, so the prompt hangs them until `CI=true` is added by @@ -3867,6 +3957,7 @@ const CI_SKIPPED_JOBS = [ "golden-path-template", "native-platforms", "performance-contracts", + "strata", "template-nonvisual", "test", "test-browser", @@ -3918,10 +4009,13 @@ describe("a CI-configuration-only pull request", () => { expect(Object.values(jobs).filter((job) => job.required).length).toBeGreaterThan(6); // Every gate the `ci` selection skipped comes back. `native-platforms` is the one a clean // develop diff legitimately waives, and this diff does not touch a native path. - for (const name of CI_SKIPPED_JOBS.filter((job) => job !== "native-platforms")) { + for (const name of CI_SKIPPED_JOBS.filter( + (job) => job !== "native-platforms" && job !== "strata", + )) { expect(jobs[name]?.required, name).toBe(true); } expect(jobs["native-platforms"]?.required).toBe(true); + expect(jobs.strata?.required).toBe(false); } finally { await removeFixture(fixture.root); } diff --git a/scripts/__tests__/ci-template-selection.spec.ts b/scripts/__tests__/ci-template-selection.spec.ts index f153a5c387..6d7cf6c486 100644 --- a/scripts/__tests__/ci-template-selection.spec.ts +++ b/scripts/__tests__/ci-template-selection.spec.ts @@ -95,6 +95,9 @@ describe("impact-driven template coverage", () => { "packages/create-threenative/src/scaffold.ts", "scripts/stamp-template-render.ts", "packages/core/src/index.ts", + "packages/core/patches/three@0.185.1.patch", + "packages/core/patches/three@0.185.1-prd269-upgrade.patch", + "packages/core/scripts/generate-three-patch.mjs", "packages/create-threenative/templates/shooter-lookalike/src/main.ts", "unknown/meaningful.ts", ])("retains full fanout for shared or unknown %s", (file) => { @@ -117,7 +120,9 @@ describe("impact-driven template coverage", () => { ? TEMPLATE_NAMES : ["starter"], ); - if (file.startsWith("packages/core/")) expect(plan.jobs["test-native"].required).toBe(true); + expect(plan.jobs["build-artifacts"].required).toBe(true); + if (file.startsWith("packages/core/src/")) + expect(plan.jobs["test-native"].required).toBe(true); } finally { rmSync(f.root, { recursive: true, force: true }); } diff --git a/scripts/__tests__/mcp-tool-schemas.spec.ts b/scripts/__tests__/mcp-tool-schemas.spec.ts index 2be2c5c38e..caec67f8ba 100644 --- a/scripts/__tests__/mcp-tool-schemas.spec.ts +++ b/scripts/__tests__/mcp-tool-schemas.spec.ts @@ -3,6 +3,7 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; import { TOOL_DEFINITIONS as BLENDER_TOOLS } from "../../packages/blender-mcp/src/index.js"; import { toolDefinitions as engineTools } from "../../packages/engine-mcp/src/index.js"; +import { TERRAIN_TOOLS } from "../../packages/terrain/src/mcp/tools.js"; /** * The recorded MCP surfaces are what the docs site publishes, so their arguments must be the ones the @@ -15,6 +16,7 @@ import { toolDefinitions as engineTools } from "../../packages/engine-mcp/src/in const SURFACES = [ { file: "packages/create-threenative/engine-mcp-tools.json", tools: engineTools() }, { file: "packages/create-threenative/blender-mcp-tools.json", tools: BLENDER_TOOLS }, + { file: "packages/create-threenative/terrain-mcp-tools.json", tools: TERRAIN_TOOLS }, ] as const; const SNAPSHOT_DIR = "packages/create-threenative"; diff --git a/scripts/__tests__/prepare-release.spec.ts b/scripts/__tests__/prepare-release.spec.ts index 0a0219a341..550dd1d9e1 100644 --- a/scripts/__tests__/prepare-release.spec.ts +++ b/scripts/__tests__/prepare-release.spec.ts @@ -3,7 +3,13 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; import { makeTempDir } from "../../test-support/temp-dir.js"; import type { RegistryLookup } from "../check-publish-state.js"; -import { assertOneZeroGatesClosed, nextPatch, selectReleaseVersions } from "../prepare-release.js"; +import { + assertOneZeroGatesClosed, + nextPatch, + selectReleaseVersions, + syncPeerRanges, + syncSourceVersions, +} from "../prepare-release.js"; describe("release preparation", () => { it("increments only the patch component", () => { @@ -105,3 +111,90 @@ describe("the 1.0.0 refusal", () => { expect(() => assertOneZeroGatesClosed(root, [gate])).toThrow(/TN_RELEASE_1_0_0_GATE_EMPTY/u); }); }); + +/** + * PRD-589: the terrain generator literal and its peer ranges travel with the release cohort. Each + * fixture copies the shipped manifests into a temporary repo, so the sync never writes the checkout. + */ +describe("terrain release metadata", () => { + const repoFile = (file: string) => path.resolve(import.meta.dirname, "../..", file); + const terrainManifest = "packages/terrain/package.json"; + const terrainIo = "packages/terrain/src/core/io.ts"; + const peerFiles = [ + "packages/core/package.json", + "packages/physics/package.json", + "packages/ui/package.json", + "packages/create-threenative/package.json", + terrainManifest, + ]; + const sourceFiles = [ + "packages/core/src/index.ts", + "packages/create-threenative/src/doctor.ts", + "packages/engine-mcp/src/index.ts", + "packages/engine-mcp/__tests__/server.spec.ts", + "packages/blender-mcp/src/index.ts", + "packages/core/mcp/servers.mjs", + "packages/create-threenative/engine-mcp-tools.json", + terrainIo, + ]; + const cohort = new Map([ + ["@threenative/core", "0.3.9"], + ["@threenative/playtest", "0.3.9"], + ["@threenative/assets", "0.3.9"], + ["threenative-engine-mcp", "0.3.9"], + ["threenative-blender-mcp", "0.3.9"], + ["@threenative/terrain", "0.1.1"], + ]); + + async function fixture(files: readonly string[]): Promise { + const root = await makeTempDir("threenative-terrain-release-"); + for (const file of files) { + fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true }); + fs.copyFileSync(repoFile(file), path.join(root, file)); + } + return root; + } + + it("syncs shipped internal terrain peers and preserves external peers", async () => { + const root = await fixture(peerFiles); + const before = JSON.parse(fs.readFileSync(path.join(root, terrainManifest), "utf8")); + syncPeerRanges(root, new Map([...cohort, ["@threenative/physics", "0.3.9"]])); + expect(JSON.parse(fs.readFileSync(path.join(root, terrainManifest), "utf8"))).toEqual({ + ...before, + peerDependencies: { + ...before.peerDependencies, + "@threenative/core": ">=0.3.9 <0.4.0", + "@threenative/physics": ">=0.3.9 <0.4.0", + }, + }); + }); + + it("syncs a terrain peer that names an internal cohort package", async () => { + const root = await fixture(peerFiles); + const peers = { "@threenative/core": ">=0.3.4 <0.4.0", three: ">=0.185.0 <0.186.0" }; + fs.writeFileSync(path.join(root, terrainManifest), JSON.stringify({ peerDependencies: peers })); + syncPeerRanges(root, cohort); + const synced = JSON.parse(fs.readFileSync(path.join(root, terrainManifest), "utf8")); + expect(synced.peerDependencies).toEqual({ + "@threenative/core": ">=0.3.9 <0.4.0", + three: ">=0.185.0 <0.186.0", + }); + }); + + it("bumps the terrain generator literal to the terrain cohort version", async () => { + const root = await fixture(sourceFiles); + syncSourceVersions(root, cohort); + expect(fs.readFileSync(path.join(root, terrainIo), "utf8")).toContain( + 'generator: "@threenative/terrain 0.1.1"', + ); + }); + + it("fails closed when the terrain cohort version is missing", async () => { + const root = await fixture(sourceFiles); + const withoutTerrain = new Map(cohort); + withoutTerrain.delete("@threenative/terrain"); + expect(() => syncSourceVersions(root, withoutTerrain)).toThrow( + /TN_RELEASE_COHORT_INCOMPLETE: @threenative\/terrain/u, + ); + }); +}); diff --git a/scripts/__tests__/release.spec.ts b/scripts/__tests__/release.spec.ts index d27bf4a37c..8488fd06a0 100644 --- a/scripts/__tests__/release.spec.ts +++ b/scripts/__tests__/release.spec.ts @@ -119,6 +119,7 @@ describe("pnpm release ordering", () => { "threenative-sculpt", "threenative-engine", "threenative-blender", + "threenative-terrain", ]); expect(cohort.templatePins).toEqual([]); expect(cohort.order).toContain("create-threenative"); diff --git a/scripts/__tests__/strata-script-census.spec.ts b/scripts/__tests__/strata-script-census.spec.ts new file mode 100644 index 0000000000..5206e2d2ac --- /dev/null +++ b/scripts/__tests__/strata-script-census.spec.ts @@ -0,0 +1,38 @@ +import { readFileSync, readdirSync } from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("strata-terrain-preview script and playtest census", () => { + it("ensures every file under scripts/ and playtests/ is documented in AGENTS.md", () => { + const exampleRoot = path.resolve(__dirname, "../../examples/strata-terrain-preview"); + const scriptsDir = path.join(exampleRoot, "scripts"); + const playtestsDir = path.join(exampleRoot, "playtests"); + + function collectFiles(dir: string, relPrefix: string): string[] { + const entries = readdirSync(dir, { withFileTypes: true }); + let out: string[] = []; + for (const entry of entries) { + const rel = path.join(relPrefix, entry.name); + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + out = out.concat(collectFiles(full, rel)); + } else if (entry.isFile()) { + out.push(rel); + } + } + return out; + } + + const scriptFiles = collectFiles(scriptsDir, "scripts"); + const playtestFiles = collectFiles(playtestsDir, "playtests"); + const allFiles = [...scriptFiles, ...playtestFiles].sort(); + + expect(allFiles.length).toBeGreaterThan(0); + + const agents = readFileSync(path.join(exampleRoot, "AGENTS.md"), "utf8"); + const documented = new Set(Array.from(agents.matchAll(/`([^`]+)`/g), (m) => m[1])); + + const undocumented = allFiles.filter((file) => !documented.has(file)); + expect(undocumented).toEqual([]); + }); +}); diff --git a/scripts/__tests__/temp-dir-guard.spec.ts b/scripts/__tests__/temp-dir-guard.spec.ts index 4fcb549288..eac9663f03 100644 --- a/scripts/__tests__/temp-dir-guard.spec.ts +++ b/scripts/__tests__/temp-dir-guard.spec.ts @@ -6,6 +6,10 @@ const repositoryRoot = path.resolve(import.meta.dirname, "../.."); const tempCreatorPattern = /\bmkdtemp(?:Sync)?\s*\(/u; const allowedProductionCreators = new Map([ + [ + "packages/terrain/src/kit-assets.ts", + "The starter-asset archive extracts into a sibling scratch directory renamed into the cache and removed in finally.", + ], [ "packages/assets/src/watch.ts", "The dev watcher stages each changed input through a scratch project removed in finally.", @@ -47,6 +51,7 @@ const allowedProductionCreators = new Map([ "The host-config sync round-trips each template through a scratch copy removed in finally.", ], ["scripts/template-baseline.ts", "The production baseline gate removes its root in finally."], + ["scripts/verify-forest-playtest.ts", "The forest proof gate removes its root in finally."], ["scripts/verify-golden-path.ts", "The production golden-path gate removes roots in finally."], ["scripts/verify-one-template.ts", "The production template gate owns its cleanup."], [ diff --git a/scripts/__tests__/vegetation-not-owned.spec.ts b/scripts/__tests__/vegetation-not-owned.spec.ts index 097b0013f2..164bc459c7 100644 --- a/scripts/__tests__/vegetation-not-owned.spec.ts +++ b/scripts/__tests__/vegetation-not-owned.spec.ts @@ -6,7 +6,7 @@ const queries = [ "foliage vegetation trees wind sway", "procedural trees", "trees swaying in the wind", - "generate a forest of trees", + "grow procedural tree meshes", ]; describe("procedural vegetation guidance", () => { diff --git a/scripts/__tests__/verify-forest-playtest.spec.ts b/scripts/__tests__/verify-forest-playtest.spec.ts new file mode 100644 index 0000000000..b6ca9c4ecf --- /dev/null +++ b/scripts/__tests__/verify-forest-playtest.spec.ts @@ -0,0 +1,138 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { describe, expect, it, vi } from "vitest"; +import { insertWorldLoad, worldLoadLines } from "../verify-forest-playtest.js"; + +const STARTER_PLAY = path.resolve( + "packages/create-threenative/templates/starter/src/scenes/Play.ts", +); +const reply = (text: string, isError = false) => + JSON.stringify({ id: 2, jsonrpc: "2.0", result: { content: [{ text, type: "text" }], isError } }); +const readStarter = () => readFileSync(STARTER_PLAY, "utf8"); +const SPAWN_X = "Number.isFinite(state.playerX) ? state.playerX : Play.initialState.playerX"; +// The forest snippet's shape: its import, its world, and the terrain field `Play` must keep. +const PHYSICAL_LOAD = [ + 'import { Heightfield as TerrainHeightfield } from "@threenative/core/world";', + 'const world = await ctx.assets.model("worlds/forest/world.glb");', + "const terrainField = TerrainHeightfield.fromSampler({ columns: 257 });", + "ctx.scene.add(world.scene);", + "const loadCells = (follow: TerrainAnchor) => { terrainFollow = follow; };", +]; + +describe("forest playtest setup", () => { + it("keeps the code lines of the tool's load snippet and drops its comment", () => { + const load = [ + 'const world = await ctx.assets.model("worlds/forest/world.glb");', + "ctx.scene.add(world.scene);", + "// recipe: x", + ].join("\n"); + expect(worldLoadLines(reply(JSON.stringify({ load })))).toEqual([ + 'const world = await ctx.assets.model("worlds/forest/world.glb");', + "ctx.scene.add(world.scene);", + ]); + }); + + it("fails closed on a tool error or a reply without a snippet", () => { + expect(() => worldLoadLines(reply("boom", true))).toThrow("TN_FOREST_TOOL_FAILED"); + expect(() => worldLoadLines(reply("{}"))).toThrow("TN_FOREST_TOOL_REPLY"); + }); + + it("adds the lines at the start of the real starter's Play.load, and only there", () => { + const patched = insertWorldLoad(readStarter(), PHYSICAL_LOAD); + expect(patched).toMatch( + /override async load\(ctx: GameCtx\): Promise \{\n {4}const world = await ctx\.assets\.model\(/u, + ); + expect(patched.split("const world = await")).toHaveLength(2); + expect(patched.startsWith(`${PHYSICAL_LOAD[0]}\n`)).toBe(true); + expect(() => insertWorldLoad("class X {}", PHYSICAL_LOAD)).toThrow("TN_FOREST_ANCHOR"); + expect(() => insertWorldLoad("x", [])).toThrow("TN_FOREST_LOAD_EMPTY"); + }); + + it("keeps the terrain field on Play, set during load, before enter reads it", () => { + const patched = insertWorldLoad(readStarter(), PHYSICAL_LOAD); + const declaredAt = patched.indexOf("const terrainField = "); + const retainedAt = patched.indexOf(" this.#terrainField = terrainField;\n"); + expect(patched).toContain(" #terrainField: TerrainHeightfield | undefined;\n"); + expect(patched).toContain( + " this.#sky === undefined ||\n this.#terrainField === undefined ||\n this.#loadCells === undefined\n", + ); + expect(declaredAt).toBeGreaterThan(patched.indexOf("override async load(")); + expect(retainedAt).toBeGreaterThan(declaredAt); + expect(retainedAt).toBeLessThan(patched.indexOf("override enter(")); + }); + + it("fails closed on a missing or duplicate anchor, or a snippet without terrainField", () => { + const source = readStarter(); + const cls = "export class Play extends Scene {\n"; + const spawn = ` x: ${SPAWN_X},\n y: PLAYER_STAND_Y,\n`; + for (const broken of [ + source.replace(cls, ""), + source.replace(cls, cls + cls), + source.replace(spawn, ""), + source.replace(spawn, spawn + spawn), + ]) { + expect(() => insertWorldLoad(broken, PHYSICAL_LOAD)).toThrow("TN_FOREST_ANCHOR"); + } + expect(() => insertWorldLoad(source, ["ctx.scene.add(a);"])).toThrow("TN_FOREST_TERRAIN_FIELD"); + }); + + it("binds streaming and regional shadows after lighting and before material setup", () => { + const patched = insertWorldLoad(readStarter(), PHYSICAL_LOAD); + expect(patched).toContain(" this.#loadCells = loadCells;"); + expect(patched).toContain( + ' #loadCells: ((follow: TerrainAnchor, invalidate?: NonNullable["invalidate"]) => void) | undefined;', + ); + expect(patched).toContain(" this.#loadCells === undefined"); + const bind = patched.indexOf(" this.#loadCells(player.mesh, (region) =>"); + expect(bind).toBeGreaterThan(patched.indexOf("new Player(ctx")); + expect(bind).toBeGreaterThan(patched.indexOf("this.#disposeLighting = disposeLighting;")); + expect(bind).toBeLessThan(patched.indexOf(" const materialEnvironment = {")); + expect(patched).toContain('import { VirtualShadowNode } from "@threenative/core";'); + expect(patched).toContain("const { key, shadows, dispose: disposeLighting } = setupLighting("); + expect(patched).toContain("Math.hypot(this.#terrainField.width, this.#terrainField.depth)"); + expect(patched).toContain( + "[terrainShadowExtent / 16, terrainShadowExtent / 4, terrainShadowExtent].filter((extent) => extent > 14)", + ); + expect(patched).toContain( + "region === undefined ? shadows?.invalidateAll() : shadows?.invalidateRegion(region)", + ); + expect(() => insertWorldLoad(readStarter(), PHYSICAL_LOAD.slice(0, -1))).toThrow( + "TN_FOREST_LOAD_CELLS", + ); + }); + + it("executes the emitted measured windows and optional regional invalidation", () => { + const patched = insertWorldLoad(readStarter(), PHYSICAL_LOAD); + const span = patched.match(/const terrainShadowExtent = (.+);/u)?.[1]; + const windows = patched.match( + /(\[terrainShadowExtent.+\]\.filter\(\(extent\) => extent > 14\))/u, + )?.[1]; + const callback = patched.match( + /this\.#loadCells\(player\.mesh, \(region\) =>\s*([^,]+),/u, + )?.[1]; + expect(span && windows && callback).toBeTruthy(); + const measure = new Function( + "field", + `const terrainShadowExtent = ${span?.replaceAll("this.#terrainField", "field")}; return ${windows};`, + ); + const diagonal = Math.hypot(512, 512); + expect(measure({ width: 512, depth: 512 })).toEqual([diagonal / 16, diagonal / 4, diagonal]); + expect(measure({ width: 3, depth: 4 })).toEqual([]); + const invalidate = new Function("shadows", "region", `return ${callback};`); + const shadows = { invalidateAll: vi.fn(), invalidateRegion: vi.fn() }; + const region = { min: { x: 0, y: 0, z: 0 }, max: { x: 2, y: 3, z: 4 } }; + invalidate(shadows, region); + invalidate(shadows, undefined); + invalidate(undefined, region); + expect(shadows.invalidateRegion).toHaveBeenCalledExactlyOnceWith(region); + expect(shadows.invalidateAll).toHaveBeenCalledOnce(); + expect(patched).toContain("ctx.scene.add(key.target);"); + }); + + it("spawns the player on the measured terrain height at the same finite x", () => { + const patched = insertWorldLoad(readStarter(), PHYSICAL_LOAD); + expect(patched).toContain( + ` x: ${SPAWN_X},\n y: this.#terrainField.heightAt(${SPAWN_X}, 0) + PLAYER_STAND_Y,\n`, + ); + }); +}); diff --git a/scripts/__tests__/verify-golden-path.spec.ts b/scripts/__tests__/verify-golden-path.spec.ts index 9321ee3ac6..2b741a7e40 100644 --- a/scripts/__tests__/verify-golden-path.spec.ts +++ b/scripts/__tests__/verify-golden-path.spec.ts @@ -148,11 +148,16 @@ describe("golden path matrix", () => { try { const cli = path.join(root, "create-threenative-0.1.0.tgz"); const core = path.join(root, "threenative-core-0.1.0.tgz"); - const script = await writeScaffoldScript(root, "starter", cli, ["--core-package", core]); + const terrain = path.join(root, "threenative-terrain-0.1.0.tgz"); + const script = await writeScaffoldScript(root, "starter", cli, ["--core-package", core], { + localTarballs: { "@threenative/terrain": terrain }, + }); expect(script).toBe(path.join(root, "scaffold.sh")); + expect(await readFile(path.join(root, "scaffold-pnpmfile.cjs"), "utf8")).toContain(terrain); expect((await stat(script)).mode & 0o111).not.toBe(0); const source = await readFile(script, "utf8"); expect(source).toContain("pnpm dlx"); + expect(source).toContain("--config.pnpmfile="); expect(source).toContain('"${1:-game}"'); expect(source).toContain(cli); expect(source).toContain(core); @@ -234,7 +239,7 @@ describe("golden path matrix", () => { }), ); const fakePnpm = path.join(bin, "pnpm"); - await writeFile(fakePnpm, '#!/bin/sh\ntest "$1" = dlx\n'); + await writeFile(fakePnpm, '#!/bin/sh\ntest "$1" = dlx && test -f "$npm_config_pnpmfile"\n'); await chmod(fakePnpm, 0o755); const actions = { @@ -265,6 +270,54 @@ describe("golden path matrix", () => { } }); + // The scaffold's own install carries the pnpmfile through `npm_config_pnpmfile`, but the golden + // path's separate `pnpm install` — and a human's corrective `pnpm install` — do not. That second + // install lost the mapping and asked the registry for the packed edge @threenative/assets -> + // @threenative/terrain, which is not published, so it 404'd after the first install had passed. + it("keeps the local tarball mapping on the independent install path", async () => { + const root = await makeTempDir("threenative-golden-path-project-pnpmfile-"); + const originalPath = process.env.PATH; + try { + const templatesRoot = path.join(root, "templates"); + const template = path.join(templatesRoot, "starter"); + const project = path.join(root, "game"); + const cli = path.join(root, "create-threenative-0.1.0.tgz"); + const terrain = path.join(root, "threenative-terrain-0.1.0.tgz"); + const bin = path.join(root, "bin"); + await mkdir(template, { recursive: true }); + await mkdir(bin, { recursive: true }); + await writeFile( + path.join(template, "package.json"), + JSON.stringify({ + name: "starter", + dependencies: { "create-threenative": "0.1.0", vite: "8.2.0" }, + }), + ); + // A scaffold creates the project directory; the fake stands in for that side effect. + const fakePnpm = path.join(bin, "pnpm"); + await writeFile(fakePnpm, '#!/bin/sh\ntest "$1" = dlx && mkdir -p game\n'); + await chmod(fakePnpm, 0o755); + process.env.PATH = [bin, originalPath] + .filter((value): value is string => value !== undefined) + .join(path.delimiter); + + await scaffold( + "starter", + project, + { "@threenative/terrain": terrain, "create-threenative": cli }, + templatesRoot, + ); + + const projectPnpmfile = path.join(project, ".pnpmfile.cjs"); + expect(existsSync(projectPnpmfile)).toBe(true); + expect(await readFile(projectPnpmfile, "utf8")).toContain(terrain); + } finally { + if (originalPath === undefined) process.env.PATH = undefined; + else process.env.PATH = originalPath; + await rm(root, { force: true, recursive: true }); + } + }); + // CI's `build` job packs the workspace once and publishes the tarballs; the golden-path job // downloads them before this script runs. Adopting that set instead of packing a second time is // the whole saving, so what matters is that it cannot adopt an incomplete or drifted one. diff --git a/scripts/__tests__/verify-registry-install.spec.ts b/scripts/__tests__/verify-registry-install.spec.ts index 9d5fb132a0..929166a7f3 100644 --- a/scripts/__tests__/verify-registry-install.spec.ts +++ b/scripts/__tests__/verify-registry-install.spec.ts @@ -164,6 +164,7 @@ function happyRunner(): CommandRunner { "threenative-blender": { command: "node", args: ["blender.mjs"] }, "threenative-engine": { command: "node", args: ["engine.mjs"] }, "threenative-sculpt": { command: "node", args: ["sculpt.mjs"] }, + "threenative-terrain": { command: "node", args: ["terrain.mjs"] }, }, }), ); @@ -323,6 +324,7 @@ function happyMcpRunner(): McpRunner { "threenative-blender": ["blender_status", "blender_convert"], "threenative-engine": ["engine_search_capabilities", "engine_capability_detail"], "threenative-sculpt": ["sculpt_grimoire"], + "threenative-terrain": ["terrain_patch"], }; const result = toolName === "asset_search_sources" @@ -338,51 +340,55 @@ function happyMcpRunner(): McpRunner { }, ], } - : toolName === "engine_search_capabilities" + : toolName === "terrain_patch" ? { - content: [ - { - text: JSON.stringify({ - guidance: "", - results: [ - { - constraints: ["requires a navigation world"], - example: "const agent = new NavigationAgent3D(world);", - importPath: "@threenative/physics/navigation", - summary: "Move an agent around obstacles.", - symbol: "NavigationAgent3D", - }, - ], - }), - }, - ], + content: [{ text: JSON.stringify({ config: {}, layers: [], version: 1 }) }], } - : toolName === "engine_capability_detail" + : toolName === "engine_search_capabilities" ? { content: [ { text: JSON.stringify({ - importPath: "@threenative/physics/navigation", - symbol: "NavigationAgent3D", + guidance: "", + results: [ + { + constraints: ["requires a navigation world"], + example: "const agent = new NavigationAgent3D(world);", + importPath: "@threenative/physics/navigation", + summary: "Move an agent around obstacles.", + symbol: "NavigationAgent3D", + }, + ], }), }, ], } - : toolName === "blender_status" + : toolName === "engine_capability_detail" ? { content: [ { text: JSON.stringify({ - available: true, - detail: "Blender 4.2", - installCommand: "install Blender", + importPath: "@threenative/physics/navigation", + symbol: "NavigationAgent3D", }), }, ], } - : { - content: [{ text: JSON.stringify({ ok: true }) }], - }; + : toolName === "blender_status" + ? { + content: [ + { + text: JSON.stringify({ + available: true, + detail: "Blender 4.2", + installCommand: "install Blender", + }), + }, + ], + } + : { + content: [{ text: JSON.stringify({ ok: true }) }], + }; const output = [ mcpMessage(1, { serverInfo: { name: serverName } }), mcpMessage(2, { tools: (tools[serverName] ?? []).map((name) => ({ name })) }), diff --git a/scripts/api-surface.json b/scripts/api-surface.json index 1730325b5b..60e4d7ac5b 100644 --- a/scripts/api-surface.json +++ b/scripts/api-surface.json @@ -7,6 +7,7 @@ "@threenative/assets#audioPass": "export function audioPass(options: IAudioPassOptions = { … }", "@threenative/assets#censusDocument": "export function censusDocument( model: string, document: Document, pageSize = 4_096, ): IContentCensus { … }", "@threenative/assets#compileAssets": "export async function compileAssets( options: IAssetCompileOptions = { … }", + "@threenative/assets#cookTerrainWorld": "export function cookTerrainWorld( options: IPreparedTerrainWorldOptions, ): Promise< { … }", "@threenative/assets#dedupeMaterials": "export function dedupeMaterials(materials: readonly IMaterialState[]): { … }", "@threenative/assets#formatAudioSizes": "export function formatAudioSizes(rows: readonly IAudioRow[]): readonly string[] { … }", "@threenative/assets#formatHealthReport": "export function formatHealthReport(report: IAssetHealthReport): readonly string[] { … }", @@ -62,6 +63,7 @@ "@threenative/core/ui-layer#publishUiState": "export function publishUiState( bridge: IUiBridge, store: IPublishableStore, options: IPublishOptions = { … }", "@threenative/core/ui-layer#sendUiIntent": "export function sendUiIntent(bridge: IUiBridge, intent: string, payload?: unknown): void { … }", "@threenative/core/ui-layer#subscribeUiState": "export function subscribeUiState(bridge: IUiBridge): IUiStateMirror { … }", + "@threenative/core/world#applyTerrainSurface": "export async function applyTerrainSurface( root: Object3D, options: ILoadTerrainSplatOptions, ): Promise { … }", "@threenative/core/world#cellPlacements": "export function cellPlacements(placements: ArrayBuffer, run: IWorldRun): Float32Array { … }", "@threenative/core/world#getWorldCapabilities": "export function getWorldCapabilities(options: IWorldCapabilitiesOptions = { … }", "@threenative/core/world#Heightfield": "export class Heightfield extends Group implements IComputeDriven { … }", @@ -408,21 +410,38 @@ "symbols": {} }, "@threenative/terrain": { - "exports": [".", "./editor", "./editor/server", "./export", "./package.json", "./three"], + "exports": [ + ".", + "./editor", + "./editor/server", + "./export", + "./kit", + "./package.json", + "./starter", + "./three" + ], "symbols": { "@threenative/terrain/editor/server#terrainEditor": "export function terrainEditor(options: { … }", "@threenative/terrain/editor/server#TerrainEditorDocument": "export class TerrainEditorDocument { … }", "@threenative/terrain/editor#checkEnvironmentAssets": "export function checkEnvironmentAssets( environment: IEnvironment, assets: readonly { … }", + "@threenative/terrain/editor#createEditorSelection": "export function createEditorSelection( { … }", + "@threenative/terrain/editor#editorPlacementCenter": "export function editorPlacementCenter(point: Vector3, pose: IPlacementOverride): Vector3 { … }", + "@threenative/terrain/editor#editorPointToClient": "export function editorPointToClient( point: Vector3, camera: Camera, box: { … }", + "@threenative/terrain/editor#editorSurfaceHeight": "export function editorSurfaceHeight( ctx: { … }", "@threenative/terrain/editor#focusCamera": "export function focusCamera( camera: ISavedCamera, request: IFocusRequest, resolve: IFocusResolver, ): IFocusOutcome { … }", "@threenative/terrain/editor#inspectImage": "export function inspectImage( bytes: Uint8Array, format: string, limits: { … }", "@threenative/terrain/editor#inspectSpatial": "export function inspectSpatial( state: ITerrainState, revision: string, query: ISpatialQuery, ): ISpatialObservation { … }", + "@threenative/terrain/editor#landformPose": "export function landformPose(layer: Layer, worldSize: number, defaultSize: number) { … }", "@threenative/terrain/editor#mountTerrainEditor": "export async function mountTerrainEditor(options: { … }", + "@threenative/terrain/editor#pickEditorPlacement": "export function pickEditorPlacement( ctx: Parameters[0], terrain: Mesh, meshes: readonly Object3D[], placements: Iterable< { … }", + "@threenative/terrain/editor#pickEditorSurface": "export function pickEditorSurface( ctx: { … }", "@threenative/terrain/editor#probeTerrain": "export function probeTerrain( state: ITerrainState, revision: string, at: readonly number[], ): IPointObservation { … }", "@threenative/terrain/editor#runCameraOperation": "export function runCameraOperation(set: ICameraSet, operation: unknown): ICameraResult { … }", "@threenative/terrain/editor#runEnvironmentOperation": "export function runEnvironmentOperation( current: IEnvironment, operation: unknown, ): IEnvironmentResult { … }", "@threenative/terrain/editor#sniff": "export function sniff( bytes: Uint8Array, ): { … }", "@threenative/terrain/editor#surfaceSpace": "export function surfaceSpace(input: string): \"srgb\" | \"linear\" { … }", "@threenative/terrain/editor#TerrainEditorController": "export class TerrainEditorController { … }", + "@threenative/terrain/editor#transformedLandform": "export function transformedLandform( layer: Layer, position: Vector3, quaternion: Quaternion, scale: Vector3, anchor: number, ): Layer { … }", "@threenative/terrain/editor#validateAsset": "export function validateAsset(input: unknown): IProjectAsset { … }", "@threenative/terrain/editor#validateAssets": "export function validateAssets(input: unknown): IProjectAsset[] { … }", "@threenative/terrain/editor#validateCamera": "export function validateCamera( input: unknown, existing: readonly ISavedCamera[] = [], ): ISavedCamera { … }", @@ -430,7 +449,11 @@ "@threenative/terrain/editor#validateEnvironment": "export function validateEnvironment(input: unknown): IEnvironment { … }", "@threenative/terrain/editor#validateSpatialReference": "export function validateSpatialReference(input: unknown): ISavedSpatialReference { … }", "@threenative/terrain/editor#validateSurfaces": "export function validateSurfaces( input: unknown, assets: readonly IProjectAsset[], ): ISurfaceMappings { … }", - "@threenative/terrain/export#exportWorldGLB": "export async function exportWorldGLB(input: IWorldGLBInput): Promise { … }", + "@threenative/terrain/export#prepareWorldGLTF": "export async function prepareWorldGLTF(input: IWorldGLBInput): Promise< { … }", + "@threenative/terrain/kit#bakeKit": "export async function bakeKit( url: string, { … }", + "@threenative/terrain/kit#starterAssetsDir": "export async function starterAssetsDir(): Promise { … }", + "@threenative/terrain/starter#addStarterWorld": "export async function addStarterWorld { … }", + "@threenative/terrain#makeExport": "export function makeExport( state: ITerrainState | null, recipe: ITerrainDocument, kind: \"project\", ): Promise;", "@threenative/terrain#sampleHeight": "sampleHeight = (grid: ISampledGrid, x: number, z: number): number => sampleGrid( grid.height, grid.resolution, grid.resolution, x / grid.size + 0.5, z / grid.size + 0.5, )", "@threenative/terrain#slopeAtIndex": "export function slopeAtIndex(grid: ISampledGrid, i: number): number { … }", "@threenative/terrain#slopeQuantile": "export function slopeQuantile(grid: ISampledGrid, quantile: number): number { … }", @@ -479,7 +502,7 @@ } }, "create-threenative": { - "exports": [".", "./package.json", "./web-brand"], + "exports": [".", "./package.json", "./terrain-kit", "./web-brand"], "symbols": {} }, "threenative-blender-mcp": { diff --git a/scripts/capture-terrain-mcp-tools.ts b/scripts/capture-terrain-mcp-tools.ts new file mode 100644 index 0000000000..3905fd87b5 --- /dev/null +++ b/scripts/capture-terrain-mcp-tools.ts @@ -0,0 +1,30 @@ +#!/usr/bin/env tsx +// Writes `packages/create-threenative/terrain-mcp-tools.json` from the tool table the terrain MCP +// server serves. The server ships its source in this repository, so `mcp-tool-schemas.spec.ts` +// compares this snapshot to that table and `verify-golden-path.ts` compares its tool names to a +// live `tools/list`. +// +// pnpm tsx scripts/capture-terrain-mcp-tools.ts +import { readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { TERRAIN_TOOLS } from "../packages/terrain/src/mcp/tools.js"; + +const REPO = path.resolve(import.meta.dirname, ".."); +const SNAPSHOT = path.join(REPO, "packages/create-threenative/terrain-mcp-tools.json"); +const manifest = JSON.parse( + readFileSync(path.join(REPO, "packages/terrain/package.json"), "utf8"), +) as { + version: string; +}; + +const snapshot = { + comment: `The terrain MCP surface a generated project gets once it installs @threenative/terrain. \`tools\` is set-equal to the tool table version ${manifest.version} serves. Regenerate with \`pnpm tsx scripts/capture-terrain-mcp-tools.ts\`.`, + version: manifest.version, + serverInfo: { name: "threenative-terrain", version: manifest.version }, + recommended: ["terrain_generate", "terrain_patch"], + tools: TERRAIN_TOOLS.map((tool) => tool.name).sort(), + inputSchemas: Object.fromEntries(TERRAIN_TOOLS.map((tool) => [tool.name, tool.inputSchema])), + descriptions: Object.fromEntries(TERRAIN_TOOLS.map((tool) => [tool.name, tool.description])), +}; +writeFileSync(SNAPSHOT, `${JSON.stringify(snapshot, null, 2)}\n`); +process.stdout.write(`terrain MCP surface: ${snapshot.tools.length} tool(s) -> ${SNAPSHOT}\n`); diff --git a/scripts/check-budgets.ts b/scripts/check-budgets.ts index c2c2dfd3fb..1897492851 100644 --- a/scripts/check-budgets.ts +++ b/scripts/check-budgets.ts @@ -460,6 +460,24 @@ export async function capabilityManifestErrors(root: string): Promise } } +export async function terrainTarballErrors(root: string): Promise { + const cwd = path.join(root, "packages", "terrain"); + if (!existsSync(path.join(cwd, "package.json"))) return []; + if (!existsSync(path.join(cwd, "dist", "kit.js"))) + return ["terrain tarball budget: build @threenative/terrain first"]; + const { stdout } = await execFileAsync( + "npm", + ["pack", "--dry-run", "--json", "--ignore-scripts"], + { cwd, maxBuffer: 8 * 1024 * 1024 }, + ); + const packed = JSON.parse(stdout) as { size: number }[]; + const size = packed[0]?.size; + if (!Number.isSafeInteger(size) || size === undefined || size <= 0) + return ["terrain tarball budget: npm pack returned no valid size"]; + console.log(`terrain packed tarball: ${size} B (limit < 2000000 B)`); + return size < 2_000_000 ? [] : [`terrain packed tarball ${size} B must be < 2000000 B`]; +} + export async function enforceBudgets(root: string): Promise { const nativeManifest = path.join(root, "packages", "runtime-native", "package.json"); const ownsNativeRuntime = existsSync(nativeManifest) @@ -470,6 +488,7 @@ export async function enforceBudgets(root: string): Promise { const report = await collectBudgets(root); const errors = [ ...budgetErrors(report), + ...(await terrainTarballErrors(root)), ...(await buildMatrixErrors(root)), ...(await capabilityManifestErrors(root)), ...(await nativeCensusErrors(root)), diff --git a/scripts/ci-change-scope.mjs b/scripts/ci-change-scope.mjs index fe74425392..a48bdb5c59 100644 --- a/scripts/ci-change-scope.mjs +++ b/scripts/ci-change-scope.mjs @@ -118,6 +118,7 @@ const FULL_JOBS = [ "test-native", "test-browser", "test-playtest", + "strata", "golden-path-template", "template-nonvisual", "golden-path", @@ -142,6 +143,23 @@ const CI_JOBS = new Set(["typecheck", "test-unit", "budgets", "build-artifacts"] // `test-native` saves `native-build-*`, `native-third-party-*` and its ccache through the cache // action's own post step. A warm run verifies nothing else, so nothing else runs. const WARM_JOBS = ["build-artifacts", "test-native"]; +// Strata terrain jobs: runs when terrain, example or core world modules change, or full qualification. +const STRATA_PATHS = [ + /^packages\/terrain\//u, + /^examples\/strata-terrain-preview\//u, + /^packages\/core\/(?:src\/world(?:-|\.ts)|src\/terrain-jobs|__tests__\/world-)/u, +]; + +export function isStrataPath(file) { + return STRATA_PATHS.some((pattern) => pattern.test(file)); +} + +function strataSelected(files, qualification) { + if (qualification) return true; + if (files.some(isStrataPath)) return true; + return false; +} + // Strength, not identity: a stronger source profile may serve a weaker requirement, never the reverse. const TARGET_RANKS = { main: 2, develop: 1, other: 0 }; const NATIVE_RANKS = { full: 2, reduced: 1, none: 0 }; @@ -184,6 +202,20 @@ export function selectionPlan( FULL_JOBS.map((name) => { // `ci` narrows the board to the gates that read the configuration and `warm` to the two cache // producers; neither widens one, so any other job is exempt exactly as it is for prose. + if (name === "strata") { + const required = full && strataSelected(files, qualification); + return [ + name, + { + required, + reason: required + ? qualification + ? `Full dependency closure: ${reason}` + : `Selected dependency closure: ${reason}` + : exemption(`Exempt: the ${selection} dependency closure does not reach ${name}`), + }, + ]; + } const required = (full && (name !== "test-native" || native)) || ((ciLane || template) && CI_JOBS.has(name)) || diff --git a/scripts/fixtures/capability-recall/budget.json b/scripts/fixtures/capability-recall/budget.json index 0c146b3c94..eedf361277 100644 --- a/scripts/fixtures/capability-recall/budget.json +++ b/scripts/fixtures/capability-recall/budget.json @@ -1,5 +1,5 @@ { - "recallAtK": 0.9080459770114943, + "recallAtK": 0.9090909090909091, "recalledRows": [ "brief.endless-runner.1", "brief.endless-runner.2", @@ -79,10 +79,11 @@ "request.drive-a-racing-kart", "request.vehicle-suspension-grip", "request.vehicle-pedals-steering", - "request.vehicle-rescue-respawn" + "request.vehicle-rescue-respawn", + "request.terrain-forest-world" ], "rejectHits": 16, - "rowCount": 87, + "rowCount": 88, "rowIds": [ "brief.endless-runner.1", "brief.endless-runner.2", @@ -170,10 +171,11 @@ "request.drive-a-racing-kart", "request.vehicle-suspension-grip", "request.vehicle-pedals-steering", - "request.vehicle-rescue-respawn" + "request.vehicle-rescue-respawn", + "request.terrain-forest-world" ], "version": 1, - "zeroResultRate": 0.05747126436781609, + "zeroResultRate": 0.056818181818181816, "unresolvedResultRate": 0, "notOwnedRows": { "request.inventory-system": "inventory", diff --git a/scripts/fixtures/capability-recall/corpus.json b/scripts/fixtures/capability-recall/corpus.json index 48dbffc12b..04d69d4004 100644 --- a/scripts/fixtures/capability-recall/corpus.json +++ b/scripts/fixtures/capability-recall/corpus.json @@ -747,6 +747,14 @@ "source": "template:racing#AGENTS.md — __PROJECT_NAME__ racing", "expect": ["VehicleBody3D"], "reject": [] + }, + { + "id": "request.terrain-forest-world", + "query": "generate a forest for my game", + "scope": "request", + "source": "template:starter#Budget real time for the look \u2014 see `node_modules/create-threenative/agent-docs/references/dream-loop.md`", + "expect": ["cookTerrainWorld"], + "reject": [] } ] } diff --git a/scripts/not-owned-capabilities.ts b/scripts/not-owned-capabilities.ts index 8bda1bb260..7fa255a310 100644 --- a/scripts/not-owned-capabilities.ts +++ b/scripts/not-owned-capabilities.ts @@ -46,7 +46,7 @@ export const NOT_OWNED_CAPABILITIES: readonly INotOwnedCapability[] = [ "procedural trees", "swaying trees", "tree foliage wind sway", - "generate a forest of trees", + "grow procedural tree meshes", ], }, { diff --git a/scripts/prepare-release.ts b/scripts/prepare-release.ts index 6ee226a507..3db1b52a1e 100644 --- a/scripts/prepare-release.ts +++ b/scripts/prepare-release.ts @@ -194,7 +194,7 @@ function requiredVersion(versions: ReadonlyMap, name: string): s return version; } -function syncPeerRanges(repo: string, versions: ReadonlyMap): number { +export function syncPeerRanges(repo: string, versions: ReadonlyMap): number { const coreVersion = requiredVersion(versions, "@threenative/core"); const playtestVersion = requiredVersion(versions, "@threenative/playtest"); const assetsVersion = requiredVersion(versions, "@threenative/assets"); @@ -220,15 +220,26 @@ function syncPeerRanges(repo: string, versions: ReadonlyMap): nu for (const [file, dependency, version] of Object.values(ranges)) { if (syncDependencyRange(file, dependency, version)) changed += 1; } + // Terrain peers are external packages, such as three and vite. + // Sync a terrain peer only when the release cohort names it. + const terrain = path.join(repo, "packages/terrain/package.json"); + const { peerDependencies = {} } = JSON.parse(readFileSync(terrain, "utf8")) as { + peerDependencies?: Record; + }; + for (const dependency of Object.keys(peerDependencies)) { + const version = versions.get(dependency); + if (version !== undefined && syncDependencyRange(terrain, dependency, version)) changed += 1; + } return changed; } -function syncSourceVersions(repo: string, versions: ReadonlyMap): number { +export function syncSourceVersions(repo: string, versions: ReadonlyMap): number { const coreVersion = versions.get("@threenative/core"); const engineVersion = versions.get("threenative-engine-mcp"); const blenderVersion = versions.get("threenative-blender-mcp"); if (coreVersion === undefined || engineVersion === undefined || blenderVersion === undefined) throw new Error("TN_RELEASE_COHORT_INCOMPLETE: required release package is missing."); + const terrainVersion = requiredVersion(versions, "@threenative/terrain"); const sourceUpdates: readonly [string, RegExp, string][] = [ [ @@ -276,6 +287,11 @@ function syncSourceVersions(repo: string, versions: ReadonlyMap) /("version": ")[^"]+(")/u, `$1${engineVersion}$2`, ], + [ + path.join(repo, "packages/terrain/src/core/io.ts"), + /(generator: "@threenative\/terrain )[^"]+(")/u, + `$1${terrainVersion}$2`, + ], ]; let changed = 0; for (const [file, pattern, replacement] of sourceUpdates) { diff --git a/scripts/verify-forest-playtest.ts b/scripts/verify-forest-playtest.ts new file mode 100644 index 0000000000..a0ceaf653f --- /dev/null +++ b/scripts/verify-forest-playtest.ts @@ -0,0 +1,229 @@ +import { spawn } from "node:child_process"; +import { cp, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createProject, templateRoot } from "../packages/create-threenative/src/index.js"; +import { packageLocalFramework } from "./visual-gate.js"; + +/** + * PRD-584 AC-4/AC-5: the stock starter, a forest produced by the real `terrain_generate` tool, the + * ordinary load the tool prints, a live-clock walk, and the playtest's own p95 frame-time bound. + * + * Usage: tsx scripts/verify-forest-playtest.ts [--target web|desktop] + * Run on a real GPU under `sh scripts/xvfb.sh`; see packages/playtest/AGENTS.md for the recipe. + */ + +const LOAD_ANCHOR = " override async load(ctx: GameCtx): Promise {\n"; +const CLASS_ANCHOR = "export class Play extends Scene {\n"; +const SPAWN_X = "Number.isFinite(state.playerX) ? state.playerX : Play.initialState.playerX"; +const SPAWN_ANCHOR = ` x: ${SPAWN_X},\n y: PLAYER_STAND_Y,\n`; +const LIGHTING_ANCHOR = ` const { key, dispose: disposeLighting } = setupLighting( + ctx.scene, + ctx.renderer.raw as Parameters[1], + isMobile(), + ); +`; +const MATERIAL_ANCHOR = " const materialEnvironment = {\n"; +const READY_ANCHOR = " this.#sky === undefined\n"; +const SCENARIO_ROOT = path.resolve(templateRoot(), "..", "template-playtests", "starter-forest"); + +/** The tool's reply is JSON in the first text block; `load` is the snippet an agent pastes. */ +export function worldLoadLines(reply: string): readonly string[] { + const result = (JSON.parse(reply) as { result?: IToolResult }).result; + const text = result?.content?.[0]?.text; + if (text === undefined) + throw new Error("TN_FOREST_TOOL_REPLY: terrain_generate returned no text."); + if (result?.isError === true) throw new Error(`TN_FOREST_TOOL_FAILED: ${text}`); + const load = (JSON.parse(text) as { load?: unknown }).load; + if (typeof load !== "string") + throw new Error("TN_FOREST_TOOL_REPLY: the result carries no load snippet."); + return load.split("\n").filter((line) => line.trim() !== "" && !line.trim().startsWith("//")); +} + +interface IToolResult { + readonly content?: readonly { readonly text?: string }[]; + readonly isError?: boolean; +} + +function anchorOnce(source: string, anchor: string, name: string): void { + if (source.split(anchor).length !== 2) + throw new Error(`TN_FOREST_ANCHOR: ${name} not found exactly once in the starter.`); +} + +/** Load the printed world and place the proof game's player on its measured terrain surface. */ +export function insertWorldLoad(source: string, lines: readonly string[]): string { + if (lines.length === 0) throw new Error("TN_FOREST_LOAD_EMPTY: the tool printed no load lines."); + anchorOnce(source, LOAD_ANCHOR, "Play.load"); + anchorOnce(source, CLASS_ANCHOR, "the Play class"); + anchorOnce(source, SPAWN_ANCHOR, "the Player spawn"); + anchorOnce(source, READY_ANCHOR, "the Play readiness check"); + anchorOnce(source, LIGHTING_ANCHOR, "the shadow lighting call"); + anchorOnce(source, MATERIAL_ANCHOR, "the streamed shadow binding"); + const imports = lines.filter((line) => line.startsWith("import ")); + const body = lines.filter((line) => !line.startsWith("import ")); + if (!body.some((line) => line.startsWith("const terrainField = "))) + throw new Error("TN_FOREST_TERRAIN_FIELD: the load snippet does not declare terrainField."); + if (!body.some((line) => line.startsWith("const loadCells = "))) + throw new Error("TN_FOREST_LOAD_CELLS: the load snippet does not declare loadCells."); + const retain = " this.#terrainField = terrainField;\n this.#loadCells = loadCells;\n"; + const loaded = source + .replace(LOAD_ANCHOR, `${LOAD_ANCHOR}${body.map((line) => ` ${line}\n`).join("")}${retain}`) + .replace( + CLASS_ANCHOR, + `${CLASS_ANCHOR} #terrainField: TerrainHeightfield | undefined;\n #loadCells: ((follow: TerrainAnchor, invalidate?: NonNullable["invalidate"]) => void) | undefined;\n`, + ) + .replace( + LIGHTING_ANCHOR, + ` const terrainShadowExtent = Math.hypot(this.#terrainField.width, this.#terrainField.depth); + const { key, shadows, dispose: disposeLighting } = setupLighting( + ctx.scene, + ctx.renderer.raw as Parameters[1], + isMobile(), + [terrainShadowExtent / 16, terrainShadowExtent / 4, terrainShadowExtent].filter((extent) => extent > 14), + VirtualShadowNode, + ); +`, + ) + .replace( + MATERIAL_ANCHOR, + ` ctx.scene.add(key.target); + this.#loadCells(player.mesh, (region) => + region === undefined ? shadows?.invalidateAll() : shadows?.invalidateRegion(region), + ); +${MATERIAL_ANCHOR}`, + ) + .replace( + READY_ANCHOR, + " this.#sky === undefined ||\n this.#terrainField === undefined ||\n this.#loadCells === undefined\n", + ) + .replace( + SPAWN_ANCHOR, + ` x: ${SPAWN_X},\n y: this.#terrainField.heightAt(${SPAWN_X}, 0) + PLAYER_STAND_Y,\n`, + ); + return `${imports.map((line) => `${line}\n`).join("")}import { VirtualShadowNode } from "@threenative/core";\n${loaded}`; +} + +function run(command: string, args: readonly string[], cwd: string, env?: NodeJS.ProcessEnv) { + return new Promise((resolve, reject) => { + const child = spawn(command, [...args], { + cwd, + env: { ...process.env, ...env }, + stdio: "inherit", + }); + child.once("error", reject); + child.once("exit", (code) => + code === 0 ? resolve() : reject(new Error(`${command} ${args.join(" ")} exited ${code}.`)), + ); + }); +} + +/** One `tools/call` over the same stdio shim an agent's `.mcp.json` launches. */ +function callTerrainGenerate(project: string): Promise { + return new Promise((resolve, reject) => { + const child = spawn("node", ["node_modules/@threenative/core/mcp/terrain.mjs"], { + cwd: project, + stdio: ["pipe", "pipe", "inherit"], + }); + let buffer = ""; + child.stdout.on("data", (chunk: Buffer) => { + buffer += String(chunk); + for (const line of buffer.split("\n")) { + if (line.includes('"id":2')) { + child.kill(); + resolve(line); + } + } + }); + child.once("error", reject); + child.once("exit", () => + reject(new Error("TN_FOREST_TOOL_EXIT: the terrain server exited before replying.")), + ); + const send = (message: object) => child.stdin.write(`${JSON.stringify(message)}\n`); + send({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }); + send({ + jsonrpc: "2.0", + id: 2, + method: "tools/call", + params: { name: "terrain_generate", arguments: { kit: "forest" } }, + }); + }); +} + +async function main(): Promise { + const target = process.argv.includes("--target") + ? process.argv[process.argv.indexOf("--target") + 1] + : "web"; + if (target !== "web" && target !== "desktop") throw new Error("--target must be web or desktop."); + const root = await mkdtemp(path.join(os.tmpdir(), "threenative-forest-playtest-")); + try { + const packageSources = await packageLocalFramework(root); + const project = path.join(root, "forest"); + await createProject({ install: true, packageSources, target: project, template: "starter" }); + const lines = worldLoadLines(await callTerrainGenerate(project)); + const play = path.join(project, "src/scenes/Play.ts"); + await writeFile(play, insertWorldLoad(await readFile(play, "utf8"), lines)); + const files = await readdir(SCENARIO_ROOT); + if (!files.includes("forest.playtest.json")) throw new Error("TN_FOREST_SCENARIO_MISSING"); + if (target === "web") { + await cp( + path.join(SCENARIO_ROOT, "forest.playtest.json"), + path.join(project, "playtests/forest.playtest.json"), + ); + await run("pnpm", ["build:web"], project); + await run( + "pnpm", + [ + "exec", + "threenative-playtest", + "--live-clock", + "--scenario", + "playtests/forest.playtest.json", + "--browser-recipe", + "webgpu", + "--headed", + "--no-screenshots", + "--server-timeout", + "120000", + "--server-command", + "pnpm exec vite preview --host 127.0.0.1 --port $PORT --strictPort", + ], + project, + ); + } else { + await cp( + path.join(SCENARIO_ROOT, "forest.native.playtest.json"), + path.join(project, "native-playtests/forest.playtest.json"), + ); + await run("pnpm", ["build:desktop"], project); + await run( + "pnpm", + [ + "exec", + "threenative-playtest", + "--live-clock", + "--scenario", + "native-playtests/forest.playtest.json", + "--target", + "desktop", + "--executable", + "dist-native/forest", + ], + project, + ); + } + } finally { + if (process.env.TN_FOREST_KEEP === "1") console.info(`TN_FOREST_KEEP root: ${root}`); + else await rm(root, { force: true, recursive: true }); + } +} + +if ( + process.argv[1] !== undefined && + path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) +) { + main().catch((error: unknown) => { + process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); + process.exitCode = 1; + }); +} diff --git a/scripts/verify-golden-path.ts b/scripts/verify-golden-path.ts index 5a213173c2..f5ffe0a48b 100644 --- a/scripts/verify-golden-path.ts +++ b/scripts/verify-golden-path.ts @@ -39,6 +39,7 @@ export const MCP_SURFACES = [ { name: "threenative-sculpt", file: "sculpt-mcp-tools.json" }, { name: "threenative-engine", file: "engine-mcp-tools.json" }, { name: "threenative-blender", file: "blender-mcp-tools.json" }, + { name: "threenative-terrain", file: "terrain-mcp-tools.json" }, ] as const; const AUTHORING_CAPABILITIES = [ @@ -937,16 +938,40 @@ export async function writeScaffoldScript( options: { readonly cliRuntimePackages?: readonly string[]; readonly ignoreInstallScripts?: boolean; + /** Packed tarballs by package name; `dlx` resolves any dependency on them locally. */ + readonly localTarballs?: Readonly>; readonly noInstall?: boolean; } = {}, ): Promise { const script = path.join(directory, "scaffold.sh"); const target = '"${1:-game}"'; + // `pnpm dlx` ignores `overrides` in every config file, but it honours a pnpmfile. Without this, + // a packed package's `workspace:*` dependency on an unpublished sibling asks the registry. + const pnpmfile = path.join(directory, "scaffold-pnpmfile.cjs"); + await writeFile( + pnpmfile, + `const local = ${JSON.stringify(options.localTarballs ?? {})}; +module.exports = { + hooks: { + readPackage(pkg) { + for (const field of ["dependencies", "optionalDependencies"]) { + for (const name of Object.keys(pkg[field] ?? {})) { + if (local[name] !== undefined) pkg[field][name] = "file:" + local[name]; + } + } + return pkg; + }, + }, +}; +`, + ); const invocation = [ ...(options.ignoreInstallScripts ? ["npm_config_ignore_scripts=true"] : []), + `npm_config_pnpmfile=${pnpmfile}`, "pnpm", "dlx", "--silent", + `--config.pnpmfile=${pnpmfile}`, "--package", cliSource, ...(options.cliRuntimePackages ?? []).flatMap((source) => ["--package", source]), @@ -988,11 +1013,14 @@ export async function scaffold( const assetSource = sources["@threenative/assets"]; await writeScaffoldScript(directory, template, cliSource, packageArgs, { ...options, + localTarballs: sources, ...(assetSource === undefined ? {} : { cliRuntimePackages: [assetSource] }), }); assertTemplateDependencies(template, templateManifest, sources); process.stdout.write(`golden-path ${template}: ./scaffold.sh ${project}\n`); await runCommand("scaffold", "./scaffold.sh", [project], directory); + // Later installs must retain the same local transitive mappings as the scaffold install. + await cp(path.join(directory, "scaffold-pnpmfile.cjs"), path.join(target, ".pnpmfile.cjs")); } async function freePort(): Promise { @@ -1190,6 +1218,30 @@ export async function verifyPackedMutationControl( }, ); await cp(path.join(REPO_ROOT, "patches"), path.join(root, "patches"), { recursive: true }); + // Prepack stages the terrain kits from the sibling terrain package: the five kit sources and + // the credits record. The 34 MB of textures beside credits.json are not read. + await cp( + path.join(REPO_ROOT, "packages/terrain/starter"), + path.join(root, "packages/terrain/starter"), + { recursive: true }, + ); + await mkdir(path.join(root, "packages/terrain/dist"), { recursive: true }); + await cp( + path.join(REPO_ROOT, "packages/terrain/dist/kit.js"), + path.join(root, "packages/terrain/dist/kit.js"), + ); + await cp( + path.join(REPO_ROOT, "packages/terrain/package.json"), + path.join(root, "packages/terrain/package.json"), + ); + await mkdir(path.join(root, "packages/terrain/starter-assets"), { recursive: true }); + await cp( + path.join( + await (await import("../packages/terrain/dist/kit.js")).starterAssetsDir(), + "credits.json", + ), + path.join(root, "packages/terrain/starter-assets/credits.json"), + ); await materializeTemporaryPackageVersions(mutatedPackage); const templateManifestPath = path.join(mutatedPackage, "templates", template, PACKAGE_FILE); const templateManifest = JSON.parse(await readFile(templateManifestPath, "utf8")) as Record< diff --git a/scripts/verify-registry-install.ts b/scripts/verify-registry-install.ts index bd6fbf9092..cf415d73a9 100644 --- a/scripts/verify-registry-install.ts +++ b/scripts/verify-registry-install.ts @@ -253,6 +253,8 @@ const MCP_SAFE_TOOLS: Readonly> = { "threenative-blender": "blender_status", "threenative-engine": "engine_search_capabilities", "threenative-sculpt": "sculpt_grimoire", + // A patch with no commands round-trips a recipe and never cooks. + "threenative-terrain": "terrain_patch", }; function safeOperation( @@ -271,7 +273,12 @@ function safeOperation( ? { topic: "glossary/3d_vocabulary" } : name === "engine_search_capabilities" ? { scope: "mechanic", situation: "enemy walks around a wall" } - : {}; + : name === "terrain_patch" + ? { + commands: [], + recipe: { config: { resolution: 17, seed: 1, size: 64 }, layers: [], version: 1 }, + } + : {}; return { arguments: argumentsValue, name }; } @@ -1182,6 +1189,10 @@ function mcpStep(project: string, runner: McpRunner): string { results.push( `${name}: initialize, tools/list, search and detail ok (${hit.symbol}; ${hits.length} hit(s))`, ); + } else if (name === "threenative-terrain") { + if (record === undefined || !Array.isArray(record.layers) || record.version !== 1) + throw new Error("terrain_patch returned no terrain recipe document."); + results.push(`${name}: initialize, tools/list and terrain_patch ok`); } else if (name === "threenative-blender") { if (record === undefined || record.available !== true) { const detail = isNonEmptyString(record?.detail) diff --git a/vitest.config.ts b/vitest.config.ts index 34efbf6d8e..cbed784926 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,10 +6,11 @@ export default defineConfig({ environment: "node", include: [ "scripts/**/*.spec.ts", + "examples/strata-terrain-preview/__tests__/**/*.spec.ts", "packages/**/__tests__/**/*.spec.ts", "packages/**/__tests__/**/*.spec.tsx", ], - exclude: ["**/node_modules/**", "**/dist/**", "examples/**"], + exclude: ["**/node_modules/**", "**/dist/**"], reporters: ["default"], coverage: { provider: "v8",